mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-14 15:16:04 +10:00
Merge branch 'master' into issue_14952
This commit is contained in:
@@ -2,7 +2,6 @@ image: "ruby:2.1"
|
||||
|
||||
services:
|
||||
- mysql:latest
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
|
||||
cache:
|
||||
@@ -35,123 +34,79 @@ spec:feature:
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:api:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:models:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:lib:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:services:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:other:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:half:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:rest:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:other:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
teaspoon:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec teaspoon
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
rubocop:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rubocop
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
scss-lint:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake scss_lint
|
||||
tags:
|
||||
- ruby
|
||||
|
||||
brakeman:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake brakeman
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
flog:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake flog
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
flay:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake flay
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
bundler:audit:
|
||||
stage: test
|
||||
@@ -159,9 +114,6 @@ bundler:audit:
|
||||
- master
|
||||
script:
|
||||
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
# Ruby 2.2 jobs
|
||||
|
||||
@@ -177,9 +129,6 @@ spec:feature:ruby22:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:api:ruby22:
|
||||
stage: test
|
||||
@@ -192,9 +141,6 @@ spec:api:ruby22:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:models:ruby22:
|
||||
stage: test
|
||||
@@ -207,9 +153,6 @@ spec:models:ruby22:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:lib:ruby22:
|
||||
stage: test
|
||||
@@ -222,9 +165,6 @@ spec:lib:ruby22:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:services:ruby22:
|
||||
stage: test
|
||||
@@ -237,9 +177,6 @@ spec:services:ruby22:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:other:ruby22:
|
||||
stage: test
|
||||
@@ -252,9 +189,6 @@ spec:other:ruby22:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:half:ruby22:
|
||||
stage: test
|
||||
@@ -268,9 +202,6 @@ spinach:project:half:ruby22:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:rest:ruby22:
|
||||
stage: test
|
||||
@@ -284,9 +215,6 @@ spinach:project:rest:ruby22:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:other:ruby22:
|
||||
stage: test
|
||||
@@ -300,10 +228,6 @@ spinach:other:ruby22:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
|
||||
notify:slack:
|
||||
stage: notifications
|
||||
|
||||
@@ -14,6 +14,7 @@ v 8.7.0 (unreleased)
|
||||
- Add links to CI setup documentation from project settings and builds pages
|
||||
- Handle nil descriptions in Slack issue messages (Stan Hu)
|
||||
- Add default scope to projects to exclude projects pending deletion
|
||||
- Ensure empty recipients are rejected in BuildsEmailService
|
||||
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
|
||||
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
|
||||
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
|
||||
@@ -22,6 +23,7 @@ v 8.7.0 (unreleased)
|
||||
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
|
||||
- Improved UX of the navigation sidebar
|
||||
- Build status notifications
|
||||
- API: Expose user location (Robert Schilling)
|
||||
|
||||
v 8.6.5 (unreleased)
|
||||
- Check permissions when user attempts to import members from another project
|
||||
|
||||
@@ -177,10 +177,11 @@ class GitLabDropdown
|
||||
selector = ".dropdown-page-one .dropdown-content a"
|
||||
|
||||
@dropdown.on "click", selector, (e) ->
|
||||
selected = self.rowClicked $(@)
|
||||
$el = $(@)
|
||||
selected = self.rowClicked $el
|
||||
|
||||
if self.options.clicked
|
||||
self.options.clicked(selected)
|
||||
self.options.clicked(selected, $el, e)
|
||||
|
||||
# Finds an element inside wrapper element
|
||||
getElement: (selector) ->
|
||||
@@ -360,6 +361,8 @@ class GitLabDropdown
|
||||
# Toggle the dropdown label
|
||||
if @options.toggleLabel
|
||||
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel
|
||||
else
|
||||
selectedObject
|
||||
else
|
||||
if !value?
|
||||
field.remove()
|
||||
@@ -375,7 +378,7 @@ class GitLabDropdown
|
||||
if @options.toggleLabel
|
||||
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
|
||||
if value?
|
||||
if !field.length
|
||||
if !field.length and fieldName
|
||||
# Create hidden input for form
|
||||
input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
|
||||
if @options.inputId?
|
||||
@@ -394,7 +397,7 @@ class GitLabDropdown
|
||||
selector = ".dropdown-page-one #{selector}"
|
||||
|
||||
# simulate a click on the first link
|
||||
$(selector).trigger "click"
|
||||
$(selector, @dropdown).trigger "click"
|
||||
|
||||
addArrowKeyEvent: ->
|
||||
ARROW_KEY_CODES = [38, 40]
|
||||
|
||||
@@ -26,6 +26,20 @@
|
||||
|
||||
$(".selected_issue").bind "change", Issues.checkChanged
|
||||
|
||||
# Update state filters if present in page
|
||||
updateStateFilters: ->
|
||||
stateFilters = $('.issues-state-filters')
|
||||
newParams = {}
|
||||
paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search']
|
||||
|
||||
for paramKey in paramKeys
|
||||
newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or ''
|
||||
|
||||
if stateFilters.length
|
||||
stateFilters.find('a').each ->
|
||||
initialUrl = $(this).attr 'href'
|
||||
$(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl)
|
||||
|
||||
# Make sure we trigger ajax request only after user stop typing
|
||||
initSearch: ->
|
||||
@timer = null
|
||||
@@ -54,6 +68,7 @@
|
||||
# Change url so if user reload a page - search results are saved
|
||||
history.replaceState {page: issuesUrl}, document.title, issuesUrl
|
||||
Issues.reload()
|
||||
Issues.updateStateFilters()
|
||||
dataType: "json"
|
||||
|
||||
checkChanged: ->
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
((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
|
||||
@@ -62,6 +62,8 @@ class @SearchAutocomplete
|
||||
search:
|
||||
fields: ['text']
|
||||
data: @getData.bind(@)
|
||||
selectable: true
|
||||
clicked: @onClick.bind(@)
|
||||
|
||||
getData: (term, callback) ->
|
||||
_this = @
|
||||
@@ -102,6 +104,8 @@ class @SearchAutocomplete
|
||||
lastCategory = suggestion.category
|
||||
|
||||
data.push
|
||||
id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}"
|
||||
category: suggestion.category
|
||||
text: suggestion.label
|
||||
url: suggestion.url
|
||||
|
||||
@@ -133,12 +137,19 @@ class @SearchAutocomplete
|
||||
}
|
||||
|
||||
bindEvents: ->
|
||||
$(document).on 'click', @onDocumentClick
|
||||
@searchInput.on 'keydown', @onSearchInputKeyDown
|
||||
@searchInput.on 'keyup', @onSearchInputKeyUp
|
||||
@searchInput.on 'click', @onSearchInputClick
|
||||
@searchInput.on 'focus', @onSearchInputFocus
|
||||
@searchInput.on 'blur', @onSearchInputBlur
|
||||
@clearInput.on 'click', @onRemoveLocationClick
|
||||
@clearInput.on 'click', @onClearInputClick
|
||||
|
||||
onDocumentClick: (e) =>
|
||||
# If clicking outside the search box
|
||||
# And search input is not focused
|
||||
# And we are not clicking inside a suggestion
|
||||
if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length
|
||||
@onSearchInputBlur()
|
||||
|
||||
enableAutocomplete: ->
|
||||
# No need to enable anything if user is not logged in
|
||||
@@ -181,6 +192,8 @@ class @SearchAutocomplete
|
||||
# We should display the menu only when input is not empty
|
||||
@enableAutocomplete()
|
||||
|
||||
@wrap.toggleClass 'has-value', !!e.target.value
|
||||
|
||||
# Avoid falsy value to be returned
|
||||
return
|
||||
|
||||
@@ -189,27 +202,20 @@ class @SearchAutocomplete
|
||||
e.stopImmediatePropagation()
|
||||
|
||||
onSearchInputFocus: =>
|
||||
@isFocused = true
|
||||
@wrap.addClass('search-active')
|
||||
|
||||
onRemoveLocationClick: (e) =>
|
||||
onClearInputClick: (e) =>
|
||||
e.preventDefault()
|
||||
@removeLocationBadge()
|
||||
@searchInput.val('').focus()
|
||||
@skipBlurEvent = true
|
||||
|
||||
onSearchInputBlur: (e) =>
|
||||
@skipBlurEvent = false
|
||||
@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 ''
|
||||
@restoreOriginalState()
|
||||
, 150)
|
||||
# If input is blank then restore state
|
||||
if @searchInput.val() is ''
|
||||
@restoreOriginalState()
|
||||
|
||||
addLocationBadge: (item) ->
|
||||
category = if item.category? then "#{item.category}: " else ''
|
||||
@@ -268,3 +274,23 @@ class @SearchAutocomplete
|
||||
<li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li>
|
||||
</ul>"
|
||||
@dropdownContent.html(html)
|
||||
|
||||
onClick: (item, $el, e) ->
|
||||
if location.pathname.indexOf(item.url) isnt -1
|
||||
e.preventDefault()
|
||||
if not @badgePresent
|
||||
if item.category is 'Projects'
|
||||
@projectInputEl.val(item.id)
|
||||
@addLocationBadge(
|
||||
value: 'This project'
|
||||
)
|
||||
|
||||
if item.category is 'Groups'
|
||||
@groupInputEl.val(item.id)
|
||||
@addLocationBadge(
|
||||
value: 'This group'
|
||||
)
|
||||
|
||||
$el.removeClass('is-active')
|
||||
@disableAutocomplete()
|
||||
@searchInput.val('').focus()
|
||||
|
||||
@@ -104,9 +104,9 @@ $orange-light: rgba(252, 109, 38, 0.80);
|
||||
$orange-normal: #e75e40;
|
||||
$orange-dark: #ce5237;
|
||||
|
||||
$red-light: #f06559;
|
||||
$red-normal: #e52c5a;
|
||||
$red-dark: #d22852;
|
||||
$red-light: #e52c5a;
|
||||
$red-normal: #d22852;
|
||||
$red-dark: darken($red-normal, 5%);
|
||||
|
||||
$border-white-light: #f1f2f4;
|
||||
$border-white-normal: #d6dae2;
|
||||
@@ -128,9 +128,9 @@ $border-orange-light: #fc6d26;
|
||||
$border-orange-normal: #ce5237;
|
||||
$border-orange-dark: #c14e35;
|
||||
|
||||
$border-red-light: #f24f41;
|
||||
$border-red-normal: #d22852;
|
||||
$border-red-dark: #ca264f;
|
||||
$border-red-light: #d22852;
|
||||
$border-red-normal: #ca264f;
|
||||
$border-red-dark: darken($border-red-normal, 5%);
|
||||
|
||||
$help-well-bg: #fafafa;
|
||||
$help-well-border: #e5e5e5;
|
||||
@@ -201,14 +201,14 @@ $award-emoji-new-btn-icon-color: #dcdcdc;
|
||||
/*
|
||||
* Search Box
|
||||
*/
|
||||
$search-input-border-color: $dropdown-input-focus-border;
|
||||
$search-input-border-color: rgba(#4688f1, .8);
|
||||
$search-input-focus-shadow-color: $dropdown-input-focus-shadow;
|
||||
$search-input-width: $dropdown-width;
|
||||
$search-input-width: 244px;
|
||||
$location-badge-color: #aaa;
|
||||
$location-badge-bg: $gray-normal;
|
||||
$location-badge-active-bg: #4f91f8;
|
||||
$location-icon-color: #e7e9ed;
|
||||
$location-active-color: $gl-text-color;
|
||||
$location-active-bg: $search-input-border-color;
|
||||
$location-icon-active-color: #807e7e;
|
||||
|
||||
/*
|
||||
* Notes
|
||||
|
||||
@@ -315,7 +315,7 @@ pre.light-well {
|
||||
}
|
||||
|
||||
.git-empty {
|
||||
margin: 0 7px;
|
||||
margin: 0 7px 7px;
|
||||
|
||||
h5 {
|
||||
color: #5c5d5e;
|
||||
@@ -401,7 +401,7 @@ pre.light-well {
|
||||
}
|
||||
|
||||
.commit_short_id {
|
||||
margin-right: 5px;
|
||||
margin: 0 5px;
|
||||
color: $gl-link-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -135,25 +135,25 @@
|
||||
|
||||
.location-badge {
|
||||
@include transition(all .15s);
|
||||
background-color: $location-active-bg;
|
||||
background-color: $location-badge-active-bg;
|
||||
color: $white-light;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
i {
|
||||
color: $location-active-color;
|
||||
color: $location-icon-active-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-location-badge {
|
||||
.search-icon {
|
||||
display: none;
|
||||
}
|
||||
&.has-value {
|
||||
.search-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
.clear-icon {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.container-fluid .content {
|
||||
.container-fluid {
|
||||
.ci-status {
|
||||
padding: 2px 7px;
|
||||
margin-right: 5px;
|
||||
|
||||
@@ -50,12 +50,15 @@ class BuildsEmailService < Service
|
||||
|
||||
def execute(push_data)
|
||||
return unless supported_events.include?(push_data[:object_kind])
|
||||
return unless should_build_be_notified?(push_data)
|
||||
|
||||
if should_build_be_notified?(push_data)
|
||||
recipients = all_recipients(push_data)
|
||||
|
||||
if recipients.any?
|
||||
BuildEmailWorker.perform_async(
|
||||
push_data[:build_id],
|
||||
all_recipients(push_data),
|
||||
push_data,
|
||||
recipients,
|
||||
push_data
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -84,7 +87,7 @@ class BuildsEmailService < Service
|
||||
end
|
||||
|
||||
def all_recipients(data)
|
||||
all_recipients = recipients.split(',')
|
||||
all_recipients = recipients.split(',').compact.reject(&:blank?)
|
||||
|
||||
if add_pusher? && data[:user][:email]
|
||||
all_recipients << "#{data[:user][:email]}"
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
%a.is-focused.dropdown-menu-empty-link
|
||||
Loading...
|
||||
= dropdown_loading
|
||||
%i.search-icon
|
||||
%i.clear-icon.js-clear-input
|
||||
%i.search-icon
|
||||
%i.clear-icon.js-clear-input
|
||||
|
||||
= hidden_field_tag :group_id, @group.try(:id)
|
||||
= hidden_field_tag :project_id, @project && @project.persisted? ? @project.id : '', id: 'search_project_id'
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
= access
|
||||
= link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do
|
||||
= icon('pencil-square-o')
|
||||
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete danger' do
|
||||
= link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button js-note-delete' do
|
||||
= icon('trash-o')
|
||||
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
|
||||
.note-text
|
||||
|
||||
@@ -69,6 +69,7 @@ GET /users
|
||||
"state": "blocked",
|
||||
"created_at": "2012-05-23T08:01:01Z",
|
||||
"bio": null,
|
||||
"location": null,
|
||||
"skype": "",
|
||||
"linkedin": "",
|
||||
"twitter": "",
|
||||
@@ -126,6 +127,7 @@ Parameters:
|
||||
"created_at": "2012-05-23T08:00:58Z",
|
||||
"is_admin": false,
|
||||
"bio": null,
|
||||
"location": null,
|
||||
"skype": "",
|
||||
"linkedin": "",
|
||||
"twitter": "",
|
||||
@@ -154,6 +156,7 @@ Parameters:
|
||||
"confirmed_at": "2012-05-23T08:00:58Z",
|
||||
"last_sign_in_at": "2015-03-23T08:00:58Z",
|
||||
"bio": null,
|
||||
"location": null,
|
||||
"skype": "",
|
||||
"linkedin": "",
|
||||
"twitter": "",
|
||||
@@ -191,6 +194,7 @@ Parameters:
|
||||
- `extern_uid` (optional) - External UID
|
||||
- `provider` (optional) - External provider name
|
||||
- `bio` (optional) - User's biography
|
||||
- `location` (optional) - User's location
|
||||
- `admin` (optional) - User is admin - true or false (default)
|
||||
- `can_create_group` (optional) - User can create groups - true or false
|
||||
- `confirm` (optional) - Require confirmation - true (default) or false
|
||||
@@ -218,6 +222,7 @@ Parameters:
|
||||
- `extern_uid` - External UID
|
||||
- `provider` - External provider name
|
||||
- `bio` - User's biography
|
||||
- `location` (optional) - User's location
|
||||
- `admin` (optional) - User is admin - true or false (default)
|
||||
- `can_create_group` (optional) - User can create groups - true or false
|
||||
- `external` (optional) - Flags the user as external - true or false(default)
|
||||
@@ -260,6 +265,7 @@ GET /user
|
||||
"state": "active",
|
||||
"created_at": "2012-05-23T08:00:58Z",
|
||||
"bio": null,
|
||||
"location": null,
|
||||
"skype": "",
|
||||
"linkedin": "",
|
||||
"twitter": "",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- [CI setup](ci_setup.md) for testing GitLab
|
||||
- [Gotchas](gotchas.md) to avoid
|
||||
- [How to dump production data to staging](db_dump.md)
|
||||
- [Instrumentation](instrumentation.md)
|
||||
- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
|
||||
- [Rake tasks](rake_tasks.md) for development
|
||||
- [Shell commands](shell_commands.md) in the GitLab codebase
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# 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
|
||||
```
|
||||
+1
-1
@@ -15,7 +15,7 @@ module API
|
||||
class User < UserBasic
|
||||
expose :created_at
|
||||
expose :is_admin?, as: :is_admin
|
||||
expose :bio, :skype, :linkedin, :twitter, :website_url
|
||||
expose :bio, :location, :skype, :linkedin, :twitter, :website_url
|
||||
end
|
||||
|
||||
class Identity < Grape::Entity
|
||||
|
||||
+4
-2
@@ -58,6 +58,7 @@ module API
|
||||
# extern_uid - External authentication provider UID
|
||||
# provider - External provider
|
||||
# bio - Bio
|
||||
# location - Location of the user
|
||||
# admin - User is admin - true or false (default)
|
||||
# can_create_group - User can create groups - true or false
|
||||
# confirm - Require user confirmation - true (default) or false
|
||||
@@ -67,7 +68,7 @@ module API
|
||||
post do
|
||||
authenticated_as_admin!
|
||||
required_attributes! [:email, :password, :name, :username]
|
||||
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin, :confirm, :external]
|
||||
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :confirm, :external]
|
||||
admin = attrs.delete(:admin)
|
||||
confirm = !(attrs.delete(:confirm) =~ (/(false|f|no|0)$/i))
|
||||
user = User.build_user(attrs)
|
||||
@@ -106,6 +107,7 @@ module API
|
||||
# website_url - Website url
|
||||
# projects_limit - Limit projects each user can create
|
||||
# bio - Bio
|
||||
# location - Location of the user
|
||||
# admin - User is admin - true or false (default)
|
||||
# can_create_group - User can create groups - true or false
|
||||
# external - Flags the user as external - true or false(default)
|
||||
@@ -114,7 +116,7 @@ module API
|
||||
put ":id" do
|
||||
authenticated_as_admin!
|
||||
|
||||
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin, :external]
|
||||
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :location, :can_create_group, :admin, :external]
|
||||
user = User.find(params[:id])
|
||||
not_found!('User') unless user
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ module Banzai
|
||||
|
||||
elsif element_node?(node)
|
||||
yield_valid_link(node) do |link, text|
|
||||
if ref_pattern && link =~ /\A#{ref_pattern}/
|
||||
if ref_pattern && link =~ /\A#{ref_pattern}\z/
|
||||
replace_link_node_with_href(node, link) do
|
||||
object_link_filter(link, ref_pattern, link_text: text)
|
||||
end
|
||||
|
||||
@@ -70,6 +70,32 @@ module Gitlab
|
||||
value.to_s.gsub('=', '\\=')
|
||||
end
|
||||
|
||||
# Measures the execution time of a block.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Gitlab::Metrics.measure(:find_by_username_timings) do
|
||||
# User.find_by_username(some_username)
|
||||
# end
|
||||
#
|
||||
# series - The name of the series to store the data in.
|
||||
# values - A Hash containing extra values to add to the metric.
|
||||
# tags - A Hash containing extra tags to add to the metric.
|
||||
#
|
||||
# Returns the value yielded by the supplied block.
|
||||
def self.measure(series, values = {}, tags = {})
|
||||
return yield unless Transaction.current
|
||||
|
||||
start = Time.now.to_f
|
||||
retval = yield
|
||||
duration = (Time.now.to_f - start) * 1000.0
|
||||
values = values.merge(duration: duration)
|
||||
|
||||
Transaction.current.add_metric(series, values, tags)
|
||||
|
||||
retval
|
||||
end
|
||||
|
||||
# When enabled this should be set before being used as the usual pattern
|
||||
# "@foo ||= bar" is _not_ thread-safe.
|
||||
if enabled?
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
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
|
||||
@@ -95,6 +95,14 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
|
||||
result = reference_pipeline_result("Fixed #{reference}")
|
||||
expect(result[:references][:issue]).to eq [issue]
|
||||
end
|
||||
|
||||
it 'does not process links containing issue numbers followed by text' do
|
||||
href = "#{reference}st"
|
||||
doc = reference_filter("<a href='#{href}'></a>")
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).to eq(href)
|
||||
end
|
||||
end
|
||||
|
||||
context 'cross-project reference' do
|
||||
|
||||
@@ -13,7 +13,7 @@ describe Gitlab::Metrics do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#submit_metrics' do
|
||||
describe '.submit_metrics' do
|
||||
it 'prepares and writes the metrics to InfluxDB' do
|
||||
connection = double(:connection)
|
||||
pool = double(:pool)
|
||||
@@ -26,7 +26,7 @@ describe Gitlab::Metrics do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#prepare_metrics' do
|
||||
describe '.prepare_metrics' do
|
||||
it 'returns a Hash with the keys as Symbols' do
|
||||
metrics = described_class.
|
||||
prepare_metrics([{ 'values' => {}, 'tags' => {} }])
|
||||
@@ -51,7 +51,7 @@ describe Gitlab::Metrics do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#escape_value' do
|
||||
describe '.escape_value' do
|
||||
it 'escapes an equals sign' do
|
||||
expect(described_class.escape_value('foo=')).to eq('foo\\=')
|
||||
end
|
||||
@@ -60,4 +60,45 @@ describe Gitlab::Metrics do
|
||||
expect(described_class.escape_value(10)).to eq('10')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.measure' do
|
||||
context 'without a transaction' do
|
||||
it 'returns the return value of the block' do
|
||||
val = Gitlab::Metrics.measure(:foo) { 10 }
|
||||
|
||||
expect(val).to eq(10)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a transaction' do
|
||||
let(:transaction) { Gitlab::Metrics::Transaction.new }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Metrics::Transaction).to receive(:current).
|
||||
and_return(transaction)
|
||||
end
|
||||
|
||||
it 'adds a metric to the current transaction' do
|
||||
expect(transaction).to receive(:add_metric).
|
||||
with(:foo, { duration: a_kind_of(Numeric) }, { tag: 'value' })
|
||||
|
||||
Gitlab::Metrics.measure(:foo, {}, tag: 'value') { 10 }
|
||||
end
|
||||
|
||||
it 'supports adding of custom values' do
|
||||
values = { duration: a_kind_of(Numeric), number: 10 }
|
||||
|
||||
expect(transaction).to receive(:add_metric).
|
||||
with(:foo, values, { tag: 'value' })
|
||||
|
||||
Gitlab::Metrics.measure(:foo, { number: 10 }, tag: 'value') { 10 }
|
||||
end
|
||||
|
||||
it 'returns the return value of the block' do
|
||||
val = Gitlab::Metrics.measure(:foo) { 10 }
|
||||
|
||||
expect(val).to eq(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,18 +6,38 @@ describe BuildsEmailService do
|
||||
let(:service) { BuildsEmailService.new }
|
||||
|
||||
describe :execute do
|
||||
it "sends email" do
|
||||
it 'sends email' do
|
||||
service.recipients = 'test@gitlab.com'
|
||||
data[:build_status] = 'failed'
|
||||
expect(BuildEmailWorker).to receive(:perform_async)
|
||||
service.execute(data)
|
||||
end
|
||||
|
||||
it "does not sends email with failed build and allowed_failure on" do
|
||||
it 'does not send email with succeeded build and notify_only_broken_builds on' do
|
||||
expect(service).to receive(:notify_only_broken_builds).and_return(true)
|
||||
data[:build_status] = 'success'
|
||||
expect(BuildEmailWorker).not_to receive(:perform_async)
|
||||
service.execute(data)
|
||||
end
|
||||
|
||||
it 'does not send email with failed build and build_allow_failure is true' do
|
||||
data[:build_status] = 'failed'
|
||||
data[:build_allow_failure] = true
|
||||
expect(BuildEmailWorker).not_to receive(:perform_async)
|
||||
service.execute(data)
|
||||
end
|
||||
|
||||
it 'does not send email with unknown build status' do
|
||||
data[:build_status] = 'foo'
|
||||
expect(BuildEmailWorker).not_to receive(:perform_async)
|
||||
service.execute(data)
|
||||
end
|
||||
|
||||
it 'does not send email when recipients list is empty' do
|
||||
service.recipients = ' ,, '
|
||||
data[:build_status] = 'failed'
|
||||
expect(BuildEmailWorker).not_to receive(:perform_async)
|
||||
service.execute(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user