diff --git a/.rubocop.yml b/.rubocop.yml index 71273ce6098f7e2f2aa88b470b91b4edd32882be..2fda0b03119adccf18fe46b3186db1d978e930af 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -691,7 +691,7 @@ Style/ZeroLengthPredicate: # branches, and conditions. Metrics/AbcSize: Enabled: true - Max: 70 + Max: 60 # Avoid excessive block nesting. Metrics/BlockNesting: diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 7486fdbc50b3f93d9d04db440483984df5d1dcce..39e898a4f952d339c155a7939d571a5fdd6c8cfc 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.2 +0.7.1 diff --git a/app/assets/javascripts/compare.js.coffee b/app/assets/javascripts/compare.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..f20992ead3ec6851819c5564499bcb08f87a95bf --- /dev/null +++ b/app/assets/javascripts/compare.js.coffee @@ -0,0 +1,67 @@ +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() + ) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index e8d25591f63a70191d9de9ee122f977e6a38e5db..ee1d0fad28910531be2e57b1f5e27b528bb842af 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -57,14 +57,30 @@ class GitLabDropdownFilter filter: (search_text) -> data = @options.data() - results = data - if search_text isnt "" - results = fuzzaldrinPlus.filter(data, search_text, - key: @options.keys - ) + if data? + results = data - @options.callback results + if search_text isnt '' + results = fuzzaldrinPlus.filter(data, search_text, + key: @options.keys + ) + + @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 constructor: (@dataEndpoint, @options) -> @@ -123,7 +139,7 @@ class GitLabDropdown if _.isString(@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 data is an array @@ -147,7 +163,14 @@ class GitLabDropdown filterInputBlur: @filterInputBlur remote: @options.filterRemote 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: => return @fullData callback: (data) => @@ -376,7 +399,7 @@ class GitLabDropdown # Toggle the dropdown label 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 !field.length and fieldName # Create hidden input for form diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index f73127f49f0fa0119cc65e97b8252ec64a6bfd1c..6bd4e885a036cf5bdbb1a19a86e813baa187f2cb 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -85,15 +85,21 @@ class @MilestoneSelect # display:block overrides the hide-collapse rule $value.removeAttr('style') clicked: (selected) -> + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is page is 'projects:merge_requests:index' + if $dropdown.hasClass 'js-filter-bulk-update' return - if $dropdown.hasClass('js-filter-submit') + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) if selected.name? selectedMilestone = selected.name else selectedMilestone = '' Issues.filterResults $dropdown.closest('form') + else if $dropdown.hasClass('js-filter-submit') + $dropdown.closest('form').submit() else selected = $selectbox .find('input[type="hidden"]') diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 82dc1acbd017c96ff2144d681a79605ab5705cdb..ba6c7930cdcd9e47d4be70977f2ad82f2985073b 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -248,7 +248,7 @@ .dropdown-title { position: relative; - padding: 0 0 15px; + padding: 0 25px 15px; margin: 0 10px 10px; font-weight: 600; line-height: 1; @@ -275,7 +275,7 @@ } .dropdown-menu-close { - right: 7px; + right: 5px; width: 20px; height: 20px; top: -1px; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 8272615768d90a3d3d09af7a9c84a8cc807829f6..6453c91d955069866bea7c0873c9f3fbc26e8306 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -47,6 +47,7 @@ li.commit { .commit_short_id { min-width: 65px; + color: $gl-dark-link-color; font-family: $monospace_font; } @@ -88,6 +89,10 @@ li.commit { padding: 0; margin: 0; } + + a { + color: $gl-dark-link-color; + } } .commit-row-info { diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index bd224705f04aa29366c410542713eccabdfdc39b..604f1700cf840543786e51c956b68d272be2966e 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -59,6 +59,9 @@ position: relative; overflow-y: auto; padding: 15px; + .form-actions { + margin: -$gl-padding+1; + } } body.modal-open { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 1c6a420897433d7f5d5f1e6b421ee1339365cff4..b79335eab9155bb336eca8d3e2b2c7fa64b27fac 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -123,6 +123,8 @@ .mr_source_commit, .mr_target_commit { + margin-bottom: 0; + .commit { margin: 0; padding: 2px 0; @@ -174,10 +176,6 @@ display: none; } -.merge-request-form .select2-container { - width: 250px !important; -} - #modal_merge_info .modal-dialog { width: 600px; @@ -200,3 +198,76 @@ 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; +} diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ed5a1901056decd020146d3fbb0b5abb5cc5f6cb..da3a32a9f8e5cb49a4fe45bcd43a181050c125b1 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -208,20 +208,20 @@ class Projects::MergeRequestsController < Projects::ApplicationController #This is always source @source_project = @merge_request.nil? ? @project : @merge_request.source_project @commit = @repository.commit(params[:ref]) if params[:ref].present? + render layout: false end def branch_to @target_project = selected_target_project @commit = @target_project.commit(params[:ref]) if params[:ref].present? + render layout: false end def update_branches @target_project = selected_target_project @target_branches = @target_project.repository.branch_names - respond_to do |format| - format.js - end + render layout: false end def ci_status diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index de508036888ca9d8e31dac372401aa59d26c3ef5..35ba543cef12f42d10d781e1eb91c27017d9f60c 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -28,7 +28,7 @@ module CommitsHelper def commit_to_html(commit, project, inline = true) 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 # Breadcrumb links for a Project and, if applicable, a tree path @@ -117,7 +117,7 @@ module CommitsHelper end end link_to( - "Browse Files »", + "Browse Files", namespace_project_tree_path(project.namespace, project, commit), class: "pull-right" ) diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..6a43be2cf3ef14049e07f6b38ed9a4ab05e0fe13 --- /dev/null +++ b/app/helpers/form_helper.rb @@ -0,0 +1,18 @@ +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 diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml index 3bc1b24b5e2067bebdeb20392d91a0e46c255ba9..06be1a53318a068f98e17ec6f39f48af82c109f1 100644 --- a/app/views/abuse_reports/new.html.haml +++ b/app/views/abuse_reports/new.html.haml @@ -3,11 +3,9 @@ %p Please use this form to report users who create spam issues, comments or behave inappropriately. %hr = 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 - - if @abuse_report.errors.any? - .alert.alert-danger - - @abuse_report.errors.full_messages.each do |msg| - %p= msg .form-group = f.label :user_id, class: 'control-label' .col-sm-10 diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 6f325914d14f048a0574546cc41a95d5b0f0bf97..d88f3ad314d01c51bca8e6ac56a20b4296e5cff9 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -1,8 +1,5 @@ = form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f| - - if @appearance.errors.any? - .alert.alert-danger - - @appearance.errors.full_messages.each do |msg| - %p= msg + = form_errors(@appearance) %fieldset.sign-in %legend diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index de86dacbb1242e3f9188750da30cf628f8baafca..a8cca1a81cb3537728aa505f8320cc2a5878e738 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -1,9 +1,5 @@ = form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| - - if @application_setting.errors.any? - #error_explanation - .alert.alert-danger - - @application_setting.errors.full_messages.each do |msg| - %p= msg + = form_errors(@application_setting) %fieldset %legend Visibility and Access Controls diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml index e18f7b499dd3fc9543fb25a5362b0164c44c2259..4aacbb8cd7791c01485068310af6aa145e8c8e28 100644 --- a/app/views/admin/applications/_form.html.haml +++ b/app/views/admin/applications/_form.html.haml @@ -1,9 +1,6 @@ = form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f| - - if application.errors.any? - .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × - - application.errors.full_messages.each do |msg| - %p= msg + = form_errors(application) + = content_tag :div, class: 'form-group' do = f.label :name, class: 'col-sm-2 control-label' .col-sm-10 diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index b748460a9f7a46f49978c163bec58b9e784fe371..6b157abf8422db584d0fdcc12dd79ccb6411c677 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -4,10 +4,8 @@ = 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| - -if @broadcast_message.errors.any? - .alert.alert-danger - - @broadcast_message.errors.full_messages.each do |msg| - %p= msg + = form_errors(@broadcast_message) + .form-group = f.label :message, class: 'control-label' .col-sm-10 diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml index 5b46b3222a9cc5a5ffcdf21c72451a0f739f39be..15aa059c93d0b5f82a329537e92a02d7189fd452 100644 --- a/app/views/admin/deploy_keys/new.html.haml +++ b/app/views/admin/deploy_keys/new.html.haml @@ -4,11 +4,7 @@ %div = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f| - -if @deploy_key.errors.any? - .alert.alert-danger - %ul - - @deploy_key.errors.full_messages.each do |msg| - %li= msg + = form_errors(@deploy_key) .form-group = f.label :title, class: "control-label" diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 7f2b1cd235d990ce976cbea59d6dbef2fe0932a3..0cc405401cf5450f712941b31eed7a21af9420a3 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -1,8 +1,5 @@ = form_for [:admin, @group], html: { class: "form-horizontal" } do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first - + = form_errors(@group) = render 'shared/group_form', f: f .form-group.group-description-holder diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 53b3cd04c682964175ec96071a94b82a4d998f2a..ad952052f253efc3ef1ab423502621d7cb677e37 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -10,10 +10,8 @@ = form_for @hook, as: :hook, url: admin_hooks_path, html: { class: 'form-horizontal' } do |f| - -if @hook.errors.any? - .alert.alert-danger - - @hook.errors.full_messages.each do |msg| - %p= msg + = form_errors(@hook) + .form-group = f.label :url, "URL:", class: 'control-label' .col-sm-10 diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml index 3a7885582262b02dd5bb9414952dbb0c1af2aae1..112a201fafa7bc975b5335e244b00a01e887e206 100644 --- a/app/views/admin/identities/_form.html.haml +++ b/app/views/admin/identities/_form.html.haml @@ -1,9 +1,5 @@ = form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f| - - if @identity.errors.any? - #error_explanation - .alert.alert-danger - - @identity.errors.full_messages.each do |msg| - %p= msg + = form_errors(@identity) .form-group = f.label :provider, class: 'control-label' diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml index 8c6b389bf157ac2c103861ae794ffe631bd71f55..448aa95354818b0ac87f39ac546984585a2ff99e 100644 --- a/app/views/admin/labels/_form.html.haml +++ b/app/views/admin/labels/_form.html.haml @@ -1,11 +1,5 @@ = form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f| - -if @label.errors.any? - .row - .col-sm-offset-2.col-sm-10 - .alert.alert-danger - - @label.errors.full_messages.each do |msg| - %span= msg - %br + = form_errors(@label) .form-group = f.label :title, class: 'control-label' diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index d2527ede99559ea4980af70ea43239fb9f460889..b05fdbd5552c50d1ad8705b12c740eb56c332c79 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -1,10 +1,6 @@ .user_new = form_for [:admin, @user], html: { class: 'form-horizontal fieldset-form' } do |f| - -if @user.errors.any? - #error_explanation - .alert.alert-danger - - @user.errors.full_messages.each do |msg| - %p= msg + = form_errors(@user) %fieldset %legend Account diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml index 906b0676150cfa50173099b6dd7058b74f306728..5c98265727a13e899f315e14d7705efd33e86b7e 100644 --- a/app/views/doorkeeper/applications/_form.html.haml +++ b/app/views/doorkeeper/applications/_form.html.haml @@ -1,9 +1,5 @@ = form_for application, url: doorkeeper_submit_path(application), html: {role: 'form'} do |f| - - if application.errors.any? - .alert.alert-danger - %ul - - application.errors.full_messages.each do |msg| - %li= msg + = form_errors(application) .form-group = f.label :name, class: 'label-light' diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index ea5a0358392911d17750a6b19987c0e4222a4dfb..a698cbbe9dbd4a5b67ca118cf2fa16b8e1ec8867 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -5,9 +5,7 @@ Group settings .panel-body = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first + = form_errors(@group) = render 'shared/group_form', f: f .form-group diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index 30ab8aeba13e9d1706bf189bdf98190ae0e7395d..2b8bc269e645c117c15955469075b156f3406c77 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -6,10 +6,7 @@ %hr = form_for @group, html: { class: 'group-form form-horizontal' } do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first - + = form_errors(@group) = render 'shared/group_form', f: f, autofocus: true .form-group.group-description-holder diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml index 4d78215ed3c3bbd19b91da0ecea081437c4e0f1d..b3ed59a1a4aa0a0cc923258a5b7a8e100eff5879 100644 --- a/app/views/profiles/keys/_form.html.haml +++ b/app/views/profiles/keys/_form.html.haml @@ -1,10 +1,6 @@ %div = form_for [:profile, @key], html: { class: 'js-requires-input' } do |f| - - if @key.errors.any? - .alert.alert-danger - %ul - - @key.errors.full_messages.each do |msg| - %li= msg + = form_errors(@key) .form-group = f.label :key, class: 'label-light' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 3d15c0d932bc22cda43e17d3fdac39a8bb03ede4..6609295a2a5517fcd4317cfe3b0e7554ccaa63f4 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -2,11 +2,7 @@ - header_title page_title, profile_notifications_path = form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f| - -if @user.errors.any? - %div.alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + = form_errors(@user) = hidden_field_tag :notification_type, 'global' .row diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 44d758dceb3190e5382b19383abd3d6a97e82eb5..5ac8a8b9d093e9f3340d621384911e4274ca4a38 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -13,11 +13,8 @@ - unless @user.password_automatically_set? or recover your current one = form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f| - -if @user.errors.any? - .alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + = form_errors(@user) + - unless @user.password_automatically_set? .form-group = f.label :current_password, class: 'label-light' diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index d165f758c81f5bc16a09cd81f4d6a05765a80ab4..2eb9fac57c31ae2934f35d3caf178ea3cb078d72 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -7,11 +7,8 @@ Please set a new password before proceeding. %br After a successful password update you will be redirected to login screen. - -if @user.errors.any? - .alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + + = form_errors(@user) - unless @user.password_automatically_set? .form-group diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index dcb3be9585db98cc3cc4d2e055636140843aa6a8..f59d27f7ed013307a7a1c0ff4f786529e4255cc9 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,9 +1,6 @@ = 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? - %div.alert.alert-danger - %ul - - @user.errors.full_messages.each do |msg| - %li= msg + = form_errors(@user) + .row .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 diff --git a/app/views/projects/_errors.html.haml b/app/views/projects/_errors.html.haml index 7c8bb33ed7edc4f3d868742ec68f1a16005d3aab..2dba22d3be665f0b975f054135b3417aca300ede 100644 --- a/app/views/projects/_errors.html.haml +++ b/app/views/projects/_errors.html.haml @@ -1,4 +1 @@ -- if @project.errors.any? - .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × - = @project.errors.full_messages.first += form_errors(@project) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index fa34f7b7d6112b8f43da0c05b4fdf03c28456483..f7c8647ac0e4994089551e7fe87c9d2e969e3bea 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -18,24 +18,17 @@ .pull-right - if commit.status = render_ci_status(commit) - = clipboard_button(clipboard_text: commit.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? .commit-row-description.js-toggle-content %pre = preserve(markdown(escape_once(commit.description), pipeline: :single_line)) .commit-row-info + by = commit_author_link(commit, avatar: true, size: 24) - authored .committed_ago #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} = link_to_browse_code(project, commit) diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index 5e182af26693f87f24cbefc167a8e3d464b7ba03..f6565f858366b793ace7f3bf50fa14685d9cc7cf 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -1,10 +1,6 @@ %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| - -if @key.errors.any? - .alert.alert-danger - %ul - - @key.errors.full_messages.each do |msg| - %li= msg + = form_errors(@key) .form-group = f.label :title, class: "control-label" diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 67d016bd871015f2c414e03eea5ea413c19a92f6..e39224d86c6a1c88f60c31961ca6492bd40fb7db 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -9,10 +9,8 @@ %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| - -if @hook.errors.any? - .alert.alert-danger - - @hook.errors.full_messages.each do |msg| - %p= msg + = form_errors(@hook) + .form-group = f.label :url, "URL", class: 'control-label' .col-sm-10 diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index be7a0bb5628060f090fe097c4f3a3561e4e94176..aa143e54ffe4a8902be01b18c4579c42f2e059a2 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -1,11 +1,5 @@ = 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? - .row - .col-sm-offset-2.col-sm-10 - .alert.alert-danger - - @label.errors.full_messages.each do |msg| - %span= msg - %br + = form_errors(@label) .form-group = f.label :title, class: 'control-label' diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 01dc7519beea37651610217fbf4d194d02329373..7d7c487e9709daebd056021597f754486e212485 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -5,33 +5,74 @@ .hide.alert.alert-danger.mr-compare-errors .merge-request-branches.row .col-md-6 - .panel.panel-default + .panel.panel-default.panel-new-merge-request .panel-heading - %strong Source branch - .panel-body - = 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 }) - - = f.select(:source_branch, @merge_request.source_branches, { include_blank: true }, { class: 'source_branch select2 span2', required: true, data: { placeholder: "Select source branch" } }) + Source branch + .panel-body.clearfix + .merge-request-select.dropdown + = f.hidden_field :source_project_id + = 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 - .mr_source_commit + = icon('spinner spin', class: 'js-source-loading') + %ul.list-unstyled.mr_source_commit .col-md-6 - .panel.panel-default + .panel.panel-default.panel-new-merge-request .panel-heading - %strong Target branch - .panel-body + Target branch + .panel-body.clearfix - 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 }) - - = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', required: true, data: { placeholder: "Select target branch" } }) + .merge-request-select.dropdown + = f.hidden_field :target_project_id + = 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 - .mr_target_commit + = icon('spinner spin', class: "js-target-loading") + %ul.list-unstyled.mr_target_commit - if @merge_request.errors.any? - .alert.alert-danger - - @merge_request.errors.full_messages.each do |msg| - %div= msg - + = form_errors(@merge_request) - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? .light-well.append-bottom-default .center @@ -45,40 +86,11 @@ and %span.label-branch #{@merge_request.target_branch} are the same. - - - .form-actions - = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" - -:javascript - var source_branch = $("#merge_request_source_branch") - , target_branch = $("#merge_request_target_branch") - , target_project = $("#merge_request_target_project_id"); - - $.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(); - }); - + = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" :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; - } + new Compare({ + targetProjectUrl: "#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", + 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)}" }); diff --git a/app/views/projects/merge_requests/branch_from.html.haml b/app/views/projects/merge_requests/branch_from.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..4f90dde6fa801e2f065ac6959f7705e6b4818a2a --- /dev/null +++ b/app/views/projects/merge_requests/branch_from.html.haml @@ -0,0 +1 @@ += commit_to_html(@commit, @source_project, false) diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml deleted file mode 100644 index 9210798f39c051f4fff1f98910bf68f3147e817d..0000000000000000000000000000000000000000 --- a/app/views/projects/merge_requests/branch_from.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -:plain - $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}"); - $('.js-timeago').timeago() diff --git a/app/views/projects/merge_requests/branch_to.html.haml b/app/views/projects/merge_requests/branch_to.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..67a7a6bcec9d993f28f9f2c578797cd95100aaeb --- /dev/null +++ b/app/views/projects/merge_requests/branch_to.html.haml @@ -0,0 +1 @@ += commit_to_html(@commit, @target_project, false) diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml deleted file mode 100644 index 32fe2d535f319ff7bac1077fcf7fbd22c85bace4..0000000000000000000000000000000000000000 --- a/app/views/projects/merge_requests/branch_to.js.haml +++ /dev/null @@ -1,3 +0,0 @@ -:plain - $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}"); - $('.js-timeago').timeago() diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/update_branches.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..1b93188a10c70a2683224bac1e0e4dcd8d14cec6 --- /dev/null +++ b/app/views/projects/merge_requests/update_branches.html.haml @@ -0,0 +1,5 @@ +%ul + - @target_branches.each do |branch| + %li + %a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } } + = branch diff --git a/app/views/projects/merge_requests/update_branches.js.haml b/app/views/projects/merge_requests/update_branches.js.haml deleted file mode 100644 index ca21b3bc0deb365c4db2f3ecc72576c80cd5e4e4..0000000000000000000000000000000000000000 --- a/app/views/projects/merge_requests/update_branches.js.haml +++ /dev/null @@ -1,9 +0,0 @@ -:plain - $(".target_branch").html("#{escape_javascript(options_for_select(@target_branches))}"); - - $('select.target_branch').select2({ - width: 'resolve', - dropdownAutoWidth: true - }); - - $(".mr_target_commit").html(""); diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 23f2bca7baf470f0f34926bd117342e10036983b..b2dae1c70ee0acb614d9dca54b3808a7df5a8f17 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -1,9 +1,6 @@ = 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? - .alert.alert-danger - %ul - - @milestone.errors.full_messages.each do |msg| - %li= msg + = form_errors(@milestone) + .row .col-md-6 .form-group diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index cfd7e1534ca81c1d24778fa72787f9a3b7353e99..653b02da4dbbb10d1bf10c952dd3ac479326f555 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -13,11 +13,7 @@ - if can? current_user, :admin_project, @project = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f| - -if @protected_branch.errors.any? - .alert.alert-danger - %ul - - @protected_branch.errors.full_messages.each do |msg| - %li= msg + = form_errors(@protected_branch) .form-group = f.label :name, "Branch", class: 'control-label' diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml index efe1e6f24c22e144d7b32de64321dfbd9d1fe1c3..ca284b84d39150deb4f6933655129154649bb73b 100644 --- a/app/views/projects/variables/show.html.haml +++ b/app/views/projects/variables/show.html.haml @@ -13,13 +13,7 @@ = nested_form_for @project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f| - - if @project.errors.any? - #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 + = form_errors(@project) = f.fields_for :variables do |variable_form| .form-group diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index f0d1932e23cfb1514ce9aeff32905a5f9998a81b..812876e283562f30afff796211508b2a78871362 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,9 +1,5 @@ = 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? - #error_explanation - .alert.alert-danger - - @page.errors.full_messages.each do |msg| - %p= msg + = form_errors(@page) = f.hidden_field :title, value: @page.title .form-group diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index 5a60ff5a5da8c9fa8e598436610d1fc21208fde9..fc935166bf67379cb59f8de94df0ed00776e753f 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -1,9 +1,4 @@ -- if @service.errors.any? - #error_explanation - .alert.alert-danger - %ul - - @service.errors.full_messages.each do |msg| - %li= msg += form_errors(@service) - if @service.help.present? .well diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index e2a9e5bfb9234574d197a76468a181f992be1a2a..757a3812deb94588a1aef40237486d8c5f2cef02 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -1,10 +1,5 @@ -- if issuable.errors.any? - .row - .col-sm-offset-2.col-sm-10 - .alert.alert-danger - - issuable.errors.full_messages.each do |msg| - %span= msg - %br += form_errors(issuable) + .form-group = f.label :title, class: 'control-label' .col-sm-10 @@ -53,10 +48,11 @@ .issue-assignee = f.label :assignee_id, "Assignee", class: 'control-label' .col-sm-10 - = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", - placeholder: 'Select assignee', class: 'custom-form-control', null_user: true, - selected: issuable.assignee_id, project: @target_project || @project, - first_user: true, current_user: true, include_blank: true) + .issuable-form-select-holder + = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", + placeholder: 'Select assignee', class: 'custom-form-control', null_user: true, + selected: issuable.assignee_id, project: @target_project || @project, + first_user: true, current_user: true, include_blank: true) = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' .form-group @@ -64,8 +60,9 @@ = f.label :milestone_id, "Milestone", class: 'control-label' .col-sm-10 - if milestone_options(issuable).present? - = f.select(:milestone_id, milestone_options(issuable), - { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } }) + .issuable-form-select-holder + = f.select(:milestone_id, milestone_options(issuable), + { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } }) - else .prepend-top-10 %span.light No open milestones available. diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 1041eccd1dfb0a08b160b2560535d0d348a20a85..47ec09f62c604262456d2267470f7f916f8861a8 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -1,10 +1,6 @@ .snippet-form-holder = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input" } do |f| - - if @snippet.errors.any? - .alert.alert-danger - %ul - - @snippet.errors.full_messages.each do |msg| - %li= msg + = form_errors(@snippet) .form-group = f.label :title, class: 'control-label' diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index a9fc38fb04a1e242123fb93cbe17108d173e3ed9..1b445bbbd10200baee8b5b0485206bd480c8a8fd 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -75,6 +75,29 @@ if Gitlab::Metrics.enabled? config.instrument_methods(const) config.instrument_instance_methods(const) end + + # Instruments all Banzai filters + Dir[Rails.root.join('lib', 'banzai', 'filter', '*.rb')].each do |file| + klass = File.basename(file, File.extname(file)).camelize + const = Banzai::Filter.const_get(klass) + + config.instrument_methods(const) + config.instrument_instance_methods(const) + end + + config.instrument_methods(Banzai::ReferenceExtractor) + config.instrument_instance_methods(Banzai::ReferenceExtractor) + + config.instrument_methods(Banzai::Renderer) + config.instrument_methods(Banzai::Querying) + + [Issuable, Mentionable, Participable].each do |klass| + config.instrument_instance_methods(klass) + config.instrument_instance_methods(klass::ClassMethods) + end + + config.instrument_methods(Gitlab::ReferenceExtractor) + config.instrument_instance_methods(Gitlab::ReferenceExtractor) end GC::Profiler.enable diff --git a/doc/development/README.md b/doc/development/README.md index 2f4e7845ccc525798cfcbb311980ad5bc86d7f86..3f3ef068f960d543ebb6b65093a32685a67a8a1a 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -2,6 +2,8 @@ - [Architecture](architecture.md) of 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 - [How to dump production data to staging](db_dump.md) - [Instrumentation](instrumentation.md) diff --git a/doc/development/code_review.md b/doc/development/code_review.md new file mode 100644 index 0000000000000000000000000000000000000000..40ae55ab905527733b6c962d3db4b8a9f2b3d269 --- /dev/null +++ b/doc/development/code_review.md @@ -0,0 +1,78 @@ +# 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) diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index c0192bd67094b587234be81d2c80fb3a82242db4..c1cf2e77c2689ec05187499af6a2750d56eae848 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -2,36 +2,35 @@ GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby code. This can be used to measure the time spent in a specific part of a larger -chunk of code. The resulting data is written to a separate series. +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 -`Gitlab::Metrics.measure` and give it a name for the series to store the data -in: +To start measuring a block of Ruby code you should use `Gitlab::Metrics.measure` +and give it a name: ```ruby -Gitlab::Metrics.measure(:user_logins) do +Gitlab::Metrics.measure(:foo) do ... end ``` -The first argument of this method is the series name and should be plural. This -name will be prefixed with `rails_` or `sidekiq_` depending on whether the code -was run in the Rails application or one of the Sidekiq workers. In the -above example the final series names would be as follows: +3 values are measured for a block: -- rails_user_logins -- sidekiq_user_logins +1. The real time elapsed, stored in NAME_real_time. +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 -other series names. +Both the real and CPU timings are measured in milliseconds. -By default metrics measured using a block contain a single value, "duration", -which contains the number of milliseconds it took to execute the block. Custom -values can be added by passing a Hash as the 2nd argument. Custom tags can be -added by passing a Hash as the 3rd argument. A simple example is as follows: +Multiple calls to the same block will result in the final values being the sum +of all individual values. Take this code for example: ```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 ``` + +Here the final value of `sleep_real_time` will be `3`, _not_ `1`. diff --git a/doc/install/installation.md b/doc/install/installation.md index d97dc7d13115f4894baeb848b6ee14e4909d1db0..f8f7d6a9ebedfb54fcc0669164eb63310d403a7a 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -352,7 +352,7 @@ GitLab Shell is an SSH access and repository management software developed speci cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.2 + sudo -u git -H git checkout v0.7.1 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md index 57847d2d9fd45af6a851feceb54e594f213374b1..8599133a726c292de860f8cf8353b9718cc646d9 100644 --- a/doc/update/8.6-to-8.7.md +++ b/doc/update/8.6-to-8.7.md @@ -58,7 +58,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.7.2 +sudo -u git -H git checkout v0.7.1 sudo -u git -H make ``` diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature index 10bd6fec80373d7682281760535ac5d66f8e8581..67f1e117f7fa9671dc71cd0e2588f958074d6c86 100644 --- a/features/project/forked_merge_requests.feature +++ b/features/project/forked_merge_requests.feature @@ -4,6 +4,7 @@ Feature: Project Forked Merge Requests And I am a member of project "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 Given I visit project "Forked Shop" merge requests page And I click link "New Merge Request" diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 823658b4f240657b768e5d8f59a48ffed4bba3bf..ecda4ea824035665c6ba4b8718dd2119eb8c3b40 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -70,6 +70,7 @@ Feature: Project Merge Requests When I click link "Reopen" Then I should see reopened merge request "Bug NS-04" + @javascript Scenario: I submit new unassigned merge request Given I click link "New Merge Request" And I submit new merge request "Wiki Feature" diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 7e4425ff6625a988449464b49b1cba4f35dc0506..612bb8fd8b1e5d4f554cbaaedbda44505cb1c50e 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -34,10 +34,14 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end 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" - select @project.path_with_namespace, from: "merge_request_target_project_id" - select "fix", from: "merge_request_source_branch" - select "master", from: "merge_request_target_branch" + first('.js-source-project').click + first('.dropdown-source-project a', text: @forked_project.path_with_namespace) + + 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" @@ -115,10 +119,10 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end 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(:select, "merge_request_target_project_id", {}).value).to eq @project.id.to_s - expect(find(:select, "merge_request_source_branch", {}).value).to eq "" - expect(find(:select, "merge_request_target_branch", {}).value).to eq "master" + expect(find_by_id("merge_request_source_project_id", visible: false).value).to eq @forked_project.id.to_s + expect(find_by_id("merge_request_target_project_id", visible: false).value).to eq @project.id.to_s + expect(find_by_id("merge_request_source_branch", visible: false).value).to eq nil + expect(find_by_id("merge_request_target_branch", visible: false).value).to eq "master" click_button "Compare branches" end @@ -127,7 +131,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end 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 step 'I click "Assign to" dropdown"' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 9887bf80a894ef7fc0ac04bcbe69e75f0d620399..2a7f2cca005947cc50ce4e2e2c2abc1fa427604d 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -93,8 +93,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I submit new merge request "Wiki Feature"' do - select "fix", from: "merge_request_source_branch" - select "feature", from: "merge_request_target_branch" + find('.js-source-branch').click + 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" fill_in "merge_request_title", with: "Wiki Feature" click_button "Submit merge request" diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 243469b8e7d55f0eb237a70e0581604ed85e65aa..e072505e5d7bec13e6807fa9921575e5b6741c8e 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -213,13 +213,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I see Browse file link' do - expect(page).to have_link 'Browse File »' - expect(page).not_to have_link 'Browse Files »' + expect(page).to have_link 'Browse File' + expect(page).not_to have_link 'Browse Files' end step 'I see Browse code link' do - expect(page).to have_link 'Browse Files »' - expect(page).not_to have_link 'Browse File »' + expect(page).to have_link 'Browse Files' expect(page).not_to have_link 'Browse Directory »' end diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index ae714c87dc54bef7bb9032af713e897b699f7081..c14a9c4c72218fea9baef0482a0dbc88f20c784c 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -19,8 +19,10 @@ module Banzai cache_key = full_cache_key(cache_key, context[:pipeline]) if cache_key - Rails.cache.fetch(cache_key) do - cacheless_render(text, context) + Gitlab::Metrics.measure(:banzai_cached_render) do + Rails.cache.fetch(cache_key) do + cacheless_render(text, context) + end end else cacheless_render(text, context) @@ -64,13 +66,15 @@ module Banzai private def self.cacheless_render(text, context = {}) - result = render_result(text, context) + Gitlab::Metrics.measure(:banzai_cacheless_render) do + result = render_result(text, context) - output = result[:output] - if output.respond_to?(:to_html) - output.to_html - else - output.to_s + output = result[:output] + if output.respond_to?(:to_html) + output.to_html + else + output.to_s + end end end diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 4a3f47b5a95632b481b1e3e0ce16b0770c23ef96..2a0a5629be55093c7ed13756e6115e7e072a18c7 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -74,24 +74,32 @@ module Gitlab # # Example: # - # Gitlab::Metrics.measure(:find_by_username_timings) do + # Gitlab::Metrics.measure(:find_by_username_duration) do # User.find_by_username(some_username) # end # - # series - The name of the series to store the data in. - # values - A Hash containing extra values to add to the metric. - # tags - A Hash containing extra tags to add to the metric. + # name - The name of the field to store the execution time in. # # Returns the value yielded by the supplied block. - def self.measure(series, values = {}, tags = {}) - return yield unless Transaction.current + def self.measure(name) + 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 - 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 end @@ -107,5 +115,11 @@ module Gitlab new(udp: { host: host, port: port }) end end + + private + + def self.current_transaction + Transaction.current + end end end diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb index 83371265278f6da3b93d1bb34bd0186771e6cc7f..a7d183b2f94a801f9c8f44d72034e35cf3edd312 100644 --- a/lib/gitlab/metrics/system.rb +++ b/lib/gitlab/metrics/system.rb @@ -30,6 +30,17 @@ module Gitlab 0 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 diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..39805da9d0bb3f8a35a6584d50cc34be7a4c0729 --- /dev/null +++ b/spec/features/dashboard_issues_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe "Dashboard Issues filtering", feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:milestone) { create(:milestone, project: project) } + + context 'filtering by milestone' do + before do + project.team << [user, :master] + login_as(user) + + create(:issue, project: project, author: user, assignee: user) + create(:issue, project: project, author: user, assignee: user, milestone: milestone) + + visit_issues + end + + it 'should show all issues with no milestone' do + show_milestone_dropdown + + click_link 'No Milestone' + + expect(page).to have_selector('.issue', count: 1) + end + + it 'should show all issues with any milestone' do + show_milestone_dropdown + + click_link 'Any Milestone' + + expect(page).to have_selector('.issue', count: 2) + end + + it 'should show all issues with the selected milestone' do + show_milestone_dropdown + + page.within '.dropdown-content' do + click_link milestone.title + end + + expect(page).to have_selector('.issue', count: 1) + end + end + + def show_milestone_dropdown + click_button 'Milestone' + expect(page).to have_selector('.dropdown-content', visible: true) + end + + def visit_issues + visit issues_dashboard_path + end +end diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb index fd02d584848ad96e94ee1fa525bcd1c13defa13b..00b60bd0e7564551a175c7bc64f908de34ecef67 100644 --- a/spec/features/merge_requests/create_new_mr_spec.rb +++ b/spec/features/merge_requests/create_new_mr_spec.rb @@ -1,6 +1,6 @@ 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(:project) { create(:project, :public) } @@ -13,9 +13,12 @@ feature 'Create New Merge Request', feature: true, js: false do it 'generates a diff for an orphaned branch' do 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_link "Changes" expect(page).to have_content "README.md" expect(page).to have_content "wm.png" @@ -23,6 +26,8 @@ feature 'Create New Merge Request', feature: true, js: false do fill_in "merge_request_title", with: "Orphaned MR test" click_button "Submit merge request" + click_link "Check out branch" + expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch' end end diff --git a/spec/helpers/form_helper_spec.rb b/spec/helpers/form_helper_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b20373a96fb4bef8a218882d4b84ab0d6916a2e3 --- /dev/null +++ b/spec/helpers/form_helper_spec.rb @@ -0,0 +1,46 @@ +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 diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index f8c1d956ca1fb9e0c5cb41d2e7e2b6b7e217dfbd..d6ae54e25e859830a534a11ed47eea2dc8f23f32 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -26,4 +26,10 @@ describe Gitlab::Metrics::System do 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 diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index 8f63a5f2043d6da59c675b1d43b12daf434b90a7..3dee13e27f442d2b7c5cba896f175c0f36b3062a 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -74,24 +74,21 @@ describe Gitlab::Metrics do let(:transaction) { Gitlab::Metrics::Transaction.new } before do - allow(Gitlab::Metrics::Transaction).to receive(:current). + allow(Gitlab::Metrics).to receive(:current_transaction). and_return(transaction) end it 'adds a metric to the current transaction' do - expect(transaction).to receive(:add_metric). - with(:foo, { duration: a_kind_of(Numeric) }, { tag: 'value' }) + expect(transaction).to receive(:increment). + with('foo_real_time', a_kind_of(Numeric)) - Gitlab::Metrics.measure(:foo, {}, tag: 'value') { 10 } - end - - it 'supports adding of custom values' do - values = { duration: a_kind_of(Numeric), number: 10 } + expect(transaction).to receive(:increment). + with('foo_cpu_time', a_kind_of(Numeric)) - expect(transaction).to receive(:add_metric). - with(:foo, values, { tag: 'value' }) + expect(transaction).to receive(:increment). + with('foo_call_count', 1) - Gitlab::Metrics.measure(:foo, { number: 10 }, tag: 'value') { 10 } + Gitlab::Metrics.measure(:foo) { 10 } end it 'returns the return value of the block' do