Commit 94c135d3 authored by Jacob Vosmaer's avatar Jacob Vosmaer

Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into gitlab-workhorse-0.7.2

parents 622b2023 a918e8bf
......@@ -2,7 +2,6 @@ image: "ruby:2.1"
services:
- mysql:latest
- postgres:latest
- redis:latest
cache:
......@@ -35,123 +34,79 @@ spec:feature:
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
tags:
- ruby
- mysql
spec:api:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
tags:
- ruby
- mysql
spec:models:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
tags:
- ruby
- mysql
spec:lib:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
tags:
- ruby
- mysql
spec:services:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
tags:
- ruby
- mysql
spec:other:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
tags:
- ruby
- mysql
spinach:project:half:
stage: test
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
tags:
- ruby
- mysql
spinach:project:rest:
stage: test
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
tags:
- ruby
- mysql
spinach:other:
stage: test
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
tags:
- ruby
- mysql
teaspoon:
stage: test
script:
- RAILS_ENV=test bundle exec teaspoon
tags:
- ruby
- mysql
rubocop:
stage: test
script:
- bundle exec rubocop
tags:
- ruby
- mysql
scss-lint:
stage: test
script:
- bundle exec rake scss_lint
tags:
- ruby
brakeman:
stage: test
script:
- bundle exec rake brakeman
tags:
- ruby
- mysql
flog:
stage: test
script:
- bundle exec rake flog
tags:
- ruby
- mysql
flay:
stage: test
script:
- bundle exec rake flay
tags:
- ruby
- mysql
bundler:audit:
stage: test
......@@ -159,9 +114,6 @@ bundler:audit:
- master
script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
tags:
- ruby
- mysql
# Ruby 2.2 jobs
......@@ -177,9 +129,6 @@ spec:feature:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:api:ruby22:
stage: test
......@@ -192,9 +141,6 @@ spec:api:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:models:ruby22:
stage: test
......@@ -207,9 +153,6 @@ spec:models:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:lib:ruby22:
stage: test
......@@ -222,9 +165,6 @@ spec:lib:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:services:ruby22:
stage: test
......@@ -237,9 +177,6 @@ spec:services:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:other:ruby22:
stage: test
......@@ -252,9 +189,6 @@ spec:other:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spinach:project:half:ruby22:
stage: test
......@@ -268,9 +202,6 @@ spinach:project:half:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spinach:project:rest:ruby22:
stage: test
......@@ -284,9 +215,6 @@ spinach:project:rest:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spinach:other:ruby22:
stage: test
......@@ -300,10 +228,6 @@ spinach:other:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
notify:slack:
stage: notifications
......
......@@ -2,18 +2,23 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu)
- Improved Markdown rendering performance !3389 (Yorick Peterse)
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
- Expose project badges in project settings
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
- Expose label description in API (Mariusz Jachimowicz)
- Allow back dating on issues when created through the API
- Fix Error 500 after renaming a project path (Stan Hu)
- Fix avatar stretching by providing a cropping feature
- Allow SAML to handle external users based on user's information !3530
- Add endpoints to archive or unarchive a project !3372
- Add links to CI setup documentation from project settings and builds pages
- Handle nil descriptions in Slack issue messages (Stan Hu)
- Add default scope to projects to exclude projects pending deletion
- Ensure empty recipients are rejected in BuildsEmailService
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
......@@ -21,9 +26,12 @@ v 8.7.0 (unreleased)
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- Improved UX of the navigation sidebar
- Fix admin/projects when using visibility levels on search (PotHix)
- Build status notifications
- API: Expose user location (Robert Schilling)
v 8.6.5 (unreleased)
- Only update repository language if it is not set to improve performance
- Check permissions when user attempts to import members from another project
v 8.6.4
......
......@@ -290,7 +290,7 @@ group :development, :test do
gem 'rubocop', '~> 0.38.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.10.0', require: false
gem 'simplecov', '~> 0.11.0', require: false
gem 'flog', require: false
gem 'flay', require: false
gem 'bundler-audit', require: false
......
......@@ -136,10 +136,9 @@ GEM
colorize (0.7.7)
concurrent-ruby (1.0.0)
connection_pool (2.2.0)
coveralls (0.8.9)
coveralls (0.8.13)
json (~> 1.8)
rest-client (>= 1.6.8, < 2)
simplecov (~> 0.10.0)
simplecov (~> 0.11.0)
term-ansicolor (~> 1.3)
thor (~> 0.19.1)
tins (~> 1.6.0)
......@@ -176,8 +175,6 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
domain_name (0.5.25)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (2.2.2)
railties (>= 3.2)
dropzonejs-rails (0.7.2)
......@@ -421,8 +418,6 @@ GEM
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
htmlentities (4.3.4)
http-cookie (1.0.2)
domain_name (~> 0.5)
http_parser.rb (0.5.3)
httparty (0.13.7)
json (~> 1.8)
......@@ -480,7 +475,6 @@ GEM
nested_form (0.3.2)
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
newrelic_rpm (3.14.1.311)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
......@@ -657,10 +651,6 @@ GEM
listen (~> 3.0)
responders (2.1.1)
railties (>= 4.2.0, < 5.1)
rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rinku (1.7.3)
rotp (2.1.1)
rouge (1.10.1)
......@@ -754,7 +744,7 @@ GEM
rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0)
simple_oauth (0.1.9)
simplecov (0.10.0)
simplecov (0.11.2)
docile (~> 1.1.0)
json (~> 1.8)
simplecov-html (~> 0.10.0)
......@@ -845,7 +835,7 @@ GEM
underscore-rails (1.8.3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.1)
unf_ext (0.0.7.2)
unicode-display_width (1.0.2)
unicorn (4.9.0)
kgio (~> 2.6)
......@@ -1032,7 +1022,7 @@ DEPENDENCIES
shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0)
simplecov (~> 0.10.0)
simplecov (~> 0.11.0)
sinatra (~> 1.4.4)
six (~> 0.2.0)
slack-notifier (~> 1.2.0)
......
......@@ -177,10 +177,11 @@ class GitLabDropdown
selector = ".dropdown-page-one .dropdown-content a"
@dropdown.on "click", selector, (e) ->
selected = self.rowClicked $(@)
$el = $(@)
selected = self.rowClicked $el
if self.options.clicked
self.options.clicked(selected)
self.options.clicked(selected, $el, e)
# Finds an element inside wrapper element
getElement: (selector) ->
......@@ -360,6 +361,8 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel
else
selectedObject
else
if !value?
field.remove()
......@@ -375,7 +378,7 @@ class GitLabDropdown
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
if value?
if !field.length
if !field.length and fieldName
# Create hidden input for form
input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
if @options.inputId?
......@@ -394,7 +397,7 @@ class GitLabDropdown
selector = ".dropdown-page-one #{selector}"
# simulate a click on the first link
$(selector).trigger "click"
$(selector, @dropdown).trigger "click"
addArrowKeyEvent: ->
ARROW_KEY_CODES = [38, 40]
......
......@@ -26,6 +26,20 @@
$(".selected_issue").bind "change", Issues.checkChanged
# Update state filters if present in page
updateStateFilters: ->
stateFilters = $('.issues-state-filters')
newParams = {}
paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or ''
if stateFilters.length
stateFilters.find('a').each ->
initialUrl = $(this).attr 'href'
$(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl)
# Make sure we trigger ajax request only after user stop typing
initSearch: ->
@timer = null
......@@ -54,6 +68,7 @@
# Change url so if user reload a page - search results are saved
history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issues.reload()
Issues.updateStateFilters()
dataType: "json"
checkChanged: ->
......
((w) ->
w.gl ?= {}
w.gl.utils ?= {}
w.gl.utils.getUrlParameter = (sParam) ->
sPageURL = decodeURIComponent(window.location.search.substring(1))
sURLVariables = sPageURL.split('&')
sParameterName = undefined
i = 0
while i < sURLVariables.length
sParameterName = sURLVariables[i].split('=')
if sParameterName[0] is sParam
return if sParameterName[1] is undefined then true else sParameterName[1]
i++
# #
# @param {Object} params - url keys and value to merge
# @param {String} url
# #
w.gl.utils.mergeUrlParams = (params, url) ->
newUrl = decodeURIComponent(url)
for paramName, paramValue of params
pattern = new RegExp "\\b(#{paramName}=).*?(&|$)"
if url.search(pattern) >= 0
newUrl = newUrl.replace pattern, "$1#{paramValue}$2"
else
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
newUrl
) window
......@@ -15,6 +15,8 @@ class @MergeRequestWidget
@pollCIStatus()
notifyPermissions()
setOpts: (@opts) ->
mergeInProgress: (deleteSourceBranch = false)->
$.ajax
type: 'GET'
......@@ -48,7 +50,7 @@ class @MergeRequestWidget
@getCIStatus(true)
@readyForCICheck = false
), 5000
), 10000
getCIStatus: (showNotification) ->
_this = @
......@@ -61,6 +63,10 @@ class @MergeRequestWidget
@firstCICheck = false
@opts.ci_status = data.status
if @opts.ci_status is ''
@opts.ci_status = data.status
return
if data.status isnt @opts.ci_status
@showCIStatus data.status
if data.coverage
......
......@@ -62,6 +62,8 @@ class @SearchAutocomplete
search:
fields: ['text']
data: @getData.bind(@)
selectable: true
clicked: @onClick.bind(@)
getData: (term, callback) ->
_this = @
......@@ -102,6 +104,8 @@ class @SearchAutocomplete
lastCategory = suggestion.category
data.push
id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}"
category: suggestion.category
text: suggestion.label
url: suggestion.url
......@@ -133,12 +137,19 @@ class @SearchAutocomplete
}
bindEvents: ->
$(document).on 'click', @onDocumentClick
@searchInput.on 'keydown', @onSearchInputKeyDown
@searchInput.on 'keyup', @onSearchInputKeyUp
@searchInput.on 'click', @onSearchInputClick
@searchInput.on 'focus', @onSearchInputFocus
@searchInput.on 'blur', @onSearchInputBlur
@clearInput.on 'click', @onRemoveLocationClick
@clearInput.on 'click', @onClearInputClick
onDocumentClick: (e) =>
# If clicking outside the search box
# And search input is not focused
# And we are not clicking inside a suggestion
if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length
@onSearchInputBlur()
enableAutocomplete: ->
# No need to enable anything if user is not logged in
......@@ -181,6 +192,8 @@ class @SearchAutocomplete
# We should display the menu only when input is not empty
@enableAutocomplete()
@wrap.toggleClass 'has-value', !!e.target.value
# Avoid falsy value to be returned
return
......@@ -189,27 +202,20 @@ class @SearchAutocomplete
e.stopImmediatePropagation()
onSearchInputFocus: =>
@isFocused = true
@wrap.addClass('search-active')
onRemoveLocationClick: (e) =>
onClearInputClick: (e) =>
e.preventDefault()
@removeLocationBadge()
@searchInput.val('').focus()
@skipBlurEvent = true
onSearchInputBlur: (e) =>
@skipBlurEvent = false
# We should wait to make sure we are not clearing the input instead
setTimeout( =>
return if @skipBlurEvent
@isFocused = false
@wrap.removeClass('search-active')
@wrap.removeClass('search-active')
# If input is blank then restore state
if @searchInput.val() is ''
@restoreOriginalState()
, 150)
# If input is blank then restore state
if @searchInput.val() is ''
@restoreOriginalState()
addLocationBadge: (item) ->
category = if item.category? then "#{item.category}: " else ''
......@@ -268,3 +274,23 @@ class @SearchAutocomplete
<li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li>
</ul>"
@dropdownContent.html(html)
onClick: (item, $el, e) ->
if location.pathname.indexOf(item.url) isnt -1
e.preventDefault()
if not @badgePresent
if item.category is 'Projects'
@projectInputEl.val(item.id)
@addLocationBadge(
value: 'This project'
)
if item.category is 'Groups'
@groupInputEl.val(item.id)
@addLocationBadge(
value: 'This group'
)
$el.removeClass('is-active')
@disableAutocomplete()
@searchInput.val('').focus()
......@@ -20,6 +20,7 @@
margin: 0;
text-align: left;
padding: 10px $gl-padding;
word-wrap: break-word;
.file-actions {
float: right;
......
......@@ -84,6 +84,7 @@
.md-preview-holder {
min-height: 167px;
padding: 10px 0;
overflow-x: auto;
}
.markdown-area {
......
......@@ -104,9 +104,9 @@ $orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #e75e40;
$orange-dark: #ce5237;
$red-light: #f06559;
$red-normal: #e52c5a;
$red-dark: #d22852;
$red-light: #e52c5a;
$red-normal: #d22852;
$red-dark: darken($red-normal, 5%);
$border-white-light: #f1f2f4;
$border-white-normal: #d6dae2;
......@@ -128,9 +128,9 @@ $border-orange-light: #fc6d26;
$border-orange-normal: #ce5237;
$border-orange-dark: #c14e35;
$border-red-light: #f24f41;
$border-red-normal: #d22852;
$border-red-dark: #ca264f;
$border-red-light: #d22852;
$border-red-normal: #ca264f;
$border-red-dark: darken($border-red-normal, 5%);
$help-well-bg: #fafafa;
$help-well-border: #e5e5e5;
......@@ -201,14 +201,14 @@ $award-emoji-new-btn-icon-color: #dcdcdc;
/*
* Search Box
*/
$search-input-border-color: $dropdown-input-focus-border;
$search-input-border-color: rgba(#4688f1, .8);
$search-input-focus-shadow-color: $dropdown-input-focus-shadow;
$search-input-width: $dropdown-width;
$search-input-width: 244px;
$location-badge-color: #aaa;
$location-badge-bg: $gray-normal;
$location-badge-active-bg: #4f91f8;
$location-icon-color: #e7e9ed;
$location-active-color: $gl-text-color;
$location-active-bg: $search-input-border-color;
$location-icon-active-color: #807e7e;
/*
* Notes
......
......@@ -33,8 +33,12 @@
.description {
margin-top: 6px;
p:last-child {
margin-bottom: 0;
p {
overflow-x: auto;
&:last-child {
margin-bottom: 0;
}
}
}
}
......@@ -59,10 +59,15 @@
border-collapse: separate;
margin: 0;
padding: 0;
.line_holder td {
line-height: $code_line_height;
font-size: $code_font_size;
}
td {
white-space: nowrap;
}
}
tr.line_holder.parallel {
......
......@@ -82,7 +82,7 @@ ul.notes {
// On diffs code should wrap nicely and not overflow
pre {
code {
white-space: pre-wrap;
white-space: pre;
}
}
......
......@@ -315,7 +315,7 @@ pre.light-well {
}
.git-empty {
margin: 0 7px;
margin: 0 7px 7px;
h5 {
color: #5c5d5e;
......
......@@ -135,25 +135,25 @@
.location-badge {
@include transition(all .15s);
background-color: $location-active-bg;
background-color: $location-badge-active-bg;
color: $white-light;
}
.search-input-wrap {
i {
color: $location-active-color;
color: $location-icon-active-color;
}
}
}
&.has-location-badge {
.search-icon {
display: none;
}
&.has-value {
.search-icon {
display: none;
}
.clear-icon {
cursor: pointer;
display: block;
}
.clear-icon {
cursor: pointer;
display: block;
}
}
......
......@@ -5,7 +5,7 @@ class Admin::ProjectsController < Admin::ApplicationController
def index
@projects = Project.all
@projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
@projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.non_archived unless params[:with_archived].present?
......
......@@ -55,7 +55,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
else
saml_user = Gitlab::Saml::User.new(oauth)
saml_user.save
saml_user.save if saml_user.changed?
@user = saml_user.gl_user
continue_login_process
......
......@@ -68,7 +68,9 @@ class Projects::ApplicationController < ApplicationController
end
def require_non_empty_project
redirect_to namespace_project_path(@project.namespace, @project) if @project.empty_repo?
# Be sure to return status code 303 to avoid a double DELETE:
# http://api.rubyonrails.org/classes/ActionController/Redirecting.html
redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo?
end
def require_branch_head
......
class Projects::BadgesController < Projects::ApplicationController
before_action :no_cache_headers
layout 'project_settings'
before_action :authorize_admin_project!, only: [:index]
before_action :no_cache_headers, except: [:index]
def index
@ref = params[:ref] || @project.default_branch || 'master'
@build_badge = Gitlab::Badge::Build.new(@project, @ref)
end
def build
badge = Gitlab::Badge::Build.new(project, params[:ref])
......
......@@ -48,7 +48,7 @@ class Projects::BranchesController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to namespace_project_branches_path(@project.namespace,
@project)
@project), status: 303
end
format.js { render status: status[:return_code] }
end
......
......@@ -24,6 +24,8 @@ class Projects::RefsController < Projects::ApplicationController
namespace_project_find_file_path(@project.namespace, @project, @id)
when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id)
when "badges"
namespace_project_badges_path(@project.namespace, @project, ref: @id)
else
namespace_project_commits_path(@project.namespace, @project, @id)
end
......
......@@ -88,6 +88,20 @@ class Projects::WikisController < Projects::ApplicationController
)
end
def markdown_preview
text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user)
ext.analyze(text)
render json: {
body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki),
references: {
users: ext.users.map(&:username)
}
}
end
def git_access
end
......
......@@ -50,12 +50,15 @@ class BuildsEmailService < Service
def execute(push_data)
return unless supported_events.include?(push_data[:object_kind])
return unless should_build_be_notified?(push_data)
if should_build_be_notified?(push_data)
recipients = all_recipients(push_data)
if recipients.any?
BuildEmailWorker.perform_async(
push_data[:build_id],
all_recipients(push_data),
push_data,
recipients,
push_data
)
end
end
......@@ -84,7 +87,7 @@ class BuildsEmailService < Service
end
def all_recipients(data)
all_recipients = recipients.split(',')
all_recipients = recipients.split(',').compact.reject(&:blank?)
if add_pusher? && data[:user][:email]
all_recipients << "#{data[:user][:email]}"
......
......@@ -331,6 +331,8 @@ class Repository
# Runs code after a repository has been created.
def after_create
expire_exists_cache
expire_root_ref_cache
expire_emptiness_caches
end
# Runs code just before a repository is deleted.
......
......@@ -55,15 +55,15 @@ class GitPushService < BaseService
end
def update_main_language
# Performance can be bad so for now only check main_language once
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/14937
return if @project.main_language.present?
return unless is_default_branch?
return unless push_to_new_branch? || push_to_existing_branch?
current_language = @project.repository.main_language
unless current_language == @project.main_language
return @project.update_attributes(main_language: current_language)
end
@project.update_attributes(main_language: current_language)
true
end
......
- if controller.controller_path =~ /^groups/
- if controller.controller_path =~ /^groups/ && @group.persisted?
- label = 'This group'
- if controller.controller_path =~ /^projects/
- if controller.controller_path =~ /^projects/ && @project.persisted?
- label = 'This project'
.search.search-form{class: "#{'has-location-badge' if label.present?}"}
......@@ -21,8 +21,8 @@
%a.is-focused.dropdown-menu-empty-link
Loading...
= dropdown_loading
%i.search-icon
%i.clear-icon.js-clear-input
%i.search-icon
%i.clear-icon.js-clear-input
= hidden_field_tag :group_id, @group.try(:id)
= hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
......
......@@ -51,8 +51,13 @@
= icon('code fw')
%span
Variables
= nav_link path: 'triggers#index' do
= nav_link(controller: :triggers) do
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
= icon('retweet fw')
%span
Triggers
= nav_link(controller: :badges) do
= link_to namespace_project_badges_path(@project.namespace, @project), title: 'Badges' do
= icon('star-half-empty fw')
%span
Badges
......@@ -5,10 +5,14 @@
- content_for :scripts_body_top do
- project = @target_project || @project
- if @project_wiki
- markdown_preview_path = namespace_project_wikis_markdown_preview_path(project.namespace, project)
- else
- markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project)
- if current_user
:javascript
window.project_uploads_path = "#{namespace_project_uploads_path project.namespace,project}";
window.markdown_preview_path = "#{markdown_preview_namespace_project_path(project.namespace, project)}";
window.markdown_preview_path = "#{markdown_preview_path}";
- content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user
......
- page_title 'Badges'
- badges_path = namespace_project_badges_path(@project.namespace, @project)
- header_title project_title(@project, 'Badges', badges_path)
.prepend-top-10
.panel.panel-default
.panel-heading
%b Builds badge &middot;
= @build_badge.to_html
.pull-right
= render 'shared/ref_switcher', destination: 'badges'
.panel-body
.row
.col-md-2.text-center
Markdown
.col-md-10.code.js-syntax-highlight
= highlight('.md', @build_badge.to_markdown)
.row
%hr
.row
.col-md-2.text-center
HTML
.col-md-10.code.js-syntax-highlight
= highlight('.html', @build_badge.to_html)
......@@ -22,4 +22,6 @@
if(typeof merge_request_widget === 'undefined') {
merge_request_widget = new MergeRequestWidget(opts);
} else {
merge_request_widget.setOpts(opts);
}
......@@ -18,7 +18,7 @@
= access
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil-square-o')
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete' do
= icon('trash-o')
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
......
......@@ -345,6 +345,8 @@ production: &base
#
# - { name: 'saml',
# label: 'Our SAML Provider',
# groups_attribute: 'Groups',
# external_groups: ['Contractors', 'Freelancers'],
# args: {
# assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
# idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
......@@ -352,6 +354,7 @@ production: &base
# issuer: 'https://gitlab.example.com',
# name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
# } }
#
# - { name: 'crowd',
# args: {
# crowd_server_url: 'CROWD SERVER URL',
......
......@@ -575,6 +575,7 @@ Rails.application.routes.draw do
# Order matters to give priority to these matches
get '/wikis/git_access', to: 'wikis#git_access'
get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages'
post '/wikis/markdown_preview', to:'wikis#markdown_preview'
post '/wikis', to: 'wikis#create'
get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID
......@@ -749,10 +750,11 @@ Rails.application.routes.draw do
end
resources :runner_projects, only: [:create, :destroy]
resources :badges, only: [], path: 'badges/*ref',
constraints: { ref: Gitlab::Regex.git_reference_regex } do
resources :badges, only: [:index] do
collection do
get :build, constraints: { format: /svg/ }
scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do
get :build, constraints: { format: /svg/ }
end
end
end
end
......
......@@ -7,8 +7,24 @@ Returns a list of project milestones.
```
GET /projects/:id/milestones
GET /projects/:id/milestones?iid=42
GET /projects/:id/milestones?state=active
GET /projects/:id/milestones?state=closed
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `iid` | integer | optional | Return only the milestone having the given `iid` |
| `state` | string | optional | Return only `active` or `closed` milestones` |
```bash
curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/milestones
```
Example Response:
```json
[
{
......@@ -25,10 +41,6 @@ GET /projects/:id/milestones?iid=42
]
```
Parameters:
- `id` (required) - The ID of a project
- `iid` (optional) - Return the milestone having the given `iid`
## Get single milestone
......
......@@ -69,6 +69,7 @@ GET /users
"state": "blocked",
"created_at": "2012-05-23T08:01:01Z",
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
......@@ -126,6 +127,7 @@ Parameters:
"created_at": "2012-05-23T08:00:58Z",
"is_admin": false,
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
......@@ -154,6 +156,7 @@ Parameters:
"confirmed_at": "2012-05-23T08:00:58Z",
"last_sign_in_at": "2015-03-23T08:00:58Z",
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
......@@ -191,6 +194,7 @@ Parameters:
- `extern_uid` (optional) - External UID
- `provider` (optional) - External provider name
- `bio` (optional) - User's biography
- `location` (optional) - User's location
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
- `confirm` (optional) - Require confirmation - true (default) or false
......@@ -218,6 +222,7 @@ Parameters:
- `extern_uid` - External UID
- `provider` - External provider name
- `bio` - User's biography
- `location` (optional) - User's location
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
- `external` (optional) - Flags the user as external - true or false(default)
......@@ -260,6 +265,7 @@ GET /user
"state": "active",
"created_at": "2012-05-23T08:00:58Z",
"bio": null,
"location": null,
"skype": "",
"linkedin": "",
"twitter": "",
......
......@@ -38,7 +38,7 @@ services:
- postgres
before_script:
- bundle_install
- bundle install
stages:
- build
......
......@@ -4,6 +4,7 @@
- [CI setup](ci_setup.md) for testing GitLab
- [Gotchas](gotchas.md) to avoid
- [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
- [Rake tasks](rake_tasks.md) for development
- [Shell commands](shell_commands.md) in the GitLab codebase
......
# Instrumenting Ruby Code
GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby
code. This can be used to measure the time spent in a specific part of a larger
chunk of code. The resulting data is written to a separate series.
To start measuring a block of Ruby code you should use
`Gitlab::Metrics.measure` and give it a name for the series to store the data
in:
```ruby
Gitlab::Metrics.measure(:user_logins) do
...
end
```
The first argument of this method is the series name and should be plural. This
name will be prefixed with `rails_` or `sidekiq_` depending on whether the code
was run in the Rails application or one of the Sidekiq workers. In the
above example the final series names would be as follows:
- rails_user_logins
- sidekiq_user_logins
Series names should be plural as this keeps the naming style in line with the
other series names.
By default metrics measured using a block contain a single value, "duration",
which contains the number of milliseconds it took to execute the block. Custom
values can be added by passing a Hash as the 2nd argument. Custom tags can be
added by passing a Hash as the 3rd argument. A simple example is as follows:
```ruby
Gitlab::Metrics.measure(:example_series, { number: 10 }, { class: self.class.to_s }) do
...
end
```
......@@ -131,8 +131,75 @@ On the sign in page there should now be a SAML button below the regular sign in
Click the icon to begin the authentication process. If everything goes well the user
will be returned to GitLab and will be signed in.
## External Groups
>**Note:**
This setting is only available on GitLab 8.7 and above.
SAML login includes support for external groups. You can define in the SAML
settings which groups, to which your users belong in your IdP, you wish to be
marked as [external](../permissions/permissions.md).
### Requirements
First you need to tell GitLab where to look for group information. For this you
need to make sure that your IdP server sends a specific `AttributeStament` along
with the regular SAML response. Here is an example:
```xml
<saml:AttributeStatement>
<saml:Attribute Name="Groups">
<saml:AttributeValue xsi:type="xs:string">SecurityGroup</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">Developers</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">Designers</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
```
The name of the attribute can be anything you like, but it must contain the groups
to which a user belongs. In order to tell GitLab where to find these groups, you need
to add a `groups_attribute:` element to your SAML settings. You will also need to
tell GitLab which groups are external via the `external_groups:` element:
```yaml
{ name: 'saml',
label: 'Our SAML Provider',
groups_attribute: 'Groups',
external_groups: ['Freelancers', 'Interns'],
args: {
assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
idp_sso_target_url: 'https://login.example.com/idp',
issuer: 'https://gitlab.example.com',
name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
} }
```
## Customization
### `auto_sign_in_with_provider`
You can add this setting to your GitLab configuration to automatically redirect you
to your SAML server for authentication, thus removing the need to click a button
before actually signing in.
For omnibus package:
```ruby
gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'saml'
```
For installations from source:
```yaml
omniauth:
auto_sign_in_with_provider: saml
```
Please keep in mind that every sign in attempt will be redirected to the SAML server,
so you will not be able to sign in using local credentials. Make sure that at least one
of the SAML users has admin permissions.
### `attribute_statements`
>**Note:**
......@@ -205,6 +272,10 @@ To bypass this you can add `skip_before_action :verify_authenticity_token` to th
where it can then be seen in the usual logs, or as a flash message in the login
screen.
That file is located at `/opt/gitlab/embedded/service/gitlab-rails/app/controllers`
for Omnibus installations and by default on `/home/git/gitlab/app/controllers` for
installations from source.
### Invalid audience
This error means that the IdP doesn't recognize GitLab as a valid sender and
......
......@@ -52,10 +52,11 @@ documentation](../workflow/add-user/add-user.md).
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Force push to protected branches | | | | | |
| Remove protected branches | | | | | |
| Force push to protected branches [^2] | | | | | |
| Remove protected branches [^2] | | | | | |
[^1]: If **Allow guest to access builds** is enabled in CI settings
[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
## Group
......
......@@ -64,7 +64,7 @@ module API
authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch])
not_found!("Branch does not exist") unless @branch
not_found!("Branch") unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch
......
......@@ -15,7 +15,7 @@ module API
class User < UserBasic
expose :created_at
expose :is_admin?, as: :is_admin
expose :bio, :skype, :linkedin, :twitter, :website_url
expose :bio, :location, :skype, :linkedin, :twitter, :website_url
end
class Identity < Grape::Entity
......
......@@ -3,17 +3,33 @@ module API
class Milestones < Grape::API
before { authenticate! }
helpers do
def filter_milestones_state(milestones, state)
case state
when 'active' then milestones.active
when 'closed' then milestones.closed
else milestones
end
end
end
resource :projects do
# Get a list of project milestones
#
# Parameters:
# id (required) - The ID of a project
# id (required) - The ID of a project
# state (optional) - Return "active" or "closed" milestones
# Example Request:
# GET /projects/:id/milestones
# GET /projects/:id/milestones?state=active
# GET /projects/:id/milestones?state=closed
get ":id/milestones" do
authorize! :read_milestone, user_project
present paginate(user_project.milestones), with: Entities::Milestone
milestones = user_project.milestones
milestones = filter_milestones_state(milestones, params[:state])
present paginate(milestones), with: Entities::Milestone
end
# Get a single project milestone
......
......@@ -58,6 +58,7 @@ module API
# extern_uid - External authentication provider UID
# provider - External provider
# bio - Bio
# location - Location of the user
# admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false
# confirm - Require user confirmation - true (default) or false
......@@ -67,7 +68,7 @@ module API
post do
authenticated_as_admin!
required_attributes! [:email, :password, :name, :username]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin, :confirm, :external]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external]
admin = attrs.delete(:admin)
confirm = !(attrs.delete(:confirm) =~ (/(false|f|no|0)$/i))
user = User.build_user(attrs)
......@@ -106,6 +107,7 @@ module API
# website_url - Website url
# projects_limit - Limit projects each user can create
# bio - Bio
# location - Location of the user
# admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false
# external - Flags the user as external - true or false(default)
......@@ -114,7 +116,7 @@ module API
put ":id" do
authenticated_as_admin!
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin, :external]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :external]
user = User.find(params[:id])
not_found!('User') unless user
......
......@@ -119,7 +119,7 @@ module Banzai
elsif element_node?(node)
yield_valid_link(node) do |link, text|
if ref_pattern && link =~ /\A#{ref_pattern}/
if ref_pattern && link =~ /\A#{ref_pattern}\z/
replace_link_node_with_href(node, link) do
object_link_filter(link, ref_pattern, link_text: text)
end
......
......@@ -118,7 +118,7 @@ module Banzai
end
if path
content_tag(:img, nil, src: path)
content_tag(:img, nil, src: path, class: 'gfm')
end
end
......@@ -144,12 +144,18 @@ module Banzai
# if it is not.
def process_page_link_tag(parts)
if parts.size == 1
url = parts[0].strip
reference = parts[0].strip
else
name, url = *parts.compact.map(&:strip)
name, reference = *parts.compact.map(&:strip)
end
content_tag(:a, name || url, href: url)
if url?(reference)
href = reference
else
href = ::File.join(project_wiki_base_path, reference)
end
content_tag(:a, name || reference, href: href, class: 'gfm')
end
def project_wiki
......
require 'uri'
module Banzai
module Filter
# HTML filter that "fixes" relative links to files in a repository.
#
# Context options:
# :project_wiki
class WikiLinkFilter < HTML::Pipeline::Filter
def call
return doc unless project_wiki?
doc.search('a:not(.gfm)').each do |el|
process_link_attr el.attribute('href')
end
doc
end
protected
def project_wiki?
!context[:project_wiki].nil?
end
def process_link_attr(html_attr)
return if html_attr.blank? || file_reference?(html_attr)
uri = URI(html_attr.value)
if uri.relative? && uri.path.present?
html_attr.value = rebuild_wiki_uri(uri).to_s
end
rescue URI::Error
# noop
end
def rebuild_wiki_uri(uri)
uri.path = ::File.join(project_wiki_base_path, uri.path)
uri
end
def file_reference?(html_attr)
!File.extname(html_attr.value).blank?
end
def project_wiki
context[:project_wiki]
end
def project_wiki_base_path
project_wiki && project_wiki.wiki_base_path
end
end
end
end
......@@ -2,8 +2,10 @@ module Banzai
module Pipeline
class WikiPipeline < FullPipeline
def self.filters
@filters ||= super.insert_after(Filter::TableOfContentsFilter,
Filter::GollumTagsFilter)
@filters ||= begin
super.insert_after(Filter::TableOfContentsFilter, Filter::GollumTagsFilter)
.insert_before(Filter::TaskListFilter, Filter::WikiLinkFilter)
end
end
end
end
......
......@@ -4,14 +4,15 @@ module Gitlab
# Build badge
#
class Build
include Gitlab::Application.routes.url_helpers
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::UrlHelper
def initialize(project, ref)
@project, @ref = project, ref
@image = ::Ci::ImageForBuildService.new.execute(project, ref: ref)
end
def to_s
@image[:name].sub(/\.svg$/, '')
end
def type
'image/svg+xml'
end
......@@ -19,6 +20,27 @@ module Gitlab
def data
File.read(@image[:path])
end
def to_s
@image[:name].sub(/\.svg$/, '')
end
def to_html
link_to(image_tag(image_url, alt: 'build status'), link_url)
end
def to_markdown
"[![build status](#{image_url})](#{link_url})"
end
def image_url
build_namespace_project_badges_url(@project.namespace,
@project, @ref, format: :svg)
end
def link_url
namespace_project_commits_url(@project.namespace, @project, id: @ref)
end
end
end
end
......@@ -33,7 +33,10 @@ module Gitlab
def allowed?
if ldap_user
return true unless ldap_config.active_directory
unless ldap_config.active_directory
user.activate if user.ldap_blocked?
return true
end
# Block user in GitLab if he/she was blocked in AD
if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
......
......@@ -70,6 +70,32 @@ module Gitlab
value.to_s.gsub('=', '\\=')
end
# Measures the execution time of a block.
#
# Example:
#
# Gitlab::Metrics.measure(:find_by_username_timings) do
# User.find_by_username(some_username)
# end
#
# series - The name of the series to store the data in.
# values - A Hash containing extra values to add to the metric.
# tags - A Hash containing extra tags to add to the metric.
#
# Returns the value yielded by the supplied block.
def self.measure(series, values = {}, tags = {})
return yield unless Transaction.current
start = Time.now.to_f
retval = yield
duration = (Time.now.to_f - start) * 1000.0
values = values.merge(duration: duration)
Transaction.current.add_metric(series, values, tags)
retval
end
# When enabled this should be set before being used as the usual pattern
# "@foo ||= bar" is _not_ thread-safe.
if enabled?
......
module Gitlab
module Saml
class AuthHash < Gitlab::OAuth::AuthHash
def groups
get_raw(Gitlab::Saml::Config.groups)
end
private
def get_raw(key)
# Needs to call `all` because of https://git.io/vVo4u
# otherwise just the first value is returned
auth_hash.extra[:raw_info].all[key]
end
end
end
end
module Gitlab
module Saml
class Config
class << self
def options
Gitlab.config.omniauth.providers.find { |provider| provider.name == 'saml' }
end
def groups
options[:groups_attribute]
end
def external_groups
options[:external_groups]
end
end
end
end
end
......@@ -18,7 +18,7 @@ module Gitlab
@user ||= find_or_create_ldap_user
end
if auto_link_saml_enabled?
if auto_link_saml_user?
@user ||= find_by_email
end
......@@ -26,6 +26,16 @@ module Gitlab
@user ||= build_new_user
end
if external_users_enabled?
# Check if there is overlap between the user's groups and the external groups
# setting then set user as external or internal.
if (auth_hash.groups & Gitlab::Saml::Config.external_groups).empty?
@user.external = false
else
@user.external = true
end
end
@user
end
......@@ -37,11 +47,23 @@ module Gitlab
end
end
def changed?
gl_user.changed? || gl_user.identities.any?(&:changed?)
end
protected
def auto_link_saml_enabled?
def auto_link_saml_user?
Gitlab.config.omniauth.auto_link_saml_user
end
def external_users_enabled?
!Gitlab::Saml::Config.external_groups.nil?
end
def auth_hash=(auth_hash)
@auth_hash = Gitlab::Saml::AuthHash.new(auth_hash)
end
end
end
end
require 'spec_helper'
describe Admin::ProjectsController do
let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
before do
sign_in(create(:admin))
end
describe 'GET /projects' do
render_views
it 'retrieves the project for the given visibility level' do
get :index, visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]
expect(response.body).to match(project.name)
end
it 'does not retrieve the project' do
get :index, visibility_levels: [Gitlab::VisibilityLevel::INTERNAL]
expect(response.body).to_not match(project.name)
end
end
end
......@@ -93,6 +93,20 @@ describe Projects::BranchesController do
end
end
describe "POST destroy with HTML format" do
render_views
it 'returns 303' do
post :destroy,
format: :html,
id: 'foo/bar/baz',
namespace_id: project.namespace.to_param,
project_id: project.to_param
expect(response.status).to eq(303)
end
end
describe "POST destroy" do
render_views
......
require 'rails_helper'
describe 'Filter issues', feature: true do
let!(:project) { create(:project) }
let!(:user) { create(:user)}
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
before do
project.team << [user, :master]
login_as(user)
end
describe 'Filter issues for assignee from issues#index' do
before do
visit namespace_project_issues_path(project.namespace, project)
find('.js-assignee-search').click
find('.dropdown-menu-user-link', text: user.username).click
sleep 2
end
context 'assignee', js: true do
it 'should update to current user' do
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
it 'should not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
it 'should not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
end
end
describe 'Filter issues for milestone from issues#index' do
before do
visit namespace_project_issues_path(project.namespace, project)
find('.js-milestone-select').click
find('.milestone-filter .dropdown-content a', text: milestone.title).click
sleep 2
end
context 'milestone', js: true do
it 'should update to current milestone' do
expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
end
it 'should not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
end
it 'should not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
end
end
end
describe 'Filter issues for assignee and label from issues#index' do
before do
visit namespace_project_issues_path(project.namespace, project)
find('.js-assignee-search').click
find('.dropdown-menu-user-link', text: user.username).click
sleep 2
find('.js-label-select').click
find('.dropdown-menu-labels .dropdown-content a', text: label.title).click
sleep 2
end
context 'assignee and label', js: true do
it 'should update to current assignee and label' do
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
end
it 'should not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
end
it 'should not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
end
end
end
end
......@@ -39,7 +39,7 @@ describe 'GitLab Markdown', feature: true do
end
def doc(html = @html)
Nokogiri::HTML::DocumentFragment.parse(html)
@doc ||= Nokogiri::HTML::DocumentFragment.parse(html)
end
# Shared behavior that all pipelines should exhibit
......@@ -230,6 +230,7 @@ describe 'GitLab Markdown', feature: true do
file = Gollum::File.new(@project_wiki.wiki)
expect(file).to receive(:path).and_return('images/example.jpg')
expect(@project_wiki).to receive(:find_file).with('images/example.jpg').and_return(file)
allow(@project_wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' }
@html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki })
end
......
require 'spec_helper'
feature 'list of badges' do
include Select2Helper
background do
user = create(:user)
project = create(:project)
project.team << [user, :master]
login_as(user)
visit edit_namespace_project_path(project.namespace, project)
end
scenario 'user displays list of badges' do
click_link 'Badges'
expect(page).to have_content 'build status'
expect(page).to have_content 'Markdown'
expect(page).to have_content 'HTML'
expect(page).to have_css('.highlight', count: 2)
expect(page).to have_xpath("//img[@alt='build status']")
page.within('.highlight', match: :first) do
expect(page).to have_content 'badges/master/build.svg'
end
end
scenario 'user changes current ref on badges list page', js: true do
click_link 'Badges'
select2('improve/awesome', from: '#ref')
expect(page).to have_content 'badges/improve/awesome/build.svg'
end
end
......@@ -70,20 +70,22 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
end
context 'linking internal resources' do
it "the created link's text will be equal to the resource's text" do
it "the created link's text includes the resource's text and wiki base path" do
tag = '[[wiki-slug]]'
doc = filter("See #{tag}", project_wiki: project_wiki)
expected_path = ::File.join(project_wiki.wiki_base_path, 'wiki-slug')
expect(doc.at_css('a').text).to eq 'wiki-slug'
expect(doc.at_css('a')['href']).to eq 'wiki-slug'
expect(doc.at_css('a')['href']).to eq expected_path
end
it "the created link's text will be link-text" do
tag = '[[link-text|wiki-slug]]'
doc = filter("See #{tag}", project_wiki: project_wiki)
expected_path = ::File.join(project_wiki.wiki_base_path, 'wiki-slug')
expect(doc.at_css('a').text).to eq 'link-text'
expect(doc.at_css('a')['href']).to eq 'wiki-slug'
expect(doc.at_css('a')['href']).to eq expected_path
end
end
......
......@@ -95,6 +95,14 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end
it 'does not process links containing issue numbers followed by text' do
href = "#{reference}st"
doc = reference_filter("<a href='#{href}'></a>")
link = doc.css('a').first.attr('href')
expect(link).to eq(href)
end
end
context 'cross-project reference' do
......
......@@ -11,7 +11,7 @@ describe Banzai::Pipeline::WikiPipeline do
Foo
MD
result = described_class.call(markdown, project: spy, project_wiki: double)
result = described_class.call(markdown, project: spy, project_wiki: spy)
aggregate_failures do
expect(result[:output].text).not_to include '[['
......@@ -29,7 +29,7 @@ describe Banzai::Pipeline::WikiPipeline do
Foo
MD
output = described_class.to_html(markdown, project: spy, project_wiki: double)
output = described_class.to_html(markdown, project: spy, project_wiki: spy)
expect(output).to include('[[<em>toc</em>]]')
end
......@@ -42,7 +42,7 @@ describe Banzai::Pipeline::WikiPipeline do
Foo
MD
output = described_class.to_html(markdown, project: spy, project_wiki: double)
output = described_class.to_html(markdown, project: spy, project_wiki: spy)
aggregate_failures do
expect(output).not_to include('<ul>')
......
......@@ -3,13 +3,44 @@ require 'spec_helper'
describe Gitlab::Badge::Build do
let(:project) { create(:project) }
let(:sha) { project.commit.sha }
let(:badge) { described_class.new(project, 'master') }
let(:branch) { 'master' }
let(:badge) { described_class.new(project, branch) }
describe '#type' do
subject { badge.type }
it { is_expected.to eq 'image/svg+xml' }
end
describe '#to_html' do
let(:html) { Nokogiri::HTML.parse(badge.to_html) }
let(:a_href) { html.at('a') }
it 'points to link' do
expect(a_href[:href]).to eq badge.link_url
end
it 'contains clickable image' do
expect(a_href.children.first.name).to eq 'img'
end
end
describe '#to_markdown' do
subject { badge.to_markdown }
it { is_expected.to include badge.image_url }
it { is_expected.to include badge.link_url }
end
describe '#image_url' do
subject { badge.image_url }
it { is_expected.to include "badges/#{branch}/build.svg" }
end
describe '#link_url' do
subject { badge.link_url }
it { is_expected.to include "commits/#{branch}" }
end
context 'build exists' do
let(:ci_commit) { create(:ci_commit, project: project, sha: sha) }
let!(:build) { create(:ci_build, commit: ci_commit) }
......
......@@ -33,7 +33,7 @@ describe Gitlab::LDAP::Access, lib: true do
it { is_expected.to be_falsey }
it 'should block user in GitLab' do
it 'blocks user in GitLab' do
access.allowed?
expect(user).to be_blocked
expect(user).to be_ldap_blocked
......@@ -78,6 +78,31 @@ describe Gitlab::LDAP::Access, lib: true do
end
it { is_expected.to be_truthy }
context 'when user cannot be found' do
before do
allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(nil)
end
it { is_expected.to be_falsey }
it 'blocks user in GitLab' do
access.allowed?
expect(user).to be_blocked
expect(user).to be_ldap_blocked
end
end
context 'when user was previously ldap_blocked' do
before do
user.ldap_block
end
it 'unblocks the user if it exists' do
access.allowed?
expect(user).not_to be_blocked
end
end
end
end
end
......
......@@ -13,7 +13,7 @@ describe Gitlab::Metrics do
end
end
describe '#submit_metrics' do
describe '.submit_metrics' do
it 'prepares and writes the metrics to InfluxDB' do
connection = double(:connection)
pool = double(:pool)
......@@ -26,7 +26,7 @@ describe Gitlab::Metrics do
end
end
describe '#prepare_metrics' do
describe '.prepare_metrics' do
it 'returns a Hash with the keys as Symbols' do
metrics = described_class.
prepare_metrics([{ 'values' => {}, 'tags' => {} }])
......@@ -51,7 +51,7 @@ describe Gitlab::Metrics do
end
end
describe '#escape_value' do
describe '.escape_value' do
it 'escapes an equals sign' do
expect(described_class.escape_value('foo=')).to eq('foo\\=')
end
......@@ -60,4 +60,45 @@ describe Gitlab::Metrics do
expect(described_class.escape_value(10)).to eq('10')
end
end
describe '.measure' do
context 'without a transaction' do
it 'returns the return value of the block' do
val = Gitlab::Metrics.measure(:foo) { 10 }
expect(val).to eq(10)
end
end
context 'with a transaction' do
let(:transaction) { Gitlab::Metrics::Transaction.new }
before do
allow(Gitlab::Metrics::Transaction).to receive(:current).
and_return(transaction)
end
it 'adds a metric to the current transaction' do
expect(transaction).to receive(:add_metric).
with(:foo, { duration: a_kind_of(Numeric) }, { tag: 'value' })
Gitlab::Metrics.measure(:foo, {}, tag: 'value') { 10 }
end
it 'supports adding of custom values' do
values = { duration: a_kind_of(Numeric), number: 10 }
expect(transaction).to receive(:add_metric).
with(:foo, values, { tag: 'value' })
Gitlab::Metrics.measure(:foo, { number: 10 }, tag: 'value') { 10 }
end
it 'returns the return value of the block' do
val = Gitlab::Metrics.measure(:foo) { 10 }
expect(val).to eq(10)
end
end
end
end
......@@ -5,7 +5,7 @@ describe Gitlab::Saml::User, lib: true do
let(:gl_user) { saml_user.gl_user }
let(:uid) { 'my-uid' }
let(:provider) { 'saml' }
let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash) }
let(:auth_hash) { OmniAuth::AuthHash.new(uid: uid, provider: provider, info: info_hash, extra: { raw_info: OneLogin::RubySaml::Attributes.new({ 'groups' => %w(Developers Freelancers Designers) }) }) }
let(:info_hash) do
{
name: 'John',
......@@ -23,10 +23,20 @@ describe Gitlab::Saml::User, lib: true do
allow(Gitlab::LDAP::Config).to receive_messages(messages)
end
def stub_basic_saml_config
allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', args: {} } })
end
def stub_saml_group_config(groups)
allow(Gitlab::Saml::Config).to receive_messages({ options: { name: 'saml', groups_attribute: 'groups', external_groups: groups, args: {} } })
end
before { stub_basic_saml_config }
describe 'account exists on server' do
before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) }
let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') }
context 'and should bind with SAML' do
let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') }
it 'adds the SAML identity to the existing user' do
saml_user.save
expect(gl_user).to be_valid
......@@ -36,6 +46,35 @@ describe Gitlab::Saml::User, lib: true do
expect(identity.provider).to eql 'saml'
end
end
context 'external groups' do
context 'are defined' do
it 'marks the user as external' do
stub_saml_group_config(%w(Freelancers))
saml_user.save
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
end
end
before { stub_saml_group_config(%w(Interns)) }
context 'are defined but the user does not belong there' do
it 'does not mark the user as external' do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user.external).to be_falsey
end
end
context 'user was external, now should not be' do
it 'should make user internal' do
existing_user.update_attribute('external', true)
saml_user.save
expect(gl_user).to be_valid
expect(gl_user.external).to be_falsey
end
end
end
end
describe 'no account exists on server' do
......@@ -68,6 +107,26 @@ describe Gitlab::Saml::User, lib: true do
end
end
context 'external groups' do
context 'are defined' do
it 'marks the user as external' do
stub_saml_group_config(%w(Freelancers))
saml_user.save
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
end
end
context 'are defined but the user does not belong there' do
it 'does not mark the user as external' do
stub_saml_group_config(%w(Interns))
saml_user.save
expect(gl_user).to be_valid
expect(gl_user.external).to be_falsey
end
end
end
context 'with auto_link_ldap_user disabled (default)' do
before { stub_omniauth_config({ auto_link_ldap_user: false, auto_link_saml_user: false, allow_single_sign_on: ['saml'] }) }
include_examples 'to verify compliance with allow_single_sign_on'
......@@ -76,12 +135,6 @@ describe Gitlab::Saml::User, lib: true do
context 'with auto_link_ldap_user enabled' do
before { stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) }
context 'and no LDAP provider defined' do
before { stub_ldap_config(providers: []) }
include_examples 'to verify compliance with allow_single_sign_on'
end
context 'and at least one LDAP provider is defined' do
before { stub_ldap_config(providers: %w(ldapmain)) }
......@@ -89,19 +142,18 @@ describe Gitlab::Saml::User, lib: true do
before do
allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { uid }
allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] }
allow(ldap_user).to receive(:email) { %w(john@mail.com john2@example.com) }
allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
end
context 'and no account for the LDAP user' do
it 'creates a user with dual LDAP and SAML identities' do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user.username).to eql uid
expect(gl_user.email).to eql 'johndoe@example.com'
expect(gl_user.email).to eql 'john@mail.com'
expect(gl_user.identities.length).to eql 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
......@@ -111,13 +163,13 @@ describe Gitlab::Saml::User, lib: true do
end
context 'and LDAP user has an account already' do
let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
it "adds the omniauth identity to the LDAP account" do
let!(:existing_user) { create(:omniauth_user, email: 'john@mail.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
it 'adds the omniauth identity to the LDAP account' do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
expect(gl_user.email).to eql 'john@example.com'
expect(gl_user.email).to eql 'john@mail.com'
expect(gl_user.identities.length).to eql 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
expect(identities_as_hash).to match_array([ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
......@@ -126,19 +178,13 @@ describe Gitlab::Saml::User, lib: true do
end
end
end
context 'and no corresponding LDAP person' do
before { allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) }
include_examples 'to verify compliance with allow_single_sign_on'
end
end
end
end
describe 'blocking' do
before { stub_omniauth_config({ allow_saml_sign_up: true, auto_link_saml_user: true }) }
before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) }
context 'signup with SAML only' do
context 'dont block on create' do
......@@ -162,64 +208,6 @@ describe Gitlab::Saml::User, lib: true do
end
end
context 'signup with linked omniauth and LDAP account' do
before do
stub_omniauth_config(auto_link_ldap_user: true)
allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { uid }
allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] }
allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
allow(saml_user).to receive(:ldap_person).and_return(ldap_user)
end
context "and no account for the LDAP user" do
context 'dont block on create (LDAP)' do
before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
it do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
end
context 'block on create (LDAP)' do
before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
it do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user).to be_blocked
end
end
end
context 'and LDAP user has an account already' do
let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
context 'dont block on create (LDAP)' do
before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
it do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
end
context 'block on create (LDAP)' do
before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
it do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
end
end
end
context 'sign-in' do
before do
saml_user.save
......@@ -245,26 +233,6 @@ describe Gitlab::Saml::User, lib: true do
expect(gl_user).not_to be_blocked
end
end
context 'dont block on create (LDAP)' do
before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
it do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
end
context 'block on create (LDAP)' do
before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
it do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
end
end
end
end
......
......@@ -6,18 +6,38 @@ describe BuildsEmailService do
let(:service) { BuildsEmailService.new }
describe :execute do
it "sends email" do
it 'sends email' do
service.recipients = 'test@gitlab.com'
data[:build_status] = 'failed'
expect(BuildEmailWorker).to receive(:perform_async)
service.execute(data)
end
it "does not sends email with failed build and allowed_failure on" do
it 'does not send email with succeeded build and notify_only_broken_builds on' do
expect(service).to receive(:notify_only_broken_builds).and_return(true)
data[:build_status] = 'success'
expect(BuildEmailWorker).not_to receive(:perform_async)
service.execute(data)
end
it 'does not send email with failed build and build_allow_failure is true' do
data[:build_status] = 'failed'
data[:build_allow_failure] = true
expect(BuildEmailWorker).not_to receive(:perform_async)
service.execute(data)
end
it 'does not send email with unknown build status' do
data[:build_status] = 'foo'
expect(BuildEmailWorker).not_to receive(:perform_async)
service.execute(data)
end
it 'does not send email when recipients list is empty' do
service.recipients = ' ,, '
data[:build_status] = 'failed'
expect(BuildEmailWorker).not_to receive(:perform_async)
service.execute(data)
end
end
end
......@@ -670,6 +670,19 @@ describe Repository, models: true do
repository.after_create
end
it 'flushes the root ref cache' do
expect(repository).to receive(:expire_root_ref_cache)
repository.after_create
end
it 'flushes the emptiness caches' do
expect(repository).to receive(:expire_emptiness_caches)
repository.after_create
end
end
describe "#main_language" do
......
......@@ -4,6 +4,7 @@ describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project) }
let!(:milestone) { create(:milestone, project: project) }
before { project.team << [user, :developer] }
......@@ -20,6 +21,24 @@ describe API::API, api: true do
get api("/projects/#{project.id}/milestones")
expect(response.status).to eq(401)
end
it 'returns an array of active milestones' do
get api("/projects/#{project.id}/milestones?state=active", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(milestone.id)
end
it 'returns an array of closed milestones' do
get api("/projects/#{project.id}/milestones?state=closed", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(closed_milestone.id)
end
end
describe 'GET /projects/:id/milestones/:milestone_id' do
......
......@@ -164,21 +164,37 @@ describe GitPushService, services: true do
end
context "after push" do
before do
@service = execute_service(project, user, @oldrev, @newrev, ref)
def execute
execute_service(project, user, @oldrev, @newrev, ref)
end
context "to master" do
let(:ref) { @ref }
it { expect(@service.update_main_language).to eq(true) }
it { expect(project.main_language).to eq("Ruby") }
context 'when main_language is nil' do
it 'obtains the language from the repository' do
expect(project.repository).to receive(:main_language)
execute
end
it 'sets the project main language' do
execute
expect(project.main_language).to eq("Ruby")
end
end
context 'when main_language is already set' do
it 'does not check the repository' do
execute # do an initial run to simulate lang being preset
expect(project.repository).not_to receive(:main_language)
execute
end
end
end
context "to other branch" do
let(:ref) { 'refs/heads/feature/branch' }
it { expect(@service.update_main_language).to eq(nil) }
it { expect(project.main_language).to eq(nil) }
end
end
......
......@@ -13,7 +13,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
link = actual.at_css('a:contains("Relative Link")')
link = actual.at_css('a:contains("Relative Link")')
image = actual.at_css('img[alt="Relative Image"]')
expect(link['href']).to end_with('master/doc/README.md')
......@@ -72,14 +72,15 @@ module MarkdownMatchers
have_css("img[src$='#{src}']")
end
prefix = '/namespace1/gitlabhq/wikis'
set_default_markdown_messages
match do |actual|
expect(actual).to have_link('linked-resource', href: 'linked-resource')
expect(actual).to have_link('link-text', href: 'linked-resource')
expect(actual).to have_link('linked-resource', href: "#{prefix}/linked-resource")
expect(actual).to have_link('link-text', href: "#{prefix}/linked-resource")
expect(actual).to have_link('http://example.com', href: 'http://example.com')
expect(actual).to have_link('link-text', href: 'http://example.com/pdfs/gollum.pdf')
expect(actual).to have_image('/gitlabhq/wikis/images/example.jpg')
expect(actual).to have_image("#{prefix}/images/example.jpg")
expect(actual).to have_image('http://example.com/images/example.jpg')
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment