Commit 3e26f6b0 authored by Jacob Vosmaer's avatar Jacob Vosmaer

Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into no-gc-auto

parents 2b91abbc a918e8bf
...@@ -2,7 +2,6 @@ image: "ruby:2.1" ...@@ -2,7 +2,6 @@ image: "ruby:2.1"
services: services:
- mysql:latest - mysql:latest
- postgres:latest
- redis:latest - redis:latest
cache: cache:
...@@ -35,123 +34,79 @@ spec:feature: ...@@ -35,123 +34,79 @@ spec:feature:
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
tags:
- ruby
- mysql
spec:api: spec:api:
stage: test stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
tags:
- ruby
- mysql
spec:models: spec:models:
stage: test stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
tags:
- ruby
- mysql
spec:lib: spec:lib:
stage: test stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
tags:
- ruby
- mysql
spec:services: spec:services:
stage: test stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
tags:
- ruby
- mysql
spec:other: spec:other:
stage: test stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
tags:
- ruby
- mysql
spinach:project:half: spinach:project:half:
stage: test stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
tags:
- ruby
- mysql
spinach:project:rest: spinach:project:rest:
stage: test stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
tags:
- ruby
- mysql
spinach:other: spinach:other:
stage: test stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
tags:
- ruby
- mysql
teaspoon: teaspoon:
stage: test stage: test
script: script:
- RAILS_ENV=test bundle exec teaspoon - RAILS_ENV=test bundle exec teaspoon
tags:
- ruby
- mysql
rubocop: rubocop:
stage: test stage: test
script: script:
- bundle exec rubocop - bundle exec rubocop
tags:
- ruby
- mysql
scss-lint: scss-lint:
stage: test stage: test
script: script:
- bundle exec rake scss_lint - bundle exec rake scss_lint
tags:
- ruby
brakeman: brakeman:
stage: test stage: test
script: script:
- bundle exec rake brakeman - bundle exec rake brakeman
tags:
- ruby
- mysql
flog: flog:
stage: test stage: test
script: script:
- bundle exec rake flog - bundle exec rake flog
tags:
- ruby
- mysql
flay: flay:
stage: test stage: test
script: script:
- bundle exec rake flay - bundle exec rake flay
tags:
- ruby
- mysql
bundler:audit: bundler:audit:
stage: test stage: test
...@@ -159,9 +114,6 @@ bundler:audit: ...@@ -159,9 +114,6 @@ bundler:audit:
- master - master
script: script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941" - "bundle exec bundle-audit check --update --ignore OSVDB-115941"
tags:
- ruby
- mysql
# Ruby 2.2 jobs # Ruby 2.2 jobs
...@@ -177,9 +129,6 @@ spec:feature:ruby22: ...@@ -177,9 +129,6 @@ spec:feature:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:api:ruby22: spec:api:ruby22:
stage: test stage: test
...@@ -192,9 +141,6 @@ spec:api:ruby22: ...@@ -192,9 +141,6 @@ spec:api:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:models:ruby22: spec:models:ruby22:
stage: test stage: test
...@@ -207,9 +153,6 @@ spec:models:ruby22: ...@@ -207,9 +153,6 @@ spec:models:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:lib:ruby22: spec:lib:ruby22:
stage: test stage: test
...@@ -222,9 +165,6 @@ spec:lib:ruby22: ...@@ -222,9 +165,6 @@ spec:lib:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:services:ruby22: spec:services:ruby22:
stage: test stage: test
...@@ -237,9 +177,6 @@ spec:services:ruby22: ...@@ -237,9 +177,6 @@ spec:services:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:other:ruby22: spec:other:ruby22:
stage: test stage: test
...@@ -252,9 +189,6 @@ spec:other:ruby22: ...@@ -252,9 +189,6 @@ spec:other:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spinach:project:half:ruby22: spinach:project:half:ruby22:
stage: test stage: test
...@@ -268,9 +202,6 @@ spinach:project:half:ruby22: ...@@ -268,9 +202,6 @@ spinach:project:half:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spinach:project:rest:ruby22: spinach:project:rest:ruby22:
stage: test stage: test
...@@ -284,9 +215,6 @@ spinach:project:rest:ruby22: ...@@ -284,9 +215,6 @@ spinach:project:rest:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spinach:other:ruby22: spinach:other:ruby22:
stage: test stage: test
...@@ -300,10 +228,6 @@ spinach:other:ruby22: ...@@ -300,10 +228,6 @@ spinach:other:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
notify:slack: notify:slack:
stage: notifications stage: notifications
......
...@@ -2,18 +2,23 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -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) v 8.7.0 (unreleased)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea). - 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) - 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) - 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 - Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu) - Make HTTP(s) label consistent on clone bar (Stan Hu)
- Expose label description in API (Mariusz Jachimowicz) - Expose label description in API (Mariusz Jachimowicz)
- Allow back dating on issues when created through the API - Allow back dating on issues when created through the API
- Fix Error 500 after renaming a project path (Stan Hu) - Fix Error 500 after renaming a project path (Stan Hu)
- Fix avatar stretching by providing a cropping feature - 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 endpoints to archive or unarchive a project !3372
- Add links to CI setup documentation from project settings and builds pages - Add links to CI setup documentation from project settings and builds pages
- Handle nil descriptions in Slack issue messages (Stan Hu) - Handle nil descriptions in Slack issue messages (Stan Hu)
- Add default scope to projects to exclude projects pending deletion - 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 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Implement 'TODOs 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) - Gracefully handle notes on deleted commits in merge requests (Stan Hu)
...@@ -21,10 +26,12 @@ v 8.7.0 (unreleased) ...@@ -21,10 +26,12 @@ v 8.7.0 (unreleased)
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - 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) - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- Improved UX of the navigation sidebar - Improved UX of the navigation sidebar
- Fix admin/projects when using visibility levels on search (PotHix)
- Build status notifications - Build status notifications
- API: Ability to retrieve a specific tag (Robert Schilling) - API: Expose user location (Robert Schilling)
v 8.6.5 (unreleased) 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 - Check permissions when user attempts to import members from another project
v 8.6.4 v 8.6.4
......
...@@ -290,7 +290,7 @@ group :development, :test do ...@@ -290,7 +290,7 @@ group :development, :test do
gem 'rubocop', '~> 0.38.0', require: false gem 'rubocop', '~> 0.38.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', 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 'flog', require: false
gem 'flay', require: false gem 'flay', require: false
gem 'bundler-audit', require: false gem 'bundler-audit', require: false
......
...@@ -136,10 +136,9 @@ GEM ...@@ -136,10 +136,9 @@ GEM
colorize (0.7.7) colorize (0.7.7)
concurrent-ruby (1.0.0) concurrent-ruby (1.0.0)
connection_pool (2.2.0) connection_pool (2.2.0)
coveralls (0.8.9) coveralls (0.8.13)
json (~> 1.8) json (~> 1.8)
rest-client (>= 1.6.8, < 2) simplecov (~> 0.11.0)
simplecov (~> 0.10.0)
term-ansicolor (~> 1.3) term-ansicolor (~> 1.3)
thor (~> 0.19.1) thor (~> 0.19.1)
tins (~> 1.6.0) tins (~> 1.6.0)
...@@ -176,8 +175,6 @@ GEM ...@@ -176,8 +175,6 @@ GEM
diff-lcs (1.2.5) diff-lcs (1.2.5)
diffy (3.0.7) diffy (3.0.7)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.25)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (2.2.2) doorkeeper (2.2.2)
railties (>= 3.2) railties (>= 3.2)
dropzonejs-rails (0.7.2) dropzonejs-rails (0.7.2)
...@@ -421,8 +418,6 @@ GEM ...@@ -421,8 +418,6 @@ GEM
nokogiri (~> 1.6.0) nokogiri (~> 1.6.0)
ruby_parser (~> 3.5) ruby_parser (~> 3.5)
htmlentities (4.3.4) htmlentities (4.3.4)
http-cookie (1.0.2)
domain_name (~> 0.5)
http_parser.rb (0.5.3) http_parser.rb (0.5.3)
httparty (0.13.7) httparty (0.13.7)
json (~> 1.8) json (~> 1.8)
...@@ -480,7 +475,6 @@ GEM ...@@ -480,7 +475,6 @@ GEM
nested_form (0.3.2) nested_form (0.3.2)
net-ldap (0.12.1) net-ldap (0.12.1)
net-ssh (3.0.1) net-ssh (3.0.1)
netrc (0.11.0)
newrelic_rpm (3.14.1.311) newrelic_rpm (3.14.1.311)
nokogiri (1.6.7.2) nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2) mini_portile2 (~> 2.0.0.rc2)
...@@ -657,10 +651,6 @@ GEM ...@@ -657,10 +651,6 @@ GEM
listen (~> 3.0) listen (~> 3.0)
responders (2.1.1) responders (2.1.1)
railties (>= 4.2.0, < 5.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) rinku (1.7.3)
rotp (2.1.1) rotp (2.1.1)
rouge (1.10.1) rouge (1.10.1)
...@@ -754,7 +744,7 @@ GEM ...@@ -754,7 +744,7 @@ GEM
rufus-scheduler (>= 2.0.24) rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0) sidekiq (>= 4.0.0)
simple_oauth (0.1.9) simple_oauth (0.1.9)
simplecov (0.10.0) simplecov (0.11.2)
docile (~> 1.1.0) docile (~> 1.1.0)
json (~> 1.8) json (~> 1.8)
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
...@@ -845,7 +835,7 @@ GEM ...@@ -845,7 +835,7 @@ GEM
underscore-rails (1.8.3) underscore-rails (1.8.3)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.1) unf_ext (0.0.7.2)
unicode-display_width (1.0.2) unicode-display_width (1.0.2)
unicorn (4.9.0) unicorn (4.9.0)
kgio (~> 2.6) kgio (~> 2.6)
...@@ -1032,7 +1022,7 @@ DEPENDENCIES ...@@ -1032,7 +1022,7 @@ DEPENDENCIES
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0) sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0) sidekiq-cron (~> 0.4.0)
simplecov (~> 0.10.0) simplecov (~> 0.11.0)
sinatra (~> 1.4.4) sinatra (~> 1.4.4)
six (~> 0.2.0) six (~> 0.2.0)
slack-notifier (~> 1.2.0) slack-notifier (~> 1.2.0)
......
...@@ -177,10 +177,11 @@ class GitLabDropdown ...@@ -177,10 +177,11 @@ class GitLabDropdown
selector = ".dropdown-page-one .dropdown-content a" selector = ".dropdown-page-one .dropdown-content a"
@dropdown.on "click", selector, (e) -> @dropdown.on "click", selector, (e) ->
selected = self.rowClicked $(@) $el = $(@)
selected = self.rowClicked $el
if self.options.clicked if self.options.clicked
self.options.clicked(selected) self.options.clicked(selected, $el, e)
# Finds an element inside wrapper element # Finds an element inside wrapper element
getElement: (selector) -> getElement: (selector) ->
...@@ -360,6 +361,8 @@ class GitLabDropdown ...@@ -360,6 +361,8 @@ class GitLabDropdown
# Toggle the dropdown label # Toggle the dropdown label
if @options.toggleLabel if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel $(@el).find(".dropdown-toggle-text").text @options.toggleLabel
else
selectedObject
else else
if !value? if !value?
field.remove() field.remove()
...@@ -375,7 +378,7 @@ class GitLabDropdown ...@@ -375,7 +378,7 @@ class GitLabDropdown
if @options.toggleLabel if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject) $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
if value? if value?
if !field.length if !field.length and fieldName
# Create hidden input for form # Create hidden input for form
input = "<input type='hidden' name='#{fieldName}' value='#{value}' />" input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
if @options.inputId? if @options.inputId?
...@@ -394,7 +397,7 @@ class GitLabDropdown ...@@ -394,7 +397,7 @@ class GitLabDropdown
selector = ".dropdown-page-one #{selector}" selector = ".dropdown-page-one #{selector}"
# simulate a click on the first link # simulate a click on the first link
$(selector).trigger "click" $(selector, @dropdown).trigger "click"
addArrowKeyEvent: -> addArrowKeyEvent: ->
ARROW_KEY_CODES = [38, 40] ARROW_KEY_CODES = [38, 40]
......
...@@ -26,6 +26,20 @@ ...@@ -26,6 +26,20 @@
$(".selected_issue").bind "change", Issues.checkChanged $(".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 # Make sure we trigger ajax request only after user stop typing
initSearch: -> initSearch: ->
@timer = null @timer = null
...@@ -54,6 +68,7 @@ ...@@ -54,6 +68,7 @@
# Change url so if user reload a page - search results are saved # Change url so if user reload a page - search results are saved
history.replaceState {page: issuesUrl}, document.title, issuesUrl history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issues.reload() Issues.reload()
Issues.updateStateFilters()
dataType: "json" dataType: "json"
checkChanged: -> 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 ...@@ -15,6 +15,8 @@ class @MergeRequestWidget
@pollCIStatus() @pollCIStatus()
notifyPermissions() notifyPermissions()
setOpts: (@opts) ->
mergeInProgress: (deleteSourceBranch = false)-> mergeInProgress: (deleteSourceBranch = false)->
$.ajax $.ajax
type: 'GET' type: 'GET'
...@@ -48,7 +50,7 @@ class @MergeRequestWidget ...@@ -48,7 +50,7 @@ class @MergeRequestWidget
@getCIStatus(true) @getCIStatus(true)
@readyForCICheck = false @readyForCICheck = false
), 5000 ), 10000
getCIStatus: (showNotification) -> getCIStatus: (showNotification) ->
_this = @ _this = @
...@@ -61,6 +63,10 @@ class @MergeRequestWidget ...@@ -61,6 +63,10 @@ class @MergeRequestWidget
@firstCICheck = false @firstCICheck = false
@opts.ci_status = data.status @opts.ci_status = data.status
if @opts.ci_status is ''
@opts.ci_status = data.status
return
if data.status isnt @opts.ci_status if data.status isnt @opts.ci_status
@showCIStatus data.status @showCIStatus data.status
if data.coverage if data.coverage
......
...@@ -62,6 +62,8 @@ class @SearchAutocomplete ...@@ -62,6 +62,8 @@ class @SearchAutocomplete
search: search:
fields: ['text'] fields: ['text']
data: @getData.bind(@) data: @getData.bind(@)
selectable: true
clicked: @onClick.bind(@)
getData: (term, callback) -> getData: (term, callback) ->
_this = @ _this = @
...@@ -102,6 +104,8 @@ class @SearchAutocomplete ...@@ -102,6 +104,8 @@ class @SearchAutocomplete
lastCategory = suggestion.category lastCategory = suggestion.category
data.push data.push
id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}"
category: suggestion.category
text: suggestion.label text: suggestion.label
url: suggestion.url url: suggestion.url
...@@ -133,12 +137,19 @@ class @SearchAutocomplete ...@@ -133,12 +137,19 @@ class @SearchAutocomplete
} }
bindEvents: -> bindEvents: ->
$(document).on 'click', @onDocumentClick
@searchInput.on 'keydown', @onSearchInputKeyDown @searchInput.on 'keydown', @onSearchInputKeyDown
@searchInput.on 'keyup', @onSearchInputKeyUp @searchInput.on 'keyup', @onSearchInputKeyUp
@searchInput.on 'click', @onSearchInputClick @searchInput.on 'click', @onSearchInputClick
@searchInput.on 'focus', @onSearchInputFocus @searchInput.on 'focus', @onSearchInputFocus
@searchInput.on 'blur', @onSearchInputBlur @clearInput.on 'click', @onClearInputClick
@clearInput.on 'click', @onRemoveLocationClick
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: -> enableAutocomplete: ->
# No need to enable anything if user is not logged in # No need to enable anything if user is not logged in
...@@ -181,6 +192,8 @@ class @SearchAutocomplete ...@@ -181,6 +192,8 @@ class @SearchAutocomplete
# We should display the menu only when input is not empty # We should display the menu only when input is not empty
@enableAutocomplete() @enableAutocomplete()
@wrap.toggleClass 'has-value', !!e.target.value
# Avoid falsy value to be returned # Avoid falsy value to be returned
return return
...@@ -189,27 +202,20 @@ class @SearchAutocomplete ...@@ -189,27 +202,20 @@ class @SearchAutocomplete
e.stopImmediatePropagation() e.stopImmediatePropagation()
onSearchInputFocus: => onSearchInputFocus: =>
@isFocused = true
@wrap.addClass('search-active') @wrap.addClass('search-active')
onRemoveLocationClick: (e) => onClearInputClick: (e) =>
e.preventDefault() e.preventDefault()
@removeLocationBadge()
@searchInput.val('').focus() @searchInput.val('').focus()
@skipBlurEvent = true
onSearchInputBlur: (e) => onSearchInputBlur: (e) =>
@skipBlurEvent = false @isFocused = false
@wrap.removeClass('search-active')
# We should wait to make sure we are not clearing the input instead
setTimeout( =>
return if @skipBlurEvent
@wrap.removeClass('search-active') # If input is blank then restore state
if @searchInput.val() is ''
# If input is blank then restore state @restoreOriginalState()
if @searchInput.val() is ''
@restoreOriginalState()
, 150)
addLocationBadge: (item) -> addLocationBadge: (item) ->
category = if item.category? then "#{item.category}: " else '' category = if item.category? then "#{item.category}: " else ''
...@@ -268,3 +274,23 @@ class @SearchAutocomplete ...@@ -268,3 +274,23 @@ class @SearchAutocomplete
<li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li>
</ul>" </ul>"
@dropdownContent.html(html) @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 @@ ...@@ -20,6 +20,7 @@
margin: 0; margin: 0;
text-align: left; text-align: left;
padding: 10px $gl-padding; padding: 10px $gl-padding;
word-wrap: break-word;
.file-actions { .file-actions {
float: right; float: right;
......
...@@ -84,6 +84,7 @@ ...@@ -84,6 +84,7 @@
.md-preview-holder { .md-preview-holder {
min-height: 167px; min-height: 167px;
padding: 10px 0; padding: 10px 0;
overflow-x: auto;
} }
.markdown-area { .markdown-area {
......
...@@ -104,9 +104,9 @@ $orange-light: rgba(252, 109, 38, 0.80); ...@@ -104,9 +104,9 @@ $orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #e75e40; $orange-normal: #e75e40;
$orange-dark: #ce5237; $orange-dark: #ce5237;
$red-light: #f06559; $red-light: #e52c5a;
$red-normal: #e52c5a; $red-normal: #d22852;
$red-dark: #d22852; $red-dark: darken($red-normal, 5%);
$border-white-light: #f1f2f4; $border-white-light: #f1f2f4;
$border-white-normal: #d6dae2; $border-white-normal: #d6dae2;
...@@ -128,9 +128,9 @@ $border-orange-light: #fc6d26; ...@@ -128,9 +128,9 @@ $border-orange-light: #fc6d26;
$border-orange-normal: #ce5237; $border-orange-normal: #ce5237;
$border-orange-dark: #c14e35; $border-orange-dark: #c14e35;
$border-red-light: #f24f41; $border-red-light: #d22852;
$border-red-normal: #d22852; $border-red-normal: #ca264f;
$border-red-dark: #ca264f; $border-red-dark: darken($border-red-normal, 5%);
$help-well-bg: #fafafa; $help-well-bg: #fafafa;
$help-well-border: #e5e5e5; $help-well-border: #e5e5e5;
...@@ -201,14 +201,14 @@ $award-emoji-new-btn-icon-color: #dcdcdc; ...@@ -201,14 +201,14 @@ $award-emoji-new-btn-icon-color: #dcdcdc;
/* /*
* Search Box * 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-focus-shadow-color: $dropdown-input-focus-shadow;
$search-input-width: $dropdown-width; $search-input-width: 244px;
$location-badge-color: #aaa; $location-badge-color: #aaa;
$location-badge-bg: $gray-normal; $location-badge-bg: $gray-normal;
$location-badge-active-bg: #4f91f8;
$location-icon-color: #e7e9ed; $location-icon-color: #e7e9ed;
$location-active-color: $gl-text-color; $location-icon-active-color: #807e7e;
$location-active-bg: $search-input-border-color;
/* /*
* Notes * Notes
......
...@@ -33,8 +33,12 @@ ...@@ -33,8 +33,12 @@
.description { .description {
margin-top: 6px; margin-top: 6px;
p:last-child { p {
margin-bottom: 0; overflow-x: auto;
&:last-child {
margin-bottom: 0;
}
} }
} }
} }
...@@ -59,10 +59,15 @@ ...@@ -59,10 +59,15 @@
border-collapse: separate; border-collapse: separate;
margin: 0; margin: 0;
padding: 0; padding: 0;
.line_holder td { .line_holder td {
line-height: $code_line_height; line-height: $code_line_height;
font-size: $code_font_size; font-size: $code_font_size;
} }
td {
white-space: nowrap;
}
} }
tr.line_holder.parallel { tr.line_holder.parallel {
......
...@@ -82,7 +82,7 @@ ul.notes { ...@@ -82,7 +82,7 @@ ul.notes {
// On diffs code should wrap nicely and not overflow // On diffs code should wrap nicely and not overflow
pre { pre {
code { code {
white-space: pre-wrap; white-space: pre;
} }
} }
......
...@@ -135,25 +135,25 @@ ...@@ -135,25 +135,25 @@
.location-badge { .location-badge {
@include transition(all .15s); @include transition(all .15s);
background-color: $location-active-bg; background-color: $location-badge-active-bg;
color: $white-light; color: $white-light;
} }
.search-input-wrap { .search-input-wrap {
i { i {
color: $location-active-color; color: $location-icon-active-color;
} }
} }
}
&.has-location-badge { &.has-value {
.search-icon { .search-icon {
display: none; display: none;
} }
.clear-icon { .clear-icon {
cursor: pointer; cursor: pointer;
display: block; display: block;
}
} }
} }
......
...@@ -5,7 +5,7 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -5,7 +5,7 @@ class Admin::ProjectsController < Admin::ApplicationController
def index def index
@projects = Project.all @projects = Project.all
@projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present? @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.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.non_archived unless params[:with_archived].present? @projects = @projects.non_archived unless params[:with_archived].present?
......
...@@ -55,7 +55,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -55,7 +55,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
else else
saml_user = Gitlab::Saml::User.new(oauth) saml_user = Gitlab::Saml::User.new(oauth)
saml_user.save saml_user.save if saml_user.changed?
@user = saml_user.gl_user @user = saml_user.gl_user
continue_login_process continue_login_process
......
...@@ -68,7 +68,9 @@ class Projects::ApplicationController < ApplicationController ...@@ -68,7 +68,9 @@ class Projects::ApplicationController < ApplicationController
end end
def require_non_empty_project 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 end
def require_branch_head def require_branch_head
......
class Projects::BadgesController < Projects::ApplicationController 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 def build
badge = Gitlab::Badge::Build.new(project, params[:ref]) badge = Gitlab::Badge::Build.new(project, params[:ref])
......
...@@ -48,7 +48,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -48,7 +48,7 @@ class Projects::BranchesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
redirect_to namespace_project_branches_path(@project.namespace, redirect_to namespace_project_branches_path(@project.namespace,
@project) @project), status: 303
end end
format.js { render status: status[:return_code] } format.js { render status: status[:return_code] }
end end
......
...@@ -24,6 +24,8 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -24,6 +24,8 @@ class Projects::RefsController < Projects::ApplicationController
namespace_project_find_file_path(@project.namespace, @project, @id) namespace_project_find_file_path(@project.namespace, @project, @id)
when "graphs_commits" when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id) commits_namespace_project_graph_path(@project.namespace, @project, @id)
when "badges"
namespace_project_badges_path(@project.namespace, @project, ref: @id)
else else
namespace_project_commits_path(@project.namespace, @project, @id) namespace_project_commits_path(@project.namespace, @project, @id)
end end
......
...@@ -88,6 +88,20 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -88,6 +88,20 @@ class Projects::WikisController < Projects::ApplicationController
) )
end 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 def git_access
end end
......
...@@ -50,12 +50,15 @@ class BuildsEmailService < Service ...@@ -50,12 +50,15 @@ class BuildsEmailService < Service
def execute(push_data) def execute(push_data)
return unless supported_events.include?(push_data[:object_kind]) 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( BuildEmailWorker.perform_async(
push_data[:build_id], push_data[:build_id],
all_recipients(push_data), recipients,
push_data, push_data
) )
end end
end end
...@@ -84,7 +87,7 @@ class BuildsEmailService < Service ...@@ -84,7 +87,7 @@ class BuildsEmailService < Service
end end
def all_recipients(data) def all_recipients(data)
all_recipients = recipients.split(',') all_recipients = recipients.split(',').compact.reject(&:blank?)
if add_pusher? && data[:user][:email] if add_pusher? && data[:user][:email]
all_recipients << "#{data[:user][:email]}" all_recipients << "#{data[:user][:email]}"
......
...@@ -331,6 +331,8 @@ class Repository ...@@ -331,6 +331,8 @@ class Repository
# Runs code after a repository has been created. # Runs code after a repository has been created.
def after_create def after_create
expire_exists_cache expire_exists_cache
expire_root_ref_cache
expire_emptiness_caches
end end
# Runs code just before a repository is deleted. # Runs code just before a repository is deleted.
......
...@@ -55,15 +55,15 @@ class GitPushService < BaseService ...@@ -55,15 +55,15 @@ class GitPushService < BaseService
end end
def update_main_language 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 is_default_branch?
return unless push_to_new_branch? || push_to_existing_branch? return unless push_to_new_branch? || push_to_existing_branch?
current_language = @project.repository.main_language current_language = @project.repository.main_language
@project.update_attributes(main_language: current_language)
unless current_language == @project.main_language
return @project.update_attributes(main_language: current_language)
end
true true
end end
......
- if controller.controller_path =~ /^groups/ - if controller.controller_path =~ /^groups/ && @group.persisted?
- label = 'This group' - label = 'This group'
- if controller.controller_path =~ /^projects/ - if controller.controller_path =~ /^projects/ && @project.persisted?
- label = 'This project' - label = 'This project'
.search.search-form{class: "#{'has-location-badge' if label.present?}"} .search.search-form{class: "#{'has-location-badge' if label.present?}"}
...@@ -21,8 +21,8 @@ ...@@ -21,8 +21,8 @@
%a.is-focused.dropdown-menu-empty-link %a.is-focused.dropdown-menu-empty-link
Loading... Loading...
= dropdown_loading = dropdown_loading
%i.search-icon %i.search-icon
%i.clear-icon.js-clear-input %i.clear-icon.js-clear-input
= hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :group_id, @group.try(:id)
= hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id' = hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
......
...@@ -51,8 +51,13 @@ ...@@ -51,8 +51,13 @@
= icon('code fw') = icon('code fw')
%span %span
Variables Variables
= nav_link path: 'triggers#index' do = nav_link(controller: :triggers) do
= link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do = link_to namespace_project_triggers_path(@project.namespace, @project), title: 'Triggers' do
= icon('retweet fw') = icon('retweet fw')
%span %span
Triggers 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 @@ ...@@ -5,10 +5,14 @@
- content_for :scripts_body_top do - content_for :scripts_body_top do
- project = @target_project || @project - 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 - if current_user
:javascript :javascript
window.project_uploads_path = "#{namespace_project_uploads_path project.namespace,project}"; 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 - content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user = 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 @@ ...@@ -22,4 +22,6 @@
if(typeof merge_request_widget === 'undefined') { if(typeof merge_request_widget === 'undefined') {
merge_request_widget = new MergeRequestWidget(opts); merge_request_widget = new MergeRequestWidget(opts);
} else {
merge_request_widget.setOpts(opts);
} }
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
= access = access
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
= icon('pencil-square-o') = 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') = icon('trash-o')
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text .note-text
......
...@@ -345,6 +345,8 @@ production: &base ...@@ -345,6 +345,8 @@ production: &base
# #
# - { name: 'saml', # - { name: 'saml',
# label: 'Our SAML Provider', # label: 'Our SAML Provider',
# groups_attribute: 'Groups',
# external_groups: ['Contractors', 'Freelancers'],
# args: { # args: {
# assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', # 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_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
...@@ -352,6 +354,7 @@ production: &base ...@@ -352,6 +354,7 @@ production: &base
# issuer: 'https://gitlab.example.com', # issuer: 'https://gitlab.example.com',
# name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' # name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
# } } # } }
#
# - { name: 'crowd', # - { name: 'crowd',
# args: { # args: {
# crowd_server_url: 'CROWD SERVER URL', # crowd_server_url: 'CROWD SERVER URL',
......
...@@ -575,6 +575,7 @@ Rails.application.routes.draw do ...@@ -575,6 +575,7 @@ Rails.application.routes.draw do
# Order matters to give priority to these matches # Order matters to give priority to these matches
get '/wikis/git_access', to: 'wikis#git_access' get '/wikis/git_access', to: 'wikis#git_access'
get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages'
post '/wikis/markdown_preview', to:'wikis#markdown_preview'
post '/wikis', to: 'wikis#create' post '/wikis', to: 'wikis#create'
get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID
...@@ -749,10 +750,11 @@ Rails.application.routes.draw do ...@@ -749,10 +750,11 @@ Rails.application.routes.draw do
end end
resources :runner_projects, only: [:create, :destroy] resources :runner_projects, only: [:create, :destroy]
resources :badges, only: [], path: 'badges/*ref', resources :badges, only: [:index] do
constraints: { ref: Gitlab::Regex.git_reference_regex } do
collection 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 end
end end
......
...@@ -7,8 +7,24 @@ Returns a list of project milestones. ...@@ -7,8 +7,24 @@ Returns a list of project milestones.
``` ```
GET /projects/:id/milestones GET /projects/:id/milestones
GET /projects/:id/milestones?iid=42 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 ```json
[ [
{ {
...@@ -25,10 +41,6 @@ GET /projects/:id/milestones?iid=42 ...@@ -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 ## Get single milestone
......
...@@ -38,38 +38,6 @@ Parameters: ...@@ -38,38 +38,6 @@ Parameters:
] ]
``` ```
## Get a single repository tag
Get a specific repository tag determined by its name. It returns 200 together
with the tag information if the tag exists. It returns 404 if the tag does not
exist.
Parameters:
- `id` (required) - The ID of a project
- `tag_name` (required) - The name of the tag
```json
{
"name": "v5.0.0",
"message": null,
"commit": {
"id": "60a8ff033665e1207714d6670fcd7b65304ec02f",
"message": "v5.0.0\n",
"parent_ids": [
"f61c062ff8bcbdb00e0a1b3317a91aed6ceee06b"
],
"authored_date": "2015-02-01T21:56:31.000+01:00",
"author_name": "Arthur Verschaeve",
"author_email": "contact@arthurverschaeve.be",
"committed_date": "2015-02-01T21:56:31.000+01:00",
"committer_name": "Arthur Verschaeve",
"committer_email": "contact@arthurverschaeve.be"
},
"release": null
}
```
## Create a new tag ## Create a new tag
Creates a new tag in the repository that points to the supplied ref. Creates a new tag in the repository that points to the supplied ref.
...@@ -180,4 +148,4 @@ Parameters: ...@@ -180,4 +148,4 @@ Parameters:
"tag_name": "1.0.0", "tag_name": "1.0.0",
"description": "Amazing release. Wow" "description": "Amazing release. Wow"
} }
``` ```
\ No newline at end of file
...@@ -69,6 +69,7 @@ GET /users ...@@ -69,6 +69,7 @@ GET /users
"state": "blocked", "state": "blocked",
"created_at": "2012-05-23T08:01:01Z", "created_at": "2012-05-23T08:01:01Z",
"bio": null, "bio": null,
"location": null,
"skype": "", "skype": "",
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
...@@ -126,6 +127,7 @@ Parameters: ...@@ -126,6 +127,7 @@ Parameters:
"created_at": "2012-05-23T08:00:58Z", "created_at": "2012-05-23T08:00:58Z",
"is_admin": false, "is_admin": false,
"bio": null, "bio": null,
"location": null,
"skype": "", "skype": "",
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
...@@ -154,6 +156,7 @@ Parameters: ...@@ -154,6 +156,7 @@ Parameters:
"confirmed_at": "2012-05-23T08:00:58Z", "confirmed_at": "2012-05-23T08:00:58Z",
"last_sign_in_at": "2015-03-23T08:00:58Z", "last_sign_in_at": "2015-03-23T08:00:58Z",
"bio": null, "bio": null,
"location": null,
"skype": "", "skype": "",
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
...@@ -191,6 +194,7 @@ Parameters: ...@@ -191,6 +194,7 @@ Parameters:
- `extern_uid` (optional) - External UID - `extern_uid` (optional) - External UID
- `provider` (optional) - External provider name - `provider` (optional) - External provider name
- `bio` (optional) - User's biography - `bio` (optional) - User's biography
- `location` (optional) - User's location
- `admin` (optional) - User is admin - true or false (default) - `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false - `can_create_group` (optional) - User can create groups - true or false
- `confirm` (optional) - Require confirmation - true (default) or false - `confirm` (optional) - Require confirmation - true (default) or false
...@@ -218,6 +222,7 @@ Parameters: ...@@ -218,6 +222,7 @@ Parameters:
- `extern_uid` - External UID - `extern_uid` - External UID
- `provider` - External provider name - `provider` - External provider name
- `bio` - User's biography - `bio` - User's biography
- `location` (optional) - User's location
- `admin` (optional) - User is admin - true or false (default) - `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false - `can_create_group` (optional) - User can create groups - true or false
- `external` (optional) - Flags the user as external - true or false(default) - `external` (optional) - Flags the user as external - true or false(default)
...@@ -260,6 +265,7 @@ GET /user ...@@ -260,6 +265,7 @@ GET /user
"state": "active", "state": "active",
"created_at": "2012-05-23T08:00:58Z", "created_at": "2012-05-23T08:00:58Z",
"bio": null, "bio": null,
"location": null,
"skype": "", "skype": "",
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
......
...@@ -38,7 +38,7 @@ services: ...@@ -38,7 +38,7 @@ services:
- postgres - postgres
before_script: before_script:
- bundle_install - bundle install
stages: stages:
- build - build
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
- [CI setup](ci_setup.md) for testing GitLab - [CI setup](ci_setup.md) for testing GitLab
- [Gotchas](gotchas.md) to avoid - [Gotchas](gotchas.md) to avoid
- [How to dump production data to staging](db_dump.md) - [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations - [Migration Style Guide](migration_style_guide.md) for creating safe migrations
- [Rake tasks](rake_tasks.md) for development - [Rake tasks](rake_tasks.md) for development
- [Shell commands](shell_commands.md) in the GitLab codebase - [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 ...@@ -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 Click the icon to begin the authentication process. If everything goes well the user
will be returned to GitLab and will be signed in. 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 ## 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` ### `attribute_statements`
>**Note:** >**Note:**
...@@ -205,6 +272,10 @@ To bypass this you can add `skip_before_action :verify_authenticity_token` to th ...@@ -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 where it can then be seen in the usual logs, or as a flash message in the login
screen. 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 ### Invalid audience
This error means that the IdP doesn't recognize GitLab as a valid sender and 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). ...@@ -52,10 +52,11 @@ documentation](../workflow/add-user/add-user.md).
| Switch visibility level | | | | | ✓ | | Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ | | Remove project | | | | | ✓ |
| Force push to protected branches | | | | | | | Force push to protected branches [^2] | | | | | |
| Remove protected branches | | | | | | | Remove protected branches [^2] | | | | | |
[^1]: If **Allow guest to access builds** is enabled in CI settings [^1]: If **Allow guest to access builds** is enabled in CI settings
[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
## Group ## Group
......
...@@ -64,7 +64,7 @@ module API ...@@ -64,7 +64,7 @@ module API
authorize_admin_project authorize_admin_project
@branch = user_project.repository.find_branch(params[:branch]) @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 = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch protected_branch.destroy if protected_branch
......
...@@ -15,7 +15,7 @@ module API ...@@ -15,7 +15,7 @@ module API
class User < UserBasic class User < UserBasic
expose :created_at expose :created_at
expose :is_admin?, as: :is_admin expose :is_admin?, as: :is_admin
expose :bio, :skype, :linkedin, :twitter, :website_url expose :bio, :location, :skype, :linkedin, :twitter, :website_url
end end
class Identity < Grape::Entity class Identity < Grape::Entity
......
...@@ -3,17 +3,33 @@ module API ...@@ -3,17 +3,33 @@ module API
class Milestones < Grape::API class Milestones < Grape::API
before { authenticate! } 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 resource :projects do
# Get a list of project milestones # Get a list of project milestones
# #
# Parameters: # Parameters:
# id (required) - The ID of a project # id (required) - The ID of a project
# state (optional) - Return "active" or "closed" milestones
# Example Request: # Example Request:
# GET /projects/:id/milestones # GET /projects/:id/milestones
# GET /projects/:id/milestones?state=active
# GET /projects/:id/milestones?state=closed
get ":id/milestones" do get ":id/milestones" do
authorize! :read_milestone, user_project 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 end
# Get a single project milestone # Get a single project milestone
......
...@@ -16,20 +16,6 @@ module API ...@@ -16,20 +16,6 @@ module API
with: Entities::RepoTag, project: user_project with: Entities::RepoTag, project: user_project
end end
# Get a single repository tag
#
# Parameters:
# id (required) - The ID of a project
# tag_name (required) - The name of the tag
# Example Request:
# GET /projects/:id/repository/tags/:tag_name
get ":id/repository/tags/:tag_name", requirements: { tag_name: /.*/ } do
tag = user_project.repository.find_tag(params[:tag_name])
not_found!('Tag') unless tag
present tag, with: Entities::RepoTag, project: user_project
end
# Create tag # Create tag
# #
# Parameters: # Parameters:
......
...@@ -58,6 +58,7 @@ module API ...@@ -58,6 +58,7 @@ module API
# extern_uid - External authentication provider UID # extern_uid - External authentication provider UID
# provider - External provider # provider - External provider
# bio - Bio # bio - Bio
# location - Location of the user
# admin - User is admin - true or false (default) # admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false # can_create_group - User can create groups - true or false
# confirm - Require user confirmation - true (default) or false # confirm - Require user confirmation - true (default) or false
...@@ -67,7 +68,7 @@ module API ...@@ -67,7 +68,7 @@ module API
post do post do
authenticated_as_admin! authenticated_as_admin!
required_attributes! [:email, :password, :name, :username] 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) admin = attrs.delete(:admin)
confirm = !(attrs.delete(:confirm) =~ (/(false|f|no|0)$/i)) confirm = !(attrs.delete(:confirm) =~ (/(false|f|no|0)$/i))
user = User.build_user(attrs) user = User.build_user(attrs)
...@@ -106,6 +107,7 @@ module API ...@@ -106,6 +107,7 @@ module API
# website_url - Website url # website_url - Website url
# projects_limit - Limit projects each user can create # projects_limit - Limit projects each user can create
# bio - Bio # bio - Bio
# location - Location of the user
# admin - User is admin - true or false (default) # admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false # can_create_group - User can create groups - true or false
# external - Flags the user as external - true or false(default) # external - Flags the user as external - true or false(default)
...@@ -114,7 +116,7 @@ module API ...@@ -114,7 +116,7 @@ module API
put ":id" do put ":id" do
authenticated_as_admin! 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]) user = User.find(params[:id])
not_found!('User') unless user not_found!('User') unless user
......
...@@ -119,7 +119,7 @@ module Banzai ...@@ -119,7 +119,7 @@ module Banzai
elsif element_node?(node) elsif element_node?(node)
yield_valid_link(node) do |link, text| 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 replace_link_node_with_href(node, link) do
object_link_filter(link, ref_pattern, link_text: text) object_link_filter(link, ref_pattern, link_text: text)
end end
......
...@@ -118,7 +118,7 @@ module Banzai ...@@ -118,7 +118,7 @@ module Banzai
end end
if path if path
content_tag(:img, nil, src: path) content_tag(:img, nil, src: path, class: 'gfm')
end end
end end
...@@ -144,12 +144,18 @@ module Banzai ...@@ -144,12 +144,18 @@ module Banzai
# if it is not. # if it is not.
def process_page_link_tag(parts) def process_page_link_tag(parts)
if parts.size == 1 if parts.size == 1
url = parts[0].strip reference = parts[0].strip
else else
name, url = *parts.compact.map(&:strip) name, reference = *parts.compact.map(&:strip)
end 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 end
def project_wiki 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 ...@@ -2,8 +2,10 @@ module Banzai
module Pipeline module Pipeline
class WikiPipeline < FullPipeline class WikiPipeline < FullPipeline
def self.filters def self.filters
@filters ||= super.insert_after(Filter::TableOfContentsFilter, @filters ||= begin
Filter::GollumTagsFilter) super.insert_after(Filter::TableOfContentsFilter, Filter::GollumTagsFilter)
.insert_before(Filter::TaskListFilter, Filter::WikiLinkFilter)
end
end end
end end
end end
......
...@@ -4,14 +4,15 @@ module Gitlab ...@@ -4,14 +4,15 @@ module Gitlab
# Build badge # Build badge
# #
class Build class Build
include Gitlab::Application.routes.url_helpers
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::UrlHelper
def initialize(project, ref) def initialize(project, ref)
@project, @ref = project, ref
@image = ::Ci::ImageForBuildService.new.execute(project, ref: ref) @image = ::Ci::ImageForBuildService.new.execute(project, ref: ref)
end end
def to_s
@image[:name].sub(/\.svg$/, '')
end
def type def type
'image/svg+xml' 'image/svg+xml'
end end
...@@ -19,6 +20,27 @@ module Gitlab ...@@ -19,6 +20,27 @@ module Gitlab
def data def data
File.read(@image[:path]) File.read(@image[:path])
end 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 end
end end
...@@ -33,7 +33,10 @@ module Gitlab ...@@ -33,7 +33,10 @@ module Gitlab
def allowed? def allowed?
if ldap_user 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 # 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) if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
......
...@@ -70,6 +70,32 @@ module Gitlab ...@@ -70,6 +70,32 @@ module Gitlab
value.to_s.gsub('=', '\\=') value.to_s.gsub('=', '\\=')
end 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 # When enabled this should be set before being used as the usual pattern
# "@foo ||= bar" is _not_ thread-safe. # "@foo ||= bar" is _not_ thread-safe.
if enabled? 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 ...@@ -18,7 +18,7 @@ module Gitlab
@user ||= find_or_create_ldap_user @user ||= find_or_create_ldap_user
end end
if auto_link_saml_enabled? if auto_link_saml_user?
@user ||= find_by_email @user ||= find_by_email
end end
...@@ -26,6 +26,16 @@ module Gitlab ...@@ -26,6 +26,16 @@ module Gitlab
@user ||= build_new_user @user ||= build_new_user
end 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 @user
end end
...@@ -37,11 +47,23 @@ module Gitlab ...@@ -37,11 +47,23 @@ module Gitlab
end end
end end
def changed?
gl_user.changed? || gl_user.identities.any?(&:changed?)
end
protected protected
def auto_link_saml_enabled? def auto_link_saml_user?
Gitlab.config.omniauth.auto_link_saml_user Gitlab.config.omniauth.auto_link_saml_user
end 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 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 ...@@ -93,6 +93,20 @@ describe Projects::BranchesController do
end end
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 describe "POST destroy" do
render_views 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 ...@@ -39,7 +39,7 @@ describe 'GitLab Markdown', feature: true do
end end
def doc(html = @html) def doc(html = @html)
Nokogiri::HTML::DocumentFragment.parse(html) @doc ||= Nokogiri::HTML::DocumentFragment.parse(html)
end end
# Shared behavior that all pipelines should exhibit # Shared behavior that all pipelines should exhibit
...@@ -230,6 +230,7 @@ describe 'GitLab Markdown', feature: true do ...@@ -230,6 +230,7 @@ describe 'GitLab Markdown', feature: true do
file = Gollum::File.new(@project_wiki.wiki) file = Gollum::File.new(@project_wiki.wiki)
expect(file).to receive(:path).and_return('images/example.jpg') expect(file).to receive(:path).and_return('images/example.jpg')
expect(@project_wiki).to receive(:find_file).with('images/example.jpg').and_return(file) 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 }) @html = markdown(@feat.raw_markdown, { pipeline: :wiki, project_wiki: @project_wiki })
end 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 ...@@ -70,20 +70,22 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
end end
context 'linking internal resources' do 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]]' tag = '[[wiki-slug]]'
doc = filter("See #{tag}", project_wiki: project_wiki) 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').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 end
it "the created link's text will be link-text" do it "the created link's text will be link-text" do
tag = '[[link-text|wiki-slug]]' tag = '[[link-text|wiki-slug]]'
doc = filter("See #{tag}", project_wiki: project_wiki) 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').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
end end
......
...@@ -95,6 +95,14 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -95,6 +95,14 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
result = reference_pipeline_result("Fixed #{reference}") result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue] expect(result[:references][:issue]).to eq [issue]
end 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 end
context 'cross-project reference' do context 'cross-project reference' do
......
...@@ -11,7 +11,7 @@ describe Banzai::Pipeline::WikiPipeline do ...@@ -11,7 +11,7 @@ describe Banzai::Pipeline::WikiPipeline do
Foo Foo
MD MD
result = described_class.call(markdown, project: spy, project_wiki: double) result = described_class.call(markdown, project: spy, project_wiki: spy)
aggregate_failures do aggregate_failures do
expect(result[:output].text).not_to include '[[' expect(result[:output].text).not_to include '[['
...@@ -29,7 +29,7 @@ describe Banzai::Pipeline::WikiPipeline do ...@@ -29,7 +29,7 @@ describe Banzai::Pipeline::WikiPipeline do
Foo Foo
MD 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>]]') expect(output).to include('[[<em>toc</em>]]')
end end
...@@ -42,7 +42,7 @@ describe Banzai::Pipeline::WikiPipeline do ...@@ -42,7 +42,7 @@ describe Banzai::Pipeline::WikiPipeline do
Foo Foo
MD 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 aggregate_failures do
expect(output).not_to include('<ul>') expect(output).not_to include('<ul>')
......
...@@ -3,13 +3,44 @@ require 'spec_helper' ...@@ -3,13 +3,44 @@ require 'spec_helper'
describe Gitlab::Badge::Build do describe Gitlab::Badge::Build do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:sha) { project.commit.sha } 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 describe '#type' do
subject { badge.type } subject { badge.type }
it { is_expected.to eq 'image/svg+xml' } it { is_expected.to eq 'image/svg+xml' }
end 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 context 'build exists' do
let(:ci_commit) { create(:ci_commit, project: project, sha: sha) } let(:ci_commit) { create(:ci_commit, project: project, sha: sha) }
let!(:build) { create(:ci_build, commit: ci_commit) } let!(:build) { create(:ci_build, commit: ci_commit) }
......
...@@ -33,7 +33,7 @@ describe Gitlab::LDAP::Access, lib: true do ...@@ -33,7 +33,7 @@ describe Gitlab::LDAP::Access, lib: true do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
it 'should block user in GitLab' do it 'blocks user in GitLab' do
access.allowed? access.allowed?
expect(user).to be_blocked expect(user).to be_blocked
expect(user).to be_ldap_blocked expect(user).to be_ldap_blocked
...@@ -78,6 +78,31 @@ describe Gitlab::LDAP::Access, lib: true do ...@@ -78,6 +78,31 @@ describe Gitlab::LDAP::Access, lib: true do
end end
it { is_expected.to be_truthy } 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 end
end end
......
...@@ -13,7 +13,7 @@ describe Gitlab::Metrics do ...@@ -13,7 +13,7 @@ describe Gitlab::Metrics do
end end
end end
describe '#submit_metrics' do describe '.submit_metrics' do
it 'prepares and writes the metrics to InfluxDB' do it 'prepares and writes the metrics to InfluxDB' do
connection = double(:connection) connection = double(:connection)
pool = double(:pool) pool = double(:pool)
...@@ -26,7 +26,7 @@ describe Gitlab::Metrics do ...@@ -26,7 +26,7 @@ describe Gitlab::Metrics do
end end
end end
describe '#prepare_metrics' do describe '.prepare_metrics' do
it 'returns a Hash with the keys as Symbols' do it 'returns a Hash with the keys as Symbols' do
metrics = described_class. metrics = described_class.
prepare_metrics([{ 'values' => {}, 'tags' => {} }]) prepare_metrics([{ 'values' => {}, 'tags' => {} }])
...@@ -51,7 +51,7 @@ describe Gitlab::Metrics do ...@@ -51,7 +51,7 @@ describe Gitlab::Metrics do
end end
end end
describe '#escape_value' do describe '.escape_value' do
it 'escapes an equals sign' do it 'escapes an equals sign' do
expect(described_class.escape_value('foo=')).to eq('foo\\=') expect(described_class.escape_value('foo=')).to eq('foo\\=')
end end
...@@ -60,4 +60,45 @@ describe Gitlab::Metrics do ...@@ -60,4 +60,45 @@ describe Gitlab::Metrics do
expect(described_class.escape_value(10)).to eq('10') expect(described_class.escape_value(10)).to eq('10')
end end
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 end
...@@ -5,7 +5,7 @@ describe Gitlab::Saml::User, lib: true do ...@@ -5,7 +5,7 @@ describe Gitlab::Saml::User, lib: true do
let(:gl_user) { saml_user.gl_user } let(:gl_user) { saml_user.gl_user }
let(:uid) { 'my-uid' } let(:uid) { 'my-uid' }
let(:provider) { 'saml' } 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 let(:info_hash) do
{ {
name: 'John', name: 'John',
...@@ -23,10 +23,20 @@ describe Gitlab::Saml::User, lib: true do ...@@ -23,10 +23,20 @@ describe Gitlab::Saml::User, lib: true do
allow(Gitlab::LDAP::Config).to receive_messages(messages) allow(Gitlab::LDAP::Config).to receive_messages(messages)
end 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 describe 'account exists on server' do
before { stub_omniauth_config({ allow_single_sign_on: ['saml'], auto_link_saml_user: true }) } 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 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 it 'adds the SAML identity to the existing user' do
saml_user.save saml_user.save
expect(gl_user).to be_valid expect(gl_user).to be_valid
...@@ -36,6 +46,35 @@ describe Gitlab::Saml::User, lib: true do ...@@ -36,6 +46,35 @@ describe Gitlab::Saml::User, lib: true do
expect(identity.provider).to eql 'saml' expect(identity.provider).to eql 'saml'
end end
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 end
describe 'no account exists on server' do describe 'no account exists on server' do
...@@ -68,6 +107,26 @@ describe Gitlab::Saml::User, lib: true do ...@@ -68,6 +107,26 @@ describe Gitlab::Saml::User, lib: true do
end end
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 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'] }) } 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' include_examples 'to verify compliance with allow_single_sign_on'
...@@ -76,12 +135,6 @@ describe Gitlab::Saml::User, lib: true do ...@@ -76,12 +135,6 @@ describe Gitlab::Saml::User, lib: true do
context 'with auto_link_ldap_user enabled' do context 'with auto_link_ldap_user enabled' do
before { stub_omniauth_config({ auto_link_ldap_user: true, auto_link_saml_user: false }) } 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 context 'and at least one LDAP provider is defined' do
before { stub_ldap_config(providers: %w(ldapmain)) } before { stub_ldap_config(providers: %w(ldapmain)) }
...@@ -89,19 +142,18 @@ describe Gitlab::Saml::User, lib: true do ...@@ -89,19 +142,18 @@ describe Gitlab::Saml::User, lib: true do
before do before do
allow(ldap_user).to receive(:uid) { uid } allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { 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(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user) allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
end end
context 'and no account for the LDAP user' do context 'and no account for the LDAP user' do
it 'creates a user with dual LDAP and SAML identities' do it 'creates a user with dual LDAP and SAML identities' do
saml_user.save saml_user.save
expect(gl_user).to be_valid expect(gl_user).to be_valid
expect(gl_user.username).to eql uid 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 expect(gl_user.identities.length).to eql 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } 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' }, 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 ...@@ -111,13 +163,13 @@ describe Gitlab::Saml::User, lib: true do
end end
context 'and LDAP user has an account already' do 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') } 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 it 'adds the omniauth identity to the LDAP account' do
saml_user.save saml_user.save
expect(gl_user).to be_valid expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john' 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 expect(gl_user.identities.length).to eql 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } 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' }, 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 ...@@ -126,19 +178,13 @@ describe Gitlab::Saml::User, lib: true do
end end
end 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 end
end end
describe 'blocking' do 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 'signup with SAML only' do
context 'dont block on create' do context 'dont block on create' do
...@@ -162,64 +208,6 @@ describe Gitlab::Saml::User, lib: true do ...@@ -162,64 +208,6 @@ describe Gitlab::Saml::User, lib: true do
end end
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 context 'sign-in' do
before do before do
saml_user.save saml_user.save
...@@ -245,26 +233,6 @@ describe Gitlab::Saml::User, lib: true do ...@@ -245,26 +233,6 @@ describe Gitlab::Saml::User, lib: true do
expect(gl_user).not_to be_blocked expect(gl_user).not_to be_blocked
end end
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 end
end end
......
...@@ -6,18 +6,38 @@ describe BuildsEmailService do ...@@ -6,18 +6,38 @@ describe BuildsEmailService do
let(:service) { BuildsEmailService.new } let(:service) { BuildsEmailService.new }
describe :execute do describe :execute do
it "sends email" do it 'sends email' do
service.recipients = 'test@gitlab.com' service.recipients = 'test@gitlab.com'
data[:build_status] = 'failed' data[:build_status] = 'failed'
expect(BuildEmailWorker).to receive(:perform_async) expect(BuildEmailWorker).to receive(:perform_async)
service.execute(data) service.execute(data)
end 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_status] = 'failed'
data[:build_allow_failure] = true data[:build_allow_failure] = true
expect(BuildEmailWorker).not_to receive(:perform_async) expect(BuildEmailWorker).not_to receive(:perform_async)
service.execute(data) service.execute(data)
end 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
end end
...@@ -670,6 +670,19 @@ describe Repository, models: true do ...@@ -670,6 +670,19 @@ describe Repository, models: true do
repository.after_create repository.after_create
end 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 end
describe "#main_language" do describe "#main_language" do
......
...@@ -4,6 +4,7 @@ describe API::API, api: true do ...@@ -4,6 +4,7 @@ describe API::API, api: true do
include ApiHelpers include ApiHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) } let!(:project) { create(:project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project) }
let!(:milestone) { create(:milestone, project: project) } let!(:milestone) { create(:milestone, project: project) }
before { project.team << [user, :developer] } before { project.team << [user, :developer] }
...@@ -20,6 +21,24 @@ describe API::API, api: true do ...@@ -20,6 +21,24 @@ describe API::API, api: true do
get api("/projects/#{project.id}/milestones") get api("/projects/#{project.id}/milestones")
expect(response.status).to eq(401) expect(response.status).to eq(401)
end 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 end
describe 'GET /projects/:id/milestones/:milestone_id' do describe 'GET /projects/:id/milestones/:milestone_id' do
......
...@@ -40,23 +40,6 @@ describe API::API, api: true do ...@@ -40,23 +40,6 @@ describe API::API, api: true do
end end
end end
describe "GET /projects/:id/repository/tags/:tag_name" do
let(:tag_name) { project.repository.tag_names.sort.reverse.first }
it 'should return a specific tag' do
get api("/projects/#{project.id}/repository/tags/#{tag_name}", user)
expect(response.status).to eq(200)
expect(json_response['name']).to eq(tag_name)
end
it 'should return 404 for an invalid tag name' do
get api("/projects/#{project.id}/repository/tags/foobar", user)
expect(response.status).to eq(404)
end
end
describe 'POST /projects/:id/repository/tags' do describe 'POST /projects/:id/repository/tags' do
context 'lightweight tags' do context 'lightweight tags' do
it 'should create a new tag' do it 'should create a new tag' do
......
...@@ -164,21 +164,37 @@ describe GitPushService, services: true do ...@@ -164,21 +164,37 @@ describe GitPushService, services: true do
end end
context "after push" do context "after push" do
before do def execute
@service = execute_service(project, user, @oldrev, @newrev, ref) execute_service(project, user, @oldrev, @newrev, ref)
end end
context "to master" do context "to master" do
let(:ref) { @ref } let(:ref) { @ref }
it { expect(@service.update_main_language).to eq(true) } context 'when main_language is nil' do
it { expect(project.main_language).to eq("Ruby") } 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 end
context "to other branch" do context "to other branch" do
let(:ref) { 'refs/heads/feature/branch' } let(:ref) { 'refs/heads/feature/branch' }
it { expect(@service.update_main_language).to eq(nil) }
it { expect(project.main_language).to eq(nil) } it { expect(project.main_language).to eq(nil) }
end end
end end
......
...@@ -13,7 +13,7 @@ module MarkdownMatchers ...@@ -13,7 +13,7 @@ module MarkdownMatchers
set_default_markdown_messages set_default_markdown_messages
match do |actual| 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"]') image = actual.at_css('img[alt="Relative Image"]')
expect(link['href']).to end_with('master/doc/README.md') expect(link['href']).to end_with('master/doc/README.md')
...@@ -72,14 +72,15 @@ module MarkdownMatchers ...@@ -72,14 +72,15 @@ module MarkdownMatchers
have_css("img[src$='#{src}']") have_css("img[src$='#{src}']")
end end
prefix = '/namespace1/gitlabhq/wikis'
set_default_markdown_messages set_default_markdown_messages
match do |actual| match do |actual|
expect(actual).to have_link('linked-resource', href: 'linked-resource') expect(actual).to have_link('linked-resource', href: "#{prefix}/linked-resource")
expect(actual).to have_link('link-text', href: '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('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_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') expect(actual).to have_image('http://example.com/images/example.jpg')
end end
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