Commit 9a44d697 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'master' into decouple-member-notification

parents 73fdd4b8 15cbbd09
Please view this file on the master branch, on stable branches it's out of date. 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)
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- 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) - 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)
...@@ -45,6 +47,7 @@ v 8.6.5 ...@@ -45,6 +47,7 @@ v 8.6.5
v 8.6.4 v 8.6.4
- Don't attempt to fetch any tags from a forked repo (Stan Hu) - Don't attempt to fetch any tags from a forked repo (Stan Hu)
- Redesign the Labels page
v 8.6.3 v 8.6.3
- Mentions on confidential issues doesn't create todos for non-members. !3374 - Mentions on confidential issues doesn't create todos for non-members. !3374
......
source "https://rubygems.org" source "https://rubygems.org"
gem 'rails', '4.2.5.2' gem 'rails', '4.2.6'
gem 'rails-deprecated_sanitizer', '~> 1.0.3' gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
...@@ -8,7 +8,7 @@ gem 'responders', '~> 2.0' ...@@ -8,7 +8,7 @@ gem 'responders', '~> 2.0'
# Specify a sprockets version due to increased performance # Specify a sprockets version due to increased performance
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069 # See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
gem 'sprockets', '~> 3.3.5' gem 'sprockets', '~> 3.6.0'
# Default values for AR models # Default values for AR models
gem "default_value_for", "~> 3.0.0" gem "default_value_for", "~> 3.0.0"
...@@ -149,6 +149,10 @@ gem 'version_sorter', '~> 2.0.0' ...@@ -149,6 +149,10 @@ gem 'version_sorter', '~> 2.0.0'
# Cache # Cache
gem "redis-rails", '~> 4.0.0' gem "redis-rails", '~> 4.0.0'
# Redis
gem 'redis', '~> 3.2'
gem 'connection_pool', '~> 2.0'
# Campfire integration # Campfire integration
gem 'tinder', '~> 1.10.0' gem 'tinder', '~> 1.10.0'
...@@ -229,14 +233,13 @@ group :metrics do ...@@ -229,14 +233,13 @@ group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri gem 'allocations', '~> 1.0', require: false, platform: :mri
gem 'method_source', '~> 0.8', require: false gem 'method_source', '~> 0.8', require: false
gem 'influxdb', '~> 0.2', require: false gem 'influxdb', '~> 0.2', require: false
gem 'connection_pool', '~> 2.0', require: false
end end
group :development do group :development do
gem "foreman" gem "foreman"
gem 'brakeman', '~> 3.2.0', require: false gem 'brakeman', '~> 3.2.0', require: false
gem "annotate", "~> 2.6.0" gem "annotate", "~> 2.7.0"
gem "letter_opener", '~> 1.1.2' gem "letter_opener", '~> 1.1.2'
gem 'quiet_assets', '~> 1.0.2' gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0' gem 'rerun', '~> 0.11.0'
......
...@@ -4,41 +4,41 @@ GEM ...@@ -4,41 +4,41 @@ GEM
CFPropertyList (2.3.2) CFPropertyList (2.3.2)
RedCloth (4.2.9) RedCloth (4.2.9)
ace-rails-ap (2.0.1) ace-rails-ap (2.0.1)
actionmailer (4.2.5.2) actionmailer (4.2.6)
actionpack (= 4.2.5.2) actionpack (= 4.2.6)
actionview (= 4.2.5.2) actionview (= 4.2.6)
activejob (= 4.2.5.2) activejob (= 4.2.6)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.5.2) actionpack (4.2.6)
actionview (= 4.2.5.2) actionview (= 4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
rack (~> 1.6) rack (~> 1.6)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.5.2) actionview (4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.5.2) activejob (4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
globalid (>= 0.3.0) globalid (>= 0.3.0)
activemodel (4.2.5.2) activemodel (4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
builder (~> 3.1) builder (~> 3.1)
activerecord (4.2.5.2) activerecord (4.2.6)
activemodel (= 4.2.5.2) activemodel (= 4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
arel (~> 6.0) arel (~> 6.0)
activerecord-deprecated_finders (1.0.4) activerecord-deprecated_finders (1.0.4)
activerecord-session_store (0.1.2) activerecord-session_store (0.1.2)
actionpack (>= 4.0.0, < 5) actionpack (>= 4.0.0, < 5)
activerecord (>= 4.0.0, < 5) activerecord (>= 4.0.0, < 5)
railties (>= 4.0.0, < 5) railties (>= 4.0.0, < 5)
activesupport (4.2.5.2) activesupport (4.2.6)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
minitest (~> 5.1) minitest (~> 5.1)
...@@ -51,8 +51,8 @@ GEM ...@@ -51,8 +51,8 @@ GEM
activerecord (>= 3.0) activerecord (>= 3.0)
akismet (2.0.0) akismet (2.0.0)
allocations (1.0.4) allocations (1.0.4)
annotate (2.6.10) annotate (2.7.0)
activerecord (>= 3.2, <= 4.3) activerecord (>= 3.2, < 6.0)
rake (~> 10.4) rake (~> 10.4)
arel (6.0.3) arel (6.0.3)
asana (0.4.0) asana (0.4.0)
...@@ -145,7 +145,7 @@ GEM ...@@ -145,7 +145,7 @@ GEM
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
creole (0.5.0) creole (0.5.0)
css_parser (1.3.7) css_parser (1.4.1)
addressable addressable
d3_rails (3.5.11) d3_rails (3.5.11)
railties (>= 3.1.0) railties (>= 3.1.0)
...@@ -459,8 +459,8 @@ GEM ...@@ -459,8 +459,8 @@ GEM
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
macaddr (1.7.1) macaddr (1.7.1)
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.3) mail (2.6.4)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 4)
mail_room (0.6.1) mail_room (0.6.1)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
...@@ -559,8 +559,8 @@ GEM ...@@ -559,8 +559,8 @@ GEM
premailer (1.8.6) premailer (1.8.6)
css_parser (>= 1.3.6) css_parser (>= 1.3.6)
htmlentities (>= 4.0.0) htmlentities (>= 4.0.0)
premailer-rails (1.9.0) premailer-rails (1.9.2)
actionmailer (>= 3, < 5) actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
pry (0.10.3) pry (0.10.3)
coderay (~> 1.1.0) coderay (~> 1.1.0)
...@@ -589,16 +589,16 @@ GEM ...@@ -589,16 +589,16 @@ GEM
rack rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.5.2) rails (4.2.6)
actionmailer (= 4.2.5.2) actionmailer (= 4.2.6)
actionpack (= 4.2.5.2) actionpack (= 4.2.6)
actionview (= 4.2.5.2) actionview (= 4.2.6)
activejob (= 4.2.5.2) activejob (= 4.2.6)
activemodel (= 4.2.5.2) activemodel (= 4.2.6)
activerecord (= 4.2.5.2) activerecord (= 4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.2.5.2) railties (= 4.2.6)
sprockets-rails sprockets-rails
rails-deprecated_sanitizer (1.0.3) rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha) activesupport (>= 4.2.0.alpha)
...@@ -608,9 +608,9 @@ GEM ...@@ -608,9 +608,9 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1) rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3) rails-html-sanitizer (1.0.3)
loofah (~> 2.0) loofah (~> 2.0)
railties (4.2.5.2) railties (4.2.6)
actionpack (= 4.2.5.2) actionpack (= 4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.1.0) rainbow (2.1.0)
...@@ -776,12 +776,13 @@ GEM ...@@ -776,12 +776,13 @@ GEM
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2) spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1) spring (>= 0.9.1)
sprockets (3.3.5) sprockets (3.6.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (2.3.3) sprockets-rails (3.0.4)
actionpack (>= 3.0) actionpack (>= 4.0)
activesupport (>= 3.0) activesupport (>= 4.0)
sprockets (>= 2.8, < 4.0) sprockets (>= 3.0.0)
state_machines (0.4.0) state_machines (0.4.0)
state_machines-activemodel (0.3.0) state_machines-activemodel (0.3.0)
activemodel (~> 4.1) activemodel (~> 4.1)
...@@ -887,7 +888,7 @@ DEPENDENCIES ...@@ -887,7 +888,7 @@ DEPENDENCIES
after_commit_queue after_commit_queue
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
annotate (~> 2.6.0) annotate (~> 2.7.0)
asana (~> 0.4.0) asana (~> 0.4.0)
asciidoctor (~> 1.5.2) asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4) attr_encrypted (~> 1.3.4)
...@@ -992,13 +993,14 @@ DEPENDENCIES ...@@ -992,13 +993,14 @@ DEPENDENCIES
rack-attack (~> 4.3.1) rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rails (= 4.2.5.2) rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
raphael-rails (~> 2.1.2) raphael-rails (~> 2.1.2)
rblineprof rblineprof
rdoc (~> 3.6) rdoc (~> 3.6)
recaptcha recaptcha
redcarpet (~> 3.3.3) redcarpet (~> 3.3.3)
redis (~> 3.2)
redis-namespace redis-namespace
redis-rails (~> 4.0.0) redis-rails (~> 4.0.0)
request_store (~> 1.3.0) request_store (~> 1.3.0)
...@@ -1032,7 +1034,7 @@ DEPENDENCIES ...@@ -1032,7 +1034,7 @@ DEPENDENCIES
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0) spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.3.5) sprockets (~> 3.6.0)
state_machines-activerecord (~> 0.3.0) state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2) task_list (~> 1.0.2)
teaspoon (~> 1.1.0) teaspoon (~> 1.1.0)
......
...@@ -22,8 +22,19 @@ class @AwardsHandler ...@@ -22,8 +22,19 @@ class @AwardsHandler
emoji = $(this) emoji = $(this)
.find(".icon") .find(".icon")
.data "emoji" .data "emoji"
if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown"
awards_handler.addAward "thumbsdown"
else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup"
awards_handler.addAward "thumbsup"
awards_handler.addAward emoji awards_handler.addAward emoji
didUserClickEmoji: (that, emoji) ->
if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
$(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
showEmojiMenu: -> showEmojiMenu: ->
if $(".emoji-menu").length if $(".emoji-menu").length
if $(".emoji-menu").is ".is-visible" if $(".emoji-menu").is ".is-visible"
...@@ -105,7 +116,7 @@ class @AwardsHandler ...@@ -105,7 +116,7 @@ class @AwardsHandler
if origTitle if origTitle
authors = origTitle.split(', ') authors = origTitle.split(', ')
authors.push("me") authors.push("me")
award_block.attr("title", authors.join(", ")) award_block.attr("data-original-title", authors.join(", "))
@resetTooltip(award_block) @resetTooltip(award_block)
resetTooltip: (award) -> resetTooltip: (award) ->
...@@ -122,7 +133,7 @@ class @AwardsHandler ...@@ -122,7 +133,7 @@ class @AwardsHandler
nodes = [] nodes = []
nodes.push( nodes.push(
"<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>", "<button class='btn award-control js-emoji-btn has-tooltip active' data-original-title='me'>",
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>", "<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
"<span class='award-control-text js-counter'>1</span>", "<span class='award-control-text js-counter'>1</span>",
"</button>" "</button>"
......
class @Compare
constructor: (@opts) ->
@source_loading = $ ".js-source-loading"
@target_loading = $ ".js-target-loading"
$('.js-compare-dropdown').each (i, dropdown) =>
$dropdown = $(dropdown)
$dropdown.glDropdown(
selectable: true
fieldName: $dropdown.data 'field-name'
filterable: true
id: (obj, $el) ->
$el.data 'id'
toggleLabel: (obj, $el) ->
$el.text().trim()
clicked: (e, el) =>
if $dropdown.is '.js-target-branch'
@getTargetHtml()
else if $dropdown.is '.js-source-branch'
@getSourceHtml()
else if $dropdown.is '.js-target-project'
@getTargetProject()
)
@initialState()
initialState: ->
@getSourceHtml()
@getTargetHtml()
getTargetProject: ->
$.ajax(
url: @opts.targetProjectUrl
data:
target_project_id: $("input[name='merge_request[target_project_id]']").val()
beforeSend: ->
$('.mr_target_commit').empty()
success: (html) ->
$('.js-target-branch-dropdown .dropdown-content').html html
)
getSourceHtml: ->
@sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit',
ref: $("input[name='merge_request[source_branch]']").val()
)
getTargetHtml: ->
@sendAjax(@opts.targetBranchUrl, @target_loading, '.mr_target_commit',
target_project_id: $("input[name='merge_request[target_project_id]']").val()
ref: $("input[name='merge_request[target_branch]']").val()
)
sendAjax: (url, loading, target, data) ->
$target = $(target)
$.ajax(
url: url
data: data
beforeSend: ->
loading.show()
$target.empty()
success: (html) ->
loading.hide()
$target.html html
$('.js-timeago', $target).timeago()
)
...@@ -57,14 +57,30 @@ class GitLabDropdownFilter ...@@ -57,14 +57,30 @@ class GitLabDropdownFilter
filter: (search_text) -> filter: (search_text) ->
data = @options.data() data = @options.data()
if data?
results = data results = data
if search_text isnt "" if search_text isnt ''
results = fuzzaldrinPlus.filter(data, search_text, results = fuzzaldrinPlus.filter(data, search_text,
key: @options.keys key: @options.keys
) )
@options.callback results @options.callback results
else
elements = @options.elements()
if search_text
elements.each ->
$el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
if matches.length
$el.show()
else
$el.hide()
else
elements.show()
class GitLabDropdownRemote class GitLabDropdownRemote
constructor: (@dataEndpoint, @options) -> constructor: (@dataEndpoint, @options) ->
...@@ -123,7 +139,7 @@ class GitLabDropdown ...@@ -123,7 +139,7 @@ class GitLabDropdown
if _.isString(@filterInput) if _.isString(@filterInput)
@filterInput = @getElement(@filterInput) @filterInput = @getElement(@filterInput)
search_fields = if @options.search then @options.search.fields else []; searchFields = if @options.search then @options.search.fields else [];
if @options.data if @options.data
# If data is an array # If data is an array
...@@ -147,7 +163,14 @@ class GitLabDropdown ...@@ -147,7 +163,14 @@ class GitLabDropdown
filterInputBlur: @filterInputBlur filterInputBlur: @filterInputBlur
remote: @options.filterRemote remote: @options.filterRemote
query: @options.data query: @options.data
keys: @options.search.fields keys: searchFields
elements: =>
selector = '.dropdown-content li:not(.divider)'
if @dropdown.find('.dropdown-toggle-page').length
selector = ".dropdown-page-one #{selector}"
return $(selector)
data: => data: =>
return @fullData return @fullData
callback: (data) => callback: (data) =>
...@@ -376,7 +399,7 @@ class GitLabDropdown ...@@ -376,7 +399,7 @@ class GitLabDropdown
# Toggle the dropdown label # Toggle the dropdown label
if @options.toggleLabel if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject) $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject, el)
if value? if value?
if !field.length and fieldName if !field.length and fieldName
# Create hidden input for form # Create hidden input for form
......
...@@ -73,6 +73,7 @@ class @MergeRequestTabs ...@@ -73,6 +73,7 @@ class @MergeRequestTabs
@expandView() @expandView()
else if action == 'diffs' else if action == 'diffs'
@loadDiff($target.attr('href')) @loadDiff($target.attr('href'))
if bp? and bp.getBreakpointSize() isnt 'lg'
@shrinkView() @shrinkView()
else if action == 'builds' else if action == 'builds'
@loadBuilds($target.attr('href')) @loadBuilds($target.attr('href'))
......
...@@ -10,10 +10,10 @@ class @Subscription ...@@ -10,10 +10,10 @@ class @Subscription
btn = $(event.currentTarget) btn = $(event.currentTarget)
action = btn.find('span').text() action = btn.find('span').text()
current_status = @subscription_status.attr('data-status') current_status = @subscription_status.attr('data-status')
btn.prop('disabled', true) btn.addClass('disabled')
$.post @url, => $.post @url, =>
btn.prop('disabled', false) btn.removeClass('disabled')
status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed' status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed'
@subscription_status.attr('data-status', status) @subscription_status.attr('data-status', status)
action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe' action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
&:focus, &:focus,
&:active { &:active {
outline: none; outline: none;
background-color: $btn-active-gray;
@include box-shadow($gl-btn-active-background); @include box-shadow($gl-btn-active-background);
} }
} }
...@@ -27,7 +28,8 @@ ...@@ -27,7 +28,8 @@
color: $color; color: $color;
} }
&:active { &:active,
&.active {
@include box-shadow ($gl-btn-active-background); @include box-shadow ($gl-btn-active-background);
background-color: $dark; background-color: $dark;
...@@ -61,7 +63,7 @@ ...@@ -61,7 +63,7 @@
} }
@mixin btn-white { @mixin btn-white {
@include btn-color($white-light, $border-white-light, $white-normal, $border-white-normal, $white-dark, $border-white-dark, #313236); @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active);
} }
.btn { .btn {
...@@ -218,3 +220,26 @@ ...@@ -218,3 +220,26 @@
margin-right: 5px; margin-right: 5px;
} }
} }
.btn-text-field {
width: 100%;
text-align: left;
padding: 6px 16px;
border-color: $border-color;
color: $btn-placeholder-gray;
background-color: $background-color;
&:hover,
&:active,
&:focus {
cursor: text;
box-shadow: none;
border-color: $border-color;
color: $btn-placeholder-gray;
background-color: $background-color;
}
}
.btn-file-option {
background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
}
...@@ -248,7 +248,7 @@ ...@@ -248,7 +248,7 @@
.dropdown-title { .dropdown-title {
position: relative; position: relative;
padding: 0 0 15px; padding: 0 25px 15px;
margin: 0 10px 10px; margin: 0 10px 10px;
font-weight: 600; font-weight: 600;
line-height: 1; line-height: 1;
...@@ -275,7 +275,7 @@ ...@@ -275,7 +275,7 @@
} }
.dropdown-menu-close { .dropdown-menu-close {
right: 7px; right: 5px;
width: 20px; width: 20px;
height: 20px; height: 20px;
top: -1px; top: -1px;
......
...@@ -15,12 +15,13 @@ ...@@ -15,12 +15,13 @@
.file-title { .file-title {
position: relative; position: relative;
background: $background-color; background-color: $background-color;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
margin: 0; margin: 0;
text-align: left; text-align: left;
padding: 10px $gl-padding; padding: 10px $gl-padding;
word-wrap: break-word; word-wrap: break-word;
border-radius: 3px 3px 0 0;
.file-actions { .file-actions {
float: right; float: right;
...@@ -49,7 +50,7 @@ ...@@ -49,7 +50,7 @@
} }
} }
a { a:not(.btn) {
color: $gl-dark-link-color; color: $gl-dark-link-color;
} }
......
...@@ -14,10 +14,6 @@ ...@@ -14,10 +14,6 @@
background: $row-hover; background: $row-hover;
} }
&:last-child {
border-bottom: none;
}
.avatar { .avatar {
margin-right: 15px; margin-right: 15px;
} }
......
...@@ -10,10 +10,10 @@ $gutter_inner_width: 258px; ...@@ -10,10 +10,10 @@ $gutter_inner_width: 258px;
/* /*
* UI elements * UI elements
*/ */
$border-color: #efeff1; $border-color: #e5e5e5;
$focus-border-color: #3aabf0; $focus-border-color: #3aabf0;
$table-border-color: #eef0f2; $table-border-color: #eef0f2;
$background-color: #faf9f9; $background-color: #fafafa;
/* /*
* Text * Text
...@@ -81,7 +81,7 @@ $provider-btn-not-active-color: #4688f1; ...@@ -81,7 +81,7 @@ $provider-btn-not-active-color: #4688f1;
$white-light: #fff; $white-light: #fff;
$white-normal: #ededed; $white-normal: #ededed;
$white-dark: #ededed; $white-dark: #ececec;
$gray-light: #faf9f9; $gray-light: #faf9f9;
$gray-normal: #f5f5f5; $gray-normal: #f5f5f5;
...@@ -108,6 +108,8 @@ $red-light: #e52c5a; ...@@ -108,6 +108,8 @@ $red-light: #e52c5a;
$red-normal: #d22852; $red-normal: #d22852;
$red-dark: darken($red-normal, 5%); $red-dark: darken($red-normal, 5%);
$black-transparent: rgba(0, 0, 0, 0.3);
$border-white-light: #f1f2f4; $border-white-light: #f1f2f4;
$border-white-normal: #d6dae2; $border-white-normal: #d6dae2;
$border-white-dark: #c6cacf; $border-white-dark: #c6cacf;
...@@ -150,15 +152,22 @@ $gl-success: $green-normal; ...@@ -150,15 +152,22 @@ $gl-success: $green-normal;
$gl-info: $blue-normal; $gl-info: $blue-normal;
$gl-warning: $orange-normal; $gl-warning: $orange-normal;
$gl-danger: $red-normal; $gl-danger: $red-normal;
$gl-btn-active-background: rgba(0, 0, 0, 0.12); $gl-btn-active-background: rgba(0, 0, 0, 0.16);
$gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background; $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
/* /*
* Commit Diff Colors * Commit Diff Colors
*/ */
$added: #63c363; $added: #63c363;
$deleted: #f77; $deleted: #f77;
$line-added: #ecfdf0;
$line-added-dark: #c7f0d2;
$line-removed: #fbe9eb;
$line-removed-dark: #fac5cd;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
$match-line: #fafafa;
$table-border-gray: #f0f0f0;
/* /*
* Fonts * Fonts
*/ */
...@@ -191,6 +200,13 @@ $dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%); ...@@ -191,6 +200,13 @@ $dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%);
$dropdown-toggle-icon-color: #c4c4c4; $dropdown-toggle-icon-color: #c4c4c4;
$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
/*
* Buttons
*/
$btn-active-gray: #ececec;
$btn-placeholder-gray: #c7c7c7;
$btn-white-active: #848484;
/* /*
* Award emoji * Award emoji
*/ */
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
} }
.diff-line-num, .diff-line-num a { .diff-line-num, .diff-line-num a {
color: rgba(0, 0, 0, 0.3); color: $black-transparent;
} }
// Code itself // Code itself
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
} }
.line_content.match { .line_content.match {
color: rgba(0, 0, 0, 0.3); color: $black-transparent;
background: rgba(255, 255, 255, 0.4); background: rgba(255, 255, 255, 0.4);
} }
} }
......
...@@ -6,12 +6,12 @@ ...@@ -6,12 +6,12 @@
} }
.diff-line-num, .diff-line-num a { .diff-line-num, .diff-line-num a {
color: rgba(0, 0, 0, 0.3); color: $black-transparent;
} }
// Code itself // Code itself
pre.code, .diff-line-num { pre.code, .diff-line-num {
border-color: $border-color; border-color: $table-border-gray;
} }
&, pre.code, .line_holder .line_content { &, pre.code, .line_holder .line_content {
...@@ -23,36 +23,36 @@ ...@@ -23,36 +23,36 @@
.line_holder { .line_holder {
.diff-line-num { .diff-line-num {
&.old { &.old {
background: #fdd; background-color: $line-number-old;
border-color: #f1c0c0; border-color: $line-removed-dark;
} }
&.new { &.new {
background: #dbffdb; background-color: $line-number-new;
border-color: #c1e9c1; border-color: $line-added-dark;
} }
} }
.line_content { .line_content {
&.old { &.old {
background: #ffecec; background: $line-removed;
span.idiff { span.idiff {
background-color: #f8cbcb; background-color: $line-removed-dark;
} }
} }
&.new { &.new {
background: #eaffea; background-color: $line-added;
span.idiff { span.idiff {
background-color: #a6f3a6; background-color: $line-added-dark;
} }
} }
&.match { &.match {
color: rgba(0, 0, 0, 0.3); color: $black-transparent;
background: #fafafa; background: $match-line;
} }
} }
} }
......
...@@ -47,6 +47,7 @@ li.commit { ...@@ -47,6 +47,7 @@ li.commit {
.commit_short_id { .commit_short_id {
min-width: 65px; min-width: 65px;
color: $gl-dark-link-color;
font-family: $monospace_font; font-family: $monospace_font;
} }
...@@ -88,6 +89,10 @@ li.commit { ...@@ -88,6 +89,10 @@ li.commit {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
a {
color: $gl-dark-link-color;
}
} }
.commit-row-info { .commit-row-info {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
.diff-file { .diff-file {
border: 1px solid $border-color; border: 1px solid $border-color;
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
border-radius: 3px;
.diff-header { .diff-header {
position: relative; position: relative;
...@@ -10,6 +11,7 @@ ...@@ -10,6 +11,7 @@
padding: 10px 16px; padding: 10px 16px;
color: #555; color: #555;
z-index: 10; z-index: 10;
border-radius: 3px 3px 0 0;
.diff-title { .diff-title {
font-family: $monospace_font; font-family: $monospace_font;
...@@ -31,6 +33,7 @@ ...@@ -31,6 +33,7 @@
overflow-y: hidden; overflow-y: hidden;
background: #fff; background: #fff;
color: #333; color: #333;
border-radius: 0 0 3px 3px;
.unfold { .unfold {
cursor: pointer; cursor: pointer;
...@@ -325,6 +328,16 @@ ...@@ -325,6 +328,16 @@
float: right; float: right;
} }
.diffs {
.content-block {
border-bottom: none;
}
}
.files-changed {
border-bottom: none;
}
// Mobile // Mobile
@media (max-width: 480px) { @media (max-width: 480px) {
.diff-title { .diff-title {
......
...@@ -49,6 +49,15 @@ ...@@ -49,6 +49,15 @@
} }
.label-row { .label-row {
.label-name {
display: inline-block;
width: 200px;
@media (max-width: $screen-xs-min) {
display: block;
}
}
.label { .label {
padding: 9px; padding: 9px;
font-size: 14px; font-size: 14px;
...@@ -69,3 +78,52 @@ ...@@ -69,3 +78,52 @@
background-color: $gl-danger; background-color: $gl-danger;
color: $white-light; color: $white-light;
} }
.manage-labels-list {
.prepend-left-10 {
display: inline-block;
width: 40%;
vertical-align: middle;
@media (max-width: $screen-xs-min) {
display: block;
width: 100%;
margin-left: 0;
padding: 10px 0;
}
}
.pull-info-right {
float: right;
@media (max-width: $screen-xs-min) {
float: none;
}
.action-buttons {
border-color: transparent;
padding: 6px;
color: $gl-text-color;
&.subscribe-button {
padding-left: 0;
}
}
i {
color: $gl-text-color;
}
.append-right-20 {
a {
color: $gl-text-color;
}
@media (max-width: $screen-xs-min) {
display: block;
margin-bottom: 10px;
}
}
}
}
...@@ -123,6 +123,8 @@ ...@@ -123,6 +123,8 @@
.mr_source_commit, .mr_source_commit,
.mr_target_commit { .mr_target_commit {
margin-bottom: 0;
.commit { .commit {
margin: 0; margin: 0;
padding: 2px 0; padding: 2px 0;
...@@ -174,10 +176,6 @@ ...@@ -174,10 +176,6 @@
display: none; display: none;
} }
.merge-request-form .select2-container {
width: 250px !important;
}
#modal_merge_info .modal-dialog { #modal_merge_info .modal-dialog {
width: 600px; width: 600px;
...@@ -200,3 +198,76 @@ ...@@ -200,3 +198,76 @@
overflow-x: scroll; overflow-x: scroll;
} }
} }
.panel-new-merge-request {
.panel-heading {
padding: 5px 10px;
font-weight: 600;
line-height: 25px;
}
.panel-body {
padding: 10px 5px;
}
.panel-footer {
padding: 5px 10px;
}
.commit {
.commit-row-title {
margin-bottom: 4px;
}
.avatar {
width: 20px;
height: 20px;
margin-right: 5px;
}
.commit-row-info {
line-height: 20px;
}
}
.btn-clipboard {
margin-right: 5px;
padding: 0;
background: transparent;
}
.ci-status-link {
margin-right: 5px;
}
}
.merge-request-select {
padding-left: 5px;
padding-right: 5px;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
@media (min-width: $screen-sm-min) {
float: left;
width: 50%;
margin-bottom: 0;
}
.dropdown-menu-toggle {
width: 100%;
}
.dropdown-menu {
left: 5px;
right: 5px;
width: auto;
}
}
.issuable-form-select-holder {
display: inline-block;
width: 250px;
}
/** /**
* Note Form * Note Form
*/ */
.reply-btn { .comment-btn {
@extend .btn-primary; @extend .btn-create;
margin: 10px $gl-padding;
} }
.diff-file .diff-content { .diff-file .diff-content {
tr.line_holder:hover > td .line_note_link { tr.line_holder:hover > td .line_note_link {
opacity: 1.0; opacity: 1.0;
...@@ -113,13 +113,12 @@ ...@@ -113,13 +113,12 @@
.discussion-body, .discussion-body,
.diff-file { .diff-file {
.notes .note { .notes .note {
border-color: #ddd;
padding: 10px 15px; padding: 10px 15px;
} }
.discussion-reply-holder { .discussion-reply-holder {
background: $background-color; background-color: $white-light;
border-top: 1px solid $border-color; padding: 10px 16px;
} }
} }
......
...@@ -58,6 +58,7 @@ ul.notes { ...@@ -58,6 +58,7 @@ ul.notes {
.note { .note {
display: block; display: block;
position: relative; position: relative;
border-bottom: 1px solid $table-border-gray;
&.is-editting { &.is-editting {
.note-header, .note-header,
...@@ -117,9 +118,6 @@ ul.notes { ...@@ -117,9 +118,6 @@ ul.notes {
padding-bottom: 3px; padding-bottom: 3px;
} }
&:last-child {
border-bottom: 1px solid $border-color;
}
} }
} }
...@@ -137,14 +135,14 @@ ul.notes { ...@@ -137,14 +135,14 @@ ul.notes {
font-family: $regular_font; font-family: $regular_font;
td { td {
border: 1px solid #ddd; border: 1px solid $table-border-gray;
border-left: none; border-left: none;
&.notes_line { &.notes_line {
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
padding: 10px 0; padding: 10px 0;
background: #fff; background: $background-color;
color: $text-color; color: $text-color;
} }
&.notes_line2 { &.notes_line2 {
...@@ -175,9 +173,6 @@ ul.notes { ...@@ -175,9 +173,6 @@ ul.notes {
} }
} }
.author_link {
font-weight: 600;
}
} }
.note-headline-light, .note-headline-light,
...@@ -203,14 +198,26 @@ ul.notes { ...@@ -203,14 +198,26 @@ ul.notes {
line-height: 24px; line-height: 24px;
.fa { .fa {
color: $notes-action-color;
position: relative; position: relative;
top: 1px; top: 1px;
font-size: 17px; font-size: 17px;
} }
.fa-trash-o { &.js-note-delete {
top: 0; i {
font-size: 16px; &:hover {
color: $gl-text-red;
}
}
}
&.js-note-edit {
i {
&:hover {
color: $gl-link-color;
}
}
} }
} }
......
...@@ -47,6 +47,16 @@ class ApplicationController < ActionController::Base ...@@ -47,6 +47,16 @@ class ApplicationController < ActionController::Base
email: current_user.email, email: current_user.email,
username: current_user.username, username: current_user.username,
) )
Raven.tags_context(program: sentry_program_context)
end
end
def sentry_program_context
if Sidekiq.server?
'sidekiq'
else
'rails'
end end
end end
......
...@@ -207,20 +207,20 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -207,20 +207,20 @@ class Projects::MergeRequestsController < Projects::ApplicationController
#This is always source #This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project @source_project = @merge_request.nil? ? @project : @merge_request.source_project
@commit = @repository.commit(params[:ref]) if params[:ref].present? @commit = @repository.commit(params[:ref]) if params[:ref].present?
render layout: false
end end
def branch_to def branch_to
@target_project = selected_target_project @target_project = selected_target_project
@commit = @target_project.commit(params[:ref]) if params[:ref].present? @commit = @target_project.commit(params[:ref]) if params[:ref].present?
render layout: false
end end
def update_branches def update_branches
@target_project = selected_target_project @target_project = selected_target_project
@target_branches = @target_project.repository.branch_names @target_branches = @target_project.repository.branch_names
respond_to do |format| render layout: false
format.js
end
end end
def ci_status def ci_status
......
...@@ -27,9 +27,9 @@ module BlobHelper ...@@ -27,9 +27,9 @@ module BlobHelper
link_opts) link_opts)
if !on_top_of_branch?(project, ref) if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn btn-default disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref) elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn' link_to "Edit", edit_path, class: 'btn btn-file-option'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
continue_params = { continue_params = {
to: edit_path, to: edit_path,
...@@ -38,7 +38,7 @@ module BlobHelper ...@@ -38,7 +38,7 @@ module BlobHelper
} }
fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params) fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
link_to "Edit", fork_path, class: 'btn', method: :post link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post
end end
end end
......
...@@ -28,7 +28,7 @@ module CommitsHelper ...@@ -28,7 +28,7 @@ module CommitsHelper
def commit_to_html(commit, project, inline = true) def commit_to_html(commit, project, inline = true)
template = inline ? "inline_commit" : "commit" template = inline ? "inline_commit" : "commit"
escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil? render "projects/commits/#{template}", commit: commit, project: project unless commit.nil?
end end
# Breadcrumb links for a Project and, if applicable, a tree path # Breadcrumb links for a Project and, if applicable, a tree path
...@@ -117,7 +117,7 @@ module CommitsHelper ...@@ -117,7 +117,7 @@ module CommitsHelper
end end
end end
link_to( link_to(
"Browse Files »", "Browse Files",
namespace_project_tree_path(project.namespace, project, commit), namespace_project_tree_path(project.namespace, project, commit),
class: "pull-right" class: "pull-right"
) )
...@@ -197,7 +197,7 @@ module CommitsHelper ...@@ -197,7 +197,7 @@ module CommitsHelper
link_to( link_to(
namespace_project_blob_path(project.namespace, project, namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff.new_path)), tree_join(commit_sha, diff.new_path)),
class: 'btn view-file js-view-file' class: 'btn view-file js-view-file btn-file-option'
) do ) do
raw('View file @') + content_tag(:span, commit_sha[0..6], raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id') class: 'commit-short-id')
......
module FormHelper
def form_errors(model)
return unless model.errors.any?
pluralized = 'error'.pluralize(model.errors.count)
headline = "The form contains the following #{pluralized}:"
content_tag(:div, class: 'alert alert-danger', id: 'error_explanation') do
content_tag(:h4, headline) <<
content_tag(:ul) do
model.errors.full_messages.
map { |msg| content_tag(:li, msg) }.
join.
html_safe
end
end
end
end
...@@ -116,29 +116,6 @@ module GitlabMarkdownHelper ...@@ -116,29 +116,6 @@ module GitlabMarkdownHelper
end end
end end
MARKDOWN_TIPS = [
"End a line with two or more spaces for a line-break, or soft-return",
"Inline code can be denoted by `surrounding it with backticks`",
"Blocks of code can be denoted by three backticks ``` or four leading spaces",
"Emoji can be added by :emoji_name:, for example :thumbsup:",
"Notify other participants using @user_name",
"Notify a specific group using @group_name",
"Notify the entire team using @all",
"Reference an issue using a hash, for example issue #123",
"Reference a merge request using an exclamation point, for example MR !123",
"Italicize words or phrases using *asterisks* or _underscores_",
"Bold words or phrases using **double asterisks** or __double underscores__",
"Strikethrough words or phrases using ~~two tildes~~",
"Make a bulleted list using + pluses, - minuses, or * asterisks",
"Denote blockquotes using > at the beginning of a line",
"Make a horizontal line using three or more hyphens ---, asterisks ***, or underscores ___"
].freeze
# Returns a random markdown tip for use as a textarea placeholder
def random_markdown_tip
MARKDOWN_TIPS.sample
end
private private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML # Return +text+, truncated to +max_chars+ characters, excluding any HTML
......
...@@ -115,17 +115,32 @@ module IssuesHelper ...@@ -115,17 +115,32 @@ module IssuesHelper
icon('eye-slash') if issue.confidential? icon('eye-slash') if issue.confidential?
end end
def emoji_icon(name, unicode = nil, aliases = []) def emoji_icon(name, unicode = nil, aliases = [], sprite: true)
unicode ||= Emoji.emoji_filename(name) rescue "" unicode ||= Emoji.emoji_filename(name) rescue ""
content_tag :div, "", data = {
class: "icon emoji-icon emoji-#{unicode}", aliases: aliases.join(" "),
title: name,
data: {
aliases: aliases.join(' '),
emoji: name, emoji: name,
unicode_name: unicode unicode_name: unicode
} }
if sprite
# Emoji icons for the emoji menu, these use a spritesheet.
content_tag :div, "",
class: "icon emoji-icon emoji-#{unicode}",
title: name,
data: data
else
# Emoji icons displayed separately, used for the awards already given
# to an issue or merge request.
content_tag :img, "",
class: "icon emoji",
title: name,
height: "20px",
width: "20px",
src: url_to_image("#{unicode}.png"),
data: data
end
end end
def emoji_author_list(notes, current_user) def emoji_author_list(notes, current_user)
......
...@@ -69,10 +69,7 @@ module NotesHelper ...@@ -69,10 +69,7 @@ module NotesHelper
line_type: line_type line_type: line_type
} }
button_tag class: 'btn btn-nr reply-btn js-discussion-reply-button', button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply' do data: data, title: 'Add a reply'
link_text = icon('comment')
link_text << ' Reply'
end
end end
end end
...@@ -3,11 +3,9 @@ ...@@ -3,11 +3,9 @@
%p Please use this form to report users who create spam issues, comments or behave inappropriately. %p Please use this form to report users who create spam issues, comments or behave inappropriately.
%hr %hr
= form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f| = form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f|
= form_errors(@abuse_report)
= f.hidden_field :user_id = f.hidden_field :user_id
- if @abuse_report.errors.any?
.alert.alert-danger
- @abuse_report.errors.full_messages.each do |msg|
%p= msg
.form-group .form-group
= f.label :user_id, class: 'control-label' = f.label :user_id, class: 'control-label'
.col-sm-10 .col-sm-10
......
= form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f| = form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f|
- if @appearance.errors.any? = form_errors(@appearance)
.alert.alert-danger
- @appearance.errors.full_messages.each do |msg|
%p= msg
%fieldset.sign-in %fieldset.sign-in
%legend %legend
......
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| = form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
- if @application_setting.errors.any? = form_errors(@application_setting)
#error_explanation
.alert.alert-danger
- @application_setting.errors.full_messages.each do |msg|
%p= msg
%fieldset %fieldset
%legend Visibility and Access Controls %legend Visibility and Access Controls
......
= form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f| = form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f|
- if application.errors.any? = form_errors(application)
.alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
- application.errors.full_messages.each do |msg|
%p= msg
= content_tag :div, class: 'form-group' do = content_tag :div, class: 'form-group' do
= f.label :name, class: 'col-sm-2 control-label' = f.label :name, class: 'col-sm-2 control-label'
.col-sm-10 .col-sm-10
......
...@@ -4,10 +4,8 @@ ...@@ -4,10 +4,8 @@
= render_broadcast_message(@broadcast_message.message.presence || "Your message here") = render_broadcast_message(@broadcast_message.message.presence || "Your message here")
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f| = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f|
-if @broadcast_message.errors.any? = form_errors(@broadcast_message)
.alert.alert-danger
- @broadcast_message.errors.full_messages.each do |msg|
%p= msg
.form-group .form-group
= f.label :message, class: 'control-label' = f.label :message, class: 'control-label'
.col-sm-10 .col-sm-10
......
...@@ -4,11 +4,7 @@ ...@@ -4,11 +4,7 @@
%div %div
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f| = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f|
-if @deploy_key.errors.any? = form_errors(@deploy_key)
.alert.alert-danger
%ul
- @deploy_key.errors.full_messages.each do |msg|
%li= msg
.form-group .form-group
= f.label :title, class: "control-label" = f.label :title, class: "control-label"
......
= form_for [:admin, @group], html: { class: "form-horizontal" } do |f| = form_for [:admin, @group], html: { class: "form-horizontal" } do |f|
- if @group.errors.any? = form_errors(@group)
.alert.alert-danger
%span= @group.errors.full_messages.first
= render 'shared/group_form', f: f = render 'shared/group_form', f: f
.form-group.group-description-holder .form-group.group-description-holder
......
...@@ -10,10 +10,8 @@ ...@@ -10,10 +10,8 @@
= form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f| = form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f|
-if @hook.errors.any? = form_errors(@hook)
.alert.alert-danger
- @hook.errors.full_messages.each do |msg|
%p= msg
.form-group .form-group
= f.label :url, "URL:", class: 'control-label' = f.label :url, "URL:", class: 'control-label'
.col-sm-10 .col-sm-10
......
= form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f| = form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f|
- if @identity.errors.any? = form_errors(@identity)
#error_explanation
.alert.alert-danger
- @identity.errors.full_messages.each do |msg|
%p= msg
.form-group .form-group
= f.label :provider, class: 'control-label' = f.label :provider, class: 'control-label'
......
= form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f| = form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f|
-if @label.errors.any? = form_errors(@label)
.row
.col-sm-offset-2.col-sm-10
.alert.alert-danger
- @label.errors.full_messages.each do |msg|
%span= msg
%br
.form-group .form-group
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
......
.user_new .user_new
= form_for [:admin, @user], html: { class: 'form-horizontal fieldset-form' } do |f| = form_for [:admin, @user], html: { class: 'form-horizontal fieldset-form' } do |f|
-if @user.errors.any? = form_errors(@user)
#error_explanation
.alert.alert-danger
- @user.errors.full_messages.each do |msg|
%p= msg
%fieldset %fieldset
%legend Account %legend Account
......
= form_for application, url: doorkeeper_submit_path(application), html: {role: 'form'} do |f| = form_for application, url: doorkeeper_submit_path(application), html: {role: 'form'} do |f|
- if application.errors.any? = form_errors(application)
.alert.alert-danger
%ul
- application.errors.full_messages.each do |msg|
%li= msg
.form-group .form-group
= f.label :name, class: 'label-light' = f.label :name, class: 'label-light'
......
...@@ -5,9 +5,7 @@ ...@@ -5,9 +5,7 @@
Group settings Group settings
.panel-body .panel-body
= form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f|
- if @group.errors.any? = form_errors(@group)
.alert.alert-danger
%span= @group.errors.full_messages.first
= render 'shared/group_form', f: f = render 'shared/group_form', f: f
.form-group .form-group
......
...@@ -6,10 +6,7 @@ ...@@ -6,10 +6,7 @@
%hr %hr
= form_for @group, html: { class: 'group-form form-horizontal' } do |f| = form_for @group, html: { class: 'group-form form-horizontal' } do |f|
- if @group.errors.any? = form_errors(@group)
.alert.alert-danger
%span= @group.errors.full_messages.first
= render 'shared/group_form', f: f, autofocus: true = render 'shared/group_form', f: f, autofocus: true
.form-group.group-description-holder .form-group.group-description-holder
......
%div %div
= form_for [:profile, @key], html: { class: 'js-requires-input' } do |f| = form_for [:profile, @key], html: { class: 'js-requires-input' } do |f|
- if @key.errors.any? = form_errors(@key)
.alert.alert-danger
%ul
- @key.errors.full_messages.each do |msg|
%li= msg
.form-group .form-group
= f.label :key, class: 'label-light' = f.label :key, class: 'label-light'
......
...@@ -13,11 +13,8 @@ ...@@ -13,11 +13,8 @@
- unless @user.password_automatically_set? - unless @user.password_automatically_set?
or recover your current one or recover your current one
= form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f| = form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
-if @user.errors.any? = form_errors(@user)
.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
- unless @user.password_automatically_set? - unless @user.password_automatically_set?
.form-group .form-group
= f.label :current_password, class: 'label-light' = f.label :current_password, class: 'label-light'
......
...@@ -7,11 +7,8 @@ ...@@ -7,11 +7,8 @@
Please set a new password before proceeding. Please set a new password before proceeding.
%br %br
After a successful password update you will be redirected to login screen. After a successful password update you will be redirected to login screen.
-if @user.errors.any?
.alert.alert-danger = form_errors(@user)
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
- unless @user.password_automatically_set? - unless @user.password_automatically_set?
.form-group .form-group
......
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f| = form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f|
-if @user.errors.any? = form_errors(@user)
%div.alert.alert-danger
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
.row .row
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
......
- if @project.errors.any? = form_errors(@project)
.alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
= @project.errors.full_messages.first
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
.md-header .md-header
%ul.nav-links %ul.nav-links
%li.active %li.active
%a.js-md-write-button{ href: "#md-write-holder" } %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
Write Write
%li %li
%a.js-md-preview-button{ href: "#md-preview-holder" } %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview Preview
%li.pull-right %li.pull-right
%button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button' } %button.zen-cotrol.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
Go full screen Go full screen
.md-write-holder .md-write-holder
......
...@@ -19,24 +19,17 @@ ...@@ -19,24 +19,17 @@
.pull-right .pull-right
- if ci_commit - if ci_commit
= render_ci_status(ci_commit) = render_ci_status(ci_commit)
&nbsp;
= clipboard_button(clipboard_text: commit.id) = clipboard_button(clipboard_text: commit.id)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
.notes_count
- if note_count > 0
%span.light
%i.fa.fa-comments
= note_count
- if commit.description? - if commit.description?
.commit-row-description.js-toggle-content .commit-row-description.js-toggle-content
%pre %pre
= preserve(markdown(escape_once(commit.description), pipeline: :single_line)) = preserve(markdown(escape_once(commit.description), pipeline: :single_line))
.commit-row-info .commit-row-info
by
= commit_author_link(commit, avatar: true, size: 24) = commit_author_link(commit, avatar: true, size: 24)
authored
.committed_ago .committed_ago
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} &nbsp; #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} &nbsp;
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
%div %div
= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal js-requires-input' } do |f|
-if @key.errors.any? = form_errors(@key)
.alert.alert-danger
%ul
- @key.errors.full_messages.each do |msg|
%li= msg
.form-group .form-group
= f.label :title, class: "control-label" = f.label :title, class: "control-label"
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- diff_files = safe_diff_files(diffs, diff_refs) - diff_files = safe_diff_files(diffs, diff_refs)
.content-block.oneline-block .content-block.oneline-block.files-changed
.inline-parallel-buttons .inline-parallel-buttons
.btn-group .btn-group
= inline_diff_btn = inline_diff_btn
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- if diff_file.diff.submodule? - if diff_file.diff.submodule?
%span %span
= icon('archive fw') = icon('archive fw')
%strong %span
= submodule_link(blob, @commit.id, project.repository) = submodule_link(blob, @commit.id, project.repository)
- else - else
= blob_icon blob.mode, blob.name = blob_icon blob.mode, blob.name
...@@ -11,13 +11,13 @@ ...@@ -11,13 +11,13 @@
= link_to "#diff-#{i}" do = link_to "#diff-#{i}" do
- if diff_file.renamed_file - if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path) - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
%strong.filename.old .filename.old
= old_path = old_path
&rarr; &rarr;
%strong.filename.new .filename.new
= new_path = new_path
- else - else
%strong %span
= diff_file.new_path = diff_file.new_path
- if diff_file.deleted_file - if diff_file.deleted_file
deleted deleted
...@@ -28,8 +28,8 @@ ...@@ -28,8 +28,8 @@
.file-actions.hidden-xs .file-actions.hidden-xs
- if blob_text_viewable?(blob) - if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file" do = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do
= icon('comments') = icon('comment')
\ \
- if editable_diff?(diff_file) - if editable_diff?(diff_file)
......
...@@ -9,10 +9,8 @@ ...@@ -9,10 +9,8 @@
%hr.clearfix %hr.clearfix
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
-if @hook.errors.any? = form_errors(@hook)
.alert.alert-danger
- @hook.errors.full_messages.each do |msg|
%p= msg
.form-group .form-group
= f.label :url, "URL", class: 'control-label' = f.label :url, "URL", class: 'control-label'
.col-sm-10 .col-sm-10
......
= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f|
-if @label.errors.any? = form_errors(@label)
.row
.col-sm-offset-2.col-sm-10
.alert.alert-danger
- @label.errors.full_messages.each do |msg|
%span= msg
%br
.form-group .form-group
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
......
%li{id: dom_id(label)} %li{id: dom_id(label)}
= render "shared/label_row", label: label = render "shared/label_row", label: label
.pull-right .pull-info-right
%strong.append-right-20 %span.append-right-20
= link_to_label(label, type: :merge_request) do = link_to_label(label, type: :merge_request) do
= pluralize label.open_merge_requests_count, 'open merge request' = pluralize label.open_merge_requests_count, 'merge request'
%strong.append-right-20 %span.append-right-20
= link_to_label(label) do = link_to_label(label) do
= pluralize label.open_issues_count(current_user), 'open issue' = pluralize label.open_issues_count(current_user), 'open issue'
- if current_user - if current_user
.label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}} .label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
.subscription-status{data: {status: label_subscription_status(label)}} .subscription-status{data: {status: label_subscription_status(label)}}
%button.btn.btn-sm.btn-info.subscribe-button
%a.subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}}
%span= label_subscription_toggle_button_text(label) %span= label_subscription_toggle_button_text(label)
- if can? current_user, :admin_label, @project - if can? current_user, :admin_label, @project
= link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm' = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn action-buttons', data: {toggle: "tooltip"} do
= link_to 'Delete', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} %i.fa.fa-pencil-square-o
= link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn action-buttons remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do
%i.fa.fa-trash-o
- if current_user - if current_user
:javascript :javascript
......
...@@ -5,33 +5,74 @@ ...@@ -5,33 +5,74 @@
.hide.alert.alert-danger.mr-compare-errors .hide.alert.alert-danger.mr-compare-errors
.merge-request-branches.row .merge-request-branches.row
.col-md-6 .col-md-6
.panel.panel-default .panel.panel-default.panel-new-merge-request
.panel-heading .panel-heading
%strong Source branch Source branch
.panel-body .panel-body.clearfix
= f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true }) .merge-request-select.dropdown
&nbsp; = f.hidden_field :source_project_id
= f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } }) = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", field_name: "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-project
= dropdown_title("Select source project")
= dropdown_filter("Search projects")
= dropdown_content do
- is_active = f.object.source_project_id == @merge_request.source_project.id
%ul
%li
%a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
= @merge_request.source_project_path
.merge-request-select.dropdown
= f.hidden_field :source_branch
= dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
.dropdown-menu.dropdown-menu-selectable.dropdown-source-branch
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
= dropdown_content do
%ul
- @merge_request.source_branches.each do |branch|
%li
%a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
= branch
.panel-footer .panel-footer
.mr_source_commit = icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit
.col-md-6 .col-md-6
.panel.panel-default .panel.panel-default.panel-new-merge-request
.panel-heading .panel-heading
%strong Target branch Target branch
.panel-body .panel-body.clearfix
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project] - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
= f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true }) .merge-request-select.dropdown
&nbsp; = f.hidden_field :target_project_id
= f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } }) = dropdown_toggle f.object.target_project.path_with_namespace, { toggle: "dropdown", field_name: "#{f.object_name}[target_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-target-project" }
.dropdown-menu.dropdown-menu-selectable.dropdown-target-project
= dropdown_title("Select target project")
= dropdown_filter("Search projects")
= dropdown_content do
%ul
- projects.each do |project|
%li
%a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
= project.path_with_namespace
.merge-request-select.dropdown
= f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
.dropdown-menu.dropdown-menu-selectable.dropdown-target-branch.js-target-branch-dropdown
= dropdown_title("Select target branch")
= dropdown_filter("Search branches")
= dropdown_content do
%ul
- @merge_request.target_branches.each do |branch|
%li
%a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
= branch
.panel-footer .panel-footer
.mr_target_commit = icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit
- if @merge_request.errors.any? - if @merge_request.errors.any?
.alert.alert-danger = form_errors(@merge_request)
- @merge_request.errors.full_messages.each do |msg|
%div= msg
- elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present?
.light-well.append-bottom-default .light-well.append-bottom-default
.center .center
...@@ -45,40 +86,11 @@ ...@@ -45,40 +86,11 @@
and and
%span.label-branch #{@merge_request.target_branch} %span.label-branch #{@merge_request.target_branch}
are the same. are the same.
.form-actions
= f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
:javascript :javascript
var source_branch = $("#merge_request_source_branch") new Compare({
, target_branch = $("#merge_request_target_branch") targetProjectUrl: "#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
, target_project = $("#merge_request_target_project_id"); sourceBranchUrl: "#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}",
targetBranchUrl: "#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}"
$.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: source_branch.val() });
$.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() });
target_project.on("change", function() {
$.get("#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: $(this).val() });
});
source_branch.on("change", function() {
$.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: $(this).val() });
$(".mr-compare-errors").fadeOut();
$(".mr-compare-btn").enable();
});
target_branch.on("change", function() {
$.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: $(this).val() });
$(".mr-compare-errors").fadeOut();
$(".mr-compare-btn").enable();
});
:javascript
$(".merge-request-form").on('submit', function () {
if ($("#merge_request_source_branch").val() === "" || $('#merge_request_target_branch').val() === "") {
$(".mr-compare-errors").html("You must select source and target branch to proceed");
$(".mr-compare-errors").fadeIn();
event.preventDefault();
return;
}
}); });
= commit_to_html(@commit, @source_project, false)
:plain
$(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
$('.js-timeago').timeago()
= commit_to_html(@commit, @target_project, false)
:plain
$(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
$('.js-timeago').timeago()
%ul
- @target_branches.each do |branch|
%li
%a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
= branch
:plain
$(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}");
$('select.target_branch').select2({
width: 'resolve',
dropdownAutoWidth: true
});
$(".mr_target_commit").html("");
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f| = form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f|
-if @milestone.errors.any? = form_errors(@milestone)
.alert.alert-danger
%ul
- @milestone.errors.full_messages.each do |msg|
%li= msg
.row .row
.col-md-6 .col-md-6
.form-group .form-group
......
...@@ -3,9 +3,6 @@ ...@@ -3,9 +3,6 @@
- if !defined?(line) || line == note.diff_line - if !defined?(line) || line == note.diff_line
%tr.notes_holder %tr.notes_holder
%td.notes_line{ colspan: 2 } %td.notes_line{ colspan: 2 }
%span.discussion-notes-count
%i.fa.fa-comment
= notes.count
%td.notes_content %td.notes_content
%ul.notes{ data: { discussion_id: note.discussion_id } } %ul.notes{ data: { discussion_id: note.discussion_id } }
= render notes = render notes
......
...@@ -4,9 +4,6 @@ ...@@ -4,9 +4,6 @@
%tr.notes_holder %tr.notes_holder
- if note1 - if note1
%td.notes_line.old %td.notes_line.old
%span.btn.disabled
%i.fa.fa-comment
= notes_left.count
%td.notes_content.parallel.old %td.notes_content.parallel.old
%ul.notes{ data: { discussion_id: note1.discussion_id } } %ul.notes{ data: { discussion_id: note1.discussion_id } }
= render notes_left = render notes_left
...@@ -19,9 +16,6 @@ ...@@ -19,9 +16,6 @@
- if note2 - if note2
%td.notes_line.new %td.notes_line.new
%span.btn.disabled
%i.fa.fa-comment
= notes_right.count
%td.notes_content.parallel.new %td.notes_content.parallel.new
%ul.notes{ data: { discussion_id: note2.discussion_id } } %ul.notes{ data: { discussion_id: note2.discussion_id } }
= render notes_right = render notes_right
......
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
%span.note-role %span.note-role
= 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')
= 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 = 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
= 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
......
...@@ -13,11 +13,7 @@ ...@@ -13,11 +13,7 @@
- if can? current_user, :admin_project, @project - if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f|
-if @protected_branch.errors.any? = form_errors(@protected_branch)
.alert.alert-danger
%ul
- @protected_branch.errors.full_messages.each do |msg|
%li= msg
.form-group .form-group
= f.label :name, "Branch", class: 'control-label' = f.label :name, "Branch", class: 'control-label'
......
...@@ -13,13 +13,7 @@ ...@@ -13,13 +13,7 @@
= nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f| = nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
- if @project.errors.any? = form_errors(@project)
#error_explanation
%p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:"
.alert.alert-error
%ul
- @project.errors.full_messages.each do |msg|
%li= msg
= f.fields_for :variables do |variable_form| = f.fields_for :variables do |variable_form|
.form-group .form-group
......
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f|
-if @page.errors.any? = form_errors(@page)
#error_explanation
.alert.alert-danger
- @page.errors.full_messages.each do |msg|
%p= msg
= f.hidden_field :title, value: @page.title = f.hidden_field :title, value: @page.title
.form-group .form-group
......
%span.label-row %span.label-row
%span.label-name
= link_to_label(label, tooltip: false) = link_to_label(label, tooltip: false)
%span.prepend-left-10 %span.prepend-left-10
= markdown(label.description, pipeline: :single_line) = markdown(label.description, pipeline: :single_line)
- if @service.errors.any? = form_errors(@service)
#error_explanation
.alert.alert-danger
%ul
- @service.errors.full_messages.each do |msg|
%li= msg
- if @service.help.present? - if @service.help.present?
.well .well
......
- if issuable.errors.any? = form_errors(issuable)
.row
.col-sm-offset-2.col-sm-10
.alert.alert-danger
- issuable.errors.full_messages.each do |msg|
%span= msg
%br
.form-group .form-group
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
.col-sm-10 .col-sm-10
...@@ -53,6 +48,7 @@ ...@@ -53,6 +48,7 @@
.issue-assignee .issue-assignee
= f.label :assignee_id, "Assignee", class: 'control-label' = f.label :assignee_id, "Assignee", class: 'control-label'
.col-sm-10 .col-sm-10
.issuable-form-select-holder
= users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
placeholder: 'Select assignee', class: 'custom-form-control', null_user: true, placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
selected: issuable.assignee_id, project: @target_project || @project, selected: issuable.assignee_id, project: @target_project || @project,
...@@ -64,6 +60,7 @@ ...@@ -64,6 +60,7 @@
= f.label :milestone_id, "Milestone", class: 'control-label' = f.label :milestone_id, "Milestone", class: 'control-label'
.col-sm-10 .col-sm-10
- if milestone_options(issuable).present? - if milestone_options(issuable).present?
.issuable-form-select-holder
= f.select(:milestone_id, milestone_options(issuable), = f.select(:milestone_id, milestone_options(issuable),
{ include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } }) { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
- else - else
......
.snippet-form-holder .snippet-form-holder
= form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f| = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f|
- if @snippet.errors.any? = form_errors(@snippet)
.alert.alert-danger
%ul
- @snippet.errors.full_messages.each do |msg|
%li= msg
.form-group .form-group
= f.label :title, class: 'control-label' = f.label :title, class: 'control-label'
......
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
'#{user_calendar_activities_path}' '#{user_calendar_activities_path}'
); );
.calendar-hint Summary of issues, merge requests and push events .calendar-hint Summary of issues, merge requests, and push events
.awards.votes-block .awards.votes-block
- awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes| - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes|
%button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user), data: {placement: "top"}} %button.btn.award-control.js-emoji-btn.has-tooltip{class: (note_active_class(notes, current_user)), data: {placement: "top", original_title: emoji_author_list(notes, current_user)}}
= emoji_icon(emoji) = emoji_icon(emoji, sprite: false)
%span.award-control-text.js-counter %span.award-control-text.js-counter
= notes.count = notes.count
......
...@@ -4,11 +4,9 @@ require 'rails/all' ...@@ -4,11 +4,9 @@ require 'rails/all'
require 'devise' require 'devise'
I18n.config.enforce_available_locales = false I18n.config.enforce_available_locales = false
Bundler.require(:default, Rails.env) Bundler.require(:default, Rails.env)
require_relative '../lib/gitlab/redis_config' require_relative '../lib/gitlab/redis'
module Gitlab module Gitlab
REDIS_CACHE_NAMESPACE = 'cache:gitlab'
class Application < Rails::Application class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers # Application configuration should go into files in config/initializers
...@@ -69,8 +67,8 @@ module Gitlab ...@@ -69,8 +67,8 @@ module Gitlab
end end
end end
redis_config_hash = Gitlab::RedisConfig.redis_store_options redis_config_hash = Gitlab::Redis.redis_store_options
redis_config_hash[:namespace] = REDIS_CACHE_NAMESPACE redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash config.cache_store = :redis_store, redis_config_hash
......
...@@ -21,6 +21,9 @@ Rails.application.configure do ...@@ -21,6 +21,9 @@ Rails.application.configure do
# Generate digests for assets URLs # Generate digests for assets URLs
config.assets.digest = true config.assets.digest = true
# Enable compression of compiled assets using gzip.
config.assets.compress = true
# Defaults to nil and saved in location specified by config.assets.prefix # Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH # config.assets.manifest = YOUR_PATH
......
...@@ -8,6 +8,7 @@ Rails.application.configure do ...@@ -8,6 +8,7 @@ Rails.application.configure do
config.cache_classes = false config.cache_classes = false
# Configure static asset server for tests with Cache-Control for performance # Configure static asset server for tests with Cache-Control for performance
config.assets.digest = false
config.serve_static_files = true config.serve_static_files = true
config.static_cache_control = "public, max-age=3600" config.static_cache_control = "public, max-age=3600"
......
...@@ -7,6 +7,7 @@ if Gitlab::Metrics.enabled? ...@@ -7,6 +7,7 @@ if Gitlab::Metrics.enabled?
# ActiveSupport. # ActiveSupport.
require 'gitlab/metrics/subscribers/action_view' require 'gitlab/metrics/subscribers/action_view'
require 'gitlab/metrics/subscribers/active_record' require 'gitlab/metrics/subscribers/active_record'
require 'gitlab/metrics/subscribers/rails_cache'
Gitlab::Application.configure do |config| Gitlab::Application.configure do |config|
config.middleware.use(Gitlab::Metrics::RackMiddleware) config.middleware.use(Gitlab::Metrics::RackMiddleware)
......
...@@ -13,7 +13,7 @@ end ...@@ -13,7 +13,7 @@ end
if Rails.env.test? if Rails.env.test?
Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session" Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session"
else else
redis_config = Gitlab::RedisConfig.redis_store_options redis_config = Gitlab::Redis.redis_store_options
redis_config[:namespace] = 'session:gitlab' redis_config[:namespace] = 'session:gitlab'
Gitlab::Application.config.session_store( Gitlab::Application.config.session_store(
......
...@@ -2,7 +2,7 @@ SIDEKIQ_REDIS_NAMESPACE = 'resque:gitlab' ...@@ -2,7 +2,7 @@ SIDEKIQ_REDIS_NAMESPACE = 'resque:gitlab'
Sidekiq.configure_server do |config| Sidekiq.configure_server do |config|
config.redis = { config.redis = {
url: Gitlab::RedisConfig.url, url: Gitlab::Redis.url,
namespace: SIDEKIQ_REDIS_NAMESPACE namespace: SIDEKIQ_REDIS_NAMESPACE
} }
...@@ -29,7 +29,7 @@ end ...@@ -29,7 +29,7 @@ end
Sidekiq.configure_client do |config| Sidekiq.configure_client do |config|
config.redis = { config.redis = {
url: Gitlab::RedisConfig.url, url: Gitlab::Redis.url,
namespace: SIDEKIQ_REDIS_NAMESPACE namespace: SIDEKIQ_REDIS_NAMESPACE
} }
end end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<% <%
require "yaml" require "yaml"
require "json" require "json"
require_relative "lib/gitlab/redis_config" require_relative "lib/gitlab/redis"
rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
...@@ -18,7 +18,7 @@ if File.exists?(config_file) ...@@ -18,7 +18,7 @@ if File.exists?(config_file)
config['mailbox'] = "inbox" if config['mailbox'].nil? config['mailbox'] = "inbox" if config['mailbox'].nil?
if config['enabled'] && config['address'] if config['enabled'] && config['address']
redis_url = Gitlab::RedisConfig.new(rails_env).url redis_url = Gitlab::Redis.new(rails_env).url
%> %>
- -
:host: <%= config['host'].to_json %> :host: <%= config['host'].to_json %>
......
...@@ -4,7 +4,7 @@ Gitlab::Seeder.quiet do ...@@ -4,7 +4,7 @@ Gitlab::Seeder.quiet do
milestone_params = { milestone_params = {
title: "v#{i}.0", title: "v#{i}.0",
description: FFaker::Lorem.sentence, description: FFaker::Lorem.sentence,
state: ['opened', 'closed'].sample, state: [:active, :closed].sample,
} }
milestone = Milestones::CreateService.new( milestone = Milestones::CreateService.new(
......
class UserColorScheme < ActiveRecord::Migration class UserColorScheme < ActiveRecord::Migration
include Gitlab::Database
def up def up
add_column :users, :color_scheme_id, :integer, null: false, default: 1 add_column :users, :color_scheme_id, :integer, null: false, default: 1
User.where(dark_scheme: true).update_all(color_scheme_id: 2) execute("UPDATE users SET color_scheme_id = 2 WHERE dark_scheme = #{true_value}")
remove_column :users, :dark_scheme remove_column :users, :dark_scheme
end end
......
...@@ -3,14 +3,16 @@ class AddLastActivityColumnIntoProject < ActiveRecord::Migration ...@@ -3,14 +3,16 @@ class AddLastActivityColumnIntoProject < ActiveRecord::Migration
add_column :projects, :last_activity_at, :datetime add_column :projects, :last_activity_at, :datetime
add_index :projects, :last_activity_at add_index :projects, :last_activity_at
Project.find_each do |project| select_all('SELECT id, updated_at FROM projects').each do |project|
last_activity_date = if project.last_activity project_id = project['id']
project.last_activity.created_at update_date = project['updated_at']
else event = select_one("SELECT created_at FROM events WHERE project_id = #{project_id} ORDER BY created_at DESC LIMIT 1")
project.updated_at
if event && event['created_at']
update_date = event['created_at']
end end
project.update_attribute(:last_activity_at, last_activity_date) execute("UPDATE projects SET last_activity_at = '#{update_date}' WHERE id = #{project_id}")
end end
end end
......
class AddVisibilityLevelToProjects < ActiveRecord::Migration class AddVisibilityLevelToProjects < ActiveRecord::Migration
include Gitlab::Database
def self.up def self.up
add_column :projects, :visibility_level, :integer, :default => 0, :null => false add_column :projects, :visibility_level, :integer, :default => 0, :null => false
Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC) execute("UPDATE projects SET visibility_level = #{Gitlab::VisibilityLevel::PUBLIC} WHERE public = #{true_value}")
remove_column :projects, :public remove_column :projects, :public
end end
def self.down def self.down
add_column :projects, :public, :boolean, :default => false, :null => false add_column :projects, :public, :boolean, :default => false, :null => false
Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true) execute("UPDATE projects SET public = #{true_value} WHERE visibility_level = #{Gitlab::VisibilityLevel::PUBLIC}")
remove_column :projects, :visibility_level remove_column :projects, :visibility_level
end end
end end
class MigrateAlreadyImportedProjects < ActiveRecord::Migration class MigrateAlreadyImportedProjects < ActiveRecord::Migration
include Gitlab::Database
def up def up
Project.where(imported: true).update_all(import_status: "finished") execute("UPDATE projects SET import_status = 'finished' WHERE imported = #{true_value}")
Project.where(imported: false).update_all(import_status: "none") execute("UPDATE projects SET import_status = 'none' WHERE imported = #{false_value}")
remove_column :projects, :imported remove_column :projects, :imported
end end
def down def down
add_column :projects, :imported, :boolean, default: false add_column :projects, :imported, :boolean, default: false
Project.where(import_status: 'finished').update_all(imported: true) execute("UPDATE projects SET imported = #{true_value} WHERE import_status = 'finished'")
end end
end end
class AddVisibilityLevelToSnippet < ActiveRecord::Migration class AddVisibilityLevelToSnippet < ActiveRecord::Migration
include Gitlab::Database
def up def up
add_column :snippets, :visibility_level, :integer, :default => 0, :null => false add_column :snippets, :visibility_level, :integer, :default => 0, :null => false
Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE) execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::PRIVATE} WHERE private = #{true_value}")
Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL) execute("UPDATE snippets SET visibility_level = #{Gitlab::VisibilityLevel::INTERNAL} WHERE private = #{false_value}")
add_index :snippets, :visibility_level add_index :snippets, :visibility_level
...@@ -13,8 +15,8 @@ class AddVisibilityLevelToSnippet < ActiveRecord::Migration ...@@ -13,8 +15,8 @@ class AddVisibilityLevelToSnippet < ActiveRecord::Migration
def down def down
add_column :snippets, :private, :boolean, :default => false, :null => false add_column :snippets, :private, :boolean, :default => false, :null => false
Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false) execute("UPDATE snippets SET private = #{false_value} WHERE visibility_level = #{Gitlab::VisibilityLevel::INTERNAL}")
Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true) execute("UPDATE snippets SET private = #{true_value} WHERE visibility_level = #{Gitlab::VisibilityLevel::PRIVATE}")
remove_column :snippets, :visibility_level remove_column :snippets, :visibility_level
end end
......
...@@ -44,7 +44,6 @@ ActiveRecord::Schema.define(version: 20160331223143) do ...@@ -44,7 +44,6 @@ ActiveRecord::Schema.define(version: 20160331223143) do
t.datetime "updated_at" t.datetime "updated_at"
t.string "home_page_url" t.string "home_page_url"
t.integer "default_branch_protection", default: 2 t.integer "default_branch_protection", default: 2
t.boolean "twitter_sharing_enabled", default: true
t.text "restricted_visibility_levels" t.text "restricted_visibility_levels"
t.boolean "version_check_enabled", default: true t.boolean "version_check_enabled", default: true
t.integer "max_attachment_size", default: 10, null: false t.integer "max_attachment_size", default: 10, null: false
......
# Introduction to build artifacts # Introduction to build artifacts
Artifacts is a list of files and directories which are attached to a build Artifacts is a list of files and directories which are attached to a build
after it completes successfully. after it completes successfully. This feature is enabled by default in all GitLab installations.
_If you are searching for ways to use artifacts, jump to
[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._
Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by
GitLab Runner are uploaded to GitLab and are downloadable as a single archive GitLab Runner are uploaded to GitLab and are downloadable as a single archive
...@@ -16,13 +19,9 @@ The artifacts browser will be available only for new artifacts that are sent ...@@ -16,13 +19,9 @@ The artifacts browser will be available only for new artifacts that are sent
to GitLab using GitLab Runner version 1.0 and up. It will not be possible to to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
browse old artifacts already uploaded to GitLab. browse old artifacts already uploaded to GitLab.
## Enabling build artifacts ## Disabling build artifacts
_If you are searching for ways to use artifacts, jump to
[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._
The artifacts feature is enabled by default in all GitLab installations. To disable artifacts site-wide, follow the steps below.
To disable it site-wide, follow the steps below.
--- ---
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
- [Architecture](architecture.md) of GitLab - [Architecture](architecture.md) of GitLab
- [CI setup](ci_setup.md) for testing GitLab - [CI setup](ci_setup.md) for testing GitLab
- [Code review guidelines](code_review.md) for reviewing code and having code
reviewed.
- [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) - [Instrumentation](instrumentation.md)
......
# Code Review Guidelines
This guide contains advice and best practices for performing code review, and
having your code reviewed.
All merge requests for GitLab CE and EE, whether written by a GitLab team member
or a volunteer contributor, must go through a code review process to ensure the
code is effective, understandable, and maintainable.
Any developer can, and is encouraged to, perform code review on merge requests
of colleagues and contributors. However, the final decision to accept a merge
request is up to one of our merge request "endbosses", denoted on the
[team page](https://about.gitlab.com/team).
## Everyone
- Accept that many programming decisions are opinions. Discuss tradeoffs, which
you prefer, and reach a resolution quickly.
- Ask questions; don't make demands. ("What do you think about naming this
`:user_id`?")
- Ask for clarification. ("I didn't understand. Can you clarify?")
- Avoid selective ownership of code. ("mine", "not mine", "yours")
- Avoid using terms that could be seen as referring to personal traits. ("dumb",
"stupid"). Assume everyone is attractive, intelligent, and well-meaning.
- Be explicit. Remember people don't always understand your intentions online.
- Be humble. ("I'm not sure - let's look it up.")
- Don't use hyperbole. ("always", "never", "endlessly", "nothing")
- Be careful about the use of sarcasm. Everything we do is public; what seems
like good-natured ribbing to you and a long-time colleague might come off as
mean and unwelcoming to a person new to the project.
- Consider one-on-one chats or video calls if there are too many "I didn't
understand" or "Alternative solution:" comments. Post a follow-up comment
summarizing one-on-one discussion.
## Having your code reviewed
- The first reviewer of your code is _you_. Before you perform that first push
of your shiny new branch, read through the entire diff. Does it make sense?
Did you include something unrelated to the overall purpose of the changes? Did
you forget to remove any debugging code?
- Be grateful for the reviewer's suggestions. ("Good call. I'll make that
change.")
- Don't take it personally. The review is of the code, not of you.
- Explain why the code exists. ("It's like that because of these reasons. Would
it be more clear if I rename this class/file/method/variable?")
- Extract unrelated changes and refactorings into future merge requests/issues.
- Seek to understand the reviewer's perspective.
- Try to respond to every comment.
- Push commits based on earlier rounds of feedback as isolated commits to the
branch. Do not squash until the branch is ready to merge. Reviewers should be
able to read individual updates based on their earlier feedback.
## Reviewing code
Understand why the change is necessary (fixes a bug, improves the user
experience, refactors the existing code). Then:
- Communicate which ideas you feel strongly about and those you don't.
- Identify ways to simplify the code while still solving the problem.
- Offer alternative implementations, but assume the author already considered
them. ("What do you think about using a custom validator here?")
- Seek to understand the author's perspective.
- If you don't understand a piece of code, _say so_. There's a good chance
someone else would be confused by it as well.
- After a round of line notes, it can be helpful to post a summary note such as
"LGTM :thumbsup:", or "Just a couple things to address."
- Avoid accepting a merge request before the build succeeds ("Merge when build
succeeds" is fine).
## Credits
Largely based on the [thoughtbot code review guide].
[thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
---
[Return to Development documentation](README.md)
...@@ -2,36 +2,35 @@ ...@@ -2,36 +2,35 @@
GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby 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 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. chunk of code. The resulting data is stored as a field in the transaction that
executed the block.
To start measuring a block of Ruby code you should use To start measuring a block of Ruby code you should use `Gitlab::Metrics.measure`
`Gitlab::Metrics.measure` and give it a name for the series to store the data and give it a name:
in:
```ruby ```ruby
Gitlab::Metrics.measure(:user_logins) do Gitlab::Metrics.measure(:foo) do
... ...
end end
``` ```
The first argument of this method is the series name and should be plural. This 3 values are measured for a block:
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 1. The real time elapsed, stored in NAME_real_time.
- sidekiq_user_logins 2. The CPU time elapsed, stored in NAME_cpu_time.
3. The call count, stored in NAME_call_count.
Series names should be plural as this keeps the naming style in line with the Both the real and CPU timings are measured in milliseconds.
other series names.
By default metrics measured using a block contain a single value, "duration", Multiple calls to the same block will result in the final values being the sum
which contains the number of milliseconds it took to execute the block. Custom of all individual values. Take this code for example:
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 ```ruby
Gitlab::Metrics.measure(:example_series, { number: 10 }, { class: self.class.to_s }) do 3.times do
... Gitlab::Metrics.measure(:sleep) do
sleep 1
end
end end
``` ```
Here the final value of `sleep_real_time` will be `3`, _not_ `1`.
...@@ -16,16 +16,18 @@ GitLab uses [factory_girl] as a test fixture replacement. ...@@ -16,16 +16,18 @@ GitLab uses [factory_girl] as a test fixture replacement.
- Factory definitions live in `spec/factories/`, named using the pluralization - Factory definitions live in `spec/factories/`, named using the pluralization
of their corresponding model (`User` factories are defined in `users.rb`). of their corresponding model (`User` factories are defined in `users.rb`).
- There should be only one top-level factory definition per file. - There should be only one top-level factory definition per file.
- Make use of [Traits] to clean up definitions and usages. - FactoryGirl methods are mixed in to all RSpec groups. This means you can (and
should) call `create(...)` instead of `FactoryGirl.create(...)`.
- Make use of [traits] to clean up definitions and usages.
- When defining a factory, don't define attributes that are not required for the - When defining a factory, don't define attributes that are not required for the
resulting record to pass validation. resulting record to pass validation.
- When instantiating from a factory, don't supply extraneous attributes that - When instantiating from a factory, don't supply attributes that aren't
aren't required by the test. required by the test.
- Factories don't have to be limited to `ActiveRecord` objects. - Factories don't have to be limited to `ActiveRecord` objects.
[See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d). [See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d).
[factory_girl]: https://github.com/thoughtbot/factory_girl [factory_girl]: https://github.com/thoughtbot/factory_girl
[Traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits [traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits
## JavaScript ## JavaScript
...@@ -102,7 +104,7 @@ Here are some things to keep in mind regarding test performance: ...@@ -102,7 +104,7 @@ Here are some things to keep in mind regarding test performance:
- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`, - Don't `create` an object when `build`, `build_stubbed`, `attributes_for`,
`spy`, or `double` will do. Database persistence is slow! `spy`, or `double` will do. Database persistence is slow!
- Use `create(:empty_project)` instead of `create(:project)` when you don't need - Use `create(:empty_project)` instead of `create(:project)` when you don't need
the underlying repository. Filesystem operations are slow! the underlying Git repository. Filesystem operations are slow!
- Don't mark a feature as requiring JavaScript (through `@javascript` in - Don't mark a feature as requiring JavaScript (through `@javascript` in
Spinach or `js: true` in RSpec) unless it's _actually_ required for the test Spinach or `js: true` in RSpec) unless it's _actually_ required for the test
to be valid. Headless browser testing is slow! to be valid. Headless browser testing is slow!
......
...@@ -19,26 +19,15 @@ See the documentation below for details on how to configure these services. ...@@ -19,26 +19,15 @@ See the documentation below for details on how to configure these services.
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins]. GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
## Project services ## Project services
Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, Integration with services such as Campfire, Flowdock, Gemnasium, HipChat,
Pivotal Tracker, and Slack are available in the form of a [Project Service][]. Pivotal Tracker, and Slack are available in the form of a [Project Service][].
You can find these within GitLab in the Services page under Project Settings if
you are at least a master on the project.
Project Services are a bit like plugins in that they allow a lot of freedom in
adding functionality to GitLab. For example there is also a service that can
send an email every time someone pushes new commits.
Because GitLab is open source we can ship with the code and tests for all
plugins. This allows the community to keep the plugins up to date so that they
always work in newer GitLab versions.
For an overview of what projects services are available without logging in,
please see the [project_services directory][projects-code].
[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
[Project Service]: ../project_services/project_services.md [Project Service]: ../project_services/project_services.md
[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services
## SSL certificate errors ## SSL certificate errors
......
# Project Services # Project Services
Project services allow you to integrate GitLab with other applications. Below Project services allow you to integrate GitLab with other applications. Below
is list of the currently supported ones. Click on the service links to see is list of the currently supported ones.
You can find these within GitLab in the Services page under Project Settings if
you are at least a master on the project.
Project Services are a bit like plugins in that they allow a lot of freedom in
adding functionality to GitLab. For example there is also a service that can
send an email every time someone pushes new commits.
Because GitLab is open source we can ship with the code and tests for all
plugins. This allows the community to keep the plugins up to date so that they
always work in newer GitLab versions.
For an overview of what projects services are available without logging in,
please see the [project_services directory][projects-code].
[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services
Click on the service links to see
further configuration instructions and details. Contributions are welcome. further configuration instructions and details. Contributions are welcome.
## Services ## Services
......
...@@ -4,6 +4,7 @@ Feature: Project Forked Merge Requests ...@@ -4,6 +4,7 @@ Feature: Project Forked Merge Requests
And I am a member of project "Shop" And I am a member of project "Shop"
And I have a project forked off of "Shop" called "Forked Shop" And I have a project forked off of "Shop" called "Forked Shop"
@javascript
Scenario: I submit new unassigned merge request to a forked project Scenario: I submit new unassigned merge request to a forked project
Given I visit project "Forked Shop" merge requests page Given I visit project "Forked Shop" merge requests page
And I click link "New Merge Request" And I click link "New Merge Request"
......
...@@ -70,6 +70,7 @@ Feature: Project Merge Requests ...@@ -70,6 +70,7 @@ Feature: Project Merge Requests
When I click link "Reopen" When I click link "Reopen"
Then I should see reopened merge request "Bug NS-04" Then I should see reopened merge request "Bug NS-04"
@javascript
Scenario: I submit new unassigned merge request Scenario: I submit new unassigned merge request
Given I click link "New Merge Request" Given I click link "New Merge Request"
And I submit new merge request "Wiki Feature" And I submit new merge request "Wiki Feature"
......
...@@ -34,10 +34,14 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps ...@@ -34,10 +34,14 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end end
step 'I fill out a "Merge Request On Forked Project" merge request' do step 'I fill out a "Merge Request On Forked Project" merge request' do
select @forked_project.path_with_namespace, from: "merge_request_source_project_id" first('.js-source-project').click
select @project.path_with_namespace, from: "merge_request_target_project_id" first('.dropdown-source-project a', text: @forked_project.path_with_namespace)
select "fix", from: "merge_request_source_branch"
select "master", from: "merge_request_target_branch" first('.js-target-project').click
first('.dropdown-target-project a', text: @project.path_with_namespace)
first('.js-source-branch').click
first('.dropdown-source-branch .dropdown-content a', text: 'fix').click
click_button "Compare branches and continue" click_button "Compare branches and continue"
...@@ -115,10 +119,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps ...@@ -115,10 +119,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end end
step 'I fill out an invalid "Merge Request On Forked Project" merge request' do step 'I fill out an invalid "Merge Request On Forked Project" merge request' do
expect(find(:select, "merge_request_source_project_id", {}).value).to eq @forked_project.id.to_s expect(find_by_id("merge_request_source_project_id", visible: false).value).to eq @forked_project.id.to_s
expect(find(:select, "merge_request_target_project_id", {}).value).to eq @project.id.to_s expect(find_by_id("merge_request_target_project_id", visible: false).value).to eq @project.id.to_s
expect(find(:select, "merge_request_source_branch", {}).value).to eq "" expect(find_by_id("merge_request_source_branch", visible: false).value).to eq nil
expect(find(:select, "merge_request_target_branch", {}).value).to eq "master" expect(find_by_id("merge_request_target_branch", visible: false).value).to eq "master"
click_button "Compare branches" click_button "Compare branches"
end end
...@@ -127,7 +131,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps ...@@ -127,7 +131,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end end
step 'the target repository should be the original repository' do step 'the target repository should be the original repository' do
expect(page).to have_select("merge_request_target_project_id", selected: @project.path_with_namespace) expect(find_by_id("merge_request_target_project_id").value).to eq "#{@project.id}"
end end
step 'I click "Assign to" dropdown"' do step 'I click "Assign to" dropdown"' do
......
...@@ -15,7 +15,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps ...@@ -15,7 +15,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I delete all labels' do step 'I delete all labels' do
page.within '.labels' do page.within '.labels' do
page.all('.btn-remove').each do |remove| page.all('.remove-row').each do |remove|
remove.click remove.click
sleep 0.05 sleep 0.05
end end
......
...@@ -93,8 +93,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -93,8 +93,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'I submit new merge request "Wiki Feature"' do step 'I submit new merge request "Wiki Feature"' do
select "fix", from: "merge_request_source_branch" find('.js-source-branch').click
select "feature", from: "merge_request_target_branch" find('.dropdown-source-branch .dropdown-content a', text: 'fix').click
find('.js-target-branch').click
first('.dropdown-target-branch .dropdown-content a', text: 'feature').click
click_button "Compare branches" click_button "Compare branches"
fill_in "merge_request_title", with: "Wiki Feature" fill_in "merge_request_title", with: "Wiki Feature"
click_button "Submit merge request" click_button "Submit merge request"
......
...@@ -213,13 +213,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -213,13 +213,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I see Browse file link' do step 'I see Browse file link' do
expect(page).to have_link 'Browse File »' expect(page).to have_link 'Browse File'
expect(page).not_to have_link 'Browse Files »' expect(page).not_to have_link 'Browse Files'
end end
step 'I see Browse code link' do step 'I see Browse code link' do
expect(page).to have_link 'Browse Files »' expect(page).to have_link 'Browse Files'
expect(page).not_to have_link 'Browse File »'
expect(page).not_to have_link 'Browse Directory »' expect(page).not_to have_link 'Browse Directory »'
end end
......
...@@ -155,7 +155,7 @@ module SharedDiffNote ...@@ -155,7 +155,7 @@ module SharedDiffNote
step 'I should see a discussion reply button' do step 'I should see a discussion reply button' do
page.within(diff_file_selector) do page.within(diff_file_selector) do
expect(page).to have_button('Reply') expect(page).to have_button('Reply...')
end end
end end
......
...@@ -43,8 +43,10 @@ module Gitlab ...@@ -43,8 +43,10 @@ module Gitlab
# false if the lease is already taken. # false if the lease is already taken.
def try_obtain def try_obtain
# Performing a single SET is atomic # Performing a single SET is atomic
Gitlab::Redis.with do |redis|
!!redis.set(redis_key, '1', nx: true, ex: @timeout) !!redis.set(redis_key, '1', nx: true, ex: @timeout)
end end
end
# No #cancel method. See comments above! # No #cancel method. See comments above!
......
...@@ -74,24 +74,32 @@ module Gitlab ...@@ -74,24 +74,32 @@ module Gitlab
# #
# Example: # Example:
# #
# Gitlab::Metrics.measure(:find_by_username_timings) do # Gitlab::Metrics.measure(:find_by_username_duration) do
# User.find_by_username(some_username) # User.find_by_username(some_username)
# end # end
# #
# series - The name of the series to store the data in. # name - The name of the field to store the execution time 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. # Returns the value yielded by the supplied block.
def self.measure(series, values = {}, tags = {}) def self.measure(name)
return yield unless Transaction.current trans = current_transaction
return yield unless trans
real_start = Time.now.to_f
cpu_start = System.cpu_time
start = Time.now.to_f
retval = yield retval = yield
duration = (Time.now.to_f - start) * 1000.0
values = values.merge(duration: duration)
Transaction.current.add_metric(series, values, tags) cpu_stop = System.cpu_time
real_stop = Time.now.to_f
real_time = (real_stop - real_start) * 1000.0
cpu_time = cpu_stop - cpu_start
trans.increment("#{name}_real_time", real_time)
trans.increment("#{name}_cpu_time", cpu_time)
trans.increment("#{name}_call_count", 1)
retval retval
end end
...@@ -107,5 +115,11 @@ module Gitlab ...@@ -107,5 +115,11 @@ module Gitlab
new(udp: { host: host, port: port }) new(udp: { host: host, port: port })
end end
end end
private
def self.current_transaction
Transaction.current
end
end end
end end
module Gitlab
module Metrics
module Subscribers
# Class for tracking the total time spent in Rails cache calls
class RailsCache < ActiveSupport::Subscriber
attach_to :active_support
def cache_read(event)
increment(:cache_read_duration, event.duration)
end
def cache_write(event)
increment(:cache_write_duration, event.duration)
end
def cache_delete(event)
increment(:cache_delete_duration, event.duration)
end
def cache_exist?(event)
increment(:cache_exists_duration, event.duration)
end
def increment(key, duration)
return unless current_transaction
current_transaction.increment(:cache_duration, duration)
current_transaction.increment(key, duration)
end
private
def current_transaction
Transaction.current
end
end
end
end
end
...@@ -30,6 +30,17 @@ module Gitlab ...@@ -30,6 +30,17 @@ module Gitlab
0 0
end end
end end
# THREAD_CPUTIME is not supported on OS X
if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
def self.cpu_time
Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
end
else
def self.cpu_time
Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
end
end
end end
end end
end end
module Gitlab module Gitlab
class RedisConfig class Redis
CACHE_NAMESPACE = 'cache:gitlab'
attr_reader :url attr_reader :url
# To be thread-safe we must be careful when writing the class instance
# variables @url and @pool. Because @pool depends on @url we need two
# mutexes to prevent deadlock.
URL_MUTEX = Mutex.new
POOL_MUTEX = Mutex.new
private_constant :URL_MUTEX, :POOL_MUTEX
def self.url def self.url
new.url @url || URL_MUTEX.synchronize { @url = new.url }
end
def self.with
if @pool.nil?
POOL_MUTEX.synchronize do
@pool = ConnectionPool.new { ::Redis.new(url: url) }
end
end
@pool.with { |redis| yield redis }
end end
def self.redis_store_options def self.redis_store_options
url = new.url url = new.url
redis_config_hash = Redis::Store::Factory.extract_host_options_from_uri(url) redis_config_hash = ::Redis::Store::Factory.extract_host_options_from_uri(url)
# Redis::Store does not handle Unix sockets well, so let's do it for them # Redis::Store does not handle Unix sockets well, so let's do it for them
redis_uri = URI.parse(url) redis_uri = URI.parse(url)
if redis_uri.scheme == 'unix' if redis_uri.scheme == 'unix'
......
...@@ -4,12 +4,12 @@ namespace :cache do ...@@ -4,12 +4,12 @@ namespace :cache do
desc "GitLab | Clear redis cache" desc "GitLab | Clear redis cache"
task :clear => :environment do task :clear => :environment do
redis = Redis.new(url: Gitlab::RedisConfig.url) Gitlab::Redis.with do |redis|
cursor = REDIS_SCAN_START_STOP cursor = REDIS_SCAN_START_STOP
loop do loop do
cursor, keys = redis.scan( cursor, keys = redis.scan(
cursor, cursor,
match: "#{Gitlab::REDIS_CACHE_NAMESPACE}*", match: "#{Gitlab::Redis::CACHE_NAMESPACE}*",
count: CLEAR_BATCH_SIZE count: CLEAR_BATCH_SIZE
) )
...@@ -18,4 +18,5 @@ namespace :cache do ...@@ -18,4 +18,5 @@ namespace :cache do
break if cursor == REDIS_SCAN_START_STOP break if cursor == REDIS_SCAN_START_STOP
end end
end end
end
end end
require 'rails_helper'
describe 'Awards Emoji', feature: true do
let!(:project) { create(:project) }
let!(:user) { create(:user) }
before do
project.team << [user, :master]
login_as(user)
end
describe 'Click award emoji from issue#show' do
let!(:issue) do
create(:issue,
author: @user,
assignee: @user,
project: project)
end
before do
visit namespace_project_issue_path(project.namespace, project, issue)
end
it 'should increment the thumbsdown emoji', js: true do
find('[data-emoji="thumbsdown"]').click
sleep 2
expect(thumbsdown_emoji).to have_text("1")
end
context 'click the thumbsup emoji' do
it 'should increment the thumbsup emoji', js: true do
find('[data-emoji="thumbsup"]').click
sleep 2
expect(thumbsup_emoji).to have_text("1")
end
it 'should decrement the thumbsdown emoji', js: true do
expect(thumbsdown_emoji).to have_text("0")
end
end
context 'click the thumbsdown emoji' do
it 'should increment the thumbsdown emoji', js: true do
find('[data-emoji="thumbsdown"]').click
sleep 2
expect(thumbsdown_emoji).to have_text("1")
end
it 'should decrement the thumbsup emoji', js: true do
expect(thumbsup_emoji).to have_text("0")
end
end
end
def thumbsup_emoji
page.all('span.js-counter').first
end
def thumbsdown_emoji
page.all('span.js-counter').last
end
end
require 'spec_helper' require 'spec_helper'
feature 'Create New Merge Request', feature: true, js: false do feature 'Create New Merge Request', feature: true, js: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
...@@ -13,9 +13,12 @@ feature 'Create New Merge Request', feature: true, js: false do ...@@ -13,9 +13,12 @@ feature 'Create New Merge Request', feature: true, js: false do
it 'generates a diff for an orphaned branch' do it 'generates a diff for an orphaned branch' do
click_link 'New Merge Request' click_link 'New Merge Request'
select "orphaned-branch", from: "merge_request_source_branch"
select "master", from: "merge_request_target_branch" first('.js-source-branch').click
first('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click
click_button "Compare branches" click_button "Compare branches"
click_link "Changes"
expect(page).to have_content "README.md" expect(page).to have_content "README.md"
expect(page).to have_content "wm.png" expect(page).to have_content "wm.png"
...@@ -23,6 +26,8 @@ feature 'Create New Merge Request', feature: true, js: false do ...@@ -23,6 +26,8 @@ feature 'Create New Merge Request', feature: true, js: false do
fill_in "merge_request_title", with: "Orphaned MR test" fill_in "merge_request_title", with: "Orphaned MR test"
click_button "Submit merge request" click_button "Submit merge request"
click_link "Check out branch"
expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch' expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
end end
end end
...@@ -210,7 +210,7 @@ describe 'Comments', feature: true do ...@@ -210,7 +210,7 @@ describe 'Comments', feature: true do
is_expected.to have_content('Another comment on line 10') is_expected.to have_content('Another comment on line 10')
is_expected.to have_css('.notes_holder') is_expected.to have_css('.notes_holder')
is_expected.to have_css('.notes_holder .note', count: 1) is_expected.to have_css('.notes_holder .note', count: 1)
is_expected.to have_button('Reply') is_expected.to have_button('Reply...')
end end
end end
end end
......
require 'rails_helper'
describe FormHelper do
describe 'form_errors' do
it 'returns nil when model has no errors' do
model = double(errors: [])
expect(helper.form_errors(model)).to be_nil
end
it 'renders an alert div' do
model = double(errors: errors_stub('Error 1'))
expect(helper.form_errors(model)).
to include('<div class="alert alert-danger" id="error_explanation">')
end
it 'contains a summary message' do
single_error = double(errors: errors_stub('A'))
multi_errors = double(errors: errors_stub('A', 'B', 'C'))
expect(helper.form_errors(single_error)).
to include('<h4>The form contains the following error:')
expect(helper.form_errors(multi_errors)).
to include('<h4>The form contains the following errors:')
end
it 'renders each message' do
model = double(errors: errors_stub('Error 1', 'Error 2', 'Error 3'))
errors = helper.form_errors(model)
aggregate_failures do
expect(errors).to include('<li>Error 1</li>')
expect(errors).to include('<li>Error 2</li>')
expect(errors).to include('<li>Error 3</li>')
end
end
def errors_stub(*messages)
ActiveModel::Errors.new(double).tap do |errors|
messages.each { |msg| errors.add(:base, msg) }
end
end
end
end
...@@ -150,13 +150,6 @@ describe GitlabMarkdownHelper do ...@@ -150,13 +150,6 @@ describe GitlabMarkdownHelper do
end end
end end
describe 'random_markdown_tip' do
it 'returns a random Markdown tip' do
stub_const("#{described_class}::MARKDOWN_TIPS", ['Random tip'])
expect(random_markdown_tip).to eq 'Random tip'
end
end
describe '#first_line_in_markdown' do describe '#first_line_in_markdown' do
let(:text) { "@#{user.username}, can you look at this?\nHello world\n"} let(:text) { "@#{user.username}, can you look at this?\nHello world\n"}
......
require 'spec_helper'
describe Gitlab::Metrics::Subscribers::RailsCache do
let(:transaction) { Gitlab::Metrics::Transaction.new }
let(:subscriber) { described_class.new }
let(:event) { double(:event, duration: 15.2) }
describe '#cache_read' do
it 'increments the cache_read duration' do
expect(subscriber).to receive(:increment).
with(:cache_read_duration, event.duration)
subscriber.cache_read(event)
end
end
describe '#cache_write' do
it 'increments the cache_write duration' do
expect(subscriber).to receive(:increment).
with(:cache_write_duration, event.duration)
subscriber.cache_write(event)
end
end
describe '#cache_delete' do
it 'increments the cache_delete duration' do
expect(subscriber).to receive(:increment).
with(:cache_delete_duration, event.duration)
subscriber.cache_delete(event)
end
end
describe '#cache_exist?' do
it 'increments the cache_exists duration' do
expect(subscriber).to receive(:increment).
with(:cache_exists_duration, event.duration)
subscriber.cache_exist?(event)
end
end
describe '#increment' do
context 'without a transaction' do
it 'returns' do
expect(transaction).not_to receive(:increment)
subscriber.increment(:foo, 15.2)
end
end
context 'with a transaction' do
before do
allow(subscriber).to receive(:current_transaction).
and_return(transaction)
end
it 'increments the total and specific cache duration' do
expect(transaction).to receive(:increment).
with(:cache_duration, event.duration)
expect(transaction).to receive(:increment).
with(:cache_delete_duration, event.duration)
subscriber.increment(:cache_delete_duration, event.duration)
end
end
end
end
...@@ -26,4 +26,10 @@ describe Gitlab::Metrics::System do ...@@ -26,4 +26,10 @@ describe Gitlab::Metrics::System do
end end
end end
end end
describe '.cpu_time' do
it 'returns a Fixnum' do
expect(described_class.cpu_time).to be_an_instance_of(Fixnum)
end
end
end end
...@@ -74,24 +74,21 @@ describe Gitlab::Metrics do ...@@ -74,24 +74,21 @@ describe Gitlab::Metrics do
let(:transaction) { Gitlab::Metrics::Transaction.new } let(:transaction) { Gitlab::Metrics::Transaction.new }
before do before do
allow(Gitlab::Metrics::Transaction).to receive(:current). allow(Gitlab::Metrics).to receive(:current_transaction).
and_return(transaction) and_return(transaction)
end end
it 'adds a metric to the current transaction' do it 'adds a metric to the current transaction' do
expect(transaction).to receive(:add_metric). expect(transaction).to receive(:increment).
with(:foo, { duration: a_kind_of(Numeric) }, { tag: 'value' }) with('foo_real_time', a_kind_of(Numeric))
Gitlab::Metrics.measure(:foo, {}, tag: 'value') { 10 } expect(transaction).to receive(:increment).
end with('foo_cpu_time', a_kind_of(Numeric))
it 'supports adding of custom values' do
values = { duration: a_kind_of(Numeric), number: 10 }
expect(transaction).to receive(:add_metric). expect(transaction).to receive(:increment).
with(:foo, values, { tag: 'value' }) with('foo_call_count', 1)
Gitlab::Metrics.measure(:foo, { number: 10 }, tag: 'value') { 10 } Gitlab::Metrics.measure(:foo) { 10 }
end end
it 'returns the return value of the block' do it 'returns the return value of the block' do
......
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