From dc20dd275fb15217b04431fa7f05b283ea03621c Mon Sep 17 00:00:00 2001 From: David Date: Fri, 15 Apr 2016 02:18:46 +0000 Subject: aiionx_sidekiq_log_patch --- lib/gitlab/sidekiq_middleware/arguments_logger.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/sidekiq_middleware/arguments_logger.rb b/lib/gitlab/sidekiq_middleware/arguments_logger.rb index 7813091ec7b..82a59a7a87e 100644 --- a/lib/gitlab/sidekiq_middleware/arguments_logger.rb +++ b/lib/gitlab/sidekiq_middleware/arguments_logger.rb @@ -2,7 +2,7 @@ module Gitlab module SidekiqMiddleware class ArgumentsLogger def call(worker, job, queue) - Sidekiq.logger.info "arguments: #{job['args']}" + Sidekiq.logger.info "arguments: #{JSON.dump(job['args'])}" yield end end -- cgit v1.2.1 From dab95689a3f267e6ee4e4fa9da304a01d37a5a4e Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Thu, 23 Jun 2016 13:07:51 +0000 Subject: Fix `ref` parameter name for `commits/statuses` The attribute to filter by branch or tag needs to be named `ref`, not `ref_name`. And indeed the attribute in the JSON response is `ref` (and not `ref_name`). Tested on Gitlab CE 8.9. --- doc/api/commits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/commits.md b/doc/api/commits.md index 57c2e1d9b87..d1317e5cc07 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -229,7 +229,7 @@ GET /projects/:id/repository/commits/:sha/statuses | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | `sha` | string | yes | The commit SHA -| `ref_name`| string | no | The name of a repository branch or tag or, if not given, the default branch +| `ref` | string | no | The name of a repository branch or tag or, if not given, the default branch | `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test` | `name` | string | no | Filter by [job name](../ci/yaml/README.md#jobs), e.g., `bundler:audit` | `all` | boolean | no | Return all statuses, not only the latest ones -- cgit v1.2.1 From 33540238ee7da03ef532385e7a52103276252c18 Mon Sep 17 00:00:00 2001 From: Christian Meter Date: Thu, 7 Jul 2016 11:47:13 +0000 Subject: Replace builds_emails_service.png to fit to new layout of GitLab --- doc/project_services/img/builds_emails_service.png | Bin 33943 -> 30956 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/project_services/img/builds_emails_service.png b/doc/project_services/img/builds_emails_service.png index 88943dc410e..440728795be 100644 Binary files a/doc/project_services/img/builds_emails_service.png and b/doc/project_services/img/builds_emails_service.png differ -- cgit v1.2.1 From 51ef233e25d6b9cfed16b8b3a5b532e429993dec Mon Sep 17 00:00:00 2001 From: Niels Keurentjes Date: Sun, 10 Jul 2016 23:35:10 +0000 Subject: Fix docker.sock reference in config.toml Mapping was omitted so it would just create a temp volume. --- doc/ci/docker/using_docker_build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 7f83f846454..ee924323d96 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -188,7 +188,7 @@ In order to do that, follow the steps: image = "docker:latest" privileged = false disable_cache = false - volumes = ["/var/run/docker.sock", "/cache"] + volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"] [runners.cache] Insecure = false ``` -- cgit v1.2.1 From f0691ec7c589543f9e294bf0d9cbc9eea2c2f166 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Jul 2016 07:12:12 +0000 Subject: Revert "Merge branch 'revert-bdb6f1e6' into 'master'" This reverts merge request !5290 --- app/assets/javascripts/dispatcher.js.coffee | 2 ++ app/assets/javascripts/labels_select.js.coffee | 31 +++++++++++++------ app/assets/javascripts/milestone_select.js.coffee | 2 +- app/assets/javascripts/users_select.js.coffee | 9 ++++-- app/assets/stylesheets/framework/dropdowns.scss | 2 +- app/assets/stylesheets/pages/merge_requests.scss | 4 +++ app/helpers/issuables_helper.rb | 2 +- app/views/shared/issuable/_filter.html.haml | 4 +-- app/views/shared/issuable/_form.html.haml | 36 +++++++--------------- .../shared/issuable/_label_dropdown.html.haml | 14 +++++---- .../shared/issuable/_milestone_dropdown.html.haml | 8 ++--- features/project/issues/issues.feature | 1 + features/steps/project/forked_merge_requests.rb | 12 +++----- features/steps/project/issues/issues.rb | 3 +- spec/features/issues/move_spec.rb | 2 +- spec/features/issues_spec.rb | 5 ++- 16 files changed, 72 insertions(+), 65 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index afaa6407b05..b5da15e9e49 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -39,6 +39,8 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() new GLForm($('.issue-form')) new IssuableForm($('.issue-form')) + new LabelsSelect() + new MilestoneSelect() when 'projects:merge_requests:new', 'projects:merge_requests:edit' new Diff() shortcut_handler = new ShortcutsNavigation() diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 7688609b301..1a802b81452 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -184,20 +184,22 @@ class @LabelsSelect .value() if $dropdown.hasClass 'js-extra-options' - if showNo - data.unshift( - id: 0 - title: 'No Label' - ) - + extraData = [] if showAny - data.unshift( + extraData.push( isAny: true title: 'Any Label' ) - if data.length > 2 - data.splice 2, 0, 'divider' + if showNo + extraData.push( + id: 0 + title: 'No Label' + ) + + if extraData.length + extraData.push 'divider' + data = extraData.concat(data) callback data @@ -287,6 +289,12 @@ class @LabelsSelect defaultLabel fieldName: $dropdown.data('field-name') id: (label) -> + if $dropdown.hasClass('js-issuable-form-dropdown') + if label.id is 0 + return + else + return label.id + if $dropdown.hasClass("js-filter-submit") and not label.isAny? label.title else @@ -300,6 +308,9 @@ class @LabelsSelect $selectbox.hide() # display:block overrides the hide-collapse rule $value.removeAttr('style') + + return if $dropdown.hasClass('js-issuable-form-dropdown') + if $dropdown.hasClass 'js-multiselect' if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) selectedLabels = $dropdown @@ -321,7 +332,7 @@ class @LabelsSelect clicked: (label) -> _this.enableBulkLabelDropdown() - if $dropdown.hasClass('js-filter-bulk-update') + if $dropdown.hasClass('js-filter-bulk-update') or $dropdown.hasClass('js-issuable-form-dropdown') return page = $('body').data 'page' diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 8ab03ed93ee..3a036569317 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -62,7 +62,7 @@ class @MilestoneSelect title: 'Upcoming' ) - if extraOptions.length > 2 + if extraOptions.length > 0 extraOptions.push 'divider' callback(extraOptions.concat(data)) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 344be811e0d..e061f042ca9 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -155,11 +155,13 @@ class @UsersSelect # display:block overrides the hide-collapse rule $value.css('display', '') - clicked: (user) -> + clicked: (user, $el, e) -> 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') + if $dropdown.hasClass('js-filter-bulk-update') or $dropdown.hasClass('js-issuable-form-dropdown') + e.preventDefault() + selectedId = user.id return if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) @@ -172,7 +174,8 @@ class @UsersSelect .closest('.selectbox') .find("input[name='#{$dropdown.data('field-name')}']").val() assignTo(selected) - + id: (user) -> + user.id renderRow: (user) -> username = if user.username then "@#{user.username}" else "" avatar = if user.avatar_url then user.avatar_url else false diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index d4e900f80ef..3fd0e12568d 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -56,7 +56,7 @@ position: absolute; top: 50%; right: 6px; - margin-top: -4px; + margin-top: -6px; color: $dropdown-toggle-icon-color; font-size: 10px; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 5254faf723d..91ea538785b 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -336,6 +336,10 @@ .issuable-form-select-holder { display: inline-block; width: 250px; + + .dropdown-menu-toggle { + width: 100%; + } } .table-holder { diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 47d174361db..a3a8c7d5ff9 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -9,7 +9,7 @@ module IssuablesHelper def multi_label_name(current_labels, default_label) # current_labels may be a string from before - if current_labels.is_a?(Array) + if current_labels.is_a?(Array) && current_labels.any? if current_labels.count > 1 "#{current_labels[0]} +#{current_labels.count - 1} more" else diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 0b7fa8c7d06..be8248876b4 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -21,10 +21,10 @@ placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) .filter-item.inline.milestone-filter - = render "shared/issuable/milestone_dropdown" + = render "shared/issuable/milestone_dropdown", selected: params[:milestone_title], name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown" + = render "shared/issuable/label_dropdown", selected: params[:label_name], data_options: { field_name: "label_name[]" } .pull-right = render 'shared/sort_dropdown' diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c30bdb0ae91..a8a8426df52 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -52,38 +52,24 @@ = f.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .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) - %div - = link_to 'Assign to me', '#', class: 'assign-to-me-link prepend-top-5 inline' + - project = @target_project || @project + - if issuable.assignee_id + = hidden_field_tag("#{issuable.class.model_name.param_key}[assignee_id]", issuable.assignee_id) + = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", + placeholder: "Search assignee", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (project.id if project), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee" } }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } - - if milestone_options(issuable).present? - .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. - - if can? current_user, :admin_milestone, issuable.project - %div - = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank, class: "prepend-top-5 inline" + .issuable-form-select-holder + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone_id, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false .form-group - has_labels = issuable.project.labels.any? + - selected_labels = issuable.label_ids.any? ? issuable.label_ids : nil + - label_dropdown_toggle = issuable.labels.map { |label| label.title } = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } - - if has_labels - .issuable-form-select-holder - = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, - { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" } - - else - %span.light No labels yet. - - if can? current_user, :admin_label, issuable.project - %div - = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank, class: "prepend-top-5 inline" + .issuable-form-select-holder + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: selected_labels, selected_toggle: label_dropdown_toggle, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: "false" } - if has_due_date .col-lg-6 .form-group diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index d34d28f6736..bcbe133ce62 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -4,19 +4,21 @@ - show_footer = local_assigns.fetch(:show_footer, true) - data_options = local_assigns.fetch(:data_options, {}) - classes = local_assigns.fetch(:classes, []) -- dropdown_data = {toggle: 'dropdown', field_name: 'label_name[]', show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"} +- selected = local_assigns.fetch(:selected, nil) +- selected_toggle = local_assigns.fetch(:selected_toggle, nil) +- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", selected: selected, project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"} - dropdown_data.merge!(data_options) - classes << 'js-extra-options' if extra_options - classes << 'js-filter-submit' if filter_submit -- if params[:label_name].present? - - if params[:label_name].respond_to?('any?') - - params[:label_name].each do |label| - = hidden_field_tag "label_name[]", label, id: nil +- if selected.present? + - if selected.respond_to?('any?') + - selected.each do |label| + = hidden_field_tag data_options[:field_name], label, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text - = h(multi_label_name(params[:label_name], "Label")) + = h(multi_label_name(selected_toggle || selected, "Label")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 2fcf40ece99..9188ef72d52 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,7 +1,7 @@ -- if params[:milestone_title].present? - = hidden_field_tag(:milestone_title, params[:milestone_title]) -= dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", - placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, show_upcoming: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do +- if selected.present? + = hidden_field_tag(name, selected) += dropdown_tag(milestone_dropdown_label(selected), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", + placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected, project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if @project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, @project diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index 358e622b736..80670063ea0 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -37,6 +37,7 @@ Feature: Project Issues And I submit new issue "500 error on profile" Then I should see issue "500 error on profile" + @javascript Scenario: I submit new unassigned issue with labels Given project "Shop" has labels: "bug", "feature", "enhancement" And I click link "New Issue" diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 6b56a77b832..8f71dfdd899 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -135,19 +135,17 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'I click "Assign to" dropdown"' do - first('.ajax-users-select').click + click_button 'Assignee' end step 'I should see the target project ID in the input selector' do - expect(page).to have_selector("input[data-project-id=\"#{@project.id}\"]") + expect(find('.js-assignee-search')["data-project-id"]).to eq "#{@project.id}" end step 'I should see the users from the target project ID' do - expect(page).to have_selector('.user-result', visible: true, count: 3) - users = page.all('.user-name') - expect(users[0].text).to eq 'Unassigned' - expect(users[1].text).to eq current_user.name - expect(users[2].text).to eq @project.users.first.name + expect(page).to have_content 'Unassigned' + expect(page).to have_content current_user.name + expect(page).to have_content @project.users.first.name end # Verify a link is generated against the correct project diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index 35f166c7c08..b785e15f70e 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -82,7 +82,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps step 'I submit new issue "500 error on profile" with label \'bug\'' do fill_in "issue_title", with: "500 error on profile" - select 'bug', from: "Labels" + click_button "Label" + click_link "bug" click_button "Submit issue" end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 7773c486b4e..055210399a7 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -55,7 +55,7 @@ feature 'issue move to another project' do first('.select2-choice').click end - fill_in('s2id_autogen2_search', with: new_project_search.name) + fill_in('s2id_autogen1_search', with: new_project_search.name) page.within '.select2-drop' do expect(page).to have_content(new_project_search.name) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index d51c9abea19..d00cffa4e2b 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -50,9 +50,8 @@ describe 'Issues', feature: true do expect(page).to have_content "Assignee #{@user.name}" - first('#s2id_issue_assignee_id').click - sleep 2 # wait for ajax stuff to complete - first('.user-result').click + first('.js-user-search').click + click_link 'Unassigned' click_button 'Save changes' -- cgit v1.2.1 From a33a67e511cd2cad1abb3d13088c8f39fd174e95 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Jul 2016 08:23:02 +0100 Subject: Use target_project instead of projectg --- app/assets/javascripts/dispatcher.js.coffee | 2 ++ app/views/shared/issuable/_label_dropdown.html.haml | 5 +++-- app/views/shared/issuable/_milestone_dropdown.html.haml | 13 +++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index b5da15e9e49..148ba394353 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -46,6 +46,8 @@ class Dispatcher shortcut_handler = new ShortcutsNavigation() new GLForm($('.merge-request-form')) new IssuableForm($('.merge-request-form')) + new LabelsSelect() + new MilestoneSelect() when 'projects:tags:new' new ZenMode() new GLForm($('.tag-form')) diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index bcbe133ce62..1e238811657 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,3 +1,4 @@ +- project = @target_project || @project - show_create = local_assigns.fetch(:show_create, true) - extra_options = local_assigns.fetch(:extra_options, true) - filter_submit = local_assigns.fetch(:filter_submit, true) @@ -6,7 +7,7 @@ - classes = local_assigns.fetch(:classes, []) - selected = local_assigns.fetch(:selected, nil) - selected_toggle = local_assigns.fetch(:selected_toggle, nil) -- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", selected: selected, project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"} +- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", selected: selected, project_id: project.try(:id), labels: labels_filter_path, default_label: "Label"} - dropdown_data.merge!(data_options) - classes << 'js-extra-options' if extra_options - classes << 'js-filter-submit' if filter_submit @@ -22,6 +23,6 @@ = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } - - if show_create and @project and can?(current_user, :admin_label, @project) + - if show_create and project and can?(current_user, :admin_label, project) = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 9188ef72d52..914f01e4137 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,16 +1,17 @@ +- project = @target_project || @project - if selected.present? = hidden_field_tag(name, selected) = dropdown_tag(milestone_dropdown_label(selected), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", - placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected, project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - - if @project + placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected, project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do + - if project %ul.dropdown-footer-list - - if can? current_user, :admin_milestone, @project + - if can? current_user, :admin_milestone, project %li - = link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do + = link_to new_namespace_project_milestone_path(project.namespace, project), title: "New Milestone" do Create new %li - = link_to namespace_project_milestones_path(@project.namespace, @project) do - - if can? current_user, :admin_milestone, @project + = link_to namespace_project_milestones_path(project.namespace, project) do + - if can? current_user, :admin_milestone, project Manage milestones - else View milestones -- cgit v1.2.1 From 7f0ccbac344ccca1fba12d5a9d4dd25f3b5f7a01 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Jul 2016 08:31:04 +0100 Subject: Fix issue with milestone dropdown submitting the form without selected value --- app/assets/javascripts/milestone_select.js.coffee | 7 ++++--- app/views/shared/issuable/_form.html.haml | 2 +- app/views/shared/issuable/_milestone_dropdown.html.haml | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 3a036569317..c4dd97d2c61 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -79,7 +79,7 @@ class @MilestoneSelect text: (milestone) -> _.escape(milestone.title) id: (milestone) -> - if !useId + if !useId and not $dropdown.is('.js-issuable-form-dropdown') milestone.name else milestone.id @@ -90,12 +90,13 @@ class @MilestoneSelect # display:block overrides the hide-collapse rule $value.css('display', '') - clicked: (selected) -> + clicked: (selected, $el, e) -> 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' + if $dropdown.hasClass('js-filter-bulk-update') or $dropdown.hasClass('js-issuable-form-dropdown') + e.preventDefault() return if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index a8a8426df52..b7fbfcb6b5f 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -61,7 +61,7 @@ = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone_id, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone_id, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: 'js-issuable-form-dropdown' .form-group - has_labels = issuable.project.labels.any? - selected_labels = issuable.label_ids.any? ? issuable.label_ids : nil diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 914f01e4137..c19e0674246 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,7 +1,8 @@ - project = @target_project || @project +- selected_text = project.milestones.find(selected).try(:name) - if selected.present? = hidden_field_tag(name, selected) -= dropdown_tag(milestone_dropdown_label(selected), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable", += dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected, project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list -- cgit v1.2.1 From 82ef4f1ef7f20720cf685b0adf738b516fa9fdab Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Jul 2016 08:46:12 +0100 Subject: Fix issue with label select not correctly showing selected values --- app/assets/javascripts/gl_dropdown.js.coffee | 8 ++++---- app/assets/javascripts/labels_select.js.coffee | 23 +++++++++++------------ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 951530e03a5..3bc9ac450e3 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -486,7 +486,7 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel - @updateLabel(selectedObject, el, @) + @updateLabel(selectedObject, el, @, false) else selectedObject else if el.hasClass(INDETERMINATE_CLASS) @@ -515,7 +515,7 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel - @updateLabel(selectedObject, el, @) + @updateLabel(selectedObject, el, @, true) if value? if !field.length and fieldName @addInput(fieldName, value) @@ -616,8 +616,8 @@ class GitLabDropdown # Scroll the dropdown content up $dropdownContent.scrollTop(listItemTop - dropdownContentTop) - updateLabel: (selected = null, el = null, instance = null) => - $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance) + updateLabel: (selected = null, el = null, instance = null, added = false) => + $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance, added) $.fn.glDropdown = (opts) -> return @.each -> diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 1a802b81452..ce030a8c07b 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -24,6 +24,7 @@ class @LabelsSelect $newLabelError = $('.js-label-error') $colorPreview = $('.js-dropdown-label-color-preview') $newLabelCreateButton = $('.js-new-label-btn') + selectedLabels = [] $newLabelError.hide() $loading = $block.find('.block-loading').fadeOut() @@ -272,19 +273,17 @@ class @LabelsSelect fields: ['title'] selectable: true filterable: true - toggleLabel: (selected, el) -> - selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active') + toggleLabel: (selected, el, e, added) -> + if added + selectedLabels.push selected.title + else + index = selectedLabels.indexOf selected.title + selectedLabels.splice index, 1 - if selected and selected.title? - if selected_labels.length > 1 - "#{selected.title} +#{selected_labels.length - 1} more" - else - selected.title - else if not selected and selected_labels.length isnt 0 - if selected_labels.length > 1 - "#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more" - else if selected_labels.length is 1 - $(selected_labels).text() + if selectedLabels.length > 1 + "#{selectedLabels[0]} +#{selectedLabels.length - 1} more" + else if selectedLabels.length is 1 + selectedLabels[0] else defaultLabel fieldName: $dropdown.data('field-name') -- cgit v1.2.1 From 0a9860817926cf9f32ce1ee6b4dc28ba01366b76 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Jul 2016 09:03:19 +0100 Subject: GL dropdown default toggle color --- app/assets/javascripts/gl_dropdown.js.coffee | 9 ++++++++- app/assets/javascripts/labels_select.js.coffee | 5 +++++ app/assets/javascripts/milestone_select.js.coffee | 5 +++-- app/assets/javascripts/users_select.js.coffee | 6 +++--- app/assets/stylesheets/framework/dropdowns.scss | 6 ++++++ app/helpers/dropdowns_helper.rb | 3 ++- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- app/views/shared/issuable/_milestone_dropdown.html.haml | 2 +- 8 files changed, 29 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 3bc9ac450e3..6313ad837f0 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -617,7 +617,14 @@ class GitLabDropdown $dropdownContent.scrollTop(listItemTop - dropdownContentTop) updateLabel: (selected = null, el = null, instance = null, added = false) => - $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance, added) + $toggleText = $(@el).find(".dropdown-toggle-text") + $toggleText.text @options.toggleLabel(selected, el, instance, added) + + if @options.defaultLabel + if $toggleText.text().trim() is @options.defaultLabel + $toggleText.addClass('is-default') + else + $toggleText.removeClass('is-default') $.fn.glDropdown = (opts) -> return @.each -> diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index ce030a8c07b..d1225eb0851 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -4,6 +4,7 @@ class @LabelsSelect $('.js-label-select').each (i, dropdown) -> $dropdown = $(dropdown) + $toggleText = $dropdown.find('.dropdown-toggle-text') projectId = $dropdown.data('project-id') labelUrl = $dropdown.data('labels') issueUpdateURL = $dropdown.data('issueUpdate') @@ -280,12 +281,16 @@ class @LabelsSelect index = selectedLabels.indexOf selected.title selectedLabels.splice index, 1 + if selected.id? and selected.id is 0 + selectedLabels = [] + if selectedLabels.length > 1 "#{selectedLabels[0]} +#{selectedLabels.length - 1} more" else if selectedLabels.length is 1 selectedLabels[0] else defaultLabel + defaultLabel: defaultLabel fieldName: $dropdown.data('field-name') id: (label) -> if $dropdown.hasClass('js-issuable-form-dropdown') diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index c4dd97d2c61..0d71f3b623a 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -70,11 +70,12 @@ class @MilestoneSelect search: fields: ['title'] selectable: true - toggleLabel: (selected) -> - if selected && 'id' of selected + toggleLabel: (selected, el, e, added) -> + if selected and 'id' of selected and added selected.title else defaultLabel + defaultLabel: defaultLabel fieldName: $dropdown.data('field-name') text: (milestone) -> _.escape(milestone.title) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index e061f042ca9..bf551430999 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -142,12 +142,12 @@ class @UsersSelect selectable: true fieldName: $dropdown.data('field-name') - toggleLabel: (selected) -> - if selected && 'id' of selected + toggleLabel: (selected, el, e, added) -> + if selected and 'id' of selected and added if selected.text then selected.text else selected.name else defaultLabel - + defaultLabel: defaultLabel inputId: 'issue_assignee_id' hidden: (e) -> diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 3fd0e12568d..8fd09cabaff 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -562,3 +562,9 @@ display: block; color: $gl-placeholder-color; } + +.dropdown-toggle-text { + &.is-default { + color: $gl-placeholder-color; + } +} diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 4566f3782cc..cc2a5a7b134 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -40,8 +40,9 @@ module DropdownsHelper end def dropdown_toggle(toggle_text, data_attr, options = {}) + default_label = options[:data][:default_label] content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do - output = content_tag(:span, toggle_text, class: "dropdown-toggle-text") + output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output << icon('chevron-down') output.html_safe end diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 1e238811657..670c2db4795 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -18,7 +18,7 @@ = hidden_field_tag data_options[:field_name], label, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} - %span.dropdown-toggle-text + %span.dropdown-toggle-text{ class: ("is-default" if selected_toggle) } = h(multi_label_name(selected_toggle || selected, "Label")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index c19e0674246..ec1864b5536 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,5 +1,5 @@ - project = @target_project || @project -- selected_text = project.milestones.find(selected).try(:name) +- selected_text = project.milestones.find_by_id(selected).try(:name) - if selected.present? = hidden_field_tag(name, selected) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable", -- cgit v1.2.1 From bf1d0cbeaefbbf431bc6c5042e7406c9842b5226 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Jul 2016 09:17:27 +0100 Subject: Fixed label dropdown not selecting no labels --- app/assets/javascripts/labels_select.js.coffee | 4 ++++ app/views/shared/issuable/_form.html.haml | 4 +++- app/views/shared/issuable/_label_dropdown.html.haml | 5 +++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index d1225eb0851..39a32f903d4 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -27,6 +27,10 @@ class @LabelsSelect $newLabelCreateButton = $('.js-new-label-btn') selectedLabels = [] + $("input[name='#{$dropdown.data('field-name')}']").each -> + title = $(this).data('title') + selectedLabels.push($(this).data('title')) if title + $newLabelError.hide() $loading = $block.find('.block-loading').fadeOut() diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index b7fbfcb6b5f..8820172a46e 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -66,10 +66,12 @@ - has_labels = issuable.project.labels.any? - selected_labels = issuable.label_ids.any? ? issuable.label_ids : nil - label_dropdown_toggle = issuable.labels.map { |label| label.title } + - field_name = "#{issuable.class.model_name.param_key}[label_ids][]" = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" + = hidden_field_tag field_name, "" .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: selected_labels, selected_toggle: label_dropdown_toggle, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: "false" } + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: selected_labels, selected_toggle: label_dropdown_toggle, data_options: { field_name: field_name, show_any: "false" } - if has_due_date .col-lg-6 .form-group diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 670c2db4795..937dc210193 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -14,11 +14,12 @@ - if selected.present? - if selected.respond_to?('any?') + - selected = project.labels.find(selected) - selected.each do |label| - = hidden_field_tag data_options[:field_name], label, id: nil + = hidden_field_tag data_options[:field_name], label.id, id: nil, data: { title: label.title } .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} - %span.dropdown-toggle-text{ class: ("is-default" if selected_toggle) } + %span.dropdown-toggle-text{ class: ("is-default" if selected.nil?) } = h(multi_label_name(selected_toggle || selected, "Label")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable -- cgit v1.2.1 From 3582861cbcb3e4c80822408c6c31d0de48720813 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Jul 2016 09:32:08 +0100 Subject: Fixed label dropdown bugs with pre-selecting the toggle label --- app/assets/javascripts/labels_select.js.coffee | 1 + app/helpers/issuables_helper.rb | 5 +++-- app/views/shared/issuable/_label_dropdown.html.haml | 6 ++++-- app/views/shared/issuable/_milestone_dropdown.html.haml | 1 + app/views/shared/issuable/_sidebar.html.haml | 8 ++++---- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 39a32f903d4..ce630dcacb3 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -293,6 +293,7 @@ class @LabelsSelect else if selectedLabels.length is 1 selectedLabels[0] else + console.log selectedLabels.length, defaultLabel defaultLabel defaultLabel: defaultLabel fieldName: $dropdown.data('field-name') diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index a3a8c7d5ff9..114cf804d6f 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -10,10 +10,11 @@ module IssuablesHelper def multi_label_name(current_labels, default_label) # current_labels may be a string from before if current_labels.is_a?(Array) && current_labels.any? + title = current_labels[0].try(:title) || current_labels[0] if current_labels.count > 1 - "#{current_labels[0]} +#{current_labels.count - 1} more" + "#{title} +#{current_labels.count - 1} more" else - current_labels[0] + title end elsif current_labels.is_a?(String) if current_labels.nil? || current_labels.empty? diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 937dc210193..36a1ac7664e 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -14,9 +14,11 @@ - if selected.present? - if selected.respond_to?('any?') - - selected = project.labels.find(selected) + - selected = project.labels.find_by_id(selected) || selected - selected.each do |label| - = hidden_field_tag data_options[:field_name], label.id, id: nil, data: { title: label.title } + - id = label.try(:id) || label + - title = label.try(:title) || label + = hidden_field_tag data_options[:field_name], id, id: nil, data: { title: title } .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text{ class: ("is-default" if selected.nil?) } diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index ec1864b5536..9dd54546cb6 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,4 +1,5 @@ - project = @target_project || @project +- extra_class = extra_class || '' - selected_text = project.milestones.find_by_id(selected).try(:name) - if selected.present? = hidden_field_tag(name, selected) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index e020a7d4d00..61fb1bd1642 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -126,11 +126,11 @@ %span.no-value None .selectbox.hide-collapsed - issuable.labels_array.each do |label| - = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil + = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil, data: { title: label.title } .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} - %span.dropdown-toggle-text - Label + %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} + %span.dropdown-toggle-text{ class: ("is-default" if issuable.labels_array.empty?)} + = multi_label_name(issuable.labels_array, "Labels") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default" -- cgit v1.2.1 From 9a1feb65cc1e947e16dc29308fbc02a1deee56f5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Jul 2016 09:41:55 +0100 Subject: Fixed labels & milestones loading the wrong path --- app/helpers/dropdowns_helper.rb | 6 +++++- app/helpers/labels_helper.rb | 3 ++- app/helpers/milestones_helper.rb | 3 ++- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index cc2a5a7b134..b48b218b6ec 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -40,7 +40,11 @@ module DropdownsHelper end def dropdown_toggle(toggle_text, data_attr, options = {}) - default_label = options[:data][:default_label] + default_label = if options[:data] + options[:data][:default_label] + else + '' + end content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output << icon('chevron-down') diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 5e9f5837101..5acfae753b9 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -115,8 +115,9 @@ module LabelsHelper end def labels_filter_path + project = @target_project || @project if @project - namespace_project_labels_path(@project.namespace, @project, :json) + namespace_project_labels_path(project.namespace, project, :json) else dashboard_labels_path(:json) end diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index b3e6e468ecd..6ea83e5dc03 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -47,8 +47,9 @@ module MilestonesHelper end def milestones_filter_dropdown_path + project = @target_project || @project if @project - namespace_project_milestones_path(@project.namespace, @project, :json) + namespace_project_milestones_path(project.namespace, project, :json) else dashboard_milestones_path(:json) end diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 36a1ac7664e..6518f448253 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -14,7 +14,7 @@ - if selected.present? - if selected.respond_to?('any?') - - selected = project.labels.find_by_id(selected) || selected + - selected = project.labels.where(id: selected) || selected - selected.each do |label| - id = label.try(:id) || label - title = label.try(:title) || label -- cgit v1.2.1 From 4ffe142f2398f9cc1a48c501e69a17799b4ed97e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 16 Jul 2016 10:28:46 +0100 Subject: Fix clearing fields not saving --- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- app/views/shared/issuable/_form.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 6313ad837f0..555198cf3da 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -479,7 +479,7 @@ class GitLabDropdown if el.hasClass(ACTIVE_CLASS) el.removeClass(ACTIVE_CLASS) - if isInput + if isInput or $(@el).is('.js-dropdown-keep-input') field.val('') else field.remove() diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 8820172a46e..4e1f584f805 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -55,13 +55,13 @@ - project = @target_project || @project - if issuable.assignee_id = hidden_field_tag("#{issuable.class.model_name.param_key}[assignee_id]", issuable.assignee_id) - = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", + = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", placeholder: "Search assignee", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (project.id if project), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee" } }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone_id, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: 'js-issuable-form-dropdown' + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone_id, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input" .form-group - has_labels = issuable.project.labels.any? - selected_labels = issuable.label_ids.any? ? issuable.label_ids : nil -- cgit v1.2.1 From ee2cfded31e23c393a80af1647f21116f80996c1 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sun, 17 Jul 2016 01:39:37 -0500 Subject: Refactor label toggling for Labels dropdown --- app/assets/javascripts/gl_dropdown.js.coffee | 20 ++++---- app/assets/javascripts/labels_select.js.coffee | 66 ++++++++++++++++++++------ 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 555198cf3da..6b8ceb6385a 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -486,7 +486,7 @@ class GitLabDropdown # Toggle the dropdown label if @options.toggleLabel - @updateLabel(selectedObject, el, @, false) + @updateLabel(selectedObject, el, @) else selectedObject else if el.hasClass(INDETERMINATE_CLASS) @@ -513,9 +513,6 @@ class GitLabDropdown # Toggle active class for the tick mark el.addClass ACTIVE_CLASS - # Toggle the dropdown label - if @options.toggleLabel - @updateLabel(selectedObject, el, @, true) if value? if !field.length and fieldName @addInput(fieldName, value) @@ -524,6 +521,10 @@ class GitLabDropdown .val value .trigger 'change' + # Toggle the dropdown label + if @options.toggleLabel + @updateLabel(selectedObject, el, @) + return selectedObject addInput: (fieldName, value)-> @@ -616,15 +617,12 @@ class GitLabDropdown # Scroll the dropdown content up $dropdownContent.scrollTop(listItemTop - dropdownContentTop) - updateLabel: (selected = null, el = null, instance = null, added = false) => - $toggleText = $(@el).find(".dropdown-toggle-text") - $toggleText.text @options.toggleLabel(selected, el, instance, added) + updateLabel: (selected = null, el = null, instance = null) => + $toggleText = @getElement '.dropdown-toggle-text' + $toggleText.text @options.toggleLabel(selected, el, instance) if @options.defaultLabel - if $toggleText.text().trim() is @options.defaultLabel - $toggleText.addClass('is-default') - else - $toggleText.removeClass('is-default') + $toggleText.toggleClass('is-default', $toggleText.text().trim() is @options.defaultLabel) $.fn.glDropdown = (opts) -> return @.each -> diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index ce630dcacb3..d37d17e086d 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -278,23 +278,61 @@ class @LabelsSelect fields: ['title'] selectable: true filterable: true - toggleLabel: (selected, el, e, added) -> - if added - selectedLabels.push selected.title + toggleLabel: (selected, $el, glDropdownInstance) -> + # When comes from a triggered event handle it VERY differently + if selected instanceof jQuery.Event + $dropdownParent = $dropdown.closest '.labels-filter' + $labelInputs = $dropdownParent.find "input[name='#{@fieldName}']" + numberSelectedLabels = $labelInputs.length + firstLabel = _.pluck($labelInputs, 'value')[0] + + if numberSelectedLabels is 1 + firstLabel + else if numberSelectedLabels > 1 + "#{firstLabel} +#{numberSelectedLabels - 1} more" + else + defaultLabel + # when clicking on a dropdown option else - index = selectedLabels.indexOf selected.title - selectedLabels.splice index, 1 + # Return when clicking "No Label" + return if selected.id is 0 + return 'Any Label' if selected.isAny is true - if selected.id? and selected.id is 0 - selectedLabels = [] + if glDropdownInstance? + $dropdownParent = glDropdownInstance.dropdown.closest '.issuable-form-select-holder, .labels-filter' + else + $dropdownParent = $() - if selectedLabels.length > 1 - "#{selectedLabels[0]} +#{selectedLabels.length - 1} more" - else if selectedLabels.length is 1 - selectedLabels[0] - else - console.log selectedLabels.length, defaultLabel - defaultLabel + $labelInputs = $dropdownParent.find "input[name='#{@fieldName}']" + + # Find the label by its attribute according the dropdown settings + if $dropdown.hasClass 'js-issuable-form-dropdown' + # When settings labels to a issuable we find the label for its ID + whereQuery = { id: parseInt $labelInputs.first().val() } + else + # When filtering issuables we find the label for its title + whereQuery = { title: $labelInputs.first().val() } + + firstLabel = _.findWhere glDropdownInstance.fullData, whereQuery + + # Better rely on inputs since filtering may returns invalid number of active labels + numberSelectedLabels = $labelInputs.length + + # If we are adding a label + if $el.is '.is-active' + if numberSelectedLabels is 1 + selected.title + else + "#{selected.title} +#{numberSelectedLabels - 1} more" + + # otherwise we are removing a label + else + if numberSelectedLabels is 1 + firstLabel.title + else if numberSelectedLabels > 1 + "#{firstLabel.title} +#{numberSelectedLabels - 1} more" + else + defaultLabel defaultLabel: defaultLabel fieldName: $dropdown.data('field-name') id: (label) -> -- cgit v1.2.1 From 02ce5fba8d7aa94d5fb53bd717e77c93c6bd61b7 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sun, 17 Jul 2016 02:34:46 -0500 Subject: Fixes preselected label --- app/views/shared/issuable/_filter.html.haml | 2 +- app/views/shared/issuable/_label_dropdown.html.haml | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index be8248876b4..1a0f1fff056 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -24,7 +24,7 @@ = render "shared/issuable/milestone_dropdown", selected: params[:milestone_title], name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: params[:label_name], data_options: { field_name: "label_name[]" } + = render "shared/issuable/label_dropdown", selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } .pull-right = render 'shared/sort_dropdown' diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 6518f448253..113447d48b7 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -13,16 +13,20 @@ - classes << 'js-filter-submit' if filter_submit - if selected.present? - - if selected.respond_to?('any?') - - selected = project.labels.where(id: selected) || selected - - selected.each do |label| - - id = label.try(:id) || label - - title = label.try(:title) || label - = hidden_field_tag data_options[:field_name], id, id: nil, data: { title: title } + - labelQuery = { id: selected } + - useId = true +- if selected_toggle.present? + - labelQuery = { title: selected_toggle } +- if labelQuery + - selected = project.labels.where(labelQuery) + - selected.each do |label| + - id = label.try(:id) || label + - title = label.try(:title) || label + = hidden_field_tag data_options[:field_name], useId ? id : title, id: nil, data: { title: title } .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text{ class: ("is-default" if selected.nil?) } - = h(multi_label_name(selected_toggle || selected, "Label")) + = h(multi_label_name(selected.to_a, "Label")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } -- cgit v1.2.1 From eb49e28ca0145fa7df6ef1077ae97e90ce39fc6b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sun, 17 Jul 2016 17:43:06 +0100 Subject: Fixed milestone dropdown tests --- app/helpers/milestones_helper.rb | 6 ++++++ app/views/shared/issuable/_milestone_dropdown.html.haml | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 6ea83e5dc03..05caf91240f 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -55,6 +55,12 @@ module MilestonesHelper end end + def milestone_dropdown_selected_text + project = @target_project || @project || @projects + + Milestone.of_projects(project).where(title: params[:milestone_title]).first().try(:name) + end + def milestone_remaining_days(milestone) if milestone.expired? content_tag(:strong, 'Past due') diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 9dd54546cb6..0b8fd3daad7 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,11 +1,11 @@ -- project = @target_project || @project +- project = @target_project || @project || @projects - extra_class = extra_class || '' -- selected_text = project.milestones.find_by_id(selected).try(:name) +- selected_text = milestone_dropdown_selected_text - if selected.present? = hidden_field_tag(name, selected) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected, project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - - if project + - if project && project.respond_to?(:namespace) %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project %li -- cgit v1.2.1 From c9cd271536fdbef3ad07d15978bada5f4a440d7f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 08:41:50 +0100 Subject: Fixed failing label dropdown tests --- app/assets/javascripts/labels_select.js.coffee | 2 +- app/helpers/issuables_helper.rb | 12 ++++++++++-- app/views/shared/issuable/_label_dropdown.html.haml | 8 ++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index d37d17e086d..2a497c2e804 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -306,7 +306,7 @@ class @LabelsSelect $labelInputs = $dropdownParent.find "input[name='#{@fieldName}']" # Find the label by its attribute according the dropdown settings - if $dropdown.hasClass 'js-issuable-form-dropdown' + if $dropdown.hasClass('js-issuable-form-dropdown') or $dropdown.hasClass('js-filter-bulk-update') # When settings labels to a issuable we find the label for its ID whereQuery = { id: parseInt $labelInputs.first().val() } else diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 114cf804d6f..305ec71bd6c 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -7,7 +7,7 @@ module IssuablesHelper "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}" end - def multi_label_name(current_labels, default_label) + def multi_label_name(current_labels, param, default_label) # current_labels may be a string from before if current_labels.is_a?(Array) && current_labels.any? title = current_labels[0].try(:title) || current_labels[0] @@ -23,7 +23,11 @@ module IssuablesHelper current_labels end else - default_label + if !param.empty? + param + else + default_label + end end end @@ -73,6 +77,10 @@ module IssuablesHelper end end + def selected_labels(project, labelQuery) + Label.where(labelQuery.merge(project_id: project)) + end + private def sidebar_gutter_collapsed? diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 113447d48b7..dc8f09b09c8 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,4 +1,4 @@ -- project = @target_project || @project +- project = @target_project || @project || @projects - show_create = local_assigns.fetch(:show_create, true) - extra_options = local_assigns.fetch(:extra_options, true) - filter_submit = local_assigns.fetch(:filter_submit, true) @@ -18,15 +18,15 @@ - if selected_toggle.present? - labelQuery = { title: selected_toggle } - if labelQuery - - selected = project.labels.where(labelQuery) + - selected = selected_labels(project, labelQuery) - selected.each do |label| - id = label.try(:id) || label - title = label.try(:title) || label = hidden_field_tag data_options[:field_name], useId ? id : title, id: nil, data: { title: title } .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} - %span.dropdown-toggle-text{ class: ("is-default" if selected.nil?) } - = h(multi_label_name(selected.to_a, "Label")) + %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.to_a.size == 0) } + = h(multi_label_name(selected.to_a, h(selected_toggle.to_a[0]), "Label")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } -- cgit v1.2.1 From c48476646eb07ab5d682fcf0fb7b3255aa16bf1d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 08:43:39 +0100 Subject: Fixed issuable sidebar label dropdown --- app/views/shared/issuable/_sidebar.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 61fb1bd1642..61a096d7bf1 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -125,12 +125,13 @@ - else %span.no-value None .selectbox.hide-collapsed + - selected_labels = params["#{issuable.to_ability_name}[label_names]"].to_a - issuable.labels_array.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil, data: { title: label.title } .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %span.dropdown-toggle-text{ class: ("is-default" if issuable.labels_array.empty?)} - = multi_label_name(issuable.labels_array, "Labels") + = multi_label_name(issuable.labels_array, h(selected_labels[0]), "Labels") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default" -- cgit v1.2.1 From 639a0b1985d2a9b22ec78ca9b61a09edaf30d655 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 09:04:36 +0100 Subject: Fixed milestone dropdown label not persisting --- app/helpers/milestones_helper.rb | 8 ++++++-- app/views/shared/issuable/_milestone_dropdown.html.haml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 05caf91240f..bc87ac32e3e 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -55,10 +55,14 @@ module MilestonesHelper end end - def milestone_dropdown_selected_text + def milestone_dropdown_selected_text(selected) project = @target_project || @project || @projects - Milestone.of_projects(project).where(title: params[:milestone_title]).first().try(:name) + if selected.is_a? Integer + Milestone.of_projects(project).where(id: selected).first.try(:title) + else + Milestone.of_projects(project).where(title: selected).first.try(:title) + end end def milestone_remaining_days(milestone) diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 0b8fd3daad7..575c71af6dc 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,6 +1,6 @@ - project = @target_project || @project || @projects - extra_class = extra_class || '' -- selected_text = milestone_dropdown_selected_text +- selected_text = milestone_dropdown_selected_text(selected) - if selected.present? = hidden_field_tag(name, selected) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable", -- cgit v1.2.1 From e98617ac7f58eb7a1c09783f03fbdaf559d5b157 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 09:16:08 +0100 Subject: Fixed Rubocop offence with underscored var names --- app/helpers/issuables_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 305ec71bd6c..7fd35613b67 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -77,8 +77,8 @@ module IssuablesHelper end end - def selected_labels(project, labelQuery) - Label.where(labelQuery.merge(project_id: project)) + def selected_labels(project, label_query) + Label.where(label_query.merge(project_id: project)) end private -- cgit v1.2.1 From c752ed064d6399471a8b1a1bba25099725702982 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 12:55:12 +0100 Subject: Fixed create label link in dropdown breaking specs --- app/views/shared/issuable/_filter.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 1a0f1fff056..b0e594653a3 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -24,7 +24,7 @@ = render "shared/issuable/milestone_dropdown", selected: params[:milestone_title], name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } + = render "shared/issuable/label_dropdown", selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }, show_create: controller.controller_name != "groups" .pull-right = render 'shared/sort_dropdown' -- cgit v1.2.1 From 75892aebabf96f6cce9057b67f21d403d70a1d95 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 13:23:45 +0100 Subject: Added tests for new/edit issue form --- app/assets/javascripts/users_select.js.coffee | 4 +- .../shared/issuable/_label_dropdown.html.haml | 2 +- spec/features/issues/form_spec.rb | 85 ++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 spec/features/issues/form_spec.rb diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index bf551430999..1c5a0dc108f 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -142,8 +142,8 @@ class @UsersSelect selectable: true fieldName: $dropdown.data('field-name') - toggleLabel: (selected, el, e, added) -> - if selected and 'id' of selected and added + toggleLabel: (selected, el, e) -> + if selected and 'id' of selected and $(el).hasClass('is-active') if selected.text then selected.text else selected.name else defaultLabel diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index dc8f09b09c8..b52fa6fa8c2 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -26,7 +26,7 @@ .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.to_a.size == 0) } - = h(multi_label_name(selected.to_a, h(selected_toggle.to_a[0]), "Label")) + = h(multi_label_name(selected.to_a, h(selected_toggle.to_a[0]), "Labels")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb new file mode 100644 index 00000000000..6c14dff6a37 --- /dev/null +++ b/spec/features/issues/form_spec.rb @@ -0,0 +1,85 @@ +require 'rails_helper' + +describe 'New/edit issue', feature: true, js: true do + let!(:project) { create(:project) } + let!(:user) { create(:user)} + let!(:milestone) { create(:milestone, project: project) } + let!(:label) { create(:label, project: project) } + let!(:label2) { create(:label, project: project) } + let!(:issue) { create(:issue, project: project, assignee: user, milestone: milestone) } + + before do + project.team << [user, :master] + login_as(user) + end + + context 'new issue' do + before do + visit new_namespace_project_issue_path(project.namespace, project) + end + + it 'should allow user to create new issue' do + fill_in 'issue_title', with: 'title' + fill_in 'issue_description', with: 'title' + + click_button 'Assignee' + click_link user.name + + page.find '.js-assignee-search' do + expect(page).to have_content user.name + end + + click_button 'Milestone' + click_link milestone.title + + page.find '.js-milestone-select' do + expect(page).to have_content milestone.title + end + + click_button 'Labels' + click_link label.title + click_link label2.title + + page.find '.js-label-select' do + expect(page).to have_content label2.title + end + + click_button 'Submit issue' + + page.find '.issuable-sidebar' do + expect(page).to have_content user.name + expect(page).to have_content milestone.title + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end + end + + context 'edit issue' do + before do + visit edit_namespace_project_issue_path(project.namespace, project, issue) + end + + it 'should allow user to update issue' do + expect(page).to have_content user.name + expect(page).to have_content milestone.title + + click_button 'Labels' + click_link label.title + click_link label2.title + + page.find '.js-label-select' do + expect(page).to have_content label2.title + end + + click_button 'Save changes' + + page.find '.issuable-sidebar' do + expect(page).to have_content user.name + expect(page).to have_content milestone.title + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end + end +end -- cgit v1.2.1 From feeb489302dcf82e0e1f7e014c34824d52f8a882 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 14:21:30 +0100 Subject: Added spec tests for merge request form including from forked project --- spec/features/merge_requests/form_spec.rb | 87 +++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 spec/features/merge_requests/form_spec.rb diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb new file mode 100644 index 00000000000..8da33b0f3c3 --- /dev/null +++ b/spec/features/merge_requests/form_spec.rb @@ -0,0 +1,87 @@ +require 'rails_helper' + +describe 'New/edit merge request', feature: true, js: true do + let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let(:fork_project) { create(:project, forked_from_project: project) } + let!(:user) { create(:user)} + let!(:milestone) { create(:milestone, project: project) } + let!(:label) { create(:label, project: project) } + let!(:label2) { create(:label, project: project) } + + before do + project.team << [user, :master] + end + + context 'owned projects' do + before do + merge_request = create(:merge_request, + source_project: project, + target_project: project, + source_branch: 'fix', + target_branch: 'master' + ) + + login_as(user) + + visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'should update merge request' do + update_merge_request + end + end + + context 'forked project' do + before do + fork_project.team << [user, :master] + + merge_request = create(:merge_request, + source_project: fork_project, + target_project: project, + source_branch: 'fix', + target_branch: 'master' + ) + + login_as(user) + + visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'should update merge request' do + update_merge_request + end + end + + def update_merge_request + click_button 'Assignee' + click_link user.name + + page.find '.js-assignee-search' do + expect(page).to have_content user.name + end + + click_button 'Milestone' + click_link milestone.title + + page.find '.js-milestone-select' do + expect(page).to have_content milestone.title + end + + click_button 'Labels' + click_link label.title + click_link label2.title + + page.find '.js-label-select' do + expect(page).to have_content label2.title + end + + click_button 'Save changes' + + page.find '.issuable-sidebar' do + expect(page).to have_content user.name + expect(page).to have_content milestone.title + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end +end -- cgit v1.2.1 From 357ad44a744db07b6c849bf262f00100b448382b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 15:13:56 +0100 Subject: Fixed Rubocop errors --- spec/features/merge_requests/form_spec.rb | 85 ++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index 8da33b0f3c3..9d6706ee9b3 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -19,7 +19,7 @@ describe 'New/edit merge request', feature: true, js: true do target_project: project, source_branch: 'fix', target_branch: 'master' - ) + ) login_as(user) @@ -27,7 +27,36 @@ describe 'New/edit merge request', feature: true, js: true do end it 'should update merge request' do - update_merge_request + click_button 'Assignee' + click_link user.name + + page.find '.js-assignee-search' do + expect(page).to have_content user.name + end + + click_button 'Milestone' + click_link milestone.title + + page.find '.js-milestone-select' do + expect(page).to have_content milestone.title + end + + click_button 'Labels' + click_link label.title + click_link label2.title + + page.find '.js-label-select' do + expect(page).to have_content label2.title + end + + click_button 'Save changes' + + page.find '.issuable-sidebar' do + expect(page).to have_content user.name + expect(page).to have_content milestone.title + expect(page).to have_content label.title + expect(page).to have_content label2.title + end end end @@ -40,7 +69,7 @@ describe 'New/edit merge request', feature: true, js: true do target_project: project, source_branch: 'fix', target_branch: 'master' - ) + ) login_as(user) @@ -48,40 +77,36 @@ describe 'New/edit merge request', feature: true, js: true do end it 'should update merge request' do - update_merge_request - end - end - - def update_merge_request - click_button 'Assignee' - click_link user.name + click_button 'Assignee' + click_link user.name - page.find '.js-assignee-search' do - expect(page).to have_content user.name - end + page.find '.js-assignee-search' do + expect(page).to have_content user.name + end - click_button 'Milestone' - click_link milestone.title + click_button 'Milestone' + click_link milestone.title - page.find '.js-milestone-select' do - expect(page).to have_content milestone.title - end + page.find '.js-milestone-select' do + expect(page).to have_content milestone.title + end - click_button 'Labels' - click_link label.title - click_link label2.title + click_button 'Labels' + click_link label.title + click_link label2.title - page.find '.js-label-select' do - expect(page).to have_content label2.title - end + page.find '.js-label-select' do + expect(page).to have_content label2.title + end - click_button 'Save changes' + click_button 'Save changes' - page.find '.issuable-sidebar' do - expect(page).to have_content user.name - expect(page).to have_content milestone.title - expect(page).to have_content label.title - expect(page).to have_content label2.title + page.find '.issuable-sidebar' do + expect(page).to have_content user.name + expect(page).to have_content milestone.title + expect(page).to have_content label.title + expect(page).to have_content label2.title + end end end end -- cgit v1.2.1 From 49bc1cd6eb7b90b34c3f2f18f27743ccf2c4338d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 17:43:00 +0100 Subject: Updated how the label toggle gets the text Fixed some issues based on self-review --- app/assets/javascripts/labels_select.js.coffee | 74 +++++----------------- app/assets/javascripts/milestone_select.js.coffee | 6 +- app/helpers/labels_helper.rb | 2 +- app/helpers/milestones_helper.rb | 2 +- .../shared/issuable/_label_dropdown.html.haml | 2 +- 5 files changed, 21 insertions(+), 65 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 2a497c2e804..5e09823b2d9 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -25,11 +25,9 @@ class @LabelsSelect $newLabelError = $('.js-label-error') $colorPreview = $('.js-dropdown-label-color-preview') $newLabelCreateButton = $('.js-new-label-btn') - selectedLabels = [] - - $("input[name='#{$dropdown.data('field-name')}']").each -> - title = $(this).data('title') - selectedLabels.push($(this).data('title')) if title + fieldName = $dropdown.data('field-name') + useId = $dropdown.hasClass('js-issuable-form-dropdown') or $dropdown.hasClass('js-filter-bulk-update') + propertyName = if useId then "id" else "title" $newLabelError.hide() $loading = $block.find('.block-loading').fadeOut() @@ -125,7 +123,7 @@ class @LabelsSelect saveLabelData = -> selected = $dropdown .closest('.selectbox') - .find("input[name='#{$dropdown.data('field-name')}']") + .find("input[name='#{fieldName}']") .map(-> @value ).get() @@ -278,63 +276,21 @@ class @LabelsSelect fields: ['title'] selectable: true filterable: true - toggleLabel: (selected, $el, glDropdownInstance) -> - # When comes from a triggered event handle it VERY differently - if selected instanceof jQuery.Event - $dropdownParent = $dropdown.closest '.labels-filter' - $labelInputs = $dropdownParent.find "input[name='#{@fieldName}']" - numberSelectedLabels = $labelInputs.length - firstLabel = _.pluck($labelInputs, 'value')[0] - - if numberSelectedLabels is 1 - firstLabel - else if numberSelectedLabels > 1 - "#{firstLabel} +#{numberSelectedLabels - 1} more" - else - defaultLabel - # when clicking on a dropdown option - else - # Return when clicking "No Label" - return if selected.id is 0 - return 'Any Label' if selected.isAny is true - - if glDropdownInstance? - $dropdownParent = glDropdownInstance.dropdown.closest '.issuable-form-select-holder, .labels-filter' - else - $dropdownParent = $() + toggleLabel: (selected, el, glDropdown) -> + if glDropdown? + selectedIds = $("input[name='#{fieldName}']").map(-> $(this).val()).get() - $labelInputs = $dropdownParent.find "input[name='#{@fieldName}']" + selected = _.filter glDropdown.fullData, (label) -> + selectedIds.indexOf("#{label[propertyName]}") >= 0 if label[propertyName]? - # Find the label by its attribute according the dropdown settings - if $dropdown.hasClass('js-issuable-form-dropdown') or $dropdown.hasClass('js-filter-bulk-update') - # When settings labels to a issuable we find the label for its ID - whereQuery = { id: parseInt $labelInputs.first().val() } + if selected.length is 1 + selected[0].title + else if selected.length > 1 + "#{selected[0].title} +#{selected.length - 1} more" else - # When filtering issuables we find the label for its title - whereQuery = { title: $labelInputs.first().val() } - - firstLabel = _.findWhere glDropdownInstance.fullData, whereQuery - - # Better rely on inputs since filtering may returns invalid number of active labels - numberSelectedLabels = $labelInputs.length - - # If we are adding a label - if $el.is '.is-active' - if numberSelectedLabels is 1 - selected.title - else - "#{selected.title} +#{numberSelectedLabels - 1} more" - - # otherwise we are removing a label - else - if numberSelectedLabels is 1 - firstLabel.title - else if numberSelectedLabels > 1 - "#{firstLabel.title} +#{numberSelectedLabels - 1} more" - else - defaultLabel + defaultLabel defaultLabel: defaultLabel - fieldName: $dropdown.data('field-name') + fieldName: fieldName id: (label) -> if $dropdown.hasClass('js-issuable-form-dropdown') if label.id is 0 diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 0d71f3b623a..56e9a18e7ff 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -70,8 +70,8 @@ class @MilestoneSelect search: fields: ['title'] selectable: true - toggleLabel: (selected, el, e, added) -> - if selected and 'id' of selected and added + toggleLabel: (selected, el, e) -> + if selected and 'id' of selected and $(el).hasClass('is-active') selected.title else defaultLabel @@ -80,7 +80,7 @@ class @MilestoneSelect text: (milestone) -> _.escape(milestone.title) id: (milestone) -> - if !useId and not $dropdown.is('.js-issuable-form-dropdown') + if not useId and not $dropdown.is('.js-issuable-form-dropdown') milestone.name else milestone.id diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 5acfae753b9..b9f3d6c75c2 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -116,7 +116,7 @@ module LabelsHelper def labels_filter_path project = @target_project || @project - if @project + if project namespace_project_labels_path(project.namespace, project, :json) else dashboard_labels_path(:json) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index bc87ac32e3e..90f83f5fde5 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -48,7 +48,7 @@ module MilestonesHelper def milestones_filter_dropdown_path project = @target_project || @project - if @project + if project namespace_project_milestones_path(project.namespace, project, :json) else dashboard_milestones_path(:json) diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index b52fa6fa8c2..666372b95a6 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -22,7 +22,7 @@ - selected.each do |label| - id = label.try(:id) || label - title = label.try(:title) || label - = hidden_field_tag data_options[:field_name], useId ? id : title, id: nil, data: { title: title } + = hidden_field_tag data_options[:field_name], useId ? id : title, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.to_a.size == 0) } -- cgit v1.2.1 From 1920cf2d161534ae7d8fca8ba6ade50d56d0b391 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 17:46:26 +0100 Subject: Gets value from native element rather than converting to jquery element --- app/assets/javascripts/labels_select.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 5e09823b2d9..ab1384c1bf9 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -278,7 +278,7 @@ class @LabelsSelect filterable: true toggleLabel: (selected, el, glDropdown) -> if glDropdown? - selectedIds = $("input[name='#{fieldName}']").map(-> $(this).val()).get() + selectedIds = $("input[name='#{fieldName}']").map(-> @value).get() selected = _.filter glDropdown.fullData, (label) -> selectedIds.indexOf("#{label[propertyName]}") >= 0 if label[propertyName]? -- cgit v1.2.1 From 44cf9e4cdf53402ee583c46a478c7fbcd551a97f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 17:56:05 +0100 Subject: Fixed issuable sidebar label toggle not working Removed un-used JS var --- app/assets/javascripts/labels_select.js.coffee | 2 +- app/assets/javascripts/users_select.js.coffee | 2 +- app/views/shared/issuable/_sidebar.html.haml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index ab1384c1bf9..8e7dee46fa6 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -26,7 +26,7 @@ class @LabelsSelect $colorPreview = $('.js-dropdown-label-color-preview') $newLabelCreateButton = $('.js-new-label-btn') fieldName = $dropdown.data('field-name') - useId = $dropdown.hasClass('js-issuable-form-dropdown') or $dropdown.hasClass('js-filter-bulk-update') + useId = $dropdown.is('.js-issuable-form-dropdown, .js-filter-bulk-update, .js-label-sidebar-dropdown') propertyName = if useId then "id" else "title" $newLabelError.hide() diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 1c5a0dc108f..cb1bf2c8154 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -142,7 +142,7 @@ class @UsersSelect selectable: true fieldName: $dropdown.data('field-name') - toggleLabel: (selected, el, e) -> + toggleLabel: (selected, el) -> if selected and 'id' of selected and $(el).hasClass('is-active') if selected.text then selected.text else selected.name else diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 61a096d7bf1..8fab7a41b81 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -127,9 +127,9 @@ .selectbox.hide-collapsed - selected_labels = params["#{issuable.to_ability_name}[label_names]"].to_a - issuable.labels_array.each do |label| - = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil, data: { title: label.title } + = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %span.dropdown-toggle-text{ class: ("is-default" if issuable.labels_array.empty?)} = multi_label_name(issuable.labels_array, h(selected_labels[0]), "Labels") = icon('chevron-down') -- cgit v1.2.1 From 9947e69905a12a580e5ac7be7b2476b804d279ef Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 18:05:25 +0100 Subject: Fixed issue where label query would bring back duplicates --- app/helpers/issuables_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 7fd35613b67..a8468537d42 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -78,7 +78,7 @@ module IssuablesHelper end def selected_labels(project, label_query) - Label.where(label_query.merge(project_id: project)) + Label.where(label_query.merge(project_id: project)).pluck(:title).uniq end private -- cgit v1.2.1 From 70d5493e21fbdf21020c5cfcf67919d157bfb9db Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 18 Jul 2016 12:53:42 -0500 Subject: Remove unnecesary update.label event --- app/assets/javascripts/gl_dropdown.js.coffee | 1 - app/assets/javascripts/issuable.js.coffee | 1 - 2 files changed, 2 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 6b8ceb6385a..b8b4c9458a8 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -217,7 +217,6 @@ class GitLabDropdown @dropdown.on "shown.bs.dropdown", @opened @dropdown.on "hidden.bs.dropdown", @hidden - $(@el).on "update.label", @updateLabel @dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate @dropdown.on 'keyup', (e) => if e.which is 27 # Escape key diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee index 7f795f8096b..d12b9910222 100644 --- a/app/assets/javascripts/issuable.js.coffee +++ b/app/assets/javascripts/issuable.js.coffee @@ -52,7 +52,6 @@ issuable_created = false # Submit the form to get new data Issuable.filterResults $('.filter-form') - $('.js-label-select').trigger('update.label') filterResults: (form) => formData = form.serialize() -- cgit v1.2.1 From 0a81c00040c6bfe451a855f41dcc7b62eda4b009 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 18:57:44 +0100 Subject: Fixed 'no label' being in dropdown toggle label --- app/assets/javascripts/labels_select.js.coffee | 10 ++++------ app/views/shared/issuable/_label_dropdown.html.haml | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 8e7dee46fa6..5ae6d1766fb 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -212,7 +212,7 @@ class @LabelsSelect $a = $('') selectedClass = [] - removesAll = label.id is 0 or not label.id? + removesAll = label.id <= 0 or not label.id? if $dropdown.hasClass('js-filter-bulk-update') indeterminate = instance.indeterminateIds @@ -281,7 +281,7 @@ class @LabelsSelect selectedIds = $("input[name='#{fieldName}']").map(-> @value).get() selected = _.filter glDropdown.fullData, (label) -> - selectedIds.indexOf("#{label[propertyName]}") >= 0 if label[propertyName]? + selectedIds.indexOf("#{label[propertyName]}") >= 0 if label[propertyName]? and label.id > 0 if selected.length is 1 selected[0].title @@ -292,11 +292,9 @@ class @LabelsSelect defaultLabel: defaultLabel fieldName: fieldName id: (label) -> + return if label.id <= 0 if $dropdown.hasClass('js-issuable-form-dropdown') - if label.id is 0 - return - else - return label.id + return label.id if $dropdown.hasClass("js-filter-submit") and not label.isAny? label.title diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 666372b95a6..4899496dcc0 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -7,7 +7,7 @@ - classes = local_assigns.fetch(:classes, []) - selected = local_assigns.fetch(:selected, nil) - selected_toggle = local_assigns.fetch(:selected_toggle, nil) -- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", selected: selected, project_id: project.try(:id), labels: labels_filter_path, default_label: "Label"} +- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", selected: selected, project_id: project.try(:id), labels: labels_filter_path, default_label: "Labels"} - dropdown_data.merge!(data_options) - classes << 'js-extra-options' if extra_options - classes << 'js-filter-submit' if filter_submit -- cgit v1.2.1 From c54d2a030ca9753282640e8247e4df6c250477fa Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 19:18:41 +0100 Subject: Removes tick from no label when selecting a label --- app/assets/javascripts/labels_select.js.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 5ae6d1766fb..1a3a38ab25d 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -333,6 +333,12 @@ class @LabelsSelect clicked: (label) -> _this.enableBulkLabelDropdown() + if $dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length + $dropdown + .parent() + .find('.dropdown-clear-active') + .removeClass('is-active') + if $dropdown.hasClass('js-filter-bulk-update') or $dropdown.hasClass('js-issuable-form-dropdown') return -- cgit v1.2.1 From 47d8fb84d80d23432c9afea055da4c56ea749206 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 19 Jul 2016 10:05:38 +0100 Subject: Uses the selected values from the controller not the params Added tests for new merge requests from forked project & owned project --- app/assets/javascripts/labels_select.js.coffee | 2 - app/helpers/dropdowns_helper.rb | 6 +- app/helpers/issuables_helper.rb | 19 +- app/helpers/milestones_helper.rb | 8 +- app/views/shared/issuable/_filter.html.haml | 4 +- app/views/shared/issuable/_form.html.haml | 14 +- .../shared/issuable/_label_dropdown.html.haml | 19 +- .../shared/issuable/_milestone_dropdown.html.haml | 4 +- app/views/shared/issuable/_sidebar.html.haml | 8 +- spec/features/issues/form_spec.rb | 90 +++++-- spec/features/merge_requests/form_spec.rb | 281 ++++++++++++++++----- 11 files changed, 310 insertions(+), 145 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 1a3a38ab25d..b30067fc814 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -9,8 +9,6 @@ class @LabelsSelect labelUrl = $dropdown.data('labels') issueUpdateURL = $dropdown.data('issueUpdate') selectedLabel = $dropdown.data('selected') - if selectedLabel? and not $dropdown.hasClass 'js-multiselect' - selectedLabel = selectedLabel.split(',') newLabelField = $('#new_label_name') newColorField = $('#new_label_color') showNo = $dropdown.data('show-no') diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index b48b218b6ec..81e0b6bb5ae 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -40,11 +40,7 @@ module DropdownsHelper end def dropdown_toggle(toggle_text, data_attr, options = {}) - default_label = if options[:data] - options[:data][:default_label] - else - '' - end + default_label = data_attr[:default_label] content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output << icon('chevron-down') diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index a8468537d42..261747c592b 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -7,27 +7,16 @@ module IssuablesHelper "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}" end - def multi_label_name(current_labels, param, default_label) - # current_labels may be a string from before - if current_labels.is_a?(Array) && current_labels.any? - title = current_labels[0].try(:title) || current_labels[0] + def multi_label_name(current_labels, selected_param, default_label) + if current_labels.any? + title = current_labels.first.try(:title) if current_labels.count > 1 "#{title} +#{current_labels.count - 1} more" else title end - elsif current_labels.is_a?(String) - if current_labels.nil? || current_labels.empty? - default_label - else - current_labels - end else - if !param.empty? - param - else - default_label - end + selected_param.presence || default_label end end diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 90f83f5fde5..f678e1540de 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -56,13 +56,7 @@ module MilestonesHelper end def milestone_dropdown_selected_text(selected) - project = @target_project || @project || @projects - - if selected.is_a? Integer - Milestone.of_projects(project).where(id: selected).first.try(:title) - else - Milestone.of_projects(project).where(title: selected).first.try(:title) - end + selected.try(:title) unless selected.nil? end def milestone_remaining_days(milestone) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index b0e594653a3..42b5a6d61d7 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -21,10 +21,10 @@ placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) .filter-item.inline.milestone-filter - = render "shared/issuable/milestone_dropdown", selected: params[:milestone_title], name: :milestone_title, show_any: true, show_upcoming: true + = render "shared/issuable/milestone_dropdown", selected: (@issuable_finder.milestones.first unless @issuable_finder.milestones.nil?), name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }, show_create: controller.controller_name != "groups" + = render "shared/issuable/label_dropdown", selected: @issuable_finder.labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }, show_create: controller.controller_name != "groups" .pull-right = render 'shared/sort_dropdown' diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 4e1f584f805..90cc0fd3211 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -1,3 +1,4 @@ +- project = @target_project || @project = form_errors(issuable) .form-group @@ -52,26 +53,23 @@ = f.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - - project = @target_project || @project - if issuable.assignee_id - = hidden_field_tag("#{issuable.class.model_name.param_key}[assignee_id]", issuable.assignee_id) + = f.hidden_field :assignee_id = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (project.id if project), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee" } }) + placeholder: "Search assignee", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (project.try(:id)), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee" } }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone_id, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input" + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input" .form-group - has_labels = issuable.project.labels.any? - - selected_labels = issuable.label_ids.any? ? issuable.label_ids : nil - label_dropdown_toggle = issuable.labels.map { |label| label.title } - - field_name = "#{issuable.class.model_name.param_key}[label_ids][]" = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" - = hidden_field_tag field_name, "" + = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: selected_labels, selected_toggle: label_dropdown_toggle, data_options: { field_name: field_name, show_any: "false" } + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, selected_toggle: label_dropdown_toggle, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false } - if has_due_date .col-lg-6 .form-group diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 4899496dcc0..cb50d2bc461 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -3,33 +3,28 @@ - extra_options = local_assigns.fetch(:extra_options, true) - filter_submit = local_assigns.fetch(:filter_submit, true) - show_footer = local_assigns.fetch(:show_footer, true) +- use_id = local_assigns.fetch(:use_id, true) - data_options = local_assigns.fetch(:data_options, {}) - classes = local_assigns.fetch(:classes, []) - selected = local_assigns.fetch(:selected, nil) - selected_toggle = local_assigns.fetch(:selected_toggle, nil) -- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", selected: selected, project_id: project.try(:id), labels: labels_filter_path, default_label: "Labels"} +- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", project_id: project.try(:id), labels: labels_filter_path, default_label: "Labels"} - dropdown_data.merge!(data_options) - classes << 'js-extra-options' if extra_options - classes << 'js-filter-submit' if filter_submit -- if selected.present? - - labelQuery = { id: selected } - - useId = true -- if selected_toggle.present? - - labelQuery = { title: selected_toggle } -- if labelQuery - - selected = selected_labels(project, labelQuery) +- if selected - selected.each do |label| - id = label.try(:id) || label - title = label.try(:title) || label - = hidden_field_tag data_options[:field_name], useId ? id : title, id: nil + = hidden_field_tag data_options[:field_name], use_id ? id : title, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} - %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.to_a.size == 0) } - = h(multi_label_name(selected.to_a, h(selected_toggle.to_a[0]), "Labels")) + %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) } + = h(multi_label_name(selected, selected_toggle.to_a.first, "Labels")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } - - if show_create and project and can?(current_user, :admin_label, project) + - if show_create && project && can?(current_user, :admin_label, project) = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 575c71af6dc..6d9c5d345e4 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -2,9 +2,9 @@ - extra_class = extra_class || '' - selected_text = milestone_dropdown_selected_text(selected) - if selected.present? - = hidden_field_tag(name, selected) + = hidden_field_tag(name, selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable", - placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected, project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do + placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project && project.respond_to?(:namespace) %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 8fab7a41b81..d800bd9fd90 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -125,13 +125,13 @@ - else %span.no-value None .selectbox.hide-collapsed - - selected_labels = params["#{issuable.to_ability_name}[label_names]"].to_a - - issuable.labels_array.each do |label| + - selected_labels = issuable.labels + - issuable.labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} - %span.dropdown-toggle-text{ class: ("is-default" if issuable.labels_array.empty?)} - = multi_label_name(issuable.labels_array, h(selected_labels[0]), "Labels") + %span.dropdown-toggle-text{ class: ("is-default" if issuable.labels.empty?)} + = h(multi_label_name(selected_labels, selected_labels.first, "Labels")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default" diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 6c14dff6a37..2d0dae6f150 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -23,34 +23,49 @@ describe 'New/edit issue', feature: true, js: true do fill_in 'issue_description', with: 'title' click_button 'Assignee' - click_link user.name - - page.find '.js-assignee-search' do + page.within '.dropdown-menu-user' do + click_link user.name + end + expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user.id.to_s) + page.within '.js-assignee-search' do expect(page).to have_content user.name end click_button 'Milestone' - click_link milestone.title - - page.find '.js-milestone-select' do + page.within '.issue-milestone' do + click_link milestone.title + end + expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + page.within '.js-milestone-select' do expect(page).to have_content milestone.title end click_button 'Labels' - click_link label.title - click_link label2.title - - page.find '.js-label-select' do - expect(page).to have_content label2.title + page.within '.dropdown-menu-labels' do + click_link label.title + click_link label2.title + end + page.within '.js-label-select' do + expect(page).to have_content label.title end + expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) + expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) click_button 'Submit issue' - page.find '.issuable-sidebar' do - expect(page).to have_content user.name - expect(page).to have_content milestone.title - expect(page).to have_content label.title - expect(page).to have_content label2.title + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end + + page.within '.milestone' do + expect(page).to have_content milestone.title + end + + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end end end end @@ -61,24 +76,43 @@ describe 'New/edit issue', feature: true, js: true do end it 'should allow user to update issue' do - expect(page).to have_content user.name - expect(page).to have_content milestone.title + expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user.id.to_s) + expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) - click_button 'Labels' - click_link label.title - click_link label2.title + page.within '.js-user-search' do + expect(page).to have_content user.name + end - page.find '.js-label-select' do - expect(page).to have_content label2.title + page.within '.js-milestone-select' do + expect(page).to have_content milestone.title end + click_button 'Labels' + page.within '.dropdown-menu-labels' do + click_link label.title + click_link label2.title + end + page.within '.js-label-select' do + expect(page).to have_content label.title + end + expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) + expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) + click_button 'Save changes' - page.find '.issuable-sidebar' do - expect(page).to have_content user.name - expect(page).to have_content milestone.title - expect(page).to have_content label.title - expect(page).to have_content label2.title + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end + + page.within '.milestone' do + expect(page).to have_content milestone.title + end + + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end end end end diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index 9d6706ee9b3..b9591644cd2 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -14,48 +14,129 @@ describe 'New/edit merge request', feature: true, js: true do context 'owned projects' do before do - merge_request = create(:merge_request, - source_project: project, - target_project: project, - source_branch: 'fix', - target_branch: 'master' - ) - login_as(user) - - visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) end + + context 'new merge request' do + before do + visit new_namespace_project_merge_request_path( + project.namespace, + project, + merge_request: { + source_project_id: project.id, + target_project_id: project.id, + source_branch: 'fix', + target_branch: 'master' + }) + end - it 'should update merge request' do - click_button 'Assignee' - click_link user.name + it 'should create new merge request' do + click_button 'Assignee' + page.within '.dropdown-menu-user' do + click_link user.name + end + expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s) + page.within '.js-assignee-search' do + expect(page).to have_content user.name + end - page.find '.js-assignee-search' do - expect(page).to have_content user.name - end + click_button 'Milestone' + page.within '.issue-milestone' do + click_link milestone.title + end + expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + page.within '.js-milestone-select' do + expect(page).to have_content milestone.title + end + + click_button 'Labels' + page.within '.dropdown-menu-labels' do + click_link label.title + click_link label2.title + end + page.within '.js-label-select' do + expect(page).to have_content label.title + end + expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) + expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) + + click_button 'Submit merge request' + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end - click_button 'Milestone' - click_link milestone.title + page.within '.milestone' do + expect(page).to have_content milestone.title + end - page.find '.js-milestone-select' do - expect(page).to have_content milestone.title + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end end + end - click_button 'Labels' - click_link label.title - click_link label2.title + context 'edit merge request' do + before do + merge_request = create(:merge_request, + source_project: project, + target_project: project, + source_branch: 'fix', + target_branch: 'master' + ) - page.find '.js-label-select' do - expect(page).to have_content label2.title + visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) end - click_button 'Save changes' + it 'should update merge request' do + click_button 'Assignee' + page.within '.dropdown-menu-user' do + click_link user.name + end + expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s) + page.within '.js-assignee-search' do + expect(page).to have_content user.name + end + + click_button 'Milestone' + page.within '.issue-milestone' do + click_link milestone.title + end + expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + page.within '.js-milestone-select' do + expect(page).to have_content milestone.title + end + + click_button 'Labels' + page.within '.dropdown-menu-labels' do + click_link label.title + click_link label2.title + end + expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) + expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) + page.within '.js-label-select' do + expect(page).to have_content label.title + end + + click_button 'Save changes' + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end + + page.within '.milestone' do + expect(page).to have_content milestone.title + end - page.find '.issuable-sidebar' do - expect(page).to have_content user.name - expect(page).to have_content milestone.title - expect(page).to have_content label.title - expect(page).to have_content label2.title + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end end end end @@ -63,49 +144,129 @@ describe 'New/edit merge request', feature: true, js: true do context 'forked project' do before do fork_project.team << [user, :master] + login_as(user) + end - merge_request = create(:merge_request, - source_project: fork_project, - target_project: project, - source_branch: 'fix', - target_branch: 'master' - ) + context 'new merge request' do + before do + visit new_namespace_project_merge_request_path( + fork_project.namespace, + fork_project, + merge_request: { + source_project_id: fork_project.id, + target_project_id: project.id, + source_branch: 'fix', + target_branch: 'master' + }) + end - login_as(user) + it 'should create new merge request' do + click_button 'Assignee' + page.within '.dropdown-menu-user' do + click_link user.name + end + expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s) + page.within '.js-assignee-search' do + expect(page).to have_content user.name + end - visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) - end + click_button 'Milestone' + page.within '.issue-milestone' do + click_link milestone.title + end + expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + page.within '.js-milestone-select' do + expect(page).to have_content milestone.title + end - it 'should update merge request' do - click_button 'Assignee' - click_link user.name + click_button 'Labels' + page.within '.dropdown-menu-labels' do + click_link label.title + click_link label2.title + end + page.within '.js-label-select' do + expect(page).to have_content label.title + end + expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) + expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) - page.find '.js-assignee-search' do - expect(page).to have_content user.name - end + click_button 'Submit merge request' + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end - click_button 'Milestone' - click_link milestone.title + page.within '.milestone' do + expect(page).to have_content milestone.title + end - page.find '.js-milestone-select' do - expect(page).to have_content milestone.title + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end end + end - click_button 'Labels' - click_link label.title - click_link label2.title + context 'edit merge request' do + before do + merge_request = create(:merge_request, + source_project: fork_project, + target_project: project, + source_branch: 'fix', + target_branch: 'master' + ) - page.find '.js-label-select' do - expect(page).to have_content label2.title + visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) end - click_button 'Save changes' + it 'should update merge request' do + click_button 'Assignee' + page.within '.dropdown-menu-user' do + click_link user.name + end + expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s) + page.within '.js-assignee-search' do + expect(page).to have_content user.name + end + + click_button 'Milestone' + page.within '.issue-milestone' do + click_link milestone.title + end + expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) + page.within '.js-milestone-select' do + expect(page).to have_content milestone.title + end + + click_button 'Labels' + page.within '.dropdown-menu-labels' do + click_link label.title + click_link label2.title + end + expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s) + expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s) + page.within '.js-label-select' do + expect(page).to have_content label.title + end + + click_button 'Save changes' + + page.within '.issuable-sidebar' do + page.within '.assignee' do + expect(page).to have_content user.name + end + + page.within '.milestone' do + expect(page).to have_content milestone.title + end - page.find '.issuable-sidebar' do - expect(page).to have_content user.name - expect(page).to have_content milestone.title - expect(page).to have_content label.title - expect(page).to have_content label2.title + page.within '.labels' do + expect(page).to have_content label.title + expect(page).to have_content label2.title + end + end end end end -- cgit v1.2.1 From ccee2aeac5e878201bca5b6d25d020a31b07d784 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 19 Jul 2016 10:26:49 +0100 Subject: Removed duplicate labels from the dropdown on dashboard --- app/helpers/issuables_helper.rb | 14 +++++--------- app/views/shared/issuable/_filter.html.haml | 2 +- app/views/shared/issuable/_label_dropdown.html.haml | 6 ++---- app/views/shared/issuable/_sidebar.html.haml | 16 ++++++++-------- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 261747c592b..a1e85570e5d 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -7,16 +7,16 @@ module IssuablesHelper "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}" end - def multi_label_name(current_labels, selected_param, default_label) - if current_labels.any? + def multi_label_name(current_labels, default_label) + if !current_labels.nil? && current_labels.any? title = current_labels.first.try(:title) - if current_labels.count > 1 - "#{title} +#{current_labels.count - 1} more" + if current_labels.size > 1 + "#{title} +#{current_labels.size - 1} more" else title end else - selected_param.presence || default_label + default_label end end @@ -66,10 +66,6 @@ module IssuablesHelper end end - def selected_labels(project, label_query) - Label.where(label_query.merge(project_id: project)).pluck(:title).uniq - end - private def sidebar_gutter_collapsed? diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 42b5a6d61d7..2b8c2903e7a 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -24,7 +24,7 @@ = render "shared/issuable/milestone_dropdown", selected: (@issuable_finder.milestones.first unless @issuable_finder.milestones.nil?), name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: @issuable_finder.labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }, show_create: controller.controller_name != "groups" + = render "shared/issuable/label_dropdown", selected: @issuable_finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }, show_create: controller.controller_name != "groups" .pull-right = render 'shared/sort_dropdown' diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index cb50d2bc461..1fe2c1956d8 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -15,13 +15,11 @@ - if selected - selected.each do |label| - - id = label.try(:id) || label - - title = label.try(:title) || label - = hidden_field_tag data_options[:field_name], use_id ? id : title, id: nil + = hidden_field_tag data_options[:field_name], use_id ? label.try(:id) : label.try(:title), id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) } - = h(multi_label_name(selected, selected_toggle.to_a.first, "Labels")) + = h(multi_label_name(selected, "Labels")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index d800bd9fd90..2b0b0c2d3c7 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -108,30 +108,30 @@ .js-due-date-calendar - if issuable.project.labels.any? + - selected_labels = issuable.labels .block.labels .sidebar-collapsed-icon = icon('tags') %span - = issuable.labels_array.size + = selected_labels.size .title.hide-collapsed Labels = icon('spinner spin', class: 'block-loading') - if can_edit_issuable = link_to 'Edit', '#', class: 'edit-link pull-right' - .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) } - - if issuable.labels_array.any? - - issuable.labels_array.each do |label| + .value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) } + - if selected_labels.any? + - selected_labels.each do |label| = link_to_label(label, type: issuable.to_ability_name) - else %span.no-value None .selectbox.hide-collapsed - - selected_labels = issuable.labels - - issuable.labels.each do |label| + - selected_labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} - %span.dropdown-toggle-text{ class: ("is-default" if issuable.labels.empty?)} - = h(multi_label_name(selected_labels, selected_labels.first, "Labels")) + %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?)} + = h(multi_label_name(selected_labels, "Labels")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default" -- cgit v1.2.1 From a87e0dacfcc8067cdb829768fa95dc7139f3cc50 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 19 Jul 2016 11:01:34 +0100 Subject: Fixed failing issuable filter specs --- spec/features/issues/filter_issues_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 4b9b5394b61..d2d9b8594c9 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -95,9 +95,9 @@ describe 'Filter issues', feature: true do wait_for_ajax page.within '.labels-filter' do - expect(page).to have_content 'No Label' + expect(page).to have_content 'Labels' end - expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label') + expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Labels') end it 'should filter by no label' do -- cgit v1.2.1 From 5995a629817a409b7cc2d9410716d2e4c2feaf4a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 20 Jul 2016 09:45:53 +0100 Subject: Fixed up frontend code based on feedback Updated a test to be more specific about where the content is --- app/assets/javascripts/labels_select.js.coffee | 2 +- app/helpers/issuables_helper.rb | 2 +- app/helpers/milestones_helper.rb | 4 ---- app/views/shared/issuable/_filter.html.haml | 8 ++++---- app/views/shared/issuable/_form.html.haml | 5 ++--- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- app/views/shared/issuable/_milestone_dropdown.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- features/steps/project/forked_merge_requests.rb | 8 +++++--- 9 files changed, 16 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index b30067fc814..5c7edf4d2d7 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -231,7 +231,7 @@ class @LabelsSelect if $form.find("input[type='hidden']\ [name='#{$dropdown.data('fieldName')}']\ - [value='#{this.id(label)}']").length + [value=\"#{this.id(label)}\"]").length selectedClass.push 'is-active' if $dropdown.hasClass('js-multiselect') and removesAll diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index a1e85570e5d..ae2a4c80706 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -8,7 +8,7 @@ module IssuablesHelper end def multi_label_name(current_labels, default_label) - if !current_labels.nil? && current_labels.any? + if current_labels && current_labels.any? title = current_labels.first.try(:title) if current_labels.size > 1 "#{title} +#{current_labels.size - 1} more" diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index f678e1540de..e3d6fbd06ef 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -55,10 +55,6 @@ module MilestonesHelper end end - def milestone_dropdown_selected_text(selected) - selected.try(:title) unless selected.nil? - end - def milestone_remaining_days(milestone) if milestone.expired? content_tag(:strong, 'Past due') diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 2b8c2903e7a..328a8eb3c3d 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -12,19 +12,19 @@ - if params[:author_id].present? = hidden_field_tag(:author_id, params[:author_id]) = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit", - placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) + placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user.try(:username), current_user: true, project_id: @project.try(:id), selected: params[:author_id], field_name: "author_id", default_label: "Author" } }) .filter-item.inline - if params[:assignee_id].present? = hidden_field_tag(:assignee_id, params[:assignee_id]) = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) + placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user.try(:username), null_user: true, current_user: true, project_id: @project.try(:id), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } }) .filter-item.inline.milestone-filter - = render "shared/issuable/milestone_dropdown", selected: (@issuable_finder.milestones.first unless @issuable_finder.milestones.nil?), name: :milestone_title, show_any: true, show_upcoming: true + = render "shared/issuable/milestone_dropdown", selected: @issuable_finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: @issuable_finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }, show_create: controller.controller_name != "groups" + = render "shared/issuable/label_dropdown", selected: @issuable_finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } .pull-right = render 'shared/sort_dropdown' diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 90cc0fd3211..d61cf698ee4 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -56,7 +56,7 @@ - if issuable.assignee_id = f.hidden_field :assignee_id = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (project.try(:id)), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee" } }) + placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee" } }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } @@ -64,12 +64,11 @@ = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input" .form-group - has_labels = issuable.project.labels.any? - - label_dropdown_toggle = issuable.labels.map { |label| label.title } = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, selected_toggle: label_dropdown_toggle, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false } + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false } - if has_due_date .col-lg-6 .form-group diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 1fe2c1956d8..2c56a3e467d 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -19,7 +19,7 @@ .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) } - = h(multi_label_name(selected, "Labels")) + = multi_label_name(selected, "Labels") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 6d9c5d345e4..44346fa1743 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,6 +1,6 @@ - project = @target_project || @project || @projects - extra_class = extra_class || '' -- selected_text = milestone_dropdown_selected_text(selected) +- selected_text = selected.try(:title) - if selected.present? = hidden_field_tag(name, selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable", diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 2b0b0c2d3c7..07d936617a8 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -131,7 +131,7 @@ .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?)} - = h(multi_label_name(selected_labels, "Labels")) + = multi_label_name(selected_labels, "Labels") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default" diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 8f71dfdd899..974ba1ce2aa 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -143,9 +143,11 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'I should see the users from the target project ID' do - expect(page).to have_content 'Unassigned' - expect(page).to have_content current_user.name - expect(page).to have_content @project.users.first.name + page.within '.dropdown-menu-user' do + expect(page).to have_content 'Unassigned' + expect(page).to have_content current_user.name + expect(page).to have_content @project.users.first.name + end end # Verify a link is generated against the correct project -- cgit v1.2.1 From 48a360f21a92bd6e74b5c7c9e7fe9ed34e912652 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 20 Jul 2016 11:20:33 +0100 Subject: Removes @projects as not used anymore - getting selected data from controller instead --- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- app/views/shared/issuable/_milestone_dropdown.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 2c56a3e467d..a14ba0a2d9f 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,4 +1,4 @@ -- project = @target_project || @project || @projects +- project = @target_project || @project - show_create = local_assigns.fetch(:show_create, true) - extra_options = local_assigns.fetch(:extra_options, true) - filter_submit = local_assigns.fetch(:filter_submit, true) diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 44346fa1743..83d654b5026 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,4 +1,4 @@ -- project = @target_project || @project || @projects +- project = @target_project || @project - extra_class = extra_class || '' - selected_text = selected.try(:title) - if selected.present? -- cgit v1.2.1 From a6e312bf1dd01aa653761c5bf1e848df9707c0cb Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 21 Jul 2016 08:52:37 +0100 Subject: Updated wording in the specs --- spec/features/issues/form_spec.rb | 4 ++-- spec/features/merge_requests/form_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 2d0dae6f150..8771cc8e157 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -18,7 +18,7 @@ describe 'New/edit issue', feature: true, js: true do visit new_namespace_project_issue_path(project.namespace, project) end - it 'should allow user to create new issue' do + it 'allows user to create new issue' do fill_in 'issue_title', with: 'title' fill_in 'issue_description', with: 'title' @@ -75,7 +75,7 @@ describe 'New/edit issue', feature: true, js: true do visit edit_namespace_project_issue_path(project.namespace, project, issue) end - it 'should allow user to update issue' do + it 'allows user to update issue' do expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user.id.to_s) expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s) diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb index b9591644cd2..7594cbf54e8 100644 --- a/spec/features/merge_requests/form_spec.rb +++ b/spec/features/merge_requests/form_spec.rb @@ -16,7 +16,7 @@ describe 'New/edit merge request', feature: true, js: true do before do login_as(user) end - + context 'new merge request' do before do visit new_namespace_project_merge_request_path( @@ -30,7 +30,7 @@ describe 'New/edit merge request', feature: true, js: true do }) end - it 'should create new merge request' do + it 'creates new merge request' do click_button 'Assignee' page.within '.dropdown-menu-user' do click_link user.name @@ -91,7 +91,7 @@ describe 'New/edit merge request', feature: true, js: true do visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request) end - it 'should update merge request' do + it 'updates merge request' do click_button 'Assignee' page.within '.dropdown-menu-user' do click_link user.name @@ -160,7 +160,7 @@ describe 'New/edit merge request', feature: true, js: true do }) end - it 'should create new merge request' do + it 'creates new merge request' do click_button 'Assignee' page.within '.dropdown-menu-user' do click_link user.name -- cgit v1.2.1 From 24538ec0902667d399b47c7715aa75035aacecf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Libor=20Klep=C3=A1=C4=8D?= Date: Mon, 8 Aug 2016 16:28:57 +1000 Subject: Fix error 500 on Runners page. ~~~~ ActionView::Template::Error (Missing partial kaminari/_paginator ~~~~ Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=819903 Signed-off-by: Dmitry Smirnov --- app/views/admin/runners/index.html.haml | 2 +- app/views/admin/runners/show.html.haml | 2 +- app/views/projects/runners/_specific_runners.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index a53876d6757..f68b6cba524 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -73,4 +73,4 @@ - @runners.each do |runner| = render "admin/runners/runner", runner: runner - = paginate @runners + = paginate @runners, theme: "gitlab" diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 61abfc6ecbe..8ca2c97481c 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -67,7 +67,7 @@ = form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f| = f.hidden_field :runner_id, value: @runner.id = f.submit 'Enable', class: 'btn btn-xs' - = paginate @projects + = paginate @projects, theme: "gitlab" .col-md-6 %h4 Recent builds served by this runner diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index d469dda5b81..4278980d1f4 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -26,4 +26,4 @@ %h4.underlined-title Available specific runners %ul.bordered-list.available-specific-runners = render partial: 'runner', collection: @assignable_runners, as: :runner - = paginate @assignable_runners + = paginate @assignable_runners, theme: "gitlab" -- cgit v1.2.1 From 94b0fa861a2759ceb3b8958fa3940844be70e32a Mon Sep 17 00:00:00 2001 From: Dustin Miller Date: Tue, 9 Aug 2016 22:38:30 +0000 Subject: Added "View wiki pages" to list of actions granted to various user permission levels in a project. --- doc/user/permissions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 66542861781..fb5058b2e30 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -23,6 +23,7 @@ The following table depicts the various user permission levels in a project. | See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | +| View wiki pages | ✓ | ✓ | ✓ | ✓ | ✓ | | Pull project code | | ✓ | ✓ | ✓ | ✓ | | Download project | | ✓ | ✓ | ✓ | ✓ | | Create code snippets | | ✓ | ✓ | ✓ | ✓ | -- cgit v1.2.1 From 34cbc51e238a6b1626ef3d656b2dc80db6463971 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 25 Aug 2016 20:01:10 +0800 Subject: Add a new pipeline email service. TODO: email templates and tests --- app/mailers/emails/pipelines.rb | 38 +++++++++ app/mailers/notify.rb | 1 + .../project_services/pipelines_email_service.rb | 93 ++++++++++++++++++++++ app/workers/pipeline_email_worker.rb | 39 +++++++++ 4 files changed, 171 insertions(+) create mode 100644 app/mailers/emails/pipelines.rb create mode 100644 app/models/project_services/pipelines_email_service.rb create mode 100644 app/workers/pipeline_email_worker.rb diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb new file mode 100644 index 00000000000..7fdba850219 --- /dev/null +++ b/app/mailers/emails/pipelines.rb @@ -0,0 +1,38 @@ +module Emails + module Pipelines + def pipeline_succeeded_email(params, to) + pipeline_mail(params, to, 'succeeded') # TODO: missing template + end + + def pipeline_failed_email(params, to) + pipeline_mail(params, to, 'failed') # TODO: missing template + end + + private + + def pipeline_mail(params, to, status) + @params = params + add_headers + + mail(to: to, subject: pipeline_subject('failed')) + end + + def add_headers + @project = @params.project # `add_project_headers` needs this + add_project_headers + add_pipeline_headers(@params.pipeline) + end + + def add_pipeline_headers(pipeline) + headers['X-GitLab-Pipeline-Id'] = pipeline.id + headers['X-GitLab-Pipeline-Ref'] = pipeline.ref + headers['X-GitLab-Pipeline-Status'] = pipeline.status + end + + def pipeline_subject(status) + subject( + "Pipeline #{status} for #{@params.project.name}", + @params.pipeline.short_sha) + end + end +end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 0cc709f68e4..fef8149b325 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -7,6 +7,7 @@ class Notify < BaseMailer include Emails::Projects include Emails::Profile include Emails::Builds + include Emails::Pipelines include Emails::Members add_template_helper MergeRequestsHelper diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb new file mode 100644 index 00000000000..1f852445c1c --- /dev/null +++ b/app/models/project_services/pipelines_email_service.rb @@ -0,0 +1,93 @@ +class PipelinesEmailService < Service + prop_accessor :recipients + boolean_accessor :add_pusher + boolean_accessor :notify_only_broken_pipelines + validates :recipients, + presence: true, + if: ->(s) { s.activated? && !s.add_pusher? } + + def initialize_properties + self.properties ||= { notify_only_broken_builds: true } + end + + def title + 'Pipelines emails' + end + + def description + 'Email the pipelines status to a list of recipients.' + end + + def to_param + 'pipelines_email' + end + + def supported_events + %w[pipeline] + end + + def execute(data, force = false) + return unless supported_events.include?(data[:object_kind]) + return unless force || should_build_be_notified?(data) + + all_recipients = retrieve_recipients(data) + + return unless all_recipients.any? + + PipelineEmailWorker.perform_async(data, all_recipients) + end + + def can_test? + project.pipelines.count > 0 + end + + def disabled_title + 'Please setup a pipeline on your repository.' + end + + def test_data(project, user) + data = Gitlab::DataBuilder::Pipeline.build(project.pipelines.last) + data[:user] = user.hook_attrs + data + end + + def fields + [ + { type: 'textarea', + name: 'recipients', + placeholder: 'Emails separated by comma' }, + { type: 'checkbox', + name: 'add_pusher', + label: 'Add pusher to recipients list' }, + { type: 'checkbox', + name: 'notify_only_broken_pipelines' }, + ] + end + + def test(data) + result = execute(data, true) + + { success: true, result: result } + rescue StandardError => error + { success: false, result: error } + end + + def should_build_be_notified?(data) + case data[:object_attributes][:status] + when 'success' + !notify_only_broken_pipelines? + else + false + end + end + + def retrieve_recipients(data) + all_recipients = recipients.to_s.split(',').reject(&:blank?) + + if add_pusher? && data[:user].try(:[], :email) + all_recipients << data[:user][:email] + else + all_recipients + end + end +end diff --git a/app/workers/pipeline_email_worker.rb b/app/workers/pipeline_email_worker.rb new file mode 100644 index 00000000000..2160207c901 --- /dev/null +++ b/app/workers/pipeline_email_worker.rb @@ -0,0 +1,39 @@ +class PipelineEmailWorker + include Sidekiq::Worker + + ParamsStruct = Struct.new(:pipeline, :project, :email_template) + class Params < ParamsStruct + def initialize(pipeline_id) + self.pipeline = Ci::Pipeline.find(pipeline_id) + self.project = pipeline.project + self.email_template = case pipeline.status + when 'success' + :pipeline_succeeded_email + when 'failed' + :pipeline_failed_email + end + end + end + + def perform(data, recipients) + params = Params.new(data['object_attributes']['id']) + + return unless params.email_template + + recipients.each do |to| + deliver(params, to) do + Notify.public_send(params.email_template, params, to).deliver_now + end + end + end + + private + + def deliver(params, to) + yield + # These are input errors and won't be corrected even if Sidekiq retries + rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e + project_name = params.project.path_with_namespace + logger.info("Failed to send email for #{project_name} to #{to}: #{e}") + end +end -- cgit v1.2.1 From 2d38d35b0d367dd6e7ef493005afc87d3c4be416 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 30 Aug 2016 12:10:05 +0100 Subject: Fixed label toggle text --- app/assets/javascripts/labels_select.js | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 4c3514e3f23..a81aae2cee3 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -212,21 +212,24 @@ }, selectable: true, filterable: true, + selected: $dropdown.data('selected') || [], toggleLabel: function(selected, el) { - var selected_labels; - selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active'); - if (selected && (selected.title != null)) { - if (selected_labels.length > 1) { - return selected.title + " +" + (selected_labels.length - 1) + " more"; - } else { - return selected.title; - } - } else if (!selected && selected_labels.length !== 0) { - if (selected_labels.length > 1) { - return ($(selected_labels[0]).text()) + " +" + (selected_labels.length - 1) + " more"; - } else if (selected_labels.length === 1) { - return $(selected_labels).text(); - } + var isSelected = el !== null ? el.hasClass('is-active') : false, + title = selected.title; + + if (isSelected) { + this.selected.push(title); + } else { + var index = this.selected.indexOf(title); + this.selected.splice(index, 1); + } + + var selectedLabels = this.selected; + + if (selectedLabels.length === 1) { + return selectedLabels; + } else if (selectedLabels.length > 1) { + return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more"; } else { return defaultLabel; } -- cgit v1.2.1 From 0fead047c930f936ca5e307a3525bef8e3de1d36 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 30 Aug 2016 12:11:09 +0100 Subject: Removed namespace test --- app/views/shared/issuable/_milestone_dropdown.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index b98f2bc4199..ab537dc0ed7 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -5,7 +5,7 @@ = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - - if project && project.respond_to?(:namespace) + - if project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project %li -- cgit v1.2.1 From 236178c3010cd40709c232b2a3a82f737d8a1eab Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 30 Aug 2016 14:50:46 +0100 Subject: Fixed boards filters --- app/controllers/projects/boards_controller.rb | 2 ++ app/views/shared/issuable/_filter.html.haml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 33206717089..0035633b774 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -1,4 +1,6 @@ class Projects::BoardsController < Projects::ApplicationController + include IssuableCollections + respond_to :html before_action :authorize_read_board!, only: [:show] diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 468f554ec39..b83e0bbfda5 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -1,4 +1,4 @@ -- finder = controller.controller_name == 'issues' ? issues_finder : merge_requests_finder +- finder = controller.controller_name == 'issues' || controller.controller_name == 'boards' ? issues_finder : merge_requests_finder .issues-filters .issues-details-filters.row-content-block.second-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do -- cgit v1.2.1 From 97582338070020dca8abcdac4009fe59e83876e7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 31 Aug 2016 17:38:25 +0800 Subject: Fix copy pasta --- app/mailers/emails/pipelines.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 7fdba850219..48bfc974768 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -14,7 +14,7 @@ module Emails @params = params add_headers - mail(to: to, subject: pipeline_subject('failed')) + mail(to: to, subject: pipeline_subject(status)) end def add_headers -- cgit v1.2.1 From 0997cfd750070f6fb4b821d5982594fc3ddb13e8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 31 Aug 2016 17:40:14 +0800 Subject: We don't need this --- app/mailers/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/mailers/.gitkeep diff --git a/app/mailers/.gitkeep b/app/mailers/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 -- cgit v1.2.1 From 1bd2327c314ca8d5782298e80443c0fbbba0b66a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 31 Aug 2016 21:38:02 +0800 Subject: Add mock email templates --- app/mailers/emails/pipelines.rb | 17 ++++----- app/models/ci/build.rb | 7 +++- app/models/service.rb | 1 + app/views/notify/pipeline_failed_email.html.haml | 44 ++++++++++++++++++++++ app/views/notify/pipeline_failed_email.text.erb | 15 ++++++++ .../notify/pipeline_succeeded_email.html.haml | 26 +++++++++++++ app/views/notify/pipeline_succeeded_email.text.erb | 7 ++++ 7 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 app/views/notify/pipeline_failed_email.html.haml create mode 100644 app/views/notify/pipeline_failed_email.text.erb create mode 100644 app/views/notify/pipeline_succeeded_email.html.haml create mode 100644 app/views/notify/pipeline_succeeded_email.text.erb diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 48bfc974768..7c181d2e366 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -11,28 +11,27 @@ module Emails private def pipeline_mail(params, to, status) - @params = params + @project = params.project + @pipeline = params.pipeline add_headers mail(to: to, subject: pipeline_subject(status)) end def add_headers - @project = @params.project # `add_project_headers` needs this add_project_headers - add_pipeline_headers(@params.pipeline) + add_pipeline_headers end - def add_pipeline_headers(pipeline) - headers['X-GitLab-Pipeline-Id'] = pipeline.id - headers['X-GitLab-Pipeline-Ref'] = pipeline.ref - headers['X-GitLab-Pipeline-Status'] = pipeline.status + def add_pipeline_headers + headers['X-GitLab-Pipeline-Id'] = @pipeline.id + headers['X-GitLab-Pipeline-Ref'] = @pipeline.ref + headers['X-GitLab-Pipeline-Status'] = @pipeline.status end def pipeline_subject(status) subject( - "Pipeline #{status} for #{@params.project.name}", - @params.pipeline.short_sha) + "Pipeline #{status} for #{@project.name}", @pipeline.short_sha) end end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 096b3b801af..755edadb7a4 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -133,8 +133,11 @@ module Ci end def trace_with_state(state = nil) - trace_with_state = Ci::Ansi2html::convert(trace, state) if trace.present? - trace_with_state || {} + if trace.present? + Ci::Ansi2html.convert(trace, state) + else + {} + end end def timeout diff --git a/app/models/service.rb b/app/models/service.rb index 09b4717a523..668c3fb69e3 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -198,6 +198,7 @@ class Service < ActiveRecord::Base bamboo buildkite builds_email + pipelines_email bugzilla campfire custom_issue_tracker diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml new file mode 100644 index 00000000000..8d68b9e8483 --- /dev/null +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -0,0 +1,44 @@ +.p + Project: + = @project.path_with_namespace +.p + Branch: + = @pipeline.ref +.p + Commit: + = @pipeline.short_sha + ( + = @pipeline.sha + ) +.p + Commit Message: + = @pipeline.git_commit_message +.p + Commit Author: + = @pipeline.git_author_name +.p + Pusher: + = @pipeline.user.try(:name) +- failed = @pipeline.statuses.latest.failed +.p + Pipeline # + = @pipeline.id + had + = failed.size + failed + = 'job'.plural(failed.size) + . + +- failed.each do |job| + .p + ID: + = job.id + .p + Stage: + = job.stage + .p + Name: + = job.name + .p + Trace: + = job.trace_with_state[:html].html_safe diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb new file mode 100644 index 00000000000..9808213af99 --- /dev/null +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -0,0 +1,15 @@ +Project: <%= @project.path_with_namespace %> +Branch: <%= @pipeline.ref %> +Commit: <%= @pipeline.short_sha %> (<%= @pipeline.sha %>) +Commit Message: <%= @pipeline.git_commit_message %> +Commit Author: <%= @pipeline.git_author_name %> +Pusher: <%= @pipeline.user.try(:name) %> +<% failed = @pipeline.statuses.latest.failed %> +Pipeline #<%= @pipeline.id %> had <%= failed.size %> failed <%= 'job'.plural(failed.size) %>. + +<% failed.each do |job| %> +ID: <%= job.id %> +Stage: <%= job.stage %> +Name: <%= job.name %> +Trace: <%= job.trace_with_state[:html] %> +<% end %> diff --git a/app/views/notify/pipeline_succeeded_email.html.haml b/app/views/notify/pipeline_succeeded_email.html.haml new file mode 100644 index 00000000000..64cf7cfe103 --- /dev/null +++ b/app/views/notify/pipeline_succeeded_email.html.haml @@ -0,0 +1,26 @@ +.p + Project: + = @project.path_with_namespace +.p + Branch: + = @pipeline.ref +.p + Commit: + = @pipeline.short_sha + ( + = @pipeline.sha + ) +.p + Commit Message: + = @pipeline.git_commit_message +.p + Commit Author: + = @pipeline.git_author_name +.p + Pusher: + = @pipeline.user.try(:name) +- failed = @pipeline.statuses.latest.failed +.p + Pipeline # + = @pipeline.id + had succeeded. diff --git a/app/views/notify/pipeline_succeeded_email.text.erb b/app/views/notify/pipeline_succeeded_email.text.erb new file mode 100644 index 00000000000..6821d1f4459 --- /dev/null +++ b/app/views/notify/pipeline_succeeded_email.text.erb @@ -0,0 +1,7 @@ +Project: <%= @project.path_with_namespace %> +Branch: <%= @pipeline.ref %> +Commit: <%= @pipeline.short_sha %> (<%= @pipeline.sha %>) +Commit Message: <%= @pipeline.git_commit_message %> +Commit Author: <%= @pipeline.git_author_name %> +Pusher: <%= @pipeline.user.try(:name) %> +Pipeline #<%= @pipeline.id %> had succeeded. -- cgit v1.2.1 From f5c9a40989782669712de5073cae32900c9d572d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 1 Sep 2016 02:15:43 +0800 Subject: Simplify the code --- app/controllers/admin/services_controller.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb index 7c37f3155da..37a1a23178e 100644 --- a/app/controllers/admin/services_controller.rb +++ b/app/controllers/admin/services_controller.rb @@ -26,14 +26,10 @@ class Admin::ServicesController < Admin::ApplicationController private def services_templates - templates = [] - - Service.available_services_names.each do |service_name| + Service.available_services_names.map do |service_name| service_template = service_name.concat("_service").camelize.constantize - templates << service_template.where(template: true).first_or_create + service_template.where(template: true).first_or_create end - - templates end def service -- cgit v1.2.1 From 193c8d07f7ad68c6645ec3ed3b32aa8c7a707669 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 1 Sep 2016 02:16:03 +0800 Subject: Fix array literal style --- app/models/service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/service.rb b/app/models/service.rb index 668c3fb69e3..754cca07648 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -192,7 +192,7 @@ class Service < ActiveRecord::Base end def self.available_services_names - %w( + %w[ asana assembla bamboo @@ -215,7 +215,7 @@ class Service < ActiveRecord::Base redmine slack teamcity - ) + ] end def self.create_from_template(project_id, template) -- cgit v1.2.1 From 11dd99d5d3560cc06d8f5e57b7791d8f4b6d993f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 1 Sep 2016 02:16:24 +0800 Subject: So that it could create the service without having a template --- app/models/project.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/project.rb b/app/models/project.rb index 8cf093be4c3..d95645bc255 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -72,6 +72,7 @@ class Project < ActiveRecord::Base has_one :drone_ci_service, dependent: :destroy has_one :emails_on_push_service, dependent: :destroy has_one :builds_email_service, dependent: :destroy + has_one :pipelines_email_service, dependent: :destroy has_one :irker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy -- cgit v1.2.1 From e83149d5ed1dd1b69786ad8029f848b31a3a047d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 1 Sep 2016 02:17:20 +0800 Subject: send works on strings --- app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index d95645bc255..ec47953799f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -710,7 +710,7 @@ class Project < ActiveRecord::Base if template.nil? # If no template, we should create an instance. Ex `create_gitlab_ci_service` - self.send :"create_#{service_name}_service" + send("create_#{service_name}_service") else Service.create_from_template(self.id, template) end -- cgit v1.2.1 From be414d4829463708e23b639fea9e6fe88863b1c5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 1 Sep 2016 02:21:16 +0800 Subject: We don't need them --- lib/tasks/.gitkeep | 0 lib/tasks/ci/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 lib/tasks/.gitkeep delete mode 100644 lib/tasks/ci/.gitkeep diff --git a/lib/tasks/.gitkeep b/lib/tasks/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/lib/tasks/ci/.gitkeep b/lib/tasks/ci/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 -- cgit v1.2.1 From 5de54a7137ee6de121e5c3477b4901a183b01762 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 1 Sep 2016 02:21:24 +0800 Subject: no longer TODO --- app/mailers/emails/pipelines.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 7c181d2e366..643b947a049 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -1,11 +1,11 @@ module Emails module Pipelines def pipeline_succeeded_email(params, to) - pipeline_mail(params, to, 'succeeded') # TODO: missing template + pipeline_mail(params, to, 'succeeded') end def pipeline_failed_email(params, to) - pipeline_mail(params, to, 'failed') # TODO: missing template + pipeline_mail(params, to, 'failed') end private -- cgit v1.2.1 From 69db604e55de2bdf1a28c274be6cc9131534517d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 14:00:57 +0100 Subject: Updated tests --- spec/features/issues/filter_issues_spec.rb | 1 - spec/views/projects/merge_requests/edit.html.haml_spec.rb | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 372ad39676d..24e5491f087 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -110,7 +110,6 @@ describe 'Filter issues', feature: true do end it 'filters by wont fix labels' do - find('.dropdown-menu-labels a', text: label.title).click page.within '.labels-filter' do expect(page).to have_content wontfix.title click_link wontfix.title diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb index 31bbb150698..9da05e1721c 100644 --- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -7,12 +7,15 @@ describe 'projects/merge_requests/edit.html.haml' do let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } + let(:milestone) { create(:milestone, project: project) } let(:closed_merge_request) do create(:closed_merge_request, source_project: fork_project, target_project: project, - author: user) + author: user, + assignee: user, + milestone: milestone) end before do -- cgit v1.2.1 From ffdccaa0a8290ff563b377d26e4e91b803e7b049 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 2 Sep 2016 15:26:45 +0100 Subject: Updated diff toggle targets and added icon --- app/assets/javascripts/single_file_diff.js | 13 ++++++++++--- app/assets/stylesheets/framework/blocks.scss | 6 ++---- app/assets/stylesheets/framework/files.scss | 11 ++++++++++- app/views/projects/diffs/_content.html.haml | 4 +++- app/views/projects/diffs/_file_header.html.haml | 1 + 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 156b9b8abec..ee6af123268 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -10,12 +10,13 @@ ERROR_HTML = '
Could not load diff
'; - COLLAPSED_HTML = '
This diff is collapsed. Click to expand it.
'; + COLLAPSED_HTML = '
'; function SingleFileDiff(file) { this.file = file; this.toggleDiff = bind(this.toggleDiff, this); this.content = $('.diff-content', this.file); + this.$toggleIcon = $('.diff-toggle-caret', this.file); this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path'); this.isOpen = !this.diffForPath; if (this.diffForPath) { @@ -23,18 +24,22 @@ this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide(); this.content = null; this.collapsedContent.after(this.loadingContent); + this.$toggleIcon.addClass('fa-caret-right'); } else { this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide(); this.content.after(this.collapsedContent); + this.$toggleIcon.addClass('fa-caret-down'); } - this.collapsedContent.on('click', this.toggleDiff); - $('.file-title > a', this.file).on('click', this.toggleDiff); + $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); } SingleFileDiff.prototype.toggleDiff = function(e) { + var $target = $(e.target); + if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; this.isOpen = !this.isOpen; if (!this.isOpen && !this.hasError) { this.content.hide(); + this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down'); this.collapsedContent.show(); if (typeof DiffNotesApp !== 'undefined') { DiffNotesApp.compileComponents(); @@ -42,10 +47,12 @@ } else if (this.content) { this.collapsedContent.hide(); this.content.show(); + this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); if (typeof DiffNotesApp !== 'undefined') { DiffNotesApp.compileComponents(); } } else { + this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right'); return this.getContentHTML(); } }; diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 7ce203d2ec7..465d58219d8 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -19,10 +19,8 @@ &.diff-collapsed { padding: 5px; - cursor: pointer; - - &:hover { - background-color: $row-hover; + .click-to-expand { + cursor: pointer; } } } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index e3be45ba1dc..655087a5f91 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -26,6 +26,15 @@ padding: 10px $gl-padding; word-wrap: break-word; border-radius: 3px 3px 0 0; + cursor: pointer; + + &:hover { + background-color: $dark-background-color; + } + + .diff-toggle-caret { + padding-right: 6px; + } &.file-title-clear { padding-left: 0; @@ -63,7 +72,7 @@ &.image_file { background: #eee; text-align: center; - + img { padding: 20px; max-width: 80%; diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index d37961c4e40..779c8ea0104 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -11,7 +11,9 @@ - elsif diff_file.collapsed? - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path)) .nothing-here-block.diff-collapsed{data: { diff_for_path: url } } - This diff is collapsed. Click to expand it. + This diff is collapsed. + %a.click-to-expand + Click to expand it. - elsif diff_file.diff_lines.length > 0 - if diff_view == :parallel = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index 95a2772fd0b..a6a2e5690b5 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -1,3 +1,4 @@ +%i.fa.diff-toggle-caret - if defined?(blob) && blob && diff_file.submodule? %span = icon('archive fw') -- cgit v1.2.1 From 40c7ba0129b0954802d4045c4b95626166cdfdc4 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sat, 3 Sep 2016 20:24:26 +0100 Subject: Updated spec with next click targets --- spec/features/expand_collapse_diffs_spec.rb | 40 ++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb index 688f68d3cff..b9f53cf0fa1 100644 --- a/spec/features/expand_collapse_diffs_spec.rb +++ b/spec/features/expand_collapse_diffs_spec.rb @@ -68,7 +68,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do context 'expanding a diff for a renamed file' do before do - large_diff_renamed.find('.nothing-here-block').click + large_diff_renamed.find('.click-to-expand').click wait_for_ajax end @@ -87,7 +87,10 @@ feature 'Expand and collapse diffs', js: true, feature: true do context 'expanding a large diff' do before do - click_link('large_diff.md') + # Wait for diffs + find('.file-title', match: :first) + # Click `large_diff.md` title + all('.file-title')[1].click wait_for_ajax end @@ -128,7 +131,10 @@ feature 'Expand and collapse diffs', js: true, feature: true do context 'expanding the diff' do before do - click_link('large_diff.md') + # Wait for diffs + find('.file-title', match: :first) + # Click `large_diff.md` title + all('.file-title')[1].click wait_for_ajax end @@ -146,7 +152,12 @@ feature 'Expand and collapse diffs', js: true, feature: true do end context 'collapsing an expanded diff' do - before { click_link('small_diff.md') } + before do + # Wait for diffs + find('.file-title', match: :first) + # Click `small_diff.md` title + all('.file-title')[3].click + end it 'hides the diff content' do expect(small_diff).not_to have_selector('.code') @@ -154,7 +165,12 @@ feature 'Expand and collapse diffs', js: true, feature: true do end context 're-expanding the same diff' do - before { click_link('small_diff.md') } + before do + # Wait for diffs + find('.file-title', match: :first) + # Click `small_diff.md` title + all('.file-title')[3].click + end it 'shows the diff content' do expect(small_diff).to have_selector('.code') @@ -224,7 +240,12 @@ feature 'Expand and collapse diffs', js: true, feature: true do end context 'collapsing an expanded diff' do - before { click_link('small_diff.md') } + before do + # Wait for diffs + find('.file-title', match: :first) + # Click `small_diff.md` title + all('.file-title')[3].click + end it 'hides the diff content' do expect(small_diff).not_to have_selector('.code') @@ -232,7 +253,12 @@ feature 'Expand and collapse diffs', js: true, feature: true do end context 're-expanding the same diff' do - before { click_link('small_diff.md') } + before do + # Wait for diffs + find('.file-title', match: :first) + # Click `small_diff.md` title + all('.file-title')[3].click + end it 'shows the diff content' do expect(small_diff).to have_selector('.code') -- cgit v1.2.1 From dbd6b2dc0ad4a379490cbb1ea23eee9d90526163 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 8 Sep 2016 19:23:30 +0100 Subject: Changed build header username area to use the full name with the username as the tooltip --- app/views/projects/builds/_user.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/builds/_user.html.haml b/app/views/projects/builds/_user.html.haml index 2642de8021d..75101b78652 100644 --- a/app/views/projects/builds/_user.html.haml +++ b/app/views/projects/builds/_user.html.haml @@ -1,4 +1,5 @@ by %a{ href: user_path(@build.user) } = image_tag avatar_icon(@build.user, 24), class: "avatar s24" - %strong= @build.user.to_reference + %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } } + = @build.user.name -- cgit v1.2.1 From f099e36c181d3f089a18dab20b5aa0cc982da0a1 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 8 Sep 2016 21:00:24 +0100 Subject: Change username full name back to only username on xs viewports --- app/views/projects/builds/_user.html.haml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/projects/builds/_user.html.haml b/app/views/projects/builds/_user.html.haml index 75101b78652..83f299da651 100644 --- a/app/views/projects/builds/_user.html.haml +++ b/app/views/projects/builds/_user.html.haml @@ -1,5 +1,7 @@ by %a{ href: user_path(@build.user) } - = image_tag avatar_icon(@build.user, 24), class: "avatar s24" - %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } } - = @build.user.name + %span.hidden-xs + = image_tag avatar_icon(@build.user, 24), class: "avatar s24" + %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } } + = @build.user.name + %strong.visible-xs-inline= @build.user.to_reference -- cgit v1.2.1 From 52d2fd49b5d913d40259161e44579b715b4a383c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 31 Aug 2016 15:28:58 +0100 Subject: Updated members UI --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/project_members.js | 10 ---- app/assets/javascripts/project_members.js.es6 | 36 +++++++++++++++ app/assets/stylesheets/framework/selects.scss | 6 ++- app/assets/stylesheets/pages/members.scss | 22 +++++++++ .../project_members/_new_project_member.html.haml | 35 ++++++-------- app/views/projects/project_members/_team.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 23 ++++----- app/views/shared/members/_member.html.haml | 54 ++++++++-------------- 9 files changed, 111 insertions(+), 79 deletions(-) delete mode 100644 app/assets/javascripts/project_members.js create mode 100644 app/assets/javascripts/project_members.js.es6 create mode 100644 app/assets/stylesheets/pages/members.scss diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 99b16f7d59b..c95aaf61443 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -132,7 +132,7 @@ break; case 'projects:project_members:index': new gl.MemberExpirationDate(); - new ProjectMembers(); + new gl.ProjectMembers(); new UsersSelect(); break; case 'groups:new': diff --git a/app/assets/javascripts/project_members.js b/app/assets/javascripts/project_members.js deleted file mode 100644 index 78f7b48bc7d..00000000000 --- a/app/assets/javascripts/project_members.js +++ /dev/null @@ -1,10 +0,0 @@ -(function() { - this.ProjectMembers = (function() { - function ProjectMembers() { - $('li.project_member').bind('ajax:success', function() { - return $(this).fadeOut(); - }); - } - return ProjectMembers; - })(); -}).call(this); diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 new file mode 100644 index 00000000000..74cedfd5006 --- /dev/null +++ b/app/assets/javascripts/project_members.js.es6 @@ -0,0 +1,36 @@ +((w) => { + window.gl = window.gl || {}; + + class ProjectMembers { + constructor() { + this.removeListeners(); + this.addListeners(); + } + + removeListeners() { + $('.project_member').off('ajax:success'); + $('.js-member-update-control').off('change'); + } + + addListeners() { + $('.project_member').on('ajax:success', this.removeRow); + $('.js-member-update-control').on('change', function () { + console.log($(this).val()); + }); + } + + removeRow(e) { + const $target = $(e.target); + + if ($target.hasClass('btn-remove')) { + $target.fadeOut(); + } + } + + submitForm() { + + } + } + + gl.ProjectMembers = ProjectMembers; +})(window); diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index c75dacf95d9..746ab89abd2 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -86,7 +86,7 @@ background: none; .select2-search-field input { - padding: $gl-padding / 2; + padding: 5px $gl-padding / 2; font-size: 13px; height: auto; font-family: inherit; @@ -191,6 +191,10 @@ &.input-clamp { max-width: 100%; } + + &.input-full { + width: 100%; + } } .select2-highlighted { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss new file mode 100644 index 00000000000..9583d7c6161 --- /dev/null +++ b/app/assets/stylesheets/pages/members.scss @@ -0,0 +1,22 @@ +.project-members-new { + > h5 { + font-weight: normal; + } +} + +.member { + .controls { + display: flex; + width: 400px; + } + + .form-horizontal { + display: flex; + flex: 1; + margin-top: 3px; + } + + .member-form-control { + width: 50%; + } +} diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index fa8cbf71733..c0b187fb460 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,27 +1,22 @@ -= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f| - .form-group - = f.label :user_ids, "People", class: 'control-label' - .col-sm-10 - = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true) - .help-block += form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project) do |f| + .row + .col-md-4.col-lg-6 + = users_select_tag(:user_ids, multiple: true, class: "input-full", scope: :all, email_user: true) + .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. - .form-group - = f.label :access_level, "Project Access", class: 'control-label' - .col-sm-10 - = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2" - .help-block - Read more about role permissions - %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + .col-md-3.col-lg-2 + = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select" + .help-block.append-bottom-10 + = link_to "Read more", help_page_path("user/permissions"), class: "vlink" + about role permissions - .form-group - = f.label :expires_at, 'Access expiration date', class: 'control-label' - .col-sm-10 + .col-md-3.col-lg-2 .clearable-input - = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date' + = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input - .help-block + .help-block.append-bottom-10 On this date, the user(s) will automatically lose access to this project. - .form-actions - = f.submit 'Add users to project', class: "btn btn-create" + .col-md-2 + = f.submit "Add to project", class: "btn btn-create btn-block" diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index b0bfdd235f7..db6c1194da7 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading + Users with access to %strong #{@project.name} - project members %span.badge= members.size .controls = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 9d063b3081f..9d47e7d725c 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,20 +1,21 @@ - page_title "Members" .project-members-page.js-project-members-page.prepend-top-default + %h4 + Members + %hr - if can?(current_user, :admin_project_member, @project) - .panel.panel-default - .panel-heading - Add new user to project - .controls - = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do - Import members - .panel-body - %p.light - Users with access to this project are listed below. - = render "new_project_member" + .project-members-new.append-bottom-default + %h5.clearfix + Add new user to + %strong= @project.name + -# = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right", title: "Import members from another project" + = render "new_project_member" - = render 'shared/members/requests', membership_source: @project, requesters: @requesters + = render 'shared/members/requests', membership_source: @project, requesters: @requesters + %h5.append-bottom-default + Existing users and groups = render 'team', members: @project_members - if @group diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 5f20e4bd42a..fd9b688dc20 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -2,27 +2,28 @@ - show_controls = local_assigns.fetch(:show_controls, true) - user = member.user -%li.js-toggle-container{ class: dom_class(member), id: dom_id(member) } +%li.member{ class: dom_class(member), id: dom_id(member) } - if show_roles .controls - %strong.control-text= member.human_access - if show_controls - - if !user && can?(current_user, action_member_permission(:admin, member), member.source) - = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), - method: :post, - class: 'btn' - - - if can?(current_user, action_member_permission(:update, member), member) - = button_tag icon('pencil'), - type: 'button', - class: 'btn inline js-toggle-button', - title: 'Edit' - - - if member.request? - = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), + - if @project.owner != user + = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + .prepend-left-5.append-right-10.clearable-input.member-form-control + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + %i.clear-icon.js-clear-input + - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, - class: 'btn btn-success', - title: 'Grant access' + class: 'btn' + - else + Owner + + - if member.request? && can?(current_user, action_member_permission(:update, member), member) + = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), + method: :post, + class: 'btn btn-success', + title: 'Grant access' - if can?(current_user, action_member_permission(:destroy, member), member) - if current_user == user @@ -44,7 +45,7 @@ = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' %strong = link_to user.name, user_path(user) - %span.cgray= user.username + %span.cgray= user.to_reference - if user == current_user %span.label.label-success It's you @@ -73,20 +74,3 @@ by = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) - - - if show_roles - .edit-member.hide.js-toggle-content - %br - = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| - .form-group - = label_tag "member_access_level_#{member.id}", 'Project access', class: 'control-label' - .col-sm-10 - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control', id: "member_access_level_#{member.id}" - .form-group - = label_tag "member_expires_at_#{member.id}", 'Access expiration date', class: 'control-label' - .col-sm-10 - .clearable-input - = f.text_field :expires_at, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date', id: "member_expires_at_#{member.id}" - %i.clear-icon.js-clear-input - .prepend-top-10 - = f.submit 'Save', class: 'btn btn-save btn-sm' -- cgit v1.2.1 From 87a0501ded0d08ae718b6f3f6feb4ac2c9c6b016 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 11:56:21 +0100 Subject: Updates the member row when values changed --- app/assets/javascripts/member_expiration_date.js | 8 ++++++-- app/assets/javascripts/project_members.js.es6 | 5 +++-- app/views/projects/project_members/update.js.haml | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js index 1935af491f7..e1532fd9ec4 100644 --- a/app/assets/javascripts/member_expiration_date.js +++ b/app/assets/javascripts/member_expiration_date.js @@ -14,14 +14,18 @@ inputs.datepicker({ dateFormat: 'yy-mm-dd', minDate: 1, - onSelect: toggleClearInput + onSelect: function () { + $(this).trigger('change'); + toggleClearInput.call(this); + } }); inputs.next('.js-clear-input').on('click', function(event) { event.preventDefault(); var input = $(this).closest('.clearable-input').find('.js-access-expiration-date'); - input.datepicker('setDate', null); + input.datepicker('setDate', null) + .trigger('change'); toggleClearInput.call(input); }); diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index 74cedfd5006..659c57d8b6c 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -15,7 +15,8 @@ addListeners() { $('.project_member').on('ajax:success', this.removeRow); $('.js-member-update-control').on('change', function () { - console.log($(this).val()); + $(this).closest('form') + .trigger("submit.rails"); }); } @@ -28,7 +29,7 @@ } submitForm() { - + } } diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 37e55dc72a3..91927181efb 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,3 +1,3 @@ :plain - $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}'); - new gl.MemberExpirationDate(); + var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}'); + $("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); -- cgit v1.2.1 From 4afd17b2786b5bca075ac7508979fad582c65bc9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 13:48:20 +0100 Subject: Included groups on project_members page --- app/assets/stylesheets/framework/lists.scss | 8 +++ app/assets/stylesheets/pages/groups.scss | 2 - app/assets/stylesheets/pages/members.scss | 9 +++ app/controllers/projects/group_links_controller.rb | 12 ++++ .../projects/project_members_controller.rb | 1 + app/views/projects/group_links/update.js.haml | 3 + .../project_members/_group_members.html.haml | 2 +- .../projects/project_members/_groups.html.haml | 9 +++ app/views/projects/project_members/_team.html.haml | 12 ---- app/views/projects/project_members/index.html.haml | 14 +++-- app/views/shared/members/_group.html.haml | 23 ++++++++ app/views/shared/members/_member.html.haml | 69 ++++++++++------------ config/routes.rb | 2 +- 13 files changed, 108 insertions(+), 58 deletions(-) create mode 100644 app/views/projects/group_links/update.js.haml create mode 100644 app/views/projects/project_members/_groups.html.haml create mode 100644 app/views/shared/members/_group.html.haml diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 965fcc06518..dfdfe4a3c89 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -164,6 +164,14 @@ ul.content-list { } } + .member-controls { + float: none; + + @media (min-width: $screen-md-min) { + float: right; + } + } + // When dragging a list item &.ui-sortable-helper { border-bottom: none; diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index b657ca47d38..a27f7a2fd77 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -1,6 +1,4 @@ .member-search-form { - float: left; - input[type='search'] { width: 225px; vertical-align: bottom; diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 9583d7c6161..4bc34ac15df 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -5,6 +5,15 @@ } .member { + .list-item-name { + float: none; + + @media (min-width: $screen-md-min) { + float: left; + width: 50%; + } + } + .controls { display: flex; width: 400px; diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index d0c4550733c..57c54bf625a 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -19,9 +19,21 @@ class Projects::GroupLinksController < Projects::ApplicationController redirect_to namespace_project_group_links_path(project.namespace, project) end + def update + @group_link = @project.project_group_links.find(params[:id]) + + @group_link.update_attributes(group_link_params) + end + def destroy project.project_group_links.find(params[:id]).destroy redirect_to namespace_project_group_links_path(project.namespace, project) end + + protected + + def group_link_params + params.require(:group_link).permit(:group_access, :expires_at) + end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 42a7e5a2c30..d83e95cf097 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -5,6 +5,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index + @groups = @project.project_group_links.all @project_members = @project.project_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml new file mode 100644 index 00000000000..d3a37847f58 --- /dev/null +++ b/app/views/projects/group_links/update.js.haml @@ -0,0 +1,3 @@ +:plain + var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link, group: @group_link.group))}'); + $("##{dom_id(@group_link.group)} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index e783d8c72c5..9738f369a35 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading + Group members with access to %strong #{@group.name} - group members %span.badge= members.size - if can?(current_user, :admin_group_member, @group) .controls diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml new file mode 100644 index 00000000000..79791af7963 --- /dev/null +++ b/app/views/projects/project_members/_groups.html.haml @@ -0,0 +1,9 @@ +.panel.panel-default + .panel-heading + Groups with access to + %strong #{@project.name} + %span.badge= groups.size + %ul.content-list + - @groups.each do |group_link| + - group = group_link.group + = render 'shared/members/group', group_link: group_link, group: group diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index db6c1194da7..6a8b28d3886 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -3,17 +3,5 @@ Users with access to %strong #{@project.name} %span.badge= members.size - .controls - = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do - .form-group - = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } - = button_tag class: 'btn', title: 'Search' do - = icon("search") %ul.content-list = render partial: 'shared/members/member', collection: members, as: :member - -:javascript - $('form.member-search-form').on('submit', function (event) { - event.preventDefault(); - Turbolinks.visit(this.action + '?' + $(this).serialize()); - }); diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 9d47e7d725c..db8a060d170 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -14,12 +14,16 @@ = render 'shared/members/requests', membership_source: @project, requesters: @requesters - %h5.append-bottom-default - Existing users and groups + .append-bottom-default.clearfix + %h5.pull-left + Existing users and groups + = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form pull-right hidden-xs hidden-sm' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } + = icon("search") + - if @grups + = render 'groups', groups: @groups = render 'team', members: @project_members - if @group = render "group_members", members: @group_members - - - if @project_group_links.any? && @project.allowed_to_share_with_group? - = render "shared_group_members" diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml new file mode 100644 index 00000000000..0502de5210b --- /dev/null +++ b/app/views/shared/members/_group.html.haml @@ -0,0 +1,23 @@ +- group = local_assigns[:group] +- group_link = local_assigns[:group_link] +%li.member{ class: dom_class(group), id: dom_id(group) } + %span{ class: "list-item-name" } + = image_tag group_icon(group), class: "avatar s40", alt: '' + %strong + = link_to group.name, group_path(group) + .cgray + Joined #{time_ago_with_tooltip(group.created_at)} + - if group_link.expires? + · + %span{ class: ('text-warning' if group_link.expires_soon?) } + Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} + .controls.member-controls + = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal' do + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}" + .prepend-left-5.append-right-10.clearable-input.member-form-control + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" + %i.clear-icon.js-clear-input + = link_to icon('trash'), namespace_project_group_link_path(@project.namespace, @project, group_link), + remote: true, + method: :delete, + class: 'btn btn-remove' diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index fd9b688dc20..800badc051a 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -3,43 +3,6 @@ - user = member.user %li.member{ class: dom_class(member), id: dom_id(member) } - - if show_roles - .controls - - if show_controls - - if @project.owner != user - = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - .prepend-left-5.append-right-10.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - %i.clear-icon.js-clear-input - - if !user && can?(current_user, action_member_permission(:admin, member), member.source) - = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), - method: :post, - class: 'btn' - - else - Owner - - - if member.request? && can?(current_user, action_member_permission(:update, member), member) - = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), - method: :post, - class: 'btn btn-success', - title: 'Grant access' - - - if can?(current_user, action_member_permission(:destroy, member), member) - - if current_user == user - = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), - method: :delete, - data: { confirm: leave_confirmation_message(member.source) }, - class: 'btn btn-remove' - - else - = link_to icon('trash'), member, - remote: true, - method: :delete, - data: { confirm: remove_member_message(member) }, - class: 'btn btn-remove', - title: remove_member_title(member) - - %span{ class: ("list-item-name" if show_controls) } - if user = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' @@ -74,3 +37,35 @@ by = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) + - if show_roles + .controls.member-controls + - if show_controls + = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + .prepend-left-5.append-right-10.clearable-input.member-form-control + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + %i.clear-icon.js-clear-input + - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), + method: :post, + class: 'btn' + + - if member.request? && can?(current_user, action_member_permission(:update, member), member) + = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), + method: :post, + class: 'btn btn-success', + title: 'Grant access' + + - if can?(current_user, action_member_permission(:destroy, member), member) + - if current_user == user + = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), + method: :delete, + data: { confirm: leave_confirmation_message(member.source) }, + class: 'btn btn-remove' + - else + = link_to icon('trash'), member, + remote: true, + method: :delete, + data: { confirm: remove_member_message(member) }, + class: 'btn btn-remove', + title: remove_member_title(member) diff --git a/config/routes.rb b/config/routes.rb index 068c92d1400..441f7249aa8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -867,7 +867,7 @@ Rails.application.routes.draw do end end - resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } + resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ } resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do member do -- cgit v1.2.1 From 23993147fbf24e868d33927dc1194b60a106076d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 14:13:49 +0100 Subject: Fixed issue with groups not displaying --- app/controllers/projects/project_members_controller.rb | 14 -------------- app/views/projects/project_members/index.html.haml | 6 ++---- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d83e95cf097..6060ddf025b 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -16,20 +16,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_members = @project_members.order('access_level DESC') - @group = @project.group - - if @group - @group_members = @group.group_members - @group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group) - - if params[:search].present? - users = @group.users.search(params[:search]).to_a - @group_members = @group_members.where(user_id: users) - end - - @group_members = @group_members.order('access_level DESC') - end - @requesters = @project.requesters if can?(current_user, :admin_project, @project) @project_member = @project.project_members.new diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index db8a060d170..42a23057ff1 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -21,9 +21,7 @@ .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } = icon("search") - - if @grups + - if @groups.size > 0 = render 'groups', groups: @groups - = render 'team', members: @project_members - - if @group - = render "group_members", members: @group_members + = render 'team', members: @project_members -- cgit v1.2.1 From e33cda96cb20f47fdde4314f6bb00e43bbf5aeb4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 15:27:42 +0100 Subject: Fixed group members not deleting Combine both group members & project members in project members list --- app/assets/javascripts/project_members.js.es6 | 7 ++++--- app/controllers/projects/project_members_controller.rb | 9 +++++---- app/views/projects/project_members/_team.html.haml | 6 ++++-- app/views/projects/project_members/index.html.haml | 1 + 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index 659c57d8b6c..8b5cb17ac2d 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -8,12 +8,12 @@ } removeListeners() { - $('.project_member').off('ajax:success'); + $('.project_member, .group_member').off('ajax:success'); $('.js-member-update-control').off('change'); } addListeners() { - $('.project_member').on('ajax:success', this.removeRow); + $('.project_member, .group_member').on('ajax:success', this.removeRow); $('.js-member-update-control').on('change', function () { $(this).closest('form') .trigger("submit.rails"); @@ -24,7 +24,8 @@ const $target = $(e.target); if ($target.hasClass('btn-remove')) { - $target.fadeOut(); + console.log('a'); + $target.closest('.member').fadeOut(); } } diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 6060ddf025b..abb92938211 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,15 +6,16 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links.all - @project_members = @project.project_members + @project_members = @project.team.members.all + @project_members_size = @project_members.size + @group_members = @project.group.group_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? - users = @project.users.search(params[:search]).to_a - @project_members = @project_members.where(user_id: users) + @project_members = @project_members.search(params[:search]) end - @project_members = @project_members.order('access_level DESC') + @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 6a8b28d3886..23c35f91b6b 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,6 +2,8 @@ .panel-heading Users with access to %strong #{@project.name} - %span.badge= members.size + %span.badge= @project_members_size %ul.content-list - = render partial: 'shared/members/member', collection: members, as: :member + - members.each do |user| + - member = @project.team.find_member(user.id) + = render 'shared/members/member', member: member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 42a23057ff1..85e512a75f4 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -25,3 +25,4 @@ = render 'groups', groups: @groups = render 'team', members: @project_members + = paginate @project_members, theme: "gitlab" -- cgit v1.2.1 From 843dd24bdf063bb199c841cd6a08643344ae7598 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 16:22:53 +0100 Subject: Mobile improvements Added group name to members row Fixed saving group member --- app/assets/javascripts/project_members.js.es6 | 7 +++- app/assets/stylesheets/framework/lists.scss | 8 +++-- app/assets/stylesheets/pages/members.scss | 42 ++++++++++++++++++---- app/views/groups/group_members/update.js.haml | 4 +-- app/views/projects/project_members/_team.html.haml | 2 +- app/views/shared/members/_group.html.haml | 7 ++-- app/views/shared/members/_member.html.haml | 28 ++++++++++----- 7 files changed, 74 insertions(+), 24 deletions(-) diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index 8b5cb17ac2d..d11467cf6eb 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -1,5 +1,5 @@ ((w) => { - window.gl = window.gl || {}; + w.gl = w.gl || {}; class ProjectMembers { constructor() { @@ -10,6 +10,7 @@ removeListeners() { $('.project_member, .group_member').off('ajax:success'); $('.js-member-update-control').off('change'); + $('.js-edit-member-form').off('ajax:success'); } addListeners() { @@ -17,6 +18,10 @@ $('.js-member-update-control').on('change', function () { $(this).closest('form') .trigger("submit.rails"); + $(this).disable(); + }); + $('.js-edit-member-form').on('ajax:success', function () { + $(this).find('.js-member-update-control').enable(); }); } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index dfdfe4a3c89..272d37763c1 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -128,6 +128,10 @@ ul.content-list { color: $gl-dark-link-color; } + .member-group-link { + color: $blue-normal; + } + .description { p { @include str-truncated; @@ -166,8 +170,8 @@ ul.content-list { .member-controls { float: none; - - @media (min-width: $screen-md-min) { + + @media (min-width: $screen-sm-min) { float: right; } } diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 4bc34ac15df..a69af862348 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -8,24 +8,52 @@ .list-item-name { float: none; - @media (min-width: $screen-md-min) { + @media (min-width: $screen-sm-min) { float: left; width: 50%; } } .controls { - display: flex; - width: 400px; + @media (min-width: $screen-sm-min) { + display: flex; + width: 400px; + max-width: 50%; + } } .form-horizontal { - display: flex; - flex: 1; - margin-top: 3px; + margin-top: 5px; + + @media (min-width: $screen-sm-min) { + display: flex; + flex: 1; + margin-top: 3px; + } + } + + .btn-remove { + width: 100%; + + @media (min-width: $screen-sm-min) { + width: auto; + } + } +} + +.member-form-control { + @media (max-width: $screen-xs-max) { + padding: 5px 0; + margin-left: 0; + margin-right: 0; } - .member-form-control { + @media (min-width: $screen-sm-min) { width: 50%; } } + +.member-access-text { + margin-left: auto; + line-height: 43px; +} diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml index 3be7ed8432c..de8f53b6b52 100644 --- a/app/views/groups/group_members/update.js.haml +++ b/app/views/groups/group_members/update.js.haml @@ -1,3 +1,3 @@ :plain - $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}'); - new gl.MemberExpirationDate(); + var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}'); + $("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 23c35f91b6b..2af9fe0519c 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -6,4 +6,4 @@ %ul.content-list - members.each do |user| - member = @project.team.find_member(user.id) - = render 'shared/members/member', member: member + = render 'shared/members/member', member: member, user: user diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 0502de5210b..5d54195646c 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -17,7 +17,10 @@ .prepend-left-5.append-right-10.clearable-input.member-form-control = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" %i.clear-icon.js-clear-input - = link_to icon('trash'), namespace_project_group_link_path(@project.namespace, @project, group_link), + = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, - class: 'btn btn-remove' + class: 'btn btn-remove' do + %span.visible-xs-block + Delete + = icon('trash', class: 'hidden-xs') diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 800badc051a..4518e84fe34 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -1,6 +1,6 @@ - show_roles = local_assigns.fetch(:show_roles, true) - show_controls = local_assigns.fetch(:show_controls, true) -- user = member.user +- user = local_assigns.fetch(:user, member.user) %li.member{ class: dom_class(member), id: dom_id(member) } %span{ class: ("list-item-name" if show_controls) } @@ -11,12 +11,16 @@ %span.cgray= user.to_reference - if user == current_user - %span.label.label-success It's you + %span.label.label-success.prepend-left-5 It's you - if user.blocked? %label.label.label-danger %strong Blocked + - if member.respond_to?(:group) && !@group + = link_to member.group, class: "member-group-link prepend-left-5" do + = "· #{member.group.name}" + .cgray - if member.request? Requested @@ -40,11 +44,14 @@ - if show_roles .controls.member-controls - if show_controls - = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - .prepend-left-5.append-right-10.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - %i.clear-icon.js-clear-input + - if user != current_user + = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + .prepend-left-5.append-right-10.clearable-input.member-form-control + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + %i.clear-icon.js-clear-input + - else + %span.member-access-text= member.human_access - if !user && can?(current_user, action_member_permission(:admin, member), member.source) = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, @@ -63,9 +70,12 @@ data: { confirm: leave_confirmation_message(member.source) }, class: 'btn btn-remove' - else - = link_to icon('trash'), member, + = link_to member, remote: true, method: :delete, data: { confirm: remove_member_message(member) }, class: 'btn btn-remove', - title: remove_member_title(member) + title: remove_member_title(member) do + %span.visible-xs-block + Delete + = icon('trash', class: 'hidden-xs') -- cgit v1.2.1 From 49a31e64b76b351c1bad91459991a69f0e0fb296 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 17:04:46 +0100 Subject: Removed console log Hides time on mobile --- app/assets/javascripts/project_members.js.es6 | 1 - app/views/shared/members/_member.html.haml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index d11467cf6eb..f525bdbb1ea 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -29,7 +29,6 @@ const $target = $(e.target); if ($target.hasClass('btn-remove')) { - console.log('a'); $target.closest('.member').fadeOut(); } } diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 4518e84fe34..ab4f1f1382b 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -21,7 +21,7 @@ = link_to member.group, class: "member-group-link prepend-left-5" do = "· #{member.group.name}" - .cgray + .hidden-xs.cgray - if member.request? Requested = time_ago_with_tooltip(member.requested_at) -- cgit v1.2.1 From 15a3111a6663894d952103e7395f2f56408f88ce Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 09:04:28 +0100 Subject: Mobile spacing improvements --- app/assets/stylesheets/framework/forms.scss | 4 --- app/assets/stylesheets/pages/groups.scss | 12 -------- app/assets/stylesheets/pages/members.scss | 36 ++++++++++++++++++++++ app/views/projects/project_members/index.html.haml | 7 +++-- app/views/shared/members/_group.html.haml | 2 +- 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 37ff7e22ed1..d1f1a372c06 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -125,7 +125,3 @@ label { border-right: 0; } } - -.help-block { - margin-bottom: 0; -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index a27f7a2fd77..cc1c0249df3 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -1,15 +1,3 @@ -.member-search-form { - input[type='search'] { - width: 225px; - vertical-align: bottom; - - @media (max-width: $screen-xs-max) { - width: 100px; - vertical-align: bottom; - } - } -} - .milestone-row { @include str-truncated(90%); } diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index a69af862348..a7f1324f69a 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -57,3 +57,39 @@ margin-left: auto; line-height: 43px; } + +.member.existing-title { + @media (min-width: $screen-sm-min) { + float: left; + } +} + +.member-search-form { + position: relative; + + @media (min-width: $screen-sm-min) { + float: right; + } + + .form-control { + width: 100%; + padding-right: 35px; + + @media (min-width: $screen-sm-min) { + width: 350px; + } + } +} + +.member-search-btn { + position: absolute; + right: 0; + top: 0; + height: 35px; + padding-left: 10px; + padding-right: 10px; + color: $gray-darkest; + background: transparent; + border: 0; + outline: 0; +} diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 85e512a75f4..abe10433387 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -15,12 +15,13 @@ = render 'shared/members/requests', membership_source: @project, requesters: @requesters .append-bottom-default.clearfix - %h5.pull-left + %h5.member.existing-title Existing users and groups - = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form pull-right hidden-xs hidden-sm' do + = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } - = icon("search") + %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } + = icon("search") - if @groups.size > 0 = render 'groups', groups: @groups diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 5d54195646c..e545aec80a9 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -12,7 +12,7 @@ %span{ class: ('text-warning' if group_link.expires_soon?) } Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls - = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal' do + = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}" .prepend-left-5.append-right-10.clearable-input.member-form-control = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" -- cgit v1.2.1 From e477ad44565dbe69e3f0200f4f4f7bebbd48cb15 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 09:25:53 +0100 Subject: Removes row from dom when deleting Fixed spacing with buttons Disables group form when user doesnt have correct permissions --- app/assets/javascripts/project_members.js.es6 | 5 ++++- .../projects/project_members_controller.rb | 7 ++----- app/models/project_team.rb | 8 ++++---- app/views/shared/members/_group.html.haml | 21 +++++++++++---------- app/views/shared/members/_member.html.haml | 10 +++++----- app/views/shared/members/_requests.html.haml | 2 +- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index f525bdbb1ea..56bc98d1076 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -29,7 +29,10 @@ const $target = $(e.target); if ($target.hasClass('btn-remove')) { - $target.closest('.member').fadeOut(); + $target.closest('.member') + .fadeOut(function () { + $(this).remove(); + }); } } diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index abb92938211..cd31653698c 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -5,11 +5,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index - @groups = @project.project_group_links.all - @project_members = @project.team.members.all + @groups = @project.project_group_links + @project_members = @project.team.members(can?(current_user, :admin_project, @project)) @project_members_size = @project_members.size - @group_members = @project.group.group_members - @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? @project_members = @project_members.search(params[:search]) @@ -20,7 +18,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @requesters = @project.requesters if can?(current_user, :admin_project, @project) @project_member = @project.project_members.new - @project_group_links = @project.project_group_links end def create diff --git a/app/models/project_team.rb b/app/models/project_team.rb index ab6ea2aae36..57925a0861a 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -52,8 +52,8 @@ class ProjectTeam ProjectMember.truncate_team(project) end - def members - @members ||= fetch_members + def members(non_invite) + @members ||= fetch_members(nil, non_invite) end alias_method :users, :members @@ -197,7 +197,7 @@ class ProjectTeam access.each { |key, value| access[key] = [value, capped_access_level].min } end - def fetch_members(level = nil) + def fetch_members(level = nil, non_invite = false) project_members = project.members group_members = group ? group.members : [] invited_members = [] @@ -236,7 +236,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && !non_invite user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index e545aec80a9..19b58ef20ae 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -13,14 +13,15 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}" - .prepend-left-5.append-right-10.clearable-input.member-form-control - = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) + .prepend-left-5.clearable-input.member-form-control + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) %i.clear-icon.js-clear-input - = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), - remote: true, - method: :delete, - class: 'btn btn-remove' do - %span.visible-xs-block - Delete - = icon('trash', class: 'hidden-xs') + - if can?(current_user, action_member_permission(:admin, group), group) + = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), + remote: true, + method: :delete, + class: 'btn btn-remove prepend-left-10' do + %span.visible-xs-block + Delete + = icon('trash', class: 'hidden-xs') diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index ab4f1f1382b..2d4853eef92 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -47,7 +47,7 @@ - if user != current_user = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - .prepend-left-5.append-right-10.clearable-input.member-form-control + .prepend-left-5.clearable-input.member-form-control = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) %i.clear-icon.js-clear-input - else @@ -55,12 +55,12 @@ - if !user && can?(current_user, action_member_permission(:admin, member), member.source) = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, - class: 'btn' + class: 'btn btn-default prepend-left-10' - if member.request? && can?(current_user, action_member_permission(:update, member), member) = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), method: :post, - class: 'btn btn-success', + class: 'btn btn-success prepend-left-10', title: 'Grant access' - if can?(current_user, action_member_permission(:destroy, member), member) @@ -68,13 +68,13 @@ = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), method: :delete, data: { confirm: leave_confirmation_message(member.source) }, - class: 'btn btn-remove' + class: 'btn btn-remove prepend-left-10' - else = link_to member, remote: true, method: :delete, data: { confirm: remove_member_message(member) }, - class: 'btn btn-remove', + class: 'btn btn-remove prepend-left-10', title: remove_member_title(member) do %span.visible-xs-block Delete diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml index 40b39e850b0..10050adfda5 100644 --- a/app/views/shared/members/_requests.html.haml +++ b/app/views/shared/members/_requests.html.haml @@ -1,8 +1,8 @@ - if requesters.any? .panel.panel-default .panel-heading + Users requesting access to %strong= membership_source.name - access requests %span.badge= requesters.size %ul.content-list = render partial: 'shared/members/member', collection: requesters, as: :member -- cgit v1.2.1 From b3d75ac5135130522f253d4b09f72a7c0a8e2f80 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 09:28:25 +0100 Subject: Return 403 if user can't update group --- app/controllers/projects/group_links_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 57c54bf625a..b5e314dced3 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -21,6 +21,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) + return render_403 unless can?(current_user, action_member_permission(:admin, @group_link.group), @group_link.group) @group_link.update_attributes(group_link_params) end -- cgit v1.2.1 From cdc55db3452ca82f0dbdcdb631a1fc48abdf1f84 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 10:13:49 +0100 Subject: Fixed members error --- app/controllers/projects/project_members_controller.rb | 2 +- app/models/project_team.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index cd31653698c..617dd9823b9 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,7 +6,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - @project_members = @project.team.members(can?(current_user, :admin_project, @project)) + @project_members = @project.team.members(!can?(current_user, :admin_project, @project)) @project_members_size = @project_members.size if params[:search].present? diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 57925a0861a..a58c56288dd 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -52,7 +52,7 @@ class ProjectTeam ProjectMember.truncate_team(project) end - def members(non_invite) + def members(non_invite = false) @members ||= fetch_members(nil, non_invite) end alias_method :users, :members @@ -236,7 +236,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && !non_invite + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && non_invite user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) -- cgit v1.2.1 From 3e19f1976f9a13fc1b13ec49b3ce31c3e114a454 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 10:50:06 +0100 Subject: Fixed issue with invited users not showing up --- app/controllers/projects/project_members_controller.rb | 14 +++++++++++++- app/models/project_team.rb | 8 ++++---- app/views/projects/project_members/_team.html.haml | 5 ++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 617dd9823b9..2175a5d8dcb 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,7 +6,19 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - @project_members = @project.team.members(!can?(current_user, :admin_project, @project)) + + members = [] + project_members = @project.project_members + project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) + members << project_members.pluck(:id) + + if @project.group + group_members = @project.group.group_members + group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) + members << group_members.pluck(:id) + end + + @project_members = Member.where(id: members) @project_members_size = @project_members.size if params[:search].present? diff --git a/app/models/project_team.rb b/app/models/project_team.rb index a58c56288dd..ab6ea2aae36 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -52,8 +52,8 @@ class ProjectTeam ProjectMember.truncate_team(project) end - def members(non_invite = false) - @members ||= fetch_members(nil, non_invite) + def members + @members ||= fetch_members end alias_method :users, :members @@ -197,7 +197,7 @@ class ProjectTeam access.each { |key, value| access[key] = [value, capped_access_level].min } end - def fetch_members(level = nil, non_invite = false) + def fetch_members(level = nil) project_members = project.members group_members = group ? group.members : [] invited_members = [] @@ -236,7 +236,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && non_invite + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 2af9fe0519c..867cb2b97e4 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -4,6 +4,5 @@ %strong #{@project.name} %span.badge= @project_members_size %ul.content-list - - members.each do |user| - - member = @project.team.find_member(user.id) - = render 'shared/members/member', member: member, user: user + - members.each do |member| + = render 'shared/members/member', member: member -- cgit v1.2.1 From 931d09f481d5e174a984c6f874e67273ba2864f0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 10:52:17 +0100 Subject: Fixed search --- .../projects/project_members_controller.rb | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 2175a5d8dcb..7581833eacc 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,21 +10,30 @@ class Projects::ProjectMembersController < Projects::ApplicationController members = [] project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) + + if params[:search].present? + users = @project.users.search(params[:search]).to_a + project_members = project_members.where(user_id: users) + end + members << project_members.pluck(:id) - if @project.group - group_members = @project.group.group_members + @group = @project.group + if @group + group_members = @group.group_members group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) + + if params[:search].present? + users = @group.users.search(params[:search]).to_a + group_members = group_members.where(user_id: users) + end + members << group_members.pluck(:id) end @project_members = Member.where(id: members) @project_members_size = @project_members.size - if params[:search].present? - @project_members = @project_members.search(params[:search]) - end - @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) -- cgit v1.2.1 From 999f18480511d81b1499b502cbc89a5b34e54544 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 13:35:43 +0100 Subject: Tests update --- app/views/projects/project_members/_groups.html.haml | 2 +- .../project_members/_new_project_member.html.haml | 2 +- app/views/shared/members/_member.html.haml | 2 ++ features/steps/admin/projects.rb | 2 +- features/steps/group/members.rb | 4 ++-- features/steps/project/team_management.rb | 15 +++++++-------- .../groups/members/owner_manages_access_requests_spec.rb | 2 +- .../members/master_manages_access_requests_spec.rb | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index 79791af7963..340e4cd06b8 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -1,4 +1,4 @@ -.panel.panel-default +.panel.panel-default.project-members-groups .panel-heading Groups with access to %strong #{@project.name} diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index c0b187fb460..26e06a14c07 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,4 +1,4 @@ -= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project) do |f| += form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f| .row .col-md-4.col-lg-6 = users_select_tag(:user_ids, multiple: true, class: "input-full", scope: :all, email_user: true) diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 2d4853eef92..2f98eeff658 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -79,3 +79,5 @@ %span.visible-xs-block Delete = icon('trash', class: 'hidden-xs') + - else + %span.member-access-text= member.human_access diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb index d77945a6b9c..2b8cd030ace 100644 --- a/features/steps/admin/projects.rb +++ b/features/steps/admin/projects.rb @@ -70,7 +70,7 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps select "Developer", from: "access_level" end - click_button "Add users to project" + click_button "Add to project" end step 'I should see current user as "Developer"' do diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb index e9b45823c67..4aec3d03ef6 100644 --- a/features/steps/group/members.rb +++ b/features/steps/group/members.rb @@ -1,4 +1,5 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps + include WaitForAjax include SharedAuthentication include SharedPaths include SharedGroup @@ -116,9 +117,8 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps member = mary_jane_member page.within "#group_member_#{member.id}" do - click_button 'Edit' select 'Developer', from: "member_access_level_#{member.id}" - click_on 'Save' + wait_for_ajax end end diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index e920f5a706b..49821b85922 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -22,7 +22,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps select2(user.id, from: "#user_ids", multiple: true) select "Reporter", from: "access_level" end - click_button "Add users to project" + click_button "Add to project" end step 'I should see "Mike" in team list as "Reporter"' do @@ -36,10 +36,10 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps step 'I select "sjobs@apple.com" as "Reporter"' do page.within ".users-project-form" do - select2("sjobs@apple.com", from: "#user_ids", multiple: true) + find('#user_ids', visible: false).set('sjobs@apple.com') select "Reporter", from: "access_level" end - click_button "Add users to project" + click_button "Add to project" end step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do @@ -65,9 +65,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps user = User.find_by(name: 'Dmitriy') project_member = project.project_members.find_by(user_id: user.id) page.within "#project_member_#{project_member.id}" do - click_button 'Edit' select "Reporter", from: "member_access_level_#{project_member.id}" - click_button "Save" end end @@ -144,8 +142,9 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end step 'I should see "Opensource" group user listing' do - expect(page).to have_content("Shared with OpenSource group, members with Master role (2)") - expect(page).to have_content(@os_user1.name) - expect(page).to have_content(@os_user2.name) + page.within '.project-members-groups' do + expect(page).to have_content('OpenSource') + expect(find('select').value).to eq('40') + end end end diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index 10d3713f19f..d811b05b0c3 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -41,7 +41,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do def expect_visible_access_request(group, user) expect(group.requesters.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{group.name} access requests 1" + expect(page).to have_content "Users requesting access to #{group.name} 1" expect(page).to have_content user.name end end diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index f7fcd9b6731..d15376931c3 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -41,7 +41,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do def expect_visible_access_request(project, user) expect(project.requesters.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{project.name} access requests 1" + expect(page).to have_content "Users requesting access to #{project.name} 1" expect(page).to have_content user.name end end -- cgit v1.2.1 From a56216c8bd1ef82c09c7ce39596e4b6436ebb7fa Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 14:06:20 +0100 Subject: Added import button back in --- app/views/projects/project_members/index.html.haml | 4 ++-- features/steps/project/team_management.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index abe10433387..d289d75454d 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,15 +1,15 @@ - page_title "Members" .project-members-page.js-project-members-page.prepend-top-default - %h4 + %h4.clearfix Members + = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project" %hr - if can?(current_user, :admin_project_member, @project) .project-members-new.append-bottom-default %h5.clearfix Add new user to %strong= @project.name - -# = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right", title: "Import members from another project" = render "new_project_member" = render 'shared/members/requests', membership_source: @project, requesters: @requesters diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index 49821b85922..b21d0849ad1 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -110,7 +110,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end step 'I click link "Import team from another project"' do - click_link "Import members from another project" + click_link "Import" end When 'I submit "Website" project for import team' do -- cgit v1.2.1 From ccf76831da422298242ce3d8d11f72ab50454c85 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 14:19:11 +0100 Subject: Updated groups member UI to match --- app/assets/javascripts/dispatcher.js | 4 +- app/assets/javascripts/groups.js | 13 ------- app/assets/javascripts/members.js.es6 | 45 ++++++++++++++++++++++ app/assets/javascripts/project_members.js.es6 | 45 ---------------------- .../group_members/_new_group_member.html.haml | 35 ++++++++--------- app/views/groups/group_members/index.html.haml | 40 +++++++++---------- app/views/projects/project_members/index.html.haml | 2 +- 7 files changed, 81 insertions(+), 103 deletions(-) delete mode 100644 app/assets/javascripts/groups.js create mode 100644 app/assets/javascripts/members.js.es6 delete mode 100644 app/assets/javascripts/project_members.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index c95aaf61443..da3757d8992 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -127,12 +127,12 @@ break; case 'groups:group_members:index': new gl.MemberExpirationDate(); - new GroupMembers(); + new gl.Members(); new UsersSelect(); break; case 'projects:project_members:index': new gl.MemberExpirationDate(); - new gl.ProjectMembers(); + new gl.Members(); new UsersSelect(); break; case 'groups:new': diff --git a/app/assets/javascripts/groups.js b/app/assets/javascripts/groups.js deleted file mode 100644 index 4382dd6860f..00000000000 --- a/app/assets/javascripts/groups.js +++ /dev/null @@ -1,13 +0,0 @@ -(function() { - this.GroupMembers = (function() { - function GroupMembers() { - $('li.group_member').bind('ajax:success', function() { - return $(this).fadeOut(); - }); - } - - return GroupMembers; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 new file mode 100644 index 00000000000..fa259520810 --- /dev/null +++ b/app/assets/javascripts/members.js.es6 @@ -0,0 +1,45 @@ +((w) => { + w.gl = w.gl || {}; + + class Members { + constructor() { + this.removeListeners(); + this.addListeners(); + } + + removeListeners() { + $('.project_member, .group_member').off('ajax:success'); + $('.js-member-update-control').off('change'); + $('.js-edit-member-form').off('ajax:success'); + } + + addListeners() { + $('.project_member, .group_member').on('ajax:success', this.removeRow); + $('.js-member-update-control').on('change', function () { + $(this).closest('form') + .trigger("submit.rails"); + $(this).disable(); + }); + $('.js-edit-member-form').on('ajax:success', function () { + $(this).find('.js-member-update-control').enable(); + }); + } + + removeRow(e) { + const $target = $(e.target); + + if ($target.hasClass('btn-remove')) { + $target.closest('.member') + .fadeOut(function () { + $(this).remove(); + }); + } + } + + submitForm() { + + } + } + + gl.Members = Members; +})(window); diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 deleted file mode 100644 index 56bc98d1076..00000000000 --- a/app/assets/javascripts/project_members.js.es6 +++ /dev/null @@ -1,45 +0,0 @@ -((w) => { - w.gl = w.gl || {}; - - class ProjectMembers { - constructor() { - this.removeListeners(); - this.addListeners(); - } - - removeListeners() { - $('.project_member, .group_member').off('ajax:success'); - $('.js-member-update-control').off('change'); - $('.js-edit-member-form').off('ajax:success'); - } - - addListeners() { - $('.project_member, .group_member').on('ajax:success', this.removeRow); - $('.js-member-update-control').on('change', function () { - $(this).closest('form') - .trigger("submit.rails"); - $(this).disable(); - }); - $('.js-edit-member-form').on('ajax:success', function () { - $(this).find('.js-member-update-control').enable(); - }); - } - - removeRow(e) { - const $target = $(e.target); - - if ($target.hasClass('btn-remove')) { - $target.closest('.member') - .fadeOut(function () { - $(this).remove(); - }); - } - } - - submitForm() { - - } - } - - gl.ProjectMembers = ProjectMembers; -})(window); diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 2fb3190ab11..2987befd2a4 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -1,27 +1,22 @@ -= form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f| - .form-group - = f.label :user_ids, "People", class: 'control-label' - .col-sm-10 - = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true) - .help-block += form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f| + .row + .col-md-4.col-lg-6 + = users_select_tag(:user_ids, multiple: true, class: 'input-full', scope: :all, email_user: true) + .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. - .form-group - = f.label :access_level, "Group Access", class: 'control-label' - .col-sm-10 - = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2" - .help-block - Read more about role permissions - %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + .col-md-3.col-lg-2 + = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select" + .help-block.append-bottom-10 + = link_to "Read more", help_page_path("user/permissions"), class: "vlink" + about role permissions - .form-group - = f.label :expires_at, 'Access expiration date', class: 'control-label' - .col-sm-10 + .col-md-3.col-lg-2 .clearable-input - = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date' + = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input - .help-block + .help-block.append-bottom-10 On this date, the user(s) will automatically lose access to this group and all of its projects. - .form-actions - = f.submit 'Add users to group', class: "btn btn-create" + .col-md-2 + = f.submit 'Add to group', class: "btn btn-create btn-block" diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index f789796e942..d2c7ec2e821 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,35 +1,31 @@ - page_title "Members" -.group-members-page.prepend-top-default +.project-members-page.prepend-top-default + %h4 + Members + %hr - if can?(current_user, :admin_group_member, @group) - .panel.panel-default - .panel-heading - Add new user to group - .panel-body - %p.light - Members of group have access to all group projects. - .new-group-member-holder - = render "new_group_member" + .project-members-new.append-bottom-default + %h5.clearfix + Add new user to + %strong= @group.name + = render "new_group_member" = render 'shared/members/requests', membership_source: @group, requesters: @requesters + .append-bottom-default.clearfix + %h5.member.existing-title + Existing users + = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } + %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } + = icon("search") .panel.panel-default .panel-heading + Users with access to %strong #{@group.name} - group members %span.badge= @members.total_count - .controls - = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do - .form-group - = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } - = button_tag class: 'btn', title: 'Search' do - = icon("search") %ul.content-list = render partial: 'shared/members/member', collection: @members, as: :member = paginate @members, theme: 'gitlab' - -:javascript - $('form.member-search-form').on('submit', function(event) { - event.preventDefault(); - Turbolinks.visit(this.action + '?' + $(this).serialize()); - }); diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index d289d75454d..a90de32bd47 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,6 +1,6 @@ - page_title "Members" -.project-members-page.js-project-members-page.prepend-top-default +.project-members-page.prepend-top-default %h4.clearfix Members = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project" -- cgit v1.2.1 From 2c3fa33ca2e75792d1027eb73e2f69fed67bc435 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 15:10:22 +0100 Subject: Updated some specs Fixed issue with group name not showing --- .../projects/project_members_controller.rb | 19 +++++++++---------- app/views/projects/project_members/_team.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 4 ++-- app/views/shared/members/_member.html.haml | 7 ++++--- features/steps/group/members.rb | 8 ++++---- .../master_adds_member_with_expiration_date_spec.rb | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 7581833eacc..bf6ac25266a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -7,7 +7,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - members = [] project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) @@ -16,25 +15,25 @@ class Projects::ProjectMembersController < Projects::ApplicationController project_members = project_members.where(user_id: users) end - members << project_members.pluck(:id) + members_ids = project_members.pluck(:id) - @group = @project.group - if @group - group_members = @group.group_members + group = @project.group + if group + group_members = group.group_members group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? - users = @group.users.search(params[:search]).to_a + users = group.users.search(params[:search]).to_a group_members = group_members.where(user_id: users) end - members << group_members.pluck(:id) + members_ids << group_members.pluck(:id) end - @project_members = Member.where(id: members) - @project_members_size = @project_members.size + @members = Member.where(id: members_ids.flatten) + @members_size = @members.size - @project_members = @project_members.page(params[:page]) + @members = @members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 867cb2b97e4..d9799033e17 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,7 +2,7 @@ .panel-heading Users with access to %strong #{@project.name} - %span.badge= @project_members_size + %span.badge= @members_size %ul.content-list - members.each do |member| = render 'shared/members/member', member: member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index a90de32bd47..80882d0c11c 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -25,5 +25,5 @@ - if @groups.size > 0 = render 'groups', groups: @groups - = render 'team', members: @project_members - = paginate @project_members, theme: "gitlab" + = render 'team', members: @members + = paginate @members, theme: "gitlab" diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 2f98eeff658..6f8c3c4da2e 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -1,6 +1,7 @@ - show_roles = local_assigns.fetch(:show_roles, true) - show_controls = local_assigns.fetch(:show_controls, true) - user = local_assigns.fetch(:user, member.user) +- source = member.source %li.member{ class: dom_class(member), id: dom_id(member) } %span{ class: ("list-item-name" if show_controls) } @@ -17,9 +18,9 @@ %label.label.label-danger %strong Blocked - - if member.respond_to?(:group) && !@group - = link_to member.group, class: "member-group-link prepend-left-5" do - = "· #{member.group.name}" + - if source.instance_of?(Group) && !@group + = link_to source, class: "member-group-link prepend-left-5" do + = "· #{source.name}" .hidden-xs.cgray - if member.request? diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb index 4aec3d03ef6..e54add1d568 100644 --- a/features/steps/group/members.rb +++ b/features/steps/group/members.rb @@ -14,7 +14,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps select "Reporter", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I select "Mike" as "Master"' do @@ -25,7 +25,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps select "Master", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I should see "Mike" in team list as "Reporter"' do @@ -48,7 +48,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps select "Reporter", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do @@ -67,7 +67,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps select "Reporter", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I should see user "John Doe" in team list' do diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index 430c384ac2e..27a83fdcd1f 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' feature 'Projects > Members > Master adds member with expiration date', feature: true, js: true do + include WaitForAjax include Select2Helper include ActiveSupport::Testing::TimeHelpers @@ -20,7 +21,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature: page.within '.users-project-form' do select2(new_member.id, from: '#user_ids', multiple: true) fill_in 'expires_at', with: '2016-08-10' - click_on 'Add users to project' + click_on 'Add to project' end page.within '.project_member:first-child' do @@ -35,9 +36,8 @@ feature 'Projects > Members > Master adds member with expiration date', feature: visit namespace_project_project_members_path(project.namespace, project) page.within '.project_member:first-child' do - click_on 'Edit' - fill_in 'Access expiration date', with: '2016-08-09' - click_on 'Save' + find('.js-access-expiration-date').set '2016-08-09' + wait_for_ajax expect(page).to have_content('Expires in 3 days') end end -- cgit v1.2.1 From 638376c35494860936bf2858c01115dc4afe0bfe Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 15:44:46 +0100 Subject: Fixed group tests --- features/steps/admin/groups.rb | 2 +- features/steps/group/members.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 0c89a3db9ad..9396a76f0a2 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -105,7 +105,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps select "Developer", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I should see current user as "Developer"' do diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb index e54add1d568..cefc55d07ab 100644 --- a/features/steps/group/members.rb +++ b/features/steps/group/members.rb @@ -109,7 +109,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps step 'I search for \'Mary\' member' do page.within '.member-search-form' do fill_in 'search', with: 'Mary' - click_button 'Search' + find('.member-search-btn').click end end -- cgit v1.2.1 From 97a51817bf1e6b0504bb84b686daf7e931ded2da Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 17:39:16 +0100 Subject: Fixed error when updating groups --- app/controllers/projects/group_links_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index b5e314dced3..3574ecf2811 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -21,7 +21,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - return render_403 unless can?(current_user, action_member_permission(:admin, @group_link.group), @group_link.group) + return render_403 unless can?(current_user, :admin_group, @group_link.group) @group_link.update_attributes(group_link_params) end -- cgit v1.2.1 From e747626fad5c0e675d6a5cd5b6fcd482f10dad90 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 09:24:47 +0100 Subject: Added test for updating groups permissions --- app/controllers/projects/group_links_controller.rb | 2 +- app/views/shared/members/_group.html.haml | 6 ++-- spec/features/projects/members/group_links_spec.rb | 37 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 spec/features/projects/members/group_links_spec.rb diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 3574ecf2811..7b4c39cdb8f 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -21,7 +21,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - return render_403 unless can?(current_user, :admin_group, @group_link.group) + return render_403 unless can?(current_user, :admin_project_member, @project) @group_link.update_attributes(group_link_params) end diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 19b58ef20ae..56d31a949ff 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -13,11 +13,11 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) .prepend-left-5.clearable-input.member-form-control - = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) %i.clear-icon.js-clear-input - - if can?(current_user, action_member_permission(:admin, group), group) + - if can?(current_user, :admin_project_member, @project) = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb new file mode 100644 index 00000000000..3d59df20197 --- /dev/null +++ b/spec/features/projects/members/group_links_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +feature 'Projects > Members > Anonymous user sees members', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:group) { create(:group, :public) } + let(:project) { create(:empty_project, :public) } + + background do + project.team << [user, :master] + @group_link = create(:project_group_link, project: project, group: group) + + login_as(user) + visit namespace_project_project_members_path(project.namespace, project) + end + + it 'updates group access level' do + select 'Guest', from: "member_access_level_#{group.id}" + wait_for_ajax + + visit namespace_project_project_members_path(project.namespace, project) + + expect(page).to have_select("member_access_level_#{group.id}", selected: 'Guest') + end + + it 'updates expiry date' do + tomorrow = Date.today + 3 + + fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F") + wait_for_ajax + + page.within(first('li.member')) do + expect(page).to have_content('Expires in 3 days') + end + end +end -- cgit v1.2.1 From fe71edc3336ae662997ebbad3b4c46b2a2b4927c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 15:11:45 +0100 Subject: JS update --- app/assets/javascripts/members.js.es6 | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index fa259520810..7986987e49a 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -15,14 +15,8 @@ addListeners() { $('.project_member, .group_member').on('ajax:success', this.removeRow); - $('.js-member-update-control').on('change', function () { - $(this).closest('form') - .trigger("submit.rails"); - $(this).disable(); - }); - $('.js-edit-member-form').on('ajax:success', function () { - $(this).find('.js-member-update-control').enable(); - }); + $('.js-member-update-control').on('change', this.formSubmit); + $('.js-edit-member-form').on('ajax:success', this.formSuccess); } removeRow(e) { @@ -36,8 +30,16 @@ } } - submitForm() { + formSubmit() { + const $this = $(this); + $this.disable() + .closest('form') + .trigger("submit.rails"); + } + + formSuccess() { + $(this).find('.js-member-update-control').enable(); } } -- cgit v1.2.1 From 3354bfd8f92ba034816890f0ea89bca630405103 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 15:45:23 +0100 Subject: CSS cleanup --- app/assets/stylesheets/framework/selects.scss | 4 ---- app/assets/stylesheets/pages/members.scss | 10 +++------- app/views/groups/group_members/_new_group_member.html.haml | 2 +- app/views/groups/group_members/index.html.haml | 2 +- .../projects/project_members/_new_project_member.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 2 +- 6 files changed, 7 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 746ab89abd2..b309e2ad9f4 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -191,10 +191,6 @@ &.input-clamp { max-width: 100%; } - - &.input-full { - width: 100%; - } } .select2-highlighted { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index a7f1324f69a..72f31cb1168 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -1,9 +1,3 @@ -.project-members-new { - > h5 { - font-weight: normal; - } -} - .member { .list-item-name { float: none; @@ -16,6 +10,7 @@ .controls { @media (min-width: $screen-sm-min) { + display: -webkit-flex; display: flex; width: 400px; max-width: 50%; @@ -26,8 +21,9 @@ margin-top: 5px; @media (min-width: $screen-sm-min) { + display: -webkit-flex; display: flex; - flex: 1; + width: 100%; margin-top: 3px; } } diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 2987befd2a4..b185b81db7f 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -1,7 +1,7 @@ = form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f| .row .col-md-4.col-lg-6 - = users_select_tag(:user_ids, multiple: true, class: 'input-full', scope: :all, email_user: true) + = users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true) .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index d2c7ec2e821..ebf9aca7700 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -6,7 +6,7 @@ %hr - if can?(current_user, :admin_group_member, @group) .project-members-new.append-bottom-default - %h5.clearfix + %p.clearfix Add new user to %strong= @group.name = render "new_group_member" diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 26e06a14c07..79dcd7a6ee9 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,7 +1,7 @@ = form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f| .row .col-md-4.col-lg-6 - = users_select_tag(:user_ids, multiple: true, class: "input-full", scope: :all, email_user: true) + = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true) .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 80882d0c11c..86b2752cc0b 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -7,7 +7,7 @@ %hr - if can?(current_user, :admin_project_member, @project) .project-members-new.append-bottom-default - %h5.clearfix + %p.clearfix Add new user to %strong= @project.name = render "new_project_member" -- cgit v1.2.1 From 68c7b52364307e26ef7a85b80aa11242abbfa5b6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 17:14:42 +0100 Subject: Fixed fields not being sent --- app/assets/javascripts/members.js.es6 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 7986987e49a..12d212ca185 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -33,9 +33,10 @@ formSubmit() { const $this = $(this); - $this.disable() - .closest('form') + $this.closest('form') .trigger("submit.rails"); + + $this.disable(); } formSuccess() { -- cgit v1.2.1 From 11c0c6509251a280f46c6be74da64a1cd7a5e190 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 10:06:13 +0100 Subject: Expires in test update --- spec/features/projects/members/group_links_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index 3d59df20197..2085e875f12 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -31,7 +31,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t wait_for_ajax page.within(first('li.member')) do - expect(page).to have_content('Expires in 3 days') + expect(page).to have_content('Expires in') end end end -- cgit v1.2.1 From 73c4da1780c5086543eb998d5bc9cbd632ef6576 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 16:20:20 +0100 Subject: Fixed removing groups --- app/controllers/projects/group_links_controller.rb | 7 ++++++- app/views/shared/members/_group.html.haml | 3 ++- spec/features/projects/members/group_links_spec.rb | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 7b4c39cdb8f..7b6f07465e0 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -29,7 +29,12 @@ class Projects::GroupLinksController < Projects::ApplicationController def destroy project.project_group_links.find(params[:id]).destroy - redirect_to namespace_project_group_links_path(project.namespace, project) + respond_to do |format| + format.html do + redirect_to namespace_project_group_links_path(project.namespace, project) + end + format.js { head :ok } + end end protected diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 56d31a949ff..171a388b233 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -1,6 +1,6 @@ - group = local_assigns[:group] - group_link = local_assigns[:group_link] -%li.member{ class: dom_class(group), id: dom_id(group) } +%li.member.group_member{ id: "group_member_#{group_link.id}" } %span{ class: "list-item-name" } = image_tag group_icon(group), class: "avatar s40", alt: '' %strong @@ -21,6 +21,7 @@ = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, + data: { confirm: "Are you sure you want to remove #{group.name}?" }, class: 'btn btn-remove prepend-left-10' do %span.visible-xs-block Delete diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index 2085e875f12..7870bc663b1 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -34,4 +34,13 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t expect(page).to have_content('Expires in') end end + + it 'deletes group link' do + page.within(first('.group_member')) do + find('.btn-remove').click + end + wait_for_ajax + + expect(page).not_to have_selector('.group_member') + end end -- cgit v1.2.1 From 2b41db9215f322ba61113a7bef2f49da157bbd53 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 16:48:48 +0100 Subject: Search project groups --- .../projects/project_members_controller.rb | 49 ++++++++++++++++++---- spec/features/projects/members/group_links_spec.rb | 20 +++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index bf6ac25266a..ac83377148a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,23 +10,30 @@ class Projects::ProjectMembersController < Projects::ApplicationController project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) - if params[:search].present? - users = @project.users.search(params[:search]).to_a - project_members = project_members.where(user_id: users) - end - - members_ids = project_members.pluck(:id) - group = @project.group + if group group_members = group.group_members group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) + end + + if params[:search].present? + groups_id = @groups.pluck(:group_id) + groups = Group.where(id: groups_id).search(params[:search]).to_a + @groups = @project.project_group_links.where(group_id: groups) + + users = @project.users.search(params[:search]).to_a + project_members = project_members.where(user_id: users) - if params[:search].present? + if group_members users = group.users.search(params[:search]).to_a group_members = group_members.where(user_id: users) end + end + members_ids = project_members.pluck(:id) + + if group_members members_ids << group_members.pluck(:id) end @@ -48,6 +55,17 @@ class Projects::ProjectMembersController < Projects::ApplicationController current_user: current_user ) + group_ids = params[:group_ids].split(',') + groups = Group.where(id: group_ids) + + groups.each do |group| + project.project_group_links.create( + group: group, + group_access: params[:access_level], + expires_at: params[:expires_at] + ) + end + redirect_to namespace_project_project_members_path(@project.namespace, @project) end @@ -101,6 +119,21 @@ class Projects::ProjectMembersController < Projects::ApplicationController notice: notice) end + def options + users = User.all + users = users.search(params[:search]) if params[:search].present? + users = users.page(1) + + groups = Group.all + groups = groups.search(params[:search]) if params[:search].present? + groups = groups.page(1) + + render json: { + Groups: groups.as_json(only: [:id, :name], methods: [:avatar_url]), + Users: users.as_json(only: [:id, :name, :username], methods: [:avatar_url]), + } + end + protected def member_params diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index 7870bc663b1..14ab7541fad 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -43,4 +43,24 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t expect(page).not_to have_selector('.group_member') end + + context 'search' do + it 'finds no results' do + page.within '.member-search-form' do + fill_in 'search', with: 'testing 123' + find('.member-search-btn').click + end + + expect(page).not_to have_selector('.group_member') + end + + it 'finds results' do + page.within '.member-search-form' do + fill_in 'search', with: group.name + find('.member-search-btn').click + end + + expect(page).to have_selector('.group_member', count: 1) + end + end end -- cgit v1.2.1 From 401b797671b9b67ef40c4afa75acdeca83b6a6de Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 17:11:58 +0100 Subject: Fixed bug when group_ids not present when creating --- .../projects/project_members_controller.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index ac83377148a..d49598d2786 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -55,15 +55,17 @@ class Projects::ProjectMembersController < Projects::ApplicationController current_user: current_user ) - group_ids = params[:group_ids].split(',') - groups = Group.where(id: group_ids) - - groups.each do |group| - project.project_group_links.create( - group: group, - group_access: params[:access_level], - expires_at: params[:expires_at] - ) + if params[:group_ids].present? + group_ids = params[:group_ids].split(',') + groups = Group.where(id: group_ids) + + groups.each do |group| + project.project_group_links.create( + group: group, + group_access: params[:access_level], + expires_at: params[:expires_at] + ) + end end redirect_to namespace_project_project_members_path(@project.namespace, @project) -- cgit v1.2.1 From ecf7640b28562468880dda97ba42e5fd18c0859f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 10:58:52 +0100 Subject: Fixed group_links expire date not updating in view --- app/views/projects/group_links/update.js.haml | 2 +- spec/features/projects/members/group_links_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml index d3a37847f58..231d5a79723 100644 --- a/app/views/projects/group_links/update.js.haml +++ b/app/views/projects/group_links/update.js.haml @@ -1,3 +1,3 @@ :plain var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link, group: @group_link.group))}'); - $("##{dom_id(@group_link.group)} .list-item-name").replaceWith($listItem.find('.list-item-name')); + $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index 14ab7541fad..cc2f695211c 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -30,7 +30,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F") wait_for_ajax - page.within(first('li.member')) do + page.within(find('li.group_member')) do expect(page).to have_content('Expires in') end end -- cgit v1.2.1 From d8fee09e338006acb09c80ebcb032b6a75f3d7cd Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 12:36:21 +0100 Subject: Fixed jQuery chaining --- app/assets/javascripts/members.js.es6 | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 12d212ca185..1b4b3f38838 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -31,12 +31,10 @@ } formSubmit() { - const $this = $(this); - - $this.closest('form') - .trigger("submit.rails"); - - $this.disable(); + $(this).closest('form') + .trigger("submit.rails") + .end() + .disable(); } formSuccess() { -- cgit v1.2.1 From 2abbb0980f061d4297aab02f914c324c7fbe073b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 14:26:07 +0100 Subject: Removed group members from the list for now --- .../projects/project_members_controller.rb | 25 ---------------------- 1 file changed, 25 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d49598d2786..64cbc76da6f 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,33 +10,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) - group = @project.group - - if group - group_members = group.group_members - group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) - end - - if params[:search].present? - groups_id = @groups.pluck(:group_id) - groups = Group.where(id: groups_id).search(params[:search]).to_a - @groups = @project.project_group_links.where(group_id: groups) - - users = @project.users.search(params[:search]).to_a - project_members = project_members.where(user_id: users) - - if group_members - users = group.users.search(params[:search]).to_a - group_members = group_members.where(user_id: users) - end - end - members_ids = project_members.pluck(:id) - if group_members - members_ids << group_members.pluck(:id) - end - @members = Member.where(id: members_ids.flatten) @members_size = @members.size -- cgit v1.2.1 From 7cca8ffe60f4cdc7ca012cf223c6d7855b928685 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 8 Sep 2016 10:45:53 +0100 Subject: Ruby update --- app/controllers/projects/project_members_controller.rb | 14 +++++++------- app/views/projects/project_members/_team.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 64cbc76da6f..1c49ebfb99d 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -7,15 +7,15 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - project_members = @project.project_members - project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) + @project_members = @project.project_members + @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) - members_ids = project_members.pluck(:id) - - @members = Member.where(id: members_ids.flatten) - @members_size = @members.size + if params[:search].present? + users = @project.users.search(params[:search]).to_a + @project_members = @project_members.where(user_id: users) + end - @members = @members.page(params[:page]) + @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index d9799033e17..ff54035cfe1 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,7 +2,7 @@ .panel-heading Users with access to %strong #{@project.name} - %span.badge= @members_size + %span.badge= @project_members.total_count %ul.content-list - members.each do |member| = render 'shared/members/member', member: member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 86b2752cc0b..f566748e95a 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -25,5 +25,5 @@ - if @groups.size > 0 = render 'groups', groups: @groups - = render 'team', members: @members - = paginate @members, theme: "gitlab" + = render 'team', members: @project_members + = paginate @project_members, theme: "gitlab" -- cgit v1.2.1 From b61cd8a3930f194333ea417a03d53a0ad91efa42 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 9 Sep 2016 10:45:55 +0100 Subject: Added back ordering --- app/controllers/projects/project_members_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 1c49ebfb99d..5fd2e77a51a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -15,6 +15,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_members = @project_members.where(user_id: users) end + @project_members = @project_members.order('access_level DESC') @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) -- cgit v1.2.1 From aac80d76c272523a1ee7c9ef751034e955dfab9e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 9 Sep 2016 11:31:57 +0100 Subject: Group links search test fix --- app/controllers/projects/project_members_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 5fd2e77a51a..4d27617608d 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -13,6 +13,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController if params[:search].present? users = @project.users.search(params[:search]).to_a @project_members = @project_members.where(user_id: users) + + group_ids = @groups.pluck(:group_id) + group_ids = Group.where(id: group_ids).search(params[:search]).to_a + @groups = @project.project_group_links.where(group_id: group_ids) end @project_members = @project_members.order('access_level DESC') -- cgit v1.2.1 From b8d41220bde1b5c1f0e86a3346959fc7b760ccf5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 12 Sep 2016 15:34:54 +0100 Subject: Admin group members UI fix --- app/views/shared/members/_member.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 6f8c3c4da2e..80e52bf5637 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -4,7 +4,7 @@ - source = member.source %li.member{ class: dom_class(member), id: dom_id(member) } - %span{ class: ("list-item-name" if show_controls) } + %span.list-item-name - if user = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' %strong -- cgit v1.2.1 From 1b1c6ebf49c7ea9f838bff0f13f53808845d8979 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 13 Sep 2016 19:08:12 +0800 Subject: Fix the template --- app/views/notify/pipeline_failed_email.html.haml | 3 +-- app/views/notify/pipeline_failed_email.text.erb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 8d68b9e8483..44eff38b571 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -26,8 +26,7 @@ had = failed.size failed - = 'job'.plural(failed.size) - . + = 'job'.pluralize(failed.size) - failed.each do |job| .p diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb index 9808213af99..88779244a4b 100644 --- a/app/views/notify/pipeline_failed_email.text.erb +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -5,7 +5,7 @@ Commit Message: <%= @pipeline.git_commit_message %> Commit Author: <%= @pipeline.git_author_name %> Pusher: <%= @pipeline.user.try(:name) %> <% failed = @pipeline.statuses.latest.failed %> -Pipeline #<%= @pipeline.id %> had <%= failed.size %> failed <%= 'job'.plural(failed.size) %>. +Pipeline #<%= @pipeline.id %> had <%= failed.size %> failed <%= 'job'.pluralize(failed.size) %>. <% failed.each do |job| %> ID: <%= job.id %> -- cgit v1.2.1 From 4add6ca6ecb42fe08c0c1fbb1e7d5cf437e8ee3b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 13 Sep 2016 19:09:53 +0800 Subject: Try to integrate the email into notification system --- app/mailers/emails/pipelines.rb | 14 ++++---- app/models/ci/pipeline.rb | 9 +++++ app/models/notification_setting.rb | 4 ++- .../project_services/pipelines_email_service.rb | 3 +- .../ci/send_pipeline_notification_service.rb | 13 ++++++++ app/services/notification_service.rb | 18 +++++++++- .../notify/pipeline_succeeded_email.html.haml | 26 --------------- app/views/notify/pipeline_succeeded_email.text.erb | 7 ---- app/views/notify/pipeline_success_email.html.haml | 26 +++++++++++++++ app/views/notify/pipeline_success_email.text.erb | 7 ++++ app/workers/pipeline_email_worker.rb | 39 ---------------------- 11 files changed, 84 insertions(+), 82 deletions(-) create mode 100644 app/services/ci/send_pipeline_notification_service.rb delete mode 100644 app/views/notify/pipeline_succeeded_email.html.haml delete mode 100644 app/views/notify/pipeline_succeeded_email.text.erb create mode 100644 app/views/notify/pipeline_success_email.html.haml create mode 100644 app/views/notify/pipeline_success_email.text.erb delete mode 100644 app/workers/pipeline_email_worker.rb diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 643b947a049..e132105807a 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -1,18 +1,18 @@ module Emails module Pipelines - def pipeline_succeeded_email(params, to) - pipeline_mail(params, to, 'succeeded') + def pipeline_success_email(pipeline, to) + pipeline_mail(pipeline, to, 'succeeded') end - def pipeline_failed_email(params, to) - pipeline_mail(params, to, 'failed') + def pipeline_failed_email(pipeline, to) + pipeline_mail(pipeline, to, 'failed') end private - def pipeline_mail(params, to, status) - @project = params.project - @pipeline = params.pipeline + def pipeline_mail(pipeline, to, status) + @project = pipeline.project + @pipeline = pipeline add_headers mail(to: to, subject: pipeline_subject(status)) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 0b1df9f4294..b9c46202e5e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -62,6 +62,10 @@ module Ci after_transition do |pipeline, transition| pipeline.execute_hooks unless transition.loopback? end + + after_transition any => [:success, :failed] do |pipeline, transition| + SendPipelineNotificationService.new(pipeline).execute + end end # ref can't be HEAD or SHA, can only be branch/tag name @@ -90,6 +94,11 @@ module Ci project.id end + # For now the only user who participants is the user who triggered + def participants(current_user = nil) + [user] + end + def valid_commit_sha if self.sha == Gitlab::Git::BLANK_SHA self.errors.add(:sha, " cant be 00000000 (branch removal)") diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 121b598b8f3..43fc218de2b 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -32,7 +32,9 @@ class NotificationSetting < ActiveRecord::Base :reopen_merge_request, :close_merge_request, :reassign_merge_request, - :merge_merge_request + :merge_merge_request, + :failed_pipeline, + :success_pipeline ] store :events, accessors: EMAIL_EVENTS, coder: JSON diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index 1f852445c1c..7ce0aa7c16e 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -34,7 +34,8 @@ class PipelinesEmailService < Service return unless all_recipients.any? - PipelineEmailWorker.perform_async(data, all_recipients) + pipeline = Ci::Pipeline.find(data[:object_attributes][:id]) + Ci::SendPipelineNotificationService.new(pipeline).execute(all_recipients) end def can_test? diff --git a/app/services/ci/send_pipeline_notification_service.rb b/app/services/ci/send_pipeline_notification_service.rb new file mode 100644 index 00000000000..d330fa1a73c --- /dev/null +++ b/app/services/ci/send_pipeline_notification_service.rb @@ -0,0 +1,13 @@ +module Ci + class SendPipelineNotificationService < BaseService + attr_reader :pipeline + + def initialize(new_pipeline) + @pipeline = new_pipeline + end + + def execute(recipients = nil) + notification_service.pipeline_finished(pipeline, recipients) + end + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 6139ed56e25..de60c49ba84 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -311,6 +311,22 @@ class NotificationService mailer.project_was_not_exported_email(current_user, project, errors).deliver_later end + def pipeline_finished(pipeline, recipients = nil) + email_template = "pipeline_#{pipeline.status}_email" + + return unless mailer.respond_to?(email_template) + + recipients ||= build_recipients( + pipeline, + pipeline.project, + pipeline.user, + action: pipeline.status) + + recipients.each do |to| + Notify.public_send(email_template, pipeline, to).deliver_later + end + end + protected # Get project/group users with CUSTOM notification level @@ -613,6 +629,6 @@ class NotificationService # Build event key to search on custom notification level # Check NotificationSetting::EMAIL_EVENTS def build_custom_key(action, object) - "#{action}_#{object.class.name.underscore}".to_sym + "#{action}_#{object.class.model_name.name.underscore}".to_sym end end diff --git a/app/views/notify/pipeline_succeeded_email.html.haml b/app/views/notify/pipeline_succeeded_email.html.haml deleted file mode 100644 index 64cf7cfe103..00000000000 --- a/app/views/notify/pipeline_succeeded_email.html.haml +++ /dev/null @@ -1,26 +0,0 @@ -.p - Project: - = @project.path_with_namespace -.p - Branch: - = @pipeline.ref -.p - Commit: - = @pipeline.short_sha - ( - = @pipeline.sha - ) -.p - Commit Message: - = @pipeline.git_commit_message -.p - Commit Author: - = @pipeline.git_author_name -.p - Pusher: - = @pipeline.user.try(:name) -- failed = @pipeline.statuses.latest.failed -.p - Pipeline # - = @pipeline.id - had succeeded. diff --git a/app/views/notify/pipeline_succeeded_email.text.erb b/app/views/notify/pipeline_succeeded_email.text.erb deleted file mode 100644 index 6821d1f4459..00000000000 --- a/app/views/notify/pipeline_succeeded_email.text.erb +++ /dev/null @@ -1,7 +0,0 @@ -Project: <%= @project.path_with_namespace %> -Branch: <%= @pipeline.ref %> -Commit: <%= @pipeline.short_sha %> (<%= @pipeline.sha %>) -Commit Message: <%= @pipeline.git_commit_message %> -Commit Author: <%= @pipeline.git_author_name %> -Pusher: <%= @pipeline.user.try(:name) %> -Pipeline #<%= @pipeline.id %> had succeeded. diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml new file mode 100644 index 00000000000..64cf7cfe103 --- /dev/null +++ b/app/views/notify/pipeline_success_email.html.haml @@ -0,0 +1,26 @@ +.p + Project: + = @project.path_with_namespace +.p + Branch: + = @pipeline.ref +.p + Commit: + = @pipeline.short_sha + ( + = @pipeline.sha + ) +.p + Commit Message: + = @pipeline.git_commit_message +.p + Commit Author: + = @pipeline.git_author_name +.p + Pusher: + = @pipeline.user.try(:name) +- failed = @pipeline.statuses.latest.failed +.p + Pipeline # + = @pipeline.id + had succeeded. diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb new file mode 100644 index 00000000000..6821d1f4459 --- /dev/null +++ b/app/views/notify/pipeline_success_email.text.erb @@ -0,0 +1,7 @@ +Project: <%= @project.path_with_namespace %> +Branch: <%= @pipeline.ref %> +Commit: <%= @pipeline.short_sha %> (<%= @pipeline.sha %>) +Commit Message: <%= @pipeline.git_commit_message %> +Commit Author: <%= @pipeline.git_author_name %> +Pusher: <%= @pipeline.user.try(:name) %> +Pipeline #<%= @pipeline.id %> had succeeded. diff --git a/app/workers/pipeline_email_worker.rb b/app/workers/pipeline_email_worker.rb deleted file mode 100644 index 2160207c901..00000000000 --- a/app/workers/pipeline_email_worker.rb +++ /dev/null @@ -1,39 +0,0 @@ -class PipelineEmailWorker - include Sidekiq::Worker - - ParamsStruct = Struct.new(:pipeline, :project, :email_template) - class Params < ParamsStruct - def initialize(pipeline_id) - self.pipeline = Ci::Pipeline.find(pipeline_id) - self.project = pipeline.project - self.email_template = case pipeline.status - when 'success' - :pipeline_succeeded_email - when 'failed' - :pipeline_failed_email - end - end - end - - def perform(data, recipients) - params = Params.new(data['object_attributes']['id']) - - return unless params.email_template - - recipients.each do |to| - deliver(params, to) do - Notify.public_send(params.email_template, params, to).deliver_now - end - end - end - - private - - def deliver(params, to) - yield - # These are input errors and won't be corrected even if Sidekiq retries - rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e - project_name = params.project.path_with_namespace - logger.info("Failed to send email for #{project_name} to #{to}: #{e}") - end -end -- cgit v1.2.1 From 2ccd4c32d7d94cf794467750ef8c9632c2c8bed5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 13 Sep 2016 19:19:41 +0800 Subject: Use mailer --- app/services/notification_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index de60c49ba84..0b7b3fb5b89 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -323,7 +323,7 @@ class NotificationService action: pipeline.status) recipients.each do |to| - Notify.public_send(email_template, pipeline, to).deliver_later + mailer.public_send(email_template, pipeline, to).deliver_later end end -- cgit v1.2.1 From 4ad63c29bb64bbb29598c6eef3b38e8b07c8d9e8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 13 Sep 2016 23:38:46 +0800 Subject: Fixed the names and add missing check --- app/models/project_services/pipelines_email_service.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index 7ce0aa7c16e..5f8d7554057 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -7,7 +7,7 @@ class PipelinesEmailService < Service if: ->(s) { s.activated? && !s.add_pusher? } def initialize_properties - self.properties ||= { notify_only_broken_builds: true } + self.properties ||= { notify_only_broken_pipelines: true } end def title @@ -28,7 +28,7 @@ class PipelinesEmailService < Service def execute(data, force = false) return unless supported_events.include?(data[:object_kind]) - return unless force || should_build_be_notified?(data) + return unless force || should_pipeline_be_notified?(data) all_recipients = retrieve_recipients(data) @@ -73,10 +73,12 @@ class PipelinesEmailService < Service { success: false, result: error } end - def should_build_be_notified?(data) + def should_pipeline_be_notified?(data) case data[:object_attributes][:status] when 'success' !notify_only_broken_pipelines? + when 'failed' + true else false end -- cgit v1.2.1 From 9d9c2d314c10347da4a0929db429c2c2bc24a64d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Sep 2016 01:10:58 +0800 Subject: Fix pipeline emails and add tests --- app/services/notification_service.rb | 4 +- .../pipeline_email_service_spec.rb | 130 +++++++++++++++++++++ .../ci/send_pipeline_notification_service_spec.rb | 35 ++++++ 3 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 spec/models/project_services/pipeline_email_service_spec.rb create mode 100644 spec/services/ci/send_pipeline_notification_service_spec.rb diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 0b7b3fb5b89..9ce06b69e9d 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -319,11 +319,11 @@ class NotificationService recipients ||= build_recipients( pipeline, pipeline.project, - pipeline.user, + nil, # The acting user, who won't be added to recipients action: pipeline.status) recipients.each do |to| - mailer.public_send(email_template, pipeline, to).deliver_later + mailer.public_send(email_template, pipeline, to.email).deliver_later end end diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipeline_email_service_spec.rb new file mode 100644 index 00000000000..38c2b25c88c --- /dev/null +++ b/spec/models/project_services/pipeline_email_service_spec.rb @@ -0,0 +1,130 @@ +require 'spec_helper' + +describe PipelinesEmailService do + let(:data) do + Gitlab::DataBuilder::Pipeline.build(create(:ci_pipeline)) + end + + let(:recipient) { 'test@gitlab.com' } + + def expect_pipeline_service + expect_any_instance_of(Ci::SendPipelineNotificationService) + end + + def receive_execute + receive(:execute).with([recipient]) + end + + describe 'Validations' do + context 'when service is active' do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of(:recipients) } + + context 'when pusher is added' do + before do + subject.add_pusher = true + end + + it { is_expected.not_to validate_presence_of(:recipients) } + end + end + + context 'when service is inactive' do + before do + subject.active = false + end + + it { is_expected.not_to validate_presence_of(:recipients) } + end + end + + describe '#test_data' do + let(:build) { create(:ci_build) } + let(:project) { build.project } + let(:user) { create(:user) } + + before do + project.team << [user, :developer] + end + + it 'builds test data' do + data = subject.test_data(project, user) + + expect(data[:object_kind]).to eq('pipeline') + end + end + + describe '#test' do + before do + subject.recipients = recipient + end + + shared_examples 'sending email' do + it 'sends email' do + expect_pipeline_service.to receive_execute + + subject.test(data) + end + end + + it_behaves_like 'sending email' + + context 'when pipeline is succeeded' do + before do + data[:object_attributes][:status] = 'success' + end + + it_behaves_like 'sending email' + end + end + + describe '#execute' do + context 'with recipients' do + before do + subject.recipients = recipient + end + + it 'sends email for failed pipeline' do + data[:object_attributes][:status] = 'failed' + + expect_pipeline_service.to receive_execute + + subject.execute(data) + end + + it 'does not send email for succeeded pipeline' do + data[:object_attributes][:status] = 'success' + + expect_pipeline_service.not_to receive_execute + + subject.execute(data) + end + + context 'with notify_only_broken_pipelines on' do + before do + subject.notify_only_broken_pipelines = true + end + + it 'sends email for failed pipeline' do + data[:object_attributes][:status] = 'failed' + + expect_pipeline_service.to receive_execute + + subject.execute(data) + end + end + end + + it 'does not send email when recipients list is empty' do + subject.recipients = ' ,, ' + data[:object_attributes][:status] = 'failed' + + expect_pipeline_service.not_to receive_execute + + subject.execute(data) + end + end +end diff --git a/spec/services/ci/send_pipeline_notification_service_spec.rb b/spec/services/ci/send_pipeline_notification_service_spec.rb new file mode 100644 index 00000000000..ba588ed8ef5 --- /dev/null +++ b/spec/services/ci/send_pipeline_notification_service_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Ci::SendPipelineNotificationService, services: true do + let(:user) { create(:user) } + let(:pipeline) { create(:ci_pipeline, user: user, status: status) } + subject{ described_class.new(pipeline) } + + describe '#execute' do + shared_examples 'sending emails' do + it 'sends an email to pipeline user' do + perform_enqueued_jobs do + subject.execute + end + + email = ActionMailer::Base.deliveries.last + expect(email.subject).to include(email_subject) + expect(email.to).to eq([user.email]) + end + end + + context 'with success pipeline' do + let(:status) { 'success' } + let(:email_subject) { 'Pipeline succeeded for' } + + it_behaves_like 'sending emails' + end + + context 'with failed pipeline' do + let(:status) { 'failed' } + let(:email_subject) { 'Pipeline failed for' } + + it_behaves_like 'sending emails' + end + end +end -- cgit v1.2.1 From 1404aa8677969a03ed56e8f8350257f317f576d8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Sep 2016 19:34:24 +0800 Subject: Split notification integration into another branch --- app/models/ci/pipeline.rb | 9 --------- app/models/notification_setting.rb | 4 +--- app/services/ci/send_pipeline_notification_service.rb | 10 ++++++++-- app/services/notification_service.rb | 16 ---------------- .../ci/send_pipeline_notification_service_spec.rb | 2 +- 5 files changed, 10 insertions(+), 31 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index b9c46202e5e..0b1df9f4294 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -62,10 +62,6 @@ module Ci after_transition do |pipeline, transition| pipeline.execute_hooks unless transition.loopback? end - - after_transition any => [:success, :failed] do |pipeline, transition| - SendPipelineNotificationService.new(pipeline).execute - end end # ref can't be HEAD or SHA, can only be branch/tag name @@ -94,11 +90,6 @@ module Ci project.id end - # For now the only user who participants is the user who triggered - def participants(current_user = nil) - [user] - end - def valid_commit_sha if self.sha == Gitlab::Git::BLANK_SHA self.errors.add(:sha, " cant be 00000000 (branch removal)") diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 43fc218de2b..121b598b8f3 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -32,9 +32,7 @@ class NotificationSetting < ActiveRecord::Base :reopen_merge_request, :close_merge_request, :reassign_merge_request, - :merge_merge_request, - :failed_pipeline, - :success_pipeline + :merge_merge_request ] store :events, accessors: EMAIL_EVENTS, coder: JSON diff --git a/app/services/ci/send_pipeline_notification_service.rb b/app/services/ci/send_pipeline_notification_service.rb index d330fa1a73c..db462fe0291 100644 --- a/app/services/ci/send_pipeline_notification_service.rb +++ b/app/services/ci/send_pipeline_notification_service.rb @@ -6,8 +6,14 @@ module Ci @pipeline = new_pipeline end - def execute(recipients = nil) - notification_service.pipeline_finished(pipeline, recipients) + def execute(recipients) + email_template = "pipeline_#{pipeline.status}_email" + + return unless Notify.respond_to?(email_template) + + recipients.each do |to| + Notify.public_send(email_template, pipeline, to).deliver_later + end end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 9ce06b69e9d..0479a41b6a5 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -311,22 +311,6 @@ class NotificationService mailer.project_was_not_exported_email(current_user, project, errors).deliver_later end - def pipeline_finished(pipeline, recipients = nil) - email_template = "pipeline_#{pipeline.status}_email" - - return unless mailer.respond_to?(email_template) - - recipients ||= build_recipients( - pipeline, - pipeline.project, - nil, # The acting user, who won't be added to recipients - action: pipeline.status) - - recipients.each do |to| - mailer.public_send(email_template, pipeline, to.email).deliver_later - end - end - protected # Get project/group users with CUSTOM notification level diff --git a/spec/services/ci/send_pipeline_notification_service_spec.rb b/spec/services/ci/send_pipeline_notification_service_spec.rb index ba588ed8ef5..cb53ba8b051 100644 --- a/spec/services/ci/send_pipeline_notification_service_spec.rb +++ b/spec/services/ci/send_pipeline_notification_service_spec.rb @@ -9,7 +9,7 @@ describe Ci::SendPipelineNotificationService, services: true do shared_examples 'sending emails' do it 'sends an email to pipeline user' do perform_enqueued_jobs do - subject.execute + subject.execute([user.email]) end email = ActionMailer::Base.deliveries.last -- cgit v1.2.1 From c5e0305d5fe150acd74a66efa46e3ee741642978 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Sep 2016 19:44:35 +0800 Subject: Revert this until we do notifications --- app/services/notification_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 0479a41b6a5..6139ed56e25 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -613,6 +613,6 @@ class NotificationService # Build event key to search on custom notification level # Check NotificationSetting::EMAIL_EVENTS def build_custom_key(action, object) - "#{action}_#{object.class.model_name.name.underscore}".to_sym + "#{action}_#{object.class.name.underscore}".to_sym end end -- cgit v1.2.1 From 15bb44fc6357ca57e344d7b4818ac74ffb0a4dea Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Sep 2016 20:38:20 +0800 Subject: test for real --- .../pipeline_email_service_spec.rb | 116 +++++++++++++++------ 1 file changed, 82 insertions(+), 34 deletions(-) diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipeline_email_service_spec.rb index 38c2b25c88c..c352ddc00c3 100644 --- a/spec/models/project_services/pipeline_email_service_spec.rb +++ b/spec/models/project_services/pipeline_email_service_spec.rb @@ -1,18 +1,15 @@ require 'spec_helper' describe PipelinesEmailService do - let(:data) do - Gitlab::DataBuilder::Pipeline.build(create(:ci_pipeline)) - end - + let(:pipeline) { create(:ci_pipeline) } let(:recipient) { 'test@gitlab.com' } - def expect_pipeline_service - expect_any_instance_of(Ci::SendPipelineNotificationService) + let(:data) do + Gitlab::DataBuilder::Pipeline.build(pipeline) end - def receive_execute - receive(:execute).with([recipient]) + before do + ActionMailer::Base.deliveries.clear end describe 'Validations' do @@ -57,24 +54,53 @@ describe PipelinesEmailService do end end - describe '#test' do + shared_examples 'sending email' do before do - subject.recipients = recipient + perform_enqueued_jobs do + run + end end - shared_examples 'sending email' do - it 'sends email' do - expect_pipeline_service.to receive_execute + it 'sends email' do + sent_to = ActionMailer::Base.deliveries.flat_map(&:to) + expect(sent_to).to contain_exactly(recipient) + end + end - subject.test(data) + shared_examples 'not sending email' do + before do + perform_enqueued_jobs do + run end end - it_behaves_like 'sending email' + it 'does not send email' do + expect(ActionMailer::Base.deliveries).to be_empty + end + end + + describe '#test' do + def run + subject.test(data) + end + + before do + subject.recipients = recipient + end + + context 'when pipeline is failed' do + before do + data[:object_attributes][:status] = 'failed' + pipeline.update(status: 'failed') + end + + it_behaves_like 'sending email' + end context 'when pipeline is succeeded' do before do data[:object_attributes][:status] = 'success' + pipeline.update(status: 'success') end it_behaves_like 'sending email' @@ -82,25 +108,31 @@ describe PipelinesEmailService do end describe '#execute' do + def run + subject.execute(data) + end + context 'with recipients' do before do subject.recipients = recipient end - it 'sends email for failed pipeline' do - data[:object_attributes][:status] = 'failed' - - expect_pipeline_service.to receive_execute + context 'with failed pipeline' do + before do + data[:object_attributes][:status] = 'failed' + pipeline.update(status: 'failed') + end - subject.execute(data) + it_behaves_like 'sending email' end - it 'does not send email for succeeded pipeline' do - data[:object_attributes][:status] = 'success' - - expect_pipeline_service.not_to receive_execute + context 'with succeeded pipeline' do + before do + data[:object_attributes][:status] = 'success' + pipeline.update(status: 'success') + end - subject.execute(data) + it_behaves_like 'not sending email' end context 'with notify_only_broken_pipelines on' do @@ -108,23 +140,39 @@ describe PipelinesEmailService do subject.notify_only_broken_pipelines = true end - it 'sends email for failed pipeline' do - data[:object_attributes][:status] = 'failed' + context 'with failed pipeline' do + before do + data[:object_attributes][:status] = 'failed' + pipeline.update(status: 'failed') + end - expect_pipeline_service.to receive_execute + it_behaves_like 'sending email' + end + + context 'with succeeded pipeline' do + before do + data[:object_attributes][:status] = 'success' + pipeline.update(status: 'success') + end - subject.execute(data) + it_behaves_like 'not sending email' end end end - it 'does not send email when recipients list is empty' do - subject.recipients = ' ,, ' - data[:object_attributes][:status] = 'failed' + context 'with empty recipients list' do + before do + subject.recipients = ' ,, ' + end - expect_pipeline_service.not_to receive_execute + context 'with failed pipeline' do + before do + data[:object_attributes][:status] = 'failed' + pipeline.update(status: 'failed') + end - subject.execute(data) + it_behaves_like 'not sending email' + end end end end -- cgit v1.2.1 From 0f06c6c9cecfb4db4d0ded941a9660e76503759d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Sep 2016 21:24:52 +0800 Subject: Add CHANGELOG entry [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ae5470964ff..bc37a2bb70d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.12.0 (unreleased) - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Enable pipeline events by default !6278 + - Add pipeline email service !6019 - Move parsing of sidekiq ps into helper !6245 (pascalbetz) - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) -- cgit v1.2.1 From 1b8bbe5cd0c3f0ed66fb0b393d0dab7c85495a0b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Sep 2016 23:59:21 +0800 Subject: Prefer public_send, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019/diffs#note_15335949 --- app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 9fd06bdc6ae..678fa0af176 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -723,7 +723,7 @@ class Project < ActiveRecord::Base if template.nil? # If no template, we should create an instance. Ex `create_gitlab_ci_service` - send("create_#{service_name}_service") + public_send("create_#{service_name}_service") else Service.create_from_template(self.id, template) end -- cgit v1.2.1 From 844d4e24af244b34d425e4d027a8acfe235b7178 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 15 Sep 2016 00:00:02 +0800 Subject: Use keyword arg, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019/diffs#note_15336212 --- app/models/project_services/pipelines_email_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index 5f8d7554057..e133b1696da 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -26,7 +26,7 @@ class PipelinesEmailService < Service %w[pipeline] end - def execute(data, force = false) + def execute(data, force: false) return unless supported_events.include?(data[:object_kind]) return unless force || should_pipeline_be_notified?(data) @@ -66,7 +66,7 @@ class PipelinesEmailService < Service end def test(data) - result = execute(data, true) + result = execute(data, force: true) { success: true, result: result } rescue StandardError => error -- cgit v1.2.1 From 9d92d41ff98f6afdcfa98d556541f93f38533519 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 15 Sep 2016 00:00:47 +0800 Subject: Omit else, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019/diffs#note_15336254 --- app/models/project_services/pipelines_email_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index e133b1696da..701b879a73e 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -89,8 +89,8 @@ class PipelinesEmailService < Service if add_pusher? && data[:user].try(:[], :email) all_recipients << data[:user][:email] - else - all_recipients end + + all_recipients end end -- cgit v1.2.1 From e0dbd81642d06bc196e8f909e249fd17051d77e3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 15 Sep 2016 00:01:26 +0800 Subject: Doesn't need BaseService, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019/diffs#note_15336294 --- app/services/ci/send_pipeline_notification_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/ci/send_pipeline_notification_service.rb b/app/services/ci/send_pipeline_notification_service.rb index db462fe0291..ceb182801f7 100644 --- a/app/services/ci/send_pipeline_notification_service.rb +++ b/app/services/ci/send_pipeline_notification_service.rb @@ -1,5 +1,5 @@ module Ci - class SendPipelineNotificationService < BaseService + class SendPipelineNotificationService attr_reader :pipeline def initialize(new_pipeline) -- cgit v1.2.1 From 76ae5af8ce9d88cb1da4f8b9836fe78b7c2ad30e Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 19 Sep 2016 18:02:52 -0500 Subject: ensure the 'fixed layout' preference is honored whenever possible see #22343 for issue description --- CHANGELOG | 1 + app/assets/javascripts/diff.js | 7 +++++ .../merge_conflict_data_provider.js.es6 | 12 +++++--- .../javascripts/merge_conflict_resolver.js.es6 | 5 ++-- app/assets/javascripts/merge_request.js | 9 ++---- app/assets/javascripts/merge_request_tabs.js | 34 ++++++++++++++++++---- app/helpers/page_layout_helper.rb | 8 ++--- app/views/layouts/header/_default.html.haml | 2 +- app/views/profiles/preferences/update.js.erb | 4 +-- app/views/projects/diffs/_diffs.html.haml | 2 -- app/views/projects/merge_requests/_show.html.haml | 3 -- 11 files changed, 55 insertions(+), 32 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b16302e0a03..1ff4bef86bd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.12.0 (unreleased) - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Fix long comments in diffs messing with table width - Fix pagination on user snippets page + - Honor "fixed layout" preference in more places !6422 - Run CI builds with the permissions of users !5735 - Fix sorting of issues in API - Sort project variables by key. !6275 (Diego Souza) diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index c8634b78f2b..8086c10ad6b 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -7,6 +7,9 @@ function Diff() { $('.files .diff-file').singleFileDiff(); this.filesCommentButton = $('.files .diff-file').filesCommentButton(); + if (this.diffViewType() === 'parallel') { + $('.content-wrapper .container-fluid').removeClass('container-limited'); + } $(document).off('click', '.js-unfold'); $(document).on('click', '.js-unfold', (function(_this) { return function(event) { @@ -52,6 +55,10 @@ })(this)); } + Diff.prototype.diffViewType = function() { + return $('.inline-parallel-buttons a.active').data('view-type'); + } + Diff.prototype.lineNumbers = function(line) { if (!line.children().length) { return [0, 0]; diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index cd92df8ddc5..f6e1409ee47 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -7,13 +7,16 @@ const ORIGIN_BUTTON_TITLE = 'Use theirs'; class MergeConflictDataProvider { getInitialData() { + // TODO: remove reliance on jQuery and DOM state introspection const diffViewType = $.cookie('diff_view'); + const fixedLayout = $('.content-wrapper .container-fluid').hasClass('container-limited'); return { isLoading : true, hasError : false, isParallel : diffViewType === 'parallel', diffViewType : diffViewType, + fixedLayout : fixedLayout, isSubmitting : false, conflictsData : {}, resolutionData : {} @@ -192,14 +195,15 @@ class MergeConflictDataProvider { updateViewType(newType) { const vi = this.vueInstance; - if (newType === vi.diffView || !(newType === 'parallel' || newType === 'inline')) { + if (newType === vi.diffViewType || !(newType === 'parallel' || newType === 'inline')) { return; } - vi.diffView = newType; - vi.isParallel = newType === 'parallel'; + vi.diffViewType = newType; + vi.isParallel = newType === 'parallel'; $.cookie('diff_view', newType); // TODO: Make sure that cookie path added. - $('.content-wrapper .container-fluid').toggleClass('container-limited'); + $('.content-wrapper .container-fluid') + .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout); } diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6 index b56fd5aa658..7e756433bf5 100644 --- a/app/assets/javascripts/merge_conflict_resolver.js.es6 +++ b/app/assets/javascripts/merge_conflict_resolver.js.es6 @@ -60,9 +60,8 @@ class MergeConflictResolver { $('#conflicts .js-syntax-highlight').syntaxHighlight(); }); - if (this.vue.diffViewType === 'parallel') { - $('.content-wrapper .container-fluid').removeClass('container-limited'); - } + $('.content-wrapper .container-fluid') + .toggleClass('container-limited', !this.vue.isParallel && this.vue.fixedLayout); }) } diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index 05644b3d03c..02ff5a382e2 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -36,13 +36,10 @@ }; MergeRequest.prototype.initTabs = function() { - if (this.opts.action !== 'new') { - // `MergeRequests#new` has no tab-persisting or lazy-loading behavior - window.mrTabs = new MergeRequestTabs(this.opts); - } else { - // Show the first tab (Commits) - return $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show'); + if (window.mrTabs) { + window.mrTabs.unbindEvents(); } + window.mrTabs = new MergeRequestTabs(this.opts); }; MergeRequest.prototype.showAllCommits = function() { diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 18bbfa7a459..bec11a198a1 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -56,6 +56,8 @@ MergeRequestTabs.prototype.commitsLoaded = false; + MergeRequestTabs.prototype.fixedLayoutPref = null; + function MergeRequestTabs(opts) { this.opts = opts != null ? opts : {}; this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true; @@ -70,7 +72,12 @@ MergeRequestTabs.prototype.bindEvents = function() { $(document).on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown); - return $(document).on('click', '.js-show-tab', this.showTab); + $(document).on('click', '.js-show-tab', this.showTab); + }; + + MergeRequestTabs.prototype.unbindEvents = function() { + $(document).off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown); + $(document).off('click', '.js-show-tab', this.showTab); }; MergeRequestTabs.prototype.showTab = function(event) { @@ -85,11 +92,15 @@ if (action === 'commits') { this.loadCommits($target.attr('href')); this.expandView(); + this.resetViewContainer(); } else if (action === 'diffs') { this.loadDiff($target.attr('href')); if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') { this.shrinkView(); } + if (this.diffViewType() === 'parallel') { + this.expandViewContainer(); + } navBarHeight = $('.navbar-gitlab').outerHeight(); $.scrollTo(".merge-request-details .merge-request-tabs", { offset: -navBarHeight @@ -97,11 +108,14 @@ } else if (action === 'builds') { this.loadBuilds($target.attr('href')); this.expandView(); + this.resetViewContainer(); } else if (action === 'pipelines') { this.loadPipelines($target.attr('href')); this.expandView(); + this.resetViewContainer(); } else { this.expandView(); + this.resetViewContainer(); } if (this.opts.setUrl) { this.setCurrentAction(action); @@ -126,7 +140,7 @@ if (action === 'show') { action = 'notes'; } - return $(".merge-request-tabs a[data-action='" + action + "']").tab('show'); + $(".merge-request-tabs a[data-action='" + action + "']").tab('show').trigger('shown.bs.tab'); }; // Replaces the current Merge Request-specific action in the URL with a new one @@ -209,7 +223,7 @@ gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); $('#diffs .js-syntax-highlight').syntaxHighlight(); $('#diffs .diff-file').singleFileDiff(); - if (_this.diffViewType() === 'parallel') { + if (_this.diffViewType() === 'parallel' && _this.currentAction === 'diffs') { _this.expandViewContainer(); } _this.diffsLoaded = true; @@ -308,11 +322,21 @@ MergeRequestTabs.prototype.diffViewType = function() { return $('.inline-parallel-buttons a.active').data('view-type'); - // Returns diff view type }; MergeRequestTabs.prototype.expandViewContainer = function() { - return $('.container-fluid').removeClass('container-limited'); + var $wrapper = $('.content-wrapper .container-fluid'); + if (this.fixedLayoutPref === null) { + this.fixedLayoutPref = $wrapper.hasClass('container-limited'); + } + $wrapper.removeClass('container-limited'); + }; + + MergeRequestTabs.prototype.resetViewContainer = function() { + if (this.fixedLayoutPref !== null) { + $('.content-wrapper .container-fluid') + .toggleClass('container-limited', this.fixedLayoutPref); + } }; MergeRequestTabs.prototype.shrinkView = function() { diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 22387d66451..7d4d049101a 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -92,12 +92,8 @@ module PageLayoutHelper end end - def fluid_layout(enabled = false) - if @fluid_layout.nil? - @fluid_layout = (current_user && current_user.layout == "fluid") || enabled - else - @fluid_layout - end + def fluid_layout + current_user && current_user.layout == "fluid" end def blank_container(enabled = false) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 94c53882623..237280872f1 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,5 +1,5 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } - %div{ class: fluid_layout ? "container-fluid" : "container-fluid" } + %div{ class: "container-fluid" } .header-content %button.side-nav-toggle{ type: 'button', "aria-label" => "Toggle global navigation" } %span.sr-only Toggle navigation diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb index 4433cab7782..8966dd3fd86 100644 --- a/app/views/profiles/preferences/update.js.erb +++ b/app/views/profiles/preferences/update.js.erb @@ -4,9 +4,9 @@ $('body').addClass('<%= user_application_theme %>') // Toggle container-fluid class if ('<%= current_user.layout %>' === 'fluid') { - $('.content-wrapper').find('.container-fluid').removeClass('container-limited') + $('.content-wrapper .container-fluid').removeClass('container-limited') } else { - $('.content-wrapper').find('.container-fluid').addClass('container-limited') + $('.content-wrapper .container-fluid').addClass('container-limited') } // Re-enable the "Save" button diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 62aff36aadd..576e7ef021a 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,7 +1,5 @@ - show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true) - diff_files = diffs.diff_files -- if diff_view == :parallel - - fluid_layout true .content-block.oneline-block.files-changed .inline-parallel-buttons diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index d03ff9ec7e8..9f34ca9ff4e 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -4,9 +4,6 @@ - content_for :page_specific_javascripts do = page_specific_javascript_tag('diff_notes/diff_notes_bundle.js') -- if diff_view == :parallel - - fluid_layout true - .merge-request{'data-url' => merge_request_path(@merge_request)} = render "projects/merge_requests/show/mr_title" -- cgit v1.2.1 From 62ba000e2d5e12985476f4b831366c2766f52f0e Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Tue, 20 Sep 2016 00:23:55 -0500 Subject: fix diff_view cookie path within merge conflict page --- app/assets/javascripts/merge_conflict_data_provider.js.es6 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index f6e1409ee47..13ee794ba38 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -201,7 +201,9 @@ class MergeConflictDataProvider { vi.diffViewType = newType; vi.isParallel = newType === 'parallel'; - $.cookie('diff_view', newType); // TODO: Make sure that cookie path added. + $.cookie('diff_view', newType, { + path: (gon && gon.relative_url_root) || '/' + }); $('.content-wrapper .container-fluid') .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout); } -- cgit v1.2.1 From 5869fb201459f0e44c4544076758e02299fa9227 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 21 Sep 2016 14:45:11 +0800 Subject: Avoid using the same name with method --- app/models/ci/build.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index da28f94bbe3..f9aa1984c1f 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -238,11 +238,11 @@ module Ci end def trace - trace = raw_trace - if project && trace.present? && project.runners_token.present? - trace.gsub(project.runners_token, 'xxxxxx') + result = raw_trace + if project && result.present? && project.runners_token.present? + result.gsub(project.runners_token, 'xxxxxx') else - trace + result end end -- cgit v1.2.1 From 63e03dada7e754a92ca088c683f4189424ab34b1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 21 Sep 2016 16:12:32 +0800 Subject: Make various trace methods take last_lines argument: So that we could read last few lines rather than read the entire file which could be huge. --- app/controllers/projects/builds_controller.rb | 4 ++- app/models/ci/build.rb | 20 ++++++----- lib/gitlab/ci/trace_reader.rb | 49 +++++++++++++++++++++++++++ spec/lib/gitlab/ci/trace_reader_spec.rb | 40 ++++++++++++++++++++++ 4 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 lib/gitlab/ci/trace_reader.rb create mode 100644 spec/lib/gitlab/ci/trace_reader_spec.rb diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index f13fb1df373..6069e620ba2 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -43,7 +43,9 @@ class Projects::BuildsController < Projects::ApplicationController def trace respond_to do |format| format.json do - render json: @build.trace_with_state(params[:state].presence).merge!(id: @build.id, status: @build.status) + state = params[:state].presence + render json: @build.trace_with_state(state: state). + merge!(id: @build.id, status: @build.status) end end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index f9aa1984c1f..13f101cbecb 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -128,13 +128,14 @@ module Ci latest_builds.where('stage_idx < ?', stage_idx) end - def trace_html - trace_with_state[:html] || '' + def trace_html(**args) + trace_with_state(**args)[:html] || '' end - def trace_with_state(state = nil) - if trace.present? - Ci::Ansi2html.convert(trace, state) + def trace_with_state(state: nil, last_lines: nil) + trace_ansi = trace(last_lines) + if trace_ansi.present? + Ci::Ansi2html.convert(trace_ansi, state) else {} end @@ -220,9 +221,10 @@ module Ci raw_trace.present? end - def raw_trace + def raw_trace(last_lines: nil) if File.exist?(trace_file_path) - File.read(trace_file_path) + Gitlab::Ci::TraceReader.new(trace_file_path). + read(last_lines: last_lines) else # backward compatibility read_attribute :trace @@ -237,8 +239,8 @@ module Ci project.ci_id && File.exist?(old_path_to_trace) end - def trace - result = raw_trace + def trace(last_lines: nil) + result = raw_trace(last_lines) if project && result.present? && project.runners_token.present? result.gsub(project.runners_token, 'xxxxxx') else diff --git a/lib/gitlab/ci/trace_reader.rb b/lib/gitlab/ci/trace_reader.rb new file mode 100644 index 00000000000..37e51536e8f --- /dev/null +++ b/lib/gitlab/ci/trace_reader.rb @@ -0,0 +1,49 @@ +module Gitlab + module Ci + # This was inspired from: http://stackoverflow.com/a/10219411/1520132 + class TraceReader + BUFFER_SIZE = 4096 + + attr_accessor :path, :buffer_size + + def initialize(new_path, buffer_size: BUFFER_SIZE) + self.path = new_path + self.buffer_size = Integer(buffer_size) + end + + def read(last_lines: nil) + if last_lines + read_last_lines(last_lines) + else + File.read(path) + end + end + + def read_last_lines(max_lines) + File.open(path) do |file| + chunks = [] + pos = lines = 0 + max = file.size + + # We want an extra line to make sure fist line has full contents + while lines <= max_lines && pos < max + pos += buffer_size + + buf = if pos <= max + file.seek(-pos, IO::SEEK_END) + file.read(buffer_size) + else # Reached the head, read only left + file.seek(0) + file.read(buffer_size - (pos - max)) + end + + lines += buf.count("\n") + chunks.unshift(buf) + end + + chunks.join.lines.last(max_lines).join + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/trace_reader_spec.rb b/spec/lib/gitlab/ci/trace_reader_spec.rb new file mode 100644 index 00000000000..f06d78694d6 --- /dev/null +++ b/spec/lib/gitlab/ci/trace_reader_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Gitlab::Ci::TraceReader do + let(:path) { __FILE__ } + let(:lines) { File.readlines(path) } + let(:bytesize) { lines.sum(&:bytesize) } + + it 'returns last few lines' do + 10.times do + subject = build_subject + last_lines = random_lines + + expected = lines.last(last_lines).join + + expect(subject.read(last_lines: last_lines)).to eq(expected) + end + end + + it 'returns everything if trying to get too many lines' do + expect(build_subject.read(last_lines: lines.size * 2)).to eq(lines.join) + end + + it 'raises an error if not passing an integer for last_lines' do + expect do + build_subject.read(last_lines: lines) + end.to raise_error(ArgumentError) + end + + def random_lines + Random.rand(lines.size) + 1 + end + + def random_buffer + Random.rand(bytesize) + 1 + end + + def build_subject + described_class.new(__FILE__, buffer_size: random_buffer) + end +end -- cgit v1.2.1 From 6887521ecfbc98441e905a920406d0cd09d24a2d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 21 Sep 2016 17:41:59 +0800 Subject: Sorry, was confused. --- app/models/ci/build.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5be2aca0cfb..416ea820e0c 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -142,7 +142,7 @@ module Ci end def trace_with_state(state: nil, last_lines: nil) - trace_ansi = trace(last_lines) + trace_ansi = trace(last_lines: last_lines) if trace_ansi.present? Ci::Ansi2html.convert(trace_ansi, state) else -- cgit v1.2.1 From 182bcc977aec49d802bb7b511faf618b2df1d4ea Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 11 Jul 2016 12:48:00 -0600 Subject: Initial incomplete draft of the Frontend Development Guidelines. [ci skip] --- doc/development/README.md | 1 + doc/development/frontend.md | 61 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 doc/development/frontend.md diff --git a/doc/development/README.md b/doc/development/README.md index 58c00f618fa..9706cb1de7f 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -13,6 +13,7 @@ - [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations - [Testing standards and style guidelines](testing.md) - [UI guide](ui_guide.md) for building GitLab with existing CSS styles and elements +- [Frontend guidelines](frontend.md) - [SQL guidelines](sql.md) for SQL guidelines ## Process diff --git a/doc/development/frontend.md b/doc/development/frontend.md new file mode 100644 index 00000000000..39460b72ec0 --- /dev/null +++ b/doc/development/frontend.md @@ -0,0 +1,61 @@ +# Frontend Guidelines + +This document describes various guidelines to follow to ensure good and +consistent performance of GitLab. + +## Performance + +### Per-page JavaScript + +Per-page JavaScript is JavaScript that is loaded only where necessary. + +There are some potential problems to per-page JavaScript as well. For one, + +### Minimizing page size + +A smaller page size means the page loads faster (especially important on mobile +and poor connections), the page is parsed faster by the browser, and less data is used for +users with capped data plans. + +General tips: + +- Don't add fonts that are unnecessary. +- Prefer font formats with better compression, e.g. WOFF2 is better than WOFF is better than TFF. +- Compress and minify assets wherever possible (For CSS/JS, Sprockets does this for us). +- If a piece of functionality can be reasonably done without adding extra libraries, prefer not to use extra libraries. +- Use per-page JavaScripts as described above to remove libraries that are only loaded on certain pages. + +## Accessibility + +The [Chrome Accessibility Developer Tools][chrome-accessibility-developer-tools] +are useful for testing for potential accessibility problems in GitLab. + +Accessibility best-practices and more in-depth information is available on +[the Audit Rules page][audit-rules] for the Chrome Accessibility Developer Tools. + +## Security + +### Content Security Policy + + + +### Subresource Integrity + + + +### Including external resources + +External fonts, CSS, JavaScript should never be used with the exception of +Google Analytics - and only when the instance has enabled it. Assets should +always be hosted and served locally from the GitLab instance. Embedded resources +via `iframes` should never be used except in certain circumstances such as with +ReCaptcha, which cannot reasonably be used without an iframe. + +### Avoiding inline scripts and styles + +While inline scripts can be useful, they're also a security concern. If +user-supplied content is unintentionally left un-sanitized, malicious users can +inject scripts into the site. + +[chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools +[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules -- cgit v1.2.1 From ccbaed208b5ea3d79bafed7267d644147308556a Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 25 Jul 2016 12:15:09 -0600 Subject: Add more information on page-specific JS. [ci skip] --- doc/development/frontend.md | 64 +++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index 39460b72ec0..c2dd130c258 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -5,17 +5,40 @@ consistent performance of GitLab. ## Performance -### Per-page JavaScript +### Page-specific JavaScript -Per-page JavaScript is JavaScript that is loaded only where necessary. +Certain pages may require the use of a third party library, such as [d3][d3] for +the User Activity Calendar and [Chart.js][chartjs] for the Graphs pages. These +libraries increase the page size significantly, and impact load times due to +bandwidth bottlenecks and the browser needing to parse more JavaScript. -There are some potential problems to per-page JavaScript as well. For one, +In cases where libraries are only used on a few specific pages, we use +"Page-specific JavaScript" to prevent the main `application.js` file from +becoming unnecessarily large. + +Steps to split page-specific JavaScript from the main `application.js`: + +1. Create a directory for the specific page(s), e.g. `graphs/`. +1. In that directory, create a `namespace_bundle.js` file, e.g. `graphs_bundle.js`. +1. Add the new "bundle" file to the list of precompiled assets in +`config/application.rb`. + - For example: `config.assets.precompile << "graphs/graphs_bundle.js"`. +1. Add any necessary libraries to `app/assets/javascripts/lib/`, all files directly descendant from this directory will be precompiled as separate assets. In this case, `chart.js` would be added. +1. In the relevant views, add the scripts to the page with the following: + +```haml +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/chart.js') + = page_specific_javascript_tag('graphs/graphs_bundle.js') +``` + +The above loads `chart.js` and `graphs_bundle.js` for only this page. `chart.js` is separated from the bundle file so it can be cached separately from the bundle and reused for other pages that also rely on the library. ### Minimizing page size A smaller page size means the page loads faster (especially important on mobile -and poor connections), the page is parsed faster by the browser, and less data is used for -users with capped data plans. +and poor connections), the page is parsed more quickly by the browser, and less +data is used for users with capped data plans. General tips: @@ -23,7 +46,7 @@ General tips: - Prefer font formats with better compression, e.g. WOFF2 is better than WOFF is better than TFF. - Compress and minify assets wherever possible (For CSS/JS, Sprockets does this for us). - If a piece of functionality can be reasonably done without adding extra libraries, prefer not to use extra libraries. -- Use per-page JavaScripts as described above to remove libraries that are only loaded on certain pages. +- Use page-specific JavaScripts as described above to dynamically load libraries that are only needed on certain pages. ## Accessibility @@ -35,27 +58,30 @@ Accessibility best-practices and more in-depth information is available on ## Security -### Content Security Policy - - - -### Subresource Integrity - - - ### Including external resources -External fonts, CSS, JavaScript should never be used with the exception of -Google Analytics - and only when the instance has enabled it. Assets should -always be hosted and served locally from the GitLab instance. Embedded resources -via `iframes` should never be used except in certain circumstances such as with -ReCaptcha, which cannot reasonably be used without an iframe. +External fonts, CSS, and JavaScript should never be used with the exception of +Google Analytics and Piwik - and only when the instance has enabled it. Assets +should always be hosted and served locally from the GitLab instance. Embedded +resources via `iframes` should never be used except in certain circumstances +such as with ReCaptcha, which cannot be used without an `iframe`. ### Avoiding inline scripts and styles +In order to protect users from [XSS vulnerabilities][xss], our intention is to +disable inline scripts entirely using Content Security Policy at some point in +the future. + While inline scripts can be useful, they're also a security concern. If user-supplied content is unintentionally left un-sanitized, malicious users can inject scripts into the site. +Inline styles should be avoided in almost all cases, they should only be used +when no alternatives can be found. This allows reusability of styles as well as +readability. + +[d3]: https://d3js.org/ +[chartjs]: http://www.chartjs.org/ [chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools [audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules +[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting -- cgit v1.2.1 From 79c2f60e9172cb7ce41ae75ae47938d6ceddd4c4 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 25 Jul 2016 12:29:43 -0600 Subject: Further revisions/additions [ci skip] --- doc/development/frontend.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index c2dd130c258..f6ed740a61a 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -1,7 +1,7 @@ -# Frontend Guidelines +# Frontend Development Guidelines -This document describes various guidelines to follow to ensure good and -consistent performance of GitLab. +This document describes various guidelines to ensure consistency and quality +across GitLab's frontend. ## Performance @@ -20,10 +20,12 @@ Steps to split page-specific JavaScript from the main `application.js`: 1. Create a directory for the specific page(s), e.g. `graphs/`. 1. In that directory, create a `namespace_bundle.js` file, e.g. `graphs_bundle.js`. +1. In `graphs_bundle.js` add the line `//= require_tree .`, this adds all other files in the directory to the bundle. +1. Add any necessary libraries to `app/assets/javascripts/lib/`, all files directly descendant from this directory will be precompiled as separate assets. In this case, `chart.js` would be added. 1. Add the new "bundle" file to the list of precompiled assets in `config/application.rb`. - For example: `config.assets.precompile << "graphs/graphs_bundle.js"`. -1. Add any necessary libraries to `app/assets/javascripts/lib/`, all files directly descendant from this directory will be precompiled as separate assets. In this case, `chart.js` would be added. +1. Move code reliant on these libraries into the `graphs` directory. 1. In the relevant views, add the scripts to the page with the following: ```haml @@ -32,7 +34,9 @@ Steps to split page-specific JavaScript from the main `application.js`: = page_specific_javascript_tag('graphs/graphs_bundle.js') ``` -The above loads `chart.js` and `graphs_bundle.js` for only this page. `chart.js` is separated from the bundle file so it can be cached separately from the bundle and reused for other pages that also rely on the library. +The above loads `chart.js` and `graphs_bundle.js` for only this page. `chart.js` +is separated from the bundle file so it can be cached separately from the bundle +and reused for other pages that also rely on the library. ### Minimizing page size @@ -42,7 +46,7 @@ data is used for users with capped data plans. General tips: -- Don't add fonts that are unnecessary. +- Don't add unnecessary fonts. - Prefer font formats with better compression, e.g. WOFF2 is better than WOFF is better than TFF. - Compress and minify assets wherever possible (For CSS/JS, Sprockets does this for us). - If a piece of functionality can be reasonably done without adding extra libraries, prefer not to use extra libraries. @@ -80,8 +84,15 @@ Inline styles should be avoided in almost all cases, they should only be used when no alternatives can be found. This allows reusability of styles as well as readability. +## Style Guides and Linting + +See the relevant style guides for details and information on linting: + +- [SCSS][scss-style-guide] + [d3]: https://d3js.org/ [chartjs]: http://www.chartjs.org/ [chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools [audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting +[scss-style-guide]: scss_styleguide.md -- cgit v1.2.1 From 3b3e9d17b9f238ba1eab6c0a7e121c92f5f5d5c4 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 25 Jul 2016 13:23:19 -0600 Subject: Add CSP and SRI information [ci skip] --- doc/development/frontend.md | 74 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index f6ed740a61a..ff75fe14393 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -13,7 +13,7 @@ libraries increase the page size significantly, and impact load times due to bandwidth bottlenecks and the browser needing to parse more JavaScript. In cases where libraries are only used on a few specific pages, we use -"Page-specific JavaScript" to prevent the main `application.js` file from +"page-specific JavaScript" to prevent the main `application.js` file from becoming unnecessarily large. Steps to split page-specific JavaScript from the main `application.js`: @@ -21,7 +21,7 @@ Steps to split page-specific JavaScript from the main `application.js`: 1. Create a directory for the specific page(s), e.g. `graphs/`. 1. In that directory, create a `namespace_bundle.js` file, e.g. `graphs_bundle.js`. 1. In `graphs_bundle.js` add the line `//= require_tree .`, this adds all other files in the directory to the bundle. -1. Add any necessary libraries to `app/assets/javascripts/lib/`, all files directly descendant from this directory will be precompiled as separate assets. In this case, `chart.js` would be added. +1. Add any necessary libraries to `app/assets/javascripts/lib/`, all files directly descendant from this directory will be precompiled as separate assets, in this case `chart.js` would be added. 1. Add the new "bundle" file to the list of precompiled assets in `config/application.rb`. - For example: `config.assets.precompile << "graphs/graphs_bundle.js"`. @@ -62,6 +62,62 @@ Accessibility best-practices and more in-depth information is available on ## Security +[Mozilla’s HTTP Observatory CLI][observatory-cli] and the +[Qualys SSL Labs Server Test][qualys-ssl] are good resources for finding +potential problems and ensuring compliance with security best practices. + + + ### Including external resources External fonts, CSS, and JavaScript should never be used with the exception of @@ -84,7 +140,7 @@ Inline styles should be avoided in almost all cases, they should only be used when no alternatives can be found. This allows reusability of styles as well as readability. -## Style Guides and Linting +## Style guides and linting See the relevant style guides for details and information on linting: @@ -94,5 +150,17 @@ See the relevant style guides for details and information on linting: [chartjs]: http://www.chartjs.org/ [chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools [audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules +[observatory-cli]: https://github.com/mozilla/http-observatory-cli) +[qualys-ssl]: https://www.ssllabs.com/ssltest/analyze.html +[secure_headers]: https://github.com/twitter/secureheaders +[mdn-csp]: https://developer.mozilla.org/en-US/docs/Web/Security/CSP +[github-eng-csp]: http://githubengineering.com/githubs-csp-journey/ +[dropbox-csp-1]: https://blogs.dropbox.com/tech/2015/09/on-csp-reporting-and-filtering/ +[dropbox-csp-2]: https://blogs.dropbox.com/tech/2015/09/unsafe-inline-and-nonce-deployment/ +[dropbox-csp-3]: https://blogs.dropbox.com/tech/2015/09/csp-the-unexpected-eval/ +[dropbox-csp-4]: https://blogs.dropbox.com/tech/2015/09/csp-third-party-integrations-and-privilege-separation/ +[mdn-sri]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity +[github-eng-sri]: http://githubengineering.com/subresource-integrity/ +[sprockets-sri]: https://github.com/rails/sprockets-rails#sri-support [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting [scss-style-guide]: scss_styleguide.md -- cgit v1.2.1 From d4e2c4eff57291157e3990fe412c095d3a7c8de9 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 27 Jul 2016 11:21:55 -0600 Subject: Add short Testing section, minor fixes. --- doc/development/frontend.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index ff75fe14393..05ea10e754c 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -47,7 +47,7 @@ data is used for users with capped data plans. General tips: - Don't add unnecessary fonts. -- Prefer font formats with better compression, e.g. WOFF2 is better than WOFF is better than TFF. +- Prefer font formats with better compression, e.g. WOFF2 is better than WOFF, which is better than TTF. - Compress and minify assets wherever possible (For CSS/JS, Sprockets does this for us). - If a piece of functionality can be reasonably done without adding extra libraries, prefer not to use extra libraries. - Use page-specific JavaScripts as described above to dynamically load libraries that are only needed on certain pages. @@ -146,6 +146,12 @@ See the relevant style guides for details and information on linting: - [SCSS][scss-style-guide] +## Testing + +Feature tests should be written for all new features as well as any regressions to prevent them from occuring again. + +See [the Testing Standards and Style Guidelines](testing.md) for more information. + [d3]: https://d3js.org/ [chartjs]: http://www.chartjs.org/ [chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools -- cgit v1.2.1 From 7730ec2d1cc778f45ea65191b37d347f000ffecb Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 27 Jul 2016 11:51:08 -0600 Subject: Add Overview section detailing our frontend stack. [ci skip] --- doc/development/frontend.md | 49 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index 05ea10e754c..cded244c0d4 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -3,8 +3,28 @@ This document describes various guidelines to ensure consistency and quality across GitLab's frontend. +## Overview + +GitLab is built on top of [Ruby on Rails][rails] using [Haml][haml] (with +[Hamlit][hamlit] for performance reasons, be wary of [the limitations this comes +with][hamlit-limits]), [SCSS][scss], and plain JavaScript with +[ES6 by way of Babel][es6]. + +The asset pipeline is [Sprockets][sprockets], which handles the concatenation, +minification, and compression of our assets. + +[jQuery][jquery] is used throughout the application's JavaScript, with +[Vue.js][vue] for particularly advanced, dynamic elements. + ## Performance +### Resources + +- [WebPage Test][web-page-test] for testing site loading time and size. +- [Google PageSpeed Insights][pagespeed-insights] grades web pages and provides feedback to improve the page. +- [Profiling with Chrome DevTools][google-devtools-profiling] +- [Browser Diet][browser-diet] is a community-built guide that catalogues practical tips for improving web page performance. + ### Page-specific JavaScript Certain pages may require the use of a third party library, such as [d3][d3] for @@ -54,6 +74,8 @@ General tips: ## Accessibility +### Resources + The [Chrome Accessibility Developer Tools][chrome-accessibility-developer-tools] are useful for testing for potential accessibility problems in GitLab. @@ -62,6 +84,8 @@ Accessibility best-practices and more in-depth information is available on ## Security +### Resources + [Mozilla’s HTTP Observatory CLI][observatory-cli] and the [Qualys SSL Labs Server Test][qualys-ssl] are good resources for finding potential problems and ensuring compliance with security best practices. @@ -142,16 +166,31 @@ readability. ## Style guides and linting -See the relevant style guides for details and information on linting: +See the relevant style guides for our guidelines and for information on linting: - [SCSS][scss-style-guide] ## Testing -Feature tests should be written for all new features as well as any regressions to prevent them from occuring again. - -See [the Testing Standards and Style Guidelines](testing.md) for more information. - +Feature tests should be written for all new features as well as any regressions +to prevent them from occurring again. + +See [the Testing Standards and Style Guidelines](testing.md) for more +information. + +[rails]: http://rubyonrails.org/ +[haml]: http://haml.info/ +[hamlit]: https://github.com/k0kubun/hamlit +[hamlit-limits]: https://github.com/k0kubun/hamlit/blob/master/REFERENCE.md#limitations +[scss]: http://sass-lang.com/ +[es6]: https://babeljs.io/ +[sprockets]: https://github.com/rails/sprockets +[jquery]: https://jquery.com/ +[vue]: http://vuejs.org/ +[web-page-test]: http://www.webpagetest.org/ +[pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/ +[google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en +[browser-diet]: https://browserdiet.com/ [d3]: https://d3js.org/ [chartjs]: http://www.chartjs.org/ [chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools -- cgit v1.2.1 From 059656ad21430c165de57f4de639f5e30e727d4c Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Thu, 4 Aug 2016 14:05:22 -0600 Subject: Add a section on vue and one on supported browsers. --- doc/development/frontend.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index cded244c0d4..9073a11a2f3 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -16,6 +16,14 @@ minification, and compression of our assets. [jQuery][jquery] is used throughout the application's JavaScript, with [Vue.js][vue] for particularly advanced, dynamic elements. +### Vue + +For more complex frontend features, we recommend using Vue.js. It shares +some ideas with React.js while being smaller and – arguably – easier to get +into. + +To get started with Vue, read through [their documentation][vue-docs]. + ## Performance ### Resources @@ -178,6 +186,10 @@ to prevent them from occurring again. See [the Testing Standards and Style Guidelines](testing.md) for more information. +## Supported browsers + +For our currently-supported browsers, see our [requirements][requirements]. + [rails]: http://rubyonrails.org/ [haml]: http://haml.info/ [hamlit]: https://github.com/k0kubun/hamlit @@ -187,6 +199,7 @@ information. [sprockets]: https://github.com/rails/sprockets [jquery]: https://jquery.com/ [vue]: http://vuejs.org/ +[vue-docs]: http://vuejs.org/guide/index.html [web-page-test]: http://www.webpagetest.org/ [pagespeed-insights]: https://developers.google.com/speed/pagespeed/insights/ [google-devtools-profiling]: https://developers.google.com/web/tools/chrome-devtools/profile/?hl=en @@ -209,3 +222,4 @@ information. [sprockets-sri]: https://github.com/rails/sprockets-rails#sri-support [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting [scss-style-guide]: scss_styleguide.md +[requirements]: ../install/requirements.md#supported-web-browsers -- cgit v1.2.1 From fc94c637b12eabfbf82452eee331458da7193bd6 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Sun, 11 Sep 2016 15:12:42 -0600 Subject: Update Frontend Docs based on feedback. --- doc/development/frontend.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index 9073a11a2f3..f879cd57e25 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -1,13 +1,13 @@ # Frontend Development Guidelines This document describes various guidelines to ensure consistency and quality -across GitLab's frontend. +across GitLab's frontend team. ## Overview -GitLab is built on top of [Ruby on Rails][rails] using [Haml][haml] (with -[Hamlit][hamlit] for performance reasons, be wary of [the limitations this comes -with][hamlit-limits]), [SCSS][scss], and plain JavaScript with +GitLab is built on top of [Ruby on Rails][rails] using [Haml][haml] with +[Hamlit][hamlit]. Be wary of [the limitations that come with using +Hamlit][hamlit-limits]. We also use [SCSS][scss] and plain JavaScript with [ES6 by way of Babel][es6]. The asset pipeline is [Sprockets][sprockets], which handles the concatenation, @@ -19,8 +19,7 @@ minification, and compression of our assets. ### Vue For more complex frontend features, we recommend using Vue.js. It shares -some ideas with React.js while being smaller and – arguably – easier to get -into. +some ideas with React.js as well as Angular. To get started with Vue, read through [their documentation][vue-docs]. @@ -62,9 +61,10 @@ Steps to split page-specific JavaScript from the main `application.js`: = page_specific_javascript_tag('graphs/graphs_bundle.js') ``` -The above loads `chart.js` and `graphs_bundle.js` for only this page. `chart.js` +The above loads `chart.js` and `graphs_bundle.js` for this page only. `chart.js` is separated from the bundle file so it can be cached separately from the bundle -and reused for other pages that also rely on the library. +and reused for other pages that also rely on the library. For an example, see +[this Haml file][page-specific-js-example]. ### Minimizing page size @@ -74,17 +74,17 @@ data is used for users with capped data plans. General tips: -- Don't add unnecessary fonts. +- Don't add new fonts. - Prefer font formats with better compression, e.g. WOFF2 is better than WOFF, which is better than TTF. - Compress and minify assets wherever possible (For CSS/JS, Sprockets does this for us). -- If a piece of functionality can be reasonably done without adding extra libraries, prefer not to use extra libraries. -- Use page-specific JavaScripts as described above to dynamically load libraries that are only needed on certain pages. +- If some functionality can reasonably be achieved without adding extra libraries, avoid them. +- Use page-specific JavaScript as described above to dynamically load libraries that are only needed on certain pages. ## Accessibility ### Resources -The [Chrome Accessibility Developer Tools][chrome-accessibility-developer-tools] +[Chrome Accessibility Developer Tools][chrome-accessibility-developer-tools] are useful for testing for potential accessibility problems in GitLab. Accessibility best-practices and more in-depth information is available on @@ -160,13 +160,11 @@ such as with ReCaptcha, which cannot be used without an `iframe`. ### Avoiding inline scripts and styles -In order to protect users from [XSS vulnerabilities][xss], our intention is to -disable inline scripts entirely using Content Security Policy at some point in -the future. +In order to protect users from [XSS vulnerabilities][xss], we will disable inline scripts in the future using Content Security Policy. While inline scripts can be useful, they're also a security concern. If user-supplied content is unintentionally left un-sanitized, malicious users can -inject scripts into the site. +inject scripts into the web app. Inline styles should be avoided in almost all cases, they should only be used when no alternatives can be found. This allows reusability of styles as well as @@ -180,8 +178,9 @@ See the relevant style guides for our guidelines and for information on linting: ## Testing -Feature tests should be written for all new features as well as any regressions -to prevent them from occurring again. +Feature tests need to be written for all new features. Regression tests +also need to be written for all bug fixes to prevent them from occurring +again in the future. See [the Testing Standards and Style Guidelines](testing.md) for more information. @@ -206,6 +205,7 @@ For our currently-supported browsers, see our [requirements][requirements]. [browser-diet]: https://browserdiet.com/ [d3]: https://d3js.org/ [chartjs]: http://www.chartjs.org/ +[page-specific-js-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/13bb9ed77f405c5f6ee4fdbc964ecf635c9a223f/app/views/projects/graphs/_head.html.haml#L6-8 [chrome-accessibility-developer-tools]: https://github.com/GoogleChrome/accessibility-developer-tools [audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules [observatory-cli]: https://github.com/mozilla/http-observatory-cli) -- cgit v1.2.1 From 6b02127f03b1f8e1dbbecccfc2ba16e62aadcbed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 28 Jul 2016 19:28:56 +0200 Subject: New Members::RequestAccessService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/concerns/membership_actions.rb | 2 +- app/models/concerns/access_requestable.rb | 16 ------ app/models/group.rb | 5 +- app/models/project.rb | 5 +- app/services/members/request_access_service.rb | 25 ++++++++++ lib/api/access_requests.rb | 2 +- spec/models/concerns/access_requestable_spec.rb | 40 --------------- spec/models/group_spec.rb | 7 +++ spec/models/project_spec.rb | 8 +++ spec/requests/api/access_requests_spec.rb | 18 ++++++- .../members/request_access_service_spec.rb | 57 ++++++++++++++++++++++ 11 files changed, 123 insertions(+), 62 deletions(-) delete mode 100644 app/models/concerns/access_requestable.rb create mode 100644 app/services/members/request_access_service.rb delete mode 100644 spec/models/concerns/access_requestable_spec.rb create mode 100644 spec/services/members/request_access_service_spec.rb diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 52682ef9dc9..1d34441e8ea 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -3,7 +3,7 @@ module MembershipActions include MembersHelper def request_access - membershipable.request_access(current_user) + Members::RequestAccessService.new(membershipable, current_user).execute redirect_to polymorphic_path(membershipable), notice: 'Your request for access has been queued for review.' diff --git a/app/models/concerns/access_requestable.rb b/app/models/concerns/access_requestable.rb deleted file mode 100644 index eedd32a729f..00000000000 --- a/app/models/concerns/access_requestable.rb +++ /dev/null @@ -1,16 +0,0 @@ -# == AccessRequestable concern -# -# Contains functionality related to objects that can receive request for access. -# -# Used by Project, and Group. -# -module AccessRequestable - extend ActiveSupport::Concern - - def request_access(user) - members.create( - access_level: Gitlab::Access::DEVELOPER, - user: user, - requested_at: Time.now.utc) - end -end diff --git a/app/models/group.rb b/app/models/group.rb index aefb94b2ada..1f610aef87a 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -3,7 +3,6 @@ require 'carrierwave/orm/activerecord' class Group < Namespace include Gitlab::ConfigHelper include Gitlab::VisibilityLevel - include AccessRequestable include Referable has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' @@ -102,6 +101,10 @@ class Group < Namespace self[:lfs_enabled] end + def request_access(user) + Members::RequestAccessService.new(self, user).execute + end + def add_users(user_ids, access_level, current_user: nil, expires_at: nil) user_ids.each do |user_id| Member.add_user( diff --git a/app/models/project.rb b/app/models/project.rb index 7265cb55594..cebf6199974 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -5,7 +5,6 @@ class Project < ActiveRecord::Base include Gitlab::ShellAdapter include Gitlab::VisibilityLevel include Gitlab::CurrentSettings - include AccessRequestable include Referable include Sortable include AfterCommitQueue @@ -1012,6 +1011,10 @@ class Project < ActiveRecord::Base update_all(updated_at: Time.now) end + def request_access(user) + Members::RequestAccessService.new(self, user).execute + end + def project_member(user) project_members.find_by(user_id: user) end diff --git a/app/services/members/request_access_service.rb b/app/services/members/request_access_service.rb new file mode 100644 index 00000000000..5c0d2d83445 --- /dev/null +++ b/app/services/members/request_access_service.rb @@ -0,0 +1,25 @@ +module Members + class RequestAccessService < BaseService + attr_accessor :source + + def initialize(source, current_user) + @source = source + @current_user = current_user + end + + def execute + raise Gitlab::Access::AccessDeniedError if cannot_request_access?(source) + + source.members.create( + access_level: Gitlab::Access::DEVELOPER, + user: current_user, + requested_at: Time.now.utc) + end + + private + + def cannot_request_access?(source) + !source || !can?(current_user, :request_access, source) + end + end +end diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 29a97ccbd75..895847add9e 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -33,7 +33,7 @@ module API # POST /projects/:id/access_requests post ":id/access_requests" do source = find_source(source_type, params[:id]) - access_requester = source.request_access(current_user) + access_requester = ::Members::RequestAccessService.new(source, current_user).execute if access_requester.persisted? present access_requester.user, with: Entities::AccessRequester, access_requester: access_requester diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb deleted file mode 100644 index 96eee0e8bdd..00000000000 --- a/spec/models/concerns/access_requestable_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'spec_helper' - -describe AccessRequestable do - describe 'Group' do - describe '#request_access' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } - - it { expect(group.request_access(user)).to be_a(GroupMember) } - it { expect(group.request_access(user).user).to eq(user) } - end - - describe '#access_requested?' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } - - before { group.request_access(user) } - - it { expect(group.requesters.exists?(user_id: user)).to be_truthy } - end - end - - describe 'Project' do - describe '#request_access' do - let(:project) { create(:empty_project, :public) } - let(:user) { create(:user) } - - it { expect(project.request_access(user)).to be_a(ProjectMember) } - end - - describe '#access_requested?' do - let(:project) { create(:empty_project, :public) } - let(:user) { create(:user) } - - before { project.request_access(user) } - - it { expect(project.requesters.exists?(user_id: user)).to be_truthy } - end - end -end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 0b3ef9b98fd..c015147f0db 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -105,6 +105,13 @@ describe Group, models: true do it { expect(group.human_name).to eq(group.name) } end + describe '#request_access' do + let(:user) { create(:user) } + + it { expect(group.request_access(user)).to be_a(GroupMember) } + it { expect(group.request_access(user).user).to eq(user) } + end + describe '#add_user' do let(:user) { create(:user) } before { group.add_user(user, GroupMember::MASTER) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 83f61f0af0a..f88636e1c19 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -942,6 +942,14 @@ describe Project, models: true do end end + describe '#request_access' do + let(:project) { create(:empty_project, :public) } + let(:user) { create(:user) } + + it { expect(project.request_access(user)).to be_a(ProjectMember) } + it { expect(project.request_access(user).user).to eq(user) } + end + describe '.search' do let(:project) { create(:project, description: 'kitten mittens') } diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index d78494b76fa..905a7311372 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -64,12 +64,12 @@ describe API::AccessRequests, api: true do context 'when authenticated as a member' do %i[developer master].each do |type| context "as a #{type}" do - it 'returns 400' do + it 'returns 403' do expect do user = public_send(type) post api("/#{source_type.pluralize}/#{source.id}/access_requests", user) - expect(response).to have_http_status(400) + expect(response).to have_http_status(403) end.not_to change { source.requesters.count } end end @@ -87,6 +87,20 @@ describe API::AccessRequests, api: true do end context 'when authenticated as a stranger' do + context "when access request is disabled for the #{source_type}" do + before do + source.update(request_access_enabled: false) + end + + it 'returns 403' do + expect do + post api("/#{source_type.pluralize}/#{source.id}/access_requests", stranger) + + expect(response).to have_http_status(403) + end.not_to change { source.requesters.count } + end + end + it 'returns 201' do expect do post api("/#{source_type.pluralize}/#{source.id}/access_requests", stranger) diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb new file mode 100644 index 00000000000..dff5b4917ae --- /dev/null +++ b/spec/services/members/request_access_service_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe Members::RequestAccessService, services: true do + let(:user) { create(:user) } + let(:project) { create(:project, :private) } + let(:group) { create(:group, :private) } + + shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do + it 'raises Gitlab::Access::AccessDeniedError' do + expect { described_class.new(source, user).execute }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + + shared_examples 'a service creating a access request' do + it 'succeeds' do + expect { described_class.new(source, user).execute }.to change { source.requesters.count }.by(1) + end + + it 'returns a Member' do + member = described_class.new(source, user).execute + + expect(member).to be_a "#{source.class.to_s}Member".constantize + expect(member.requested_at).to be_present + end + end + + context 'when source is nil' do + it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do + let(:source) { nil } + end + end + + context 'when current user cannot request access to the project' do + it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do + let(:source) { project } + end + + it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do + let(:source) { group } + end + end + + context 'when current user can request access to the project' do + before do + project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + group.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it_behaves_like 'a service creating a access request' do + let(:source) { project } + end + + it_behaves_like 'a service creating a access request' do + let(:source) { group } + end + end +end -- cgit v1.2.1 From 178e2758f685ee124c06d2ce8961950a4d39f013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 9 Sep 2016 17:09:43 +0200 Subject: Fix specs that requires an access request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/helpers/members_helper_spec.rb | 4 ++-- spec/mailers/notify_spec.rb | 4 ++-- spec/models/member_spec.rb | 2 +- spec/models/project_spec.rb | 2 +- spec/models/project_team_spec.rb | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb index 7998209b7b0..6703d88e357 100644 --- a/spec/helpers/members_helper_spec.rb +++ b/spec/helpers/members_helper_spec.rb @@ -11,7 +11,7 @@ describe MembersHelper do describe '#remove_member_message' do let(:requester) { build(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project, :public) } let(:project_member) { build(:project_member, project: project) } let(:project_member_invite) { build(:project_member, project: project).tap { |m| m.generate_invite_token! } } let(:project_member_request) { project.request_access(requester) } @@ -32,7 +32,7 @@ describe MembersHelper do describe '#remove_member_title' do let(:requester) { build(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project, :public) } let(:project_member) { build(:project_member, project: project) } let(:project_member_request) { project.request_access(requester) } let(:group) { create(:group) } diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 0363bc74939..2e558018d74 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -402,7 +402,7 @@ describe Notify do describe 'project access requested' do context 'for a project in a user namespace' do - let(:project) { create(:project).tap { |p| p.team << [p.owner, :master, p.owner] } } + let(:project) { create(:project, :public).tap { |p| p.team << [p.owner, :master, p.owner] } } let(:user) { create(:user) } let(:project_member) do project.request_access(user) @@ -429,7 +429,7 @@ describe Notify do context 'for a project in a group' do let(:group_owner) { create(:user) } let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } } - let(:project) { create(:project, namespace: group) } + let(:project) { create(:project, :public, namespace: group) } let(:user) { create(:user) } let(:project_member) do project.request_access(user) diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 0b1634f654a..c2f4601790d 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -57,7 +57,7 @@ describe Member, models: true do describe 'Scopes & finders' do before do - project = create(:empty_project) + project = create(:empty_project, :public) group = create(:group) @owner_user = create(:user).tap { |u| group.add_owner(u) } @owner = group.members.find_by(user_id: @owner_user.id) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f88636e1c19..b8f268e95c3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -68,7 +68,7 @@ describe Project, models: true do it { is_expected.to have_many(:forks).through(:forked_project_links) } describe '#members & #requesters' do - let(:project) { create(:project) } + let(:project) { create(:project, :public) } let(:requester) { create(:user) } let(:developer) { create(:user) } before do diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index f979d66c88c..e0f2dadf189 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -137,7 +137,7 @@ describe ProjectTeam, models: true do describe '#find_member' do context 'personal project' do - let(:project) { create(:empty_project) } + let(:project) { create(:empty_project, :public) } let(:requester) { create(:user) } before do @@ -200,7 +200,7 @@ describe ProjectTeam, models: true do let(:requester) { create(:user) } context 'personal project' do - let(:project) { create(:empty_project) } + let(:project) { create(:empty_project, :public) } context 'when project is not shared with group' do before do -- cgit v1.2.1 From 29cb161e925f787624e7c6b7bdfdeb4d2462c43a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 19 Sep 2016 15:15:01 +0200 Subject: Re-add the AccessRequestable concern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/concerns/access_requestable.rb | 13 ++++++++ app/models/group.rb | 1 + app/models/project.rb | 1 + spec/models/concerns/access_requestable_spec.rb | 40 +++++++++++++++++++++++++ spec/models/group_spec.rb | 7 ----- spec/models/project_spec.rb | 8 ----- 6 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 app/models/concerns/access_requestable.rb create mode 100644 spec/models/concerns/access_requestable_spec.rb diff --git a/app/models/concerns/access_requestable.rb b/app/models/concerns/access_requestable.rb new file mode 100644 index 00000000000..62bc6b809f4 --- /dev/null +++ b/app/models/concerns/access_requestable.rb @@ -0,0 +1,13 @@ +# == AccessRequestable concern +# +# Contains functionality related to objects that can receive request for access. +# +# Used by Project, and Group. +# +module AccessRequestable + extend ActiveSupport::Concern + + def request_access(user) + Members::RequestAccessService.new(self, user).execute + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 1f610aef87a..194fa1ffa69 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -3,6 +3,7 @@ require 'carrierwave/orm/activerecord' class Group < Namespace include Gitlab::ConfigHelper include Gitlab::VisibilityLevel + include AccessRequestable include Referable has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' diff --git a/app/models/project.rb b/app/models/project.rb index cebf6199974..35e1d1a25a8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -5,6 +5,7 @@ class Project < ActiveRecord::Base include Gitlab::ShellAdapter include Gitlab::VisibilityLevel include Gitlab::CurrentSettings + include AccessRequestable include Referable include Sortable include AfterCommitQueue diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb new file mode 100644 index 00000000000..96eee0e8bdd --- /dev/null +++ b/spec/models/concerns/access_requestable_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe AccessRequestable do + describe 'Group' do + describe '#request_access' do + let(:group) { create(:group, :public) } + let(:user) { create(:user) } + + it { expect(group.request_access(user)).to be_a(GroupMember) } + it { expect(group.request_access(user).user).to eq(user) } + end + + describe '#access_requested?' do + let(:group) { create(:group, :public) } + let(:user) { create(:user) } + + before { group.request_access(user) } + + it { expect(group.requesters.exists?(user_id: user)).to be_truthy } + end + end + + describe 'Project' do + describe '#request_access' do + let(:project) { create(:empty_project, :public) } + let(:user) { create(:user) } + + it { expect(project.request_access(user)).to be_a(ProjectMember) } + end + + describe '#access_requested?' do + let(:project) { create(:empty_project, :public) } + let(:user) { create(:user) } + + before { project.request_access(user) } + + it { expect(project.requesters.exists?(user_id: user)).to be_truthy } + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index c015147f0db..0b3ef9b98fd 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -105,13 +105,6 @@ describe Group, models: true do it { expect(group.human_name).to eq(group.name) } end - describe '#request_access' do - let(:user) { create(:user) } - - it { expect(group.request_access(user)).to be_a(GroupMember) } - it { expect(group.request_access(user).user).to eq(user) } - end - describe '#add_user' do let(:user) { create(:user) } before { group.add_user(user, GroupMember::MASTER) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index b8f268e95c3..98f5305a855 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -942,14 +942,6 @@ describe Project, models: true do end end - describe '#request_access' do - let(:project) { create(:empty_project, :public) } - let(:user) { create(:user) } - - it { expect(project.request_access(user)).to be_a(ProjectMember) } - it { expect(project.request_access(user).user).to eq(user) } - end - describe '.search' do let(:project) { create(:project, description: 'kitten mittens') } -- cgit v1.2.1 From 690d19db1eaeb56ade5debd5305a15f73813ec51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 19 Sep 2016 16:04:00 +0200 Subject: Remove duplicate methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/group.rb | 4 ---- app/models/project.rb | 4 ---- 2 files changed, 8 deletions(-) diff --git a/app/models/group.rb b/app/models/group.rb index 194fa1ffa69..aefb94b2ada 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -102,10 +102,6 @@ class Group < Namespace self[:lfs_enabled] end - def request_access(user) - Members::RequestAccessService.new(self, user).execute - end - def add_users(user_ids, access_level, current_user: nil, expires_at: nil) user_ids.each do |user_id| Member.add_user( diff --git a/app/models/project.rb b/app/models/project.rb index 35e1d1a25a8..7265cb55594 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1012,10 +1012,6 @@ class Project < ActiveRecord::Base update_all(updated_at: Time.now) end - def request_access(user) - Members::RequestAccessService.new(self, user).execute - end - def project_member(user) project_members.find_by(user_id: user) end -- cgit v1.2.1 From 94edafdf09fdc36054dcc3a71b34649a48c2a357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 22 Sep 2016 11:11:33 +0200 Subject: Inverse condition in Members::RequestAccessService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/services/members/request_access_service.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/members/request_access_service.rb b/app/services/members/request_access_service.rb index 5c0d2d83445..2614153d900 100644 --- a/app/services/members/request_access_service.rb +++ b/app/services/members/request_access_service.rb @@ -8,7 +8,7 @@ module Members end def execute - raise Gitlab::Access::AccessDeniedError if cannot_request_access?(source) + raise Gitlab::Access::AccessDeniedError unless can_request_access?(source) source.members.create( access_level: Gitlab::Access::DEVELOPER, @@ -18,8 +18,8 @@ module Members private - def cannot_request_access?(source) - !source || !can?(current_user, :request_access, source) + def can_request_access?(source) + source && can?(current_user, :request_access, source) end end end -- cgit v1.2.1 From 089ac50f92f4b7c035b961645b58d33e541b0db9 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 14 Sep 2016 02:38:02 +0100 Subject: Changed mr widget build status to pipeline status with pipeline id, with a link to the pipeline View details now links to pipelines tab Added changelog entry --- CHANGELOG | 1 + app/assets/stylesheets/pages/merge_requests.scss | 3 ++- app/views/projects/merge_requests/widget/_heading.html.haml | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 248ec6faae3..898d03ba38a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,7 @@ v 8.12.0 (unreleased) - Reduce contributions calendar data payload (ClemMakesApps) - Show all pipelines for merge requests even from discarded commits !6414 - Replace contributions calendar timezone payload with dates (ClemMakesApps) + - Changed MR widget build status to pipeline status !6335 - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Enable pipeline events by default !6278 - Move parsing of sidekiq ps into helper !6245 (pascalbetz) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 926247e5e87..3514ee2f35e 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -70,7 +70,8 @@ &.ci-success { color: $gl-success; - a.environment { + a.environment, + a.pipeline { color: inherit; } } diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 44e645a7e81..b5f5e11d4c3 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -4,14 +4,15 @@ .ci_widget{ class: "ci-#{status}", style: ("display:none" unless @pipeline.status == status) } = ci_icon_for_status(status) %span - CI build + Pipeline + = link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline' = ci_label_for_status(status) for - commit = @merge_request.diff_head_commit = succeed "." do = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace" %span.ci-coverage - = link_to "View details", builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'builds'} + = link_to "View details", pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "js-show-tab", data: {action: 'pipelines'} - elsif @merge_request.has_ci? - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX -- cgit v1.2.1 From 76232d541baebb25ca3536364e27b379e5356460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 22 Sep 2016 17:01:00 +0200 Subject: Use AccessRequestable#request_access in MembershipActions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/concerns/membership_actions.rb | 2 +- lib/api/access_requests.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 1d34441e8ea..52682ef9dc9 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -3,7 +3,7 @@ module MembershipActions include MembersHelper def request_access - Members::RequestAccessService.new(membershipable, current_user).execute + membershipable.request_access(current_user) redirect_to polymorphic_path(membershipable), notice: 'Your request for access has been queued for review.' diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 895847add9e..29a97ccbd75 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -33,7 +33,7 @@ module API # POST /projects/:id/access_requests post ":id/access_requests" do source = find_source(source_type, params[:id]) - access_requester = ::Members::RequestAccessService.new(source, current_user).execute + access_requester = source.request_access(current_user) if access_requester.persisted? present access_requester.user, with: Entities::AccessRequester, access_requester: access_requester -- cgit v1.2.1 From 0275a6d4fd7d91bf143d51385f6af623320cc4d3 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 22 Sep 2016 23:17:41 -0500 Subject: implement ci notification email template (first pass) --- .../gitlab-logo-full-horizontal.gif | Bin 0 -> 3654 bytes .../mailers/ci_pipeline_notif_v1/gitlab-logo.gif | Bin 0 -> 3040 bytes .../ci_pipeline_notif_v1/icon-branch-gray.gif | Bin 0 -> 663 bytes .../ci_pipeline_notif_v1/icon-commit-gray.gif | Bin 0 -> 278 bytes .../ci_pipeline_notif_v1/icon-x-red-inverted.gif | Bin 0 -> 591 bytes .../mailers/ci_pipeline_notif_v1/icon-x-red.gif | Bin 0 -> 660 bytes app/views/notify/pipeline_failed_email.html.haml | 209 ++++++++++++++++----- 7 files changed, 167 insertions(+), 42 deletions(-) create mode 100644 app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif create mode 100644 app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo.gif create mode 100644 app/assets/images/mailers/ci_pipeline_notif_v1/icon-branch-gray.gif create mode 100644 app/assets/images/mailers/ci_pipeline_notif_v1/icon-commit-gray.gif create mode 100644 app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif create mode 100644 app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red.gif diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif new file mode 100644 index 00000000000..3f4ef31947b Binary files /dev/null and b/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif differ diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo.gif new file mode 100644 index 00000000000..387628f831c Binary files /dev/null and b/app/assets/images/mailers/ci_pipeline_notif_v1/gitlab-logo.gif differ diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/icon-branch-gray.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-branch-gray.gif new file mode 100644 index 00000000000..5f8f8ca143c Binary files /dev/null and b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-branch-gray.gif differ diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/icon-commit-gray.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-commit-gray.gif new file mode 100644 index 00000000000..8fe3281d2f6 Binary files /dev/null and b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-commit-gray.gif differ diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif new file mode 100644 index 00000000000..798c2531be0 Binary files /dev/null and b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif differ diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red.gif new file mode 100644 index 00000000000..6de166ce0a2 Binary files /dev/null and b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red.gif differ diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 44eff38b571..3196916648c 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -1,43 +1,168 @@ -.p - Project: - = @project.path_with_namespace -.p - Branch: - = @pipeline.ref -.p - Commit: - = @pipeline.short_sha - ( - = @pipeline.sha - ) -.p - Commit Message: - = @pipeline.git_commit_message -.p - Commit Author: - = @pipeline.git_author_name -.p - Pusher: - = @pipeline.user.try(:name) -- failed = @pipeline.statuses.latest.failed -.p - Pipeline # - = @pipeline.id - had - = failed.size - failed - = 'job'.pluralize(failed.size) +!!! +%html{:lang => "en"} + %head + %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ + %meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/ + %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/ + %title + :css + /* CLIENT-SPECIFIC STYLES */ + body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } + img { -ms-interpolation-mode: bicubic; } -- failed.each do |job| - .p - ID: - = job.id - .p - Stage: - = job.stage - .p - Name: - = job.name - .p - Trace: - = job.trace_with_state[:html].html_safe + /* iOS BLUE LINKS */ + a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + font-size: inherit !important; + font-family: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; + } + + /* ANDROID MARGIN HACK */ + body { margin:0 !important; } + div[style*="margin: 16px 0"] { margin:0 !important; } + + @media only screen and (max-width: 639px) { + body, #body { + min-width: 320px !important; + } + table.wrapper { + width: 100% !important; + min-width: 320px !important; + } + table.wrapper > tbody > tr > td { + border-left: 0 !important; + border-right: 0 !important; + border-radius: 0 !important; + padding: 0 10px !important; + } + } + %body{:style => "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} + %table#body{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;"} + %tbody + %tr.line + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;"}   + %tr.header + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} + %img{:alt => "GitLab", :height => "50", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), :width => "55"}/ + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} + %table.wrapper{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:0 25px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} + %table.content{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;border-collapse:separate;border-spacing:0;"} + %tbody + %tr.alert + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;"} + %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;margin:0 auto;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} + %img{:alt => "x", :height => "10", :src => image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), :style => "display:block;", :width => "10"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} Uh oh, your CI pipeline has failed. + %tr.spacer + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} +   + %tr.section + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} + %table.info{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} + - if @project.group + %a.muted{:href => group_url(@project.group), :style => "color:#333333;text-decoration:none;"} + = @project.group.name + - else + %a.muted{:href => user_url(@project.namespace.owner), :style => "color:#333333;text-decoration:none;"} + = @project.namespace.owner.name + \/ + %a.muted{:href => namespace_project_url(@project.namespace, @project), :style => "color:#333333;text-decoration:none;"} + = @project.name + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img{:height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), :style => "display:block;", :width => "13"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %a.muted{:href => namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), :style => "color:#333333;text-decoration:none;"} + = @pipeline.ref + ( + %a{:href => "{{{merge-request-link}}}", :style => "color:#3084bb;text-decoration:none;white-space: nowrap;"}> merge request + ) + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img{:height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), :style => "display:block;", :width => "13"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %a{:href => namespace_project_commit_url(@project.namespace, @project, @pipeline.sha), :style => "color:#3084bb;text-decoration:none;"} + = @pipeline.short_sha + .commit{:style => "color:#5c5c5c;font-weight:300;"} + = @pipeline.git_commit_message.truncate(50) + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Author + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img.avatar{:height => "24", :src => "{{{commit-author-avatar-url}}}", :style => "display:block;border-radius:12px;margin:-2px 0;", :width => "24"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %a.muted{:href => "{{{commit-author-link}}}", :style => "color:#333333;text-decoration:none;"} + = @pipeline.git_author_name + %tr.spacer + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} +   + - failed = @pipeline.statuses.latest.failed + %tr.pre-section + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;"} + Pipeline + %a{:href => "{{{pipline-link}}}", :style => "color:#3084bb;text-decoration:none;"} + = "\##{@pipeline.id}" + had + = failed.size + failed + = 'build'.pluralize(failed.size) + '.' + %tr.warning + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;"} + Logs may contain sensitive data. Please consider before forwarding this email. + %tr.section + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;"} + %table.builds{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;border-collapse:collapse;"} + %tbody + - failed.each do |build| + %tr.build-state + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} + %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;"} + %img{:alt => "x", :height => "10", :src => image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), :style => "display:block;", :width => "10"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;"} + = build.stage + %td{:align => "right", :style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} + %a{:href => "{{{pipeline-build-link}}}", :style => "color:#3084bb;text-decoration:none;"} + = build.name + %tr.build-log + %td{:colspan => "2", :style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"} + %pre{:style => "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;"} + = build.trace_with_state[:html].html_safe + %tr.footer + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} + %img{:alt => "GitLab", :height => "33", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), :style => "display:block;margin:0 auto 1em;", :width => "90"}/ + %div + %a{:href => "{{{manage-notifications-link}}}", :style => "color:#3084bb;text-decoration:none;"} Manage all notifications + · + %a{:href => "{{{help-link}}}", :style => "color:#3084bb;text-decoration:none;"} Help + %div GitLab, 1233 Howard St 2F, San Francisco, CA 94103, USA -- cgit v1.2.1 From ecc1b13f2ca23093d76c220dd27f60878edb8bd8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 23 Sep 2016 19:00:24 +0800 Subject: tabs to spaces --- app/views/notify/pipeline_failed_email.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 3196916648c..d7a626ba625 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -30,7 +30,7 @@ min-width: 320px !important; } table.wrapper { - width: 100% !important; + width: 100% !important; min-width: 320px !important; } table.wrapper > tbody > tr > td { -- cgit v1.2.1 From 0a7ab639dd8c9b9e292237983f2a9ece37102b6f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 23 Sep 2016 19:05:07 +0800 Subject: Only show last 10 lines from trace --- app/views/notify/pipeline_failed_email.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index d7a626ba625..c6e8480e77f 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -157,7 +157,7 @@ %tr.build-log %td{:colspan => "2", :style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"} %pre{:style => "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;"} - = build.trace_with_state[:html].html_safe + = build.trace_html(last_lines: 10).html_safe %tr.footer %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} %img{:alt => "GitLab", :height => "33", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), :style => "display:block;margin:0 auto 1em;", :width => "90"}/ -- cgit v1.2.1 From 2bb52d952718d5f8d78f18c0cec76425a72c919d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 23 Sep 2016 19:11:16 +0800 Subject: Fix test by having a real commit --- spec/models/project_services/pipeline_email_service_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipeline_email_service_spec.rb index c352ddc00c3..1368a2925e8 100644 --- a/spec/models/project_services/pipeline_email_service_spec.rb +++ b/spec/models/project_services/pipeline_email_service_spec.rb @@ -1,7 +1,11 @@ require 'spec_helper' describe PipelinesEmailService do - let(:pipeline) { create(:ci_pipeline) } + let(:pipeline) do + create(:ci_pipeline, project: project, sha: project.commit('master').sha) + end + + let(:project) { create(:project) } let(:recipient) { 'test@gitlab.com' } let(:data) do -- cgit v1.2.1 From da0550c2214ba46b624361190a2ff5c5f3e887c6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 23 Sep 2016 19:11:24 +0800 Subject: Fix url routes --- app/views/notify/pipeline_failed_email.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index c6e8480e77f..9610d6580d1 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -92,7 +92,7 @@ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} %img{:height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), :style => "display:block;", :width => "13"}/ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a.muted{:href => namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), :style => "color:#333333;text-decoration:none;"} + %a.muted{:href => namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), :style => "color:#333333;text-decoration:none;"} = @pipeline.ref ( %a{:href => "{{{merge-request-link}}}", :style => "color:#3084bb;text-decoration:none;white-space: nowrap;"}> merge request -- cgit v1.2.1 From fd51f19c978023160ad759676a0363c12aea3fc8 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 22 Sep 2016 13:56:43 +0100 Subject: API: disable rails session auth for non-GET/HEAD requests --- lib/api/helpers.rb | 5 ++++- spec/requests/api/api_helpers_spec.rb | 39 +++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 714d4ea3dc6..8b8c4eb4d46 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -21,8 +21,11 @@ module API end # Check the Rails session for valid authentication details + # + # Until CSRF protection is added to the API, disallow this method for + # state-changing endpoints def find_user_from_warden - warden ? warden.authenticate : nil + warden.try(:authenticate) if request.get? || request.head? end def find_user_by_private_token diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb index e66faeed705..0f41f8dc7f1 100644 --- a/spec/requests/api/api_helpers_spec.rb +++ b/spec/requests/api/api_helpers_spec.rb @@ -10,7 +10,8 @@ describe API::Helpers, api: true do let(:key) { create(:key, user: user) } let(:params) { {} } - let(:env) { {} } + let(:env) { { 'REQUEST_METHOD' => 'GET' } } + let(:request) { Rack::Request.new(env) } def set_env(token_usr, identifier) clear_env @@ -52,17 +53,43 @@ describe API::Helpers, api: true do describe ".current_user" do subject { current_user } - describe "when authenticating via Warden" do + describe "Warden authentication" do before { doorkeeper_guard_returns false } - context "fails" do - it { is_expected.to be_nil } + context "with invalid credentials" do + context "GET request" do + before { env['REQUEST_METHOD'] = 'GET' } + it { is_expected.to be_nil } + end end - context "succeeds" do + context "with valid credentials" do before { warden_authenticate_returns user } - it { is_expected.to eq(user) } + context "GET request" do + before { env['REQUEST_METHOD'] = 'GET' } + it { is_expected.to eq(user) } + end + + context "HEAD request" do + before { env['REQUEST_METHOD'] = 'HEAD' } + it { is_expected.to eq(user) } + end + + context "PUT request" do + before { env['REQUEST_METHOD'] = 'PUT' } + it { is_expected.to be_nil } + end + + context "POST request" do + before { env['REQUEST_METHOD'] = 'POST' } + it { is_expected.to be_nil } + end + + context "DELETE request" do + before { env['REQUEST_METHOD'] = 'DELETE' } + it { is_expected.to be_nil } + end end end -- cgit v1.2.1 From af5e54f9ce4f491ccf605c7c74c137785da743a4 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 22 Sep 2016 17:07:57 +0100 Subject: Convert label creation from API to controller endpoint --- app/assets/javascripts/api.js | 9 +++++---- .../javascripts/boards/components/new_list_dropdown.js.es6 | 3 +-- app/assets/javascripts/create_label.js.es6 | 9 +++++---- app/assets/javascripts/labels_select.js | 7 ++++--- app/controllers/projects/labels_controller.rb | 10 ++++++++-- app/views/shared/issuable/_filter.html.haml | 2 +- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- 8 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 1cd2302111e..7e5e9fa9ae5 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -5,7 +5,7 @@ namespacesPath: "/api/:version/namespaces.json", groupProjectsPath: "/api/:version/groups/:id/projects.json", projectsPath: "/api/:version/projects.json?simple=true", - labelsPath: "/api/:version/projects/:id/labels", + labelsPath: "/:namespace_path/:project_path/labels", licensePath: "/api/:version/licenses/:key", gitignorePath: "/api/:version/gitignores/:key", gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key", @@ -65,13 +65,14 @@ return callback(projects); }); }, - newLabel: function(project_id, data, callback) { + newLabel: function(namespace_path, project_path, data, callback) { var url = Api.buildUrl(Api.labelsPath) - .replace(':id', project_id); + .replace(':namespace_path', namespace_path) + .replace(':project_path', project_path); return $.ajax({ url: url, type: "POST", - data: data, + data: {'label': data}, dataType: "json" }).done(function(label) { return callback(label); diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 index 1a4d8157970..6ccd83e2d84 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 @@ -3,8 +3,7 @@ $(() => { $('.js-new-board-list').each(function () { const $this = $(this); - - new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('project-id')); + new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path')); $this.glDropdown({ data(term, callback) { diff --git a/app/assets/javascripts/create_label.js.es6 b/app/assets/javascripts/create_label.js.es6 index 46d1c3f00c1..c5f8c29242d 100644 --- a/app/assets/javascripts/create_label.js.es6 +++ b/app/assets/javascripts/create_label.js.es6 @@ -1,8 +1,9 @@ (function (w) { class CreateLabelDropdown { - constructor ($el, projectId) { + constructor ($el, namespacePath, projectPath) { this.$el = $el; - this.projectId = projectId; + this.namespacePath = namespacePath; + this.projectPath = projectPath; this.$dropdownBack = $('.dropdown-menu-back', this.$el.closest('.dropdown')); this.$cancelButton = $('.js-cancel-label-btn', this.$el); this.$newLabelField = $('#new_label_name', this.$el); @@ -91,8 +92,8 @@ e.preventDefault(); e.stopPropagation(); - Api.newLabel(this.projectId, { - name: this.$newLabelField.val(), + Api.newLabel(this.namespacePath, this.projectPath, { + title: this.$newLabelField.val(), color: this.$newColorField.val() }, (label) => { this.$newLabelCreateButton.enable(); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 3f15a117ca8..51a84066185 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -4,9 +4,10 @@ var _this; _this = this; $('.js-label-select').each(function(i, dropdown) { - var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip; + var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip; $dropdown = $(dropdown); - projectId = $dropdown.data('project-id'); + namespacePath = $dropdown.data('namespace-path'); + projectPath = $dropdown.data('project-path'); labelUrl = $dropdown.data('labels'); issueUpdateURL = $dropdown.data('issueUpdate'); selectedLabel = $dropdown.data('selected'); @@ -35,7 +36,7 @@ $sidebarLabelTooltip.tooltip(); if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) { - new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), projectId); + new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), namespacePath, projectPath); } saveLabelData = function() { diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 28fa4a5b141..a6626df4826 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -30,9 +30,15 @@ class Projects::LabelsController < Projects::ApplicationController @label = @project.labels.create(label_params) if @label.valid? - redirect_to namespace_project_labels_path(@project.namespace, @project) + respond_to do |format| + format.html { redirect_to namespace_project_labels_path(@project.namespace, @project) } + format.json { render json: @label } + end else - render 'new' + respond_to do |format| + format.html { render 'new' } + format.json { render json: { message: @label.errors.messages }, status: 400 } + end end end diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index cf26197f7d7..f17096968f6 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -37,7 +37,7 @@ %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" } - if can?(current_user, :admin_list, @project) .dropdown.pull-right - %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, project_id: @project.try(:id) } } + %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } } Create new list .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" } diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index d34d28f6736..ebe441c931e 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -4,7 +4,7 @@ - show_footer = local_assigns.fetch(:show_footer, true) - data_options = local_assigns.fetch(:data_options, {}) - classes = local_assigns.fetch(:classes, []) -- dropdown_data = {toggle: 'dropdown', field_name: 'label_name[]', show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"} +- dropdown_data = {toggle: 'dropdown', field_name: 'label_name[]', show_no: "true", show_any: "true", selected: params[:label_name], namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Label"} - dropdown_data.merge!(data_options) - classes << 'js-extra-options' if extra_options - classes << 'js-filter-submit' if filter_submit diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index b13daaf43c9..0464ebddc11 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -128,7 +128,7 @@ - issuable.labels_array.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} + %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %span.dropdown-toggle-text Label = icon('chevron-down') -- cgit v1.2.1 From 61d650fed72ad15916168cefaf01cdcde888cc59 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 23 Sep 2016 19:45:48 +0800 Subject: Fix the other missing commit --- spec/services/ci/send_pipeline_notification_service_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/services/ci/send_pipeline_notification_service_spec.rb b/spec/services/ci/send_pipeline_notification_service_spec.rb index cb53ba8b051..aa5c14e10f9 100644 --- a/spec/services/ci/send_pipeline_notification_service_spec.rb +++ b/spec/services/ci/send_pipeline_notification_service_spec.rb @@ -1,8 +1,12 @@ require 'spec_helper' describe Ci::SendPipelineNotificationService, services: true do + let(:pipeline) do + create(:ci_pipeline, project: project, sha: project.commit('master').sha) + end + + let(:project) { create(:project) } let(:user) { create(:user) } - let(:pipeline) { create(:ci_pipeline, user: user, status: status) } subject{ described_class.new(pipeline) } describe '#execute' do -- cgit v1.2.1 From 225ab2a7db76862a5517992e029547a0bedfed38 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 23 Sep 2016 20:27:20 +0800 Subject: Cleanup emails between tests --- spec/services/ci/send_pipeline_notification_service_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/services/ci/send_pipeline_notification_service_spec.rb b/spec/services/ci/send_pipeline_notification_service_spec.rb index aa5c14e10f9..e7e1c4f297a 100644 --- a/spec/services/ci/send_pipeline_notification_service_spec.rb +++ b/spec/services/ci/send_pipeline_notification_service_spec.rb @@ -10,6 +10,10 @@ describe Ci::SendPipelineNotificationService, services: true do subject{ described_class.new(pipeline) } describe '#execute' do + before do + reset_delivered_emails! + end + shared_examples 'sending emails' do it 'sends an email to pipeline user' do perform_enqueued_jobs do -- cgit v1.2.1 From db3fe3109ad716ff83387898024474935228195f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 23 Sep 2016 21:43:40 +0800 Subject: Not sure why I missed this --- spec/services/ci/send_pipeline_notification_service_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/services/ci/send_pipeline_notification_service_spec.rb b/spec/services/ci/send_pipeline_notification_service_spec.rb index e7e1c4f297a..f6cf1039287 100644 --- a/spec/services/ci/send_pipeline_notification_service_spec.rb +++ b/spec/services/ci/send_pipeline_notification_service_spec.rb @@ -2,7 +2,11 @@ require 'spec_helper' describe Ci::SendPipelineNotificationService, services: true do let(:pipeline) do - create(:ci_pipeline, project: project, sha: project.commit('master').sha) + create(:ci_pipeline, + project: project, + sha: project.commit('master').sha, + user: user, + status: status) end let(:project) { create(:project) } -- cgit v1.2.1 From bbd7597d401cacb1e14e0b1c22a2acdeffcacea2 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 23 Sep 2016 16:49:05 -0500 Subject: fix some remaining links --- app/views/notify/pipeline_failed_email.html.haml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 9610d6580d1..76d45efd6f7 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -116,11 +116,16 @@ %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} %tbody %tr + - commit = @pipeline.commit %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img.avatar{:height => "24", :src => "{{{commit-author-avatar-url}}}", :style => "display:block;border-radius:12px;margin:-2px 0;", :width => "24"}/ + %img.avatar{:height => "24", :src => avatar_icon(commit.author || commit.author_email, 24), :style => "display:block;border-radius:12px;margin:-2px 0;", :width => "24"}/ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a.muted{:href => "{{{commit-author-link}}}", :style => "color:#333333;text-decoration:none;"} - = @pipeline.git_author_name + - if commit.author.nil? + %span + = commit.author_name + - else + %a.muted{:href => user_path(commit.author), :style => "color:#333333;text-decoration:none;"} + = commit.author.try(:name) || commit.author_name %tr.spacer %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   @@ -128,7 +133,7 @@ %tr.pre-section %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;"} Pipeline - %a{:href => "{{{pipline-link}}}", :style => "color:#3084bb;text-decoration:none;"} + %a{:href => namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id), :style => "color:#3084bb;text-decoration:none;"} = "\##{@pipeline.id}" had = failed.size @@ -152,7 +157,7 @@ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;"} = build.stage %td{:align => "right", :style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} - %a{:href => "{{{pipeline-build-link}}}", :style => "color:#3084bb;text-decoration:none;"} + %a{:href => namespace_project_build_url(@project.namespace, @project, build.id), :style => "color:#3084bb;text-decoration:none;"} = build.name %tr.build-log %td{:colspan => "2", :style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"} @@ -162,7 +167,7 @@ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} %img{:alt => "GitLab", :height => "33", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), :style => "display:block;margin:0 auto 1em;", :width => "90"}/ %div - %a{:href => "{{{manage-notifications-link}}}", :style => "color:#3084bb;text-decoration:none;"} Manage all notifications + %a{:href => profile_notifications_url, :style => "color:#3084bb;text-decoration:none;"} Manage all notifications · - %a{:href => "{{{help-link}}}", :style => "color:#3084bb;text-decoration:none;"} Help + %a{:href => help_url, :style => "color:#3084bb;text-decoration:none;"} Help %div GitLab, 1233 Howard St 2F, San Francisco, CA 94103, USA -- cgit v1.2.1 From ce77bc0bf0958578fd8f3aa7ea606154ae5346ab Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 11:18:52 +0200 Subject: Move container_registry user docs to new location [ci skip] --- doc/administration/container_registry.md | 9 + doc/container_registry/README.md | 99 +------- doc/container_registry/img/container_registry.png | Bin 222782 -> 0 bytes doc/container_registry/img/mitmproxy-docker.png | Bin 407004 -> 0 bytes doc/container_registry/img/project_feature.png | Bin 248750 -> 0 bytes doc/container_registry/troubleshooting.md | 142 +----------- doc/user/project/container_registry.md | 253 +++++++++++++++++++++ doc/user/project/img/container_registry_enable.png | Bin 0 -> 5526 bytes doc/user/project/img/container_registry_panel.png | Bin 0 -> 96315 bytes doc/user/project/img/container_registry_tab.png | Bin 0 -> 7284 bytes doc/user/project/img/mitmproxy-docker.png | Bin 0 -> 407004 bytes 11 files changed, 264 insertions(+), 239 deletions(-) delete mode 100644 doc/container_registry/img/container_registry.png delete mode 100644 doc/container_registry/img/mitmproxy-docker.png delete mode 100644 doc/container_registry/img/project_feature.png create mode 100644 doc/user/project/container_registry.md create mode 100644 doc/user/project/img/container_registry_enable.png create mode 100644 doc/user/project/img/container_registry_panel.png create mode 100644 doc/user/project/img/container_registry_tab.png create mode 100644 doc/user/project/img/mitmproxy-docker.png diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index c5611e2a121..c5ba777d093 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -2,6 +2,15 @@ > [Introduced][ce-4040] in GitLab 8.8. +--- + +> **Note** +Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker +versions earlier than 1.10. +> +This document is about the admin guide. To learn how to use GitLab Container +Registry [user documentation](../user/project/container_registry.md). + With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images. diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md index d7740647a91..fe3e4681ba7 100644 --- a/doc/container_registry/README.md +++ b/doc/container_registry/README.md @@ -1,98 +1 @@ -# GitLab Container Registry - -> [Introduced][ce-4040] in GitLab 8.8. Docker Registry manifest -`v1` support was added in GitLab 8.9 to support Docker versions earlier than 1.10. - -> **Note:** -This document is about the user guide. To learn how to enable GitLab Container -Registry across your GitLab instance, visit the -[administrator documentation](../administration/container_registry.md). - -With the Docker Container Registry integrated into GitLab, every project can -have its own space to store its Docker images. - -You can read more about Docker Registry at https://docs.docker.com/registry/introduction/. - ---- - -## Enable the Container Registry for your project - -1. First, ask your system administrator to enable GitLab Container Registry - following the [administration documentation](../administration/container_registry.md). - If you are using GitLab.com, this is enabled by default so you can start using - the Registry immediately. - -1. Go to your project's settings and enable the **Container Registry** feature - on your project. For new projects this might be enabled by default. For - existing projects you will have to explicitly enable it. - - ![Enable Container Registry](img/project_feature.png) - -## Build and push images - -After you save your project's settings, you should see a new link in the -sidebar called **Container Registry**. Following this link will get you to -your project's Registry panel where you can see how to login to the Container -Registry using your GitLab credentials. - -For example if the Registry's URL is `registry.example.com`, the you should be -able to login with: - -``` -docker login registry.example.com -``` - -Building and publishing images should be a straightforward process. Just make -sure that you are using the Registry URL with the namespace and project name -that is hosted on GitLab: - -``` -docker build -t registry.example.com/group/project . -docker push registry.example.com/group/project -``` - -## Use images from GitLab Container Registry - -To download and run a container from images hosted in GitLab Container Registry, -use `docker run`: - -``` -docker run [options] registry.example.com/group/project [arguments] -``` - -For more information on running Docker containers, visit the -[Docker documentation][docker-docs]. - -## Control Container Registry from within GitLab - -GitLab offers a simple Container Registry management panel. Go to your project -and click **Container Registry** in the left sidebar. - -This view will show you all tags in your project and will easily allow you to -delete them. - -![Container Registry panel](img/container_registry.png) - -## Build and push images using GitLab CI - -> **Note:** -This feature requires GitLab 8.8 and GitLab Runner 1.2. - -Make sure that your GitLab Runner is configured to allow building Docker images by -following the [Using Docker Build](../ci/docker/using_docker_build.md) -and [Using the GitLab Container Registry documentation](../ci/docker/using_docker_build.md#using-the-gitlab-container-registry). - -## Limitations - -In order to use a container image from your private project as an `image:` in -your `.gitlab-ci.yml`, you have to follow the -[Using a private Docker Registry][private-docker] -documentation. This workflow will be simplified in the future. - -## Troubleshooting - -See [the GitLab Docker registry troubleshooting guide](troubleshooting.md). - -[ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040 -[docker-docs]: https://docs.docker.com/engine/userguide/intro/ -[private-docker]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/configuration/advanced-configuration.md#using-a-private-docker-registry +This document was moved in [user/project/container_registry](../user/project/container_registry.md). diff --git a/doc/container_registry/img/container_registry.png b/doc/container_registry/img/container_registry.png deleted file mode 100644 index 57d6f9f22c5..00000000000 Binary files a/doc/container_registry/img/container_registry.png and /dev/null differ diff --git a/doc/container_registry/img/mitmproxy-docker.png b/doc/container_registry/img/mitmproxy-docker.png deleted file mode 100644 index 4e3e37b413d..00000000000 Binary files a/doc/container_registry/img/mitmproxy-docker.png and /dev/null differ diff --git a/doc/container_registry/img/project_feature.png b/doc/container_registry/img/project_feature.png deleted file mode 100644 index a59b4f82b56..00000000000 Binary files a/doc/container_registry/img/project_feature.png and /dev/null differ diff --git a/doc/container_registry/troubleshooting.md b/doc/container_registry/troubleshooting.md index 14c4a7d9a63..2f8cd37b488 100644 --- a/doc/container_registry/troubleshooting.md +++ b/doc/container_registry/troubleshooting.md @@ -1,141 +1 @@ -# Troubleshooting the GitLab Container Registry - -## Basic Troubleshooting - -1. Check to make sure that the system clock on your Docker client and GitLab server have - been synchronized (e.g. via NTP). - -2. If you are using an S3-backed Registry, double check that the IAM - permissions and the S3 credentials (including region) are correct. See [the - sample IAM policy](https://docs.docker.com/registry/storage-drivers/s3/) - for more details. - -3. Check the Registry logs (e.g. `/var/log/gitlab/registry/current`) and the GitLab production logs - for errors (e.g. `/var/log/gitlab/gitlab-rails/production.log`). You may be able to find clues - there. - -## Advanced Troubleshooting - ->**NOTE:** The following section is only recommended for experts. - -Sometimes it's not obvious what is wrong, and you may need to dive deeper into -the communication between the Docker client and the Registry to find out -what's wrong. We will use a concrete example in the past to illustrate how to -diagnose a problem with the S3 setup. - -### Unexpected 403 error during push - -A user attempted to enable an S3-backed Registry. The `docker login` step went -fine. However, when pushing an image, the output showed: - -``` -The push refers to a repository [s3-testing.myregistry.com:4567/root/docker-test] -dc5e59c14160: Pushing [==================================================>] 14.85 kB -03c20c1a019a: Pushing [==================================================>] 2.048 kB -a08f14ef632e: Pushing [==================================================>] 2.048 kB -228950524c88: Pushing 2.048 kB -6a8ecde4cc03: Pushing [==> ] 9.901 MB/205.7 MB -5f70bf18a086: Pushing 1.024 kB -737f40e80b7f: Waiting -82b57dbc5385: Waiting -19429b698a22: Waiting -9436069b92a3: Waiting -error parsing HTTP 403 response body: unexpected end of JSON input: "" -``` - -This error is ambiguous, as it's not clear whether the 403 is coming from the -GitLab Rails application, the Docker Registry, or something else. In this -case, since we know that since the login succeeded, we probably need to look -at the communication between the client and the Registry. - -The REST API between the Docker client and Registry is [described -here](https://docs.docker.com/registry/spec/api/). Normally, one would just -use Wireshark or tcpdump to capture the traffic and see where things went -wrong. However, since all communication between Docker clients and servers -are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even -if you know the private key. What can we do instead? - -One way would be to disable HTTPS by setting up an [insecure -Registry](https://docs.docker.com/registry/insecure/). This could introduce a -security hole and is only recommended for local testing. If you have a -production system and can't or don't want to do this, there is another way: -use mitmproxy, which stands for Man-in-the-Middle Proxy. - -### mitmproxy - -[mitmproxy](https://mitmproxy.org/) allows you to place a proxy between your -client and server to inspect all traffic. One wrinkle is that your system -needs to trust the mitmproxy SSL certificates for this to work. - -The following installation instructions assume you are running Ubuntu: - -1. Install mitmproxy (see http://docs.mitmproxy.org/en/stable/install.html) -1. Run `mitmproxy --port 9000` to generate its certificates. - Enter CTRL-C to quit. -1. Install the certificate from `~/.mitmproxy` to your system: - - ```sh - sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt - sudo update-ca-certificates - ``` - -If successful, the output should indicate that a certificate was added: - -```sh -Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done. -Running hooks in /etc/ca-certificates/update.d....done. -``` - -To verify that the certificates are properly installed, run: - -```sh -mitmproxy --port 9000 -``` - -This will run mitmproxy on port `9000`. In another window, run: - -```sh -curl --proxy http://localhost:9000 https://httpbin.org/status/200 -``` - -If everything is setup correctly, you will see information on the mitmproxy window and -no errors from the curl commands. - -### Running the Docker daemon with a proxy - -For Docker to connect through a proxy, you must start the Docker daemon with the -proper environment variables. The easiest way is to shutdown Docker (e.g. `sudo initctl stop docker`) -and then run Docker by hand. As root, run: - -```sh -export HTTP_PROXY="http://localhost:9000" -export HTTPS_PROXY="https://localhost:9000" -docker daemon --debug -``` - -This will launch the Docker daemon and proxy all connections through mitmproxy. - -### Running the Docker client - -Now that we have mitmproxy and Docker running, we can attempt to login and push -a container image. You may need to run as root to do this. For example: - -```sh -docker login s3-testing.myregistry.com:4567 -docker push s3-testing.myregistry.com:4567/root/docker-test -``` - -In the example above, we see the following trace on the mitmproxy window: - -![mitmproxy output from Docker](img/mitmproxy-docker.png) - -The above image shows: - -* The initial PUT requests went through fine with a 201 status code. -* The 201 redirected the client to the S3 bucket. -* The HEAD request to the AWS bucket reported a 403 Unauthorized. - -What does this mean? This strongly suggests that the S3 user does not have the right -[permissions to perform a HEAD request](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html). -The solution: check the [IAM permissions again](https://docs.docker.com/registry/storage-drivers/s3/). -Once the right permissions were set, the error will go away. +This document was moved to [user/project/container_registry](../user/project/container_registry.md). diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md new file mode 100644 index 00000000000..b205fea2c40 --- /dev/null +++ b/doc/user/project/container_registry.md @@ -0,0 +1,253 @@ +# GitLab Container Registry + +> [Introduced][ce-4040] in GitLab 8.8. + +--- + +> **Note** +Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker +versions earlier than 1.10. +> +This document is about the user guide. To learn how to enable GitLab Container +Registry across your GitLab instance, visit the +[administrator documentation](../../administration/container_registry.md). + +With the Docker Container Registry integrated into GitLab, every project can +have its own space to store its Docker images. + +You can read more about Docker Registry at https://docs.docker.com/registry/introduction/. + +--- + +## Enable the Container Registry for your project + +1. First, ask your system administrator to enable GitLab Container Registry + following the [administration documentation](../../administration/container_registry.md). + If you are using GitLab.com, this is enabled by default so you can start using + the Registry immediately. + +1. Go to your project's settings and enable the **Container Registry** feature + on your project. For new projects this might be enabled by default. For + existing projects (prior GitLab 8.8), you will have to explicitly enable it. + + ![Enable Container Registry](img/container_registry_enable.png) + +1. Hit **Save changes** for the changes to take effect. You should now be able + to see the **Registry** link in the project menu. + + ![Container Registry tab](img/container_registry_tab.png) + +## Build and push images + +If you visit the **Registry** link under your project's menu, you can see the +explicit instructions to login to the Container Registry using your GitLab +credentials. + +For example if the Registry's URL is `registry.example.com`, the you should be +able to login with: + +``` +docker login registry.example.com +``` + +Building and publishing images should be a straightforward process. Just make +sure that you are using the Registry URL with the namespace and project name +that is hosted on GitLab: + +``` +docker build -t registry.example.com/group/project . +docker push registry.example.com/group/project +``` + +Your image will be named after the following scheme: + +``` +// +``` + +As such, the name of the image is unique, but you can differentiate the images +using tags. + +## Use images from GitLab Container Registry + +To download and run a container from images hosted in GitLab Container Registry, +use `docker run`: + +``` +docker run [options] registry.example.com/group/project [arguments] +``` + +For more information on running Docker containers, visit the +[Docker documentation][docker-docs]. + +## Control Container Registry from within GitLab + +GitLab offers a simple Container Registry management panel. Go to your project +and click **Registry** in the project menu. + +This view will show you all tags in your project and will easily allow you to +delete them. + +![Container Registry panel](img/container_registry_panel.png) + +## Build and push images using GitLab CI + +> **Note:** +This feature requires GitLab 8.8 and GitLab Runner 1.2. + +Make sure that your GitLab Runner is configured to allow building Docker images by +following the [Using Docker Build](../ci/docker/using_docker_build.md) +and [Using the GitLab Container Registry documentation](../ci/docker/using_docker_build.md#using-the-gitlab-container-registry). + +## Limitations + +In order to use a container image from your private project as an `image:` in +your `.gitlab-ci.yml`, you have to follow the +[Using a private Docker Registry][private-docker] +documentation. This workflow will be simplified in the future. + +## Troubleshooting the GitLab Container Registry + +### Basic Troubleshooting + +1. Check to make sure that the system clock on your Docker client and GitLab server have + been synchronized (e.g. via NTP). + +2. If you are using an S3-backed Registry, double check that the IAM + permissions and the S3 credentials (including region) are correct. See [the + sample IAM policy](https://docs.docker.com/registry/storage-drivers/s3/) + for more details. + +3. Check the Registry logs (e.g. `/var/log/gitlab/registry/current`) and the GitLab production logs + for errors (e.g. `/var/log/gitlab/gitlab-rails/production.log`). You may be able to find clues + there. + +### Advanced Troubleshooting + +>**NOTE:** The following section is only recommended for experts. + +Sometimes it's not obvious what is wrong, and you may need to dive deeper into +the communication between the Docker client and the Registry to find out +what's wrong. We will use a concrete example in the past to illustrate how to +diagnose a problem with the S3 setup. + +#### Unexpected 403 error during push + +A user attempted to enable an S3-backed Registry. The `docker login` step went +fine. However, when pushing an image, the output showed: + +``` +The push refers to a repository [s3-testing.myregistry.com:4567/root/docker-test] +dc5e59c14160: Pushing [==================================================>] 14.85 kB +03c20c1a019a: Pushing [==================================================>] 2.048 kB +a08f14ef632e: Pushing [==================================================>] 2.048 kB +228950524c88: Pushing 2.048 kB +6a8ecde4cc03: Pushing [==> ] 9.901 MB/205.7 MB +5f70bf18a086: Pushing 1.024 kB +737f40e80b7f: Waiting +82b57dbc5385: Waiting +19429b698a22: Waiting +9436069b92a3: Waiting +error parsing HTTP 403 response body: unexpected end of JSON input: "" +``` + +This error is ambiguous, as it's not clear whether the 403 is coming from the +GitLab Rails application, the Docker Registry, or something else. In this +case, since we know that since the login succeeded, we probably need to look +at the communication between the client and the Registry. + +The REST API between the Docker client and Registry is [described +here](https://docs.docker.com/registry/spec/api/). Normally, one would just +use Wireshark or tcpdump to capture the traffic and see where things went +wrong. However, since all communication between Docker clients and servers +are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even +if you know the private key. What can we do instead? + +One way would be to disable HTTPS by setting up an [insecure +Registry](https://docs.docker.com/registry/insecure/). This could introduce a +security hole and is only recommended for local testing. If you have a +production system and can't or don't want to do this, there is another way: +use mitmproxy, which stands for Man-in-the-Middle Proxy. + +#### mitmproxy + +[mitmproxy](https://mitmproxy.org/) allows you to place a proxy between your +client and server to inspect all traffic. One wrinkle is that your system +needs to trust the mitmproxy SSL certificates for this to work. + +The following installation instructions assume you are running Ubuntu: + +1. Install mitmproxy (see http://docs.mitmproxy.org/en/stable/install.html) +1. Run `mitmproxy --port 9000` to generate its certificates. + Enter CTRL-C to quit. +1. Install the certificate from `~/.mitmproxy` to your system: + + ```sh + sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt + sudo update-ca-certificates + ``` + +If successful, the output should indicate that a certificate was added: + +```sh +Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done. +Running hooks in /etc/ca-certificates/update.d....done. +``` + +To verify that the certificates are properly installed, run: + +```sh +mitmproxy --port 9000 +``` + +This will run mitmproxy on port `9000`. In another window, run: + +```sh +curl --proxy http://localhost:9000 https://httpbin.org/status/200 +``` + +If everything is setup correctly, you will see information on the mitmproxy window and +no errors from the curl commands. + +#### Running the Docker daemon with a proxy + +For Docker to connect through a proxy, you must start the Docker daemon with the +proper environment variables. The easiest way is to shutdown Docker (e.g. `sudo initctl stop docker`) +and then run Docker by hand. As root, run: + +```sh +export HTTP_PROXY="http://localhost:9000" +export HTTPS_PROXY="https://localhost:9000" +docker daemon --debug +``` + +This will launch the Docker daemon and proxy all connections through mitmproxy. + +#### Running the Docker client + +Now that we have mitmproxy and Docker running, we can attempt to login and push +a container image. You may need to run as root to do this. For example: + +```sh +docker login s3-testing.myregistry.com:4567 +docker push s3-testing.myregistry.com:4567/root/docker-test +``` + +In the example above, we see the following trace on the mitmproxy window: + +![mitmproxy output from Docker](img/mitmproxy-docker.png) + +The above image shows: + +* The initial PUT requests went through fine with a 201 status code. +* The 201 redirected the client to the S3 bucket. +* The HEAD request to the AWS bucket reported a 403 Unauthorized. + +What does this mean? This strongly suggests that the S3 user does not have the right +[permissions to perform a HEAD request](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html). +The solution: check the [IAM permissions again](https://docs.docker.com/registry/storage-drivers/s3/). +Once the right permissions were set, the error will go away. + +[ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040 +[docker-docs]: https://docs.docker.com/engine/userguide/intro/ +[private-docker]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/configuration/advanced-configuration.md#using-a-private-docker-registry diff --git a/doc/user/project/img/container_registry_enable.png b/doc/user/project/img/container_registry_enable.png new file mode 100644 index 00000000000..6fffa2a91d8 Binary files /dev/null and b/doc/user/project/img/container_registry_enable.png differ diff --git a/doc/user/project/img/container_registry_panel.png b/doc/user/project/img/container_registry_panel.png new file mode 100644 index 00000000000..60fd76192b7 Binary files /dev/null and b/doc/user/project/img/container_registry_panel.png differ diff --git a/doc/user/project/img/container_registry_tab.png b/doc/user/project/img/container_registry_tab.png new file mode 100644 index 00000000000..36b883aaa97 Binary files /dev/null and b/doc/user/project/img/container_registry_tab.png differ diff --git a/doc/user/project/img/mitmproxy-docker.png b/doc/user/project/img/mitmproxy-docker.png new file mode 100644 index 00000000000..4e3e37b413d Binary files /dev/null and b/doc/user/project/img/mitmproxy-docker.png differ -- cgit v1.2.1 From dffd33252f029901e33883935b20f6b0368d819b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 11:55:14 +0200 Subject: Move reply by email docs to a new location [ci skip] --- config/gitlab.yml.example | 2 +- doc/README.md | 2 +- doc/administration/reply_by_email.md | 302 +++++++++++++++++++ doc/administration/reply_by_email_postfix_setup.md | 324 +++++++++++++++++++++ doc/administration/restart_gitlab.md | 2 +- doc/incoming_email/README.md | 303 +------------------ doc/incoming_email/postfix.md | 322 +------------------- doc/install/installation.md | 2 +- lib/tasks/gitlab/check.rake | 6 +- 9 files changed, 635 insertions(+), 630 deletions(-) create mode 100644 doc/administration/reply_by_email.md create mode 100644 doc/administration/reply_by_email_postfix_setup.md diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1470a6e2550..b26c9f7ccc9 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -111,7 +111,7 @@ production: &base ## Reply by email # Allow users to comment on issues and merge requests by replying to notification emails. - # For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html + # For documentation on how to set this up, see http://doc.gitlab.com/ce/administration/reply_by_email.html incoming_email: enabled: false diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..cb378e68c60 100644 --- a/doc/README.md +++ b/doc/README.md @@ -42,7 +42,7 @@ - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. -- [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. +- [Reply by email](administration/reply_by_email.md) Allow users to comment on issues and merge requests by replying to notification emails. - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md new file mode 100644 index 00000000000..5a9a1582877 --- /dev/null +++ b/doc/administration/reply_by_email.md @@ -0,0 +1,302 @@ +# Reply by email + +GitLab can be set up to allow users to comment on issues and merge requests by +replying to notification emails. + +## Requirement + +Reply by email requires an IMAP-enabled email account. GitLab allows you to use +three strategies for this feature: +- using email sub-addressing +- using a dedicated email address +- using a catch-all mailbox + +### Email sub-addressing + +**If your provider or server supports email sub-addressing, we recommend using it.** + +[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is +a feature where any email to `user+some_arbitrary_tag@example.com` will end up +in the mailbox for `user@example.com`, and is supported by providers such as +Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix +mail server which you can run on-premises. + +### Dedicated email address + +This solution is really simple to set up: you just have to create an email +address dedicated to receive your users' replies to GitLab notifications. + +### Catch-all mailbox + +A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will +"catch all" the emails addressed to the domain that do not exist in the mail +server. + +## How it works? + +### 1. GitLab sends a notification email + +When GitLab sends a notification and Reply by email is enabled, the `Reply-To` +header is set to the address defined in your GitLab configuration, with the +`%{key}` placeholder (if present) replaced by a specific "reply key". In +addition, this "reply key" is also added to the `References` header. + +### 2. You reply to the notification email + +When you reply to the notification email, your email client will: + +- send the email to the `Reply-To` address it got from the notification email +- set the `In-Reply-To` header to the value of the `Message-ID` header from the + notification email +- set the `References` header to the value of the `Message-ID` plus the value of + the notification email's `References` header. + +### 3. GitLab receives your reply to the notification email + +When GitLab receives your reply, it will look for the "reply key" in the +following headers, in this order: + +1. the `To` header +1. the `References` header + +If it finds a reply key, it will be able to leave your reply as a comment on +the entity the notification was about (issue, merge request, commit...). + +For more details about the `Message-ID`, `In-Reply-To`, and `References headers`, +please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4). + +## Set it up + +If you want to use Gmail / Google Apps with Reply by email, make sure you have +[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) +and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255). + +To set up a basic Postfix mail server with IMAP access on Ubuntu, follow +[these instructions](./postfix.md). + +### Omnibus package installations + +1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the + feature and fill in the details for your specific IMAP server and email account: + + ```ruby + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "incoming" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "gitlab.example.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 143 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = false + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + ``` + + ```ruby + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "imap.gmail.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 993 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = true + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + ``` + +1. Reconfigure GitLab and restart mailroom for the changes to take effect: + + ```sh + sudo gitlab-ctl reconfigure + sudo gitlab-ctl restart mailroom + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo gitlab-rake gitlab:incoming_email:check + ``` + +1. Reply by email should now be working. + +### Installations from source + +1. Go to the GitLab installation directory: + + ```sh + cd /home/git/gitlab + ``` + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature + and fill in the details for your specific IMAP server and email account: + + ```sh + sudo editor config/gitlab.yml + ``` + + ```yaml + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "incoming" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "gitlab.example.com" + # IMAP server port + port: 143 + # Whether the IMAP server uses SSL + ssl: false + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + + ```yaml + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + +1. Enable `mail_room` in the init script at `/etc/default/gitlab`: + + ```sh + sudo mkdir -p /etc/default + echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab + ``` + +1. Restart GitLab: + + ```sh + sudo service gitlab restart + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production + ``` + +1. Reply by email should now be working. + +### Development + +1. Go to the GitLab installation directory. + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: + + ```yaml + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. + +1. Uncomment the `mail_room` line in your `Procfile`: + + ```yaml + mail_room: bundle exec mail_room -q -c config/mail_room.yml + ``` + +1. Restart GitLab: + + ```sh + bundle exec foreman start + ``` + +1. Verify that everything is configured correctly: + + ```sh + bundle exec rake gitlab:incoming_email:check RAILS_ENV=development + ``` + +1. Reply by email should now be working. diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md new file mode 100644 index 00000000000..22f10489a6c --- /dev/null +++ b/doc/administration/reply_by_email_postfix_setup.md @@ -0,0 +1,324 @@ +# Set up Postfix for Reply by email + +This document will take you through the steps of setting up a basic Postfix mail +server with IMAP authentication on Ubuntu, to be used with [Reply by email]. + +The instructions make the assumption that you will be using the email address `incoming@gitlab.example.com`, that is, username `incoming` on host `gitlab.example.com`. Don't forget to change it to your actual host when executing the example code snippets. + +## Configure your server firewall + +1. Open up port 25 on your server so that people can send email into the server over SMTP. +2. If the mail server is different from the server running GitLab, open up port 143 on your server so that GitLab can read email from the server over IMAP. + +## Install packages + +1. Install the `postfix` package if it is not installed already: + + ```sh + sudo apt-get install postfix + ``` + + When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`. + +1. Install the `mailutils` package. + + ```sh + sudo apt-get install mailutils + ``` + +## Create user + +1. Create a user for incoming email. + + ```sh + sudo useradd -m -s /bin/bash incoming + ``` + +1. Set a password for this user. + + ```sh + sudo passwd incoming + ``` + + Be sure not to forget this, you'll need it later. + +## Test the out-of-the-box setup + +1. Connect to the local SMTP server: + + ```sh + telnet localhost 25 + ``` + + You should see a prompt like this: + + ```sh + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + 220 gitlab.example.com ESMTP Postfix (Ubuntu) + ``` + + If you get a `Connection refused` error instead, verify that `postfix` is running: + + ```sh + sudo postfix status + ``` + + If it is not, start it: + + ```sh + sudo postfix start + ``` + +1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: + + ``` + ehlo localhost + mail from: root@localhost + rcpt to: incoming@localhost + data + Subject: Re: Some issue + + Sounds good! + . + quit + ``` + + _**Note:** The `.` is a literal period on its own line._ + + _**Note:** If you receive an error after entering `rcpt to: incoming@localhost` + then your Postfix `my_network` configuration is not correct. The error will + say 'Temporary lookup failure'. See + [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._ + +1. Check if the `incoming` user received the email: + + ```sh + su - incoming + mail + ``` + + You should see output like this: + + ``` + "/var/mail/incoming": 1 message 1 unread + >U 1 root@localhost 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + +1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +## Configure Postfix to use Maildir-style mailboxes + +Courier, which we will install later to add IMAP authentication, requires mailboxes to have the Maildir format, rather than mbox. + +1. Configure Postfix to use Maildir-style mailboxes: + + ```sh + sudo postconf -e "home_mailbox = Maildir/" + ``` + +1. Restart Postfix: + + ```sh + sudo /etc/init.d/postfix restart + ``` + +1. Test the new setup: + + 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_. + 1. Check if the `incoming` user received the email: + + ```sh + su - incoming + MAIL=/home/incoming/Maildir + mail + ``` + + You should see output like this: + + ``` + "/home/incoming/Maildir": 1 message 1 unread + >U 1 root@localhost 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + + _**Note:** If `mail` returns an error `Maildir: Is a directory` then your + version of `mail` doesn't support Maildir style mailboxes. Install + `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then, + try the above steps again, substituting `heirloom-mailx` for the `mail` + command._ + +1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +## Install the Courier IMAP server + +1. Install the `courier-imap` package: + + ```sh + sudo apt-get install courier-imap + ``` + +## Configure Postfix to receive email from the internet + +1. Let Postfix know about the domains that it should consider local: + + ```sh + sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost" + ``` + +1. Let Postfix know about the IPs that it should consider part of the LAN: + + We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network. + + ```sh + sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24" + ``` + +1. Configure Postfix to receive mail on all interfaces, which includes the internet: + + ```sh + sudo postconf -e "inet_interfaces = all" + ``` + +1. Configure Postfix to use the `+` delimiter for sub-addressing: + + ```sh + sudo postconf -e "recipient_delimiter = +" + ``` + +1. Restart Postfix: + + ```sh + sudo service postfix restart + ``` + +## Test the final setup + +1. Test SMTP under the new setup: + + 1. Connect to the SMTP server: + + ```sh + telnet gitlab.example.com 25 + ``` + + You should see a prompt like this: + + ```sh + Trying 123.123.123.123... + Connected to gitlab.example.com. + Escape character is '^]'. + 220 gitlab.example.com ESMTP Postfix (Ubuntu) + ``` + + If you get a `Connection refused` error instead, make sure your firewall is setup to allow inbound traffic on port 25. + + 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: + + ``` + ehlo gitlab.example.com + mail from: root@gitlab.example.com + rcpt to: incoming@gitlab.example.com + data + Subject: Re: Some issue + + Sounds good! + . + quit + ``` + + (Note: The `.` is a literal period on its own line) + + 1. Check if the `incoming` user received the email: + + ```sh + su - incoming + MAIL=/home/incoming/Maildir + mail + ``` + + You should see output like this: + + ``` + "/home/incoming/Maildir": 1 message 1 unread + >U 1 root@gitlab.example.com 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + + 1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +1. Test IMAP under the new setup: + + 1. Connect to the IMAP server: + + ```sh + telnet gitlab.example.com 143 + ``` + + You should see a prompt like this: + + ```sh + Trying 123.123.123.123... + Connected to mail.example.gitlab.com. + Escape character is '^]'. + - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information. + ``` + + 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt: + + ``` + a login incoming PASSWORD + ``` + + Replace PASSWORD with the password you set on the `incoming` user earlier. + + You should see output like this: + + ``` + a OK LOGIN Ok. + ``` + + 1. Disconnect from the IMAP server: + + ```sh + a logout + ``` + +## Done! + +If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [Reply by email](./README.md) guide to configure GitLab. + +--- + +_This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._ + +[reply by email]: reply_by_email.md diff --git a/doc/administration/restart_gitlab.md b/doc/administration/restart_gitlab.md index 483060395dd..b561c2f82aa 100644 --- a/doc/administration/restart_gitlab.md +++ b/doc/administration/restart_gitlab.md @@ -139,7 +139,7 @@ If you are using other init systems, like systemd, you can check the [omnibus-dl]: https://about.gitlab.com/downloads/ "Download the Omnibus packages" [install]: ../install/installation.md "Documentation to install GitLab from source" -[mailroom]: ../incoming_email/README.md "Used for replying by email in GitLab issues and merge requests" +[mailroom]: reply_by_email.md "Used for replying by email in GitLab issues and merge requests" [chef]: https://www.chef.io/chef/ "Chef official website" [src-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab "GitLab init service file" [gl-recipes]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/init "GitLab Recipes repository" diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md index 5a9a1582877..db0f03f2c98 100644 --- a/doc/incoming_email/README.md +++ b/doc/incoming_email/README.md @@ -1,302 +1 @@ -# Reply by email - -GitLab can be set up to allow users to comment on issues and merge requests by -replying to notification emails. - -## Requirement - -Reply by email requires an IMAP-enabled email account. GitLab allows you to use -three strategies for this feature: -- using email sub-addressing -- using a dedicated email address -- using a catch-all mailbox - -### Email sub-addressing - -**If your provider or server supports email sub-addressing, we recommend using it.** - -[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is -a feature where any email to `user+some_arbitrary_tag@example.com` will end up -in the mailbox for `user@example.com`, and is supported by providers such as -Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix -mail server which you can run on-premises. - -### Dedicated email address - -This solution is really simple to set up: you just have to create an email -address dedicated to receive your users' replies to GitLab notifications. - -### Catch-all mailbox - -A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will -"catch all" the emails addressed to the domain that do not exist in the mail -server. - -## How it works? - -### 1. GitLab sends a notification email - -When GitLab sends a notification and Reply by email is enabled, the `Reply-To` -header is set to the address defined in your GitLab configuration, with the -`%{key}` placeholder (if present) replaced by a specific "reply key". In -addition, this "reply key" is also added to the `References` header. - -### 2. You reply to the notification email - -When you reply to the notification email, your email client will: - -- send the email to the `Reply-To` address it got from the notification email -- set the `In-Reply-To` header to the value of the `Message-ID` header from the - notification email -- set the `References` header to the value of the `Message-ID` plus the value of - the notification email's `References` header. - -### 3. GitLab receives your reply to the notification email - -When GitLab receives your reply, it will look for the "reply key" in the -following headers, in this order: - -1. the `To` header -1. the `References` header - -If it finds a reply key, it will be able to leave your reply as a comment on -the entity the notification was about (issue, merge request, commit...). - -For more details about the `Message-ID`, `In-Reply-To`, and `References headers`, -please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4). - -## Set it up - -If you want to use Gmail / Google Apps with Reply by email, make sure you have -[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) -and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255). - -To set up a basic Postfix mail server with IMAP access on Ubuntu, follow -[these instructions](./postfix.md). - -### Omnibus package installations - -1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the - feature and fill in the details for your specific IMAP server and email account: - - ```ruby - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "incoming" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "gitlab.example.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 143 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = false - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - ``` - - ```ruby - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "imap.gmail.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 993 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = true - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - ``` - -1. Reconfigure GitLab and restart mailroom for the changes to take effect: - - ```sh - sudo gitlab-ctl reconfigure - sudo gitlab-ctl restart mailroom - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo gitlab-rake gitlab:incoming_email:check - ``` - -1. Reply by email should now be working. - -### Installations from source - -1. Go to the GitLab installation directory: - - ```sh - cd /home/git/gitlab - ``` - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature - and fill in the details for your specific IMAP server and email account: - - ```sh - sudo editor config/gitlab.yml - ``` - - ```yaml - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "incoming" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "gitlab.example.com" - # IMAP server port - port: 143 - # Whether the IMAP server uses SSL - ssl: false - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - -1. Enable `mail_room` in the init script at `/etc/default/gitlab`: - - ```sh - sudo mkdir -p /etc/default - echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab - ``` - -1. Restart GitLab: - - ```sh - sudo service gitlab restart - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production - ``` - -1. Reply by email should now be working. - -### Development - -1. Go to the GitLab installation directory. - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - - As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. - -1. Uncomment the `mail_room` line in your `Procfile`: - - ```yaml - mail_room: bundle exec mail_room -q -c config/mail_room.yml - ``` - -1. Restart GitLab: - - ```sh - bundle exec foreman start - ``` - -1. Verify that everything is configured correctly: - - ```sh - bundle exec rake gitlab:incoming_email:check RAILS_ENV=development - ``` - -1. Reply by email should now be working. +This document was moved to [administration/reply_by_email](../administration/reply_by_email.md). diff --git a/doc/incoming_email/postfix.md b/doc/incoming_email/postfix.md index 787d21f7f8f..90833238ac5 100644 --- a/doc/incoming_email/postfix.md +++ b/doc/incoming_email/postfix.md @@ -1,321 +1 @@ -# Set up Postfix for Reply by email - -This document will take you through the steps of setting up a basic Postfix mail server with IMAP authentication on Ubuntu, to be used with Reply by email. - -The instructions make the assumption that you will be using the email address `incoming@gitlab.example.com`, that is, username `incoming` on host `gitlab.example.com`. Don't forget to change it to your actual host when executing the example code snippets. - -## Configure your server firewall - -1. Open up port 25 on your server so that people can send email into the server over SMTP. -2. If the mail server is different from the server running GitLab, open up port 143 on your server so that GitLab can read email from the server over IMAP. - -## Install packages - -1. Install the `postfix` package if it is not installed already: - - ```sh - sudo apt-get install postfix - ``` - - When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`. - -1. Install the `mailutils` package. - - ```sh - sudo apt-get install mailutils - ``` - -## Create user - -1. Create a user for incoming email. - - ```sh - sudo useradd -m -s /bin/bash incoming - ``` - -1. Set a password for this user. - - ```sh - sudo passwd incoming - ``` - - Be sure not to forget this, you'll need it later. - -## Test the out-of-the-box setup - -1. Connect to the local SMTP server: - - ```sh - telnet localhost 25 - ``` - - You should see a prompt like this: - - ```sh - Trying 127.0.0.1... - Connected to localhost. - Escape character is '^]'. - 220 gitlab.example.com ESMTP Postfix (Ubuntu) - ``` - - If you get a `Connection refused` error instead, verify that `postfix` is running: - - ```sh - sudo postfix status - ``` - - If it is not, start it: - - ```sh - sudo postfix start - ``` - -1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: - - ``` - ehlo localhost - mail from: root@localhost - rcpt to: incoming@localhost - data - Subject: Re: Some issue - - Sounds good! - . - quit - ``` - - _**Note:** The `.` is a literal period on its own line._ - - _**Note:** If you receive an error after entering `rcpt to: incoming@localhost` - then your Postfix `my_network` configuration is not correct. The error will - say 'Temporary lookup failure'. See - [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._ - -1. Check if the `incoming` user received the email: - - ```sh - su - incoming - mail - ``` - - You should see output like this: - - ``` - "/var/mail/incoming": 1 message 1 unread - >U 1 root@localhost 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - -1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -## Configure Postfix to use Maildir-style mailboxes - -Courier, which we will install later to add IMAP authentication, requires mailboxes to have the Maildir format, rather than mbox. - -1. Configure Postfix to use Maildir-style mailboxes: - - ```sh - sudo postconf -e "home_mailbox = Maildir/" - ``` - -1. Restart Postfix: - - ```sh - sudo /etc/init.d/postfix restart - ``` - -1. Test the new setup: - - 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_. - 1. Check if the `incoming` user received the email: - - ```sh - su - incoming - MAIL=/home/incoming/Maildir - mail - ``` - - You should see output like this: - - ``` - "/home/incoming/Maildir": 1 message 1 unread - >U 1 root@localhost 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - - _**Note:** If `mail` returns an error `Maildir: Is a directory` then your - version of `mail` doesn't support Maildir style mailboxes. Install - `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then, - try the above steps again, substituting `heirloom-mailx` for the `mail` - command._ - -1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -## Install the Courier IMAP server - -1. Install the `courier-imap` package: - - ```sh - sudo apt-get install courier-imap - ``` - -## Configure Postfix to receive email from the internet - -1. Let Postfix know about the domains that it should consider local: - - ```sh - sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost" - ``` - -1. Let Postfix know about the IPs that it should consider part of the LAN: - - We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network. - - ```sh - sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24" - ``` - -1. Configure Postfix to receive mail on all interfaces, which includes the internet: - - ```sh - sudo postconf -e "inet_interfaces = all" - ``` - -1. Configure Postfix to use the `+` delimiter for sub-addressing: - - ```sh - sudo postconf -e "recipient_delimiter = +" - ``` - -1. Restart Postfix: - - ```sh - sudo service postfix restart - ``` - -## Test the final setup - -1. Test SMTP under the new setup: - - 1. Connect to the SMTP server: - - ```sh - telnet gitlab.example.com 25 - ``` - - You should see a prompt like this: - - ```sh - Trying 123.123.123.123... - Connected to gitlab.example.com. - Escape character is '^]'. - 220 gitlab.example.com ESMTP Postfix (Ubuntu) - ``` - - If you get a `Connection refused` error instead, make sure your firewall is setup to allow inbound traffic on port 25. - - 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: - - ``` - ehlo gitlab.example.com - mail from: root@gitlab.example.com - rcpt to: incoming@gitlab.example.com - data - Subject: Re: Some issue - - Sounds good! - . - quit - ``` - - (Note: The `.` is a literal period on its own line) - - 1. Check if the `incoming` user received the email: - - ```sh - su - incoming - MAIL=/home/incoming/Maildir - mail - ``` - - You should see output like this: - - ``` - "/home/incoming/Maildir": 1 message 1 unread - >U 1 root@gitlab.example.com 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - - 1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -1. Test IMAP under the new setup: - - 1. Connect to the IMAP server: - - ```sh - telnet gitlab.example.com 143 - ``` - - You should see a prompt like this: - - ```sh - Trying 123.123.123.123... - Connected to mail.example.gitlab.com. - Escape character is '^]'. - - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information. - ``` - - 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt: - - ``` - a login incoming PASSWORD - ``` - - Replace PASSWORD with the password you set on the `incoming` user earlier. - - You should see output like this: - - ``` - a OK LOGIN Ok. - ``` - - 1. Disconnect from the IMAP server: - - ```sh - a logout - ``` - -## Done! - -If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [Reply by email](./README.md) guide to configure GitLab. - ---------- - -_This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._ +This document was moved to [administration/reply_by_email_postfix_setup](../administration/reply_by_email_postfix_setup.md). diff --git a/doc/install/installation.md b/doc/install/installation.md index 3ac813aa914..da3f92f9a6c 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -563,7 +563,7 @@ Using a self-signed certificate is discouraged but if you must use it follow the ### Enable Reply by email -See the ["Reply by email" documentation](../incoming_email/README.md) for more information on how to set this up. +See the ["Reply by email" documentation](../administration/reply_by_email.md) for more information on how to set this up. ### LDAP Authentication diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 5f4a6bbfa35..2ae48a970ce 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -671,7 +671,7 @@ namespace :gitlab do "Enable mail_room in the init.d configuration." ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end @@ -690,7 +690,7 @@ namespace :gitlab do "Enable mail_room in your Procfile." ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end @@ -747,7 +747,7 @@ namespace :gitlab do "Check that the information in config/gitlab.yml is correct" ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end -- cgit v1.2.1 From 6207dc9037601d5188c86876c2ff79d6ddbbe540 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 12:16:14 +0200 Subject: Move monitoring/ to new location --- doc/README.md | 4 +- doc/administration/monitoring/health_check.md | 66 +++++++ .../monitoring/img/health_check_token.png | Bin 0 -> 6630 bytes .../monitoring/performance/gitlab_configuration.md | 40 +++++ .../performance/grafana_configuration.md | 111 ++++++++++++ .../performance/img/grafana_dashboard_dropdown.png | Bin 0 -> 14368 bytes .../performance/img/grafana_dashboard_import.png | Bin 0 -> 18267 bytes .../img/grafana_data_source_configuration.png | Bin 0 -> 26060 bytes .../performance/img/grafana_data_source_empty.png | Bin 0 -> 21821 bytes .../performance/img/grafana_save_icon.png | Bin 0 -> 9107 bytes .../img/metrics_gitlab_configuration_settings.png | Bin 0 -> 61357 bytes .../performance/influxdb_configuration.md | 193 ++++++++++++++++++++ .../monitoring/performance/influxdb_schema.md | 97 +++++++++++ .../monitoring/performance/introduction.md | 65 +++++++ doc/monitoring/health_check.md | 67 +------ doc/monitoring/img/health_check_token.png | Bin 6630 -> 0 bytes doc/monitoring/performance/gitlab_configuration.md | 41 +---- .../performance/grafana_configuration.md | 112 +----------- .../performance/influxdb_configuration.md | 194 +-------------------- doc/monitoring/performance/influxdb_schema.md | 98 +---------- doc/monitoring/performance/introduction.md | 66 +------ 21 files changed, 580 insertions(+), 574 deletions(-) create mode 100644 doc/administration/monitoring/health_check.md create mode 100644 doc/administration/monitoring/img/health_check_token.png create mode 100644 doc/administration/monitoring/performance/gitlab_configuration.md create mode 100644 doc/administration/monitoring/performance/grafana_configuration.md create mode 100644 doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png create mode 100644 doc/administration/monitoring/performance/img/grafana_dashboard_import.png create mode 100644 doc/administration/monitoring/performance/img/grafana_data_source_configuration.png create mode 100644 doc/administration/monitoring/performance/img/grafana_data_source_empty.png create mode 100644 doc/administration/monitoring/performance/img/grafana_save_icon.png create mode 100644 doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png create mode 100644 doc/administration/monitoring/performance/influxdb_configuration.md create mode 100644 doc/administration/monitoring/performance/influxdb_schema.md create mode 100644 doc/administration/monitoring/performance/introduction.md delete mode 100644 doc/monitoring/img/health_check_token.png diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..5a6f467c17c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -46,8 +46,8 @@ - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. -- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. -- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint. +- [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. +- [Monitoring uptime](administration/monitoring/health_check.md) Check the server status using the health check endpoint. - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. diff --git a/doc/administration/monitoring/health_check.md b/doc/administration/monitoring/health_check.md new file mode 100644 index 00000000000..eac57bc3de4 --- /dev/null +++ b/doc/administration/monitoring/health_check.md @@ -0,0 +1,66 @@ +# Health Check + +> [Introduced][ce-3888] in GitLab 8.8. + +GitLab provides a health check endpoint for uptime monitoring on the `health_check` web +endpoint. The health check reports on the overall system status based on the status of +the database connection, the state of the database migrations, and the ability to write +and access the cache. This endpoint can be provided to uptime monitoring services like +[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. + +## Access Token + +An access token needs to be provided while accessing the health check endpoint. The current +accepted token can be found on the `admin/health_check` page of your GitLab instance. + +![access token](img/health_check_token.png) + +The access token can be passed as a URL parameter: + +``` +https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN +``` + +or as an HTTP header: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +## Using the Endpoint + +Once you have the access token, health information can be retrieved as plain text, JSON, +or XML using the `health_check` endpoint: + +- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` + +You can also ask for the status of specific services: + +- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` + +For example, the JSON output of the following health check: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +would be like: + +``` +{"healthy":true,"message":"success"} +``` + +## Status + +On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint +will return a valid successful HTTP status code, and a `success` message. Ideally your +uptime monitoring should look for the success message. + +[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 +[pingdom]: https://www.pingdom.com +[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html +[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/administration/monitoring/img/health_check_token.png b/doc/administration/monitoring/img/health_check_token.png new file mode 100644 index 00000000000..2d7c82a65a8 Binary files /dev/null and b/doc/administration/monitoring/img/health_check_token.png differ diff --git a/doc/administration/monitoring/performance/gitlab_configuration.md b/doc/administration/monitoring/performance/gitlab_configuration.md new file mode 100644 index 00000000000..771584268d9 --- /dev/null +++ b/doc/administration/monitoring/performance/gitlab_configuration.md @@ -0,0 +1,40 @@ +# GitLab Configuration + +GitLab Performance Monitoring is disabled by default. To enable it and change any of its +settings, navigate to the Admin area in **Settings > Metrics** +(`/admin/application_settings`). + +The minimum required settings you need to set are the InfluxDB host and port. +Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the +changes. + +--- + +![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png) + +--- + +Finally, a restart of all GitLab processes is required for the changes to take +effect: + +```bash +# For Omnibus installations +sudo gitlab-ctl restart + +# For installations from source +sudo service gitlab restart +``` + +## Pending Migrations + +When any migrations are pending, the metrics are disabled until the migrations +have been performed. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md new file mode 100644 index 00000000000..7947b0fedc4 --- /dev/null +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -0,0 +1,111 @@ +# Grafana Configuration + +[Grafana](http://grafana.org/) is a tool that allows you to visualize time +series metrics through graphs and dashboards. It supports several backend +data stores, including InfluxDB. GitLab writes performance data to InfluxDB +and Grafana will allow you to query InfluxDB to display useful graphs. + +For the easiest installation and configuration, install Grafana on the same +server as InfluxDB. For larger installations, you may want to split out these +services. + +## Installation + +Grafana supplies package repositories (Yum/Apt) for easy installation. +See [Grafana installation documentation](http://docs.grafana.org/installation/) +for detailed steps. + +> **Note**: Before starting Grafana for the first time, set the admin user +and password in `/etc/grafana/grafana.ini`. Otherwise, the default password +will be `admin`. + +## Configuration + +Login as the admin user. Expand the menu by clicking the Grafana logo in the +top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new' +in the top bar. + +![Grafana empty data source page](img/grafana_data_source_empty.png) + +Fill in the configuration details for the InfluxDB data source. Save and +Test Connection to ensure the configuration is correct. + +- **Name**: InfluxDB +- **Default**: Checked +- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x) +- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB +on a separate server) +- **Access**: proxy +- **Database**: gitlab +- **User**: admin (Or the username configured when setting up InfluxDB) +- **Password**: The password configured when you set up InfluxDB + +![Grafana data source configurations](img/grafana_data_source_configuration.png) + +## Apply retention policies and create continuous queries + +If you intend to import the GitLab provided Grafana dashboards, you will need to +set up the right retention policies and continuous queries. The easiest way of +doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management) +repository. + +To use this repository you must first clone it: + +``` +git clone https://gitlab.com/gitlab-org/influxdb-management.git +cd influxdb-management +``` + +Next you must install the required dependencies: + +``` +gem install bundler +bundle install +``` + +Now you must configure the repository by first copying `.env.example` to `.env` +and then editing the `.env` file to contain the correct InfluxDB settings. Once +configured you can simply run `bundle exec rake` and the InfluxDB database will +be configured for you. + +For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md). + +## Import Dashboards + +You can now import a set of default dashboards that will give you a good +start on displaying useful information. GitLab has published a set of default +[Grafana dashboards][grafana-dashboards] to get you started. Clone the +repository or download a zip/tarball, then follow these steps to import each +JSON file. + +Open the dashboard dropdown menu and click 'Import' + +![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) + +Click 'Choose file' and browse to the location where you downloaded or cloned +the dashboard repository. Pick one of the JSON files to import. + +![Grafana dashboard import](img/grafana_dashboard_import.png) + +Once the dashboard is imported, be sure to click save icon in the top bar. If +you do not save the dashboard after importing it will be removed when you +navigate away. + +![Grafana save icon](img/grafana_save_icon.png) + +Repeat this process for each dashboard you wish to import. + +Alternatively you can automatically import all the dashboards into your Grafana +instance. See the README of the [Grafana dashboards][grafana-dashboards] +repository for more information on this process. + +[grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Installation/Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png b/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png new file mode 100644 index 00000000000..7e34fad71ce Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_import.png b/doc/administration/monitoring/performance/img/grafana_dashboard_import.png new file mode 100644 index 00000000000..f97624365c7 Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_dashboard_import.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png b/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png new file mode 100644 index 00000000000..7d50e4c88c2 Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_empty.png b/doc/administration/monitoring/performance/img/grafana_data_source_empty.png new file mode 100644 index 00000000000..aa39a53acae Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_data_source_empty.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_save_icon.png b/doc/administration/monitoring/performance/img/grafana_save_icon.png new file mode 100644 index 00000000000..c740e33cd1c Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_save_icon.png differ diff --git a/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png new file mode 100644 index 00000000000..db396423e30 Binary files /dev/null and b/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png differ diff --git a/doc/administration/monitoring/performance/influxdb_configuration.md b/doc/administration/monitoring/performance/influxdb_configuration.md new file mode 100644 index 00000000000..c30cd2950d8 --- /dev/null +++ b/doc/administration/monitoring/performance/influxdb_configuration.md @@ -0,0 +1,193 @@ +# InfluxDB Configuration + +The default settings provided by [InfluxDB] are not sufficient for a high traffic +GitLab environment. The settings discussed in this document are based on the +settings GitLab uses for GitLab.com, depending on your own needs you may need to +further adjust them. + +If you are intending to run InfluxDB on the same server as GitLab, make sure +you have plenty of RAM since InfluxDB can use quite a bit depending on traffic. + +Unless you are going with a budget setup, it's advised to run it separately. + +## Requirements + +- InfluxDB 0.9.5 or newer +- A fairly modern version of Linux +- At least 4GB of RAM +- At least 10GB of storage for InfluxDB data + +Note that the RAM and storage requirements can differ greatly depending on the +amount of data received/stored. To limit the amount of stored data users can +look into [InfluxDB Retention Policies][influxdb-retention]. + +## Installation + +Installing InfluxDB is out of the scope of this document. Please refer to the +[InfluxDB documentation]. + +## InfluxDB Server Settings + +Since InfluxDB has many settings that users may wish to customize themselves +(e.g. what port to run InfluxDB on), we'll only cover the essentials. + +The configuration file in question is usually located at +`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file, +InfluxDB needs to be restarted. + +### Storage Engine + +InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new +storage engine is available, called [TSM Tree]. All users **must** use the new +`tsm1` storage engine as this [will be the default engine][tsm1-commit] in +upcoming InfluxDB releases. + +Make sure you have the following in your configuration file: + +``` +[data] + dir = "/var/lib/influxdb/data" + engine = "tsm1" +``` + +### Admin Panel + +Production environments should have the InfluxDB admin panel **disabled**. This +feature can be disabled by adding the following to your InfluxDB configuration +file: + +``` +[admin] + enabled = false +``` + +### HTTP + +HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana, +thus it should be enabled. When enabling make sure to _also_ enable +authentication: + +``` +[http] + enabled = true + auth-enabled = true +``` + +_**Note:** Before you enable authentication, you might want to [create an +admin user](#create-a-new-admin-user)._ + +### UDP + +GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling +UDP can be done using the following settings: + +``` +[[udp]] + enabled = true + bind-address = ":8089" + database = "gitlab" + batch-size = 1000 + batch-pending = 5 + batch-timeout = "1s" + read-buffer = 209715200 +``` + +This does the following: + +1. Enable UDP and bind it to port 8089 for all addresses. +2. Store any data received in the "gitlab" database. +3. Define a batch of points to be 1000 points in size and allow a maximum of + 5 batches _or_ flush them automatically after 1 second. +4. Define a UDP read buffer size of 200 MB. + +One of the most important settings here is the UDP read buffer size as if this +value is set too low, packets will be dropped. You must also make sure the OS +buffer size is set to the same value, the default value is almost never enough. + +To set the OS buffer size to 200 MB, on Linux you can run the following command: + +```bash +sysctl -w net.core.rmem_max=209715200 +``` + +To make this permanent, add the following to `/etc/sysctl.conf` and restart the +server: + +```bash +net.core.rmem_max=209715200 +``` + +It is **very important** to make sure the buffer sizes are large enough to +handle all data sent to InfluxDB as otherwise you _will_ lose data. The above +buffer sizes are based on the traffic for GitLab.com. Depending on the amount of +traffic, users may be able to use a smaller buffer size, but we highly recommend +using _at least_ 100 MB. + +When enabling UDP, users should take care to not expose the port to the public, +as doing so will allow anybody to write data into your InfluxDB database (as +[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either +whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only +allowing traffic from members of said VLAN. + +## Create a new admin user + +If you want to [enable authentication](#http), you might want to [create an +admin user][influx-admin]: + +``` +influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES" +``` + +## Create the `gitlab` database + +Once you get InfluxDB up and running, you need to create a database for GitLab. +Make sure you have changed the [storage engine](#storage-engine) to `tsm1` +before creating a database. + +_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled +[HTTP authentication](#http), remember to append the username (`-username `) +and password (`-password `) you set earlier to the commands below._ + +Run the following command to create a database named `gitlab`: + +```bash +influx -execute 'CREATE DATABASE gitlab' +``` + +The name **must** be `gitlab`, do not use any other name. + +Next, make sure that the database was successfully created: + +```bash +influx -execute 'SHOW DATABASES' +``` + +The output should be similar to: + +``` +name: databases +--------------- +name +_internal +gitlab +``` + +That's it! Now your GitLab instance should send data to InfluxDB. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) + +[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management +[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ +[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/ +[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/ +[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d +[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user diff --git a/doc/administration/monitoring/performance/influxdb_schema.md b/doc/administration/monitoring/performance/influxdb_schema.md new file mode 100644 index 00000000000..eff0e29f58d --- /dev/null +++ b/doc/administration/monitoring/performance/influxdb_schema.md @@ -0,0 +1,97 @@ +# InfluxDB Schema + +The following measurements are currently stored in InfluxDB: + +- `PROCESS_file_descriptors` +- `PROCESS_gc_statistics` +- `PROCESS_memory_usage` +- `PROCESS_method_calls` +- `PROCESS_object_counts` +- `PROCESS_transactions` +- `PROCESS_views` +- `events` + +Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the +process type. In all series, any form of duration is stored in milliseconds. + +## PROCESS_file_descriptors + +This measurement contains the number of open file descriptors over time. The +value field `value` contains the number of descriptors. + +## PROCESS_gc_statistics + +This measurement contains Ruby garbage collection statistics such as the amount +of minor/major GC runs (relative to the last sampling interval), the time spent +in garbage collection cycles, and all fields/values returned by `GC.stat`. + +## PROCESS_memory_usage + +This measurement contains the process' memory usage (in bytes) over time. The +value field `value` contains the number of bytes. + +## PROCESS_method_calls + +This measurement contains the methods called during a transaction along with +their duration, and a name of the transaction action that invoked the method (if +available). The method call duration is stored in the value field `duration`, +while the method name is stored in the tag `method`. The tag `action` contains +the full name of the transaction action. Both the `method` and `action` fields +are in the following format: + +``` +ClassName#method_name +``` + +For example, a method called by the `show` method in the `UsersController` class +would have `action` set to `UsersController#show`. + +## PROCESS_object_counts + +This measurement is used to store retained Ruby objects (per class) and the +amount of retained objects. The number of objects is stored in the `count` value +field while the class name is stored in the `type` tag. + +## PROCESS_transactions + +This measurement is used to store basic transaction details such as the time it +took to complete a transaction, how much time was spent in SQL queries, etc. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The total duration of the transaction | +| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers | +| `method_duration` | The total time spent in method calls | +| `sql_duration` | The total time spent in SQL queries | +| `view_duration` | The total time spent in views | + +## PROCESS_views + +This measurement is used to store view rendering timings for a transaction. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The rendering time of the view | +| `view` | The path of the view, relative to the application's root directory | + +The `action` tag contains the action name of the transaction that rendered the +view. + +## events + +This measurement is used to store generic events such as the number of Git +pushes, Emails sent, etc. Each point in this measurement has a single value +field called `count`. The value of this field is simply set to `1`. Each point +also has at least one tag: `event`. This tag's value is set to the event name. +Depending on the event type additional tags may be available as well. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/administration/monitoring/performance/introduction.md b/doc/administration/monitoring/performance/introduction.md new file mode 100644 index 00000000000..79904916b7e --- /dev/null +++ b/doc/administration/monitoring/performance/introduction.md @@ -0,0 +1,65 @@ +# GitLab Performance Monitoring + +GitLab comes with its own application performance measuring system as of GitLab +8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the +Community and Enterprise editions. + +Apart from this introduction, you are advised to read through the following +documents in order to understand and properly configure GitLab Performance Monitoring: + +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Install/Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) + +## Introduction to GitLab Performance Monitoring + +GitLab Performance Monitoring makes it possible to measure a wide variety of statistics +including (but not limited to): + +- The time it took to complete a transaction (a web request or Sidekiq job). +- The time spent in running SQL queries and rendering HAML views. +- The time spent executing (instrumented) Ruby methods. +- Ruby object allocations, and retained objects in particular. +- System statistics such as the process' memory usage and open file descriptors. +- Ruby garbage collection statistics. + +Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored +data can be visualized using [Grafana][grafana] or any other application that +supports reading data from InfluxDB. Alternatively data can be queried using the +InfluxDB CLI. + +## Metric Types + +Two types of metrics are collected: + +1. Transaction specific metrics. +1. Sampled metrics, collected at a certain interval in a separate thread. + +### Transaction Metrics + +Transaction metrics are metrics that can be associated with a single +transaction. This includes statistics such as the transaction duration, timings +of any executed SQL queries, time spent rendering HAML views, etc. These metrics +are collected for every Rack request and Sidekiq job processed. + +### Sampled Metrics + +Sampled metrics are metrics that can't be associated with a single transaction. +Examples include garbage collection statistics and retained Ruby objects. These +metrics are collected at a regular interval. This interval is made up out of two +parts: + +1. A user defined interval. +1. A randomly generated offset added on top of the interval, the same offset + can't be used twice in a row. + +The actual interval can be anywhere between a half of the defined interval and a +half above the interval. For example, for a user defined interval of 15 seconds +the actual interval can be anywhere between 7.5 and 22.5. The interval is +re-generated for every sampling run instead of being generated once and re-used +for the duration of the process' lifetime. + +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[grafana]: http://grafana.org/ diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md index eac57bc3de4..23ae1b17258 100644 --- a/doc/monitoring/health_check.md +++ b/doc/monitoring/health_check.md @@ -1,66 +1 @@ -# Health Check - -> [Introduced][ce-3888] in GitLab 8.8. - -GitLab provides a health check endpoint for uptime monitoring on the `health_check` web -endpoint. The health check reports on the overall system status based on the status of -the database connection, the state of the database migrations, and the ability to write -and access the cache. This endpoint can be provided to uptime monitoring services like -[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. - -## Access Token - -An access token needs to be provided while accessing the health check endpoint. The current -accepted token can be found on the `admin/health_check` page of your GitLab instance. - -![access token](img/health_check_token.png) - -The access token can be passed as a URL parameter: - -``` -https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN -``` - -or as an HTTP header: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -## Using the Endpoint - -Once you have the access token, health information can be retrieved as plain text, JSON, -or XML using the `health_check` endpoint: - -- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` - -You can also ask for the status of specific services: - -- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` - -For example, the JSON output of the following health check: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -would be like: - -``` -{"healthy":true,"message":"success"} -``` - -## Status - -On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint -will return a valid successful HTTP status code, and a `success` message. Ideally your -uptime monitoring should look for the success message. - -[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 -[pingdom]: https://www.pingdom.com -[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html -[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring +This document was moved to [administration/monitoring/health_check](../administration/monitoring/health_check.md). diff --git a/doc/monitoring/img/health_check_token.png b/doc/monitoring/img/health_check_token.png deleted file mode 100644 index 2d7c82a65a8..00000000000 Binary files a/doc/monitoring/img/health_check_token.png and /dev/null differ diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md index 771584268d9..a669bb28904 100644 --- a/doc/monitoring/performance/gitlab_configuration.md +++ b/doc/monitoring/performance/gitlab_configuration.md @@ -1,40 +1 @@ -# GitLab Configuration - -GitLab Performance Monitoring is disabled by default. To enable it and change any of its -settings, navigate to the Admin area in **Settings > Metrics** -(`/admin/application_settings`). - -The minimum required settings you need to set are the InfluxDB host and port. -Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the -changes. - ---- - -![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png) - ---- - -Finally, a restart of all GitLab processes is required for the changes to take -effect: - -```bash -# For Omnibus installations -sudo gitlab-ctl restart - -# For installations from source -sudo service gitlab restart -``` - -## Pending Migrations - -When any migrations are pending, the metrics are disabled until the migrations -have been performed. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [InfluxDB Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) +This document was moved to [administration/monitoring/performance/gitlab_configuration](../administration/monitoring/performance/gitlab_configuration.md). diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 7947b0fedc4..93320b40174 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -1,111 +1 @@ -# Grafana Configuration - -[Grafana](http://grafana.org/) is a tool that allows you to visualize time -series metrics through graphs and dashboards. It supports several backend -data stores, including InfluxDB. GitLab writes performance data to InfluxDB -and Grafana will allow you to query InfluxDB to display useful graphs. - -For the easiest installation and configuration, install Grafana on the same -server as InfluxDB. For larger installations, you may want to split out these -services. - -## Installation - -Grafana supplies package repositories (Yum/Apt) for easy installation. -See [Grafana installation documentation](http://docs.grafana.org/installation/) -for detailed steps. - -> **Note**: Before starting Grafana for the first time, set the admin user -and password in `/etc/grafana/grafana.ini`. Otherwise, the default password -will be `admin`. - -## Configuration - -Login as the admin user. Expand the menu by clicking the Grafana logo in the -top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new' -in the top bar. - -![Grafana empty data source page](img/grafana_data_source_empty.png) - -Fill in the configuration details for the InfluxDB data source. Save and -Test Connection to ensure the configuration is correct. - -- **Name**: InfluxDB -- **Default**: Checked -- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x) -- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB -on a separate server) -- **Access**: proxy -- **Database**: gitlab -- **User**: admin (Or the username configured when setting up InfluxDB) -- **Password**: The password configured when you set up InfluxDB - -![Grafana data source configurations](img/grafana_data_source_configuration.png) - -## Apply retention policies and create continuous queries - -If you intend to import the GitLab provided Grafana dashboards, you will need to -set up the right retention policies and continuous queries. The easiest way of -doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management) -repository. - -To use this repository you must first clone it: - -``` -git clone https://gitlab.com/gitlab-org/influxdb-management.git -cd influxdb-management -``` - -Next you must install the required dependencies: - -``` -gem install bundler -bundle install -``` - -Now you must configure the repository by first copying `.env.example` to `.env` -and then editing the `.env` file to contain the correct InfluxDB settings. Once -configured you can simply run `bundle exec rake` and the InfluxDB database will -be configured for you. - -For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md). - -## Import Dashboards - -You can now import a set of default dashboards that will give you a good -start on displaying useful information. GitLab has published a set of default -[Grafana dashboards][grafana-dashboards] to get you started. Clone the -repository or download a zip/tarball, then follow these steps to import each -JSON file. - -Open the dashboard dropdown menu and click 'Import' - -![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) - -Click 'Choose file' and browse to the location where you downloaded or cloned -the dashboard repository. Pick one of the JSON files to import. - -![Grafana dashboard import](img/grafana_dashboard_import.png) - -Once the dashboard is imported, be sure to click save icon in the top bar. If -you do not save the dashboard after importing it will be removed when you -navigate away. - -![Grafana save icon](img/grafana_save_icon.png) - -Repeat this process for each dashboard you wish to import. - -Alternatively you can automatically import all the dashboards into your Grafana -instance. See the README of the [Grafana dashboards][grafana-dashboards] -repository for more information on this process. - -[grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Installation/Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) +This document was moved to [administration/monitoring/performance/grafana_configuration](../administration/monitoring/performance/grafana_configuration.md). diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md index c30cd2950d8..02647de1eb0 100644 --- a/doc/monitoring/performance/influxdb_configuration.md +++ b/doc/monitoring/performance/influxdb_configuration.md @@ -1,193 +1 @@ -# InfluxDB Configuration - -The default settings provided by [InfluxDB] are not sufficient for a high traffic -GitLab environment. The settings discussed in this document are based on the -settings GitLab uses for GitLab.com, depending on your own needs you may need to -further adjust them. - -If you are intending to run InfluxDB on the same server as GitLab, make sure -you have plenty of RAM since InfluxDB can use quite a bit depending on traffic. - -Unless you are going with a budget setup, it's advised to run it separately. - -## Requirements - -- InfluxDB 0.9.5 or newer -- A fairly modern version of Linux -- At least 4GB of RAM -- At least 10GB of storage for InfluxDB data - -Note that the RAM and storage requirements can differ greatly depending on the -amount of data received/stored. To limit the amount of stored data users can -look into [InfluxDB Retention Policies][influxdb-retention]. - -## Installation - -Installing InfluxDB is out of the scope of this document. Please refer to the -[InfluxDB documentation]. - -## InfluxDB Server Settings - -Since InfluxDB has many settings that users may wish to customize themselves -(e.g. what port to run InfluxDB on), we'll only cover the essentials. - -The configuration file in question is usually located at -`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file, -InfluxDB needs to be restarted. - -### Storage Engine - -InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new -storage engine is available, called [TSM Tree]. All users **must** use the new -`tsm1` storage engine as this [will be the default engine][tsm1-commit] in -upcoming InfluxDB releases. - -Make sure you have the following in your configuration file: - -``` -[data] - dir = "/var/lib/influxdb/data" - engine = "tsm1" -``` - -### Admin Panel - -Production environments should have the InfluxDB admin panel **disabled**. This -feature can be disabled by adding the following to your InfluxDB configuration -file: - -``` -[admin] - enabled = false -``` - -### HTTP - -HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana, -thus it should be enabled. When enabling make sure to _also_ enable -authentication: - -``` -[http] - enabled = true - auth-enabled = true -``` - -_**Note:** Before you enable authentication, you might want to [create an -admin user](#create-a-new-admin-user)._ - -### UDP - -GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling -UDP can be done using the following settings: - -``` -[[udp]] - enabled = true - bind-address = ":8089" - database = "gitlab" - batch-size = 1000 - batch-pending = 5 - batch-timeout = "1s" - read-buffer = 209715200 -``` - -This does the following: - -1. Enable UDP and bind it to port 8089 for all addresses. -2. Store any data received in the "gitlab" database. -3. Define a batch of points to be 1000 points in size and allow a maximum of - 5 batches _or_ flush them automatically after 1 second. -4. Define a UDP read buffer size of 200 MB. - -One of the most important settings here is the UDP read buffer size as if this -value is set too low, packets will be dropped. You must also make sure the OS -buffer size is set to the same value, the default value is almost never enough. - -To set the OS buffer size to 200 MB, on Linux you can run the following command: - -```bash -sysctl -w net.core.rmem_max=209715200 -``` - -To make this permanent, add the following to `/etc/sysctl.conf` and restart the -server: - -```bash -net.core.rmem_max=209715200 -``` - -It is **very important** to make sure the buffer sizes are large enough to -handle all data sent to InfluxDB as otherwise you _will_ lose data. The above -buffer sizes are based on the traffic for GitLab.com. Depending on the amount of -traffic, users may be able to use a smaller buffer size, but we highly recommend -using _at least_ 100 MB. - -When enabling UDP, users should take care to not expose the port to the public, -as doing so will allow anybody to write data into your InfluxDB database (as -[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either -whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only -allowing traffic from members of said VLAN. - -## Create a new admin user - -If you want to [enable authentication](#http), you might want to [create an -admin user][influx-admin]: - -``` -influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES" -``` - -## Create the `gitlab` database - -Once you get InfluxDB up and running, you need to create a database for GitLab. -Make sure you have changed the [storage engine](#storage-engine) to `tsm1` -before creating a database. - -_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled -[HTTP authentication](#http), remember to append the username (`-username `) -and password (`-password `) you set earlier to the commands below._ - -Run the following command to create a database named `gitlab`: - -```bash -influx -execute 'CREATE DATABASE gitlab' -``` - -The name **must** be `gitlab`, do not use any other name. - -Next, make sure that the database was successfully created: - -```bash -influx -execute 'SHOW DATABASES' -``` - -The output should be similar to: - -``` -name: databases ---------------- -name -_internal -gitlab -``` - -That's it! Now your GitLab instance should send data to InfluxDB. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) - -[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management -[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ -[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/ -[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ -[influxdb]: https://influxdata.com/time-series-platform/influxdb/ -[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/ -[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d -[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user +This document was moved to [administration/monitoring/performance/influxdb_configuration](../administration/monitoring/performance/influxdb_configuration.md). diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md index eff0e29f58d..a989e323e04 100644 --- a/doc/monitoring/performance/influxdb_schema.md +++ b/doc/monitoring/performance/influxdb_schema.md @@ -1,97 +1 @@ -# InfluxDB Schema - -The following measurements are currently stored in InfluxDB: - -- `PROCESS_file_descriptors` -- `PROCESS_gc_statistics` -- `PROCESS_memory_usage` -- `PROCESS_method_calls` -- `PROCESS_object_counts` -- `PROCESS_transactions` -- `PROCESS_views` -- `events` - -Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the -process type. In all series, any form of duration is stored in milliseconds. - -## PROCESS_file_descriptors - -This measurement contains the number of open file descriptors over time. The -value field `value` contains the number of descriptors. - -## PROCESS_gc_statistics - -This measurement contains Ruby garbage collection statistics such as the amount -of minor/major GC runs (relative to the last sampling interval), the time spent -in garbage collection cycles, and all fields/values returned by `GC.stat`. - -## PROCESS_memory_usage - -This measurement contains the process' memory usage (in bytes) over time. The -value field `value` contains the number of bytes. - -## PROCESS_method_calls - -This measurement contains the methods called during a transaction along with -their duration, and a name of the transaction action that invoked the method (if -available). The method call duration is stored in the value field `duration`, -while the method name is stored in the tag `method`. The tag `action` contains -the full name of the transaction action. Both the `method` and `action` fields -are in the following format: - -``` -ClassName#method_name -``` - -For example, a method called by the `show` method in the `UsersController` class -would have `action` set to `UsersController#show`. - -## PROCESS_object_counts - -This measurement is used to store retained Ruby objects (per class) and the -amount of retained objects. The number of objects is stored in the `count` value -field while the class name is stored in the `type` tag. - -## PROCESS_transactions - -This measurement is used to store basic transaction details such as the time it -took to complete a transaction, how much time was spent in SQL queries, etc. The -following value fields are available: - -| Value | Description | -| ----- | ----------- | -| `duration` | The total duration of the transaction | -| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers | -| `method_duration` | The total time spent in method calls | -| `sql_duration` | The total time spent in SQL queries | -| `view_duration` | The total time spent in views | - -## PROCESS_views - -This measurement is used to store view rendering timings for a transaction. The -following value fields are available: - -| Value | Description | -| ----- | ----------- | -| `duration` | The rendering time of the view | -| `view` | The path of the view, relative to the application's root directory | - -The `action` tag contains the action name of the transaction that rendered the -view. - -## events - -This measurement is used to store generic events such as the number of Git -pushes, Emails sent, etc. Each point in this measurement has a single value -field called `count`. The value of this field is simply set to `1`. Each point -also has at least one tag: `event`. This tag's value is set to the event name. -Depending on the event type additional tags may be available as well. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Configuration](influxdb_configuration.md) -- [Grafana Install/Configuration](grafana_configuration.md) +This document was moved to [administration/monitoring/performance/influxdb_schema](../administration/monitoring/performance/influxdb_schema.md). diff --git a/doc/monitoring/performance/introduction.md b/doc/monitoring/performance/introduction.md index 79904916b7e..ab3f3ac1664 100644 --- a/doc/monitoring/performance/introduction.md +++ b/doc/monitoring/performance/introduction.md @@ -1,65 +1 @@ -# GitLab Performance Monitoring - -GitLab comes with its own application performance measuring system as of GitLab -8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the -Community and Enterprise editions. - -Apart from this introduction, you are advised to read through the following -documents in order to understand and properly configure GitLab Performance Monitoring: - -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Install/Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) - -## Introduction to GitLab Performance Monitoring - -GitLab Performance Monitoring makes it possible to measure a wide variety of statistics -including (but not limited to): - -- The time it took to complete a transaction (a web request or Sidekiq job). -- The time spent in running SQL queries and rendering HAML views. -- The time spent executing (instrumented) Ruby methods. -- Ruby object allocations, and retained objects in particular. -- System statistics such as the process' memory usage and open file descriptors. -- Ruby garbage collection statistics. - -Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored -data can be visualized using [Grafana][grafana] or any other application that -supports reading data from InfluxDB. Alternatively data can be queried using the -InfluxDB CLI. - -## Metric Types - -Two types of metrics are collected: - -1. Transaction specific metrics. -1. Sampled metrics, collected at a certain interval in a separate thread. - -### Transaction Metrics - -Transaction metrics are metrics that can be associated with a single -transaction. This includes statistics such as the transaction duration, timings -of any executed SQL queries, time spent rendering HAML views, etc. These metrics -are collected for every Rack request and Sidekiq job processed. - -### Sampled Metrics - -Sampled metrics are metrics that can't be associated with a single transaction. -Examples include garbage collection statistics and retained Ruby objects. These -metrics are collected at a regular interval. This interval is made up out of two -parts: - -1. A user defined interval. -1. A randomly generated offset added on top of the interval, the same offset - can't be used twice in a row. - -The actual interval can be anywhere between a half of the defined interval and a -half above the interval. For example, for a user defined interval of 15 seconds -the actual interval can be anywhere between 7.5 and 22.5. The interval is -re-generated for every sampling run instead of being generated once and re-used -for the duration of the process' lifetime. - -[influxdb]: https://influxdata.com/time-series-platform/influxdb/ -[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ -[grafana]: http://grafana.org/ +This document was moved to [administration/monitoring/performance/introduction](../administration/monitoring/performance/introduction.md). -- cgit v1.2.1 From 44d5be2e8a248657165f932d859a3be4f1ee9089 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 12:24:35 +0200 Subject: Add links to internal docs in Metrics section in the admin area --- app/views/admin/application_settings/_form.html.haml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 0d79ca7dc52..c4c68cd7891 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -221,7 +221,11 @@ %fieldset %legend Metrics %p - These settings require a restart to take effect. + Setup InfluxDB to measure a wide variety of statistics like the time spent + in running SQL queries. These settings require a + = link_to 'restart', help_page_path('administration/restart_gitlab') + to take effect. + = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction') .form-group .col-sm-offset-2.col-sm-10 .checkbox -- cgit v1.2.1 From fd2ce241497eac5ddb1f7e303109211ddbeec9db Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 26 Sep 2016 16:39:03 +0800 Subject: We need to use URL in the email --- app/views/notify/pipeline_failed_email.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 76d45efd6f7..da28b4c30cd 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -124,7 +124,7 @@ %span = commit.author_name - else - %a.muted{:href => user_path(commit.author), :style => "color:#333333;text-decoration:none;"} + %a.muted{:href => user_url(commit.author), :style => "color:#333333;text-decoration:none;"} = commit.author.try(:name) || commit.author_name %tr.spacer %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} -- cgit v1.2.1 From d245f980174f6a3ab5d658e7c2c040284c234f28 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 26 Sep 2016 16:39:24 +0800 Subject: We already checked commit.author is not nil, so it must be there --- app/views/notify/pipeline_failed_email.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index da28b4c30cd..d238353ed5d 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -125,7 +125,7 @@ = commit.author_name - else %a.muted{:href => user_url(commit.author), :style => "color:#333333;text-decoration:none;"} - = commit.author.try(:name) || commit.author_name + = commit.author.name %tr.spacer %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   -- cgit v1.2.1 From 8de8506d44de76af4f00ca50283ff46d005a00ab Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 26 Sep 2016 16:39:52 +0800 Subject: Use string interpolation if possible (better performance) --- app/views/notify/pipeline_failed_email.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index d238353ed5d..3a85d4831f8 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -138,7 +138,7 @@ had = failed.size failed - = 'build'.pluralize(failed.size) + '.' + = "#{'build'.pluralize(failed.size)}." %tr.warning %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;"} Logs may contain sensitive data. Please consider before forwarding this email. -- cgit v1.2.1 From db6b2b18990297d98bd74af1d2f475d0d42ec443 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 26 Sep 2016 17:18:30 +0800 Subject: Show open merge request against default branch if exists --- app/mailers/emails/pipelines.rb | 4 ++++ app/views/notify/pipeline_failed_email.html.haml | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index e132105807a..2b2b7a4f08c 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -13,6 +13,10 @@ module Emails def pipeline_mail(pipeline, to, status) @project = pipeline.project @pipeline = pipeline + @merge_request = @project.merge_requests.opened. + find_by(source_project: @project, + source_branch: @pipeline.ref, + target_branch: @project.default_branch) add_headers mail(to: to, subject: pipeline_subject(status)) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 3a85d4831f8..6b013055d45 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -94,9 +94,10 @@ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} %a.muted{:href => namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), :style => "color:#333333;text-decoration:none;"} = @pipeline.ref - ( - %a{:href => "{{{merge-request-link}}}", :style => "color:#3084bb;text-decoration:none;white-space: nowrap;"}> merge request - ) + - if @merge_request + ( + %a{:href => namespace_project_merge_request_url(@project.namespace, @project, @merge_request), :style => "color:#3084bb;text-decoration:none;white-space: nowrap;"}> merge request + ) %tr %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} -- cgit v1.2.1 From 528b988aea44cc1016ee5a3c09ce0d383114e395 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 26 Sep 2016 11:43:55 +0200 Subject: Escape HTML nodes in builds commands in ci linter --- app/views/ci/lints/_create.html.haml | 3 +-- spec/views/ci/lints/show.html.haml_spec.rb | 35 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 spec/views/ci/lints/show.html.haml_spec.rb diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml index f7875e68b7e..1545c00af45 100644 --- a/app/views/ci/lints/_create.html.haml +++ b/app/views/ci/lints/_create.html.haml @@ -16,8 +16,7 @@ %tr %td #{stage.capitalize} Job - #{build[:name]} %td - %pre - = simple_format build[:commands] + %pre= build[:commands] %br %b Tag list: diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb new file mode 100644 index 00000000000..3a65a86cd88 --- /dev/null +++ b/spec/views/ci/lints/show.html.haml_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe 'ci/lints/show' do + include Devise::TestHelpers + + before do + assign(:status, true) + assign(:stages, %w[test]) + assign(:builds, builds) + end + + context 'when builds attrbiutes contain HTML nodes' do + let(:builds) do + [ { name: 'rspec', stage: 'test', commands: '

rspec

' } ] + end + + it 'does not render HTML elements' do + render + + expect(rendered).not_to have_css('h1', text: 'rspec') + end + end + + context 'when builds attributes do not contain HTML nodes' do + let(:builds) do + [ { name: 'rspec', stage: 'test', commands: 'rspec' } ] + end + + it 'shows configuration in the table' do + render + + expect(rendered).to have_css('td pre', text: 'rspec') + end + end +end -- cgit v1.2.1 From ae5831500a953528ec79a87f1da52ced014f74d7 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 22 Sep 2016 13:20:17 +0100 Subject: Move Rack::Attack and Rack::Cors middlewares to be before Warden::Manager --- config/application.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/application.rb b/config/application.rb index 4792f6670a8..4f04687a5e4 100644 --- a/config/application.rb +++ b/config/application.rb @@ -99,10 +99,10 @@ module Gitlab config.action_view.sanitized_allowed_protocols = %w(smb) - config.middleware.use Rack::Attack + config.middleware.insert_before Warden::Manager, Rack::Attack # Allow access to GitLab API from other domains - config.middleware.use Rack::Cors do + config.middleware.insert_before Warden::Manager, Rack::Cors do allow do origins '*' resource '/api/*', -- cgit v1.2.1 From 3870138960b6918d999f879bed5e8d938ea43fae Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 22 Sep 2016 13:21:55 +0100 Subject: Set a restrictive CORS policy on the API for credentialed requests Cross-origin requests can still be made, as long as the client doesn't use the Rails session cookie to do so. Existing clients should not be setting 'withCredentials: true', so this should be fine. --- config/application.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/config/application.rb b/config/application.rb index 4f04687a5e4..1ebdb43d662 100644 --- a/config/application.rb +++ b/config/application.rb @@ -103,9 +103,20 @@ module Gitlab # Allow access to GitLab API from other domains config.middleware.insert_before Warden::Manager, Rack::Cors do + allow do + origins Gitlab.config.gitlab.url + resource '/api/*', + credentials: true, + headers: :any, + methods: :any, + expose: ['Link'] + end + + # Cross-origin requests must not have the session cookie available allow do origins '*' resource '/api/*', + credentials: false, headers: :any, methods: :any, expose: ['Link'] -- cgit v1.2.1 From 05745737c659098d3cc1e9ae0f8eedddac7b3603 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 26 Sep 2016 14:21:39 +0200 Subject: Explain the extra chmod --- lib/gitlab/workhorse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 5d33f98e89e..594439a5d4b 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -111,7 +111,7 @@ module Gitlab def write_secret bytes = SecureRandom.random_bytes(SECRET_LENGTH) File.open(secret_path, 'w:BINARY', 0600) do |f| - f.chmod(0600) + f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op. f.write(Base64.strict_encode64(bytes)) end end -- cgit v1.2.1 From 55be351f81b771209982e4bcffe93939b14dfe24 Mon Sep 17 00:00:00 2001 From: Robert Marcano Date: Sun, 25 Sep 2016 01:14:47 -0400 Subject: Fix "Create project" button layout with restricted visibility When the administrator restrict visibility options the information message break the buttons layout. Change the message style. --- CHANGELOG | 1 + app/views/shared/_visibility_level.html.haml | 2 +- app/views/shared/_visibility_radios.html.haml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 463b07b635e..3822fa7f3f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Speed-up group milestones show page - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) + - Fix "Create project" button layout when visibility options are restricted v 8.12.2 (unreleased) - Fix Import/Export not recognising correctly the imported services. diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml index add4536a0a2..b11257ee0e6 100644 --- a/app/views/shared/_visibility_level.html.haml +++ b/app/views/shared/_visibility_level.html.haml @@ -6,7 +6,7 @@ - if can_change_visibility_level = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) - else - .col-sm-10 + %div %span.info = visibility_level_icon(visibility_level) %strong diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml index ebe2eb0433d..182c4eebd50 100644 --- a/app/views/shared/_visibility_radios.html.haml +++ b/app/views/shared/_visibility_radios.html.haml @@ -10,6 +10,6 @@ .option-descr = visibility_level_description(level, form_model) - unless restricted_visibility_levels.empty? - .col-sm-10 + %div %span.info Some visibility level settings have been restricted by the administrator. -- cgit v1.2.1 From f837238a99075fd6cf7cdc2e1a3acf0fb7e139c6 Mon Sep 17 00:00:00 2001 From: Makoto Scott-Hinkle Date: Mon, 26 Sep 2016 16:47:34 -0700 Subject: Allowing ">" to be used for Milestone models's title and storing the value in db as unescaped. Updating test value for milestone title Adding API test for title with reserved HTML characters. Updating changelog Adding the MR number for fixing bug #22452. removing duplicate line Updating MR number. --- CHANGELOG | 1 + app/models/milestone.rb | 6 +++++- spec/models/milestone_spec.rb | 4 ++-- spec/requests/api/milestones_spec.rb | 8 ++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5b08e8cd754..762cbbf3a0f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.13.0 (unreleased) - Speed-up group milestones show page - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Revoke button in Applications Settings underlines on hover. + - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 v 8.12.2 (unreleased) - Fix Import/Export not recognising correctly the imported services. diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 2bd7f198030..44c3cbb2c73 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -158,7 +158,7 @@ class Milestone < ActiveRecord::Base end def title=(value) - write_attribute(:title, Sanitize.clean(value.to_s)) if value.present? + write_attribute(:title, sanitize_title(value)) if value.present? end # Sorts the issues for the given IDs. @@ -204,4 +204,8 @@ class Milestone < ActiveRecord::Base iid end end + + def sanitize_title(value) + CGI.unescape_html(Sanitize.clean(value.to_s)) + end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index d64d6cde2b5..33fe22dd98c 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -20,10 +20,10 @@ describe Milestone, models: true do let(:user) { create(:user) } describe "#title" do - let(:milestone) { create(:milestone, title: "test") } + let(:milestone) { create(:milestone, title: "foo & bar -> 2.2") } it "sanitizes title" do - expect(milestone.title).to eq("test") + expect(milestone.title).to eq("foo & bar -> 2.2") end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index b89dac01040..dd192bea432 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -104,6 +104,14 @@ describe API::API, api: true do expect(response).to have_http_status(400) end + + it 'creates a new project with reserved html characters' do + post api("/projects/#{project.id}/milestones", user), title: 'foo & bar 1.1 -> 2.2' + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') + expect(json_response['description']).to be_nil + end end describe 'PUT /projects/:id/milestones/:milestone_id' do -- cgit v1.2.1 From 3ed80a0176a0c8155ff6f84a8f3e70718babd8ce Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 19 Sep 2016 19:28:41 +0100 Subject: Enforce the fork_project permission in Projects::CreateService Projects::ForkService delegates to this service almost entirely, but needed one small change so it would propagate create errors correctly. CreateService#execute needs significant refactoring; it is now right at the complexity limit set by Rubocop. I avoided doing so in this commit to keep the diff as small as possible. Several tests depend on the insecure behaviour of ForkService, so fi them up at the same time. --- app/services/projects/create_service.rb | 12 ++++++++++++ app/services/projects/fork_service.rb | 2 ++ features/steps/project/fork.rb | 1 + spec/controllers/users_controller_spec.rb | 4 ++-- spec/helpers/projects_helper_spec.rb | 2 +- spec/models/forked_project_link_spec.rb | 1 + spec/requests/api/fork_spec.rb | 2 +- spec/services/projects/fork_service_spec.rb | 25 +++++++++++++++++++++++-- spec/services/system_note_service_spec.rb | 2 +- 9 files changed, 44 insertions(+), 7 deletions(-) diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index be749ba4a1c..696fe3efe8f 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -15,6 +15,11 @@ module Projects return @project end + unless allowed_fork?(forked_from_project_id) + @project.errors.add(:forked_from_project_id, 'is forbidden') + return @project + end + # Set project name from path if @project.name.present? && @project.path.present? # if both name and path set - everything is ok @@ -71,6 +76,13 @@ module Projects @project.errors.add(:namespace, "is not valid") end + def allowed_fork?(source_project_id) + return true if source_project_id.nil? + + source_project = Project.find_by(id: source_project_id) + current_user.can?(:fork_project, source_project) + end + def allowed_namespace?(user, namespace_id) namespace = Namespace.find_by(id: namespace_id) current_user.can?(:create_projects, namespace) diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index a2de4dccece..a2b23ea6171 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -16,6 +16,8 @@ module Projects end new_project = CreateService.new(current_user, new_params).execute + return new_project unless new_project.persisted? + builds_access_level = @project.project_feature.builds_access_level new_project.project_feature.update_attributes(builds_access_level: builds_access_level) diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb index 8abeb5ee242..70dbd030003 100644 --- a/features/steps/project/fork.rb +++ b/features/steps/project/fork.rb @@ -70,6 +70,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps step 'There is an existent fork of the "Shop" project' do user = create(:user, name: 'Mike') + @project.team << [user, :reporter] @forked_project = Projects::ForkService.new(@project, user).execute end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 54a2d3d9460..19a8b1fe524 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -73,8 +73,8 @@ describe UsersController do end context 'forked project' do - let!(:project) { create(:project) } - let!(:forked_project) { Projects::ForkService.new(project, user).execute } + let(:project) { create(:project) } + let(:forked_project) { Projects::ForkService.new(project, user).execute } before do sign_in(user) diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 70032e7df94..bcd53440cb4 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -11,7 +11,7 @@ describe ProjectsHelper do describe "can_change_visibility_level?" do let(:project) { create(:project) } - let(:user) { create(:user) } + let(:user) { create(:project_member, :reporter, user: create(:user), project: project).user } let(:fork_project) { Projects::ForkService.new(project, user).execute } it "returns false if there are no appropriate permissions" do diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 9c81d159cdf..1863581f57b 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -6,6 +6,7 @@ describe ForkedProjectLink, "add link on fork" do let(:user) { create(:user, namespace: namespace) } before do + create(:project_member, :reporter, user: user, project: project_from) @project_to = fork_project(project_from, user) end diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index 34f84f78952..e38d5745d44 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -18,7 +18,7 @@ describe API::API, api: true do end let(:project_user2) do - create(:project_member, :guest, user: user2, project: project) + create(:project_member, :reporter, user: user2, project: project) end describe 'POST /projects/fork/:id' do diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index ef2036c78b1..64d15c0523c 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -12,12 +12,26 @@ describe Projects::ForkService, services: true do description: 'wow such project') @to_namespace = create(:namespace) @to_user = create(:user, namespace: @to_namespace) + @from_project.add_user(@to_user, :developer) end context 'fork project' do + context 'when forker is a guest' do + before do + @guest = create(:user) + @from_project.add_user(@guest, :guest) + end + subject { fork_project(@from_project, @guest) } + + it { is_expected.not_to be_persisted } + it { expect(subject.errors[:forked_from_project_id]).to eq(['is forbidden']) } + end + describe "successfully creates project in the user namespace" do let(:to_project) { fork_project(@from_project, @to_user) } + it { expect(to_project).to be_persisted } + it { expect(to_project.errors).to be_empty } it { expect(to_project.owner).to eq(@to_user) } it { expect(to_project.namespace).to eq(@to_user.namespace) } it { expect(to_project.star_count).to be_zero } @@ -29,7 +43,9 @@ describe Projects::ForkService, services: true do it "fails due to validation, not transaction failure" do @existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) @to_project = fork_project(@from_project, @to_user) - expect(@existing_project.persisted?).to be_truthy + expect(@existing_project).to be_persisted + + expect(@to_project).not_to be_persisted expect(@to_project.errors[:name]).to eq(['has already been taken']) expect(@to_project.errors[:path]).to eq(['has already been taken']) end @@ -81,18 +97,23 @@ describe Projects::ForkService, services: true do @group = create(:group) @group.add_user(@group_owner, GroupMember::OWNER) @group.add_user(@developer, GroupMember::DEVELOPER) + @project.add_user(@developer, :developer) + @project.add_user(@group_owner, :developer) @opts = { namespace: @group } end context 'fork project for group' do it 'group owner successfully forks project into the group' do to_project = fork_project(@project, @group_owner, @opts) + + expect(to_project).to be_persisted + expect(to_project.errors).to be_empty expect(to_project.owner).to eq(@group) expect(to_project.namespace).to eq(@group) expect(to_project.name).to eq(@project.name) expect(to_project.path).to eq(@project.path) expect(to_project.description).to eq(@project.description) - expect(to_project.star_count).to be_zero + expect(to_project.star_count).to be_zero end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 3d854a959f3..a3f16e2d945 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -445,7 +445,7 @@ describe SystemNoteService, services: true do end context 'commit with cross-reference from fork' do - let(:author2) { create(:user) } + let(:author2) { create(:project_member, :reporter, user: create(:user), project: project).user } let(:forked_project) { Projects::ForkService.new(project, author2).execute } let(:commit2) { forked_project.commit } -- cgit v1.2.1 From 619dd1cfcf661bc1c0ce64010504cee6623c0b8a Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 27 Sep 2016 13:34:40 +0100 Subject: Add a CHANGELOG entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 61e3ed748c9..64918b89264 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.13.0 (unreleased) v 8.12.2 (unreleased) - Fix Import/Export not recognising correctly the imported services. + - Respect the fork_project permission when forking projects v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST -- cgit v1.2.1 From 4c480be39b0862f41043e94a6dc5b943b6f8e8f0 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 12 Aug 2016 12:04:33 +0200 Subject: Prevent claiming associated model IDs via import and added relevant specs --- lib/gitlab/import_export/attribute_cleaner.rb | 13 +++ lib/gitlab/import_export/project_tree_restorer.rb | 5 +- lib/gitlab/import_export/relation_factory.rb | 12 +- .../gitlab/import_export/attribute_cleaner_spec.rb | 34 ++++++ .../gitlab/import_export/relation_factory_spec.rb | 125 +++++++++++++++++++++ 5 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 lib/gitlab/import_export/attribute_cleaner.rb create mode 100644 spec/lib/gitlab/import_export/attribute_cleaner_spec.rb create mode 100644 spec/lib/gitlab/import_export/relation_factory_spec.rb diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb new file mode 100644 index 00000000000..c12b898a665 --- /dev/null +++ b/lib/gitlab/import_export/attribute_cleaner.rb @@ -0,0 +1,13 @@ +module Gitlab + module ImportExport + class AttributeCleaner + IGNORED_REFERENCES = Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES + Gitlab::ImportExport::RelationFactory::USER_REFERENCES + + def self.clean!(relation_hash:) + relation_hash.select! do |key, _value| + IGNORED_REFERENCES.include?(key) || !key.end_with?('_id') + end + end + end + end +end diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index c7b3551b84c..35adcd8f0da 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -104,9 +104,10 @@ module Gitlab def create_relation(relation, relation_hash_list) relation_array = [relation_hash_list].flatten.map do |relation_hash| Gitlab::ImportExport::RelationFactory.create(relation_sym: relation.to_sym, - relation_hash: relation_hash.merge('project_id' => restored_project.id), + relation_hash: relation_hash, members_mapper: members_mapper, - user: @user) + user: @user, + project_id: restored_project.id) end relation_hash_list.is_a?(Array) ? relation_array : relation_array.first diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 354ccd64696..9300f789e1b 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -13,6 +13,8 @@ module Gitlab USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze + PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze + BUILD_MODELS = %w[Ci::Build commit_status].freeze IMPORTED_OBJECT_MAX_RETRIES = 5.freeze @@ -25,9 +27,9 @@ module Gitlab new(*args).create end - def initialize(relation_sym:, relation_hash:, members_mapper:, user:) + def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project_id:) @relation_name = OVERRIDES[relation_sym] || relation_sym - @relation_hash = relation_hash.except('id', 'noteable_id') + @relation_hash = relation_hash.except('id', 'noteable_id').merge('project_id' => project_id) @members_mapper = members_mapper @user = user @imported_object_retries = 0 @@ -153,7 +155,11 @@ module Gitlab end def parsed_relation_hash - @parsed_relation_hash ||= @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } + @parsed_relation_hash ||= begin + Gitlab::ImportExport::AttributeCleaner.clean!(relation_hash: @relation_hash) + + @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } + end end def set_st_diffs diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb new file mode 100644 index 00000000000..b8e7932eb4a --- /dev/null +++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::AttributeCleaner, lib: true do + let(:unsafe_hash) do + { + 'service_id' => 99, + 'moved_to_id' => 99, + 'namespace_id' => 99, + 'ci_id' => 99, + 'random_project_id' => 99, + 'random_id' => 99, + 'milestone_id' => 99, + 'project_id' => 99, + 'user_id' => 99, + 'random_id_in_the_middle' => 99, + 'notid' => 99 + } + end + + let(:post_safe_hash) do + { + 'project_id' => 99, + 'user_id' => 99, + 'random_id_in_the_middle' => 99, + 'notid' => 99 + } + end + + it 'removes unwanted attributes from the hash' do + described_class.clean!(relation_hash: unsafe_hash) + + expect(unsafe_hash).to eq(post_safe_hash) + end +end diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb new file mode 100644 index 00000000000..3aa492a8ab1 --- /dev/null +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -0,0 +1,125 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::RelationFactory, lib: true do + let(:project) { create(:empty_project) } + let(:members_mapper) { double('members_mapper').as_null_object } + let(:user) { create(:user) } + let(:created_object) do + described_class.create(relation_sym: relation_sym, + relation_hash: relation_hash, + members_mapper: members_mapper, + user: user, + project_id: project.id) + end + + context 'hook object' do + let(:relation_sym) { :hooks } + let(:id) { 999 } + let(:service_id) { 99 } + let(:original_project_id) { 8 } + let(:token) { 'secret' } + + let(:relation_hash) do + { + 'id' => id, + 'url' => 'https://example.json', + 'project_id' => original_project_id, + 'created_at' => '2016-08-12T09:41:03.462Z', + 'updated_at' => '2016-08-12T09:41:03.462Z', + 'service_id' => service_id, + 'push_events' => true, + 'issues_events' => false, + 'merge_requests_events' => true, + 'tag_push_events' => false, + 'note_events' => true, + 'enable_ssl_verification' => true, + 'build_events' => false, + 'wiki_page_events' => true, + 'token' => token + } + end + + it 'does not have the original ID' do + expect(created_object.id).not_to eq(id) + end + + it 'does not have the original service_id' do + expect(created_object.service_id).not_to eq(service_id) + end + + it 'does not have the original project_id' do + expect(created_object.project_id).not_to eq(original_project_id) + end + + it 'has the new project_id' do + expect(created_object.project_id).to eq(project.id) + end + + it 'has a token' do + expect(created_object.token).to eq(token) + end + + context 'original service exists' do + let(:service_id) { Service.create(project: project).id } + + it 'does not have the original service_id' do + expect(created_object.service_id).not_to eq(service_id) + end + end + end + + # Mocks an ActiveRecordish object with the dodgy columns + class FooModel + include ActiveModel::Model + + def initialize(params) + params.each { |key, value| send("#{key}=", value) } + end + + def values + instance_variables.map { |ivar| instance_variable_get(ivar) } + end + end + + # `project_id`, `described_class.USER_REFERENCES`, noteable_id, target_id, and some project IDs are already + # re-assigned by described_class. + context 'Potentially hazardous foreign keys' do + let(:relation_sym) { :hazardous_foo_model } + let(:relation_hash) do + { + 'service_id' => 99, + 'moved_to_id' => 99, + 'namespace_id' => 99, + 'ci_id' => 99, + 'random_project_id' => 99, + 'random_id' => 99, + 'milestone_id' => 99, + 'project_id' => 99, + 'user_id' => 99, + } + end + + class HazardousFooModel < FooModel + attr_accessor :service_id, :moved_to_id, :namespace_id, :ci_id, :random_project_id, :random_id, :milestone_id, :project_id + end + + it 'does not preserve any foreign key IDs' do + expect(created_object.values).not_to include(99) + end + end + + context 'Project references' do + let(:relation_sym) { :project_foo_model } + let(:relation_hash) do + Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES.map { |ref| { ref => 99 } }.inject(:merge) + end + + class ProjectFooModel < FooModel + attr_accessor(*Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES) + end + + it 'does not preserve any project foreign key IDs' do + expect(created_object.values).not_to include(99) + end + end +end -- cgit v1.2.1 From 9e0b7c630f2fc64805062d0c7e02fd6092631071 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 27 Sep 2016 16:12:08 +0200 Subject: updated attribute cleaner to use allowed keyword and reject attributes --- lib/gitlab/import_export/attribute_cleaner.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb index c12b898a665..b9e4042220a 100644 --- a/lib/gitlab/import_export/attribute_cleaner.rb +++ b/lib/gitlab/import_export/attribute_cleaner.rb @@ -1,11 +1,11 @@ module Gitlab module ImportExport class AttributeCleaner - IGNORED_REFERENCES = Gitlab::ImportExport::RelationFactory::PROJECT_REFERENCES + Gitlab::ImportExport::RelationFactory::USER_REFERENCES + ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES def self.clean!(relation_hash:) - relation_hash.select! do |key, _value| - IGNORED_REFERENCES.include?(key) || !key.end_with?('_id') + relation_hash.reject! do |key, _value| + key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key) end end end -- cgit v1.2.1 From ffec230f537776959f40e66c3cb0809bb9c173b1 Mon Sep 17 00:00:00 2001 From: TMate Software Support Date: Tue, 27 Sep 2016 16:28:19 +0000 Subject: Update migrating_from_svn.md Documentation updated to cover Git/SVN mirror approach to migration from SVN to GitLab. --- doc/workflow/importing/migrating_from_svn.md | 92 +++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index 4828bb5dce6..76839315c53 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -4,6 +4,94 @@ Subversion (SVN) is a central version control system (VCS) while Git is a distributed version control system. There are some major differences between the two, for more information consult your favorite search engine. +## Overview + +There are two approaches to SVN to Git migration: + +#### [Git/SVN Mirror](#mirror) + + Make GitLab repository mirror SVN project. + + Git and SVN project are kept in sync; use either one or another. + + Smoothens migration process and allows to manage migration risks. + +#### [Cut over migration](#cutover) + + Translate existing data and history from SVN to Git. + + Fire and forget approach, good for smaller teams. + +## Smooth migration with a Git/SVN mirror using SubGit + +#### Prerequisites + +Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions follow this +[instruction](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). + +Download SubGit tool from [https://subgit.com/download/](https://subgit.com/download/) + +Unpack downloaded SubGit zip archive to `/opt` directory, subgit will be available +at `/opt/subgit-VERSION/bin/subgit` + +#### Configuration + +In GitLab create new empty repository. In filesystem it will be located at +`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path. + +Run SubGit to set up a Git/SVN mirror. Make sure `subgit` command is ran +on behalf of the same user that runs GitLab. + +``` +subgit configure --layout auto SVN_PROJECT_URL GIT_REPOS_PATH +``` + +Adjust authors and branches mappings, if necessary: + +``` +edit GIT_REPOS_PATH/subgit/authors.txt +edit GIT_REPOS_PATH/subgit/config +``` + +For more information regarding SubGit configuration options, refer to +[documentation](https://subgit.com/documentation.html) at SubGit web site. + +#### Initial translation + +Run `subgit` to perform initial translation of existing SVN revisions into +Git repository: + +``` +subgit install GIT_REPOS_PATH +``` + +After initial translation is completed, GitLab Git repository and SVN project +will be kept in sync by `subgit` - new Git commits will be translated to SVN +revisions and new SVN revisions will be translated to Git commits. Mirror works +transparently and does not require any special commands. + +Would you prefer to perform one-time cut over migration with `subgit` use +`import` command in place of `install`: + +``` +subgit import GIT_REPOS_PATH +``` + +#### Licensing + +Running SubGit in a mirror mode requires [registration](https://subgit.com/pricing.html). Registration is free for Open Source, +Academic and Startup projects. + +We're currently working on deeper GitLab/SubGit intergation. You may track our +progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). + +#### Support + +For any questions related to SVN to GitLab migration with SubGit contact us at [support@subgit.com](mailto:support@subgit.com). We support +all our users. + +## Cut over migration with svn2git + If you are currently using an SVN repository, you can migrate the repository to Git and GitLab. We recommend a hard cut over - run the migration command once and then have all developers start using the new GitLab repository immediately. @@ -74,6 +162,4 @@ git push --tags origin ## Contribute to this guide We welcome all contributions that would expand this guide with instructions on -how to migrate from SVN and other version control systems. - - +how to migrate from SVN and other version control systems. \ No newline at end of file -- cgit v1.2.1 From 0d1fa878a90026a1724025c97a9484d96f903840 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 27 Sep 2016 18:29:12 +0100 Subject: Changed compare dropdowns to dropdowns with isolated search input Updated compare specs --- app/assets/javascripts/compare_autocomplete.js | 5 +++-- app/assets/stylesheets/pages/projects.scss | 15 ++++++++++++- app/views/projects/compare/_form.html.haml | 8 +++++-- app/views/projects/compare/_ref_dropdown.html.haml | 1 + spec/features/compare_spec.rb | 26 ++++++++++++++-------- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js index 4e3a28cd163..294d2c9052c 100644 --- a/app/assets/javascripts/compare_autocomplete.js +++ b/app/assets/javascripts/compare_autocomplete.js @@ -23,8 +23,9 @@ selectable: true, filterable: true, filterByText: true, - fieldName: $dropdown.attr('name'), - filterInput: 'input[type="text"]', + toggleLabel: true, + fieldName: $dropdown.data('field-name'), + filterInput: 'input[type="search"]', renderRow: function(ref) { var link; if (ref.header != null) { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 8c8c403244e..7c8ab7cb2a2 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -743,6 +743,19 @@ pre.light-well { .dropdown-menu { width: 300px; } + + > .input-group > .compare-dropdown-toggle { + width: 200px; + + .dropdown-toggle-text { + display: block; + height: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + } + } } .clearable-input { @@ -779,4 +792,4 @@ pre.light-well { border-top-right-radius: 0; border-bottom-right-radius: 0; } -} \ No newline at end of file +} diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index d79336f5a60..b66027115b2 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -5,13 +5,17 @@ .form-group.dropdown.compare-form-group.js-compare-from-dropdown .input-group.inline-input-group %span.input-group-addon from - = text_field_tag :from, params[:from], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from].presence } + = hidden_field_tag :from, params[:from] + = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do + .dropdown-toggle-text= params[:from] || 'Select branch/tag' = render "ref_dropdown" = "..." .form-group.dropdown.compare-form-group.js-compare-to-dropdown .input-group.inline-input-group %span.input-group-addon to - = text_field_tag :to, params[:to], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to].presence } + = hidden_field_tag :to, params[:to] + = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do + .dropdown-toggle-text= params[:to] || 'Select branch/tag' = render "ref_dropdown"   = button_tag "Compare", class: "btn btn-create commits-compare-btn" diff --git a/app/views/projects/compare/_ref_dropdown.html.haml b/app/views/projects/compare/_ref_dropdown.html.haml index c604c6d0135..27d928c87a0 100644 --- a/app/views/projects/compare/_ref_dropdown.html.haml +++ b/app/views/projects/compare/_ref_dropdown.html.haml @@ -1,4 +1,5 @@ .dropdown-menu.dropdown-menu-selectable = dropdown_title "Select branch/tag" + = dropdown_filter "Filter by branch/tag" = dropdown_content = dropdown_loading diff --git a/spec/features/compare_spec.rb b/spec/features/compare_spec.rb index ca7f73e24cc..33dfd0d5b62 100644 --- a/spec/features/compare_spec.rb +++ b/spec/features/compare_spec.rb @@ -12,15 +12,16 @@ describe "Compare", js: true do describe "branches" do it "pre-populates fields" do - expect(page.find_field("from").value).to eq("master") + expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("master") + expect(find(".js-compare-to-dropdown .dropdown-toggle-text")).to have_content("master") end it "compares branches" do - fill_in "from", with: "fea" - find("#from").click + select_using_dropdown "from", "feature" + expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("feature") - click_link "feature" - expect(page.find_field("from").value).to eq("feature") + select_using_dropdown "to", "binary-encoding" + expect(find(".js-compare-to-dropdown .dropdown-toggle-text")).to have_content("binary-encoding") click_button "Compare" expect(page).to have_content "Commits" @@ -29,14 +30,21 @@ describe "Compare", js: true do describe "tags" do it "compares tags" do - fill_in "from", with: "v1.0" - find("#from").click + select_using_dropdown "from", "v1.0.0" + expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("v1.0.0") - click_link "v1.0.0" - expect(page.find_field("from").value).to eq("v1.0.0") + select_using_dropdown "to", "v1.1.0" + expect(find(".js-compare-to-dropdown .dropdown-toggle-text")).to have_content("v1.1.0") click_button "Compare" expect(page).to have_content "Commits" end end + + def select_using_dropdown(dropdown_type, selection) + dropdown = find(".js-compare-#{dropdown_type}-dropdown") + dropdown.find(".compare-dropdown-toggle").click + dropdown.fill_in("Filter by branch/tag", with: selection) + click_link selection + end end -- cgit v1.2.1 From 8062380f60df576478677e9819ff602303166e4d Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 21 Sep 2016 17:06:33 -0600 Subject: Upgrade Devise from 4.1.1 to 4.2.0. This fixes an issue with Rails 5 and brings us up-to-date with the latest Devise release. This also replaces the deprecated Devise::TestHelpers with Devise::Test::ControllerHelpers. Changelog: https://github.com/plataformatec/devise/blob/v4.2.0/CHANGELOG.md#420---2016-07-01 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- spec/spec_helper.rb | 2 +- spec/views/admin/dashboard/index.html.haml_spec.rb | 2 +- spec/views/projects/builds/show.html.haml_spec.rb | 2 +- spec/views/projects/issues/_related_branches.html.haml_spec.rb | 2 +- spec/views/projects/merge_requests/_heading.html.haml_spec.rb | 2 +- spec/views/projects/merge_requests/edit.html.haml_spec.rb | 2 +- spec/views/projects/merge_requests/show.html.haml_spec.rb | 2 +- spec/views/projects/notes/_form.html.haml_spec.rb | 2 +- spec/views/projects/pipelines/show.html.haml_spec.rb | 2 +- spec/views/projects/tree/show.html.haml_spec.rb | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Gemfile b/Gemfile index 633ce2de785..d9ace58a6f2 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem 'mysql2', '~> 0.3.16', group: :mysql gem 'pg', '~> 0.18.2', group: :postgres # Authentication libraries -gem 'devise', '~> 4.0' +gem 'devise', '~> 4.2' gem 'doorkeeper', '~> 4.2.0' gem 'omniauth', '~> 1.3.1' gem 'omniauth-auth0', '~> 1.4.1' diff --git a/Gemfile.lock b/Gemfile.lock index 1691f92c8ce..d4f5f301f2b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -161,7 +161,7 @@ GEM activerecord (>= 3.2.0, < 5.1) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) - devise (4.1.1) + devise (4.2.0) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0, < 5.1) @@ -587,7 +587,7 @@ GEM request_store (1.3.1) rerun (0.11.0) listen (~> 3.0) - responders (2.1.1) + responders (2.3.0) railties (>= 4.2.0, < 5.1) rinku (2.0.0) rotp (2.1.2) @@ -835,7 +835,7 @@ DEPENDENCIES d3_rails (~> 3.5.0) database_cleaner (~> 1.5.0) default_value_for (~> 3.0.0) - devise (~> 4.0) + devise (~> 4.2) devise-two-factor (~> 3.0.0) diffy (~> 3.0.3) doorkeeper (~> 4.2.0) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 02b2b3ca101..b19f5824236 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -26,7 +26,7 @@ RSpec.configure do |config| config.verbose_retry = true config.display_try_failure_messages = true - config.include Devise::TestHelpers, type: :controller + config.include Devise::Test::ControllerHelpers, type: :controller config.include Warden::Test::Helpers, type: :request config.include LoginHelpers, type: :feature config.include StubConfiguration diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb index dae858a52f6..68d2d72876e 100644 --- a/spec/views/admin/dashboard/index.html.haml_spec.rb +++ b/spec/views/admin/dashboard/index.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'admin/dashboard/index.html.haml' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers before do assign(:projects, create_list(:empty_project, 1)) diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index 446ba3bfa14..da43622d3f9 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/builds/show' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers let(:project) { create(:project) } let(:pipeline) do diff --git a/spec/views/projects/issues/_related_branches.html.haml_spec.rb b/spec/views/projects/issues/_related_branches.html.haml_spec.rb index 78af61f15a7..c8a3d02d8fd 100644 --- a/spec/views/projects/issues/_related_branches.html.haml_spec.rb +++ b/spec/views/projects/issues/_related_branches.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/issues/_related_branches' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers let(:project) { create(:project) } let(:branch) { project.repository.find_branch('feature') } diff --git a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb index 21f49d396e7..86980f59cd8 100644 --- a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/merge_requests/widget/_heading' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers context 'when released to an environment' do let(:project) { merge_request.target_project } diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb index 31bbb150698..26ea252fecb 100644 --- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/merge_requests/edit.html.haml' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index fe0780e72df..68fbb4585c1 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/merge_requests/show.html.haml' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/views/projects/notes/_form.html.haml_spec.rb b/spec/views/projects/notes/_form.html.haml_spec.rb index 932d6756ad2..b14b1ece2d0 100644 --- a/spec/views/projects/notes/_form.html.haml_spec.rb +++ b/spec/views/projects/notes/_form.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/notes/_form' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers let(:user) { create(:user) } let(:project) { create(:empty_project) } diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb index ac7f3ffb157..bf027499c94 100644 --- a/spec/views/projects/pipelines/show.html.haml_spec.rb +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/pipelines/show' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) } diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb index 0f3fc1ee1ac..c381b1a86df 100644 --- a/spec/views/projects/tree/show.html.haml_spec.rb +++ b/spec/views/projects/tree/show.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'projects/tree/show' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers let(:project) { create(:project) } let(:repository) { project.repository } -- cgit v1.2.1 From e13dc69be271e57fe9cc1a3cbf0f7a86d735b2c6 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Tue, 27 Sep 2016 20:52:41 -0600 Subject: Fix test failure by accessing Content-Type header directly. --- spec/controllers/projects/repositories_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index 2fe3c263524..38e02a46626 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -8,7 +8,7 @@ describe Projects::RepositoriesController do it 'responds with redirect in correct format' do get :archive, namespace_id: project.namespace.path, project_id: project.path, format: "zip" - expect(response.content_type).to start_with 'text/html' + expect(response.header["Content-Type"]).to start_with('text/html') expect(response).to be_redirect end end -- cgit v1.2.1 From 924a6b7d33a245845071894de8d46d77bd44b52e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 28 Jul 2016 19:38:32 +0200 Subject: New AccessRequestsFinder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/admin/groups_controller.rb | 2 +- app/controllers/admin/projects_controller.rb | 2 +- app/controllers/groups/group_members_controller.rb | 2 +- .../projects/project_members_controller.rb | 2 +- app/finders/access_requests_finder.rb | 29 +++++++ lib/api/access_requests.rb | 4 +- spec/finders/access_requests_finder_spec.rb | 89 ++++++++++++++++++++++ 7 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 app/finders/access_requests_finder.rb create mode 100644 spec/finders/access_requests_finder_spec.rb diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index aed77d0358a..aa7570cd896 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -10,7 +10,7 @@ class Admin::GroupsController < Admin::ApplicationController def show @members = @group.members.order("access_level DESC").page(params[:members_page]) - @requesters = @group.requesters + @requesters = AccessRequestsFinder.new(@group).execute(current_user) @projects = @group.projects.page(params[:projects_page]) end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 0d2f4f6eb38..1d963bdf7d5 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -22,7 +22,7 @@ class Admin::ProjectsController < Admin::ApplicationController end @project_members = @project.members.page(params[:project_members_page]) - @requesters = @project.requesters + @requesters = AccessRequestsFinder.new(@project).execute(current_user) end def transfer diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 272164cd0cc..9c323d7705a 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -15,7 +15,7 @@ class Groups::GroupMembersController < Groups::ApplicationController end @members = @members.order('access_level DESC').page(params[:page]).per(50) - @requesters = @group.requesters if can?(current_user, :admin_group, @group) + @requesters = AccessRequestsFinder.new(@group).execute(current_user) @group_member = @group.group_members.new end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 42a7e5a2c30..2343c7d20ec 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -29,7 +29,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @group_members = @group_members.order('access_level DESC') end - @requesters = @project.requesters if can?(current_user, :admin_project, @project) + @requesters = AccessRequestsFinder.new(@project).execute(current_user) @project_member = @project.project_members.new @project_group_links = @project.project_group_links diff --git a/app/finders/access_requests_finder.rb b/app/finders/access_requests_finder.rb new file mode 100644 index 00000000000..78fb7627741 --- /dev/null +++ b/app/finders/access_requests_finder.rb @@ -0,0 +1,29 @@ +class AccessRequestsFinder + attr_accessor :source + + # Arguments: + # source - a Group or Project + def initialize(source) + @source = source + end + + def execute(current_user, raise_error: false) + if cannot_see_access_requests?(current_user) + raise Gitlab::Access::AccessDeniedError if raise_error + + return [] + end + + source.requesters + end + + def execute!(current_user) + execute(current_user, raise_error: true) + end + + private + + def cannot_see_access_requests?(current_user) + !source || !current_user || !current_user.can?(:"admin_#{source.class.to_s.underscore}", source) + end +end diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 9d1d9058996..7b9de7c9598 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -16,9 +16,9 @@ module API # GET /projects/:id/access_requests get ":id/access_requests" do source = find_source(source_type, params[:id]) - authorize_admin_source!(source_type, source) - access_requesters = paginate(source.requesters.includes(:user)) + access_requesters = AccessRequestsFinder.new(source).execute!(current_user) + access_requesters = paginate(access_requesters.includes(:user)) present access_requesters.map(&:user), with: Entities::AccessRequester, source: source end diff --git a/spec/finders/access_requests_finder_spec.rb b/spec/finders/access_requests_finder_spec.rb new file mode 100644 index 00000000000..6cc90299417 --- /dev/null +++ b/spec/finders/access_requests_finder_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe AccessRequestsFinder, services: true do + let(:user) { create(:user) } + let(:access_requester) { create(:user) } + let(:project) { create(:project) } + let(:group) { create(:group) } + + before do + project.request_access(access_requester) + group.request_access(access_requester) + end + + shared_examples 'a finder returning access requesters' do |method_name| + it 'returns access requesters' do + access_requesters = described_class.new(source).public_send(method_name, user) + + expect(access_requesters.size).to eq(1) + expect(access_requesters.first).to be_a "#{source.class.to_s}Member".constantize + expect(access_requesters.first.user).to eq(access_requester) + end + end + + shared_examples 'a finder returning no results' do |method_name| + it 'raises Gitlab::Access::AccessDeniedError' do + expect(described_class.new(source).public_send(method_name, user)).to be_empty + end + end + + shared_examples 'a finder raising Gitlab::Access::AccessDeniedError' do |method_name| + it 'raises Gitlab::Access::AccessDeniedError' do + expect { described_class.new(source).public_send(method_name, user) }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + + describe '#execute' do + context 'when current user cannot see project access requests' do + it_behaves_like 'a finder returning no results', :execute do + let(:source) { project } + end + + it_behaves_like 'a finder returning no results', :execute do + let(:source) { group } + end + end + + context 'when current user can see access requests' do + before do + project.team << [user, :master] + group.add_owner(user) + end + + it_behaves_like 'a finder returning access requesters', :execute do + let(:source) { project } + end + + it_behaves_like 'a finder returning access requesters', :execute do + let(:source) { group } + end + end + end + + describe '#execute!' do + context 'when current user cannot see access requests' do + it_behaves_like 'a finder raising Gitlab::Access::AccessDeniedError', :execute! do + let(:source) { project } + end + + it_behaves_like 'a finder raising Gitlab::Access::AccessDeniedError', :execute! do + let(:source) { group } + end + end + + context 'when current user can see access requests' do + before do + project.team << [user, :master] + group.add_owner(user) + end + + it_behaves_like 'a finder returning access requesters', :execute! do + let(:source) { project } + end + + it_behaves_like 'a finder returning access requesters', :execute! do + let(:source) { group } + end + end + end +end -- cgit v1.2.1 From 5f88aafbf697d791707938c9732ec2d86723252a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 27 Sep 2016 14:35:42 +0200 Subject: Improve the logic in AccessRequestsFinder#execute & #execute! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/finders/access_requests_finder.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/app/finders/access_requests_finder.rb b/app/finders/access_requests_finder.rb index 78fb7627741..79b0ba39e2c 100644 --- a/app/finders/access_requests_finder.rb +++ b/app/finders/access_requests_finder.rb @@ -7,23 +7,21 @@ class AccessRequestsFinder @source = source end - def execute(current_user, raise_error: false) - if cannot_see_access_requests?(current_user) - raise Gitlab::Access::AccessDeniedError if raise_error - - return [] - end - - source.requesters + def execute(*args) + execute!(*args) + rescue Gitlab::Access::AccessDeniedError + [] end def execute!(current_user) - execute(current_user, raise_error: true) + raise Gitlab::Access::AccessDeniedError unless can_see_access_requests?(current_user) + + source.requesters end private - def cannot_see_access_requests?(current_user) - !source || !current_user || !current_user.can?(:"admin_#{source.class.to_s.underscore}", source) + def can_see_access_requests?(current_user) + source && current_user && current_user.can?(:"admin_#{source.class.to_s.underscore}", source) end end -- cgit v1.2.1 From 486f755c4fc3606df3c6cfc24412a9e7d02d9e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 27 Sep 2016 15:52:30 +0200 Subject: Use Ability.allowed? instead of current_user.can? in AccessRequestsFinder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/finders/access_requests_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/finders/access_requests_finder.rb b/app/finders/access_requests_finder.rb index 79b0ba39e2c..b6ee49df99b 100644 --- a/app/finders/access_requests_finder.rb +++ b/app/finders/access_requests_finder.rb @@ -22,6 +22,6 @@ class AccessRequestsFinder private def can_see_access_requests?(current_user) - source && current_user && current_user.can?(:"admin_#{source.class.to_s.underscore}", source) + source && Ability.allowed?(current_user, :"admin_#{source.class.to_s.underscore}", source) end end -- cgit v1.2.1 From ec0061a95cbba02286b2c143048c93d8f26ff5f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 16 Sep 2016 17:54:21 +0200 Subject: Allow Member.add_user to handle access requesters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes include: - Ensure Member.add_user is not called directly when not necessary - New GroupMember.add_users_to_group to have the same abstraction level as for Project - Refactor Member.add_user to take a source instead of an array of members - Fix Rubocop offenses - Always use Project#add_user instead of project.team.add_user - Factorize users addition as members in Member.add_users_to_source - Make access_level a keyword argument in GroupMember.add_users_to_group and ProjectMember.add_users_to_projects - Destroy any requester before adding them as a member - Improve the way we handle access requesters in Member.add_user Instead of removing the requester and creating a new member, we now simply accepts their access request. This way, they will receive a "access request granted" email. - Fix error that was previously silently ignored - Stop raising when access level is invalid in Member, let Rails validation do their work Signed-off-by: Rémy Coutable --- app/models/group.rb | 36 +-- app/models/member.rb | 79 ++++--- app/models/members/group_member.rb | 16 ++ app/models/members/project_member.rb | 44 ++-- app/models/project.rb | 5 +- app/models/project_team.rb | 14 +- db/fixtures/development/06_teams.rb | 2 +- lib/api/members.rb | 17 +- lib/gitlab/access.rb | 4 + .../projects/templates_controller_spec.rb | 2 +- spec/factories/project_members.rb | 23 +- .../members/owner_cannot_leave_project_spec.rb | 4 +- ...er_cannot_request_access_to_his_project_spec.rb | 4 +- spec/features/projects_spec.rb | 6 +- spec/finders/joined_groups_finder_spec.rb | 2 +- spec/finders/projects_finder_spec.rb | 2 +- spec/lib/gitlab/template/issue_template_spec.rb | 6 +- .../gitlab/template/merge_request_template_spec.rb | 6 +- spec/mailers/notify_spec.rb | 50 +++-- spec/models/issue_spec.rb | 2 +- spec/models/member_spec.rb | 243 +++++++++++++++++---- spec/models/members/group_member_spec.rb | 27 +++ spec/models/members/project_member_spec.rb | 50 +++-- spec/models/project_spec.rb | 2 +- spec/requests/api/merge_requests_spec.rb | 4 +- 25 files changed, 431 insertions(+), 219 deletions(-) diff --git a/app/models/group.rb b/app/models/group.rb index aefb94b2ada..a2f88cca828 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -102,40 +102,44 @@ class Group < Namespace self[:lfs_enabled] end - def add_users(user_ids, access_level, current_user: nil, expires_at: nil) - user_ids.each do |user_id| - Member.add_user( - self.group_members, - user_id, - access_level, - current_user: current_user, - expires_at: expires_at - ) - end + def add_users(users, access_level, current_user: nil, expires_at: nil) + GroupMember.add_users_to_group( + self, + users, + access_level, + current_user: current_user, + expires_at: expires_at + ) end def add_user(user, access_level, current_user: nil, expires_at: nil) - add_users([user], access_level, current_user: current_user, expires_at: expires_at) + GroupMember.add_user( + self, + user, + access_level, + current_user: current_user, + expires_at: expires_at + ) end def add_guest(user, current_user = nil) - add_user(user, Gitlab::Access::GUEST, current_user: current_user) + add_user(user, :guest, current_user: current_user) end def add_reporter(user, current_user = nil) - add_user(user, Gitlab::Access::REPORTER, current_user: current_user) + add_user(user, :reporter, current_user: current_user) end def add_developer(user, current_user = nil) - add_user(user, Gitlab::Access::DEVELOPER, current_user: current_user) + add_user(user, :developer, current_user: current_user) end def add_master(user, current_user = nil) - add_user(user, Gitlab::Access::MASTER, current_user: current_user) + add_user(user, :master, current_user: current_user) end def add_owner(user, current_user = nil) - add_user(user, Gitlab::Access::OWNER, current_user: current_user) + add_user(user, :owner, current_user: current_user) end def has_owner?(user) diff --git a/app/models/member.rb b/app/models/member.rb index 69406379948..265c11ca113 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -80,49 +80,70 @@ class Member < ActiveRecord::Base find_by(invite_token: invite_token) end - # This method is used to find users that have been entered into the "Add members" field. - # These can be the User objects directly, their IDs, their emails, or new emails to be invited. - def user_for_id(user_id) - return user_id if user_id.is_a?(User) - - user = User.find_by(id: user_id) - user ||= User.find_by(email: user_id) - user ||= user_id - user - end - - def add_user(members, user_id, access_level, current_user: nil, expires_at: nil) - user = user_for_id(user_id) + def add_user(source, user, access_level, current_user: nil, expires_at: nil) + user = retrieve_user(user) + access_level = retrieve_access_level(access_level) # `user` can be either a User object or an email to be invited - if user.is_a?(User) - member = members.find_or_initialize_by(user_id: user.id) + member = + if user.is_a?(User) + source.members.find_by(user_id: user.id) || + source.requesters.find_by(user_id: user.id) || + source.members.build(user_id: user.id) + else + source.members.build(invite_email: user) + end + + return member unless can_update_member?(current_user, member) + + member.attributes = { + created_by: member.created_by || current_user, + access_level: access_level, + expires_at: expires_at + } + + if member.request? + member.accept_request else - member = members.build - member.invite_email = user + member.save end - if can_update_member?(current_user, member) || project_creator?(member, access_level) - member.created_by ||= current_user - member.access_level = access_level - member.expires_at = expires_at + member + end - member.save - end + def access_levels + Gitlab::Access.sym_options end private + # This method is used to find users that have been entered into the "Add members" field. + # These can be the User objects directly, their IDs, their emails, or new emails to be invited. + def retrieve_user(user) + return user if user.is_a?(User) + + User.find_by(id: user) || User.find_by(email: user) || user + end + + def retrieve_access_level(access_level) + access_levels.fetch(access_level) { access_level.to_i } + end + def can_update_member?(current_user, member) # There is no current user for bulk actions, in which case anything is allowed - !current_user || - current_user.can?(:update_group_member, member) || - current_user.can?(:update_project_member, member) + !current_user || current_user.can?(:"update_#{member.type.underscore}", member) end - def project_creator?(member, access_level) - member.new_record? && member.owner? && - access_level.to_i == ProjectMember::MASTER + def add_users_to_source(source, users, access_level, current_user: nil, expires_at: nil) + users.each do |user| + add_user( + source, + user, + access_level, + current_user: current_user, + expires_at: expires_at + ) + end end end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 2f13d339c89..1b54a85d064 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -12,6 +12,22 @@ class GroupMember < Member Gitlab::Access.options_with_owner end + def self.access_levels + Gitlab::Access.sym_options_with_owner + end + + def self.add_users_to_group(group, users, access_level, current_user: nil, expires_at: nil) + self.transaction do + add_users_to_source( + group, + users, + access_level, + current_user: current_user, + expires_at: expires_at + ) + end + end + def group source end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index ec2d40eb11c..125f26369d7 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -34,36 +34,20 @@ class ProjectMember < Member # :master # ) # - def add_users_to_projects(project_ids, user_ids, access, current_user: nil, expires_at: nil) - access_level = if roles_hash.has_key?(access) - roles_hash[access] - elsif roles_hash.values.include?(access.to_i) - access - else - raise "Non valid access" - end - - users = user_ids.map { |user_id| Member.user_for_id(user_id) } - - ProjectMember.transaction do + def add_users_to_projects(project_ids, users, access_level, current_user: nil, expires_at: nil) + self.transaction do project_ids.each do |project_id| project = Project.find(project_id) - users.each do |user| - Member.add_user( - project.project_members, - user, - access_level, - current_user: current_user, - expires_at: expires_at - ) - end + add_users_to_source( + project, + users, + access_level, + current_user: current_user, + expires_at: expires_at + ) end end - - true - rescue - false end def truncate_teams(project_ids) @@ -84,13 +68,15 @@ class ProjectMember < Member truncate_teams [project.id] end - def roles_hash - Gitlab::Access.sym_options - end - def access_level_roles Gitlab::Access.options end + + private + + def can_update_member?(current_user, member) + super || (member.owner? && member.new_record?) + end end def access_field diff --git a/app/models/project.rb b/app/models/project.rb index 7265cb55594..507228606df 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -146,6 +146,7 @@ class Project < ActiveRecord::Base delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true + delegate :add_user, to: :team # Validations validates :creator, presence: true, on: :create @@ -1016,10 +1017,6 @@ class Project < ActiveRecord::Base project_members.find_by(user_id: user) end - def add_user(user, access_level, current_user: nil, expires_at: nil) - team.add_user(user, access_level, current_user: current_user, expires_at: expires_at) - end - def default_branch @default_branch ||= repository.root_ref if repository.exists? end diff --git a/app/models/project_team.rb b/app/models/project_team.rb index d9ce5088903..79d041d2775 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -33,18 +33,24 @@ class ProjectTeam member end - def add_users(users, access, current_user: nil, expires_at: nil) + def add_users(users, access_level, current_user: nil, expires_at: nil) ProjectMember.add_users_to_projects( [project.id], users, - access, + access_level, current_user: current_user, expires_at: expires_at ) end - def add_user(user, access, current_user: nil, expires_at: nil) - add_users([user], access, current_user: current_user, expires_at: expires_at) + def add_user(user, access_level, current_user: nil, expires_at: nil) + ProjectMember.add_user( + project, + user, + access_level, + current_user: current_user, + expires_at: expires_at + ) end # Remove all users from project team diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb index 3e8cdcd67b4..9739a5ac8d5 100644 --- a/db/fixtures/development/06_teams.rb +++ b/db/fixtures/development/06_teams.rb @@ -1,7 +1,7 @@ Gitlab::Seeder.quiet do Group.all.each do |group| User.all.sample(4).each do |user| - if group.add_users([user.id], Gitlab::Access.values.sample) + if group.add_user(user, Gitlab::Access.values.sample).persisted? print '.' else print 'F' diff --git a/lib/api/members.rb b/lib/api/members.rb index 37f0a6512f4..a18ce769e29 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -59,13 +59,6 @@ module API authorize_admin_source!(source_type, source) required_attributes! [:user_id, :access_level] - access_requester = source.requesters.find_by(user_id: params[:user_id]) - if access_requester - # We pass current_user = access_requester so that the requester doesn't - # receive a "access denied" email - ::Members::DestroyService.new(access_requester, access_requester.user).execute - end - member = source.members.find_by(user_id: params[:user_id]) # This is to ensure back-compatibility but 409 behavior should be used @@ -73,18 +66,12 @@ module API conflict!('Member already exists') if source_type == 'group' && member unless member - source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at]) - member = source.members.find_by(user_id: params[:user_id]) + member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at]) end - if member + if member.persisted? && member.valid? present member.user, with: Entities::Member, member: member else - # Since `source.add_user` doesn't return a member object, we have to - # build a new one and populate its errors in order to render them. - member = source.members.build(attributes_for_keys([:user_id, :access_level, :expires_at])) - member.valid? # populate the errors - # This is to ensure back-compatibility but 400 behavior should be used # for all validation errors in 9.0! render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level) diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index a533bac2692..9b484a2ecfd 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -53,6 +53,10 @@ module Gitlab } end + def sym_options_with_owner + sym_options.merge(owner: OWNER) + end + def protection_options { "Not protected: Both developers and masters can push new commits, force push, or delete the branch." => PROTECTION_NONE, diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index 7b3a26d7ca7..19a152bcb05 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::TemplatesController do end before do - project.team.add_user(user, Gitlab::Access::MASTER) + project.add_user(user, Gitlab::Access::MASTER) project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false) end diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb index cf3659ba275..1ddb305a8af 100644 --- a/spec/factories/project_members.rb +++ b/spec/factories/project_members.rb @@ -4,24 +4,9 @@ FactoryGirl.define do project master - trait :guest do - access_level ProjectMember::GUEST - end - - trait :reporter do - access_level ProjectMember::REPORTER - end - - trait :developer do - access_level ProjectMember::DEVELOPER - end - - trait :master do - access_level ProjectMember::MASTER - end - - trait :owner do - access_level ProjectMember::OWNER - end + trait(:guest) { access_level ProjectMember::GUEST } + trait(:reporter) { access_level ProjectMember::REPORTER } + trait(:developer) { access_level ProjectMember::DEVELOPER } + trait(:master) { access_level ProjectMember::MASTER } end end diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb index 67811b1048e..6e948b7a616 100644 --- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb +++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb @@ -1,12 +1,10 @@ require 'spec_helper' feature 'Projects > Members > Owner cannot leave project', feature: true do - let(:owner) { create(:user) } let(:project) { create(:project) } background do - project.team << [owner, :owner] - login_as(owner) + login_as(project.owner) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb index 0e54c4fdf20..4ca9272b9c1 100644 --- a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb +++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb @@ -1,12 +1,10 @@ require 'spec_helper' feature 'Projects > Members > Owner cannot request access to his project', feature: true do - let(:owner) { create(:user) } let(:project) { create(:project) } background do - project.team << [owner, :owner] - login_as(owner) + login_as(project.owner) visit namespace_project_path(project.namespace, project) end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 2242cb6236a..c30d38b6508 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -82,7 +82,7 @@ feature 'Project', feature: true do before do login_with(user) - project.team.add_user(user, Gitlab::Access::MASTER) + project.add_user(user, Gitlab::Access::MASTER) visit namespace_project_path(project.namespace, project) end @@ -101,8 +101,8 @@ feature 'Project', feature: true do context 'on issues page', js: true do before do login_with(user) - project.team.add_user(user, Gitlab::Access::MASTER) - project2.team.add_user(user, Gitlab::Access::MASTER) + project.add_user(user, Gitlab::Access::MASTER) + project2.add_user(user, Gitlab::Access::MASTER) visit namespace_project_issue_path(project.namespace, project, issue) end diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb index f90a8e007c8..29a47e005a6 100644 --- a/spec/finders/joined_groups_finder_spec.rb +++ b/spec/finders/joined_groups_finder_spec.rb @@ -43,7 +43,7 @@ describe JoinedGroupsFinder do context 'if profile visitor is in one of the private group projects' do before do project = create(:project, :private, group: private_group, name: 'B', path: 'B') - project.team.add_user(profile_visitor, Gitlab::Access::DEVELOPER) + project.add_user(profile_visitor, Gitlab::Access::DEVELOPER) end it 'shows group' do diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index 7a3a74335e8..13bda5f7c5a 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -38,7 +38,7 @@ describe ProjectsFinder do describe 'with private projects' do before do - private_project.team.add_user(user, Gitlab::Access::MASTER) + private_project.add_user(user, Gitlab::Access::MASTER) end it do diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb index f770857e958..d2d334e6413 100644 --- a/spec/lib/gitlab/template/issue_template_spec.rb +++ b/spec/lib/gitlab/template/issue_template_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::Template::IssueTemplate do let(:file_path_3) { '.gitlab/issue_templates/feature_proposal.md' } before do - project.team.add_user(user, Gitlab::Access::MASTER) + project.add_user(user, Gitlab::Access::MASTER) project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false) project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false) project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false) @@ -53,7 +53,7 @@ describe Gitlab::Template::IssueTemplate do context 'when repo is bare or empty' do let(:empty_project) { create(:empty_project) } - before { empty_project.team.add_user(user, Gitlab::Access::MASTER) } + before { empty_project.add_user(user, Gitlab::Access::MASTER) } it "returns empty array" do templates = subject.by_category('', empty_project) @@ -78,7 +78,7 @@ describe Gitlab::Template::IssueTemplate do context "when repo is empty" do let(:empty_project) { create(:empty_project) } - before { empty_project.team.add_user(user, Gitlab::Access::MASTER) } + before { empty_project.add_user(user, Gitlab::Access::MASTER) } it "raises file not found" do issue_template = subject.new('.gitlab/issue_templates/not_existent.md', empty_project) diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb index bb0f68043fa..ddf68c4cf78 100644 --- a/spec/lib/gitlab/template/merge_request_template_spec.rb +++ b/spec/lib/gitlab/template/merge_request_template_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::Template::MergeRequestTemplate do let(:file_path_3) { '.gitlab/merge_request_templates/feature_proposal.md' } before do - project.team.add_user(user, Gitlab::Access::MASTER) + project.add_user(user, Gitlab::Access::MASTER) project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false) project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false) project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false) @@ -53,7 +53,7 @@ describe Gitlab::Template::MergeRequestTemplate do context 'when repo is bare or empty' do let(:empty_project) { create(:empty_project) } - before { empty_project.team.add_user(user, Gitlab::Access::MASTER) } + before { empty_project.add_user(user, Gitlab::Access::MASTER) } it "returns empty array" do templates = subject.by_category('', empty_project) @@ -78,7 +78,7 @@ describe Gitlab::Template::MergeRequestTemplate do context "when repo is empty" do let(:empty_project) { create(:empty_project) } - before { empty_project.team.add_user(user, Gitlab::Access::MASTER) } + before { empty_project.add_user(user, Gitlab::Access::MASTER) } it "raises file not found" do issue_template = subject.new('.gitlab/merge_request_templates/not_existent.md', empty_project) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 0363bc74939..0f69119e82e 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -492,21 +492,22 @@ describe Notify do end end - def invite_to_project(project:, email:, inviter:) - Member.add_user( - project.project_members, - 'toto@example.com', - Gitlab::Access::DEVELOPER, - current_user: inviter + def invite_to_project(project, inviter:) + create( + :project_member, + :developer, + project: project, + invite_token: '1234', + invite_email: 'toto@example.com', + user: nil, + created_by: inviter ) - - project.project_members.invite.last end describe 'project invitation' do let(:project) { create(:project) } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } - let(:project_member) { invite_to_project(project: project, email: 'toto@example.com', inviter: master) } + let(:project_member) { invite_to_project(project, inviter: master) } subject { Notify.member_invited_email('project', project_member.id, project_member.invite_token) } @@ -525,10 +526,10 @@ describe Notify do describe 'project invitation accepted' do let(:project) { create(:project) } - let(:invited_user) { create(:user) } + let(:invited_user) { create(:user, name: 'invited user') } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } let(:project_member) do - invitee = invite_to_project(project: project, email: 'toto@example.com', inviter: master) + invitee = invite_to_project(project, inviter: master) invitee.accept_invite!(invited_user) invitee end @@ -552,7 +553,7 @@ describe Notify do let(:project) { create(:project) } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } let(:project_member) do - invitee = invite_to_project(project: project, email: 'toto@example.com', inviter: master) + invitee = invite_to_project(project, inviter: master) invitee.decline_invite! invitee end @@ -744,21 +745,22 @@ describe Notify do end end - def invite_to_group(group:, email:, inviter:) - Member.add_user( - group.group_members, - 'toto@example.com', - Gitlab::Access::DEVELOPER, - current_user: inviter + def invite_to_group(group, inviter:) + create( + :group_member, + :developer, + group: group, + invite_token: '1234', + invite_email: 'toto@example.com', + user: nil, + created_by: inviter ) - - group.group_members.invite.last end describe 'group invitation' do let(:group) { create(:group) } let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } - let(:group_member) { invite_to_group(group: group, email: 'toto@example.com', inviter: owner) } + let(:group_member) { invite_to_group(group, inviter: owner) } subject { Notify.member_invited_email('group', group_member.id, group_member.invite_token) } @@ -777,10 +779,10 @@ describe Notify do describe 'group invitation accepted' do let(:group) { create(:group) } - let(:invited_user) { create(:user) } + let(:invited_user) { create(:user, name: 'invited user') } let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } let(:group_member) do - invitee = invite_to_group(group: group, email: 'toto@example.com', inviter: owner) + invitee = invite_to_group(group, inviter: owner) invitee.accept_invite!(invited_user) invitee end @@ -804,7 +806,7 @@ describe Notify do let(:group) { create(:group) } let(:owner) { create(:user).tap { |u| group.add_user(u, Gitlab::Access::OWNER) } } let(:group_member) do - invitee = invite_to_group(group: group, email: 'toto@example.com', inviter: owner) + invitee = invite_to_group(group, inviter: owner) invitee.decline_invite! invitee end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 3259f795296..3b8b743af2d 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -494,7 +494,7 @@ describe Issue, models: true do context 'with an admin user' do let(:project) { create(:empty_project) } - let(:user) { create(:user, admin: true) } + let(:user) { create(:admin) } it 'returns true for a regular issue' do issue = build(:issue, project: project) diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 0b1634f654a..a9d3fcc6587 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -74,22 +74,17 @@ describe Member, models: true do @blocked_master = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::MASTER) @blocked_developer = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::DEVELOPER) - Member.add_user( - project.members, - 'toto1@example.com', - Gitlab::Access::DEVELOPER, - current_user: @master_user - ) - @invited_member = project.members.invite.find_by_invite_email('toto1@example.com') + @invited_member = create(:project_member, :developer, + project: project, + invite_token: '1234', + invite_email: 'toto1@example.com') accepted_invite_user = build(:user, state: :active) - Member.add_user( - project.members, - 'toto2@example.com', - Gitlab::Access::DEVELOPER, - current_user: @master_user - ) - @accepted_invite_member = project.members.invite.find_by_invite_email('toto2@example.com').tap { |u| u.accept_invite!(accepted_invite_user) } + @accepted_invite_member = create(:project_member, :developer, + project: project, + invite_token: '1234', + invite_email: 'toto2@example.com'). + tap { |u| u.accept_invite!(accepted_invite_user) } requested_user = create(:user).tap { |u| project.request_access(u) } @requested_member = project.requesters.find_by(user_id: requested_user.id) @@ -176,39 +171,209 @@ describe Member, models: true do it { is_expected.to respond_to(:user_email) } end - describe ".add_user" do - let!(:user) { create(:user) } - let(:project) { create(:project) } + describe '.add_user' do + %w[project group].each do |source_type| + context "when source is a #{source_type}" do + let!(:source) { create(source_type) } + let!(:user) { create(:user) } + let!(:admin) { create(:admin) } - context "when called with a user id" do - it "adds the user as a member" do - Member.add_user(project.project_members, user.id, ProjectMember::MASTER) + it 'returns a Member object' do + member = described_class.add_user(source, user, :master) - expect(project.users).to include(user) - end - end + expect(member).to be_a "#{source_type.classify}Member".constantize + expect(member).to be_persisted + end - context "when called with a user object" do - it "adds the user as a member" do - Member.add_user(project.project_members, user, ProjectMember::MASTER) + it 'sets members.created_by to the given current_user' do + member = described_class.add_user(source, user, :master, current_user: admin) - expect(project.users).to include(user) - end - end + expect(member.created_by).to eq(admin) + end - context "when called with a known user email" do - it "adds the user as a member" do - Member.add_user(project.project_members, user.email, ProjectMember::MASTER) + it 'sets members.expires_at to the given expires_at' do + member = described_class.add_user(source, user, :master, expires_at: Date.new(2016, 9, 22)) - expect(project.users).to include(user) - end - end + expect(member.expires_at).to eq(Date.new(2016, 9, 22)) + end + + described_class.access_levels.each do |sym_key, int_access_level| + it "accepts the :#{sym_key} symbol as access level" do + expect(source.users).not_to include(user) + + member = described_class.add_user(source, user.id, sym_key) + + expect(member.access_level).to eq(int_access_level) + expect(source.users.reload).to include(user) + end + + it "accepts the #{int_access_level} integer as access level" do + expect(source.users).not_to include(user) + + member = described_class.add_user(source, user.id, int_access_level) + + expect(member.access_level).to eq(int_access_level) + expect(source.users.reload).to include(user) + end + end + + context 'with no current_user' do + context 'when called with a known user id' do + it 'adds the user as a member' do + expect(source.users).not_to include(user) + + described_class.add_user(source, user.id, :master) + + expect(source.users.reload).to include(user) + end + end + + context 'when called with an unknown user id' do + it 'adds the user as a member' do + expect(source.users).not_to include(user) + + described_class.add_user(source, 42, :master) + + expect(source.users.reload).not_to include(user) + end + end + + context 'when called with a user object' do + it 'adds the user as a member' do + expect(source.users).not_to include(user) + + described_class.add_user(source, user, :master) + + expect(source.users.reload).to include(user) + end + end + + context 'when called with a requester user object' do + before do + source.request_access(user) + end + + it 'adds the requester as a member' do + expect(source.users).not_to include(user) + expect(source.requesters.exists?(user_id: user)).to be_truthy + + expect { described_class.add_user(source, user, :master) }. + to raise_error(Gitlab::Access::AccessDeniedError) + + expect(source.users.reload).not_to include(user) + expect(source.requesters.reload.exists?(user_id: user)).to be_truthy + end + end + + context 'when called with a known user email' do + it 'adds the user as a member' do + expect(source.users).not_to include(user) + + described_class.add_user(source, user.email, :master) + + expect(source.users.reload).to include(user) + end + end + + context 'when called with an unknown user email' do + it 'creates an invited member' do + expect(source.users).not_to include(user) + + described_class.add_user(source, 'user@example.com', :master) + + expect(source.members.invite.pluck(:invite_email)).to include('user@example.com') + end + end + end + + context 'when current_user can update member' do + it 'creates the member' do + expect(source.users).not_to include(user) + + described_class.add_user(source, user, :master, current_user: admin) + + expect(source.users.reload).to include(user) + end + + context 'when called with a requester user object' do + before do + source.request_access(user) + end + + it 'adds the requester as a member' do + expect(source.users).not_to include(user) + expect(source.requesters.exists?(user_id: user)).to be_truthy + + described_class.add_user(source, user, :master, current_user: admin) + + expect(source.users.reload).to include(user) + expect(source.requesters.reload.exists?(user_id: user)).to be_falsy + end + end + end + + context 'when current_user cannot update member' do + it 'does not create the member' do + expect(source.users).not_to include(user) + + member = described_class.add_user(source, user, :master, current_user: user) + + expect(source.users.reload).not_to include(user) + expect(member).not_to be_persisted + end + + context 'when called with a requester user object' do + before do + source.request_access(user) + end + + it 'does not destroy the requester' do + expect(source.users).not_to include(user) + expect(source.requesters.exists?(user_id: user)).to be_truthy + + described_class.add_user(source, user, :master, current_user: user) + + expect(source.users.reload).not_to include(user) + expect(source.requesters.exists?(user_id: user)).to be_truthy + end + end + end + + context 'when member already exists' do + before do + source.add_user(user, :developer) + end + + context 'with no current_user' do + it 'updates the member' do + expect(source.users).to include(user) + + described_class.add_user(source, user, :master) + + expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MASTER) + end + end + + context 'when current_user can update member' do + it 'updates the member' do + expect(source.users).to include(user) + + described_class.add_user(source, user, :master, current_user: admin) + + expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MASTER) + end + end + + context 'when current_user cannot update member' do + it 'does not update the member' do + expect(source.users).to include(user) - context "when called with an unknown user email" do - it "adds a member invite" do - Member.add_user(project.project_members, "user@example.com", ProjectMember::MASTER) + described_class.add_user(source, user, :master, current_user: user) - expect(project.project_members.invite.pluck(:invite_email)).to include("user@example.com") + expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER) + end + end + end end end end diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 56fa7fa6134..370aeb9e0a9 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -1,6 +1,33 @@ require 'spec_helper' describe GroupMember, models: true do + describe '.access_level_roles' do + it 'returns Gitlab::Access.options_with_owner' do + expect(described_class.access_level_roles).to eq(Gitlab::Access.options_with_owner) + end + end + + describe '.access_levels' do + it 'returns Gitlab::Access.options_with_owner' do + expect(described_class.access_levels).to eq(Gitlab::Access.sym_options_with_owner) + end + end + + describe '.add_users_to_group' do + it 'adds the given users to the given group' do + group = create(:group) + users = create_list(:user, 2) + + described_class.add_users_to_group( + group, + [users.first.id, users.second], + described_class::MASTER + ) + + expect(group.users).to include(users.first, users.second) + end + end + describe 'notifications' do describe "#after_create" do it "sends email to user" do diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 805c15a4e5e..d85a1c1e3b2 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -15,6 +15,26 @@ describe ProjectMember, models: true do it { is_expected.to include_module(Gitlab::ShellAdapter) } end + describe '.access_level_roles' do + it 'returns Gitlab::Access.options' do + expect(described_class.access_level_roles).to eq(Gitlab::Access.options) + end + end + + describe '.add_user' do + context 'when called with the project owner' do + it 'adds the user as a member' do + project = create(:empty_project) + + expect(project.users).not_to include(project.owner) + + described_class.add_user(project, project.owner, :master, current_user: project.owner) + + expect(project.users.reload).to include(project.owner) + end + end + end + describe '#real_source_type' do subject { create(:project_member).real_source_type } @@ -50,7 +70,7 @@ describe ProjectMember, models: true do end end - describe :import_team do + describe '.import_team' do before do @project_1 = create :project @project_2 = create :project @@ -81,25 +101,21 @@ describe ProjectMember, models: true do end describe '.add_users_to_projects' do - before do - @project_1 = create :project - @project_2 = create :project + it 'adds the given users to the given projects' do + projects = create_list(:empty_project, 2) + users = create_list(:user, 2) - @user_1 = create :user - @user_2 = create :user - - ProjectMember.add_users_to_projects( - [@project_1.id, @project_2.id], - [@user_1.id, @user_2.id], - ProjectMember::MASTER - ) - end + described_class.add_users_to_projects( + [projects.first.id, projects.second], + [users.first.id, users.second], + described_class::MASTER) - it { expect(@project_1.users).to include(@user_1) } - it { expect(@project_1.users).to include(@user_2) } + expect(projects.first.users).to include(users.first) + expect(projects.first.users).to include(users.second) - it { expect(@project_2.users).to include(@user_1) } - it { expect(@project_2.users).to include(@user_2) } + expect(projects.second.users).to include(users.first) + expect(projects.second.users).to include(users.second) + end end describe '.truncate_teams' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 83f61f0af0a..50423d027ac 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -836,7 +836,7 @@ describe Project, models: true do describe 'when a user has access to a project' do before do - project.team.add_user(user, Gitlab::Access::MASTER) + project.add_user(user, Gitlab::Access::MASTER) end it { is_expected.to eq([project]) } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index a7930c59df9..b813ee967f8 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -15,7 +15,7 @@ describe API::API, api: true do let(:milestone) { create(:milestone, title: '1.0.0', project: project) } before do - project.team << [user, :reporters] + project.team << [user, :reporter] end describe "GET /projects/:id/merge_requests" do @@ -299,7 +299,7 @@ describe API::API, api: true do let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } before :each do |each| - fork_project.team << [user2, :reporters] + fork_project.team << [user2, :reporter] end it "returns merge_request" do -- cgit v1.2.1 From 60790cac6b3a13e0b33f37d896146539fddcb7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 27 Sep 2016 15:03:52 +0200 Subject: Use the new Members::ApproveAccessRequestService in Member#add_user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/member.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/member.rb b/app/models/member.rb index 265c11ca113..38a278ea559 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -103,7 +103,7 @@ class Member < ActiveRecord::Base } if member.request? - member.accept_request + ::Members::ApproveAccessRequestService.new(source, current_user, id: member.id).execute else member.save end -- cgit v1.2.1 From edbbbe3a2777c273b3dea1b40aa23b1b7847ca4f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 10 Nov 2015 23:23:49 +0200 Subject: Fix grammar and typos in Runners pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/views/admin/runners/index.html.haml | 22 ++++++++++++---------- app/views/admin/runners/show.html.haml | 12 ++++++------ app/views/projects/runners/_form.html.haml | 4 ++-- app/views/projects/runners/_runner.html.haml | 2 +- .../projects/runners/_shared_runners.html.haml | 16 +++++++++------- .../projects/runners/_specific_runners.html.haml | 12 ++++++------ app/views/projects/runners/index.html.haml | 12 ++++++------ spec/features/runners_spec.rb | 4 ++-- 8 files changed, 44 insertions(+), 40 deletions(-) diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index a53876d6757..b760b42fde0 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -5,8 +5,10 @@ %p.prepend-top-default %span - To register a new runner you should enter the following registration token. - With this token the runner will request a unique runner token and use that for future communication. + To register a new Runner you should enter the following registration + token. + With this token the Runner will request a unique Runner token and use + that for future communication. %br Registration token is %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token} @@ -24,27 +26,27 @@ .bs-callout %p - A 'runner' is a process which runs a build. - You can setup as many runners as you need. + A 'Runner' is a process which runs a build. + You can setup as many Runners as you need. %br - Runners can be placed on separate users, servers, and even on your local machine. + Runners can be placed on separate users, servers, even on your local machine. %br %div - %span Each runner can be in one of the following states: + %span Each Runner can be in one of the following states: %ul %li %span.label.label-success shared - \- run builds from all unassigned projects + \- Runner runs builds from all unassigned projects %li %span.label.label-info specific - \- run builds from assigned projects + \- Runner runs builds from assigned projects %li %span.label.label-warning locked - \- runner cannot be assigned to other projects + \- Runner cannot be assigned to other projects %li %span.label.label-danger paused - \- runner will not receive any new builds + \- Runner will not receive any new builds .append-bottom-20.clearfix .pull-left diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 61abfc6ecbe..a5e82e55cc1 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -11,14 +11,14 @@ - if @runner.shared? .bs-callout.bs-callout-success - %h4 This runner will process builds from ALL UNASSIGNED projects + %h4 This Runner will process builds from ALL UNASSIGNED projects %p - If you want runners to build only specific projects, enable them in the table below. + If you want Runners to build only specific projects, enable them in the table below. Keep in mind that this is a one way transition. - else .bs-callout.bs-callout-info - %h4 This runner will process builds only from ASSIGNED projects - %p You can't make this a shared runner. + %h4 This Runner will process builds only from ASSIGNED projects + %p You can't make this a shared Runner. %hr .append-bottom-20 @@ -26,7 +26,7 @@ .row .col-md-6 - %h4 Restrict projects for this runner + %h4 Restrict projects for this Runner - if @runner.projects.any? %table.table.assigned-projects %thead @@ -70,7 +70,7 @@ = paginate @projects .col-md-6 - %h4 Recent builds served by this runner + %h4 Recent builds served by this Runner %table.table.builds.runner-builds %thead %tr diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index c45a9d4f81f..33a9a96183c 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -5,7 +5,7 @@ .col-sm-10 .checkbox = f.check_box :active - %span.light Paused runners don't accept new builds + %span.light Paused Runners don't accept new builds .form-group = label :run_untagged, 'Run untagged jobs', class: 'control-label' .col-sm-10 @@ -33,6 +33,6 @@ Tags .col-sm-10 = f.text_field :tag_list, value: runner.tag_list.to_s, class: 'form-control' - .help-block You can setup jobs to only use runners with specific tags + .help-block You can setup jobs to only use Runners with specific tags .form-actions = f.submit 'Save changes', class: 'btn btn-save' diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 85225857758..6e58e5a0c78 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -15,7 +15,7 @@ .pull-right - if @project_runners.include?(runner) - if runner.belongs_to_one_project? - = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' + = link_to 'Remove Runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' - else - runner_project = @project.runner_projects.find_by(runner_id: runner) = link_to 'Disable for this project', namespace_project_runner_project_path(@project.namespace, @project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml index 9fa4127c948..752b9e060d5 100644 --- a/app/views/projects/runners/_shared_runners.html.haml +++ b/app/views/projects/runners/_shared_runners.html.haml @@ -1,24 +1,26 @@ -%h3 Shared runners +%h3 Shared Runners .bs-callout.bs-callout-warning.shared-runners-description - if shared_runners_text.present? = markdown(shared_runners_text, pipeline: 'plain_markdown') - else - Shared runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com). + GitLab Shared Runners execute code of different projects on the same Runner + unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is + on GitLab.com). %hr - if @project.shared_runners_enabled? = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do - Disable shared runners + Disable shared Runners - else = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-success', method: :post do - Enable shared runners + Enable shared Runners   for this project - if @shared_runners_count.zero? - This GitLab server does not provide any shared runners yet. - Please use specific runners or ask the administrator to create one. + This GitLab server does not provide any shared Runners yet. + Please use the specific Runners or ask your administrator to create one. - else - %h4.underlined-title Available shared runners - #{@shared_runners_count} + %h4.underlined-title Available shared Runners : #{@shared_runners_count} %ul.bordered-list.available-shared-runners = render partial: 'runner', collection: @shared_runners, as: :runner - if @shared_runners_count > 10 diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index d469dda5b81..858af78f7bf 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -1,20 +1,20 @@ -%h3 Specific runners +%h3 Specific Runners .bs-callout.help-callout - %h4 How to setup a new project specific runner + %h4 How to setup a specific Runner for a new project %ol %li - Install GitLab Runner software. - Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it + Install a Runner compatible with GitLab CI + (checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it). %li - Specify the following URL during runner setup: + Specify the following URL during the Runner setup: %code #{ci_root_url(only_path: false)} %li Use the following registration token during setup: %code #{@project.runners_token} %li - Start runner! + Start the Runner! - if @project_runners.any? diff --git a/app/views/projects/runners/index.html.haml b/app/views/projects/runners/index.html.haml index 2d5b9f43c24..92957470070 100644 --- a/app/views/projects/runners/index.html.haml +++ b/app/views/projects/runners/index.html.haml @@ -2,24 +2,24 @@ .light.prepend-top-default %p - A 'runner' is a process which runs a build. - You can setup as many runners as you need. + A 'Runner' is a process which runs a build. + You can setup as many Runners as you need. %br Runners can be placed on separate users, servers, and even on your local machine. - %p Each runner can be in one of the following states: + %p Each Runner can be in one of the following states: %div %ul %li %span.label.label-success active - \- runner is active and can process any new build + \- Runner is active and can process any new builds %li %span.label.label-danger paused - \- runner is paused and will not receive any new build + \- Runner is paused and will not receive any new builds %hr -%p.lead To start serving your builds you can either add specific runners to your project or use shared runners +%p.lead To start serving your builds you can either add specific Runners to your project or use shared Runners .row .col-sm-6 = render 'specific_runners' diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index a5ed3595b0a..0e1cc9a0f73 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -60,7 +60,7 @@ describe "Runners" do it "removes specific runner for project if this is last project for that runners" do within ".activated-specific-runners" do - click_on "Remove runner" + click_on "Remove Runner" end expect(Ci::Runner.exists?(id: @specific_runner)).to be_falsey @@ -75,7 +75,7 @@ describe "Runners" do end it "enables shared runners" do - click_on "Enable shared runners" + click_on "Enable shared Runners" expect(@project.reload.shared_runners_enabled).to be_truthy end end -- cgit v1.2.1 From 5838d1ba892872553dcd4e3945a3fab8faf339a4 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 28 Sep 2016 11:41:39 +0300 Subject: Fix a confusion in OAuth2 documentation --- doc/api/oauth2.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md index 0b0fc39ec7e..3ea481eadb5 100644 --- a/doc/api/oauth2.md +++ b/doc/api/oauth2.md @@ -1,10 +1,10 @@ -# GitLab as an OAuth2 client +# GitLab as an OAuth2 provider This document covers using the OAuth2 protocol to access GitLab. If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [Oauth2 provider documentation](../integration/oauth_provider.md). -OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password to a third-party. +OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password to a third-party. This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper) @@ -22,7 +22,7 @@ In the following sections you will be introduced to the three steps needed for t ### 1. Registering the client First, you should create an application (`/profile/applications`) in your user's account. -Each application gets a unique App ID and App Secret parameters. +Each application gets a unique App ID and App Secret parameters. >**Note:** **You should not share/leak your App ID or App Secret.** @@ -46,10 +46,10 @@ http://myapp.com/oauth/redirect?code=1234567890&state=your_unique_state_hash You should then use the `code` to request an access token. >**Important:** -It is highly recommended that you send a `state` value with the request to `/oauth/authorize` and -validate that value is returned and matches in the redirect request. -This is important to prevent [CSFR attacks](http://www.oauthsecurity.com/#user-content-authorization-code-flow), -`state` really should have been a requirement in the standard! +It is highly recommended that you send a `state` value with the request to `/oauth/authorize` and +validate that value is returned and matches in the redirect request. +This is important to prevent [CSFR attacks](http://www.oauthsecurity.com/#user-content-authorization-code-flow), +`state` really should have been a requirement in the standard! ### 3. Requesting the access token @@ -62,7 +62,7 @@ RestClient.post 'http://localhost:3000/oauth/token', parameters # The response will be { "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54", - "token_type": "bearer", + "token_type": "bearer", "expires_in": 7200, "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1" } @@ -95,7 +95,7 @@ curl --header "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/ --- -In this flow, a token is requested in exchange for the resource owner credentials (username and password). +In this flow, a token is requested in exchange for the resource owner credentials (username and password). The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the client is part of the device operating system or a highly privileged application), and when other authorization grant types are not available (such as an authorization code). @@ -112,7 +112,7 @@ You can do POST request to `/oauth/token` with parameters: { "grant_type" : "password", "username" : "user@example.com", - "password" : "sekret" + "password" : "secret" } ``` @@ -130,7 +130,7 @@ For testing you can use the oauth2 ruby gem: ``` client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com") -access_token = client.password.get_token('user@example.com', 'sekret') +access_token = client.password.get_token('user@example.com', 'secret') puts access_token.token ``` -- cgit v1.2.1 From 44d1560db4bdf678e9c98dfcddbef3180b2aaf8e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 28 Sep 2016 11:07:41 +0200 Subject: Clarify the Plan to Code stages in Cycle Analytics docs [ci skip] --- doc/user/project/cycle_analytics.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index abef80e7914..84231f879bf 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -28,9 +28,10 @@ You can see that there are seven stages in total: (first assignment, any milestone, milestone date or assignee is not required) - **Plan** (Board) - Median time from giving an issue a milestone or label until pushing the - first commit + first commit to the branch - **Code** (IDE) - - Median time from the first commit until the merge request is created + - Median time from the first commit to the branch until the merge request is + created - **Test** (CI) - Median total test time for all commits/merges - **Review** (Merge Request/MR) @@ -57,7 +58,7 @@ Below you can see in more detail what the various stages of Cycle Analytics mean | **Stage** | **Description** | | --------- | --------------- | | Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list][board] created for it. | -| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the repository. To make this change tracked, the pushed commit needs to contain the [issue closing pattern], for example `Closes #xxx`, where `xxx` is the number of the issue related to this commit. If the commit does not contain the issue closing pattern, it is not considered to the measurement time of the stage. | +| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit is the one that triggers the separation between **Plan** and **Code** and at least one commit needs to contain the [issue closing pattern] in order to know what branch and commit is related to the issue (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this commit). The closing pattern doesn't necessarily need to be in the first commit message. If there is no commit containing the issue closing pattern, it is not considered to the measurement time of the stage. | | Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request related to that commit. The key to keep the process tracked is include the [issue closing pattern] to the description of the merge request. | | Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. `master` is not excluded. It does not attempt to track time for any particular stages. | | Review | Measures the median time taken to review the merge request, between its creation and until it's merged. | -- cgit v1.2.1 From 263c7f0bccd4821804bb293df3f45f513c1163b6 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 22 Sep 2016 08:38:35 +0200 Subject: Use a ConnectionPool for Rails.cache on Sidekiq servers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s important to remember that connections on ConnectionPool are created when needed so if modify the objects later to create those connections weird things could happen https://gitlab.com/gitlab-com/infrastructure/issues/464#note_15850653 --- CHANGELOG | 1 + config/application.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index df93f89a41a..5cd5b12a1e9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.13.0 (unreleased) - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) + - Use a ConnectionPool for Rails.cache on Sidekiq servers - Only update issuable labels if they have been changed - Revoke button in Applications Settings underlines on hover. - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) diff --git a/config/application.rb b/config/application.rb index 4792f6670a8..8166b6003f6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -116,6 +116,10 @@ module Gitlab redis_config_hash = Gitlab::Redis.params redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever + if Sidekiq.server? # threaded context + redis_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5 + redis_config_hash[:pool_timeout] = 1 + end config.cache_store = :redis_store, redis_config_hash config.active_record.raise_in_transactional_callbacks = true -- cgit v1.2.1 From 222f723631fadb2624c6a0c5c2542e91602de9fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 28 Sep 2016 12:17:29 +0200 Subject: Update "Installation from source" guide for 8.13.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- doc/install/installation.md | 6 +- doc/update/8.12-to-8.13.md | 199 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 doc/update/8.12-to-8.13.md diff --git a/doc/install/installation.md b/doc/install/installation.md index 3ac813aa914..b3122280909 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -108,7 +108,7 @@ Then select 'Internet Site' and press enter to confirm the hostname. ## 2. Ruby -_**Note:** The current supported Ruby versions are 2.1.x and 2.3.x. 2.3.x is preferred, and support for 2.1.x will be dropped in the future. +**Note:** The current supported Ruby versions are 2.1.x and 2.3.x. 2.3.x is preferred, and support for 2.1.x will be dropped in the future. The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab in production, frequently leads to hard to diagnose problems. For example, @@ -268,9 +268,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-12-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-13-stable gitlab -**Note:** You can change `8-12-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `8-13-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md new file mode 100644 index 00000000000..75b9c57edd3 --- /dev/null +++ b/doc/update/8.12-to-8.13.md @@ -0,0 +1,199 @@ +# From 8.12 to 8.13 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +If you are you running Ruby 2.1.x, you do not _need_ to upgrade Ruby yet, but you should note that support for 2.1.x is deprecated and we will require 2.3.x in 8.13. It's strongly recommended that you upgrade as soon as possible. + +You can check which version you are running with `ruby -v`. + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz +echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz +cd ruby-2.3.1 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-13-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-13-stable-ee +``` + +### 5. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v3.6.2 +``` + +### 6. Update gitlab-workhorse + +Install and compile gitlab-workhorse. This requires +[Go 1.5](https://golang.org/dl) which should already be on your system from +GitLab 8.1. + +```bash +cd /home/git/gitlab-workhorse +sudo -u git -H git fetch --all +sudo -u git -H git checkout v0.8.2 +sudo -u git -H make +``` + +### 7. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 8. Update configuration files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +git diff origin/8-12-stable:config/gitlab.yml.example origin/8-13-stable:config/gitlab.yml.example +``` + +#### Git configuration + +Configure Git to generate packfile bitmaps (introduced in Git 2.0) on +the GitLab server during `git gc`. + +```sh +sudo -u git -H git config --global repack.writeBitmaps true +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +# For HTTPS configurations +git diff origin/8-12-stable:lib/support/nginx/gitlab-ssl origin/8-13-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-12-stable:lib/support/nginx/gitlab origin/8-13-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-13-stable/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +### 9. Start application + + sudo service gitlab start + sudo service nginx restart + +### 10. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (8.12) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 8.11 to 8.12](8.11-to-8.12.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. -- cgit v1.2.1 From 93d849beaebb00dc4dcb6cb5ffa4721883e0da51 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 22 Sep 2016 17:31:18 -0300 Subject: Expose project share expiration_date field on API --- CHANGELOG | 2 ++ doc/api/projects.md | 1 + lib/api/entities.rb | 2 +- lib/api/projects.rb | 11 ++++++----- spec/requests/api/projects_spec.rb | 9 ++++++--- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 70eee44b679..b539e20f5d9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ Please view this file on the master branch, on stable branches it's out of date. +v 8.13.0 (unreleased) + - Expose expires_at field when sharing project on API v 8.12.0 (unreleased) - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 diff --git a/doc/api/projects.md b/doc/api/projects.md index 750ce1508df..869907b0dd7 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -899,6 +899,7 @@ Parameters: - `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked - `group_id` (required) - The ID of a group - `group_access` (required) - Level of permissions for sharing +- `expires_at` - Share expiration date in ISO 8601 format: 2016-09-26 ## Hooks diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 92a6f29adb0..29ad9f3565a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -343,7 +343,7 @@ module API end class ProjectGroupLink < Grape::Entity - expose :id, :project_id, :group_id, :group_access + expose :id, :project_id, :group_id, :group_access, :expires_at end class Todo < Grape::Entity diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 6d99617b56f..680055c95eb 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -393,23 +393,24 @@ module API # Share project with group # # Parameters: - # id (required) - The ID of a project - # group_id (required) - The ID of a group + # id (required) - The ID of a project + # group_id (required) - The ID of a group # group_access (required) - Level of permissions for sharing + # expires_at (optional) - Share expiration date # # Example Request: # POST /projects/:id/share post ":id/share" do authorize! :admin_project, user_project required_attributes! [:group_id, :group_access] + attrs = attributes_for_keys [:group_id, :group_access, :expires_at] unless user_project.allowed_to_share_with_group? return render_api_error!("The project sharing with group is disabled", 400) end - link = user_project.project_group_links.new - link.group_id = params[:group_id] - link.group_access = params[:group_access] + link = user_project.project_group_links.new(attrs) + if link.save present link, with: Entities::ProjectGroupLink else diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 192c7d14c13..4a0d727faea 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -761,13 +761,16 @@ describe API::API, api: true do let(:group) { create(:group) } it "shares project with group" do + expires_at = 10.days.from_now.to_date + expect do - post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER + post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at end.to change { ProjectGroupLink.count }.by(1) expect(response.status).to eq 201 - expect(json_response['group_id']).to eq group.id - expect(json_response['group_access']).to eq Gitlab::Access::DEVELOPER + expect(json_response['group_id']).to eq(group.id) + expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER) + expect(json_response['expires_at']).to eq(expires_at.to_s) end it "returns a 400 error when group id is not given" do -- cgit v1.2.1 From ec50d20d7ca90acb93ff7bf3dd45478a5b467682 Mon Sep 17 00:00:00 2001 From: john McGehee Date: Wed, 28 Sep 2016 15:45:32 -0700 Subject: Rebase and resolve conflicts in backup doc for !3761 --- doc/raketasks/backup_restore.md | 57 ++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 3f4056dc440..faa7e9eaa44 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -2,34 +2,45 @@ ![backup banner](backup_hrz.png) -## Create a backup of the GitLab system - -A backup creates an archive file that contains the database, all repositories and all attachments. -This archive will be saved in backup_path (see `config/gitlab.yml`). -The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup. -You can only restore a backup to exactly the same version of GitLab that you created it -on, for example 7.2.1. The best way to migrate your repositories from one server to +An application data backup creates an archive file that contains the database, +all repositories and all attachments. +This archive will be saved in `backup_path`, which is specified in the +`config/gitlab.yml` file. +The filename will be `[TIMESTAMP]_gitlab_backup.tar`, where `TIMESTAMP` +identifies the time at which each backup was created. + +You can only restore a backup to exactly the same version of GitLab on which it +was created. The best way to migrate your repositories from one server to another is through backup restore. -You need to keep separate copies of `/etc/gitlab/gitlab-secrets.json` and -`/etc/gitlab/gitlab.rb` (for omnibus packages) or -`/home/git/gitlab/config/secrets.yml` (for installations from source). This file -contains the database encryption keys used for two-factor authentication and CI -secret variables, among other things. If you restore a GitLab backup without -restoring the database encryption key, users who have two-factor authentication -enabled will lose access to your GitLab server. +To restore a backup, you will also need to restore `/etc/gitlab/gitlab-secrets.json` +(for omnibus packages) or `/home/git/gitlab/.secret` (for installations +from source). This file contains the database encryption key used +for two-factor authentication. If you fail to restore this encryption key file +along with the application data backup, users with two-factor +authentication enabled will lose access to your GitLab server. + +## Create a backup of the GitLab system +Use this command if you've installed GitLab with the Omnibus package: ``` -# use this command if you've installed GitLab with the Omnibus package sudo gitlab-rake gitlab:backup:create - -# if you've installed GitLab from source +``` +Use this if you've installed GitLab from source: +``` sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` -Also you can choose what should be backed up by adding environment variable SKIP. Available options: db, -uploads (attachments), repositories, builds(CI build output logs), artifacts (CI build artifacts), lfs (LFS objects). -Use a comma to specify several options at the same time. +You can specify that portions of the application data be skipped using the +environment variable `SKIP`. You can skip: +- `db` +- `uploads` (attachments) +- `repositories` +- `builds` (CI build output logs) +- `artifacts` (CI build artifacts) +- `lfs` (LFS objects) + +Separate multiple data types to skip using a comma. For example: ``` sudo gitlab-rake gitlab:backup:create SKIP=db,uploads @@ -69,7 +80,7 @@ Deleting old backups... [SKIPPING] Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates. It uses the [Fog library](http://fog.io/) to perform the upload. In the example below we use Amazon S3 for storage. -But Fog also lets you use [other storage providers](http://fog.io/storage/). +Fog also supports [other storage providers](http://fog.io/storage/). For omnibus packages: @@ -161,7 +172,7 @@ with the name of your bucket: ### Uploading to locally mounted shares You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by -using the [`Local`](https://github.com/fog/fog-local#usage) storage provider. +using the Fog [`Local`](https://github.com/fog/fog-local#usage) storage provider. The directory pointed to by the `local_root` key **must** be owned by the `git` user **when mounted** (mounting with the `uid=` of the `git` user for `CIFS` and `SMB`) or the user that you are executing the backup tasks under (for omnibus @@ -228,7 +239,7 @@ of using encryption in the first place! If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration). If you have a cookbook installation there should be a copy of your configuration in Chef. -If you have an installation from source, please consider backing up your `config/secrets.yml` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079). +If you installed from source, please consider backing up your `config/secrets.yml` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079). At the very **minimum** you should backup `/etc/gitlab/gitlab.rb` and `/etc/gitlab/gitlab-secrets.json` (Omnibus), or -- cgit v1.2.1 From e3f12e975d59b0606b8f1365e21814b34ea83767 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 28 Sep 2016 19:38:43 -0600 Subject: Remove Flog as we use a Rubocop that does its job. --- .gitlab-ci.yml | 3 --- Gemfile | 1 - Gemfile.lock | 4 ---- lib/tasks/flog.rake | 25 ------------------------- 4 files changed, 33 deletions(-) delete mode 100644 lib/tasks/flog.rake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3f3873e57c1..a11c4705e82 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -209,9 +209,6 @@ rubocop: *exec rake haml_lint: *exec rake scss_lint: *exec rake brakeman: *exec -rake flog: - <<: *exec - allow_failure: yes rake flay: <<: *exec allow_failure: yes diff --git a/Gemfile b/Gemfile index f80d745fff8..d6b358f3b43 100644 --- a/Gemfile +++ b/Gemfile @@ -300,7 +300,6 @@ group :development, :test do gem 'scss_lint', '~> 0.47.0', require: false gem 'haml_lint', '~> 0.18.2', require: false gem 'simplecov', '0.12.0', require: false - gem 'flog', '~> 4.3.2', require: false gem 'flay', '~> 2.6.1', require: false gem 'bundler-audit', '~> 0.5.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 911ffcd7d9c..2b8f28f3ebe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -209,9 +209,6 @@ GEM flay (2.6.1) ruby_parser (~> 3.0) sexp_processor (~> 4.0) - flog (4.3.2) - ruby_parser (~> 3.1, > 3.1.0) - sexp_processor (~> 4.4) flowdock (0.7.1) httparty (~> 0.7) multi_json @@ -845,7 +842,6 @@ DEPENDENCIES factory_girl_rails (~> 4.6.0) ffaker (~> 2.0.0) flay (~> 2.6.1) - flog (~> 4.3.2) fog-aws (~> 0.9) fog-azure (~> 0.0) fog-core (~> 1.40) diff --git a/lib/tasks/flog.rake b/lib/tasks/flog.rake deleted file mode 100644 index 3bfe999ae74..00000000000 --- a/lib/tasks/flog.rake +++ /dev/null @@ -1,25 +0,0 @@ -desc 'Code complexity analyze via flog' -task :flog do - output = %x(bundle exec flog -m app/ lib/gitlab) - exit_code = 0 - minimum_score = 70 - output = output.lines - - # Skip total complexity score - output.shift - - # Skip some trash info - output.shift - - output.each do |line| - score, method = line.split(" ") - score = score.to_i - - if score > minimum_score - exit_code = 1 - puts "High complexity in #{method}. Score: #{score}" - end - end - - exit exit_code -end -- cgit v1.2.1 From e80e4cb8b97a3865b7e3c551a856a53d98cccf75 Mon Sep 17 00:00:00 2001 From: Guilherme Salazar Date: Fri, 23 Sep 2016 20:43:57 -0300 Subject: expose pipeline data in builds API add pipeline ref, sha, and status to the build API response add tests of build API (pipeline data) change API documentation for builds API log change to builds API in CHANGELOG CHANGELOG: add reference to pull request and contributor's name --- CHANGELOG | 1 + doc/api/builds.md | 30 ++++++++++++++++++++++++++++++ lib/api/entities.rb | 9 +++++++-- spec/requests/api/builds_spec.rb | 27 +++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index df93f89a41a..6e1eae5870b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.13.0 (unreleased) - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Add organization field to user profile - Optimize GitHub importing for speed and memory + - API: expose pipeline data in builds API (!6502, Guilherme Salazar) v 8.12.2 (unreleased) - Fix Import/Export not recognising correctly the imported services. diff --git a/doc/api/builds.md b/doc/api/builds.md index dce666445d0..e8a9e4743d3 100644 --- a/doc/api/builds.md +++ b/doc/api/builds.md @@ -40,6 +40,12 @@ Example of response "finished_at": "2015-12-24T17:54:27.895Z", "id": 7, "name": "teaspoon", + "pipeline": { + "id": 6, + "ref": "master", + "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "status": "pending" + } "ref": "master", "runner": null, "stage": "test", @@ -78,6 +84,12 @@ Example of response "finished_at": "2015-12-24T17:54:24.921Z", "id": 6, "name": "spinach:other", + "pipeline": { + "id": 6, + "ref": "master", + "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "status": "pending" + } "ref": "master", "runner": null, "stage": "test", @@ -146,6 +158,12 @@ Example of response "finished_at": "2016-01-11T10:14:09.526Z", "id": 69, "name": "rubocop", + "pipeline": { + "id": 6, + "ref": "master", + "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "status": "pending" + } "ref": "master", "runner": null, "stage": "test", @@ -170,6 +188,12 @@ Example of response "finished_at": "2015-12-24T17:54:33.913Z", "id": 9, "name": "brakeman", + "pipeline": { + "id": 6, + "ref": "master", + "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "status": "pending" + } "ref": "master", "runner": null, "stage": "test", @@ -231,6 +255,12 @@ Example of response "finished_at": "2015-12-24T17:54:31.198Z", "id": 8, "name": "rubocop", + "pipeline": { + "id": 6, + "ref": "master", + "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd", + "status": "pending" + } "ref": "master", "runner": null, "stage": "test", diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 0adc118ba27..409a4c7cc07 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -545,6 +545,10 @@ module API expose :filename, :size end + class PipelineBasic < Grape::Entity + expose :id, :sha, :ref, :status + end + class Build < Grape::Entity expose :id, :status, :stage, :name, :ref, :tag, :coverage expose :created_at, :started_at, :finished_at @@ -552,6 +556,7 @@ module API expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? } expose :commit, with: RepoCommit expose :runner, with: Runner + expose :pipeline, with: PipelineBasic end class Trigger < Grape::Entity @@ -562,8 +567,8 @@ module API expose :key, :value end - class Pipeline < Grape::Entity - expose :id, :status, :ref, :sha, :before_sha, :tag, :yaml_errors + class Pipeline < PipelineBasic + expose :before_sha, :tag, :yaml_errors expose :user, with: Entities::UserBasic expose :created_at, :updated_at, :started_at, :finished_at, :committed_at diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index ee0b61e2ca4..95c7bbf99c9 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -30,6 +30,15 @@ describe API::API, api: true do expect(json_response.first['commit']['id']).to eq project.commit.id end + it 'returns pipeline data' do + json_build = json_response.first + expect(json_build['pipeline']).not_to be_empty + expect(json_build['pipeline']['id']).to eq build.pipeline.id + expect(json_build['pipeline']['ref']).to eq build.pipeline.ref + expect(json_build['pipeline']['sha']).to eq build.pipeline.sha + expect(json_build['pipeline']['status']).to eq build.pipeline.status + end + context 'filter project with one scope element' do let(:query) { 'scope=pending' } @@ -91,6 +100,15 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(json_response.size).to eq 2 end + + it 'returns pipeline data' do + json_build = json_response.first + expect(json_build['pipeline']).not_to be_empty + expect(json_build['pipeline']['id']).to eq build.pipeline.id + expect(json_build['pipeline']['ref']).to eq build.pipeline.ref + expect(json_build['pipeline']['sha']).to eq build.pipeline.sha + expect(json_build['pipeline']['status']).to eq build.pipeline.status + end end context 'when pipeline has no builds' do @@ -133,6 +151,15 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response['name']).to eq('test') end + + it 'returns pipeline data' do + json_build = json_response + expect(json_build['pipeline']).not_to be_empty + expect(json_build['pipeline']['id']).to eq build.pipeline.id + expect(json_build['pipeline']['ref']).to eq build.pipeline.ref + expect(json_build['pipeline']['sha']).to eq build.pipeline.sha + expect(json_build['pipeline']['status']).to eq build.pipeline.status + end end context 'unauthorized user' do -- cgit v1.2.1 From 6f1d243b998bfba6beedd0f7f6ff6ddb976a18c8 Mon Sep 17 00:00:00 2001 From: Alan Moore Date: Thu, 29 Sep 2016 05:07:17 +0000 Subject: Fixed Session Cookie header --- doc/api/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/README.md b/doc/api/README.md index 8e4f7f12b4b..bbd5bcfb386 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -101,7 +101,7 @@ Once you have your token, pass it to the API using either the `private_token` parameter or the `PRIVATE-TOKEN` header. -### Session cookie +### Session Cookie When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is set. The API will use this cookie for authentication if it is present, but using -- cgit v1.2.1 From 7cc831eeeb7de4b91a344c1304a177171c412237 Mon Sep 17 00:00:00 2001 From: Gal Schlezinger Date: Fri, 23 Sep 2016 21:47:25 +0000 Subject: Fixes #22499 --- CHANGELOG | 1 + app/views/projects/builds/_sidebar.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b806e5e7f80..66d56816ef6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Use gitlab-shell v3.6.2 (GIT TRACE logging) - AbstractReferenceFilter caches project_refs on RequestStore when active + - Replaced the check sign to arrow in the show build view. !6501 - Speed-up group milestones show page - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 0aa3092baa2..c88d5c7e2f4 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -128,7 +128,7 @@ - builds.select{|build| build.status == build_status}.each do |build| .build-job{class: ('active' if build == @build), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do - = icon('check') + = icon('right-arrow') = ci_icon_for_status(build.status) %span - if build.name -- cgit v1.2.1 From d75a8297b82117389bee4d075b1b17747570368d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 29 Sep 2016 10:36:38 +0200 Subject: Add '.well-known' to the list of reserved namespaces See https://gitlab.com/gitlab-org/gitlab-ce/issues/22759 --- app/validators/namespace_validator.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb index 7a35958cc5f..4dc3b2ab9a0 100644 --- a/app/validators/namespace_validator.rb +++ b/app/validators/namespace_validator.rb @@ -5,7 +5,8 @@ # Values are checked for formatting and exclusion from a list of reserved path # names. class NamespaceValidator < ActiveModel::EachValidator - RESERVED = %w( + RESERVED = %w[ + .well-known admin all assets @@ -31,7 +32,7 @@ class NamespaceValidator < ActiveModel::EachValidator u unsubscribes users - ).freeze + ].freeze def validate_each(record, attribute, value) unless value =~ Gitlab::Regex.namespace_regex -- cgit v1.2.1 From eb4d3eef90770aa14c804f9d186afc0bcc55c0a0 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 29 Sep 2016 10:48:24 +0200 Subject: Remove duplicate VersionInfo class This was brought over during the CI merge and already exists at `lib/gitlab/version_info.rb`. --- lib/ci/version_info.rb | 52 -------------------------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 lib/ci/version_info.rb diff --git a/lib/ci/version_info.rb b/lib/ci/version_info.rb deleted file mode 100644 index 2a87c91db5e..00000000000 --- a/lib/ci/version_info.rb +++ /dev/null @@ -1,52 +0,0 @@ -class VersionInfo - include Comparable - - attr_reader :major, :minor, :patch - - def self.parse(str) - if str && m = str.match(/(\d+)\.(\d+)\.(\d+)/) - VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i) - else - VersionInfo.new - end - end - - def initialize(major = 0, minor = 0, patch = 0) - @major = major - @minor = minor - @patch = patch - end - - def <=>(other) - return unless other.is_a? VersionInfo - return unless valid? && other.valid? - - if other.major < @major - 1 - elsif @major < other.major - -1 - elsif other.minor < @minor - 1 - elsif @minor < other.minor - -1 - elsif other.patch < @patch - 1 - elsif @patch < other.patch - -1 - else - 0 - end - end - - def to_s - if valid? - "%d.%d.%d" % [@major, @minor, @patch] - else - "Unknown" - end - end - - def valid? - @major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0 - end -end -- cgit v1.2.1 From 666762f2ded91d10d5dece3c47bf208c792d5d28 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 28 Sep 2016 10:22:08 -0500 Subject: fix typo "this files" -> "this file" --- app/views/projects/diffs/_file.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 1a51ccd4c7d..d07de45fdde 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -5,7 +5,7 @@ - unless diff_file.submodule? .file-actions.hidden-xs - if blob_text_viewable?(blob) - = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this files", disabled: @diff_notes_disabled do + = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = icon('comment') \ -- cgit v1.2.1 From 50e2bd22954a92ef2f3f7c7c899ad8e700d84d17 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 28 Sep 2016 10:48:26 -0500 Subject: make sure split-view diff discussions are made visible by "toggle comments" button --- CHANGELOG | 1 + app/assets/javascripts/application.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index da436431c7f..5d7e6113075 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.13.0 (unreleased) - Revoke button in Applications Settings underlines on hover. - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Add organization field to user profile + - Fix resolved discussion display in side-by-side diff view !6575 - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c029bf3b5ca..8a61669822c 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -247,7 +247,7 @@ $this.toggleClass('active'); var notesHolders = $this.closest('.diff-file').find('.notes_holder'); if ($this.hasClass('active')) { - notesHolders.show(); + notesHolders.show().find('.hide').show(); } else { notesHolders.hide(); } -- cgit v1.2.1 From 6e6f34bffb641ae698177055b8f3528ec41fb7c8 Mon Sep 17 00:00:00 2001 From: Dimitris Karakasilis Date: Mon, 26 Sep 2016 18:34:56 +0300 Subject: Notify current_user about automatic merge after successful build Fixes: https://gitlab.com/gitlab-org/gitlab-ce/issues/14409 --- CHANGELOG | 1 + app/services/notification_service.rb | 14 +++++++++----- spec/services/notification_service_spec.rb | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index da436431c7f..f9ac51beb2f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.13.0 (unreleased) - Add organization field to user profile - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) + - Notify the Merger about merge after successful build (Dimitris Karakasilis) v 8.12.2 (unreleased) - Added University content to doc/university diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 6139ed56e25..2cc0c31d77d 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -134,7 +134,8 @@ class NotificationService merge_request, merge_request.target_project, current_user, - :merged_merge_request_email + :merged_merge_request_email, + skip_current_user: !merge_request.merge_when_build_succeeds? ) end @@ -514,9 +515,11 @@ class NotificationService end end - def close_resource_email(target, project, current_user, method) + def close_resource_email(target, project, current_user, method, skip_current_user: true) action = method == :merged_merge_request_email ? "merge" : "close" - recipients = build_recipients(target, project, current_user, action: action) + + recipients = build_recipients(target, project, current_user, action: action, + skip_current_user: skip_current_user) recipients.each do |recipient| mailer.send(method, recipient.id, target.id, current_user.id).deliver_later @@ -557,7 +560,7 @@ class NotificationService end end - def build_recipients(target, project, current_user, action: nil, previous_assignee: nil) + def build_recipients(target, project, current_user, action: nil, previous_assignee: nil, skip_current_user: true) custom_action = build_custom_key(action, target) recipients = target.participants(current_user) @@ -586,7 +589,8 @@ class NotificationService recipients = reject_unsubscribed_users(recipients, target) recipients = reject_users_without_access(recipients, target) - recipients.delete(current_user) + recipients.delete(current_user) if skip_current_user + recipients.uniq end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 0d152534c38..d820646ebdf 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -962,6 +962,20 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end + it "notifies the merger when merge_when_build_succeeds is true" do + merge_request.merge_when_build_succeeds = true + notification.merge_mr(merge_request, @u_watcher) + + should_email(@u_watcher) + end + + it "does not notify the merger when merge_when_build_succeeds is false" do + merge_request.merge_when_build_succeeds = false + notification.merge_mr(merge_request, @u_watcher) + + should_not_email(@u_watcher) + end + context 'participating' do context 'by assignee' do before do -- cgit v1.2.1 From fa0bf1350eccf5f8d182a79d9655c6e3eb085781 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 28 Sep 2016 12:07:30 -0500 Subject: remove instances of due to inconsistent browser support --- app/views/admin/broadcast_messages/_form.html.haml | 4 ++-- app/views/admin/labels/_form.html.haml | 2 +- app/views/projects/labels/_form.html.haml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index 6b157abf842..f952d2e9aa1 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -18,11 +18,11 @@ .form-group.js-toggle-colors-container.hide = f.label :color, "Background Color", class: 'control-label' .col-sm-10 - = f.color_field :color, class: "form-control" + = f.text_field :color, class: "form-control" .form-group.js-toggle-colors-container.hide = f.label :font, "Font Color", class: 'control-label' .col-sm-10 - = f.color_field :font, class: "form-control" + = f.text_field :font, class: "form-control" .form-group = f.label :starts_at, class: 'control-label' .col-sm-10.datetime-controls diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml index 602cfa9b6fc..d5e6bede36a 100644 --- a/app/views/admin/labels/_form.html.haml +++ b/app/views/admin/labels/_form.html.haml @@ -14,7 +14,7 @@ .col-sm-10 .input-group .input-group-addon.label-color-preview   - = f.color_field :color, class: "form-control" + = f.text_field :color, class: "form-control" .help-block Choose any color. %br diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index aa143e54ffe..6ab6ae50389 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -14,7 +14,7 @@ .col-sm-10 .input-group .input-group-addon.label-color-preview   - = f.color_field :color, class: "form-control" + = f.text_field :color, class: "form-control" .help-block Choose any color. %br -- cgit v1.2.1 From f73eb01974c559d2cd19f7d4f4bdc4d869009aa0 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Thu, 29 Sep 2016 14:14:16 +0000 Subject: Update CHANGELOG --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e1b9cbf0b1d..11464db5980 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,4 @@ Please view this file on the master branch, on stable branches it's out of date. -v 8.13.0 (unreleased) - - Expose expires_at field when sharing project on API v 8.13.0 (unreleased) - Use gitlab-shell v3.6.2 (GIT TRACE logging) @@ -10,6 +8,7 @@ v 8.13.0 (unreleased) - Add more tests for calendar contribution (ClemMakesApps) - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Fix permission for setting an issue's due date + - Expose expires_at field when sharing project on API - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Only update issuable labels if they have been changed -- cgit v1.2.1 From cf5a55429df3405b606584b39e4fd97342a37e14 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 28 Sep 2016 15:50:02 +0100 Subject: Fixes long commit messages overflow viewport in file tree --- CHANGELOG | 1 + app/assets/stylesheets/pages/tree.scss | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 11464db5980..b70d81cdd55 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.13.0 (unreleased) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Only update issuable labels if they have been changed - Revoke button in Applications Settings underlines on hover. + - Fix Long commit messages overflow viewport in file tree - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Add organization field to user profile - Fix resolved discussion display in side-by-side diff view !6575 diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 7b6577c513e..41ad10f07bd 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -27,7 +27,12 @@ } .last-commit { - @include str-truncated(60%); + @include str-truncated(506px); + + @media (min-width: $screen-sm-max) and (max-width: $screen-md-max) { + @include str-truncated(450px); + } + } .commit-history-link-spacer { -- cgit v1.2.1 From 29141ed3ea6157a60d9748921782015626a17f9e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 23 Sep 2016 09:42:07 +0200 Subject: fix broken repo 500 errors in UI and added relevant specs --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 ++-- app/controllers/projects_controller.rb | 7 ++++++- app/services/projects/import_service.rb | 5 +++++ spec/controllers/projects_controller_spec.rb | 22 ++++++++++++++++++++++ spec/factories/projects.rb | 15 +++++++++++++++ spec/services/projects/import_service_spec.rb | 10 ++++++++++ 8 files changed, 62 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b70d81cdd55..0892bd820c3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ v 8.13.0 (unreleased) - Fix resolved discussion display in side-by-side diff view !6575 - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) + - Fix broken repository 500 errors in project list v 8.12.2 (unreleased) - Added University content to doc/university diff --git a/Gemfile b/Gemfile index d2677d69556..76ca6427feb 100644 --- a/Gemfile +++ b/Gemfile @@ -51,7 +51,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.6.6' +gem 'gitlab_git', '~> 10.6.7' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 6f6667b51e9..f15715a20ff 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -276,7 +276,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.6.6) + gitlab_git (10.6.7) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -857,7 +857,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.6.6) + gitlab_git (~> 10.6.7) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) gollum-rugged_adapter (~> 0.4.2) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index eaa38fa6c98..c99aeadb5e4 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -324,7 +324,12 @@ class ProjectsController < Projects::ApplicationController end def repo_exists? - project.repository_exists? && !project.empty_repo? + project.repository_exists? && !project.empty_repo? && project.repo + + rescue Gitlab::Git::Repository::NoRepository + project.repository.expire_exists_cache + + false end def project_view_files? diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index cdad0426b02..e466ffa60eb 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -44,6 +44,11 @@ module Projects begin gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url) rescue => e + # Expire cache to prevent scenarios such as: + # 1. First import failed, but the repo was imported successfully, so +exists?+ returns true + # 2. Retried import, repo is broken or not imported but +exists?+ still returns true + project.repository.before_import if project.repository_exists? + raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index b0f740f48f7..da0fdce39db 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -63,6 +63,28 @@ describe ProjectsController do end end + context "project with broken repo" do + let(:empty_project) { create(:project_broken_repo, :public) } + + before { sign_in(user) } + + User.project_views.keys.each do |project_view| + context "with #{project_view} view set" do + before do + user.update_attributes(project_view: project_view) + + get :show, namespace_id: empty_project.namespace.path, id: empty_project.path + end + + it "renders the empty project view" do + allow(Project).to receive(:repo).and_raise(Gitlab::Git::Repository::NoRepository) + + expect(response).to render_template('projects/no_repo') + end + end + end + end + context "rendering default project view" do render_views diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index e61b1fd9647..873d3fcb5af 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -27,6 +27,14 @@ FactoryGirl.define do end end + trait :broken_repo do + after(:create) do |project| + project.create_repository + + FileUtils.rm_r(File.join(project.repository_storage_path, "#{project.path_with_namespace}.git", 'refs')) + end + end + # Nest Project Feature attributes transient do wiki_access_level ProjectFeature::ENABLED @@ -56,6 +64,13 @@ FactoryGirl.define do empty_repo end + # Project with broken repository + # + # Project with an invalid repository state + factory :project_broken_repo, parent: :empty_project do + broken_repo + end + # Project with test repository # # Test repository source can be found at diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index d5d4d7c56ef..ed1384798ab 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -108,6 +108,16 @@ describe Projects::ImportService, services: true do expect(result[:status]).to eq :error expect(result[:message]).to eq 'Github: failed to connect API' end + + it 'expires existence cache after error' do + allow_any_instance_of(Project).to receive(:repository_exists?).and_return(true) + + expect_any_instance_of(Gitlab::Shell).to receive(:import_repository).with(project.repository_storage_path, project.path_with_namespace, project.import_url).and_raise(Gitlab::Shell::Error.new('Failed to import the repository')) + expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).and_call_original + expect_any_instance_of(Repository).to receive(:expire_exists_cache).and_call_original + + subject.execute + end end def stub_github_omniauth_provider -- cgit v1.2.1 From 26678d8ea3f05b5508c0ebc80cb2bc40e0a66556 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 29 Sep 2016 08:18:15 -0500 Subject: Fix race condition that can be triggered if the token expires right after we retrieve it, but before we can set the new expiry time. --- lib/gitlab/lfs_token.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index 7b3bbcf6a32..5f67e97fa2a 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -20,13 +20,8 @@ module Gitlab def token Gitlab::Redis.with do |redis| token = redis.get(redis_key) - - if token - redis.expire(redis_key, EXPIRY_TIME) - else - token = Devise.friendly_token(TOKEN_LENGTH) - redis.set(redis_key, token, ex: EXPIRY_TIME) - end + token ||= Devise.friendly_token(TOKEN_LENGTH) + redis.set(redis_key, token, ex: EXPIRY_TIME) token end -- cgit v1.2.1 From 59157c0423d34c1f20c446548df540d103bda939 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Tue, 27 Sep 2016 17:56:02 -0700 Subject: Expose the Koding application settings in the API This will allow the Koding app to enable the integration itself once is has authorized an admin user using the application secrets. --- CHANGELOG | 1 + doc/api/settings.md | 12 +++++++++--- lib/api/entities.rb | 2 ++ spec/requests/api/settings_spec.rb | 36 ++++++++++++++++++++++++++---------- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b70d81cdd55..137257add3b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.13.0 (unreleased) - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Fix permission for setting an issue's due date - Expose expires_at field when sharing project on API + - Allow the Koding integration to be configured through the API - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Only update issuable labels if they have been changed diff --git a/doc/api/settings.md b/doc/api/settings.md index aaa2c99642b..f7ad3b4cc8e 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -41,7 +41,9 @@ Example response: "gravatar_enabled" : true, "sign_in_text" : null, "container_registry_token_expire_delay": 5, - "repository_storage": "default" + "repository_storage": "default", + "koding_enabled": false, + "koding_url": null } ``` @@ -72,7 +74,9 @@ PUT /application/settings | `after_sign_out_path` | string | no | Where to redirect users after logout | | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes | | `repository_storage` | string | no | Storage path for new projects. The value should be the name of one of the repository storage paths defined in your gitlab.yml | -| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. +| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. | +| `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. | +| `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1 @@ -103,6 +107,8 @@ Example response: "user_oauth_applications": true, "after_sign_out_path": "", "container_registry_token_expire_delay": 5, - "repository_storage": "default" + "repository_storage": "default", + "koding_enabled": false, + "koding_url": null } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index c5dc8b22f60..04437322ec1 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -494,6 +494,8 @@ module API expose :after_sign_out_path expose :container_registry_token_expire_delay expose :repository_storage + expose :koding_enabled + expose :koding_url end class Release < Grape::Entity diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 54d096e8b7f..f4903d8e0be 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -14,22 +14,38 @@ describe API::API, 'Settings', api: true do expect(json_response['default_projects_limit']).to eq(42) expect(json_response['signin_enabled']).to be_truthy expect(json_response['repository_storage']).to eq('default') + expect(json_response['koding_enabled']).to be_falsey + expect(json_response['koding_url']).to be_nil end end describe "PUT /application/settings" do - before do - storages = { 'custom' => 'tmp/tests/custom_repositories' } - allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + context "custom repository storage type set in the config" do + before do + storages = { 'custom' => 'tmp/tests/custom_repositories' } + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + end + + it "updates application settings" do + put api("/application/settings", admin), + default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com' + expect(response).to have_http_status(200) + expect(json_response['default_projects_limit']).to eq(3) + expect(json_response['signin_enabled']).to be_falsey + expect(json_response['repository_storage']).to eq('custom') + expect(json_response['koding_enabled']).to be_truthy + expect(json_response['koding_url']).to eq('http://koding.example.com') + end end - it "updates application settings" do - put api("/application/settings", admin), - default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom' - expect(response).to have_http_status(200) - expect(json_response['default_projects_limit']).to eq(3) - expect(json_response['signin_enabled']).to be_falsey - expect(json_response['repository_storage']).to eq('custom') + context "missing koding_url value when koding_enabled is true" do + it "returns a blank parameter error message" do + put api("/application/settings", admin), koding_enabled: true + + expect(response).to have_http_status(400) + expect(json_response['message']).to have_key('koding_url') + expect(json_response['message']['koding_url']).to include "can't be blank" + end end end end -- cgit v1.2.1 From 0a42c6a2c965defe8a67dee2b8fbe1006b9988ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 29 Sep 2016 19:01:28 +0200 Subject: Add CHANGELOG entries for 8.11.8, 8.10.11, and 8.9.11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- CHANGELOG | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0892bd820c3..b4211706347 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -223,6 +223,12 @@ v 8.12.0 - Fix non-master branch readme display in tree view - Add UX improvements for merge request version diffs +v 8.11.8 + - Respect the fork_project permission when forking projects + - Set a restrictive CORS policy on the API for credentialed requests + - API: disable rails session auth for non-GET/HEAD requests + - Escape HTML nodes in builds commands in CI linter + v 8.11.7 - Avoid conflict with admin labels when importing GitHub labels. !6158 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 @@ -442,6 +448,12 @@ v 8.11.0 - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done +v 8.10.11 + - Respect the fork_project permission when forking projects + - Set a restrictive CORS policy on the API for credentialed requests + - API: disable rails session auth for non-GET/HEAD requests + - Escape HTML nodes in builds commands in CI linter + v 8.10.10 - Allow the Rails cookie to be used for API authentication. @@ -678,6 +690,12 @@ v 8.10.0 - Show tooltip on GitLab export link in new project page - Fix import_data wrongly saved as a result of an invalid import_url !5206 +v 8.9.11 + - Respect the fork_project permission when forking projects + - Set a restrictive CORS policy on the API for credentialed requests + - API: disable rails session auth for non-GET/HEAD requests + - Escape HTML nodes in builds commands in CI linter + v 8.9.10 - Allow the Rails cookie to be used for API authentication. -- cgit v1.2.1 From 7b6ffae249569b5323a6ae7d618776f1362d6bdd Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 29 Sep 2016 13:28:09 -0500 Subject: right-align dropdown menus to prevent horizontal page overflow --- app/views/explore/projects/_filter.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index cd485da5104..132bbe26fe0 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -8,7 +8,7 @@ - else Any %b.caret - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(visibility_level: nil) do Any @@ -28,7 +28,7 @@ - else Any %b.caret - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(tag: nil) do Any -- cgit v1.2.1 From 6d49fcf439c88492ff599432818d1c302e509997 Mon Sep 17 00:00:00 2001 From: Tony Gambone Date: Thu, 22 Sep 2016 17:30:37 -0400 Subject: Close todos when accepting a MR via the API. --- CHANGELOG | 1 + .../projects/merge_requests_controller.rb | 2 -- app/services/merge_requests/post_merge_service.rb | 1 + spec/services/merge_requests/merge_service_spec.rb | 24 ++++++++++++++++++++++ .../merge_requests/refresh_service_spec.rb | 12 +++++------ 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b4211706347..9bd7b28bf72 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.13.0 (unreleased) - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Fix broken repository 500 errors in project list + - Close todos when accepting merge requests via the API !6486 (tonygambone) v 8.12.2 (unreleased) - Added University content to doc/university diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 935417d4ae8..020a21ddf93 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -308,8 +308,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController return end - TodoService.new.merge_merge_request(merge_request, current_user) - @merge_request.update(merge_error: nil) if params[:merge_when_build_succeeds].present? diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb index 8437d9b8b43..e8fb1b59752 100644 --- a/app/services/merge_requests/post_merge_service.rb +++ b/app/services/merge_requests/post_merge_service.rb @@ -7,6 +7,7 @@ module MergeRequests class PostMergeService < MergeRequests::BaseService def execute(merge_request) close_issues(merge_request) + todo_service.merge_merge_request(merge_request, current_user) merge_request.mark_as_merged create_merge_event(merge_request, current_user) create_note(merge_request) diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 159f6817e8d..31167675d07 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -38,6 +38,30 @@ describe MergeRequests::MergeService, services: true do end end + context 'closes related todos' do + let(:merge_request) { create(:merge_request, assignee: user, author: user) } + let(:project) { merge_request.project } + let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') } + let!(:todo) do + create(:todo, :assigned, + project: project, + author: user, + user: user, + target: merge_request) + end + + before do + allow(service).to receive(:execute_hooks) + + perform_enqueued_jobs do + service.execute(merge_request) + todo.reload + end + end + + it { expect(todo).to be_done } + end + context 'remove source branch by author' do let(:service) do merge_request.merge_params['force_remove_source_branch'] = '1' diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index a162df5fc34..59d3912018a 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -79,8 +79,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request).to be_merged } it { expect(@fork_merge_request).to be_merged } it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } - it { expect(@build_failed_todo).to be_pending } - it { expect(@fork_build_failed_todo).to be_pending } + it { expect(@build_failed_todo).to be_done } + it { expect(@fork_build_failed_todo).to be_done } end context 'manual merge of source branch' do @@ -99,8 +99,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.diffs.size).to be > 0 } it { expect(@fork_merge_request).to be_merged } it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } - it { expect(@build_failed_todo).to be_pending } - it { expect(@fork_build_failed_todo).to be_pending } + it { expect(@build_failed_todo).to be_done } + it { expect(@fork_build_failed_todo).to be_done } end context 'push to fork repo source branch' do @@ -149,8 +149,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request).to be_merged } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } - it { expect(@build_failed_todo).to be_pending } - it { expect(@fork_build_failed_todo).to be_pending } + it { expect(@build_failed_todo).to be_done } + it { expect(@fork_build_failed_todo).to be_done } end context 'push new branch that exists in a merge request' do -- cgit v1.2.1 From c70db581bfbe241f6d1354a73b671e7453cf19e6 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 29 Sep 2016 20:16:28 +0100 Subject: Added temporary responsive design --- app/assets/stylesheets/pages/projects.scss | 59 ++++++++++++++++++++++++++---- app/views/projects/compare/_form.html.haml | 9 +++-- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 7c8ab7cb2a2..78bc4b79e86 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -744,18 +744,61 @@ pre.light-well { width: 300px; } - > .input-group > .compare-dropdown-toggle { - width: 200px; + &.from .compare-dropdown-toggle { + width: 237px; + } - .dropdown-toggle-text { - display: block; - height: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + &.to .compare-dropdown-toggle { + width: 254px; + } + + .dropdown-toggle-text { + display: block; + height: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + } +} + +.compare-ellipsis { + display: inline; +} + +@media (max-width: $screen-xs-max) { + .compare-form-group { + .input-group { + width: 100%; + + & > .compare-dropdown-toggle { + width: 100%; + } + } + + .dropdown-menu { width: 100%; } } + + .compare-switch-container { + text-align: center; + padding: 0 0 $gl-padding; + + .commits-compare-switch { + float: none; + } + } + + .compare-ellipsis { + display: block; + text-align: center; + padding: 0 0 $gl-padding; + } + + .commits-compare-btn { + width: 100%; + } } .clearable-input { diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index b66027115b2..76b68c544aa 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,16 +1,17 @@ = form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do .clearfix - if params[:to] && params[:from] - = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'} - .form-group.dropdown.compare-form-group.js-compare-from-dropdown + .compare-switch-container + = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'} + .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown .input-group.inline-input-group %span.input-group-addon from = hidden_field_tag :from, params[:from] = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do .dropdown-toggle-text= params[:from] || 'Select branch/tag' = render "ref_dropdown" - = "..." - .form-group.dropdown.compare-form-group.js-compare-to-dropdown + .compare-ellipsis ... + .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown .input-group.inline-input-group %span.input-group-addon to = hidden_field_tag :to, params[:to] -- cgit v1.2.1 From a095a3a694091ac9a7387e89e06f12c8ba80815c Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sat, 24 Sep 2016 19:17:05 +0100 Subject: Removed blocks-first declaration that was added to fix coverage padding, moved the padding to a new coverage block declaration Review changes --- app/assets/stylesheets/pages/builds.scss | 13 ++++++++++--- app/views/projects/builds/_sidebar.html.haml | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index a5a260d4c8f..194a39a8377 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -107,10 +107,14 @@ .block { width: 100%; - } - .block-first { - padding: 5px 16px 11px; + &.coverage { + padding: 0 16px 11px; + } + + .btn-group-justified { + margin-top: 5px; + } } .js-build-variable { @@ -214,6 +218,9 @@ .build-detail-row { margin-bottom: 5px; + &:last-of-type { + margin-bottom: 0; + } } .build-light-text { diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 0aa3092baa2..8846cf8577c 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -8,7 +8,7 @@ %a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" } = icon('angle-double-right') - if @build.coverage - .block.block-first + .block.coverage .title Test coverage %p.build-detail-row @@ -95,7 +95,7 @@ - @build.trigger_request.variables.each do |key, value| .hide.js-build - .js-build-variable= key + .js-build-variable= key .js-build-value= value .block -- cgit v1.2.1 From 385c602bd0e45418ccc10893f8f472584ba95551 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 30 Sep 2016 03:41:22 +0800 Subject: Less confusing name --- spec/services/ci/process_pipeline_service_spec.rb | 38 +++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 8326e5cd313..e55daabe5a7 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -18,7 +18,7 @@ describe Ci::ProcessPipelineService, services: true do all_builds.where.not(status: [:created, :skipped]) end - def create_builds + def process_pipeline described_class.new(pipeline.project, user).execute(pipeline) end @@ -36,26 +36,26 @@ describe Ci::ProcessPipelineService, services: true do end it 'processes a pipeline' do - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy succeed_pending expect(builds.success.count).to eq(2) - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy succeed_pending expect(builds.success.count).to eq(4) - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy succeed_pending expect(builds.success.count).to eq(5) - expect(create_builds).to be_falsey + expect(process_pipeline).to be_falsey end it 'does not process pipeline if existing stage is running' do - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.pending.count).to eq(2) - expect(create_builds).to be_falsey + expect(process_pipeline).to be_falsey expect(builds.pending.count).to eq(2) end end @@ -67,7 +67,7 @@ describe Ci::ProcessPipelineService, services: true do end it 'automatically triggers a next stage when build finishes' do - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.pluck(:status)).to contain_exactly('pending') pipeline.builds.running_or_pending.each(&:drop) @@ -88,7 +88,7 @@ describe Ci::ProcessPipelineService, services: true do context 'when builds are successful' do it 'properly creates builds' do - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.pluck(:name)).to contain_exactly('build') expect(builds.pluck(:status)).to contain_exactly('pending') pipeline.builds.running_or_pending.each(&:success) @@ -113,7 +113,7 @@ describe Ci::ProcessPipelineService, services: true do context 'when test job fails' do it 'properly creates builds' do - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.pluck(:name)).to contain_exactly('build') expect(builds.pluck(:status)).to contain_exactly('pending') pipeline.builds.running_or_pending.each(&:success) @@ -138,7 +138,7 @@ describe Ci::ProcessPipelineService, services: true do context 'when test and test_failure jobs fail' do it 'properly creates builds' do - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.pluck(:name)).to contain_exactly('build') expect(builds.pluck(:status)).to contain_exactly('pending') pipeline.builds.running_or_pending.each(&:success) @@ -164,7 +164,7 @@ describe Ci::ProcessPipelineService, services: true do context 'when deploy job fails' do it 'properly creates builds' do - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.pluck(:name)).to contain_exactly('build') expect(builds.pluck(:status)).to contain_exactly('pending') pipeline.builds.running_or_pending.each(&:success) @@ -189,7 +189,7 @@ describe Ci::ProcessPipelineService, services: true do context 'when build is canceled in the second stage' do it 'does not schedule builds after build has been canceled' do - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.pluck(:name)).to contain_exactly('build') expect(builds.pluck(:status)).to contain_exactly('pending') pipeline.builds.running_or_pending.each(&:success) @@ -208,7 +208,7 @@ describe Ci::ProcessPipelineService, services: true do context 'when listing manual actions' do it 'returns only for skipped builds' do # currently all builds are created - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(manual_actions).to be_empty # succeed stage build @@ -242,7 +242,7 @@ describe Ci::ProcessPipelineService, services: true do end it 'does trigger builds in the next stage' do - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.pluck(:name)).to contain_exactly('build:1', 'build:2') pipeline.builds.running_or_pending.each(&:success) @@ -297,14 +297,14 @@ describe Ci::ProcessPipelineService, services: true do expect(all_builds.count).to eq(2) # Create builds will mark the created as pending - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.count).to eq(2) expect(all_builds.count).to eq(2) # When we builds succeed we will create a rest of pipeline from .gitlab-ci.yml # We will have 2 succeeded, 2 pending (from stage test), total 5 (one more build from deploy) succeed_pending - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.success.count).to eq(2) expect(builds.pending.count).to eq(2) expect(all_builds.count).to eq(5) @@ -312,14 +312,14 @@ describe Ci::ProcessPipelineService, services: true do # When we succeed the 2 pending from stage test, # We will queue a deploy stage, no new builds will be created succeed_pending - expect(create_builds).to be_truthy + expect(process_pipeline).to be_truthy expect(builds.pending.count).to eq(1) expect(builds.success.count).to eq(4) expect(all_builds.count).to eq(5) # When we succeed last pending build, we will have a total of 5 succeeded builds, no new builds will be created succeed_pending - expect(create_builds).to be_falsey + expect(process_pipeline).to be_falsey expect(builds.success.count).to eq(5) expect(all_builds.count).to eq(5) end -- cgit v1.2.1 From 6053acf5e6cee3ceceb1010ed77f1ce71099d5e1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 30 Sep 2016 03:41:38 +0800 Subject: We consider skipped = success, fixes #22598 --- app/models/concerns/has_status.rb | 2 +- spec/services/ci/process_pipeline_service_spec.rb | 49 +++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 0fa4df0fb56..755eadf779a 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -21,7 +21,7 @@ module HasStatus deduce_status = "(CASE WHEN (#{builds})=(#{created}) THEN 'created' - WHEN (#{builds})=(#{skipped}) THEN 'skipped' + WHEN (#{builds})=(#{skipped}) THEN 'success' WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending' WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index e55daabe5a7..b6c23002dda 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -230,6 +230,55 @@ describe Ci::ProcessPipelineService, services: true do end end + context 'when there are manual jobs in earlier stages' do + before do + builds + process_pipeline + builds.each(&:reload) + end + + context 'when first stage has only manual jobs' do + let(:builds) do + [create_build('build', 0, 'manual'), + create_build('check', 1), + create_build('test', 2)] + end + + it 'starts from the second stage' do + expect(builds.map(&:status)).to contain_exactly( + 'skipped', 'pending', 'created') + end + end + + context 'when second stage has only manual jobs' do + let(:builds) do + [create_build('check', 0), + create_build('build', 1, 'manual'), + create_build('test', 2)] + end + + it 'skips second stage and continues on third stage' do + expect(builds.map(&:status)).to contain_exactly( + 'pending', 'created', 'created') + + builds.first.success + builds.each(&:reload) + + expect(builds.map(&:status)).to contain_exactly( + 'success', 'skipped', 'pending') + end + end + + def create_build(name, stage_idx, when_value = nil) + create(:ci_build, + :created, + pipeline: pipeline, + name: name, + stage_idx: stage_idx, + when: when_value) + end + end + context 'when failed build in the middle stage is retried' do context 'when failed build is the only unsuccessful build in the stage' do before do -- cgit v1.2.1 From 069d739efd17d8a6af5f3798a37e8f2cbfb02e5b Mon Sep 17 00:00:00 2001 From: John McGehee Date: Thu, 29 Sep 2016 20:38:43 +0000 Subject: Added "CI secret variables" per @axil. --- doc/raketasks/backup_restore.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index faa7e9eaa44..91c2df7d464 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -15,9 +15,9 @@ another is through backup restore. To restore a backup, you will also need to restore `/etc/gitlab/gitlab-secrets.json` (for omnibus packages) or `/home/git/gitlab/.secret` (for installations -from source). This file contains the database encryption key used -for two-factor authentication. If you fail to restore this encryption key file -along with the application data backup, users with two-factor +from source). This file contains the database encryption key and CI secret +variables used for two-factor authentication. If you fail to restore this +encryption key file along with the application data backup, users with two-factor authentication enabled will lose access to your GitLab server. ## Create a backup of the GitLab system -- cgit v1.2.1 From 1553275ebc5ecccda208c1138755edf9b3198700 Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Thu, 29 Sep 2016 17:00:21 -0400 Subject: Added CC0 license to list of licenses --- doc/development/licensing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/development/licensing.md b/doc/development/licensing.md index 8c8c7486fff..05972b33fdb 100644 --- a/doc/development/licensing.md +++ b/doc/development/licensing.md @@ -54,6 +54,7 @@ Libraries with the following licenses are acceptable for use: - [BSD 2-Clause License][BSD-2-Clause]: A permissive (non-copyleft) license as defined by the Open Source Initiative. - [BSD 3-Clause License][BSD-3-Clause] (also known as New BSD or Modified BSD): A permissive (non-copyleft) license as defined by the Open Source Initiative - [ISC License][ISC] (also known as the OpenBSD License): A permissive (non-copyleft) license as defined by the Open Source Initiative. +- [Creative Commons Zero (CC0)][CC0]: A public domain dedication, recommended as a way to disclaim copyright on your work to the maximum extent possible. ## Unacceptable Licenses @@ -85,6 +86,7 @@ Gems which are included only in the "development" or "test" groups by Bundler ar [BSD-2-Clause]: https://opensource.org/licenses/BSD-2-Clause [BSD-3-Clause]: https://opensource.org/licenses/BSD-3-Clause [ISC]: https://opensource.org/licenses/ISC +[CC0]: https://creativecommons.org/publicdomain/zero/1.0/ [GPL]: http://choosealicense.com/licenses/gpl-3.0/ [GPLv2]: http://www.gnu.org/licenses/gpl-2.0.txt [GPLv3]: http://www.gnu.org/licenses/gpl-3.0.txt -- cgit v1.2.1 From c2cc5d3448b3b5fe0c08e71496ea6b8114052b39 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 29 Sep 2016 17:06:01 -0500 Subject: Update CHANGELOG with 8.12.2 and 8.12.3 changes. --- CHANGELOG | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b4211706347..b4368c2cf9e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,8 +21,12 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Fix broken repository 500 errors in project list -v 8.12.2 (unreleased) - - Added University content to doc/university +v 8.12.4 (unreleased) + +v 8.12.3 + - Update Gitlab Shell to support low IO priority for storage moves + +v 8.12.2 - Fix Import/Export not recognising correctly the imported services. - Fix snippets pagination - Fix List-Unsubscribe header in emails @@ -31,11 +35,14 @@ v 8.12.2 (unreleased) - Fix errors importing project feature and milestone models using GitLab project import - Make JWT messages Docker-compatible - Fix duplicate branch entry in the merge request version compare dropdown + - Respect the fork_project permission when forking projects + - Only update issuable labels if they have been changed + - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) + - Fix resolve discussion buttons endpoint path v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix issue with search filter labels not displaying - - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) v 8.12.0 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 @@ -52,7 +59,6 @@ v 8.12.0 - Filter tags by name !6121 - Update gitlab shell secret file also when it is empty. !3774 (glensc) - Give project selection dropdowns responsive width, make non-wrapping. - - Fix resolve discussion buttons endpoint path - Fix note form hint showing slash commands supported for commits. - Make push events have equal vertical spacing. - API: Ensure invitees are not returned in Members API. -- cgit v1.2.1 From ec82cecf3c54cb2cd1ab7b72f77e787be1bf1264 Mon Sep 17 00:00:00 2001 From: Matthew Dodds Date: Thu, 29 Sep 2016 21:51:33 -0400 Subject: Add link to comparison from system note, update changelog --- CHANGELOG | 1 + app/services/system_note_service.rb | 15 ++++++++++++++- spec/services/system_note_service_spec.rb | 20 +++++++++++++++++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a1add261a89..b74c7a05908 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) - AbstractReferenceFilter caches project_refs on RequestStore when active - Speed-up group milestones show page diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 0c8446e7c3d..8da1af67ee6 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -21,7 +21,8 @@ module SystemNoteService total_count = new_commits.length + existing_commits.length commits_text = "#{total_count} commit".pluralize(total_count) - body = "Added #{commits_text}:\n\n" + body = "[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})\n\n" + body << "Added #{commits_text}:\n\n" body << existing_commit_summary(noteable, existing_commits, oldrev) body << new_commit_summary(new_commits).join("\n") @@ -466,4 +467,16 @@ module SystemNoteService def escape_html(text) Rack::Utils.escape_html(text) end + + def diff_comparison_url(merge_request, project, oldrev) + diff_id = merge_request.merge_request_diff.id + + Gitlab::Routing.url_helpers.diffs_namespace_project_merge_request_url( + project.namespace, + project, + merge_request.iid, + diff_id: diff_id, + start_sha: oldrev + ) + end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 3d854a959f3..16e345501d9 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -40,21 +40,35 @@ describe SystemNoteService, services: true do describe 'note body' do let(:note_lines) { subject.note.split("\n").reject(&:blank?) } + describe 'comparison diff link line' do + it 'adds the comparison link' do + link = Gitlab::Routing.url_helpers.diffs_namespace_project_merge_request_url( + project.namespace, + project, + noteable.iid, + diff_id: noteable.merge_request_diff.id, + start_sha: oldrev + ) + + expect(note_lines[0]).to eq "[Compare with previous version](#{link})" + end + end + context 'without existing commits' do it 'adds a message header' do - expect(note_lines[0]).to eq "Added #{new_commits.size} commits:" + expect(note_lines[1]).to eq "Added #{new_commits.size} commits:" end it 'adds a message line for each commit' do new_commits.each_with_index do |commit, i| # Skip the header - expect(note_lines[i + 1]).to eq "* #{commit.short_id} - #{commit.title}" + expect(note_lines[i + 2]).to eq "* #{commit.short_id} - #{commit.title}" end end end describe 'summary line for existing commits' do - let(:summary_line) { note_lines[1] } + let(:summary_line) { note_lines[2] } context 'with one existing commit' do let(:old_commits) { [noteable.commits.last] } -- cgit v1.2.1 From 82b13a21a6ab71027fb21b4555a207cfd398dafb Mon Sep 17 00:00:00 2001 From: Makoto Scott-Hinkle Date: Mon, 26 Sep 2016 16:47:34 -0700 Subject: Allowing ">" to be used for Milestone models's title and storing the value in db as unescaped. Updating test value for milestone title Adding API test for title with reserved HTML characters. Updating changelog Adding the MR number for fixing bug #22452. removing duplicate line Updating MR number. --- CHANGELOG | 1 + app/models/milestone.rb | 6 +++++- spec/models/milestone_spec.rb | 4 ++-- spec/requests/api/milestones_spec.rb | 8 ++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b4368c2cf9e..ffa1096c058 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.13.0 (unreleased) - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Fix broken repository 500 errors in project list + - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 v 8.12.4 (unreleased) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 2bd7f198030..44c3cbb2c73 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -158,7 +158,7 @@ class Milestone < ActiveRecord::Base end def title=(value) - write_attribute(:title, Sanitize.clean(value.to_s)) if value.present? + write_attribute(:title, sanitize_title(value)) if value.present? end # Sorts the issues for the given IDs. @@ -204,4 +204,8 @@ class Milestone < ActiveRecord::Base iid end end + + def sanitize_title(value) + CGI.unescape_html(Sanitize.clean(value.to_s)) + end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index d64d6cde2b5..33fe22dd98c 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -20,10 +20,10 @@ describe Milestone, models: true do let(:user) { create(:user) } describe "#title" do - let(:milestone) { create(:milestone, title: "test") } + let(:milestone) { create(:milestone, title: "foo & bar -> 2.2") } it "sanitizes title" do - expect(milestone.title).to eq("test") + expect(milestone.title).to eq("foo & bar -> 2.2") end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index b89dac01040..dd192bea432 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -104,6 +104,14 @@ describe API::API, api: true do expect(response).to have_http_status(400) end + + it 'creates a new project with reserved html characters' do + post api("/projects/#{project.id}/milestones", user), title: 'foo & bar 1.1 -> 2.2' + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2') + expect(json_response['description']).to be_nil + end end describe 'PUT /projects/:id/milestones/:milestone_id' do -- cgit v1.2.1 From c81c853a0db25342142021affaa4617af1818add Mon Sep 17 00:00:00 2001 From: Makoto Scott-Hinkle Date: Fri, 30 Sep 2016 02:34:34 +0000 Subject: Removing duplicate entries for !6533. --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 298264e8cdf..4e5a9189b0a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,7 +21,6 @@ v 8.13.0 (unreleased) - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Fix broken repository 500 errors in project list - - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 v 8.12.4 (unreleased) -- cgit v1.2.1 From 1bde1985c22c947a28d4da8d237f5ea8b894c2e0 Mon Sep 17 00:00:00 2001 From: Luke Howell Date: Sat, 10 Sep 2016 14:14:24 -0500 Subject: Fix tooltip for Copy to clipboard button. Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. Closes #22022 --- CHANGELOG | 1 + app/assets/javascripts/copy_to_clipboard.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b4368c2cf9e..e4f39c27f40 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.13.0 (unreleased) - Fix broken repository 500 errors in project list v 8.12.4 (unreleased) + - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. (lukehowell) v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js index 3e20db7e308..e23bda2fa4e 100644 --- a/app/assets/javascripts/copy_to_clipboard.js +++ b/app/assets/javascripts/copy_to_clipboard.js @@ -26,15 +26,15 @@ }; showTooltip = function(target, title) { - return $(target).tooltip({ - container: 'body', - html: 'true', - placement: 'auto bottom', - title: title, - trigger: 'manual' - }).tooltip('show').one('mouseleave', function() { - return $(this).tooltip('hide'); - }); + var $target = $(target); + var originalTitle = $target.data('original-title'); + + $target + .attr('title', 'Copied!') + .tooltip('fixTitle') + .tooltip('show') + .attr('title', originalTitle) + .tooltip('fixTitle'); }; $(function() { -- cgit v1.2.1 From 47573a5c93dcdc7c6f6de40f1086d2b27a4f6e37 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Clorichel Date: Fri, 30 Sep 2016 06:57:58 +0000 Subject: document the need to be owner or have the master permission level for the initial push --- app/views/projects/empty.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 636beb73ec2..4065b27156f 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -23,6 +23,8 @@ or a = link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore'), class: 'underlined-link' to this project. + %p + You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected. - if can?(current_user, :push_code, @project) %div{ class: container_class } -- cgit v1.2.1 From 32e32fa0fd45ef091fe4c4639313c3e281bfc5df Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 29 Sep 2016 12:59:31 +0200 Subject: Revert to use Mounter method to check existence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See: https://gitlab.com/gitlab-org/gitlab-ce/commit/6280fd3777920670ee42111fddf29576cbf85988#note_14766241 We wanted to avoid accesses to the file system, because it seems reasonable that if the mounter column has some content it’s because there are an associate file. This is an speed boost when you access to a bunch of build instances to show their information. The problem is that solution doesn’t work if you uses that method to detect the thing and you’re changing the uploaded file on instances that had that column empty. Until you don’t persist the instance the database column will be empty so we can have false negatives --- CHANGELOG | 1 + app/models/ci/build.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b4368c2cf9e..7f3111bfd44 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Only update issuable labels if they have been changed - Revoke button in Applications Settings underlines on hover. - Fix Long commit messages overflow viewport in file tree + - Revert avoid touching file system on Build#artifacts? - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Add organization field to user profile - Fix resolved discussion display in side-by-side diff view !6575 diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 522e2264bb8..5dbf66173de 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -373,7 +373,7 @@ module Ci end def artifacts? - !artifacts_expired? && self[:artifacts_file].present? + !artifacts_expired? && artifacts_file.exists? end def artifacts_metadata? -- cgit v1.2.1 From 25edb0709ccfae340817dc2f414cc8207899a2e0 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 22 Sep 2016 15:49:53 +0200 Subject: Close merge request if open without source project --- app/controllers/projects/merge_requests_controller.rb | 6 ++++-- spec/views/projects/merge_requests/show.html.haml_spec.rb | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 020a21ddf93..ae844db4ab0 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -417,8 +417,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController def validates_merge_request # If source project was removed and merge request for some reason - # wasn't close (Ex. mr from fork to origin) - return invalid_mr if !@merge_request.source_project && @merge_request.open? + # wasn't close (Ex. mr from fork to origin) merge request will be closed + if !@merge_request.source_project && @merge_request.open? + @merge_request.close + end # Show git not found page # if there is no saved commits between source & target branch diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index 68fbb4585c1..842b53344c1 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -41,4 +41,17 @@ describe 'projects/merge_requests/show.html.haml' do expect(rendered).to have_css('a', visible: false, text: 'Close') end end + + context 'when the merge request is open' do + it 'closes open merge request' do + closed_merge_request.update_attributes(state: 'open') + fork_project.destroy + + render + + expect(closed_merge_request.reload.state).to eq('closed') + expect(rendered).to have_css('a', visible: false, text: 'Reopen') + expect(rendered).to have_css('a', visible: false, text: 'Close') + end + end end -- cgit v1.2.1 From 1b547cfba8e0e7b0fb9eef5c2fcc8a9606c83f4d Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 23 Sep 2016 10:18:54 +0200 Subject: Improve grammar --- app/controllers/projects/merge_requests_controller.rb | 2 +- spec/views/projects/merge_requests/show.html.haml_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ae844db4ab0..6fcdbda58e8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -417,7 +417,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def validates_merge_request # If source project was removed and merge request for some reason - # wasn't close (Ex. mr from fork to origin) merge request will be closed + # wasn't closed (Ex. MR from fork to origin) merge request will be closed if !@merge_request.source_project && @merge_request.open? @merge_request.close end diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index 842b53344c1..33cabd14913 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -43,7 +43,7 @@ describe 'projects/merge_requests/show.html.haml' do end context 'when the merge request is open' do - it 'closes open merge request' do + it 'closes the merge request if the source project does not exist' do closed_merge_request.update_attributes(state: 'open') fork_project.destroy -- cgit v1.2.1 From cb34e97d2c0ac24e791583431213967f02404a33 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 23 Sep 2016 21:55:05 +0200 Subject: Add CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 10111b446ce..baeede44bc5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) v 8.12.4 (unreleased) -- cgit v1.2.1 From 13d2ec2d1f7d94f16ad2dce881c37376235bd6ff Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 26 Sep 2016 13:49:20 +0200 Subject: Callback close_merge_request_without_source_project --- app/controllers/projects/merge_requests_controller.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 6fcdbda58e8..657a5cf32bd 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -18,6 +18,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :define_commit_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] + before_action :close_merge_request_without_source_project, only: [:show] # Allow read any merge_request before_action :authorize_read_merge_request! @@ -416,12 +417,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def validates_merge_request - # If source project was removed and merge request for some reason - # wasn't closed (Ex. MR from fork to origin) merge request will be closed - if !@merge_request.source_project && @merge_request.open? - @merge_request.close - end - # Show git not found page # if there is no saved commits between source & target branch if @merge_request.commits.blank? @@ -496,7 +491,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def invalid_mr - # Render special view for MR with removed source or target branch + # Render special view for MR with removed target branch render 'invalid' end @@ -538,4 +533,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController @diff_notes_disabled = !@merge_request_diff.latest? @diffs = @merge_request_diff.diffs(diff_options) end + + def close_merge_request_without_source_project + if !@merge_request.source_project && @merge_request.open? + @merge_request.close + end + end end -- cgit v1.2.1 From 57229f4807e65b83eaaa4d0f29dd38d80ff8f109 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 29 Sep 2016 21:03:44 +0200 Subject: Change callback --- app/controllers/projects/merge_requests_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 657a5cf32bd..68bb4232f5b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -18,7 +18,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :define_commit_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] - before_action :close_merge_request_without_source_project, only: [:show] + before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] # Allow read any merge_request before_action :authorize_read_merge_request! -- cgit v1.2.1 From 02db2dc5f34e51c507ebcda876184fca1b5a9c1e Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 30 Sep 2016 11:48:39 +0200 Subject: Update CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index baeede44bc5..249c4993811 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.13.0 (unreleased) - Fix permission for setting an issue's due date - Expose expires_at field when sharing project on API - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) + - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Only update issuable labels if they have been changed - Revoke button in Applications Settings underlines on hover. @@ -21,7 +22,6 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) - - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) v 8.12.4 (unreleased) -- cgit v1.2.1 From 9b361a3f44eec7a301565318ce86742f2f139a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 23 Sep 2016 15:16:11 +0200 Subject: Take filters in account in issuable counters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/finders/issuable_finder.rb | 15 +-- app/helpers/application_helper.rb | 19 ++- app/views/shared/issuable/_nav.html.haml | 20 ++-- spec/features/dashboard_issues_spec.rb | 15 +++ spec/features/issues/filter_by_labels_spec.rb | 133 ++++++--------------- spec/features/issues/filter_issues_spec.rb | 56 ++++++++- .../merge_requests/filter_by_milestone_spec.rb | 15 +++ 8 files changed, 141 insertions(+), 133 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1eb7ea15756..9d0fd2526ee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.13.0 (unreleased) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Only update issuable labels if they have been changed + - Take filters in account in issuable counters. !6496 - Revoke button in Applications Settings underlines on hover. - Fix Long commit messages overflow viewport in file tree - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 8f9ef8f725c..9f170428100 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -183,17 +183,12 @@ class IssuableFinder end def by_state(items) - case params[:state] - when 'closed' - items.closed - when 'merged' - items.respond_to?(:merged) ? items.merged : items.closed - when 'all' - items - when 'opened' - items.opened + params[:state] ||= 'all' + + if items.respond_to?(params[:state]) + items.public_send(params[:state]) else - raise 'You must specify default state' + items end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1df430e6279..bbc037288db 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -280,27 +280,24 @@ module ApplicationHelper end end - def state_filters_text_for(entity, project) + def issuables_state_counter_text(state, issuables) titles = { opened: "Open" } - entity_title = titles[entity] || entity.to_s.humanize + state_title = titles[state] || state.to_s.humanize count = - if project.nil? - nil - elsif current_controller?(:issues) - project.issues.visible_to_user(current_user).send(entity).count - elsif current_controller?(:merge_requests) - project.merge_requests.send(entity).count + if @issues || @merge_requests + issuables_finder = @issues ? issues_finder : merge_requests_finder + issuables_finder.params[:state] = state + issuables_finder.execute.page(1).total_count end - html = content_tag :span, entity_title + html = content_tag(:span, state_title) if count.present? - html += " " - html += content_tag :span, number_with_delimiter(count), class: 'badge' + html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge') end html.html_safe diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index 1d9b09a5ef1..475b99a2a07 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -1,25 +1,25 @@ +- type = local_assigns.fetch(:type, :issues) +- page_context_word = type.to_s.humanize(capitalize: false) +- issuables = @issues || @merge_requests + %ul.nav-links.issues-state-filters - - if defined?(type) && type == :merge_requests - - page_context_word = 'merge requests' - - else - - page_context_word = 'issues' %li{class: ("active" if params[:state] == 'opened')} = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do - #{state_filters_text_for(:opened, @project)} + #{issuables_state_counter_text(:opened, issuables)} - - if defined?(type) && type == :merge_requests + - if type == :merge_requests %li{class: ("active" if params[:state] == 'merged')} = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do - #{state_filters_text_for(:merged, @project)} + #{issuables_state_counter_text(:merged, issuables)} %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do - #{state_filters_text_for(:closed, @project)} + #{issuables_state_counter_text(:closed, issuables)} - else %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do - #{state_filters_text_for(:closed, @project)} + #{issuables_state_counter_text(:closed, issuables)} %li{class: ("active" if params[:state] == 'all')} = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do - #{state_filters_text_for(:all, @project)} + #{issuables_state_counter_text(:all, issuables)} diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb index 3fb1cb37544..4dccf645454 100644 --- a/spec/features/dashboard_issues_spec.rb +++ b/spec/features/dashboard_issues_spec.rb @@ -21,6 +21,11 @@ describe "Dashboard Issues filtering", feature: true, js: true do click_link 'No Milestone' + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end expect(page).to have_selector('.issue', count: 1) end @@ -29,6 +34,11 @@ describe "Dashboard Issues filtering", feature: true, js: true do click_link 'Any Milestone' + page.within '.issues-state-filters' do + expect(page).to have_content('Open 2') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 2') + end expect(page).to have_selector('.issue', count: 2) end @@ -39,6 +49,11 @@ describe "Dashboard Issues filtering", feature: true, js: true do click_link milestone.title end + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end expect(page).to have_selector('.issue', count: 1) end end diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 908b18e5339..a615174c00e 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -1,10 +1,10 @@ require 'rails_helper' -feature 'Issue filtering by Labels', feature: true do +feature 'Issue filtering by Labels', feature: true, js: true do include WaitForAjax let(:project) { create(:project, :public) } - let!(:user) { create(:user)} + let!(:user) { create(:user) } let!(:label) { create(:label, project: project) } before do @@ -28,156 +28,85 @@ feature 'Issue filtering by Labels', feature: true do visit namespace_project_issues_path(project.namespace, project) end - context 'filter by label bug', js: true do + context 'filter by label bug' do before do - page.find('.js-label-select').click - wait_for_ajax - execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") - page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax + select_labels('bug') end - it 'shows issue "Bugfix1" and "Bugfix2" in issues list' do + it 'apply the filter' do expect(page).to have_content "Bugfix1" expect(page).to have_content "Bugfix2" - end - - it 'does not show "Feature1" in issues list' do expect(page).not_to have_content "Feature1" - end - - it 'shows label "bug" in filtered-labels' do expect(find('.filtered-labels')).to have_content "bug" - end - - it 'does not show label "feature" and "enhancement" in filtered-labels' do expect(find('.filtered-labels')).not_to have_content "feature" expect(find('.filtered-labels')).not_to have_content "enhancement" - end - it 'removes label "bug"' do find('.js-label-filter-remove').click wait_for_ajax expect(find('.filtered-labels', visible: false)).to have_no_content "bug" end end - context 'filter by label feature', js: true do + context 'filter by label feature' do before do - page.find('.js-label-select').click - wait_for_ajax - execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") - page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax + select_labels('feature') end - it 'shows issue "Feature1" in issues list' do + it 'applies the filter' do expect(page).to have_content "Feature1" - end - - it 'does not show "Bugfix1" and "Bugfix2" in issues list' do expect(page).not_to have_content "Bugfix2" expect(page).not_to have_content "Bugfix1" - end - - it 'shows label "feature" in filtered-labels' do expect(find('.filtered-labels')).to have_content "feature" - end - - it 'does not show label "bug" and "enhancement" in filtered-labels' do expect(find('.filtered-labels')).not_to have_content "bug" expect(find('.filtered-labels')).not_to have_content "enhancement" end end - context 'filter by label enhancement', js: true do + context 'filter by label enhancement' do before do - page.find('.js-label-select').click - wait_for_ajax - execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") - page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax + select_labels('enhancement') end - it 'shows issue "Bugfix2" in issues list' do + it 'applies the filter' do expect(page).to have_content "Bugfix2" - end - - it 'does not show "Feature1" and "Bugfix1" in issues list' do expect(page).not_to have_content "Feature1" expect(page).not_to have_content "Bugfix1" - end - - it 'shows label "enhancement" in filtered-labels' do expect(find('.filtered-labels')).to have_content "enhancement" - end - - it 'does not show label "feature" and "bug" in filtered-labels' do expect(find('.filtered-labels')).not_to have_content "bug" expect(find('.filtered-labels')).not_to have_content "feature" end end - context 'filter by label enhancement or feature', js: true do + context 'filter by label enhancement and bug in issues list' do before do - page.find('.js-label-select').click - wait_for_ajax - execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") - execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") - page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax + select_labels('bug', 'enhancement') end - it 'does not show "Bugfix1" or "Feature1" in issues list' do - expect(page).not_to have_content "Bugfix1" + it 'applies the filters' do + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end + expect(page).to have_content "Bugfix2" expect(page).not_to have_content "Feature1" - end - - it 'shows label "enhancement" and "feature" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "bug" expect(find('.filtered-labels')).to have_content "enhancement" - expect(find('.filtered-labels')).to have_content "feature" - end - - it 'does not show label "bug" in filtered-labels' do - expect(find('.filtered-labels')).not_to have_content "bug" - end + expect(find('.filtered-labels')).not_to have_content "feature" - it 'removes label "enhancement"' do find('.js-label-filter-remove', match: :first).click wait_for_ajax - expect(find('.filtered-labels')).to have_no_content "enhancement" - end - end - context 'filter by label enhancement and bug in issues list', js: true do - before do - page.find('.js-label-select').click - wait_for_ajax - execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") - execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") - page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - wait_for_ajax - end - - it 'shows issue "Bugfix2" in issues list' do expect(page).to have_content "Bugfix2" - end - - it 'does not show "Feature1"' do expect(page).not_to have_content "Feature1" - end - - it 'shows label "bug" and "enhancement" in filtered-labels' do - expect(find('.filtered-labels')).to have_content "bug" + expect(page).not_to have_content "Bugfix1" + expect(find('.filtered-labels')).not_to have_content "bug" expect(find('.filtered-labels')).to have_content "enhancement" - end - - it 'does not show label "feature" in filtered-labels' do expect(find('.filtered-labels')).not_to have_content "feature" end end - context 'remove filtered labels', js: true do + context 'remove filtered labels' do before do page.within '.labels-filter' do click_button 'Label' @@ -200,7 +129,7 @@ feature 'Issue filtering by Labels', feature: true do end end - context 'dropdown filtering', js: true do + context 'dropdown filtering' do it 'filters by label name' do page.within '.labels-filter' do click_button 'Label' @@ -214,4 +143,14 @@ feature 'Issue filtering by Labels', feature: true do end end end + + def select_labels(*labels) + page.find('.js-label-select').click + wait_for_ajax + labels.each do |label| + execute_script("$('.dropdown-menu-labels li:contains(\"#{label}\") a').click()") + end + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + wait_for_ajax + end end diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index d1501c9791a..8fd67acff69 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -7,15 +7,15 @@ describe 'Filter issues', feature: true do let!(:user) { create(:user)} let!(:milestone) { create(:milestone, project: project) } let!(:label) { create(:label, project: project) } - let!(:issue1) { create(:issue, project: project) } let!(:wontfix) { create(:label, project: project, title: "Won't fix") } before do project.team << [user, :master] login_as(user) + create(:issue, project: project) end - describe 'Filter issues for assignee from issues#index' do + describe 'for assignee from issues#index' do before do visit namespace_project_issues_path(project.namespace, project) @@ -45,7 +45,7 @@ describe 'Filter issues', feature: true do end end - describe 'Filter issues for milestone from issues#index' do + describe 'for milestone from issues#index' do before do visit namespace_project_issues_path(project.namespace, project) @@ -75,7 +75,7 @@ describe 'Filter issues', feature: true do end end - describe 'Filter issues for label from issues#index', js: true do + describe 'for label from issues#index', js: true do before do visit namespace_project_issues_path(project.namespace, project) find('.js-label-select').click @@ -115,6 +115,7 @@ describe 'Filter issues', feature: true do expect(page).to have_content wontfix.title click_link wontfix.title end + expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title) end @@ -146,7 +147,7 @@ describe 'Filter issues', feature: true do end end - describe 'Filter issues for assignee and label from issues#index' do + describe 'for assignee and label from issues#index' do before do visit namespace_project_issues_path(project.namespace, project) @@ -226,6 +227,11 @@ describe 'Filter issues', feature: true do it 'filters by text and label' do fill_in 'issuable_search', with: 'Bug' + page.within '.issues-state-filters' do + expect(page).to have_content('Open 2') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 2') + end page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end @@ -236,6 +242,11 @@ describe 'Filter issues', feature: true do end find('.dropdown-menu-close-icon').click + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end page.within '.issues-list' do expect(page).to have_selector('.issue', count: 1) end @@ -244,6 +255,11 @@ describe 'Filter issues', feature: true do it 'filters by text and milestone' do fill_in 'issuable_search', with: 'Bug' + page.within '.issues-state-filters' do + expect(page).to have_content('Open 2') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 2') + end page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end @@ -253,6 +269,11 @@ describe 'Filter issues', feature: true do click_link '8' end + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end page.within '.issues-list' do expect(page).to have_selector('.issue', count: 1) end @@ -261,6 +282,11 @@ describe 'Filter issues', feature: true do it 'filters by text and assignee' do fill_in 'issuable_search', with: 'Bug' + page.within '.issues-state-filters' do + expect(page).to have_content('Open 2') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 2') + end page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end @@ -270,6 +296,11 @@ describe 'Filter issues', feature: true do click_link user.name end + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end page.within '.issues-list' do expect(page).to have_selector('.issue', count: 1) end @@ -278,6 +309,11 @@ describe 'Filter issues', feature: true do it 'filters by text and author' do fill_in 'issuable_search', with: 'Bug' + page.within '.issues-state-filters' do + expect(page).to have_content('Open 2') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 2') + end page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end @@ -287,6 +323,11 @@ describe 'Filter issues', feature: true do click_link user.name end + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end page.within '.issues-list' do expect(page).to have_selector('.issue', count: 1) end @@ -315,6 +356,11 @@ describe 'Filter issues', feature: true do find('.dropdown-menu-close-icon').click wait_for_ajax + page.within '.issues-state-filters' do + expect(page).to have_content('Open 2') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 2') + end page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb index bb0bb590a46..3d5d9614fb6 100644 --- a/spec/features/merge_requests/filter_by_milestone_spec.rb +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -17,6 +17,11 @@ feature 'Merge Request filtering by Milestone', feature: true do visit_merge_requests(project) filter_by_milestone(Milestone::None.title) + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end expect(page).to have_css('.merge-request', count: 1) end @@ -39,6 +44,11 @@ feature 'Merge Request filtering by Milestone', feature: true do visit_merge_requests(project) filter_by_milestone(Milestone::Upcoming.title) + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end expect(page).to have_css('.merge-request', count: 1) end @@ -61,6 +71,11 @@ feature 'Merge Request filtering by Milestone', feature: true do visit_merge_requests(project) filter_by_milestone(milestone.title) + page.within '.issues-state-filters' do + expect(page).to have_content('Open 1') + expect(page).to have_content('Closed 0') + expect(page).to have_content('All 1') + end expect(page).to have_css('.merge-request', count: 1) end -- cgit v1.2.1 From 383dafdf31adaded392664cba9ba8b7262505dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 26 Sep 2016 19:12:16 +0200 Subject: Cache the issuable counters for 2 minutes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/helpers/application_helper.rb | 23 -------- app/helpers/issuables_helper.rb | 36 ++++++++++++ app/views/shared/issuable/_nav.html.haml | 10 ++-- spec/helpers/issuables_helper_spec.rb | 97 +++++++++++++++++++++++++++++++- 4 files changed, 136 insertions(+), 30 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bbc037288db..ebd78bf9888 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -280,29 +280,6 @@ module ApplicationHelper end end - def issuables_state_counter_text(state, issuables) - titles = { - opened: "Open" - } - - state_title = titles[state] || state.to_s.humanize - - count = - if @issues || @merge_requests - issuables_finder = @issues ? issues_finder : merge_requests_finder - issuables_finder.params[:state] = state - issuables_finder.execute.page(1).total_count - end - - html = content_tag(:span, state_title) - - if count.present? - html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge') - end - - html.html_safe - end - def truncate_first_line(message, length = 50) truncate(message.each_line.first.chomp, length: length) if message end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 5c04bba323f..8aa1ece017c 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -94,6 +94,24 @@ module IssuablesHelper label_names.join(', ') end + def issuables_state_counter_text(issuable_type, state) + titles = { + opened: "Open" + } + + state_title = titles[state] || state.to_s.humanize + + count = + Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do + issuables_count_for_state(issuable_type, state) + end + + html = content_tag(:span, state_title) + html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge') + + html.html_safe + end + private def sidebar_gutter_collapsed? @@ -111,4 +129,22 @@ module IssuablesHelper issuable.open? ? :opened : :closed end end + + def issuables_count_for_state(issuable_type, state) + issuables_finder = public_send("#{issuable_type}_finder") + issuables_finder.params[:state] = state + + issuables_finder.execute.page(1).total_count + end + + IRRELEVANT_PARAMS_FOR_CACHE_KEY = %w[utf8 sort page] + private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY + + def issuables_state_counter_cache_key(issuable_type, state) + opts = params.dup + opts['state'] = state + opts.delete_if { |k, v| IRRELEVANT_PARAMS_FOR_CACHE_KEY.include?(k) } + + hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-')) + end end diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index 475b99a2a07..5527a2f889a 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -5,21 +5,21 @@ %ul.nav-links.issues-state-filters %li{class: ("active" if params[:state] == 'opened')} = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do - #{issuables_state_counter_text(:opened, issuables)} + #{issuables_state_counter_text(type, :opened)} - if type == :merge_requests %li{class: ("active" if params[:state] == 'merged')} = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do - #{issuables_state_counter_text(:merged, issuables)} + #{issuables_state_counter_text(type, :merged)} %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do - #{issuables_state_counter_text(:closed, issuables)} + #{issuables_state_counter_text(type, :closed)} - else %li{class: ("active" if params[:state] == 'closed')} = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do - #{issuables_state_counter_text(:closed, issuables)} + #{issuables_state_counter_text(type, :closed)} %li{class: ("active" if params[:state] == 'all')} = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do - #{issuables_state_counter_text(:all, issuables)} + #{issuables_state_counter_text(type, :all)} diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 2dd2eab0524..5effb7ea36c 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' -describe IssuablesHelper do +describe IssuablesHelper do let(:label) { build_stubbed(:label) } let(:label2) { build_stubbed(:label) } - context 'label tooltip' do + describe '#issuable_labels_tooltip' do it 'returns label text' do expect(issuable_labels_tooltip([label])).to eq(label.title) end @@ -13,4 +13,97 @@ describe IssuablesHelper do expect(issuable_labels_tooltip([label, label2], limit: 1)).to eq("#{label.title}, and 1 more") end end + + describe '#issuables_state_counter_text' do + let(:user) { create(:user) } + + describe 'state text' do + before do + allow(helper).to receive(:issuables_count_for_state).and_return(42) + end + + it 'returns "Open" when state is :opened' do + expect(helper.issuables_state_counter_text(:issues, :opened)). + to eq('Open 42') + end + + it 'returns "Closed" when state is :closed' do + expect(helper.issuables_state_counter_text(:issues, :closed)). + to eq('Closed 42') + end + + it 'returns "Merged" when state is :merged' do + expect(helper.issuables_state_counter_text(:merge_requests, :merged)). + to eq('Merged 42') + end + + it 'returns "All" when state is :all' do + expect(helper.issuables_state_counter_text(:merge_requests, :all)). + to eq('All 42') + end + end + + describe 'counter caching based on issuable type and params', :caching do + let(:params) do + { + 'scope' => 'created-by-me', + 'state' => 'opened', + 'utf8' => '✓', + 'author_id' => '11', + 'assignee_id' => '18', + 'label_name' => ['bug', 'discussion', 'documentation'], + 'milestone_title' => 'v4.0', + 'sort' => 'due_date_asc', + 'namespace_id' => 'gitlab-org', + 'project_id' => 'gitlab-ce', + 'page' => 2 + } + end + + it 'returns the cached value when called for the same issuable type & with the same params' do + expect(helper).to receive(:params).twice.and_return(params) + expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42) + + expect(helper.issuables_state_counter_text(:issues, :opened)). + to eq('Open 42') + + expect(helper).not_to receive(:issuables_count_for_state) + + expect(helper.issuables_state_counter_text(:issues, :opened)). + to eq('Open 42') + end + + describe 'keys not taken in account in the cache key' do + %w[state sort utf8 page].each do |param| + it "does not take in account params['#{param}'] in the cache key" do + expect(helper).to receive(:params).and_return('author_id' => '11', param => 'foo') + expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42) + + expect(helper.issuables_state_counter_text(:issues, :opened)). + to eq('Open 42') + + expect(helper).to receive(:params).and_return('author_id' => '11', param => 'bar') + expect(helper).not_to receive(:issuables_count_for_state) + + expect(helper.issuables_state_counter_text(:issues, :opened)). + to eq('Open 42') + end + end + end + + it 'does not take params order in acount in the cache key' do + expect(helper).to receive(:params).and_return('author_id' => '11', 'state' => 'opened') + expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42) + + expect(helper.issuables_state_counter_text(:issues, :opened)). + to eq('Open 42') + + expect(helper).to receive(:params).and_return('state' => 'opened', 'author_id' => '11') + expect(helper).not_to receive(:issuables_count_for_state) + + expect(helper.issuables_state_counter_text(:issues, :opened)). + to eq('Open 42') + end + end + end end -- cgit v1.2.1 From 56259155d5a0ecfbbafb7319651495582504cfb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 29 Sep 2016 16:55:55 +0200 Subject: Small improvements thanks to Robert's feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../projects/boards/issues_controller.rb | 2 +- app/controllers/projects_controller.rb | 4 +- app/helpers/issuables_helper.rb | 8 +-- lib/api/milestones.rb | 3 +- spec/features/dashboard_issues_spec.rb | 18 ++----- spec/features/issues/filter_by_labels_spec.rb | 6 +-- spec/features/issues/filter_issues_spec.rb | 54 ++++--------------- .../merge_requests/filter_by_milestone_spec.rb | 18 ++----- spec/helpers/issuables_helper_spec.rb | 60 ++++++++++++---------- spec/support/matchers/have_issuable_counts.rb | 21 ++++++++ 10 files changed, 79 insertions(+), 115 deletions(-) create mode 100644 spec/support/matchers/have_issuable_counts.rb diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 9404612a993..4aa7982eab4 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -33,7 +33,7 @@ module Projects def issue @issue ||= - IssuesFinder.new(current_user, project_id: project.id, state: 'all') + IssuesFinder.new(current_user, project_id: project.id) .execute .where(iid: params[:id]) .first! diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c99aeadb5e4..62916270172 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -137,10 +137,10 @@ class ProjectsController < Projects::ApplicationController noteable = case params[:type] when 'Issue' - IssuesFinder.new(current_user, project_id: @project.id, state: 'all'). + IssuesFinder.new(current_user, project_id: @project.id). execute.find_by(iid: params[:type_id]) when 'MergeRequest' - MergeRequestsFinder.new(current_user, project_id: @project.id, state: 'all'). + MergeRequestsFinder.new(current_user, project_id: @project.id). execute.find_by(iid: params[:type_id]) when 'Commit' @project.commit(params[:type_id]) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8aa1ece017c..8c04200fab9 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -137,13 +137,13 @@ module IssuablesHelper issuables_finder.execute.page(1).total_count end - IRRELEVANT_PARAMS_FOR_CACHE_KEY = %w[utf8 sort page] + IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page] private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY def issuables_state_counter_cache_key(issuable_type, state) - opts = params.dup - opts['state'] = state - opts.delete_if { |k, v| IRRELEVANT_PARAMS_FOR_CACHE_KEY.include?(k) } + opts = params.with_indifferent_access + opts[:state] = state + opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY) hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-')) end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index 7a0cb7c99f3..9b73f6826cf 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -108,8 +108,7 @@ module API finder_params = { project_id: user_project.id, - milestone_title: @milestone.title, - state: 'all' + milestone_title: @milestone.title } issues = IssuesFinder.new(current_user, finder_params).execute diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb index 4dccf645454..9b54b5301e5 100644 --- a/spec/features/dashboard_issues_spec.rb +++ b/spec/features/dashboard_issues_spec.rb @@ -21,11 +21,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do click_link 'No Milestone' - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_selector('.issue', count: 1) end @@ -34,11 +30,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do click_link 'Any Milestone' - page.within '.issues-state-filters' do - expect(page).to have_content('Open 2') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 2') - end + expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) expect(page).to have_selector('.issue', count: 2) end @@ -49,11 +41,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do click_link milestone.title end - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_selector('.issue', count: 1) end end diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index a615174c00e..0253629f753 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -83,11 +83,7 @@ feature 'Issue filtering by Labels', feature: true, js: true do end it 'applies the filters' do - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_content "Bugfix2" expect(page).not_to have_content "Feature1" expect(find('.filtered-labels')).to have_content "bug" diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 8fd67acff69..8d19198efd3 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -227,11 +227,7 @@ describe 'Filter issues', feature: true do it 'filters by text and label' do fill_in 'issuable_search', with: 'Bug' - page.within '.issues-state-filters' do - expect(page).to have_content('Open 2') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 2') - end + expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end @@ -242,11 +238,7 @@ describe 'Filter issues', feature: true do end find('.dropdown-menu-close-icon').click - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) page.within '.issues-list' do expect(page).to have_selector('.issue', count: 1) end @@ -255,11 +247,7 @@ describe 'Filter issues', feature: true do it 'filters by text and milestone' do fill_in 'issuable_search', with: 'Bug' - page.within '.issues-state-filters' do - expect(page).to have_content('Open 2') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 2') - end + expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end @@ -269,11 +257,7 @@ describe 'Filter issues', feature: true do click_link '8' end - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) page.within '.issues-list' do expect(page).to have_selector('.issue', count: 1) end @@ -282,11 +266,7 @@ describe 'Filter issues', feature: true do it 'filters by text and assignee' do fill_in 'issuable_search', with: 'Bug' - page.within '.issues-state-filters' do - expect(page).to have_content('Open 2') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 2') - end + expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end @@ -296,11 +276,7 @@ describe 'Filter issues', feature: true do click_link user.name end - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) page.within '.issues-list' do expect(page).to have_selector('.issue', count: 1) end @@ -309,11 +285,7 @@ describe 'Filter issues', feature: true do it 'filters by text and author' do fill_in 'issuable_search', with: 'Bug' - page.within '.issues-state-filters' do - expect(page).to have_content('Open 2') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 2') - end + expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end @@ -323,11 +295,7 @@ describe 'Filter issues', feature: true do click_link user.name end - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) page.within '.issues-list' do expect(page).to have_selector('.issue', count: 1) end @@ -356,11 +324,7 @@ describe 'Filter issues', feature: true do find('.dropdown-menu-close-icon').click wait_for_ajax - page.within '.issues-state-filters' do - expect(page).to have_content('Open 2') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 2') - end + expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2) page.within '.issues-list' do expect(page).to have_selector('.issue', count: 2) end diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb index 3d5d9614fb6..d917d5950ec 100644 --- a/spec/features/merge_requests/filter_by_milestone_spec.rb +++ b/spec/features/merge_requests/filter_by_milestone_spec.rb @@ -17,11 +17,7 @@ feature 'Merge Request filtering by Milestone', feature: true do visit_merge_requests(project) filter_by_milestone(Milestone::None.title) - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_css('.merge-request', count: 1) end @@ -44,11 +40,7 @@ feature 'Merge Request filtering by Milestone', feature: true do visit_merge_requests(project) filter_by_milestone(Milestone::Upcoming.title) - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_css('.merge-request', count: 1) end @@ -71,11 +63,7 @@ feature 'Merge Request filtering by Milestone', feature: true do visit_merge_requests(project) filter_by_milestone(milestone.title) - page.within '.issues-state-filters' do - expect(page).to have_content('Open 1') - expect(page).to have_content('Closed 0') - expect(page).to have_content('All 1') - end + expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1) expect(page).to have_css('.merge-request', count: 1) end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 5effb7ea36c..62cc10f579a 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -46,18 +46,18 @@ describe IssuablesHelper do describe 'counter caching based on issuable type and params', :caching do let(:params) do { - 'scope' => 'created-by-me', - 'state' => 'opened', - 'utf8' => '✓', - 'author_id' => '11', - 'assignee_id' => '18', - 'label_name' => ['bug', 'discussion', 'documentation'], - 'milestone_title' => 'v4.0', - 'sort' => 'due_date_asc', - 'namespace_id' => 'gitlab-org', - 'project_id' => 'gitlab-ce', - 'page' => 2 - } + scope: 'created-by-me', + state: 'opened', + utf8: '✓', + author_id: '11', + assignee_id: '18', + label_name: ['bug', 'discussion', 'documentation'], + milestone_title: 'v4.0', + sort: 'due_date_asc', + namespace_id: 'gitlab-org', + project_id: 'gitlab-ce', + page: 2 + }.with_indifferent_access end it 'returns the cached value when called for the same issuable type & with the same params' do @@ -73,25 +73,33 @@ describe IssuablesHelper do to eq('Open 42') end - describe 'keys not taken in account in the cache key' do - %w[state sort utf8 page].each do |param| - it "does not take in account params['#{param}'] in the cache key" do - expect(helper).to receive(:params).and_return('author_id' => '11', param => 'foo') - expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42) + it 'does not take some keys into account in the cache key' do + expect(helper).to receive(:params).and_return({ + author_id: '11', + state: 'foo', + sort: 'foo', + utf8: 'foo', + page: 'foo' + }.with_indifferent_access) + expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42) - expect(helper.issuables_state_counter_text(:issues, :opened)). - to eq('Open 42') + expect(helper.issuables_state_counter_text(:issues, :opened)). + to eq('Open 42') - expect(helper).to receive(:params).and_return('author_id' => '11', param => 'bar') - expect(helper).not_to receive(:issuables_count_for_state) + expect(helper).to receive(:params).and_return({ + author_id: '11', + state: 'bar', + sort: 'bar', + utf8: 'bar', + page: 'bar' + }.with_indifferent_access) + expect(helper).not_to receive(:issuables_count_for_state) - expect(helper.issuables_state_counter_text(:issues, :opened)). - to eq('Open 42') - end - end + expect(helper.issuables_state_counter_text(:issues, :opened)). + to eq('Open 42') end - it 'does not take params order in acount in the cache key' do + it 'does not take params order into account in the cache key' do expect(helper).to receive(:params).and_return('author_id' => '11', 'state' => 'opened') expect(helper).to receive(:issuables_count_for_state).with(:issues, :opened).and_return(42) diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb new file mode 100644 index 00000000000..02605d6b70e --- /dev/null +++ b/spec/support/matchers/have_issuable_counts.rb @@ -0,0 +1,21 @@ +RSpec::Matchers.define :have_issuable_counts do |opts| + match do |actual| + expected_counts = opts.map do |state, count| + "#{state.to_s.humanize} #{count}" + end + + actual.within '.issues-state-filters' do + expected_counts.each do |expected_count| + expect(actual).to have_content(expected_count) + end + end + end + + description do + "displays the following issuable counts: #{expected_counts.inspect}" + end + + failure_message do + "expected the following issuable counts: #{expected_counts.inspect} to be displayed" + end +end -- cgit v1.2.1 From 5dbf1f487188e73bc5eb556c7b955088ff4676e5 Mon Sep 17 00:00:00 2001 From: Andre Guedes Date: Wed, 21 Sep 2016 09:46:13 -0300 Subject: Use `Module#prepend` instead of `alias_method_chain` --- CHANGELOG | 1 + .../attr_encrypted_no_db_connection.rb | 25 ++++++++++---------- config/initializers/postgresql_limit_fix.rb | 27 +++++++++++----------- lib/banzai/filter/task_list_filter.rb | 12 ++++++---- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 41d8c5b7c8f..60c08a47ed9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -97,6 +97,7 @@ v 8.12.0 - Fix issue with search filter labels not displaying - Reduce contributions calendar data payload (ClemMakesApps) - Show all pipelines for merge requests even from discarded commits !6414 + - Replace `alias_method_chain` with `Module#prepend` - Replace contributions calendar timezone payload with dates (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Enable pipeline events by default !6278 diff --git a/config/initializers/attr_encrypted_no_db_connection.rb b/config/initializers/attr_encrypted_no_db_connection.rb index c668864089b..e007666b852 100644 --- a/config/initializers/attr_encrypted_no_db_connection.rb +++ b/config/initializers/attr_encrypted_no_db_connection.rb @@ -1,20 +1,21 @@ module AttrEncrypted module Adapters module ActiveRecord - def attribute_instance_methods_as_symbols_with_no_db_connection - # Use with_connection so the connection doesn't stay pinned to the thread. - connected = ::ActiveRecord::Base.connection_pool.with_connection(&:active?) rescue false - - if connected - # Call version from AttrEncrypted::Adapters::ActiveRecord - attribute_instance_methods_as_symbols_without_no_db_connection - else - # Call version from AttrEncrypted, i.e., `super` with regards to AttrEncrypted::Adapters::ActiveRecord - AttrEncrypted.instance_method(:attribute_instance_methods_as_symbols).bind(self).call + module DBConnectionQuerier + def attribute_instance_methods_as_symbols + # Use with_connection so the connection doesn't stay pinned to the thread. + connected = ::ActiveRecord::Base.connection_pool.with_connection(&:active?) rescue false + + if connected + # Call version from AttrEncrypted::Adapters::ActiveRecord + super + else + # Call version from AttrEncrypted, i.e., `super` with regards to AttrEncrypted::Adapters::ActiveRecord + AttrEncrypted.instance_method(:attribute_instance_methods_as_symbols).bind(self).call + end end end - - alias_method_chain :attribute_instance_methods_as_symbols, :no_db_connection + prepend DBConnectionQuerier end end end diff --git a/config/initializers/postgresql_limit_fix.rb b/config/initializers/postgresql_limit_fix.rb index 0cb3aaf4d24..4224d857e8a 100644 --- a/config/initializers/postgresql_limit_fix.rb +++ b/config/initializers/postgresql_limit_fix.rb @@ -1,5 +1,19 @@ if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter + module LimitFilter + def add_column(table_name, column_name, type, options = {}) + options.delete(:limit) if type == :text + super(table_name, column_name, type, options) + end + + def change_column(table_name, column_name, type, options = {}) + options.delete(:limit) if type == :text + super(table_name, column_name, type, options) + end + end + + prepend ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::LimitFilter + class TableDefinition def text(*args) options = args.extract_options! @@ -9,18 +23,5 @@ if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) column_names.each { |name| column(name, type, options) } end end - - def add_column_with_limit_filter(table_name, column_name, type, options = {}) - options.delete(:limit) if type == :text - add_column_without_limit_filter(table_name, column_name, type, options) - end - - def change_column_with_limit_filter(table_name, column_name, type, options = {}) - options.delete(:limit) if type == :text - change_column_without_limit_filter(table_name, column_name, type, options) - end - - alias_method_chain :add_column, :limit_filter - alias_method_chain :change_column, :limit_filter end end diff --git a/lib/banzai/filter/task_list_filter.rb b/lib/banzai/filter/task_list_filter.rb index 66608c9859c..4efbcaf5c7f 100644 --- a/lib/banzai/filter/task_list_filter.rb +++ b/lib/banzai/filter/task_list_filter.rb @@ -10,19 +10,21 @@ module Banzai # task_list gem. # # See https://github.com/github/task_list/pull/60 - class TaskListFilter < TaskList::Filter - def add_css_class_with_fix(node, *new_class_names) + module ClassNamesFilter + def add_css_class(node, *new_class_names) if new_class_names.include?('task-list') # Don't add class to all lists return elsif new_class_names.include?('task-list-item') - add_css_class_without_fix(node.parent, 'task-list') + super(node.parent, 'task-list') end - add_css_class_without_fix(node, *new_class_names) + super(node, *new_class_names) end + end - alias_method_chain :add_css_class, :fix + class TaskListFilter < TaskList::Filter + prepend ClassNamesFilter end end end -- cgit v1.2.1 From d16e1184befc0b724a7abccfdbe50b3fc8da4ed9 Mon Sep 17 00:00:00 2001 From: Andre Guedes Date: Thu, 29 Sep 2016 11:09:43 -0300 Subject: Bumped change to 8.13 release on CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 60c08a47ed9..0042858163f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.13.0 (unreleased) - Expose expires_at field when sharing project on API - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Use a ConnectionPool for Rails.cache on Sidekiq servers + - Replace `alias_method_chain` with `Module#prepend` - Only update issuable labels if they have been changed - Revoke button in Applications Settings underlines on hover. - Fix Long commit messages overflow viewport in file tree @@ -97,7 +98,6 @@ v 8.12.0 - Fix issue with search filter labels not displaying - Reduce contributions calendar data payload (ClemMakesApps) - Show all pipelines for merge requests even from discarded commits !6414 - - Replace `alias_method_chain` with `Module#prepend` - Replace contributions calendar timezone payload with dates (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Enable pipeline events by default !6278 -- cgit v1.2.1 From 19b1b8499a55bdb66b7ea793be66c3e253af92ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 30 Sep 2016 12:16:41 +0200 Subject: Update templates for 8.13 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- vendor/gitignore/Erlang.gitignore | 2 +- vendor/gitignore/Global/Ansible.gitignore | 1 + vendor/gitignore/Global/Linux.gitignore | 3 ++ vendor/gitignore/Go.gitignore | 3 ++ vendor/gitignore/Node.gitignore | 3 ++ vendor/gitignore/TeX.gitignore | 3 ++ vendor/gitignore/VisualStudio.gitignore | 8 +++++ vendor/gitlab-ci-yml/.gitlab-ci.yml | 4 +++ vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml | 34 +++++++++++++++++++ vendor/gitlab-ci-yml/Julia.gitlab-ci.yml | 54 +++++++++++++++++++++++++++++++ 10 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 vendor/gitignore/Global/Ansible.gitignore create mode 100644 vendor/gitlab-ci-yml/.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Julia.gitlab-ci.yml diff --git a/vendor/gitignore/Erlang.gitignore b/vendor/gitignore/Erlang.gitignore index 8e46d5a07f8..3826c85736f 100644 --- a/vendor/gitignore/Erlang.gitignore +++ b/vendor/gitignore/Erlang.gitignore @@ -4,7 +4,7 @@ deps *.beam *.plt erl_crash.dump -ebin +ebin/*.beam rel/example_project .concrete/DEV_MODE .rebar diff --git a/vendor/gitignore/Global/Ansible.gitignore b/vendor/gitignore/Global/Ansible.gitignore new file mode 100644 index 00000000000..a8b42eb6eed --- /dev/null +++ b/vendor/gitignore/Global/Ansible.gitignore @@ -0,0 +1 @@ +*.retry diff --git a/vendor/gitignore/Global/Linux.gitignore b/vendor/gitignore/Global/Linux.gitignore index cc9586893b6..b56bf65d855 100644 --- a/vendor/gitignore/Global/Linux.gitignore +++ b/vendor/gitignore/Global/Linux.gitignore @@ -8,3 +8,6 @@ # Linux trash folder which might appear on any partition or disk .Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* diff --git a/vendor/gitignore/Go.gitignore b/vendor/gitignore/Go.gitignore index cd0d5d1e2f4..397a0ed4acb 100644 --- a/vendor/gitignore/Go.gitignore +++ b/vendor/gitignore/Go.gitignore @@ -25,3 +25,6 @@ _testmain.go # Output of the go coverage tool, specifically when used with LiteIDE *.out + +# external packages folder +vendor/ diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore index bf7525f9912..bc7fc55724c 100644 --- a/vendor/gitignore/Node.gitignore +++ b/vendor/gitignore/Node.gitignore @@ -39,3 +39,6 @@ jspm_packages # Optional REPL history .node_repl_history + +# Output of 'npm pack' +*.tgz diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore index 34f999df3e7..f620fad23eb 100644 --- a/vendor/gitignore/TeX.gitignore +++ b/vendor/gitignore/TeX.gitignore @@ -192,3 +192,6 @@ TSWLatexianTemp* # KBibTeX *~[0-9]* + +# auto folder when using emacs and auctex +/auto/* diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index d56f8b53288..1b86e7ec918 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -110,6 +110,10 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover +# Visual Studio code coverage results +*.coverage +*.coveragexml + # NCrunch _NCrunch_* .*crunch*.local.xml @@ -189,6 +193,7 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview +*.jfm *.pfx *.publishsettings node_modules/ @@ -258,3 +263,6 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc + +# Cake - Uncomment if you are using it +# tools/ diff --git a/vendor/gitlab-ci-yml/.gitlab-ci.yml b/vendor/gitlab-ci-yml/.gitlab-ci.yml new file mode 100644 index 00000000000..18b14554887 --- /dev/null +++ b/vendor/gitlab-ci-yml/.gitlab-ci.yml @@ -0,0 +1,4 @@ +image: ruby:2.3-alpine + +test: + script: ruby verify_templates.rb diff --git a/vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml b/vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml new file mode 100644 index 00000000000..263c4c19999 --- /dev/null +++ b/vendor/gitlab-ci-yml/Gradle.gitlab-ci.yml @@ -0,0 +1,34 @@ +# This template uses the java:8 docker image because there isn't any +# official Gradle image at this moment +# +# This is the Gradle build system for JVM applications +# https://gradle.org/ +# https://github.com/gradle/gradle +image: java:8 + +# Make the gradle wrapper executable. This essentially downloads a copy of +# Gradle to build the project with. +# https://docs.gradle.org/current/userguide/gradle_wrapper.html +# It is expected that any modern gradle project has a wrapper +before_script: + - chmod +x gradlew + +# We redirect the gradle user home using -g so that it caches the +# wrapper and dependencies. +# https://docs.gradle.org/current/userguide/gradle_command_line.html +# +# Unfortunately it also caches the build output so +# cleaning removes reminants of any cached builds. +# The assemble task actually builds the project. +# If it fails here, the tests can't run. +build: + stage: build + script: + - ./gradlew -g /cache/.gradle clean assemble + allow_failure: false + +# Use the generated build output to run the tests. +test: + stage: test + script: + - ./gradlew -g /cache./gradle check diff --git a/vendor/gitlab-ci-yml/Julia.gitlab-ci.yml b/vendor/gitlab-ci-yml/Julia.gitlab-ci.yml new file mode 100644 index 00000000000..140cb4635f3 --- /dev/null +++ b/vendor/gitlab-ci-yml/Julia.gitlab-ci.yml @@ -0,0 +1,54 @@ +# An example .gitlab-ci.yml file to test (and optionally report the coverage +# results of) your [Julia][1] packages. Please refer to the [documentation][2] +# for more information about package development in Julia. +# +# Here, it is assumed that your Julia package is named `MyPackage`. Change it to +# whatever name you have given to your package. +# +# [1]: http://julialang.org/ +# [2]: http://julia.readthedocs.org/ + +# Below is the template to run your tests in Julia +.test_template: &test_definition + # Uncomment below if you would like to run the tests on specific references + # only, such as the branches `master`, `development`, etc. + # only: + # - master + # - development + script: + # Let's run the tests. Substitute `coverage = false` below, if you do not + # want coverage results. + - /opt/julia/bin/julia -e 'Pkg.clone(pwd()); Pkg.test("MyPackage", + coverage = true)' + # Comment out below if you do not want coverage results. + - /opt/julia/bin/julia -e 'Pkg.add("Coverage"); cd(Pkg.dir("MyPackage")); + using Coverage; cl, tl = get_summary(process_folder()); + println("(", cl/tl*100, "%) covered")' + +# Name a test and select an appropriate image. +test:0.4.6: + image: julialang/julia:v0.4.6 + <<: *test_definition + +# Maybe you would like to test your package against the development branch: +test:0.5.0-dev: + image: julialang/julia:v0.5.0-dev + # ... allowing for failures, since we are testing against the development + # branch: + allow_failure: true + <<: *test_definition + +# REMARK: Do not forget to enable the coverage feature for your project, if you +# are using code coverage reporting above. This can be done by +# +# - Navigating to the `CI/CD Pipelines` settings of your project, +# - Copying and pasting the default `Simplecov` regex example provided, i.e., +# `\(\d+.\d+\%\) covered` in the `test coverage parsing` textfield. +# +# WARNING: This template is using the `julialang/julia` images from [Docker +# Hub][3]. One can use custom Julia images and/or the official ones found +# in the same place. However, care must be taken to correctly locate the binary +# file (`/opt/julia/bin/julia` above), which is usually given on the image's +# description page. +# +# [3]: http://hub.docker.com/ -- cgit v1.2.1 From 375072aa279f8e89a095b957005ae03ee1b0a7ff Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 9 Sep 2016 10:51:44 +0200 Subject: Add missing values to linter --- app/views/ci/lints/_create.html.haml | 5 ++++- lib/ci/gitlab_ci_yaml_processor.rb | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml index f7875e68b7e..59be8bbad81 100644 --- a/app/views/ci/lints/_create.html.haml +++ b/app/views/ci/lints/_create.html.haml @@ -21,7 +21,7 @@ %br %b Tag list: - = build[:tags] + = build[:tag_list] %br %b Refs only: = build[:only] && build[:only].join(", ") @@ -29,6 +29,9 @@ %b Refs except: = build[:except] && build[:except].join(", ") %br + %b Environment: + = build[:environment] + %br %b When: = build[:when] - if build[:allow_failure] diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 0369e80312a..b61388d6ea7 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -59,6 +59,8 @@ module Ci tag_list: job[:tags] || [], name: job[:name].to_s, allow_failure: job[:allow_failure] || false, + only: job[:only], + except: job[:except], when: job[:when] || 'on_success', environment: job[:environment_name], yaml_variables: yaml_variables(name), -- cgit v1.2.1 From b13502ef825ec6f105ab47a29e7d397636c4d03b Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 9 Sep 2016 14:43:43 +0200 Subject: Add test for linter values visibility --- app/views/ci/lints/_create.html.haml | 2 +- spec/views/ci/lints/show.html.haml_spec.rb | 33 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 spec/views/ci/lints/show.html.haml_spec.rb diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml index 59be8bbad81..733c5d0b7fe 100644 --- a/app/views/ci/lints/_create.html.haml +++ b/app/views/ci/lints/_create.html.haml @@ -21,7 +21,7 @@ %br %b Tag list: - = build[:tag_list] + = build[:tag_list] && build[:tag_list].join(", ") %br %b Refs only: = build[:only] && build[:only].join(", ") diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb new file mode 100644 index 00000000000..f1d91eab356 --- /dev/null +++ b/spec/views/ci/lints/show.html.haml_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'ci/lints/show' do + let(:content) do + { build_template: { + script: './build.sh', + tags: ['dotnet'], + only: ['test@dude/repo'], + except: ['deploy'], + environment: 'testing' + } + } + end + let(:config_processor) { Ci::GitlabCiYamlProcessor.new(YAML.dump(content)) } + + context 'when content is valid' do + before do + assign(:status, true) + assign(:builds, config_processor.builds) + assign(:stages, config_processor.stages) + end + + it 'shows correct values' do + render + + expect(rendered).to have_content('Tag list: dotnet') + expect(rendered).to have_content('Refs only: test@dude/repo') + expect(rendered).to have_content('Refs except: deploy') + expect(rendered).to have_content('Environment: testing') + expect(rendered).to have_content('When: on_success') + end + end +end -- cgit v1.2.1 From 3efbf92e34eb8328817c64fda7ea73357128a61d Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 12 Sep 2016 08:47:35 +0200 Subject: Improve tests grammar --- spec/views/ci/lints/show.html.haml_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb index f1d91eab356..be41134d12a 100644 --- a/spec/views/ci/lints/show.html.haml_spec.rb +++ b/spec/views/ci/lints/show.html.haml_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe 'ci/lints/show' do let(:content) do - { build_template: { + { + build_template: { script: './build.sh', tags: ['dotnet'], only: ['test@dude/repo'], @@ -11,16 +12,17 @@ describe 'ci/lints/show' do } } end + let(:config_processor) { Ci::GitlabCiYamlProcessor.new(YAML.dump(content)) } - context 'when content is valid' do + context 'when the content is valid' do before do assign(:status, true) assign(:builds, config_processor.builds) assign(:stages, config_processor.stages) end - it 'shows correct values' do + it 'shows the correct values' do render expect(rendered).to have_content('Tag list: dotnet') -- cgit v1.2.1 From 6fa77ab8af22ea694d3ed2df32663a22ee3a6193 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 12 Sep 2016 08:52:04 +0200 Subject: Add CHANGELOG --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 41d8c5b7c8f..da2ee1fec7e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -252,6 +252,10 @@ v 8.11.6 - Restore SSH Key title auto-population behavior. !6186 - Fix DB schema to match latest migration. !6256 - Exclude some pending or inactivated rows in Member scopes. + - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) + +v 8.11.6 (unreleased) + - Fix an error where we were unable to create a CommitStatus for running state v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 -- cgit v1.2.1 From cd556253974dd0d4b86f92a5797ddf83474bea7d Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 12 Sep 2016 16:45:46 +0200 Subject: Workaround for Build attributes --- app/services/ci/create_pipeline_builds_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/ci/create_pipeline_builds_service.rb b/app/services/ci/create_pipeline_builds_service.rb index 005014fa1de..c7a624f38ce 100644 --- a/app/services/ci/create_pipeline_builds_service.rb +++ b/app/services/ci/create_pipeline_builds_service.rb @@ -21,6 +21,8 @@ module Ci user: current_user, trigger_request: trigger_request ) + # OPTIMIZE: We copy over all attributes of Job into Build, therefore this workaround + build_attributes = build_attributes.except(:only, :except) pipeline.builds.create(build_attributes) end -- cgit v1.2.1 From 52ebeb5a89927058e1eba5bda777852011f001eb Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 13 Sep 2016 12:26:44 +0200 Subject: Build attributes with hash, fix gitlab yaml processor tests --- app/services/ci/create_pipeline_builds_service.rb | 17 ++++++++++---- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 28 +++++++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/app/services/ci/create_pipeline_builds_service.rb b/app/services/ci/create_pipeline_builds_service.rb index c7a624f38ce..3c12b806438 100644 --- a/app/services/ci/create_pipeline_builds_service.rb +++ b/app/services/ci/create_pipeline_builds_service.rb @@ -13,16 +13,25 @@ module Ci private def create_build(build_attributes) - build_attributes = build_attributes.merge( + build_attributes = { + stage_idx: build_attributes[:stage_idx], + stage: build_attributes[:stage], + commands: build_attributes[:commands], + tag_list: build_attributes[:tag_list], + name: build_attributes[:name], + when: build_attributes[:when], + allow_failure: build_attributes[:allow_failure], + environment: build_attributes[:environment], + yaml_variables: build_attributes[:yaml_variables], + options: build_attributes[:options], pipeline: pipeline, project: pipeline.project, ref: pipeline.ref, tag: pipeline.tag, user: current_user, trigger_request: trigger_request - ) - # OPTIMIZE: We copy over all attributes of Job into Build, therefore this workaround - build_attributes = build_attributes.except(:only, :except) + } + pipeline.builds.create(build_attributes) end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 6dedd25e9d3..317259f0c27 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -26,7 +26,9 @@ module Ci allow_failure: false, when: "on_success", environment: nil, - yaml_variables: [] + yaml_variables: [], + only: nil, + except: nil }) end @@ -443,7 +445,9 @@ module Ci allow_failure: false, when: "on_success", environment: nil, - yaml_variables: [] + yaml_variables: [], + only: nil, + except: nil }) end @@ -471,7 +475,9 @@ module Ci allow_failure: false, when: "on_success", environment: nil, - yaml_variables: [] + yaml_variables: [], + only: nil, + except: nil }) end end @@ -716,7 +722,9 @@ module Ci when: "on_success", allow_failure: false, environment: nil, - yaml_variables: [] + yaml_variables: [], + only: nil, + except: nil }) end @@ -859,7 +867,9 @@ module Ci when: "on_success", allow_failure: false, environment: nil, - yaml_variables: [] + yaml_variables: [], + only: nil, + except: nil }) end end @@ -904,7 +914,9 @@ module Ci when: "on_success", allow_failure: false, environment: nil, - yaml_variables: [] + yaml_variables: [], + only: nil, + except: nil }) expect(subject.second).to eq({ stage: "build", @@ -916,7 +928,9 @@ module Ci when: "on_success", allow_failure: false, environment: nil, - yaml_variables: [] + yaml_variables: [], + only: nil, + except: nil }) end end -- cgit v1.2.1 From e26953bc4bc7c9522258f201b175c60fd6c0b2a2 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 16 Sep 2016 13:23:32 +0200 Subject: Build attributes with slice method --- CHANGELOG | 3 --- app/services/ci/create_pipeline_builds_service.rb | 19 +++++++------------ 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index da2ee1fec7e..2e12dd31d32 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -254,9 +254,6 @@ v 8.11.6 - Exclude some pending or inactivated rows in Member scopes. - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) -v 8.11.6 (unreleased) - - Fix an error where we were unable to create a CommitStatus for running state - v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 - Fix member expiration date picker after update. !6184 diff --git a/app/services/ci/create_pipeline_builds_service.rb b/app/services/ci/create_pipeline_builds_service.rb index 3c12b806438..3fc707b1e14 100644 --- a/app/services/ci/create_pipeline_builds_service.rb +++ b/app/services/ci/create_pipeline_builds_service.rb @@ -13,24 +13,19 @@ module Ci private def create_build(build_attributes) - build_attributes = { - stage_idx: build_attributes[:stage_idx], - stage: build_attributes[:stage], - commands: build_attributes[:commands], - tag_list: build_attributes[:tag_list], - name: build_attributes[:name], - when: build_attributes[:when], - allow_failure: build_attributes[:allow_failure], - environment: build_attributes[:environment], - yaml_variables: build_attributes[:yaml_variables], - options: build_attributes[:options], + build_attributes = build_attributes.slice( + :stage_idx, :stage, :commands, :tag_list, :name, :when, :allow_failure, + :environment, :yaml_variables, :options + ) + + build_attributes = build_attributes.merge( pipeline: pipeline, project: pipeline.project, ref: pipeline.ref, tag: pipeline.tag, user: current_user, trigger_request: trigger_request - } + ) pipeline.builds.create(build_attributes) end -- cgit v1.2.1 From 7dfb204ef9d343888e2fd8327c5a5348b98a76ce Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 20 Sep 2016 15:19:55 +0200 Subject: Expose jobs to view --- app/controllers/ci/lints_controller.rb | 1 + app/services/ci/create_pipeline_builds_service.rb | 6 ----- app/views/ci/lints/_create.html.haml | 6 ++--- lib/ci/gitlab_ci_yaml_processor.rb | 4 +--- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 28 ++++++----------------- spec/views/ci/lints/show.html.haml_spec.rb | 16 +++++++++++++ 6 files changed, 28 insertions(+), 33 deletions(-) diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index e06d12cfce1..78012960252 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -14,6 +14,7 @@ module Ci @config_processor = Ci::GitlabCiYamlProcessor.new(@content) @stages = @config_processor.stages @builds = @config_processor.builds + @jobs = @config_processor.jobs end rescue @error = 'Undefined error' diff --git a/app/services/ci/create_pipeline_builds_service.rb b/app/services/ci/create_pipeline_builds_service.rb index 3fc707b1e14..005014fa1de 100644 --- a/app/services/ci/create_pipeline_builds_service.rb +++ b/app/services/ci/create_pipeline_builds_service.rb @@ -13,11 +13,6 @@ module Ci private def create_build(build_attributes) - build_attributes = build_attributes.slice( - :stage_idx, :stage, :commands, :tag_list, :name, :when, :allow_failure, - :environment, :yaml_variables, :options - ) - build_attributes = build_attributes.merge( pipeline: pipeline, project: pipeline.project, @@ -26,7 +21,6 @@ module Ci user: current_user, trigger_request: trigger_request ) - pipeline.builds.create(build_attributes) end diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml index 733c5d0b7fe..342259226a2 100644 --- a/app/views/ci/lints/_create.html.haml +++ b/app/views/ci/lints/_create.html.haml @@ -21,13 +21,13 @@ %br %b Tag list: - = build[:tag_list] && build[:tag_list].join(", ") + = build[:tag_list] && build[:tag_list].to_a.join(", ") %br %b Refs only: - = build[:only] && build[:only].join(", ") + = @jobs[build[:name].to_sym][:only] && @jobs[build[:name].to_sym][:only].to_a.join(", ") %br %b Refs except: - = build[:except] && build[:except].join(", ") + = @jobs[build[:name].to_sym][:except] && @jobs[build[:name].to_sym][:except].to_a.join(", ") %br %b Environment: = build[:environment] diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index b61388d6ea7..2fd1fced65c 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -4,7 +4,7 @@ module Ci include Gitlab::Ci::Config::Node::LegacyValidationHelpers - attr_reader :path, :cache, :stages + attr_reader :path, :cache, :stages, :jobs def initialize(config, path = nil) @ci_config = Gitlab::Ci::Config.new(config) @@ -59,8 +59,6 @@ module Ci tag_list: job[:tags] || [], name: job[:name].to_s, allow_failure: job[:allow_failure] || false, - only: job[:only], - except: job[:except], when: job[:when] || 'on_success', environment: job[:environment_name], yaml_variables: yaml_variables(name), diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 317259f0c27..6dedd25e9d3 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -26,9 +26,7 @@ module Ci allow_failure: false, when: "on_success", environment: nil, - yaml_variables: [], - only: nil, - except: nil + yaml_variables: [] }) end @@ -445,9 +443,7 @@ module Ci allow_failure: false, when: "on_success", environment: nil, - yaml_variables: [], - only: nil, - except: nil + yaml_variables: [] }) end @@ -475,9 +471,7 @@ module Ci allow_failure: false, when: "on_success", environment: nil, - yaml_variables: [], - only: nil, - except: nil + yaml_variables: [] }) end end @@ -722,9 +716,7 @@ module Ci when: "on_success", allow_failure: false, environment: nil, - yaml_variables: [], - only: nil, - except: nil + yaml_variables: [] }) end @@ -867,9 +859,7 @@ module Ci when: "on_success", allow_failure: false, environment: nil, - yaml_variables: [], - only: nil, - except: nil + yaml_variables: [] }) end end @@ -914,9 +904,7 @@ module Ci when: "on_success", allow_failure: false, environment: nil, - yaml_variables: [], - only: nil, - except: nil + yaml_variables: [] }) expect(subject.second).to eq({ stage: "build", @@ -928,9 +916,7 @@ module Ci when: "on_success", allow_failure: false, environment: nil, - yaml_variables: [], - only: nil, - except: nil + yaml_variables: [] }) end end diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb index be41134d12a..620fb0e4821 100644 --- a/spec/views/ci/lints/show.html.haml_spec.rb +++ b/spec/views/ci/lints/show.html.haml_spec.rb @@ -20,6 +20,7 @@ describe 'ci/lints/show' do assign(:status, true) assign(:builds, config_processor.builds) assign(:stages, config_processor.stages) + assign(:jobs, config_processor.jobs) end it 'shows the correct values' do @@ -32,4 +33,19 @@ describe 'ci/lints/show' do expect(rendered).to have_content('When: on_success') end end + + context 'when the content is invalid' do + before do + assign(:status, false) + assign(:error, 'Undefined error') + end + + it 'shows error message' do + render + + expect(rendered).to have_content('Status: syntax is incorrec') + expect(rendered).to have_content('Error: Undefined error') + expect(rendered).not_to have_content('Tag list:') + end + end end -- cgit v1.2.1 From 3ef515f3f111e7b470dbb29ba1c75479aee8dc7b Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 20 Sep 2016 17:28:54 +0200 Subject: Refactor fields in view --- app/views/ci/lints/_create.html.haml | 6 +++--- spec/views/ci/lints/show.html.haml_spec.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml index 342259226a2..d5c21c6dffe 100644 --- a/app/views/ci/lints/_create.html.haml +++ b/app/views/ci/lints/_create.html.haml @@ -21,13 +21,13 @@ %br %b Tag list: - = build[:tag_list] && build[:tag_list].to_a.join(", ") + = build[:tag_list].to_a.join(", ") %br %b Refs only: - = @jobs[build[:name].to_sym][:only] && @jobs[build[:name].to_sym][:only].to_a.join(", ") + = @jobs[build[:name].to_sym][:only].to_a.join(", ") %br %b Refs except: - = @jobs[build[:name].to_sym][:except] && @jobs[build[:name].to_sym][:except].to_a.join(", ") + = @jobs[build[:name].to_sym][:except].to_a.join(", ") %br %b Environment: = build[:environment] diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb index 620fb0e4821..793b747e7eb 100644 --- a/spec/views/ci/lints/show.html.haml_spec.rb +++ b/spec/views/ci/lints/show.html.haml_spec.rb @@ -43,7 +43,7 @@ describe 'ci/lints/show' do it 'shows error message' do render - expect(rendered).to have_content('Status: syntax is incorrec') + expect(rendered).to have_content('Status: syntax is incorrect') expect(rendered).to have_content('Error: Undefined error') expect(rendered).not_to have_content('Tag list:') end -- cgit v1.2.1 From b26931cccf31f39f508899751f6ef618f0bcce23 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 23 Sep 2016 21:59:42 +0200 Subject: Update CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2e12dd31d32..87b657c9531 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) v 8.12.4 (unreleased) @@ -252,7 +253,6 @@ v 8.11.6 - Restore SSH Key title auto-population behavior. !6186 - Fix DB schema to match latest migration. !6256 - Exclude some pending or inactivated rows in Member scopes. - - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 -- cgit v1.2.1 From e26ea861575d575f5b1daf051c3864831bbcba97 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 30 Sep 2016 12:23:36 +0200 Subject: Update CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 87b657c9531..a6da1324359 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.13.0 (unreleased) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Only update issuable labels if they have been changed - Revoke button in Applications Settings underlines on hover. + - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) @@ -23,7 +24,6 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) - - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) v 8.12.4 (unreleased) -- cgit v1.2.1 From 52ee85e7bf35d372285d70cc93016854774839b1 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 30 Sep 2016 12:27:43 +0200 Subject: Initialize Redis pool in single-threaded context This side-steps the need for mutexes and whatnot. --- config/initializers/7_redis.rb | 3 +++ lib/gitlab/redis.rb | 24 ++++++------------------ 2 files changed, 9 insertions(+), 18 deletions(-) create mode 100644 config/initializers/7_redis.rb diff --git a/config/initializers/7_redis.rb b/config/initializers/7_redis.rb new file mode 100644 index 00000000000..ae2ca258df1 --- /dev/null +++ b/config/initializers/7_redis.rb @@ -0,0 +1,3 @@ +# Make sure we initialize a Redis connection pool before Sidekiq starts +# multi-threaded execution. +Gitlab::Redis.with { nil } diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index 69c4ef721d5..3faab937726 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -11,13 +11,6 @@ module Gitlab DEFAULT_REDIS_URL = 'redis://localhost:6379' CONFIG_FILE = File.expand_path('../../config/resque.yml', __dir__) - # To be thread-safe we must be careful when writing the class instance - # variables @_raw_config and @pool. Because @pool depends on @_raw_config we need two - # mutexes to prevent deadlock. - RAW_CONFIG_MUTEX = Mutex.new - POOL_MUTEX = Mutex.new - private_constant :RAW_CONFIG_MUTEX, :POOL_MUTEX - class << self # Do NOT cache in an instance variable. Result may be mutated by caller. def params @@ -31,24 +24,19 @@ module Gitlab end def with - if @pool.nil? - POOL_MUTEX.synchronize do - @pool = ConnectionPool.new { ::Redis.new(params) } - end - end + @pool ||= ConnectionPool.new { ::Redis.new(params) } @pool.with { |redis| yield redis } end def _raw_config return @_raw_config if defined?(@_raw_config) - RAW_CONFIG_MUTEX.synchronize do - begin - @_raw_config = File.read(CONFIG_FILE).freeze - rescue Errno::ENOENT - @_raw_config = false - end + begin + @_raw_config = File.read(CONFIG_FILE).freeze + rescue Errno::ENOENT + @_raw_config = false end + @_raw_config end end -- cgit v1.2.1 From f3b02b9e6e79f178b92a65b5cd598a4733b570bd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 30 Sep 2016 18:38:36 +0800 Subject: Or we could simply ignore skipped manual jobs --- app/models/concerns/has_status.rb | 2 +- app/services/ci/process_pipeline_service.rb | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 755eadf779a..0fa4df0fb56 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -21,7 +21,7 @@ module HasStatus deduce_status = "(CASE WHEN (#{builds})=(#{created}) THEN 'created' - WHEN (#{builds})=(#{skipped}) THEN 'success' + WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending' WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 36c93dddadb..7adf093557b 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -61,7 +61,12 @@ module Ci end def status_for_prior_stages(index) - pipeline.builds.where('stage_idx < ?', index).latest.status || 'success' + quoted_when = pipeline.builds.connection.quote_column_name('when') + pipeline.builds. + where('stage_idx < ?', index). + # We want to ignore skipped manual jobs + where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). + latest.status || 'success' end def stage_indexes_of_created_builds -- cgit v1.2.1 From cf707081b01943382f53be9b82ac33349ddcf125 Mon Sep 17 00:00:00 2001 From: Nils Brinkmann Date: Fri, 30 Sep 2016 11:11:04 +0000 Subject: Smaller formatting fix --- doc/ci/triggers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index b78422f6d0e..84048f1d25f 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -4,7 +4,7 @@ > **Note**: GitLab 8.12 has a completely redesigned build permissions system. -Read all about the [new model and its implications][../../user/project/new_ci_build_permissions_model.md#build-triggers]. +Read all about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#build-triggers). Triggers can be used to force a rebuild of a specific branch, tag or commit, with an API call. -- cgit v1.2.1 From 9c0b965d817c105565152452a09362468cfda8ca Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 7 Jun 2016 15:18:46 +0200 Subject: Use custom Ruby images to test builds It allows us to remove redundant steps of installing required dependencies for every build. --- .gitlab-ci.yml | 4 ++-- CHANGELOG | 1 + scripts/prepare_build.sh | 15 --------------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a11c4705e82..43307a5e7db 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "ruby:2.3.1" +image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.3" cache: key: "ruby-231" @@ -141,7 +141,7 @@ spinach 9 10: *spinach-knapsack # Execute all testing suites against Ruby 2.1 .ruby-21: &ruby-21 - image: "ruby:2.1" + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.1" <<: *use-db only: - master diff --git a/CHANGELOG b/CHANGELOG index 24c5a0409fc..7c9d69992a2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -882,6 +882,7 @@ v 8.9.0 - Add API endpoint for Sidekiq Metrics !4653 - Refactoring Award Emoji with API support for Issues and MergeRequests - Use Knapsack to evenly distribute tests across multiple nodes + - Use custom Ruby images to test builds (registry.gitlab.com/gitlab-org/gitlab-build-images:*) - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged - Don't allow MRs to be merged when commits were added since the last review / page load - Add DB index on users.state diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index 6e987e7d9c9..1eaafdce389 100755 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -16,21 +16,6 @@ retry() { } if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then - mkdir -p vendor/apt - - # Install phantomjs package - pushd vendor/apt - PHANTOMJS_FILE="phantomjs-$PHANTOMJS_VERSION-linux-x86_64" - if [ ! -d "$PHANTOMJS_FILE" ]; then - curl -q -L "https://s3.amazonaws.com/gitlab-build-helpers/$PHANTOMJS_FILE.tar.bz2" | tar jx - fi - cp "$PHANTOMJS_FILE/bin/phantomjs" "/usr/bin/" - popd - - # Try to install packages - retry 'apt-get update -yqqq; apt-get -o dir::cache::archives="vendor/apt" install -y -qq --force-yes \ - libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client unzip' - cp config/database.yml.mysql config/database.yml sed -i 's/username:.*/username: root/g' config/database.yml sed -i 's/password:.*/password:/g' config/database.yml -- cgit v1.2.1 From dad03a805880711a32dde44e7e8c0f6098386724 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 29 Jun 2016 15:53:36 +0200 Subject: remove unused cache from .gitlab-ci.yml --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 43307a5e7db..ec4547804e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,6 @@ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.3" cache: key: "ruby-231" paths: - - vendor/apt - vendor/ruby variables: @@ -148,7 +147,6 @@ spinach 9 10: *spinach-knapsack cache: key: "ruby21" paths: - - vendor/apt - vendor/ruby .rspec-knapsack-ruby21: &rspec-knapsack-ruby21 -- cgit v1.2.1 From b19dd19b21e37522c4a75e7d39717860aee645aa Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 3 Aug 2016 11:12:24 +0200 Subject: Set CHANGELOG item to right release --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7c9d69992a2..08018e6169c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -140,6 +140,7 @@ v 8.12.0 - Fix issue with slash commands not loading on new issue page - Fix inconsistent background color for filter input field (ClemMakesApps) - Remove prefixes from transition CSS property (ClemMakesApps) + - Use custom Ruby images to test builds (registry.gitlab.com/gitlab-org/gitlab-build-images:*) - Add Sentry logging to API calls - Add BroadcastMessage API - Use 'git update-ref' for safer web commits !6130 @@ -882,7 +883,6 @@ v 8.9.0 - Add API endpoint for Sidekiq Metrics !4653 - Refactoring Award Emoji with API support for Issues and MergeRequests - Use Knapsack to evenly distribute tests across multiple nodes - - Use custom Ruby images to test builds (registry.gitlab.com/gitlab-org/gitlab-build-images:*) - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged - Don't allow MRs to be merged when commits were added since the last review / page load - Add DB index on users.state -- cgit v1.2.1 From a8383ec4f5b9fe3c16ef6e4b6324f3d6591a4480 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 27 Sep 2016 18:50:14 +0200 Subject: Update CI yaml and update CHANGELOG entry --- .gitlab-ci.yml | 4 ++-- CHANGELOG | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ec4547804e6..177b70c0c5e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.3" +image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.3-git-2.7-phantomjs-2.1" cache: key: "ruby-231" @@ -140,7 +140,7 @@ spinach 9 10: *spinach-knapsack # Execute all testing suites against Ruby 2.1 .ruby-21: &ruby-21 - image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.1" + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1" <<: *use-db only: - master diff --git a/CHANGELOG b/CHANGELOG index 08018e6169c..9df7f9746b2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Only update issuable labels if they have been changed - Take filters in account in issuable counters. !6496 + - Use custom Ruby images to test builds (registry.gitlab.com/gitlab-org/gitlab-build-images:*) - Revoke button in Applications Settings underlines on hover. - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? @@ -140,7 +141,6 @@ v 8.12.0 - Fix issue with slash commands not loading on new issue page - Fix inconsistent background color for filter input field (ClemMakesApps) - Remove prefixes from transition CSS property (ClemMakesApps) - - Use custom Ruby images to test builds (registry.gitlab.com/gitlab-org/gitlab-build-images:*) - Add Sentry logging to API calls - Add BroadcastMessage API - Use 'git update-ref' for safer web commits !6130 -- cgit v1.2.1 From d90166172e91dbd63062c24d61e6566e57079a45 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 30 Sep 2016 11:38:56 +0200 Subject: Cache gems in CI on tags --- .gitlab-ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3f3873e57c1..899e0b6b105 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -332,3 +332,16 @@ pages: - public only: - master + +# Insurance in case a gem needed by one of our releases gets yanked from +# rubygems.org in the future. +cache gems: + only: + - tags + variables: + SETUP_DB: "false" + script: + - bundle package --all --all-platforms + artifacts: + paths: + - vendor/cache -- cgit v1.2.1 From 29fa93b09c105571f1cc3ebf545a3a43fe41fd92 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 30 Sep 2016 13:45:03 +0200 Subject: Move the images over to dev.gitlab.org --- .gitlab-ci.yml | 4 ++-- CHANGELOG | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 177b70c0c5e..5d2fad03f19 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.3-git-2.7-phantomjs-2.1" +image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3-git-2.7-phantomjs-2.1" cache: key: "ruby-231" @@ -140,7 +140,7 @@ spinach 9 10: *spinach-knapsack # Execute all testing suites against Ruby 2.1 .ruby-21: &ruby-21 - image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1" + image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.1-git-2.7-phantomjs-2.1" <<: *use-db only: - master diff --git a/CHANGELOG b/CHANGELOG index 9df7f9746b2..d95bd633b8d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,7 +14,7 @@ v 8.13.0 (unreleased) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Only update issuable labels if they have been changed - Take filters in account in issuable counters. !6496 - - Use custom Ruby images to test builds (registry.gitlab.com/gitlab-org/gitlab-build-images:*) + - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) - Revoke button in Applications Settings underlines on hover. - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? -- cgit v1.2.1 From 8a443ca735b29df5ae685c4217550ba16e0985d6 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Clorichel Date: Fri, 30 Sep 2016 13:50:23 +0000 Subject: updated missed indentation --- app/views/projects/empty.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 4065b27156f..7a39064adc5 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -24,7 +24,7 @@ = link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore'), class: 'underlined-link' to this project. %p - You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected. + You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected. - if can?(current_user, :push_code, @project) %div{ class: container_class } -- cgit v1.2.1 From 958d9f11e80633f7120a782900fe1f78b3dbebea Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 29 Sep 2016 17:17:22 +0200 Subject: fix export project file permissions issue --- CHANGELOG | 3 +++ lib/gitlab/import_export/command_line_util.rb | 9 ++++++++- lib/gitlab/import_export/file_importer.rb | 2 +- lib/gitlab/import_export/project_tree_saver.rb | 4 +++- lib/gitlab/import_export/repo_restorer.rb | 2 +- lib/gitlab/import_export/repo_saver.rb | 2 +- lib/gitlab/import_export/version_saver.rb | 4 +++- lib/gitlab/import_export/wiki_repo_saver.rb | 2 +- spec/features/projects/import_export/export_file_spec.rb | 2 ++ spec/support/import_export/export_file_helper.rb | 4 ++++ 10 files changed, 27 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 64918b89264..c243920283c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,9 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Speed-up group milestones show page +v 8.12.4 (unreleased) + - Set GitLab project exported file permissions to owner only + v 8.12.2 (unreleased) - Fix Import/Export not recognising correctly the imported services. - Respect the fork_project permission when forking projects diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index e522a0fc8f6..f00c7460e82 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -1,6 +1,8 @@ module Gitlab module ImportExport module CommandLineUtil + DEFAULT_MODE = 0700 + def tar_czf(archive:, dir:) tar_with_options(archive: archive, dir: dir, options: 'czf') end @@ -21,6 +23,11 @@ module Gitlab execute(%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args) end + def mkdir_p(path) + FileUtils.mkdir_p(path, mode: DEFAULT_MODE) + FileUtils.chmod(DEFAULT_MODE, path) + end + private def tar_with_options(archive:, dir:, options:) @@ -45,7 +52,7 @@ module Gitlab # if we are copying files, create the destination folder destination_folder = File.file?(source) ? File.dirname(destination) : destination - FileUtils.mkdir_p(destination_folder) + mkdir_p(destination_folder) FileUtils.copy_entry(source, destination) true end diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb index eca6e5b6d51..113895ba22c 100644 --- a/lib/gitlab/import_export/file_importer.rb +++ b/lib/gitlab/import_export/file_importer.rb @@ -15,7 +15,7 @@ module Gitlab end def import - FileUtils.mkdir_p(@shared.export_path) + mkdir_p(@shared.export_path) wait_for_archived_file do decompress_archive diff --git a/lib/gitlab/import_export/project_tree_saver.rb b/lib/gitlab/import_export/project_tree_saver.rb index 9153088e966..2fbf437ec26 100644 --- a/lib/gitlab/import_export/project_tree_saver.rb +++ b/lib/gitlab/import_export/project_tree_saver.rb @@ -1,6 +1,8 @@ module Gitlab module ImportExport class ProjectTreeSaver + include Gitlab::ImportExport::CommandLineUtil + attr_reader :full_path def initialize(project:, shared:) @@ -10,7 +12,7 @@ module Gitlab end def save - FileUtils.mkdir_p(@shared.export_path) + mkdir_p(@shared.export_path) File.write(full_path, project_json_tree) true diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb index d1e33ea8678..48a9a6fa5e2 100644 --- a/lib/gitlab/import_export/repo_restorer.rb +++ b/lib/gitlab/import_export/repo_restorer.rb @@ -12,7 +12,7 @@ module Gitlab def restore return true unless File.exist?(@path_to_bundle) - FileUtils.mkdir_p(path_to_repo) + mkdir_p(path_to_repo) git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle) && repo_restore_hooks rescue => e diff --git a/lib/gitlab/import_export/repo_saver.rb b/lib/gitlab/import_export/repo_saver.rb index 331e14021e6..a7028a32570 100644 --- a/lib/gitlab/import_export/repo_saver.rb +++ b/lib/gitlab/import_export/repo_saver.rb @@ -20,7 +20,7 @@ module Gitlab private def bundle_to_disk - FileUtils.mkdir_p(@shared.export_path) + mkdir_p(@shared.export_path) git_bundle(repo_path: path_to_repo, bundle_path: @full_path) rescue => e @shared.error(e) diff --git a/lib/gitlab/import_export/version_saver.rb b/lib/gitlab/import_export/version_saver.rb index 9b642d740b7..7cf88298642 100644 --- a/lib/gitlab/import_export/version_saver.rb +++ b/lib/gitlab/import_export/version_saver.rb @@ -1,12 +1,14 @@ module Gitlab module ImportExport class VersionSaver + include Gitlab::ImportExport::CommandLineUtil + def initialize(shared:) @shared = shared end def save - FileUtils.mkdir_p(@shared.export_path) + mkdir_p(@shared.export_path) File.write(version_file, Gitlab::ImportExport.version, mode: 'w') rescue => e diff --git a/lib/gitlab/import_export/wiki_repo_saver.rb b/lib/gitlab/import_export/wiki_repo_saver.rb index 6107420e4dd..1e6722a7bba 100644 --- a/lib/gitlab/import_export/wiki_repo_saver.rb +++ b/lib/gitlab/import_export/wiki_repo_saver.rb @@ -9,7 +9,7 @@ module Gitlab end def bundle_to_disk(full_path) - FileUtils.mkdir_p(@shared.export_path) + mkdir_p(@shared.export_path) git_bundle(repo_path: path_to_repo, bundle_path: full_path) rescue => e @shared.error(e) diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index 27c986c5187..52d08982c7a 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -47,6 +47,8 @@ feature 'Import/Export - project export integration test', feature: true, js: tr expect(page).to have_content('Download export') + expect(file_permissions(project.export_path)).to eq(0700) + in_directory_with_expanded_export(project) do |exit_status, tmpdir| expect(exit_status).to eq(0) diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb index be0772d6a4a..1b0a4583f5c 100644 --- a/spec/support/import_export/export_file_helper.rb +++ b/spec/support/import_export/export_file_helper.rb @@ -130,4 +130,8 @@ module ExportFileHelper (parsed_model_attributes - parent.keys - excluded_attributes).empty? end + + def file_permissions(file) + File.stat(file).mode & 0777 + end end -- cgit v1.2.1 From 7895910e6c4da83214c145c493c4e67f9e1c62e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 30 Sep 2016 16:36:21 +0200 Subject: Update the "Update Ruby" part since we still support Ruby 2.1 for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/update/8.11-to-8.12.md | 4 +++- doc/update/8.12-to-8.13.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 076696f565b..ee9fb1a2a68 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -22,7 +22,9 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ### 3. Update Ruby -If you are you running Ruby 2.1.x, you do not _need_ to upgrade Ruby yet, but you should note that support for 2.1.x is deprecated and we will require 2.3.x in 8.13. It's strongly recommended that you upgrade as soon as possible. +We will continue supporting Ruby < 2.3 for the time being but we recommend you +upgrade to Ruby 2.3 if you're running a source installation, as this is the same +version that ships with our Omnibus package. You can check which version you are running with `ruby -v`. diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index 75b9c57edd3..d61c76673f7 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -22,7 +22,9 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ### 3. Update Ruby -If you are you running Ruby 2.1.x, you do not _need_ to upgrade Ruby yet, but you should note that support for 2.1.x is deprecated and we will require 2.3.x in 8.13. It's strongly recommended that you upgrade as soon as possible. +We will continue supporting Ruby < 2.3 for the time being but we recommend you +upgrade to Ruby 2.3 if you're running a source installation, as this is the same +version that ships with our Omnibus package. You can check which version you are running with `ruby -v`. -- cgit v1.2.1 From 39b0b9412e079c15d6a14bf3d08558ff6c0746aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 30 Sep 2016 16:37:42 +0200 Subject: Update the "Update Ruby" part for the 8.10 -> 8.11 as well MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/update/8.10-to-8.11.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md index b058f8e2a03..b24d338e3e0 100644 --- a/doc/update/8.10-to-8.11.md +++ b/doc/update/8.10-to-8.11.md @@ -22,7 +22,9 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ### 3. Update Ruby -If you are you running Ruby 2.1.x, you do not _need_ to upgrade Ruby yet, but you should note that support for 2.1.x is deprecated and we will require 2.3.x in 8.13. It's strongly recommended that you upgrade as soon as possible. +We will continue supporting Ruby < 2.3 for the time being but we recommend you +upgrade to Ruby 2.3 if you're running a source installation, as this is the same +version that ships with our Omnibus package. You can check which version you are running with `ruby -v`. -- cgit v1.2.1 From 0c0b6f438ee5ac12f9975fb1c3b1dc93f6447015 Mon Sep 17 00:00:00 2001 From: Matthew Dodds Date: Fri, 30 Sep 2016 11:00:03 -0400 Subject: Refactor url_helpers for system note service and remove duplication of logic in spec --- app/services/system_note_service.rb | 13 ++++++++----- spec/services/system_note_service_spec.rb | 18 +++++------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 8da1af67ee6..5ccaa5275b7 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -21,10 +21,10 @@ module SystemNoteService total_count = new_commits.length + existing_commits.length commits_text = "#{total_count} commit".pluralize(total_count) - body = "[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})\n\n" - body << "Added #{commits_text}:\n\n" + body = "Added #{commits_text}:\n\n" body << existing_commit_summary(noteable, existing_commits, oldrev) body << new_commit_summary(new_commits).join("\n") + body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -255,8 +255,7 @@ module SystemNoteService # # "Started branch `201-issue-branch-button`" def new_issue_branch(issue, project, author, branch) - h = Gitlab::Routing.url_helpers - link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) + link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) body = "Started branch [`#{branch}`](#{link})" create_note(noteable: issue, project: project, author: author, note: body) @@ -468,10 +467,14 @@ module SystemNoteService Rack::Utils.escape_html(text) end + def url_helpers + @url_helpers ||= Gitlab::Routing.url_helpers + end + def diff_comparison_url(merge_request, project, oldrev) diff_id = merge_request.merge_request_diff.id - Gitlab::Routing.url_helpers.diffs_namespace_project_merge_request_url( + url_helpers.diffs_namespace_project_merge_request_url( project.namespace, project, merge_request.iid, diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 16e345501d9..b16840a1238 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -41,34 +41,26 @@ describe SystemNoteService, services: true do let(:note_lines) { subject.note.split("\n").reject(&:blank?) } describe 'comparison diff link line' do - it 'adds the comparison link' do - link = Gitlab::Routing.url_helpers.diffs_namespace_project_merge_request_url( - project.namespace, - project, - noteable.iid, - diff_id: noteable.merge_request_diff.id, - start_sha: oldrev - ) - - expect(note_lines[0]).to eq "[Compare with previous version](#{link})" + it 'adds the comparison text' do + expect(note_lines[2]).to match "[Compare with previous version]" end end context 'without existing commits' do it 'adds a message header' do - expect(note_lines[1]).to eq "Added #{new_commits.size} commits:" + expect(note_lines[0]).to eq "Added #{new_commits.size} commits:" end it 'adds a message line for each commit' do new_commits.each_with_index do |commit, i| # Skip the header - expect(note_lines[i + 2]).to eq "* #{commit.short_id} - #{commit.title}" + expect(note_lines[i + 1]).to eq "* #{commit.short_id} - #{commit.title}" end end end describe 'summary line for existing commits' do - let(:summary_line) { note_lines[2] } + let(:summary_line) { note_lines[1] } context 'with one existing commit' do let(:old_commits) { [noteable.commits.last] } -- cgit v1.2.1 From 1d85ea9ed47993743bc4ba70c2c6dfbf77335322 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 30 Sep 2016 14:26:45 -0500 Subject: Fix lint-doc error --- CHANGELOG | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7cb8cd75879..ed534b825aa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,12 +34,10 @@ v 8.12.4 (unreleased) v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -v 8.12.2 - - Fix "Create project" button layout when visibility options are restricted - v 8.12.2 (unreleased) - Fix Import/Export not recognising correctly the imported services. - Fix snippets pagination + - Fix "Create project" button layout when visibility options are restricted - Fix List-Unsubscribe header in emails - Fix IssuesController#show degradation including project on loaded notes - Fix an issue with the "Commits" section of the cycle analytics summary. !6513 -- cgit v1.2.1 From edcc3285ee785986ef3799576eb67259af555a40 Mon Sep 17 00:00:00 2001 From: Ashley Dumaine Date: Thu, 29 Sep 2016 12:39:59 -0400 Subject: Center the header logo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/assets/stylesheets/framework/header.scss | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index ed534b825aa..aaf91e565a0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Use gitlab-shell v3.6.2 (GIT TRACE logging) + - Fix centering of custom header logos (Ashley Dumaine) - AbstractReferenceFilter caches project_refs on RequestStore when active - Speed-up group milestones show page - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index d4a030f7f7a..7470457b1e7 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -112,11 +112,15 @@ header { .header-logo { position: absolute; left: 50%; - margin-left: -18px; top: 7px; transition-duration: .3s; z-index: 999; + #logo { + position: relative; + left: -50%; + } + svg, img { height: 36px; } -- cgit v1.2.1 From 1e0d1c0546d6af2b65314a69783aa187971b95a9 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 30 Sep 2016 09:30:53 -0500 Subject: Fix logo positioning on mobile --- app/assets/stylesheets/framework/header.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 7470457b1e7..c748f856501 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -130,8 +130,12 @@ header { } @media (max-width: $screen-xs-max) { - right: 25px; + right: 20px; left: auto; + + #logo { + left: auto; + } } } -- cgit v1.2.1 From e81f89ee5a36dcda5680a459d29256899a6f7e00 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 29 Sep 2016 15:13:25 +0100 Subject: Added CHANGELOG entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ed534b825aa..83e1f82e963 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.13.0 (unreleased) - Fix permission for setting an issue's due date - Expose expires_at field when sharing project on API - Allow the Koding integration to be configured through the API + - Added soft wrap button to repository file/blob editor - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Use a ConnectionPool for Rails.cache on Sidekiq servers -- cgit v1.2.1 From 61afa65d4e5e1ea908d90e45364fbb4e0bcfeb56 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 2 Sep 2016 19:42:42 +0100 Subject: Added soft wrap logic and button to editor Added tests Added awesomeeeeee icons --- app/assets/javascripts/blob_edit/edit_blob.js | 43 ++++++++++++++ app/assets/stylesheets/pages/editor.scss | 1 + app/helpers/appearances_helper.rb | 5 +- app/views/projects/blob/_editor.html.haml | 4 ++ app/views/shared/icons/_icon_no_wrap.svg.erb | 3 + app/views/shared/icons/_icon_soft_wrap.svg.erb | 3 + .../projects/files/edit_file_soft_wrap_spec.rb | 67 ++++++++++++++++++++++ 7 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 app/views/shared/icons/_icon_no_wrap.svg.erb create mode 100644 app/views/shared/icons/_icon_soft_wrap.svg.erb create mode 100644 spec/features/projects/files/edit_file_soft_wrap_spec.rb diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index b846bab0424..8f4a2c7ae84 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -22,6 +22,7 @@ // submitted textarea })(this)); this.initModePanesAndLinks(); + this.initSoftWrap(); new BlobLicenseSelectors({ editor: this.editor }); @@ -50,6 +51,7 @@ this.$editModePanes.hide(); currentPane.fadeIn(200); if (paneId === "#preview") { + this.$toggleButton.hide(); return $.post(currentLink.data("preview-url"), { content: this.editor.getValue() }, function(response) { @@ -57,10 +59,51 @@ return currentPane.syntaxHighlight(); }); } else { + this.$toggleButton.show(); return this.editor.focus(); } }; + EditBlob.prototype.initSoftWrap = function() { + this.isExplicitySelected = false + this.$filePathInput = $('#file_path, #file_name'); + this.$toggleButton = $('.soft-wrap-toggle'); + this.$toggleText = $('span', this.$toggleButton); + this.$noWrapIcon = $('.no-wrap-icon', this.$toggleButton); + this.$softWrapIcon = $('.soft-wrap-icon', this.$toggleButton); + this.checkFilePathIsCode(); + this.$filePathInput.on('keyup', _.debounce(this.checkFilePathIsCode.bind(this), 300)); + this.$toggleButton.on('click', this.clickSoftWrapButton.bind(this)); + }; + + EditBlob.prototype.toggleSoftWrap = function(forceToggle) { + if (_.isBoolean(forceToggle)) { + this.isSoftWrapped = forceToggle; + } else { + this.isSoftWrapped = !this.isSoftWrapped; + } + if(this.isSoftWrapped) { + this.$toggleText.text('No wrap'); + this.$noWrapIcon.removeClass('hidden'); + this.$softWrapIcon.addClass('hidden'); + } else { + this.$toggleText.text('Soft wrap'); + this.$softWrapIcon.removeClass('hidden'); + this.$noWrapIcon.addClass('hidden'); + } + this.editor.getSession().setUseWrapMode(this.isSoftWrapped); + }; + + EditBlob.prototype.checkFilePathIsCode = function() { + var isNotCode = /^(.*?\.(txt|md)|[^.]*?)$/i.test(this.$filePathInput.val()); + if (!this.isExplicitySelected) this.toggleSoftWrap(isNotCode); + }; + + EditBlob.prototype.clickSoftWrapButton = function() { + if (!this.isExplicitySelected) this.isExplicitySelected = true; + this.toggleSoftWrap(); + }; + return EditBlob; })(); diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 1aa4e06d975..ffd6089c100 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -59,6 +59,7 @@ } .encoding-selector, + .soft-wrap-toggle, .license-selector, .gitignore-selector, .gitlab-ci-yml-selector { diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index de13e7a1fc2..1e8ddb9d423 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -31,9 +31,10 @@ module AppearancesHelper end end - def custom_icon(icon_name, size: 16) + def custom_icon(icon_name, opts = {}) + opts[:size] = 16 unless opts[:size] # We can't simply do the below, because there are some .erb SVGs. # File.read(Rails.root.join("app/views/shared/icons/_#{icon_name}.svg")).html_safe - render "shared/icons/#{icon_name}.svg", size: size + render "shared/icons/#{icon_name}.svg", opts end end diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 0237e152b54..f225b1be59e 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -21,6 +21,10 @@ = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) + = button_tag class: 'soft-wrap-toggle btn', type: 'button' do + = custom_icon('icon_soft_wrap', klass: 'soft-wrap-icon') + = custom_icon('icon_no_wrap', klass: 'no-wrap-icon hidden') + %span Soft wrap .encoding-selector = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' diff --git a/app/views/shared/icons/_icon_no_wrap.svg.erb b/app/views/shared/icons/_icon_no_wrap.svg.erb new file mode 100644 index 00000000000..e4c44091ddd --- /dev/null +++ b/app/views/shared/icons/_icon_no_wrap.svg.erb @@ -0,0 +1,3 @@ + + + diff --git a/app/views/shared/icons/_icon_soft_wrap.svg.erb b/app/views/shared/icons/_icon_soft_wrap.svg.erb new file mode 100644 index 00000000000..d9613b0c3d7 --- /dev/null +++ b/app/views/shared/icons/_icon_soft_wrap.svg.erb @@ -0,0 +1,3 @@ + + + diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb new file mode 100644 index 00000000000..7ad90b4a89c --- /dev/null +++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +feature 'User uses soft wrap whilst editing file', feature: true, js: true do + before do + user = create(:user) + project = create(:project) + project.team << [user, :master] + login_as user + visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'test_file-name') + editor = find('.file-editor.code') + editor.click + editor.send_keys 'Touch water with paw then recoil in horror chase dog then + run away chase the pig around the house eat owner\'s food, and knock + dish off table head butt cant eat out of my own dish. Cat is love, cat + is life rub face on everything poop on grasses so meow. Playing with + balls of wool flee in terror at cucumber discovered on floor run in + circles tuxedo cats always looking dapper, but attack dog, run away + and pretend to be victim so all of a sudden cat goes crazy, yet chase + laser. Make muffins sit in window and stare ooo, a bird! yum lick yarn + hanging out of own butt jump off balcony, onto stranger\'s head yet + chase laser. Purr for no reason stare at ceiling hola te quiero.'.squish + end + + let(:toggle_button) { find('.soft-wrap-toggle') } + + scenario 'user clicks the "No wrap" button and then "Soft wrap" button' do + wrapped_content_width = get_content_width + toggle_button.click + expect(toggle_button).to have_content 'Soft wrap' + unwrapped_content_width = get_content_width + expect(unwrapped_content_width).to be > wrapped_content_width + + toggle_button.click + expect(toggle_button).to have_content 'No wrap' + expect(get_content_width).to be < unwrapped_content_width + end + + scenario 'user adds a ".js" extension and then changes to a ".md" extension' do + wrapped_content_width = get_content_width + + fill_in 'file_name', with: 'test_file-name.js' + expect(toggle_button).to have_content 'Soft wrap' + unwrapped_content_width = get_content_width + expect(unwrapped_content_width).to be > wrapped_content_width + + fill_in 'file_name', with: 'test_file-name.md' + expect(toggle_button).to have_content 'No wrap' + expect(get_content_width).to be < unwrapped_content_width + end + + scenario 'user clicks "No wrap" and then changes to a ".md" extension' do + wrapped_content_width = get_content_width + + toggle_button.click + expect(toggle_button).to have_content 'Soft wrap' + unwrapped_content_width = get_content_width + expect(unwrapped_content_width).to be > wrapped_content_width + + fill_in 'file_name', with: 'test_file-name.md' + expect(toggle_button).to have_content 'Soft wrap' + expect(unwrapped_content_width).to be == get_content_width + end + + def get_content_width + find('.ace_content')[:style].slice!(/width: \d+/).slice!(/\d+/) + end +end -- cgit v1.2.1 From 70f7d2446a4ba9e301d0f7151a95a52e127f2423 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 27 Sep 2016 22:08:05 +0100 Subject: Review changes Updated tests --- app/assets/javascripts/blob_edit/edit_blob.js | 38 +++------------------- app/assets/stylesheets/pages/editor.scss | 18 ++++++++++ app/helpers/appearances_helper.rb | 5 ++- app/views/projects/blob/_editor.html.haml | 9 +++-- app/views/shared/icons/_icon_no_wrap.svg | 3 ++ app/views/shared/icons/_icon_no_wrap.svg.erb | 3 -- app/views/shared/icons/_icon_soft_wrap.svg | 3 ++ app/views/shared/icons/_icon_soft_wrap.svg.erb | 3 -- .../projects/files/edit_file_soft_wrap_spec.rb | 32 ++---------------- 9 files changed, 40 insertions(+), 74 deletions(-) create mode 100644 app/views/shared/icons/_icon_no_wrap.svg delete mode 100644 app/views/shared/icons/_icon_no_wrap.svg.erb create mode 100644 app/views/shared/icons/_icon_soft_wrap.svg delete mode 100644 app/views/shared/icons/_icon_soft_wrap.svg.erb diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 8f4a2c7ae84..de6cdd851be 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -65,45 +65,17 @@ }; EditBlob.prototype.initSoftWrap = function() { - this.isExplicitySelected = false - this.$filePathInput = $('#file_path, #file_name'); + this.isSoftWrapped = false; this.$toggleButton = $('.soft-wrap-toggle'); - this.$toggleText = $('span', this.$toggleButton); - this.$noWrapIcon = $('.no-wrap-icon', this.$toggleButton); - this.$softWrapIcon = $('.soft-wrap-icon', this.$toggleButton); - this.checkFilePathIsCode(); - this.$filePathInput.on('keyup', _.debounce(this.checkFilePathIsCode.bind(this), 300)); - this.$toggleButton.on('click', this.clickSoftWrapButton.bind(this)); + this.$toggleButton.on('click', this.toggleSoftWrap.bind(this)); }; - EditBlob.prototype.toggleSoftWrap = function(forceToggle) { - if (_.isBoolean(forceToggle)) { - this.isSoftWrapped = forceToggle; - } else { - this.isSoftWrapped = !this.isSoftWrapped; - } - if(this.isSoftWrapped) { - this.$toggleText.text('No wrap'); - this.$noWrapIcon.removeClass('hidden'); - this.$softWrapIcon.addClass('hidden'); - } else { - this.$toggleText.text('Soft wrap'); - this.$softWrapIcon.removeClass('hidden'); - this.$noWrapIcon.addClass('hidden'); - } + EditBlob.prototype.toggleSoftWrap = function(e) { + this.isSoftWrapped = !this.isSoftWrapped; + this.$toggleButton.toggleClass('soft-wrap-active', this.isSoftWrapped); this.editor.getSession().setUseWrapMode(this.isSoftWrapped); }; - EditBlob.prototype.checkFilePathIsCode = function() { - var isNotCode = /^(.*?\.(txt|md)|[^.]*?)$/i.test(this.$filePathInput.val()); - if (!this.isExplicitySelected) this.toggleSoftWrap(isNotCode); - }; - - EditBlob.prototype.clickSoftWrapButton = function() { - if (!this.isExplicitySelected) this.isExplicitySelected = true; - this.toggleSoftWrap(); - }; - return EditBlob; })(); diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index ffd6089c100..e1304335271 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -68,6 +68,24 @@ font-family: $regular_font; } + .soft-wrap-toggle { + margin: 0 $btn-side-margin; + .soft-wrap { + display: block; + } + .no-wrap { + display: none; + } + &.soft-wrap-active { + .soft-wrap { + display: none; + } + .no-wrap { + display: block; + } + } + } + .gitignore-selector, .license-selector, .gitlab-ci-yml-selector { .dropdown { line-height: 21px; diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index 1e8ddb9d423..de13e7a1fc2 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -31,10 +31,9 @@ module AppearancesHelper end end - def custom_icon(icon_name, opts = {}) - opts[:size] = 16 unless opts[:size] + def custom_icon(icon_name, size: 16) # We can't simply do the below, because there are some .erb SVGs. # File.read(Rails.root.join("app/views/shared/icons/_#{icon_name}.svg")).html_safe - render "shared/icons/#{icon_name}.svg", opts + render "shared/icons/#{icon_name}.svg", size: size end end diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index f225b1be59e..d4f59764a70 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -22,9 +22,12 @@ .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) = button_tag class: 'soft-wrap-toggle btn', type: 'button' do - = custom_icon('icon_soft_wrap', klass: 'soft-wrap-icon') - = custom_icon('icon_no_wrap', klass: 'no-wrap-icon hidden') - %span Soft wrap + %span.no-wrap + = custom_icon('icon_no_wrap') + No wrap + %span.soft-wrap + = custom_icon('icon_soft_wrap') + Soft wrap .encoding-selector = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' diff --git a/app/views/shared/icons/_icon_no_wrap.svg b/app/views/shared/icons/_icon_no_wrap.svg new file mode 100644 index 00000000000..fe34cada002 --- /dev/null +++ b/app/views/shared/icons/_icon_no_wrap.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/views/shared/icons/_icon_no_wrap.svg.erb b/app/views/shared/icons/_icon_no_wrap.svg.erb deleted file mode 100644 index e4c44091ddd..00000000000 --- a/app/views/shared/icons/_icon_no_wrap.svg.erb +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/views/shared/icons/_icon_soft_wrap.svg b/app/views/shared/icons/_icon_soft_wrap.svg new file mode 100644 index 00000000000..ea27a2024b1 --- /dev/null +++ b/app/views/shared/icons/_icon_soft_wrap.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/views/shared/icons/_icon_soft_wrap.svg.erb b/app/views/shared/icons/_icon_soft_wrap.svg.erb deleted file mode 100644 index d9613b0c3d7..00000000000 --- a/app/views/shared/icons/_icon_soft_wrap.svg.erb +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb index 7ad90b4a89c..012befa7990 100644 --- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb +++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb @@ -23,42 +23,16 @@ feature 'User uses soft wrap whilst editing file', feature: true, js: true do let(:toggle_button) { find('.soft-wrap-toggle') } - scenario 'user clicks the "No wrap" button and then "Soft wrap" button' do + scenario 'user clicks the "Soft wrap" button and then "No wrap" button' do wrapped_content_width = get_content_width - toggle_button.click - expect(toggle_button).to have_content 'Soft wrap' - unwrapped_content_width = get_content_width - expect(unwrapped_content_width).to be > wrapped_content_width - toggle_button.click expect(toggle_button).to have_content 'No wrap' - expect(get_content_width).to be < unwrapped_content_width - end - - scenario 'user adds a ".js" extension and then changes to a ".md" extension' do - wrapped_content_width = get_content_width - - fill_in 'file_name', with: 'test_file-name.js' - expect(toggle_button).to have_content 'Soft wrap' unwrapped_content_width = get_content_width - expect(unwrapped_content_width).to be > wrapped_content_width - - fill_in 'file_name', with: 'test_file-name.md' - expect(toggle_button).to have_content 'No wrap' - expect(get_content_width).to be < unwrapped_content_width - end - - scenario 'user clicks "No wrap" and then changes to a ".md" extension' do - wrapped_content_width = get_content_width + expect(unwrapped_content_width).to be < wrapped_content_width toggle_button.click expect(toggle_button).to have_content 'Soft wrap' - unwrapped_content_width = get_content_width - expect(unwrapped_content_width).to be > wrapped_content_width - - fill_in 'file_name', with: 'test_file-name.md' - expect(toggle_button).to have_content 'Soft wrap' - expect(unwrapped_content_width).to be == get_content_width + expect(get_content_width).to be > unwrapped_content_width end def get_content_width -- cgit v1.2.1 From e6cd68603dada1320cc6bf7b4f75567149e2cc7c Mon Sep 17 00:00:00 2001 From: secustor Date: Fri, 30 Sep 2016 23:03:30 +0200 Subject: Changed Slack service user referencing from full name to username --- CHANGELOG | 1 + app/models/project_services/slack_service/issue_message.rb | 2 +- app/models/project_services/slack_service/merge_message.rb | 2 +- app/models/project_services/slack_service/note_message.rb | 2 +- app/models/project_services/slack_service/wiki_page_message.rb | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 12fcec03514..0c9f2df3698 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) v 8.12.4 (unreleased) diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb index 88e053ec192..cd87a79d0c6 100644 --- a/app/models/project_services/slack_service/issue_message.rb +++ b/app/models/project_services/slack_service/issue_message.rb @@ -11,7 +11,7 @@ class SlackService attr_reader :description def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb index 11fc691022b..b7615c96068 100644 --- a/app/models/project_services/slack_service/merge_message.rb +++ b/app/models/project_services/slack_service/merge_message.rb @@ -10,7 +10,7 @@ class SlackService attr_reader :title def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb index 89ba51cb662..9e84e90f38c 100644 --- a/app/models/project_services/slack_service/note_message.rb +++ b/app/models/project_services/slack_service/note_message.rb @@ -10,7 +10,7 @@ class SlackService def initialize(params) params = HashWithIndifferentAccess.new(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/wiki_page_message.rb b/app/models/project_services/slack_service/wiki_page_message.rb index f336d9e7691..160ca3ac115 100644 --- a/app/models/project_services/slack_service/wiki_page_message.rb +++ b/app/models/project_services/slack_service/wiki_page_message.rb @@ -9,7 +9,7 @@ class SlackService attr_reader :description def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] -- cgit v1.2.1 From 3474668333a5e73f5051d340344fcfffbff1552d Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Wed, 31 Aug 2016 13:33:00 -0400 Subject: Preserve label filters when sorting Closes #20982 https://gitlab.com/gitlab-org/gitlab-ce/issues/20982 --- CHANGELOG | 1 + app/views/shared/_sort_dropdown.html.haml | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed534b825aa..f48bcb7c758 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Replace `alias_method_chain` with `Module#prepend` + - Preserve label filters when sorting !6136 (Joseph Frazier) - Only update issuable labels if they have been changed - Take filters in account in issuable counters. !6496 - Revoke button in Applications Settings underlines on hover. diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 249bce926ce..36bbac6fbf5 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -8,26 +8,26 @@ %b.caret %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li - = link_to page_filter_path(sort: sort_value_priority) do + = link_to page_filter_path(sort: sort_value_priority, label: true) do = sort_title_priority - = link_to page_filter_path(sort: sort_value_recently_created) do + = link_to page_filter_path(sort: sort_value_recently_created, label: true) do = sort_title_recently_created - = link_to page_filter_path(sort: sort_value_oldest_created) do + = link_to page_filter_path(sort: sort_value_oldest_created, label: true) do = sort_title_oldest_created - = link_to page_filter_path(sort: sort_value_recently_updated) do + = link_to page_filter_path(sort: sort_value_recently_updated, label: true) do = sort_title_recently_updated - = link_to page_filter_path(sort: sort_value_oldest_updated) do + = link_to page_filter_path(sort: sort_value_oldest_updated, label: true) do = sort_title_oldest_updated - = link_to page_filter_path(sort: sort_value_milestone_soon) do + = link_to page_filter_path(sort: sort_value_milestone_soon, label: true) do = sort_title_milestone_soon - = link_to page_filter_path(sort: sort_value_milestone_later) do + = link_to page_filter_path(sort: sort_value_milestone_later, label: true) do = sort_title_milestone_later - if controller.controller_name == 'issues' || controller.action_name == 'issues' - = link_to page_filter_path(sort: sort_value_due_date_soon) do + = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do = sort_title_due_date_soon - = link_to page_filter_path(sort: sort_value_due_date_later) do + = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do = sort_title_due_date_later - = link_to page_filter_path(sort: sort_value_upvotes) do + = link_to page_filter_path(sort: sort_value_upvotes, label: true) do = sort_title_upvotes - = link_to page_filter_path(sort: sort_value_downvotes) do + = link_to page_filter_path(sort: sort_value_downvotes, label: true) do = sort_title_downvotes -- cgit v1.2.1 From dd3f6772a8734aa243abc77c2ba0ea999fa2ac7e Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 9 Sep 2016 19:06:20 -0500 Subject: Remove contianer from last push widget --- app/views/projects/_activity.html.haml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index ac50ce83f6a..6b7f8519252 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,13 +1,17 @@ -.nav-block.activity-filter-block - - if current_user - .controls - = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do - %i.fa.fa-rss +- @no_container = true - = render 'shared/event_filter' -.content_list.project-activity{:"data-href" => activity_project_path(@project)} -= spinner +%div{ class: container_class } + .nav-block.activity-filter-block + - if current_user + .controls + = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do + %i.fa.fa-rss + + = render 'shared/event_filter' + + .content_list.project-activity{:"data-href" => activity_project_path(@project)} + = spinner :javascript var activity = new Activities(); -- cgit v1.2.1 From 96a7648f242cfab6649aab86fd4d03e5efe85ba9 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 9 Sep 2016 19:14:49 -0500 Subject: Move create MR banner below subnav --- app/views/projects/tree/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 37d341212af..9864be3562a 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -4,8 +4,8 @@ = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") -= render 'projects/last_push' = render "projects/commits/head" += render 'projects/last_push' %div{ class: container_class } .tree-controls -- cgit v1.2.1 From 502c53b5fe52d7378a72f2363d39c6ed02108261 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 9 Sep 2016 19:17:19 -0500 Subject: Add white background to create MR banner --- app/views/projects/_activity.html.haml | 3 +-- app/views/projects/_last_push.html.haml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 6b7f8519252..d011e51e696 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,12 +1,11 @@ - @no_container = true - %div{ class: container_class } .nav-block.activity-filter-block - if current_user .controls = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do - %i.fa.fa-rss + = icon('rss') = render 'shared/event_filter' diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index 3c6b931f41a..1c3bccccb5c 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -1,6 +1,6 @@ - if event = last_push_event - if show_last_push_widget?(event) - .row-content-block.top-block.clear-block.hidden-xs + .row-content-block.top-block.hidden-xs.white %div{ class: container_class } .event-last-push .event-last-push-text -- cgit v1.2.1 From a2af7790045e22bef15d922e6feae85479b917bc Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 29 Sep 2016 10:54:45 -0500 Subject: Add flash containers and broadcast messages below subnav --- CHANGELOG | 1 + app/assets/stylesheets/framework/flash.scss | 8 ++++ app/views/admin/background_jobs/_head.html.haml | 49 ++++++++++----------- app/views/admin/dashboard/_head.html.haml | 57 +++++++++++++------------ app/views/layouts/_flash.html.haml | 6 ++- app/views/layouts/_page.html.haml | 1 + app/views/projects/commits/_head.html.haml | 45 +++++++++---------- app/views/projects/commits/show.html.haml | 3 +- app/views/projects/graphs/_head.html.haml | 35 +++++++-------- app/views/projects/issues/_head.html.haml | 57 +++++++++++++------------ app/views/projects/issues/index.html.haml | 3 +- app/views/projects/pipelines/_head.html.haml | 49 ++++++++++----------- app/views/projects/wikis/_nav.html.haml | 25 +++++------ 13 files changed, 180 insertions(+), 159 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed534b825aa..963189fa682 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.13.0 (unreleased) - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? + - Add broadcast messages and alerts below sub-nav - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 7ae309ba103..3ac1678dd05 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -3,6 +3,8 @@ margin: 0; margin-bottom: $gl-padding; font-size: 14px; + position: relative; + z-index: 1; .flash-notice { @extend .alert; @@ -33,6 +35,12 @@ } } +.content-wrapper { + .flash-notice .container-fluid { + background-color: transparent; + } +} + @media (max-width: $screen-md-min) { ul.notes { .flash-container.timeline-content { diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml index 107fc25244a..b3530915068 100644 --- a/app/views/admin/background_jobs/_head.html.haml +++ b/app/views/admin/background_jobs/_head.html.haml @@ -1,24 +1,25 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - = nav_link(controller: :system_info) do - = link_to admin_system_info_path, title: 'System Info' do - %span - System Info - = nav_link(controller: :background_jobs) do - = link_to admin_background_jobs_path, title: 'Background Jobs' do - %span - Background Jobs - = nav_link(controller: :logs) do - = link_to admin_logs_path, title: 'Logs' do - %span - Logs - = nav_link(controller: :health_check) do - = link_to admin_health_check_path, title: 'Health Check' do - %span - Health Check - = nav_link(controller: :requests_profiles) do - = link_to admin_requests_profiles_path, title: 'Requests Profiles' do - %span - Requests Profiles += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(controller: :system_info) do + = link_to admin_system_info_path, title: 'System Info' do + %span + System Info + = nav_link(controller: :background_jobs) do + = link_to admin_background_jobs_path, title: 'Background Jobs' do + %span + Background Jobs + = nav_link(controller: :logs) do + = link_to admin_logs_path, title: 'Logs' do + %span + Logs + = nav_link(controller: :health_check) do + = link_to admin_health_check_path, title: 'Health Check' do + %span + Health Check + = nav_link(controller: :requests_profiles) do + = link_to admin_requests_profiles_path, title: 'Requests Profiles' do + %span + Requests Profiles diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml index c91ab4cb946..ec40391a3e3 100644 --- a/app/views/admin/dashboard/_head.html.haml +++ b/app/views/admin/dashboard/_head.html.haml @@ -1,28 +1,29 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - = nav_link(controller: :dashboard, html_options: {class: 'home'}) do - = link_to admin_root_path, title: 'Overview' do - %span - Overview - = nav_link(controller: [:admin, :projects]) do - = link_to admin_namespaces_projects_path, title: 'Projects' do - %span - Projects - = nav_link(controller: :users) do - = link_to admin_users_path, title: 'Users' do - %span - Users - = nav_link(controller: :groups) do - = link_to admin_groups_path, title: 'Groups' do - %span - Groups - = nav_link path: 'builds#index' do - = link_to admin_builds_path, title: 'Builds' do - %span - Builds - = nav_link path: ['runners#index', 'runners#show'] do - = link_to admin_runners_path, title: 'Runners' do - %span - Runners += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(controller: :dashboard, html_options: {class: 'home'}) do + = link_to admin_root_path, title: 'Overview' do + %span + Overview + = nav_link(controller: [:admin, :projects]) do + = link_to admin_namespaces_projects_path, title: 'Projects' do + %span + Projects + = nav_link(controller: :users) do + = link_to admin_users_path, title: 'Users' do + %span + Users + = nav_link(controller: :groups) do + = link_to admin_groups_path, title: 'Groups' do + %span + Groups + = nav_link path: 'builds#index' do + = link_to admin_builds_path, title: 'Builds' do + %span + Builds + = nav_link path: ['runners#index', 'runners#show'] do + = link_to admin_runners_path, title: 'Runners' do + %span + Runners diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index 3612f1ce5c6..baa8036de10 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -1,8 +1,10 @@ .flash-container.flash-container-page - if alert .flash-alert - = alert + %div{ class: (container_class) } + %span= alert - elsif notice .flash-notice - = notice + %div{ class: (container_class) } + %span= notice diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 4f7839a881f..3950b4f53e7 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -20,6 +20,7 @@ .container-fluid = render "layouts/nav/#{nav}" .content-wrapper{ class: "#{layout_nav_class}" } + = yield :sub_nav = render "layouts/broadcast" = render "layouts/flash" = yield :flash_message diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index 4d1ee1c5318..80763ce67ca 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,27 +1,28 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do - = link_to project_files_path(@project) do - Files += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do + = link_to project_files_path(@project) do + Files - = nav_link(controller: [:commit, :commits]) do - = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do - Commits + = nav_link(controller: [:commit, :commits]) do + = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do + Commits - = nav_link(controller: %w(network)) do - = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do - Network + = nav_link(controller: %w(network)) do + = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do + Network - = nav_link(controller: :compare) do - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do - Compare + = nav_link(controller: :compare) do + = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do + Compare - = nav_link(html_options: {class: branches_tab_class}) do - = link_to namespace_project_branches_path(@project.namespace, @project) do - Branches + = nav_link(html_options: {class: branches_tab_class}) do + = link_to namespace_project_branches_path(@project.namespace, @project) do + Branches - = nav_link(controller: [:tags, :releases]) do - = link_to namespace_project_tags_path(@project.namespace, @project) do - Tags + = nav_link(controller: [:tags, :releases]) do + = link_to namespace_project_tags_path(@project.namespace, @project) do + Tags diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 9a44ba94970..876c8002627 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -5,7 +5,8 @@ - if current_user = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") -= render "head" += content_for :sub_nav do + = render "head" %div{ class: container_class } .row-content-block.second-block.content-component-block diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index 082e2cb4d8c..1a62a6a809c 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -1,18 +1,19 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } - - content_for :page_specific_javascripts do - = page_specific_javascript_tag('lib/chart.js') - = page_specific_javascript_tag('graphs/graphs_bundle.js') - = nav_link(action: :show) do - = link_to 'Contributors', namespace_project_graph_path - = nav_link(action: :commits) do - = link_to 'Commits', commits_namespace_project_graph_path - = nav_link(action: :languages) do - = link_to 'Languages', languages_namespace_project_graph_path - - if @project.feature_available?(:builds, current_user) - = nav_link(action: :ci) do - = link_to ci_namespace_project_graph_path do - Continuous Integration + - content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/chart.js') + = page_specific_javascript_tag('graphs/graphs_bundle.js') + = nav_link(action: :show) do + = link_to 'Contributors', namespace_project_graph_path + = nav_link(action: :commits) do + = link_to 'Commits', commits_namespace_project_graph_path + = nav_link(action: :languages) do + = link_to 'Languages', languages_namespace_project_graph_path + - if @project.feature_available?(:builds, current_user) + = nav_link(action: :ci) do + = link_to ci_namespace_project_graph_path do + Continuous Integration diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index f88b33018d0..509b01c548a 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -1,32 +1,33 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) - = nav_link(controller: :issues) do - = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do - %span - Issues += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) + = nav_link(controller: :issues) do + = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do + %span + Issues - = nav_link(controller: :boards) do - = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do - %span - Board + = nav_link(controller: :boards) do + = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do + %span + Board - - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) - = nav_link(controller: :merge_requests) do - = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do - %span - Merge Requests + - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests) + = nav_link(controller: :merge_requests) do + = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do + %span + Merge Requests - - if project_nav_tab? :labels - = nav_link(controller: :labels) do - = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do - %span - Labels + - if project_nav_tab? :labels + = nav_link(controller: :labels) do + = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do + %span + Labels - - if project_nav_tab? :milestones - = nav_link(controller: :milestones) do - = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do - %span - Milestones \ No newline at end of file + - if project_nav_tab? :milestones + = nav_link(controller: :milestones) do + = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + %span + Milestones diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 8da9f2100e9..cc57cfdb342 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -3,7 +3,8 @@ - page_title "Issues" - new_issue_email = @project.new_issue_address(current_user) -= render "projects/issues/head" += content_for :sub_nav do + = render "projects/issues/head" = content_for :meta_tags do - if current_user diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index 5f571499e80..7d421c0e740 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -1,27 +1,28 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - - if project_nav_tab? :pipelines - = nav_link(controller: :pipelines) do - = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do - %span - Pipelines += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + - if project_nav_tab? :pipelines + = nav_link(controller: :pipelines) do + = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do + %span + Pipelines - - if project_nav_tab? :builds - = nav_link(controller: %w(builds)) do - = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do - %span - Builds + - if project_nav_tab? :builds + = nav_link(controller: %w(builds)) do + = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do + %span + Builds - - if project_nav_tab? :environments - = nav_link(controller: %w(environments)) do - = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do - %span - Environments + - if project_nav_tab? :environments + = nav_link(controller: %w(environments)) do + = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do + %span + Environments - - if can?(current_user, :read_cycle_analytics, @project) - = nav_link(controller: %w(cycle_analytics)) do - = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do - %span - Cycle Analytics + - if can?(current_user, :read_cycle_analytics, @project) + = nav_link(controller: %w(cycle_analytics)) do + = link_to project_cycle_analytics_path(@project), title: 'Cycle Analytics' do + %span + Cycle Analytics diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 551a20c1044..09c4411d67e 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,15 +1,16 @@ -.scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %ul{ class: (container_class) } - = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do - = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: (container_class) } + = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do + = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) - = nav_link(path: 'wikis#pages') do - = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) + = nav_link(path: 'wikis#pages') do + = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) - = nav_link(path: 'wikis#git_access') do - = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do - Git Access + = nav_link(path: 'wikis#git_access') do + = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do + Git Access - = render 'projects/wikis/new' + = render 'projects/wikis/new' -- cgit v1.2.1 From 88c1db4a2b365a75a43c46a0bed58ca1f8069408 Mon Sep 17 00:00:00 2001 From: Jared Deckard Date: Fri, 30 Sep 2016 16:38:02 -0500 Subject: Replace talk_list patch with a patched fork --- Gemfile | 2 +- Gemfile.lock | 8 +++++--- lib/banzai/filter/task_list_filter.rb | 22 ---------------------- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/Gemfile b/Gemfile index 76ca6427feb..9ab140aea79 100644 --- a/Gemfile +++ b/Gemfile @@ -100,7 +100,6 @@ gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' -gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'github-markup', '~> 1.4' gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.3.2' @@ -110,6 +109,7 @@ gem 'creole', '~> 0.5.0' gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 1.5.2' gem 'rouge', '~> 2.0' +gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM diff --git a/Gemfile.lock b/Gemfile.lock index f15715a20ff..ca06b21ae65 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -157,6 +157,10 @@ GEM database_cleaner (1.5.3) debug_inspector (0.0.2) debugger-ruby_core_source (1.3.8) + deckar01-task_list (1.0.5) + activesupport (~> 4.0) + html-pipeline + rack (~> 1.0) default_value_for (3.0.2) activerecord (>= 3.2.0, < 5.1) descendants_tracker (0.0.4) @@ -725,8 +729,6 @@ GEM ffi sysexits (1.2.0) systemu (2.6.5) - task_list (1.0.2) - html-pipeline teaspoon (1.1.5) railties (>= 3.2.5, < 6) teaspoon-jasmine (2.2.0) @@ -831,6 +833,7 @@ DEPENDENCIES creole (~> 0.5.0) d3_rails (~> 3.5.0) database_cleaner (~> 1.5.0) + deckar01-task_list (= 1.0.5) default_value_for (~> 3.0.0) devise (~> 4.2) devise-two-factor (~> 3.0.0) @@ -963,7 +966,6 @@ DEPENDENCIES sprockets-es6 (~> 0.9.2) state_machines-activerecord (~> 0.4.0) sys-filesystem (~> 1.1.6) - task_list (~> 1.0.2) teaspoon (~> 1.1.0) teaspoon-jasmine (~> 2.2.0) test_after_commit (~> 0.4.2) diff --git a/lib/banzai/filter/task_list_filter.rb b/lib/banzai/filter/task_list_filter.rb index 4efbcaf5c7f..9fa5f589f3e 100644 --- a/lib/banzai/filter/task_list_filter.rb +++ b/lib/banzai/filter/task_list_filter.rb @@ -2,29 +2,7 @@ require 'task_list/filter' module Banzai module Filter - # Work around a bug in the default TaskList::Filter that adds a `task-list` - # class to every list element, regardless of whether or not it contains a - # task list. - # - # This is a (hopefully) temporary fix, pending a new release of the - # task_list gem. - # - # See https://github.com/github/task_list/pull/60 - module ClassNamesFilter - def add_css_class(node, *new_class_names) - if new_class_names.include?('task-list') - # Don't add class to all lists - return - elsif new_class_names.include?('task-list-item') - super(node.parent, 'task-list') - end - - super(node, *new_class_names) - end - end - class TaskListFilter < TaskList::Filter - prepend ClassNamesFilter end end end -- cgit v1.2.1 From 3808032520953e8f5d98b8cacfe611e22ab1bf28 Mon Sep 17 00:00:00 2001 From: Jared Deckard Date: Fri, 30 Sep 2016 16:39:19 -0500 Subject: Align gem version strings --- Gemfile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 9ab140aea79..d7de92bdcec 100644 --- a/Gemfile +++ b/Gemfile @@ -99,17 +99,17 @@ gem 'unf', '~> 0.1.4' gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing -gem 'html-pipeline', '~> 1.11.0' -gem 'github-markup', '~> 1.4' -gem 'redcarpet', '~> 3.3.3' -gem 'RedCloth', '~> 4.3.2' -gem 'rdoc', '~>3.6' -gem 'org-ruby', '~> 0.9.12' -gem 'creole', '~> 0.5.0' -gem 'wikicloth', '0.8.1' -gem 'asciidoctor', '~> 1.5.2' -gem 'rouge', '~> 2.0' +gem 'html-pipeline', '~> 1.11.0' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' +gem 'github-markup', '~> 1.4' +gem 'redcarpet', '~> 3.3.3' +gem 'RedCloth', '~> 4.3.2' +gem 'rdoc', '~>3.6' +gem 'org-ruby', '~> 0.9.12' +gem 'creole', '~> 0.5.0' +gem 'wikicloth', '0.8.1' +gem 'asciidoctor', '~> 1.5.2' +gem 'rouge', '~> 2.0' # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM -- cgit v1.2.1 From 4a23d7f49150c9292f643b6f1c4fdb3494868de7 Mon Sep 17 00:00:00 2001 From: Jared Deckard Date: Fri, 30 Sep 2016 17:14:39 -0500 Subject: Remove the task_list test since it is patched upstream --- spec/lib/banzai/filter/task_list_filter_spec.rb | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 spec/lib/banzai/filter/task_list_filter_spec.rb diff --git a/spec/lib/banzai/filter/task_list_filter_spec.rb b/spec/lib/banzai/filter/task_list_filter_spec.rb deleted file mode 100644 index 569cbc885c7..00000000000 --- a/spec/lib/banzai/filter/task_list_filter_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe Banzai::Filter::TaskListFilter, lib: true do - include FilterSpecHelper - - it 'does not apply `task-list` class to non-task lists' do - exp = act = %(
  • Item
) - expect(filter(act).to_html).to eq exp - end - - it 'applies `task-list` to single-item task lists' do - act = filter('
  • [ ] Task 1
') - - expect(act.to_html).to start_with '
    ' - end -end -- cgit v1.2.1 From dd01f3a748ea8be2af75ef03ff203efe1b8f5962 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 1 Oct 2016 13:56:41 +0800 Subject: Should use eq because we want orders --- spec/services/ci/process_pipeline_service_spec.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index b6c23002dda..95bdb43db05 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -245,8 +245,7 @@ describe Ci::ProcessPipelineService, services: true do end it 'starts from the second stage' do - expect(builds.map(&:status)).to contain_exactly( - 'skipped', 'pending', 'created') + expect(builds.map(&:status)).to eq(%w[skipped pending created]) end end @@ -258,14 +257,12 @@ describe Ci::ProcessPipelineService, services: true do end it 'skips second stage and continues on third stage' do - expect(builds.map(&:status)).to contain_exactly( - 'pending', 'created', 'created') + expect(builds.map(&:status)).to eq(%w[pending created created]) builds.first.success builds.each(&:reload) - expect(builds.map(&:status)).to contain_exactly( - 'success', 'skipped', 'pending') + expect(builds.map(&:status)).to eq(%w[success skipped pending]) end end -- cgit v1.2.1 From 33bbfd277b94ecdf3cb8ffdfc973bbfb67b54810 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 1 Oct 2016 14:17:54 +0800 Subject: on_failure should also be ignored, and status_sql should also respect this ignorance. Address feedback from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6604#note_16273363 --- app/models/concerns/has_status.rb | 12 +++++++++--- app/services/ci/process_pipeline_service.rb | 6 +----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 0fa4df0fb56..5a6c2725354 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -8,7 +8,7 @@ module HasStatus class_methods do def status_sql - scope = all + scope = exclude_ignored_jobs builds = scope.select('count(*)').to_sql created = scope.created.select('count(*)').to_sql success = scope.success.select('count(*)').to_sql @@ -21,8 +21,8 @@ module HasStatus deduce_status = "(CASE WHEN (#{builds})=(#{created}) THEN 'created' - WHEN (#{builds})=(#{skipped}) THEN 'skipped' - WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' + WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' + WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending' WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running' @@ -68,6 +68,12 @@ module HasStatus scope :skipped, -> { where(status: 'skipped') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } + scope :exclude_ignored_jobs, -> do + quoted_when = connection.quote_column_name('when') + # We want to ignore skipped manual jobs + where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). + where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped') + end end def started? diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 7adf093557b..3e76dc32f01 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -61,11 +61,7 @@ module Ci end def status_for_prior_stages(index) - quoted_when = pipeline.builds.connection.quote_column_name('when') - pipeline.builds. - where('stage_idx < ?', index). - # We want to ignore skipped manual jobs - where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). + pipeline.builds.exclude_ignored_jobs.where('stage_idx < ?', index). latest.status || 'success' end -- cgit v1.2.1 From f0002da09c0f8ae4fb9f732c2e225c9affd98b04 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 1 Oct 2016 14:38:39 +0800 Subject: Add a test for on_failure jobs in the middle --- app/models/concerns/has_status.rb | 1 + spec/services/ci/process_pipeline_service_spec.rb | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 5a6c2725354..582fe8bb1a7 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -72,6 +72,7 @@ module HasStatus quoted_when = connection.quote_column_name('when') # We want to ignore skipped manual jobs where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). + # We want to ignore skipped on_failure where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped') end end diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index 95bdb43db05..ff113efd916 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -230,7 +230,7 @@ describe Ci::ProcessPipelineService, services: true do end end - context 'when there are manual jobs in earlier stages' do + context 'when there are manual/on_failure jobs in earlier stages' do before do builds process_pipeline @@ -266,6 +266,23 @@ describe Ci::ProcessPipelineService, services: true do end end + context 'when second stage has only on_failure jobs' do + let(:builds) do + [create_build('check', 0), + create_build('build', 1, 'on_failure'), + create_build('test', 2)] + end + + it 'skips second stage and continues on third stage' do + expect(builds.map(&:status)).to eq(%w[pending created created]) + + builds.first.success + builds.each(&:reload) + + expect(builds.map(&:status)).to eq(%w[success skipped pending]) + end + end + def create_build(name, stage_idx, when_value = nil) create(:ci_build, :created, -- cgit v1.2.1 From 6d1cd0e2085d8654dbcfeac61825b7b2cf5f1f15 Mon Sep 17 00:00:00 2001 From: Matthew Dodds Date: Sat, 1 Oct 2016 03:32:21 -0400 Subject: Add a spec to verify comparison context inclusion in path when a version is chosen to compare against --- .../merge_requests/merge_request_versions_spec.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb index 22d9e42119d..32b0b65fbac 100644 --- a/spec/features/merge_requests/merge_request_versions_spec.rb +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -1,12 +1,13 @@ require 'spec_helper' feature 'Merge Request versions', js: true, feature: true do + let(:merge_request) { create(:merge_request, importing: true) } + let(:project) { merge_request.source_project } + before do login_as :admin - merge_request = create(:merge_request, importing: true) merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') - project = merge_request.source_project visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) end @@ -47,6 +48,16 @@ feature 'Merge Request versions', js: true, feature: true do end end + it 'has a path with comparison context' do + expect(page).to have_current_path diffs_namespace_project_merge_request_path( + project.namespace, + project, + merge_request.iid, + diff_id: 2, + start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' + ) + end + it 'should have correct value in the compare dropdown' do page.within '.mr-version-compare-dropdown' do expect(page).to have_content 'version 1' -- cgit v1.2.1 From f8349e320770fe8e0498439719d33e0ef80d1d06 Mon Sep 17 00:00:00 2001 From: Matthew Dodds Date: Sat, 1 Oct 2016 03:33:20 -0400 Subject: Remove duplicate test --- spec/features/merge_requests/merge_request_versions_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb index 32b0b65fbac..23cee891bac 100644 --- a/spec/features/merge_requests/merge_request_versions_spec.rb +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -72,10 +72,6 @@ feature 'Merge Request versions', js: true, feature: true do expect(page).to have_content '4 changed files with 15 additions and 6 deletions' end - it 'show diff between new and old version' do - expect(page).to have_content '4 changed files with 15 additions and 6 deletions' - end - it 'should return to latest version when "Show latest version" button is clicked' do click_link 'Show latest version' page.within '.mr-version-dropdown' do -- cgit v1.2.1 From dd088d14bb51e9c0baee013b9d3b21a12a768051 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 13 Sep 2016 20:55:00 +0200 Subject: GrapeDSL for Keys endpoint --- lib/api/keys.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/api/keys.rb b/lib/api/keys.rb index 2b723b79504..767f27ef334 100644 --- a/lib/api/keys.rb +++ b/lib/api/keys.rb @@ -4,10 +4,9 @@ module API before { authenticate! } resource :keys do - # Get single ssh key by id. Only available to admin users. - # - # Example Request: - # GET /keys/:id + desc 'Get single ssh key by id. Only available to admin users' do + success Entities::SSHKeyWithUser + end get ":id" do authenticated_as_admin! -- cgit v1.2.1 From 69aea6b59a44e99e02decbc3b8adada832178bcb Mon Sep 17 00:00:00 2001 From: secustor Date: Sat, 1 Oct 2016 15:34:46 +0200 Subject: adapted tests for usage of the username --- .../project_services/slack_service/issue_message_spec.rb | 6 +++--- .../project_services/slack_service/merge_message_spec.rb | 6 +++--- .../project_services/slack_service/note_message_spec.rb | 10 +++++----- .../project_services/slack_service/push_message_spec.rb | 12 ++++++------ .../project_services/slack_service/wiki_page_message_spec.rb | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb index 0f8889bdf3c..98c36ec088d 100644 --- a/spec/models/project_services/slack_service/issue_message_spec.rb +++ b/spec/models/project_services/slack_service/issue_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::IssueMessage, models: true do { user: { name: 'Test User', - username: 'Test User' + username: 'test.user' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -40,7 +40,7 @@ describe SlackService::IssueMessage, models: true do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - '] Issue opened by Test User') + '] Issue opened by test.user') expect(subject.attachments).to eq([ { title: "#100 Issue title", @@ -60,7 +60,7 @@ describe SlackService::IssueMessage, models: true do it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - '] Issue closed by Test User') + '] Issue closed by test.user') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb index 224c7ceabe8..c5c052d9af1 100644 --- a/spec/models/project_services/slack_service/merge_message_spec.rb +++ b/spec/models/project_services/slack_service/merge_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::MergeMessage, models: true do { user: { name: 'Test User', - username: 'Test User' + username: 'test.user' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -31,7 +31,7 @@ describe SlackService::MergeMessage, models: true do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'Test User opened '\ + 'test.user opened '\ 'in : *Issue title*') expect(subject.attachments).to be_empty end @@ -43,7 +43,7 @@ describe SlackService::MergeMessage, models: true do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'Test User closed '\ + 'test.user closed '\ 'in : *Issue title*') expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb index 41b93f08050..38cfe4ad3e3 100644 --- a/spec/models/project_services/slack_service/note_message_spec.rb +++ b/spec/models/project_services/slack_service/note_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::NoteMessage, models: true do @args = { user: { name: 'Test User', - username: 'username', + username: 'test.user', avatar_url: 'http://fakeavatar' }, project_name: 'project_name', @@ -37,7 +37,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on commits' do message = SlackService::NoteMessage.new(@args) - expect(message.pretext).to eq("Test User commented on " \ + expect(message.pretext).to eq("test.user commented on " \ " in : " \ "*Added a commit message*") expected_attachments = [ @@ -63,7 +63,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on a merge request' do message = SlackService::NoteMessage.new(@args) - expect(message.pretext).to eq("Test User commented on " \ + expect(message.pretext).to eq("test.user commented on " \ " in : " \ "*merge request title*") expected_attachments = [ @@ -90,7 +90,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on an issue' do message = SlackService::NoteMessage.new(@args) expect(message.pretext).to eq( - "Test User commented on " \ + "test.user commented on " \ " in : " \ "*issue title*") expected_attachments = [ @@ -115,7 +115,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on a project snippet' do message = SlackService::NoteMessage.new(@args) - expect(message.pretext).to eq("Test User commented on " \ + expect(message.pretext).to eq("test.user commented on " \ " in : " \ "*snippet title*") expected_attachments = [ diff --git a/spec/models/project_services/slack_service/push_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb index cda9ee670b0..17cd05e24f1 100644 --- a/spec/models/project_services/slack_service/push_message_spec.rb +++ b/spec/models/project_services/slack_service/push_message_spec.rb @@ -9,7 +9,7 @@ describe SlackService::PushMessage, models: true do before: 'before', project_name: 'project_name', ref: 'refs/heads/master', - user_name: 'user_name', + user_name: 'test.user', project_url: 'url' } end @@ -26,7 +26,7 @@ describe SlackService::PushMessage, models: true do it 'returns a message regarding pushes' do expect(subject.pretext).to eq( - 'user_name pushed to branch of '\ + 'test.user pushed to branch of '\ ' ()' ) expect(subject.attachments).to eq([ @@ -46,13 +46,13 @@ describe SlackService::PushMessage, models: true do before: Gitlab::Git::BLANK_SHA, project_name: 'project_name', ref: 'refs/tags/new_tag', - user_name: 'user_name', + user_name: 'test.user', project_url: 'url' } end it 'returns a message regarding pushes' do - expect(subject.pretext).to eq('user_name pushed new tag ' \ + expect(subject.pretext).to eq('test.user pushed new tag ' \ ' to ' \ '') expect(subject.attachments).to be_empty @@ -66,7 +66,7 @@ describe SlackService::PushMessage, models: true do it 'returns a message regarding a new branch' do expect(subject.pretext).to eq( - 'user_name pushed new branch to '\ + 'test.user pushed new branch to '\ '' ) expect(subject.attachments).to be_empty @@ -80,7 +80,7 @@ describe SlackService::PushMessage, models: true do it 'returns a message regarding a removed branch' do expect(subject.pretext).to eq( - 'user_name removed branch master from ' + 'test.user removed branch master from ' ) expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/slack_service/wiki_page_message_spec.rb b/spec/models/project_services/slack_service/wiki_page_message_spec.rb index 13aea0b0600..093911598b0 100644 --- a/spec/models/project_services/slack_service/wiki_page_message_spec.rb +++ b/spec/models/project_services/slack_service/wiki_page_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::WikiPageMessage, models: true do { user: { name: 'Test User', - username: 'Test User' + username: 'test.user' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -25,7 +25,7 @@ describe SlackService::WikiPageMessage, models: true do it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( - 'Test User created in : '\ + 'test.user created in : '\ '*Wiki page title*') end end @@ -35,7 +35,7 @@ describe SlackService::WikiPageMessage, models: true do it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( - 'Test User edited in : '\ + 'test.user edited in : '\ '*Wiki page title*') end end -- cgit v1.2.1 From 89e663122b1c114f9b7566cd42c5bbee4873430a Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Sat, 1 Oct 2016 12:18:44 -0500 Subject: Changed zero padded days to no padded days in date_format --- spec/features/calendar_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index f52682f229c..7fa0c95cae2 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -5,7 +5,9 @@ feature 'Contributions Calendar', js: true, feature: true do let(:contributed_project) { create(:project, :public) } - date_format = '%A %b %d, %Y' + # Ex/ Sunday Jan 1, 2016 + date_format = '%A %b %-d, %Y' + issue_title = 'Bug in old browser' issue_params = { title: issue_title } -- cgit v1.2.1 From cdf44d9fe512398115efeb03112a3b2b7d631f92 Mon Sep 17 00:00:00 2001 From: Luke Howell Date: Fri, 30 Sep 2016 21:54:57 -0500 Subject: Fix page scrolling to top on sidebar toggle Added preventDefault for toggling sidebar off as well as for pinning and unpinning the sidebar. Closes #22851 --- CHANGELOG | 1 + app/assets/javascripts/sidebar.js.es6 | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1cb9b8acf51..c6c0c4559da 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.13.0 (unreleased) - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Fix permission for setting an issue's due date - Expose expires_at field when sharing project on API + - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - Allow the Koding integration to be configured through the API - Added soft wrap button to repository file/blob editor - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6 index 755fac8107b..ead3219bc31 100644 --- a/app/assets/javascripts/sidebar.js.es6 +++ b/app/assets/javascripts/sidebar.js.es6 @@ -34,8 +34,8 @@ $(pageSelector).hasClass(expandedPageClass) ); $(document) - .on('click', sidebarToggleSelector, () => this.toggleSidebar()) - .on('click', pinnedToggleSelector, () => this.togglePinnedState()) + .on('click', sidebarToggleSelector, (e) => this.toggleSidebar(e)) + .on('click', pinnedToggleSelector, (e) => this.togglePinnedState(e)) .on('click', 'html, body', (e) => this.handleClickEvent(e)) .on('page:change', () => this.renderState()); this.renderState(); @@ -47,17 +47,19 @@ const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0; const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0; if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) { - this.toggleSidebar(); + this.toggleSidebar(e); } } } - toggleSidebar() { + toggleSidebar(e) { + e.preventDefault(); this.isExpanded = !this.isExpanded; this.renderState(); } - togglePinnedState() { + togglePinnedState(e) { + e.preventDefault(); this.isPinned = !this.isPinned; if (!this.isPinned) { this.isExpanded = false; -- cgit v1.2.1 From 0968a29fcc5636766c3e3e5f89029302dbe920e9 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 19 Sep 2016 10:17:10 -0500 Subject: Add word-wrap to issue title on issue and milestone boards --- CHANGELOG | 1 + app/assets/stylesheets/pages/boards.scss | 1 + app/assets/stylesheets/pages/milestone.scss | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1cb9b8acf51..b5b58a47b6e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.13.0 (unreleased) - Expose expires_at field when sharing project on API - Allow the Koding integration to be configured through the API - Added soft wrap button to repository file/blob editor + - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Use a ConnectionPool for Rails.cache on Sidekiq servers diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 9c84dceed05..ecc5b24e360 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -197,6 +197,7 @@ lex a { color: inherit; + word-wrap: break-word; } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 6b865730487..8c2ba3ed58c 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -33,6 +33,7 @@ // Issue title span a { color: $gl-text-color; + word-wrap: break-word; } } } -- cgit v1.2.1 From 3d8900c0d403d6fb97c6f24c21b9d50d9c3678d9 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 2 Oct 2016 15:03:11 +0200 Subject: Fix RuboCop failure in app/services/notification_service.rb --- app/services/notification_service.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 2cc0c31d77d..de8049b8e2e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -518,8 +518,13 @@ class NotificationService def close_resource_email(target, project, current_user, method, skip_current_user: true) action = method == :merged_merge_request_email ? "merge" : "close" - recipients = build_recipients(target, project, current_user, action: action, - skip_current_user: skip_current_user) + recipients = build_recipients( + target, + project, + current_user, + action: action, + skip_current_user: skip_current_user + ) recipients.each do |recipient| mailer.send(method, recipient.id, target.id, current_user.id).deliver_later -- cgit v1.2.1 From 1bb23dc6fa2b1ef2367b83d510aa1a4f8a6c7ae6 Mon Sep 17 00:00:00 2001 From: secustor Date: Fri, 30 Sep 2016 23:03:30 +0200 Subject: Changed Slack service user referencing from full name to username --- CHANGELOG | 1 + app/models/project_services/slack_service/issue_message.rb | 2 +- app/models/project_services/slack_service/merge_message.rb | 2 +- app/models/project_services/slack_service/note_message.rb | 2 +- app/models/project_services/slack_service/wiki_page_message.rb | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 92de32d3f71..6c070bdbab9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -33,6 +33,7 @@ v 8.13.0 (unreleased) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) v 8.12.4 (unreleased) diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb index 88e053ec192..cd87a79d0c6 100644 --- a/app/models/project_services/slack_service/issue_message.rb +++ b/app/models/project_services/slack_service/issue_message.rb @@ -11,7 +11,7 @@ class SlackService attr_reader :description def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb index 11fc691022b..b7615c96068 100644 --- a/app/models/project_services/slack_service/merge_message.rb +++ b/app/models/project_services/slack_service/merge_message.rb @@ -10,7 +10,7 @@ class SlackService attr_reader :title def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb index 89ba51cb662..9e84e90f38c 100644 --- a/app/models/project_services/slack_service/note_message.rb +++ b/app/models/project_services/slack_service/note_message.rb @@ -10,7 +10,7 @@ class SlackService def initialize(params) params = HashWithIndifferentAccess.new(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/wiki_page_message.rb b/app/models/project_services/slack_service/wiki_page_message.rb index f336d9e7691..160ca3ac115 100644 --- a/app/models/project_services/slack_service/wiki_page_message.rb +++ b/app/models/project_services/slack_service/wiki_page_message.rb @@ -9,7 +9,7 @@ class SlackService attr_reader :description def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] -- cgit v1.2.1 From 0c623c5c6d5d7c4c9cdf7e7be0e2c77e47ac939b Mon Sep 17 00:00:00 2001 From: secustor Date: Sat, 1 Oct 2016 15:34:46 +0200 Subject: adapted tests for usage of the username --- .../project_services/slack_service/issue_message_spec.rb | 6 +++--- .../project_services/slack_service/merge_message_spec.rb | 6 +++--- .../project_services/slack_service/note_message_spec.rb | 10 +++++----- .../project_services/slack_service/push_message_spec.rb | 12 ++++++------ .../project_services/slack_service/wiki_page_message_spec.rb | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb index 0f8889bdf3c..98c36ec088d 100644 --- a/spec/models/project_services/slack_service/issue_message_spec.rb +++ b/spec/models/project_services/slack_service/issue_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::IssueMessage, models: true do { user: { name: 'Test User', - username: 'Test User' + username: 'test.user' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -40,7 +40,7 @@ describe SlackService::IssueMessage, models: true do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - '] Issue opened by Test User') + '] Issue opened by test.user') expect(subject.attachments).to eq([ { title: "#100 Issue title", @@ -60,7 +60,7 @@ describe SlackService::IssueMessage, models: true do it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - '] Issue closed by Test User') + '] Issue closed by test.user') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb index 224c7ceabe8..c5c052d9af1 100644 --- a/spec/models/project_services/slack_service/merge_message_spec.rb +++ b/spec/models/project_services/slack_service/merge_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::MergeMessage, models: true do { user: { name: 'Test User', - username: 'Test User' + username: 'test.user' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -31,7 +31,7 @@ describe SlackService::MergeMessage, models: true do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'Test User opened '\ + 'test.user opened '\ 'in : *Issue title*') expect(subject.attachments).to be_empty end @@ -43,7 +43,7 @@ describe SlackService::MergeMessage, models: true do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'Test User closed '\ + 'test.user closed '\ 'in : *Issue title*') expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb index 41b93f08050..38cfe4ad3e3 100644 --- a/spec/models/project_services/slack_service/note_message_spec.rb +++ b/spec/models/project_services/slack_service/note_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::NoteMessage, models: true do @args = { user: { name: 'Test User', - username: 'username', + username: 'test.user', avatar_url: 'http://fakeavatar' }, project_name: 'project_name', @@ -37,7 +37,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on commits' do message = SlackService::NoteMessage.new(@args) - expect(message.pretext).to eq("Test User commented on " \ + expect(message.pretext).to eq("test.user commented on " \ " in : " \ "*Added a commit message*") expected_attachments = [ @@ -63,7 +63,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on a merge request' do message = SlackService::NoteMessage.new(@args) - expect(message.pretext).to eq("Test User commented on " \ + expect(message.pretext).to eq("test.user commented on " \ " in : " \ "*merge request title*") expected_attachments = [ @@ -90,7 +90,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on an issue' do message = SlackService::NoteMessage.new(@args) expect(message.pretext).to eq( - "Test User commented on " \ + "test.user commented on " \ " in : " \ "*issue title*") expected_attachments = [ @@ -115,7 +115,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on a project snippet' do message = SlackService::NoteMessage.new(@args) - expect(message.pretext).to eq("Test User commented on " \ + expect(message.pretext).to eq("test.user commented on " \ " in : " \ "*snippet title*") expected_attachments = [ diff --git a/spec/models/project_services/slack_service/push_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb index cda9ee670b0..17cd05e24f1 100644 --- a/spec/models/project_services/slack_service/push_message_spec.rb +++ b/spec/models/project_services/slack_service/push_message_spec.rb @@ -9,7 +9,7 @@ describe SlackService::PushMessage, models: true do before: 'before', project_name: 'project_name', ref: 'refs/heads/master', - user_name: 'user_name', + user_name: 'test.user', project_url: 'url' } end @@ -26,7 +26,7 @@ describe SlackService::PushMessage, models: true do it 'returns a message regarding pushes' do expect(subject.pretext).to eq( - 'user_name pushed to branch of '\ + 'test.user pushed to branch of '\ ' ()' ) expect(subject.attachments).to eq([ @@ -46,13 +46,13 @@ describe SlackService::PushMessage, models: true do before: Gitlab::Git::BLANK_SHA, project_name: 'project_name', ref: 'refs/tags/new_tag', - user_name: 'user_name', + user_name: 'test.user', project_url: 'url' } end it 'returns a message regarding pushes' do - expect(subject.pretext).to eq('user_name pushed new tag ' \ + expect(subject.pretext).to eq('test.user pushed new tag ' \ ' to ' \ '') expect(subject.attachments).to be_empty @@ -66,7 +66,7 @@ describe SlackService::PushMessage, models: true do it 'returns a message regarding a new branch' do expect(subject.pretext).to eq( - 'user_name pushed new branch to '\ + 'test.user pushed new branch to '\ '' ) expect(subject.attachments).to be_empty @@ -80,7 +80,7 @@ describe SlackService::PushMessage, models: true do it 'returns a message regarding a removed branch' do expect(subject.pretext).to eq( - 'user_name removed branch master from ' + 'test.user removed branch master from ' ) expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/slack_service/wiki_page_message_spec.rb b/spec/models/project_services/slack_service/wiki_page_message_spec.rb index 13aea0b0600..093911598b0 100644 --- a/spec/models/project_services/slack_service/wiki_page_message_spec.rb +++ b/spec/models/project_services/slack_service/wiki_page_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::WikiPageMessage, models: true do { user: { name: 'Test User', - username: 'Test User' + username: 'test.user' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -25,7 +25,7 @@ describe SlackService::WikiPageMessage, models: true do it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( - 'Test User created in : '\ + 'test.user created in : '\ '*Wiki page title*') end end @@ -35,7 +35,7 @@ describe SlackService::WikiPageMessage, models: true do it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( - 'Test User edited in : '\ + 'test.user edited in : '\ '*Wiki page title*') end end -- cgit v1.2.1 From 267a96f3d42c1cdd38fc1424fe2e53ede8bd9950 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Sun, 2 Oct 2016 12:32:30 +0100 Subject: Link to the "What requires downtime?" page from the Migration Style Guide [ci skip] --- doc/development/migration_style_guide.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 295eae0a88e..61b0fbc89c9 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -9,10 +9,10 @@ a big burden for most organizations. For this reason it is important that your migrations are written carefully, can be applied online and adhere to the style guide below. Migrations should not require GitLab installations to be taken offline unless -_absolutely_ necessary. If a migration requires downtime this should be -clearly mentioned during the review process as well as being documented in the -monthly release post. For more information see the "Downtime Tagging" section -below. +_absolutely_ necessary - see the ["What Requires Downtime?"](what_requires_downtime.md) +page. If a migration requires downtime, this should be clearly mentioned during +the review process, as well as being documented in the monthly release post. For +more information, see the "Downtime Tagging" section below. When writing your migrations, also consider that databases might have stale data or inconsistencies and guard for that. Try to make as little assumptions as possible -- cgit v1.2.1 From 0b38e213a735994628a5ceabaf7ff51eeef38eae Mon Sep 17 00:00:00 2001 From: secustor Date: Fri, 30 Sep 2016 23:03:30 +0200 Subject: Changed Slack service user referencing from full name to username --- CHANGELOG | 1 + app/models/project_services/slack_service/issue_message.rb | 2 +- app/models/project_services/slack_service/merge_message.rb | 2 +- app/models/project_services/slack_service/note_message.rb | 2 +- .../project_services/slack_service/wiki_page_message.rb | 2 +- .../project_services/slack_service/issue_message_spec.rb | 6 +++--- .../project_services/slack_service/merge_message_spec.rb | 6 +++--- .../project_services/slack_service/note_message_spec.rb | 10 +++++----- .../project_services/slack_service/push_message_spec.rb | 12 ++++++------ .../project_services/slack_service/wiki_page_message_spec.rb | 6 +++--- 10 files changed, 25 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8510f374c3a..851b7d5013a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.13.0 (unreleased) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) v 8.12.4 (unreleased) diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb index 88e053ec192..cd87a79d0c6 100644 --- a/app/models/project_services/slack_service/issue_message.rb +++ b/app/models/project_services/slack_service/issue_message.rb @@ -11,7 +11,7 @@ class SlackService attr_reader :description def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb index 11fc691022b..b7615c96068 100644 --- a/app/models/project_services/slack_service/merge_message.rb +++ b/app/models/project_services/slack_service/merge_message.rb @@ -10,7 +10,7 @@ class SlackService attr_reader :title def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb index 89ba51cb662..9e84e90f38c 100644 --- a/app/models/project_services/slack_service/note_message.rb +++ b/app/models/project_services/slack_service/note_message.rb @@ -10,7 +10,7 @@ class SlackService def initialize(params) params = HashWithIndifferentAccess.new(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/app/models/project_services/slack_service/wiki_page_message.rb b/app/models/project_services/slack_service/wiki_page_message.rb index f336d9e7691..160ca3ac115 100644 --- a/app/models/project_services/slack_service/wiki_page_message.rb +++ b/app/models/project_services/slack_service/wiki_page_message.rb @@ -9,7 +9,7 @@ class SlackService attr_reader :description def initialize(params) - @user_name = params[:user][:name] + @user_name = params[:user][:username] @project_name = params[:project_name] @project_url = params[:project_url] diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb index 0f8889bdf3c..98c36ec088d 100644 --- a/spec/models/project_services/slack_service/issue_message_spec.rb +++ b/spec/models/project_services/slack_service/issue_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::IssueMessage, models: true do { user: { name: 'Test User', - username: 'Test User' + username: 'test.user' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -40,7 +40,7 @@ describe SlackService::IssueMessage, models: true do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - '] Issue opened by Test User') + '] Issue opened by test.user') expect(subject.attachments).to eq([ { title: "#100 Issue title", @@ -60,7 +60,7 @@ describe SlackService::IssueMessage, models: true do it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - '] Issue closed by Test User') + '] Issue closed by test.user') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb index 224c7ceabe8..c5c052d9af1 100644 --- a/spec/models/project_services/slack_service/merge_message_spec.rb +++ b/spec/models/project_services/slack_service/merge_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::MergeMessage, models: true do { user: { name: 'Test User', - username: 'Test User' + username: 'test.user' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -31,7 +31,7 @@ describe SlackService::MergeMessage, models: true do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'Test User opened '\ + 'test.user opened '\ 'in : *Issue title*') expect(subject.attachments).to be_empty end @@ -43,7 +43,7 @@ describe SlackService::MergeMessage, models: true do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'Test User closed '\ + 'test.user closed '\ 'in : *Issue title*') expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb index 41b93f08050..38cfe4ad3e3 100644 --- a/spec/models/project_services/slack_service/note_message_spec.rb +++ b/spec/models/project_services/slack_service/note_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::NoteMessage, models: true do @args = { user: { name: 'Test User', - username: 'username', + username: 'test.user', avatar_url: 'http://fakeavatar' }, project_name: 'project_name', @@ -37,7 +37,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on commits' do message = SlackService::NoteMessage.new(@args) - expect(message.pretext).to eq("Test User commented on " \ + expect(message.pretext).to eq("test.user commented on " \ " in : " \ "*Added a commit message*") expected_attachments = [ @@ -63,7 +63,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on a merge request' do message = SlackService::NoteMessage.new(@args) - expect(message.pretext).to eq("Test User commented on " \ + expect(message.pretext).to eq("test.user commented on " \ " in : " \ "*merge request title*") expected_attachments = [ @@ -90,7 +90,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on an issue' do message = SlackService::NoteMessage.new(@args) expect(message.pretext).to eq( - "Test User commented on " \ + "test.user commented on " \ " in : " \ "*issue title*") expected_attachments = [ @@ -115,7 +115,7 @@ describe SlackService::NoteMessage, models: true do it 'returns a message regarding notes on a project snippet' do message = SlackService::NoteMessage.new(@args) - expect(message.pretext).to eq("Test User commented on " \ + expect(message.pretext).to eq("test.user commented on " \ " in : " \ "*snippet title*") expected_attachments = [ diff --git a/spec/models/project_services/slack_service/push_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb index cda9ee670b0..17cd05e24f1 100644 --- a/spec/models/project_services/slack_service/push_message_spec.rb +++ b/spec/models/project_services/slack_service/push_message_spec.rb @@ -9,7 +9,7 @@ describe SlackService::PushMessage, models: true do before: 'before', project_name: 'project_name', ref: 'refs/heads/master', - user_name: 'user_name', + user_name: 'test.user', project_url: 'url' } end @@ -26,7 +26,7 @@ describe SlackService::PushMessage, models: true do it 'returns a message regarding pushes' do expect(subject.pretext).to eq( - 'user_name pushed to branch of '\ + 'test.user pushed to branch of '\ ' ()' ) expect(subject.attachments).to eq([ @@ -46,13 +46,13 @@ describe SlackService::PushMessage, models: true do before: Gitlab::Git::BLANK_SHA, project_name: 'project_name', ref: 'refs/tags/new_tag', - user_name: 'user_name', + user_name: 'test.user', project_url: 'url' } end it 'returns a message regarding pushes' do - expect(subject.pretext).to eq('user_name pushed new tag ' \ + expect(subject.pretext).to eq('test.user pushed new tag ' \ ' to ' \ '') expect(subject.attachments).to be_empty @@ -66,7 +66,7 @@ describe SlackService::PushMessage, models: true do it 'returns a message regarding a new branch' do expect(subject.pretext).to eq( - 'user_name pushed new branch to '\ + 'test.user pushed new branch to '\ '' ) expect(subject.attachments).to be_empty @@ -80,7 +80,7 @@ describe SlackService::PushMessage, models: true do it 'returns a message regarding a removed branch' do expect(subject.pretext).to eq( - 'user_name removed branch master from ' + 'test.user removed branch master from ' ) expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/slack_service/wiki_page_message_spec.rb b/spec/models/project_services/slack_service/wiki_page_message_spec.rb index 13aea0b0600..093911598b0 100644 --- a/spec/models/project_services/slack_service/wiki_page_message_spec.rb +++ b/spec/models/project_services/slack_service/wiki_page_message_spec.rb @@ -7,7 +7,7 @@ describe SlackService::WikiPageMessage, models: true do { user: { name: 'Test User', - username: 'Test User' + username: 'test.user' }, project_name: 'project_name', project_url: 'somewhere.com', @@ -25,7 +25,7 @@ describe SlackService::WikiPageMessage, models: true do it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( - 'Test User created in : '\ + 'test.user created in : '\ '*Wiki page title*') end end @@ -35,7 +35,7 @@ describe SlackService::WikiPageMessage, models: true do it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( - 'Test User edited in : '\ + 'test.user edited in : '\ '*Wiki page title*') end end -- cgit v1.2.1 From c920aad3ee82900c11f240f913abcd2caa0a3353 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Mon, 3 Oct 2016 01:11:50 +0000 Subject: Grammar fixes in docs --- doc/ci/docker/using_docker_images.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index a849905ac6b..1007db3df1a 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -37,7 +37,7 @@ The registered runner will use the `ruby:2.1` docker image and will run two services, `postgres:latest` and `mysql:latest`, both of which will be accessible during the build process. -## What is image +## What is an image The `image` keyword is the name of the docker image that is present in the local Docker Engine (list all images with `docker images`) or any image that @@ -47,7 +47,7 @@ Hub please read the [Docker Fundamentals][] documentation. In short, with `image` we refer to the docker image, which will be used to create a container on which your build will run. -## What is service +## What is a service The `services` keyword defines just another docker image that is run during your build and is linked to the docker image that the `image` keyword defines. @@ -61,7 +61,7 @@ time the project is built. You can see some widely used services examples in the relevant documentation of [CI services examples](../services/README.md). -### How is service linked to the build +### How services are linked to the build To better understand how the container linking works, read [Linking containers together][linking-containers]. -- cgit v1.2.1 From ddbe676dc318b87c3d656a08bbf5d75485ad544b Mon Sep 17 00:00:00 2001 From: Thomas Balthazar Date: Thu, 8 Sep 2016 11:18:41 +0200 Subject: Add a /wip slash command It toggles the 'WIP' prefix in the MR title. --- CHANGELOG | 1 + .../projects/merge_requests_controller.rb | 2 +- app/models/merge_request.rb | 24 ++++++++-- app/services/merge_requests/base_service.rb | 9 ++-- app/services/merge_requests/update_service.rb | 15 +++++- app/services/slash_commands/interpret_service.rb | 12 +++++ doc/user/project/slash_commands.md | 3 +- .../projects/merge_requests_controller_spec.rb | 14 ++++++ .../issues/user_uses_slash_commands_spec.rb | 10 ++++ .../user_uses_slash_commands_spec.rb | 55 ++++++++++++++++++++-- spec/models/merge_request_spec.rb | 40 ++++++++++++++++ .../slash_commands/interpret_service_spec.rb | 27 +++++++++++ 12 files changed, 196 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c0c4b980038..e207649c2ac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.13.0 (unreleased) - Fix centering of custom header logos (Ashley Dumaine) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 + - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Speed-up group milestones show page - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 68bb4232f5b..8c8c56228ad 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -276,7 +276,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def remove_wip - MergeRequests::UpdateService.new(project, current_user, title: @merge_request.wipless_title).execute(@merge_request) + MergeRequests::UpdateService.new(project, current_user, wip_event: 'unwip').execute(@merge_request) redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), notice: "The merge request can now be merged." diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index aec555dcec0..a431d46cc9e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -155,6 +155,20 @@ class MergeRequest < ActiveRecord::Base where("merge_requests.id IN (#{union.to_sql})") end + WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze + + def self.work_in_progress?(title) + !!(title =~ WIP_REGEX) + end + + def self.wipless_title(title) + title.sub(WIP_REGEX, "") + end + + def self.wip_title(title) + work_in_progress?(title) ? title : "WIP: #{title}" + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" @@ -389,14 +403,16 @@ class MergeRequest < ActiveRecord::Base @closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last end - WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze - def work_in_progress? - !!(title =~ WIP_REGEX) + self.class.work_in_progress?(title) end def wipless_title - self.title.sub(WIP_REGEX, "") + self.class.wipless_title(self.title) + end + + def wip_title + self.class.wip_title(self.title) end def mergeable?(skip_ci_check: false) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index ba424b09463..d0d155b7ee1 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -5,16 +5,17 @@ module MergeRequests end def create_title_change_note(issuable, old_title) - removed_wip = old_title =~ MergeRequest::WIP_REGEX && !issuable.work_in_progress? - added_wip = old_title !~ MergeRequest::WIP_REGEX && issuable.work_in_progress? + removed_wip = MergeRequest.work_in_progress?(old_title) && !issuable.work_in_progress? + added_wip = !MergeRequest.work_in_progress?(old_title) && issuable.work_in_progress? + changed_title = MergeRequest.wipless_title(old_title) != issuable.wipless_title if removed_wip SystemNoteService.remove_merge_request_wip(issuable, issuable.project, current_user) elsif added_wip SystemNoteService.add_merge_request_wip(issuable, issuable.project, current_user) - else - super end + + super if changed_title end def hook_data(merge_request, action, oldrev = nil) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index f14f9e4b327..9dbec49d163 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -16,7 +16,7 @@ module MergeRequests end merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) - + handle_wip_event(merge_request) update(merge_request) end @@ -81,5 +81,18 @@ module MergeRequests def after_update(issuable) issuable.cache_merge_request_closes_issues!(current_user) end + + private + + def handle_wip_event(merge_request) + if wip_event = params.delete(:wip_event) + # We update the title that is provided in the params or we use the mr title + title = params[:title] || merge_request.title + params[:title] = case wip_event + when 'wip' then MergeRequest.wip_title(title) + when 'unwip' then MergeRequest.wipless_title(title) + end + end + end end end diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index ffcad5b3a87..1725a30fae5 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -214,6 +214,18 @@ module SlashCommands @updates[:due_date] = nil end + desc do + "Toggle the Work In Progress status" + end + condition do + issuable.persisted? && + issuable.respond_to?(:work_in_progress?) && + current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + end + command :wip do + @updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip' + end + # This is a dummy command, so that it appears in the autocomplete commands desc 'CC' params '@user' diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md index 1792a0c501d..5f6a6c6503e 100644 --- a/doc/user/project/slash_commands.md +++ b/doc/user/project/slash_commands.md @@ -27,4 +27,5 @@ do. | `/subscribe` | Subscribe | | `/unsubscribe` | Unsubscribe | | /due <in 2 days | this Friday | December 31st> | Set due date | -| `/remove_due_date` | Remove due date | +| `/remove_due_date` | Remove due date | +| `/wip` | Toggle the Work In Progress status | diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 94c9edc91fe..742edd8ba3d 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -644,6 +644,20 @@ describe Projects::MergeRequestsController do end end + context 'POST remove_wip' do + it 'removes the wip status' do + merge_request.title = merge_request.wip_title + merge_request.save + + post :remove_wip, + namespace_id: merge_request.project.namespace.to_param, + project_id: merge_request.project.to_param, + id: merge_request.iid + + expect(merge_request.reload.title).to eq(merge_request.wipless_title) + end + end + context 'POST resolve_conflicts' do let(:json_response) { JSON.parse(response.body) } let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index bf2b93c92fb..3f2da1c380c 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -99,5 +99,15 @@ feature 'Issues > User uses slash commands', feature: true, js: true do end end end + + describe 'toggling the WIP prefix from the title from note' do + let(:issue) { create(:issue, project: project) } + + it 'does not recognize the command nor create a note' do + write_note("/wip") + + expect(page).not_to have_content '/wip' + end + end end end diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 22d9d1b9fd5..cb3cea3fd51 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -14,21 +14,66 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } end - describe 'adding a due date from note' do + describe 'merge-request-only commands' do before do project.team << [user, :master] login_with(user) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end - + after do wait_for_ajax end - it 'does not recognize the command nor create a note' do - write_note("/due 2016-08-28") + describe 'toggling the WIP prefix in the title from note' do + context 'when the current user can toggle the WIP prefix' do + it 'adds the WIP: prefix to the title' do + write_note("/wip") + + expect(page).not_to have_content '/wip' + expect(page).to have_content 'Your commands have been executed!' + + expect(merge_request.reload.work_in_progress?).to eq true + end + + it 'removes the WIP: prefix from the title' do + merge_request.title = merge_request.wip_title + merge_request.save + write_note("/wip") + + expect(page).not_to have_content '/wip' + expect(page).to have_content 'Your commands have been executed!' + + expect(merge_request.reload.work_in_progress?).to eq false + end + end + + context 'when the current user cannot toggle the WIP prefix' do + let(:guest) { create(:user) } + before do + project.team << [guest, :guest] + logout + login_with(guest) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'does not change the WIP prefix' do + write_note("/wip") + + expect(page).not_to have_content '/wip' + expect(page).not_to have_content 'Your commands have been executed!' + + expect(merge_request.reload.work_in_progress?).to eq false + end + end + end + + describe 'adding a due date from note' do + it 'does not recognize the command nor create a note' do + write_note('/due 2016-08-28') - expect(page).not_to have_content '/due 2016-08-28' + expect(page).not_to have_content '/due 2016-08-28' + end end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 580a3235127..9d7be2429ed 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -287,6 +287,46 @@ describe MergeRequest, models: true do end end + describe "#wipless_title" do + ['WIP ', 'WIP:', 'WIP: ', '[WIP]', '[WIP] ', ' [WIP] WIP [WIP] WIP: WIP '].each do |wip_prefix| + it "removes the '#{wip_prefix}' prefix" do + wipless_title = subject.title + subject.title = "#{wip_prefix}#{subject.title}" + + expect(subject.wipless_title).to eq wipless_title + end + + it "is satisfies the #work_in_progress? method" do + subject.title = "#{wip_prefix}#{subject.title}" + subject.title = subject.wipless_title + + expect(subject.work_in_progress?).to eq false + end + end + end + + describe "#wip_title" do + it "adds the WIP: prefix to the title" do + wip_title = "WIP: #{subject.title}" + + expect(subject.wip_title).to eq wip_title + end + + it "does not add the WIP: prefix multiple times" do + wip_title = "WIP: #{subject.title}" + subject.title = subject.wip_title + subject.title = subject.wip_title + + expect(subject.wip_title).to eq wip_title + end + + it "is satisfies the #work_in_progress? method" do + subject.title = subject.wip_title + + expect(subject.work_in_progress?).to eq true + end + end + describe '#can_remove_source_branch?' do let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb index 5b1edba87a1..ae4d286d250 100644 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ b/spec/services/slash_commands/interpret_service_spec.rb @@ -165,6 +165,23 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'wip command' do + it 'returns wip_event: "wip" if content contains /wip' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(wip_event: 'wip') + end + end + + shared_examples 'unwip command' do + it 'returns wip_event: "unwip" if content contains /wip' do + issuable.update(title: issuable.wip_title) + _, updates = service.execute(content, issuable) + + expect(updates).to eq(wip_event: 'unwip') + end + end + shared_examples 'empty command' do it 'populates {} if content contains an unsupported command' do _, updates = service.execute(content, issuable) @@ -376,6 +393,16 @@ describe SlashCommands::InterpretService, services: true do let(:issuable) { issue } end + it_behaves_like 'wip command' do + let(:content) { '/wip' } + let(:issuable) { merge_request } + end + + it_behaves_like 'unwip command' do + let(:content) { '/wip' } + let(:issuable) { merge_request } + end + it_behaves_like 'empty command' do let(:content) { '/remove_due_date' } let(:issuable) { merge_request } -- cgit v1.2.1 From 72da67485ff7ca430d8f09b7d7f28b4f54d33215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 3 Oct 2016 10:01:02 +0200 Subject: Members::RequestAccessService is tricter on permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fortunately, only specs needed to be fixed, so that's good! Signed-off-by: Rémy Coutable --- spec/finders/access_requests_finder_spec.rb | 4 ++-- spec/models/member_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/finders/access_requests_finder_spec.rb b/spec/finders/access_requests_finder_spec.rb index 6cc90299417..626513200e4 100644 --- a/spec/finders/access_requests_finder_spec.rb +++ b/spec/finders/access_requests_finder_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe AccessRequestsFinder, services: true do let(:user) { create(:user) } let(:access_requester) { create(:user) } - let(:project) { create(:project) } - let(:group) { create(:group) } + let(:project) { create(:project, :public) } + let(:group) { create(:group, :public) } before do project.request_access(access_requester) diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index bda23eaed43..485121701af 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -174,7 +174,7 @@ describe Member, models: true do describe '.add_user' do %w[project group].each do |source_type| context "when source is a #{source_type}" do - let!(:source) { create(source_type) } + let!(:source) { create(source_type, :public) } let!(:user) { create(:user) } let!(:admin) { create(:admin) } -- cgit v1.2.1 From d291ea97e0e944610c36469757a10b342ab0e05c Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 12 Aug 2016 09:27:52 +0200 Subject: GrapeDSL for Award Emoji endpoints --- lib/api/award_emoji.rb | 64 ++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index 2461a783ea8..e9ccba3b465 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -8,16 +8,19 @@ module API awardable_string = awardable_type.pluralize awardable_id_string = "#{awardable_type}_id" + params do + requires :id, type: String, desc: 'The ID of a project' + requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet" + end + [ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji", ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji" ].each do |endpoint| - # Get a list of project +awardable+ award emoji - # - # Parameters: - # id (required) - The ID of a project - # awardable_id (required) - The ID of an issue or MR - # Example Request: - # GET /projects/:id/issues/:awardable_id/award_emoji + + desc 'Get a list of project +awardable+ award emoji' do + detail 'This feature was introduced in 8.9' + success Entities::AwardEmoji + end get endpoint do if can_read_awardable? awards = paginate(awardable.award_emoji) @@ -27,14 +30,13 @@ module API end end - # Get a specific award emoji - # - # Parameters: - # id (required) - The ID of a project - # awardable_id (required) - The ID of an issue or MR - # award_id (required) - The ID of the award - # Example Request: - # GET /projects/:id/issues/:awardable_id/award_emoji/:award_id + desc 'Get a specific award emoji' do + detail 'This feature was introduced in 8.9' + success Entities::AwardEmoji + end + params do + requires :award_id, type: Integer, desc: 'The ID of the award' + end get "#{endpoint}/:award_id" do if can_read_awardable? present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji @@ -43,17 +45,14 @@ module API end end - # Award a new Emoji - # - # Parameters: - # id (required) - The ID of a project - # awardable_id (required) - The ID of an issue or mr - # name (required) - The name of a award_emoji (without colons) - # Example Request: - # POST /projects/:id/issues/:awardable_id/award_emoji + desc 'Award a new Emoji' do + detail 'This feature was introduced in 8.9' + success Entities::AwardEmoji + end + params do + requires :name, type: String, desc: 'The name of a award_emoji (without colons)' + end post endpoint do - required_attributes! [:name] - not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable? award = awardable.create_award_emoji(params[:name], current_user) @@ -65,14 +64,13 @@ module API end end - # Delete a +awardables+ award emoji - # - # Parameters: - # id (required) - The ID of a project - # awardable_id (required) - The ID of an issue or MR - # award_emoji_id (required) - The ID of an award emoji - # Example Request: - # DELETE /projects/:id/issues/:issue_id/notes/:note_id/award_emoji/:award_id + desc 'Delete a +awardables+ award emoji' do + detail 'This feature was introduced in 8.9' + success Entities::AwardEmoji + end + params do + requires :award_id, type: Integer, desc: 'The ID of an award emoji' + end delete "#{endpoint}/:award_id" do award = awardable.award_emoji.find(params[:award_id]) -- cgit v1.2.1 From e36b6daa7f7f75ea78b01ab098092ddf2d31321e Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Wed, 28 Sep 2016 13:41:09 +0300 Subject: Better empty state for Groups view. --- CHANGELOG | 1 + app/assets/stylesheets/pages/groups.scss | 36 ++++++++++++++++++++++- app/views/dashboard/groups/_empty_state.html.haml | 7 +++++ app/views/dashboard/groups/index.html.haml | 13 ++++---- app/views/shared/icons/_icon_empty_groups.svg | 1 + 5 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 app/views/dashboard/groups/_empty_state.html.haml create mode 100644 app/views/shared/icons/_icon_empty_groups.svg diff --git a/CHANGELOG b/CHANGELOG index a52ac53bae7..ff40441043a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -28,6 +28,7 @@ v 8.13.0 (unreleased) - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? - Add broadcast messages and alerts below sub-nav + - Better empty state for Groups view - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 732dc645c66..185ce970e71 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -57,7 +57,6 @@ } .groups-header { - @media (min-width: $screen-sm-min) { .nav-links { width: 35%; @@ -68,3 +67,38 @@ } } } + +.groups-empty-state { + padding: 50px 100px; + overflow: hidden; + + @media (max-width: $screen-md-min) { + padding: 50px 0; + } + + svg { + float: right; + + @media (max-width: $screen-md-min) { + float: none; + display: block; + width: 250px; + position: relative; + left: 50%; + margin-left: -125px; + } + } + + .text-content { + float: left; + width: 460px; + margin-top: 120px; + + @media (max-width: $screen-md-min) { + float: none; + margin-top: 60px; + width: auto; + text-align: center; + } + } +} diff --git a/app/views/dashboard/groups/_empty_state.html.haml b/app/views/dashboard/groups/_empty_state.html.haml new file mode 100644 index 00000000000..f5222fe631e --- /dev/null +++ b/app/views/dashboard/groups/_empty_state.html.haml @@ -0,0 +1,7 @@ +.groups-empty-state + = custom_icon("icon_empty_groups") + + .text-content + %h4 A group is a collection of several projects. + %p If you organize your projects under a group, it works like a folder. + %p You can manage your group member’s permissions and access to each project in the group. diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index caca91af536..1a679c51774 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -2,9 +2,12 @@ - header_title "Groups", dashboard_groups_path = render 'dashboard/groups_head' -%ul.content-list - - @group_members.each do |group_member| - - group = group_member.group - = render 'shared/groups/group', group: group, group_member: group_member +- if @group_members.empty? + = render 'empty_state' +- else + %ul.content-list + - @group_members.each do |group_member| + - group = group_member.group + = render 'shared/groups/group', group: group, group_member: group_member -= paginate @group_members, theme: 'gitlab' + = paginate @group_members, theme: 'gitlab' diff --git a/app/views/shared/icons/_icon_empty_groups.svg b/app/views/shared/icons/_icon_empty_groups.svg new file mode 100644 index 00000000000..0824de67ca6 --- /dev/null +++ b/app/views/shared/icons/_icon_empty_groups.svg @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.1 From 580ff22910c6e104b8d6959f47cd676b55982d01 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Thu, 29 Sep 2016 18:16:43 +0300 Subject: Updated artwork of empty group state. --- app/views/shared/icons/_icon_empty_groups.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/icons/_icon_empty_groups.svg b/app/views/shared/icons/_icon_empty_groups.svg index 0824de67ca6..9228be05f03 100644 --- a/app/views/shared/icons/_icon_empty_groups.svg +++ b/app/views/shared/icons/_icon_empty_groups.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file -- cgit v1.2.1 From 629b56288baad0abe76a977091c571a5747e43e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 30 Sep 2016 17:48:54 +0200 Subject: Update gitlab-shell to 3.6.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- GITLAB_SHELL_VERSION | 2 +- doc/update/8.12-to-8.13.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index b72762837ea..4a788a01dad 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.6.2 +3.6.3 diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index d61c76673f7..411e4837e20 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-13-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v3.6.2 +sudo -u git -H git checkout v3.6.3 ``` ### 6. Update gitlab-workhorse -- cgit v1.2.1 From a09e1d3dda57323d8932b498a711928c98404005 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 29 Aug 2016 09:46:05 +0200 Subject: Enable import/export back for non-admins --- CHANGELOG | 1 + app/controllers/import/gitlab_projects_controller.rb | 5 ----- app/views/projects/new.html.haml | 2 +- doc/user/project/settings/import_export.md | 3 ++- features/steps/dashboard/new_project.rb | 1 + spec/features/projects/import_export/import_file_spec.rb | 4 ++-- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a52ac53bae7..6cfaefd728e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.13.0 (unreleased) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Replace `alias_method_chain` with `Module#prepend` + - Enable GitLab Import/Export for non-admin users. - Preserve label filters when sorting !6136 (Joseph Frazier) - Only update issuable labels if they have been changed - Take filters in account in issuable counters. !6496 diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 7d0eff37635..3ec173abcdb 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -1,6 +1,5 @@ class Import::GitlabProjectsController < Import::BaseController before_action :verify_gitlab_project_import_enabled - before_action :authenticate_admin! def new @namespace_id = project_params[:namespace_id] @@ -48,8 +47,4 @@ class Import::GitlabProjectsController < Import::BaseController :path, :namespace_id, :file ) end - - def authenticate_admin! - render_404 unless current_user.is_admin? - end end diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index fda0592dd41..cc8cb134fb8 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -72,7 +72,7 @@ = link_to "#", class: 'btn js-toggle-button import_git' do = icon('git', text: 'Repo by URL') %div{ class: 'import_gitlab_project' } - - if gitlab_project_import_enabled? && current_user.is_admin? + - if gitlab_project_import_enabled? = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do = icon('gitlab', text: 'GitLab export') diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index 445c0ee8333..65ed9fae4ec 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -7,7 +7,8 @@ > that of the exporter. > - For existing installations, the project import option has to be enabled in > application settings (`/admin/application_settings`) under 'Import sources'. -> You will have to be an administrator to enable and use the import functionality. +> Ask your administrator if you don't see the **GitLab export** button when +> creating a new project. > - You can find some useful raketasks if you are an administrator in the > [import_export](../../../administration/raketasks/project_import_export.md) > raketask. diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index 2f0941e4113..cb36d6ae1a9 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -20,6 +20,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps expect(page).to have_link('GitLab.com') expect(page).to have_link('Google Code') expect(page).to have_link('Repo by URL') + expect(page).to have_link('GitLab export') end step 'I click on "Import project from GitHub"' do diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 09cd6369881..f32834801a0 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -86,14 +86,14 @@ feature 'Import/Export - project import integration test', feature: true, js: tr login_as(normal_user) end - scenario 'non-admin user is not allowed to import a project' do + scenario 'non-admin user is allowed to import a project' do expect(Project.all.count).to be_zero visit new_project_path fill_in :project_path, with: 'test-project-path', visible: true - expect(page).not_to have_content('GitLab export') + expect(page).to have_content('GitLab export') end end -- cgit v1.2.1 From babaaca43c350aa4b6c1d3c8e9b92a7ef76bb0ae Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 14 Sep 2016 19:16:13 +0100 Subject: Added issue due date to todos row Part of #18218 --- app/views/dashboard/todos/_todo.html.haml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index b40395c74de..c729bbec5b5 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -20,6 +20,15 @@ · #{time_ago_with_tooltip(todo.created_at)} + - if todo.target.try(:due_date) + · + %span{ class: ('text-warning' if todo.target.due_date.try(:today?)) } + Due + - if todo.target.due_date.try(:today?) + today + - else + = todo.target.due_date.to_s(:medium) + .todo-body .todo-note .md -- cgit v1.2.1 From 4c5c5ab31479b0f3e1b7c3830cae250417d02f43 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 14 Sep 2016 19:22:36 +0100 Subject: Added test for due date --- spec/features/todos/todos_spec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index fc555a74f30..bf93c1d1251 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -4,7 +4,7 @@ describe 'Dashboard Todos', feature: true do let(:user) { create(:user) } let(:author) { create(:user) } let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } - let(:issue) { create(:issue) } + let(:issue) { create(:issue, due_date: Date.today) } describe 'GET /dashboard/todos' do context 'User does not have todos' do @@ -28,6 +28,12 @@ describe 'Dashboard Todos', feature: true do expect(page).to have_selector('.todos-list .todo', count: 1) end + it 'shows due date as today' do + page.within first('.todo') do + expect(page).to have_content 'Due today' + end + end + describe 'deleting the todo' do before do first('.done-todo').click -- cgit v1.2.1 From 77f7e666752c38776cc5e09d9ad87d3a291cf993 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 14 Sep 2016 19:25:16 +0100 Subject: CHANGELOG item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index a52ac53bae7..144434ea093 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -136,6 +136,7 @@ v 8.12.0 - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files - Add textarea autoresize after comment (ClemMakesApps) - Do not write SSH public key 'comments' to authorized_keys !6381 + - Add due date to issue todos - Refresh todos count cache when an Issue/MR is deleted - Fix branches page dropdown sort alignment (ClemMakesApps) - Hides merge request button on branches page is user doesn't have permissions -- cgit v1.2.1 From 08b7480f51f6072ba70b8739301b1ba5d1df1213 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 20 Sep 2016 09:43:31 +0100 Subject: Uses variable Added red text if overdue --- app/views/dashboard/todos/_todo.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index c729bbec5b5..06ea5675a0f 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -21,10 +21,12 @@ · #{time_ago_with_tooltip(todo.created_at)} - if todo.target.try(:due_date) + - is_due_today = todo.target.due_date.try(:today?) + - is_overdue = todo.target.try(:overdue?) · - %span{ class: ('text-warning' if todo.target.due_date.try(:today?)) } + %span{ class: [('text-warning' if is_due_today), ('text-danger' if is_overdue)] } Due - - if todo.target.due_date.try(:today?) + - if is_due_today today - else = todo.target.due_date.to_s(:medium) -- cgit v1.2.1 From a3abfb9708d894b434484fa0e2a836bb0ebd2cdb Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 23 Sep 2016 17:28:06 +0100 Subject: Moved todo due date text into helper method --- app/helpers/todos_helper.rb | 6 ++++++ app/views/dashboard/todos/_todo.html.haml | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 1e86f648203..26d61c32744 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -114,6 +114,12 @@ module TodosHelper selected_type ? selected_type[:text] : default_type end + def todo_due_date(todo) + is_due_today = todo.target.due_date.try(:today?) + + "Due #{is_due_today ? "today" : todo.target.due_date.to_s(:medium)}" + end + private def show_todo_state?(todo) diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 06ea5675a0f..45d50f323e1 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -25,11 +25,7 @@ - is_overdue = todo.target.try(:overdue?) · %span{ class: [('text-warning' if is_due_today), ('text-danger' if is_overdue)] } - Due - - if is_due_today - today - - else - = todo.target.due_date.to_s(:medium) + = todo_due_date(todo) .todo-body .todo-note -- cgit v1.2.1 From 8ea18c37611ccb86b286761cc373c01199d64878 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Oct 2016 09:58:55 +0100 Subject: Moved todo due date to helper method --- app/helpers/todos_helper.rb | 7 ++++++- app/views/dashboard/todos/_todo.html.haml | 8 +------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 26d61c32744..9542d3d7941 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -115,9 +115,14 @@ module TodosHelper end def todo_due_date(todo) + return unless todo.target.try(:due_date) + is_due_today = todo.target.due_date.try(:today?) + is_overdue = todo.target.try(:overdue?) - "Due #{is_due_today ? "today" : todo.target.due_date.to_s(:medium)}" + content_tag :span, class: [('text-warning' if is_due_today), ('text-danger' if is_overdue)] do + "Due #{is_due_today ? "today" : todo.target.due_date.to_s(:medium)}" + end end private diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 45d50f323e1..cc77388563f 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -19,13 +19,7 @@ (removed) · #{time_ago_with_tooltip(todo.created_at)} - - - if todo.target.try(:due_date) - - is_due_today = todo.target.due_date.try(:today?) - - is_overdue = todo.target.try(:overdue?) - · - %span{ class: [('text-warning' if is_due_today), ('text-danger' if is_overdue)] } - = todo_due_date(todo) + · #{todo_due_date(todo)} .todo-body .todo-note -- cgit v1.2.1 From ef2ed380f3af1a198761f5bd547b360165fa7a82 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 2 Oct 2016 17:00:41 +0200 Subject: Convert "SSH Keys" Spinach features to RSpec --- features/profile/ssh_keys.feature | 20 ---------------- features/steps/profile/ssh_keys.rb | 46 ------------------------------------ spec/features/profiles/keys_spec.rb | 47 +++++++++++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 70 deletions(-) delete mode 100644 features/profile/ssh_keys.feature delete mode 100644 features/steps/profile/ssh_keys.rb diff --git a/features/profile/ssh_keys.feature b/features/profile/ssh_keys.feature deleted file mode 100644 index b0d5b748916..00000000000 --- a/features/profile/ssh_keys.feature +++ /dev/null @@ -1,20 +0,0 @@ -@profile -Feature: Profile SSH Keys - Background: - Given I sign in as a user - And I have ssh key "ssh-rsa Work" - And I visit profile keys page - - Scenario: I should see ssh keys - Then I should see my ssh keys - - Scenario: Add new ssh key - Given I should see new ssh key form - And I submit new ssh key "Laptop" - Then I should see new ssh key "Laptop" - - Scenario: Remove ssh key - Given I click link "Work" - And I click link "Remove" - Then I visit profile keys page - And I should not see "Work" ssh key diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb deleted file mode 100644 index a400488a532..00000000000 --- a/features/steps/profile/ssh_keys.rb +++ /dev/null @@ -1,46 +0,0 @@ -class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps - include SharedAuthentication - - step 'I should see my ssh keys' do - @user.keys.each do |key| - expect(page).to have_content(key.title) - end - end - - step 'I should see new ssh key form' do - expect(page).to have_content("Add an SSH key") - end - - step 'I submit new ssh key "Laptop"' do - fill_in "key_title", with: "Laptop" - fill_in "key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop" - click_button "Add key" - end - - step 'I should see new ssh key "Laptop"' do - key = Key.find_by(title: "Laptop") - expect(page).to have_content(key.title) - expect(page).to have_content(key.key) - expect(current_path).to eq profile_key_path(key) - end - - step 'I click link "Work"' do - click_link "Work" - end - - step 'I click link "Remove"' do - click_link "Remove" - end - - step 'I visit profile keys page' do - visit profile_keys_path - end - - step 'I should not see "Work" ssh key' do - expect(page).not_to have_content "Work" - end - - step 'I have ssh key "ssh-rsa Work"' do - create(:key, user: @user, title: "ssh-rsa Work", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work") - end -end diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb index 3b20d38c520..eb1050d21c6 100644 --- a/spec/features/profiles/keys_spec.rb +++ b/spec/features/profiles/keys_spec.rb @@ -1,18 +1,57 @@ require 'rails_helper' -describe 'Profile > SSH Keys', feature: true do +feature 'Profile > SSH Keys', feature: true do let(:user) { create(:user) } before do login_as(user) - visit profile_keys_path end - describe 'User adds an SSH key' do - it 'auto-populates the title', js: true do + describe 'User adds a key' do + before do + visit profile_keys_path + end + + scenario 'auto-populates the title', js: true do fill_in('Key', with: attributes_for(:key).fetch(:key)) expect(find_field('Title').value).to eq 'dummy@gitlab.com' end + + scenario 'saves the new key' do + attrs = attributes_for(:key) + + fill_in('Key', with: attrs[:key]) + fill_in('Title', with: attrs[:title]) + click_button('Add key') + + expect(page).to have_content("Title: #{attrs[:title]}") + expect(page).to have_content(attrs[:key]) + end + end + + scenario 'User sees their keys' do + key = create(:key, user: user) + visit profile_keys_path + + expect(page).to have_content(key.title) + end + + scenario 'User removes a key via the key index' do + create(:key, user: user) + visit profile_keys_path + + click_link('Remove') + + expect(page).to have_content('Your SSH keys (0)') + end + + scenario 'User removes a key via its details page' do + key = create(:key, user: user) + visit profile_key_path(key) + + click_link('Remove') + + expect(page).to have_content('Your SSH keys (0)') end end -- cgit v1.2.1 From 06fdfc7395681ee6dca6f1b9d998cdbeb57b0a24 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Oct 2016 10:08:34 +0100 Subject: Moved middot into helper method --- app/helpers/todos_helper.rb | 3 ++- app/views/dashboard/todos/_todo.html.haml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 9542d3d7941..3d2e079c380 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -120,7 +120,8 @@ module TodosHelper is_due_today = todo.target.due_date.try(:today?) is_overdue = todo.target.try(:overdue?) - content_tag :span, class: [('text-warning' if is_due_today), ('text-danger' if is_overdue)] do + html = "· ".html_safe + html << content_tag(:span, class: [('text-warning' if is_due_today), ('text-danger' if is_overdue)]) do "Due #{is_due_today ? "today" : todo.target.due_date.to_s(:medium)}" end end diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index cc77388563f..cc077fad32a 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -19,7 +19,7 @@ (removed) · #{time_ago_with_tooltip(todo.created_at)} - · #{todo_due_date(todo)} + = todo_due_date(todo) .todo-body .todo-note -- cgit v1.2.1 From de61ebc8fcae9fbcd59ffdbd12ac91adc67079d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 3 Oct 2016 11:06:52 +0200 Subject: Make Member#add_user set access_level for requesters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/member.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/member.rb b/app/models/member.rb index 38a278ea559..b89ba8ecbb8 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -103,7 +103,12 @@ class Member < ActiveRecord::Base } if member.request? - ::Members::ApproveAccessRequestService.new(source, current_user, id: member.id).execute + ::Members::ApproveAccessRequestService.new( + source, + current_user, + id: member.id, + access_level: access_level + ).execute else member.save end -- cgit v1.2.1 From 729cb3b319227dca2cdf5bd211519615a92e328f Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Mon, 3 Oct 2016 12:38:21 +0300 Subject: Fixes sidebar navigation. --- app/assets/javascripts/sidebar.js.es6 | 12 +++++------- app/assets/stylesheets/framework/sidebar.scss | 1 + app/views/layouts/_page.html.haml | 5 +++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6 index ead3219bc31..755fac8107b 100644 --- a/app/assets/javascripts/sidebar.js.es6 +++ b/app/assets/javascripts/sidebar.js.es6 @@ -34,8 +34,8 @@ $(pageSelector).hasClass(expandedPageClass) ); $(document) - .on('click', sidebarToggleSelector, (e) => this.toggleSidebar(e)) - .on('click', pinnedToggleSelector, (e) => this.togglePinnedState(e)) + .on('click', sidebarToggleSelector, () => this.toggleSidebar()) + .on('click', pinnedToggleSelector, () => this.togglePinnedState()) .on('click', 'html, body', (e) => this.handleClickEvent(e)) .on('page:change', () => this.renderState()); this.renderState(); @@ -47,19 +47,17 @@ const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0; const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0; if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) { - this.toggleSidebar(e); + this.toggleSidebar(); } } } - toggleSidebar(e) { - e.preventDefault(); + toggleSidebar() { this.isExpanded = !this.isExpanded; this.renderState(); } - togglePinnedState(e) { - e.preventDefault(); + togglePinnedState() { this.isPinned = !this.isPinned; if (!this.isPinned) { this.isExpanded = false; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 3b7de4b57bb..557ef7291cf 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -142,6 +142,7 @@ transition-duration: .3s; position: absolute; top: 0; + cursor: pointer; &:hover, &:focus { diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 3950b4f53e7..8aefdcb3d9b 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,10 +1,11 @@ .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } .sidebar-wrapper.nicescroll .sidebar-action-buttons - = link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do + .nav-header-btn.toggle-nav-collapse{ title: "Open/Close" } %span.sr-only Toggle navigation = icon('bars') - = link_to '#', class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do + + %div{ class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: { placement: 'right', container: 'body' } } %span.sr-only Toggle navigation pinning = icon('fw thumb-tack') -- cgit v1.2.1 From f5e43f0788438758e4b1404da19fb4255245aead Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 30 Sep 2016 11:14:42 +0200 Subject: Prevent rendering the link when the author has no access --- lib/banzai/filter/user_reference_filter.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index e1ca7f4d24b..b3c8e565c27 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -106,13 +106,17 @@ module Banzai project = context[:project] author = context[:author] - url = urls.namespace_project_url(project.namespace, project, - only_path: context[:only_path]) + if author && !project.team.member?(author) + '@all' + else + url = urls.namespace_project_url(project.namespace, project, + only_path: context[:only_path]) - data = data_attribute(project: project.id, author: author.try(:id)) - text = link_text || User.reference_prefix + 'all' + data = data_attribute(project: project.id, author: author.try(:id)) + text = link_text || User.reference_prefix + 'all' - link_tag(url, data, text, 'All Project and Group Members') + link_tag(url, data, text, 'All Project and Group Members') + end end def link_to_namespace(namespace, link_text: nil) -- cgit v1.2.1 From bf3b8d1c1d3d854311a638ebeaf87991367e4052 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 30 Sep 2016 14:53:37 +0200 Subject: Fix test, add author attribute to all tests --- CHANGELOG | 1 + lib/banzai/filter/user_reference_filter.rb | 4 ++-- spec/lib/banzai/filter/user_reference_filter_spec.rb | 9 +++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a52ac53bae7..b55ac5eb511 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,7 @@ v 8.13.0 (unreleased) - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) + - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index b3c8e565c27..c6302b586d3 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -106,8 +106,8 @@ module Banzai project = context[:project] author = context[:author] - if author && !project.team.member?(author) - '@all' + if author && !project.team.member?(author) + link_text else url = urls.namespace_project_url(project.namespace, project, only_path: context[:only_path]) diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index fdbdb21eac1..4b1ea6403cc 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -31,13 +31,16 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do end it 'supports a special @all mention' do + project.team << [user, :developer] doc = reference_filter("Hey #{reference}", author: user) + expect(doc.css('a').length).to eq 1 expect(doc.css('a').first.attr('href')) .to eq urls.namespace_project_url(project.namespace, project) end it 'includes a data-author attribute when there is an author' do + project.team << [user, :developer] doc = reference_filter(reference, author: user) expect(doc.css('a').first.attr('data-author')).to eq(user.id.to_s) @@ -48,6 +51,12 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do expect(doc.css('a').first.has_attribute?('data-author')).to eq(false) end + + it 'ignores reference to all when user is not a project member' do + doc = reference_filter("Hey #{reference}", author: user) + + expect(doc.css('a').length).to eq 0 + end end context 'mentioning a user' do -- cgit v1.2.1 From fd8c30d1d7da92b45732e532890453b277428588 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Sat, 1 Oct 2016 11:17:56 +0200 Subject: Imrove grammar --- spec/lib/banzai/filter/user_reference_filter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index 4b1ea6403cc..729e77fd43f 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -52,7 +52,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do expect(doc.css('a').first.has_attribute?('data-author')).to eq(false) end - it 'ignores reference to all when user is not a project member' do + it 'ignores reference to all when the user is not a project member' do doc = reference_filter("Hey #{reference}", author: user) expect(doc.css('a').length).to eq 0 -- cgit v1.2.1 From 74c8e091f40c29a59b99bf7864b9fee303c68e50 Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Fri, 2 Sep 2016 16:57:08 +0800 Subject: add configurable email subject suffix --- CHANGELOG | 4 ++++ app/mailers/notify.rb | 1 + config/gitlab.yml.example | 1 + config/initializers/1_settings.rb | 1 + doc/administration/environment_variables.md | 20 +++++++++++--------- spec/mailers/shared/notify.rb | 4 ++++ 6 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a52ac53bae7..9c84ef90d71 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -271,6 +271,10 @@ v 8.11.6 v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 - Fix member expiration date picker after update. !6184 + - Add configurable email subject suffix + +v 8.11.5 (unreleased) + - Optimize branch lookups and force a repository reload for Repository#find_branch - Fix suggested colors options for new labels in the admin area. !6138 - Optimize discussion notes resolving and unresolving - Fix GitLab import button diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 29f1c527776..1321b42ddf4 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -92,6 +92,7 @@ class Notify < BaseMailer subject = "" subject << "#{@project.name} | " if @project subject << extra.join(' | ') if extra.present? + subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.length > 0 subject end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1470a6e2550..a79356923b2 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -70,6 +70,7 @@ production: &base email_from: example@example.com email_display_name: GitLab email_reply_to: noreply@example.com + email_subject_suffix: '' # Email server smtp settings are in config/initializers/smtp_settings.rb.sample diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 195108b921b..c5ed2162c92 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -186,6 +186,7 @@ Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].ni Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}" Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab' Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}" +Settings.gitlab['email_subject_suffix'] ||= ENV['GITLAB_EMAIL_SUBJECT_SUFFIX'] || "" Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url) Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md index 7f53915a4d7..b4a953d1ccc 100644 --- a/doc/administration/environment_variables.md +++ b/doc/administration/environment_variables.md @@ -13,15 +13,17 @@ override certain values. Variable | Type | Description -------- | ---- | ----------- -`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation -`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`) -`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test` -`DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development` -`GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab -`GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab -`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab -`GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer -`GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer +`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation +`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`) +`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test` +`DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development` +`GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab +`GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab +`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab +`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab +`GITLAB_EMAIL_SUBJECT_SUFFIX` | string | The e-mail subject suffix used in e-mails sent by GitLab +`GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer +`GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer ## Complete database variables diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb index 5c9851f14c7..de1d8995534 100644 --- a/spec/mailers/shared/notify.rb +++ b/spec/mailers/shared/notify.rb @@ -2,6 +2,7 @@ shared_context 'gitlab email notification' do let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } let(:gitlab_sender) { Gitlab.config.gitlab.email_from } let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } + let(:gitlab_subject_suffix) { Gitlab.config.gitlab.email_subject_suffix } let(:recipient) { create(:user, email: 'recipient@example.com') } let(:project) { create(:project) } let(:new_user_address) { 'newguy@example.com' } @@ -31,6 +32,9 @@ shared_examples 'an email sent from GitLab' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(gitlab_sender_display_name) expect(sender.address).to eq(gitlab_sender) + if gitlab_subject_suffix.length > 0 + is_expected.to have_subject gitlab_subject_suffix + end end it 'has a Reply-To address' do -- cgit v1.2.1 From f3f7f3fe8410cd8213cbc9b736d005ac85cbe980 Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Tue, 27 Sep 2016 01:02:57 +0800 Subject: move changelog to 8.13 --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9c84ef90d71..c6eb18be6e5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -42,6 +42,7 @@ v 8.12.4 (unreleased) v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves + - Add configurable email subject suffix v 8.12.2 (unreleased) - Fix Import/Export not recognising correctly the imported services. @@ -271,7 +272,6 @@ v 8.11.6 v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 - Fix member expiration date picker after update. !6184 - - Add configurable email subject suffix v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch -- cgit v1.2.1 From 785094e201a7d3e7c88f9556da8d9539281a36d5 Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Tue, 27 Sep 2016 01:06:57 +0800 Subject: create new test in `spec/mailers/notify_spec.rb` --- spec/mailers/notify_spec.rb | 11 +++++++++++ spec/mailers/shared/notify.rb | 4 ---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index cd8578b6f49..03e0bfd3614 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -1094,4 +1094,15 @@ describe Notify do is_expected.to have_body_text /#{diff_path}/ end end + + describe 'email has correct subject' do + let(:gitlab_subject_suffix) { Gitlab.config.gitlab.email_subject_suffix } + + it 'has correct suffix' do + if gitlab_subject_suffix.length > 0 + is_expected.to have_subject gitlab_subject_suffix + end + end + end + end diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb index de1d8995534..5c9851f14c7 100644 --- a/spec/mailers/shared/notify.rb +++ b/spec/mailers/shared/notify.rb @@ -2,7 +2,6 @@ shared_context 'gitlab email notification' do let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } let(:gitlab_sender) { Gitlab.config.gitlab.email_from } let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } - let(:gitlab_subject_suffix) { Gitlab.config.gitlab.email_subject_suffix } let(:recipient) { create(:user, email: 'recipient@example.com') } let(:project) { create(:project) } let(:new_user_address) { 'newguy@example.com' } @@ -32,9 +31,6 @@ shared_examples 'an email sent from GitLab' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(gitlab_sender_display_name) expect(sender.address).to eq(gitlab_sender) - if gitlab_subject_suffix.length > 0 - is_expected.to have_subject gitlab_subject_suffix - end end it 'has a Reply-To address' do -- cgit v1.2.1 From 612a5d531722a0b846602ba99a7b2e77dc5cb1d7 Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Tue, 27 Sep 2016 14:11:17 +0800 Subject: remove extra entry --- CHANGELOG | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c6eb18be6e5..747897b18e9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -272,9 +272,6 @@ v 8.11.6 v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 - Fix member expiration date picker after update. !6184 - -v 8.11.5 (unreleased) - - Optimize branch lookups and force a repository reload for Repository#find_branch - Fix suggested colors options for new labels in the admin area. !6138 - Optimize discussion notes resolving and unresolving - Fix GitLab import button -- cgit v1.2.1 From 89dc70df4614daf95a2284d8171db2a9115ec46e Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Tue, 27 Sep 2016 19:03:24 +0800 Subject: remove empty line at block body end --- spec/mailers/notify_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 03e0bfd3614..3585432ec75 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -1104,5 +1104,4 @@ describe Notify do end end end - end -- cgit v1.2.1 From 2a438f452c920e57545add519ed7b7a2c15e660e Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Tue, 27 Sep 2016 20:18:10 +0800 Subject: stub config settings in spec --- spec/mailers/notify_spec.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 3585432ec75..8dc5e0b34d2 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -1096,12 +1096,9 @@ describe Notify do end describe 'email has correct subject' do - let(:gitlab_subject_suffix) { Gitlab.config.gitlab.email_subject_suffix } - it 'has correct suffix' do - if gitlab_subject_suffix.length > 0 - is_expected.to have_subject gitlab_subject_suffix - end + stub_config_setting(email_subject_suffix: 'A Nice Suffix') + is_expected.to have_subject /\| A Nice Suffix$/ end end end -- cgit v1.2.1 From 048f124a0851a51c449881814dbfd2f2fc2dd32d Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Wed, 28 Sep 2016 16:14:47 +0800 Subject: move spec back into shared example `an email sent from GitLab` --- spec/mailers/notify_spec.rb | 7 ------- spec/mailers/shared/notify.rb | 10 ++++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 8dc5e0b34d2..cd8578b6f49 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -1094,11 +1094,4 @@ describe Notify do is_expected.to have_body_text /#{diff_path}/ end end - - describe 'email has correct subject' do - it 'has correct suffix' do - stub_config_setting(email_subject_suffix: 'A Nice Suffix') - is_expected.to have_subject /\| A Nice Suffix$/ - end - end end diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb index 5c9851f14c7..33d1075c739 100644 --- a/spec/mailers/shared/notify.rb +++ b/spec/mailers/shared/notify.rb @@ -37,6 +37,16 @@ shared_examples 'an email sent from GitLab' do reply_to = subject.header[:reply_to].addresses expect(reply_to).to eq([gitlab_sender_reply_to]) end + + context 'when custom suffix for email subject is set' do + before do + stub_config_setting(email_subject_suffix: 'A Nice Suffix') + end + + it 'ends the subject with the suffix' do + is_expected.to have_subject (/ \| A Nice Suffix$/) + end + end end shared_examples 'an email that contains a header with author username' do -- cgit v1.2.1 From 64a60bc4545cbf85b3a025edd63bdaefd8cd9499 Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Thu, 29 Sep 2016 16:09:47 +0800 Subject: wrap subject with method subject --- app/mailers/emails/members.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index 45311690293..7b617b359ea 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -45,7 +45,7 @@ module Emails @token = token mail(to: member.invite_email, - subject: "Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}") + subject: subject("Invitation to join the #{member_source.human_name} #{member_source.model_name.singular}")) end def member_invite_accepted_email(member_source_type, member_id) -- cgit v1.2.1 From 064eb2caa671cdd18cd051e49ff2086d004e5516 Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Thu, 29 Sep 2016 16:11:40 +0800 Subject: follow the styleguide: Don't use parentheses around a literal --- spec/mailers/shared/notify.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb index 33d1075c739..3956d05060b 100644 --- a/spec/mailers/shared/notify.rb +++ b/spec/mailers/shared/notify.rb @@ -44,7 +44,7 @@ shared_examples 'an email sent from GitLab' do end it 'ends the subject with the suffix' do - is_expected.to have_subject (/ \| A Nice Suffix$/) + is_expected.to have_subject /\ \| A Nice Suffix$/ end end end -- cgit v1.2.1 From 76463c2cb460099559b8544396cf1a6656895e8d Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Fri, 30 Sep 2016 17:46:37 +0800 Subject: override subject method in devise mailer --- app/mailers/devise_mailer.rb | 8 ++++++++ spec/mailers/notify_spec.rb | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb index 415f6e12885..bd4c1c9226a 100644 --- a/app/mailers/devise_mailer.rb +++ b/app/mailers/devise_mailer.rb @@ -3,4 +3,12 @@ class DeviseMailer < Devise::Mailer default reply_to: Gitlab.config.gitlab.email_reply_to layout 'devise_mailer' + + protected + + def subject_for(key) + subject = super + subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.length > 0 + subject + end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index cd8578b6f49..0e4130e8a3a 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -831,6 +831,7 @@ describe Notify do let(:user) { create(:user, email: 'old-email@mail.com') } before do + stub_config_setting(email_subject_suffix: 'A Nice Suffix') perform_enqueued_jobs do user.email = "new-email@mail.com" user.save @@ -847,7 +848,7 @@ describe Notify do end it 'has the correct subject' do - is_expected.to have_subject "Confirmation instructions" + is_expected.to have_subject /^Confirmation instructions/ end it 'includes a link to the site' do -- cgit v1.2.1 From 867b9b43472508f0f8cead53b0e9fff89edd9064 Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Fri, 30 Sep 2016 21:06:11 +0800 Subject: change determine conditions --- app/mailers/devise_mailer.rb | 2 +- app/mailers/notify.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb index bd4c1c9226a..f7ed61625f4 100644 --- a/app/mailers/devise_mailer.rb +++ b/app/mailers/devise_mailer.rb @@ -8,7 +8,7 @@ class DeviseMailer < Devise::Mailer def subject_for(key) subject = super - subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.length > 0 + subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present? subject end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index 1321b42ddf4..2444702104e 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -92,7 +92,7 @@ class Notify < BaseMailer subject = "" subject << "#{@project.name} | " if @project subject << extra.join(' | ') if extra.present? - subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.length > 0 + subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present? subject end -- cgit v1.2.1 From 1fde68046e55b13c31217cdad551db7c26733500 Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Fri, 30 Sep 2016 21:11:22 +0800 Subject: credit myself :smile: --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 747897b18e9..e8224cd24fc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.13.0 (unreleased) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) + - Add configurable email subject suffix (Fu Xu) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Replace `alias_method_chain` with `Module#prepend` - Preserve label filters when sorting !6136 (Joseph Frazier) -- cgit v1.2.1 From 08e31875c2d345c0755707ab73c1fd71b1fd9b05 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 27 Sep 2016 11:46:13 +0200 Subject: Update runner version only when updating contacted_at --- CHANGELOG | 1 + app/models/ci/runner.rb | 2 +- lib/ci/api/builds.rb | 6 +++--- lib/ci/api/helpers.rb | 18 +++++++----------- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a52ac53bae7..e5902587bcc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Fix centering of custom header logos (Ashley Dumaine) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index ed5d4b13b7e..44cb19ece3b 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -2,7 +2,7 @@ module Ci class Runner < ActiveRecord::Base extend Ci::Model - LAST_CONTACT_TIME = 2.hours.ago + LAST_CONTACT_TIME = 1.hour.ago AVAILABLE_SCOPES = %w[specific shared active paused online] FORM_EDITABLE = %i[description tag_list active run_untagged locked] diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 59f85416ee5..ed87a2603e8 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -12,10 +12,9 @@ module Ci # POST /builds/register post "register" do authenticate_runner! - update_runner_last_contact(save: false) - update_runner_info required_attributes! [:token] not_found! unless current_runner.active? + update_runner_info build = Ci::RegisterBuildService.new.execute(current_runner) @@ -41,10 +40,11 @@ module Ci # PUT /builds/:id put ":id" do authenticate_runner! - update_runner_last_contact build = Ci::Build.where(runner_id: current_runner.id).running.find(params[:id]) forbidden!('Build has been erased!') if build.erased? + update_runner_info + build.update_attributes(trace: params[:trace]) if params[:trace] Gitlab::Metrics.add_event(:update_build, diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index 23353c62885..c6a83682e32 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -3,7 +3,7 @@ module Ci module Helpers BUILD_TOKEN_HEADER = "HTTP_BUILD_TOKEN" BUILD_TOKEN_PARAM = :token - UPDATE_RUNNER_EVERY = 40 * 60 + UPDATE_RUNNER_EVERY = 10 * 60 def authenticate_runners! forbidden! unless runner_registration_token_valid? @@ -30,14 +30,15 @@ module Ci token && (build.valid_token?(token) || build.project.valid_runners_token?(token)) end - def update_runner_last_contact(save: true) + def update_runner_info # Use a random threshold to prevent beating DB updates # it generates a distribution between: [40m, 80m] contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY) - if current_runner.contacted_at.nil? || Time.now - current_runner.contacted_at >= contacted_at_max_age - current_runner.contacted_at = Time.now - current_runner.save if current_runner.changed? && save - end + return unless current_runner.contacted_at.nil? || Time.now - current_runner.contacted_at >= contacted_at_max_age + + current_runner.contacted_at = Time.now + current_runner.assign_attributes(get_runner_version_from_params) + current_runner.save if current_runner.changed? end def build_not_found! @@ -57,11 +58,6 @@ module Ci attributes_for_keys(["name", "version", "revision", "platform", "architecture"], params["info"]) end - def update_runner_info - current_runner.assign_attributes(get_runner_version_from_params) - current_runner.save if current_runner.changed? - end - def max_artifacts_size current_application_settings.max_artifacts_size.megabytes.to_i end -- cgit v1.2.1 From 9a8e486307550aa1a590c769d9e71621c5ce8c62 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 3 Oct 2016 10:47:37 +0200 Subject: Extract method that checks if ci runner needs update --- lib/ci/api/helpers.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index c6a83682e32..e608f5f6cad 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -31,16 +31,23 @@ module Ci end def update_runner_info - # Use a random threshold to prevent beating DB updates - # it generates a distribution between: [40m, 80m] - contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY) - return unless current_runner.contacted_at.nil? || Time.now - current_runner.contacted_at >= contacted_at_max_age + return unless update_runner? current_runner.contacted_at = Time.now current_runner.assign_attributes(get_runner_version_from_params) current_runner.save if current_runner.changed? end + def update_runner? + # Use a random threshold to prevent beating DB updates. + # It generates a distribution between [40m, 80m]. + # + contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY) + + current_runner.contacted_at.nil? || + (Time.now - current_runner.contacted_at) >= contacted_at_max_age + end + def build_not_found! if headers['User-Agent'].match(/gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /) no_content! -- cgit v1.2.1 From aa1ab8aa1786517544d0a5c1a4217f98d37ea58f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 3 Oct 2016 11:49:17 +0200 Subject: Add some tests for updating ci runner information --- spec/requests/ci/api/builds_spec.rb | 43 +++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index df97f1bf7b6..7b7d62feb2c 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -35,18 +35,24 @@ describe Ci::API::API do end end - it "starts a build" do - register_builds info: { platform: :darwin } - - expect(response).to have_http_status(201) - expect(json_response['sha']).to eq(build.sha) - expect(runner.reload.platform).to eq("darwin") - expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] }) - expect(json_response["variables"]).to include( - { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, - { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, - { "key" => "DB_NAME", "value" => "postgres", "public" => true } - ) + context 'when there is a pending build' do + it 'starts a build' do + register_builds info: { platform: :darwin } + + expect(response).to have_http_status(201) + expect(json_response['sha']).to eq(build.sha) + expect(runner.reload.platform).to eq("darwin") + expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] }) + expect(json_response["variables"]).to include( + { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true }, + { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true }, + { "key" => "DB_NAME", "value" => "postgres", "public" => true } + ) + end + + it 'updates runner info' do + expect { register_builds }.to change { runner.reload.contacted_at } + end end context 'when builds are finished' do @@ -159,13 +165,18 @@ describe Ci::API::API do end context 'when runner is paused' do - let(:inactive_runner) { create(:ci_runner, :inactive, token: "InactiveRunner") } + let(:runner) { create(:ci_runner, :inactive, token: 'InactiveRunner') } - before do - register_builds inactive_runner.token + it 'responds with 404' do + register_builds + + expect(response).to have_http_status 404 end - it { expect(response).to have_http_status 404 } + it 'does not update runner info' do + expect { register_builds } + .not_to change { runner.reload.contacted_at } + end end def register_builds(token = runner.token, **params) -- cgit v1.2.1 From bb94408537dd8bd0fdb7bc1577c55bde1ee2fc41 Mon Sep 17 00:00:00 2001 From: Fu Xu Date: Mon, 3 Oct 2016 18:08:28 +0800 Subject: resolve duplicated changelog entry --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e8224cd24fc..164af436af5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,7 +43,6 @@ v 8.12.4 (unreleased) v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves - - Add configurable email subject suffix v 8.12.2 (unreleased) - Fix Import/Export not recognising correctly the imported services. -- cgit v1.2.1 From 8dfec32d2bac661313f62b338b6d991b3fa25b98 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 3 Oct 2016 18:22:51 +0800 Subject: Rename ignored to failed_but_allowed, introduce exclude_ignored which merges previous exclude_ignored_jobs and failed_but_allowed, so that we don't treat ignored a special case in HasStatus. --- app/models/ci/pipeline.rb | 2 +- app/models/commit_status.rb | 16 ++++++++++++++-- app/models/concerns/has_status.rb | 27 +++++++++++---------------- app/services/ci/process_pipeline_service.rb | 2 +- spec/models/build_spec.rb | 4 ++-- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 663c5b1e231..97df74b0cfe 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -196,7 +196,7 @@ module Ci end def has_warnings? - builds.latest.ignored.any? + builds.latest.failed_but_allowed.any? end def config_processor diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 736db1ab0f6..710c8364554 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -24,7 +24,19 @@ class CommitStatus < ActiveRecord::Base scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } - scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } + scope :failed_but_allowed, -> do + where(allow_failure: true, status: [:failed, :canceled]) + end + scope :exclude_ignored, -> do + quoted_when = connection.quote_column_name('when') + where("allow_failure = ? OR status NOT IN (?)", + false, [:failed, :canceled]). + # We want to ignore skipped manual jobs + where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). + # We want to ignore skipped on_failure + where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped') + + end scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) } scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) } @@ -111,7 +123,7 @@ class CommitStatus < ActiveRecord::Base end end - def ignored? + def failed_but_allowed? allow_failure? && (failed? || canceled?) end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 582fe8bb1a7..21b2972b5a8 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -8,28 +8,28 @@ module HasStatus class_methods do def status_sql - scope = exclude_ignored_jobs + scope = if respond_to?(:exclude_ignored) + exclude_ignored + else + all + end builds = scope.select('count(*)').to_sql created = scope.created.select('count(*)').to_sql success = scope.success.select('count(*)').to_sql - ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored) - ignored ||= '0' pending = scope.pending.select('count(*)').to_sql running = scope.running.select('count(*)').to_sql - canceled = scope.canceled.select('count(*)').to_sql skipped = scope.skipped.select('count(*)').to_sql + canceled = scope.canceled.select('count(*)').to_sql - deduce_status = "(CASE + "(CASE WHEN (#{builds})=(#{created}) THEN 'created' - WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' - WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'skipped' - WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending' - WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' + WHEN (#{builds})=(#{success}) THEN 'success' + WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'skipped' + WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' + WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running' ELSE 'failed' END)" - - deduce_status end def status @@ -69,11 +69,6 @@ module HasStatus scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } scope :exclude_ignored_jobs, -> do - quoted_when = connection.quote_column_name('when') - # We want to ignore skipped manual jobs - where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). - # We want to ignore skipped on_failure - where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped') end end diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 3e76dc32f01..f81f44f6716 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -61,7 +61,7 @@ module Ci end def status_for_prior_stages(index) - pipeline.builds.exclude_ignored_jobs.where('stage_idx < ?', index). + pipeline.builds.exclude_ignored.where('stage_idx < ?', index). latest.status || 'success' end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index e7864b7ad33..ae185de9ca3 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -39,8 +39,8 @@ describe Ci::Build, models: true do end end - describe '#ignored?' do - subject { build.ignored? } + describe '#failed_but_allowed?' do + subject { build.failed_but_allowed? } context 'when build is not allowed to fail' do before do -- cgit v1.2.1 From 23e0a3ee7d3e050c6c6beb8fafa178779e7e4fc8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 3 Oct 2016 18:25:18 +0800 Subject: Specify 3 cases we want them to be excluded. [ci skip] --- app/models/commit_status.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 710c8364554..0cf8d32453e 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -29,6 +29,7 @@ class CommitStatus < ActiveRecord::Base end scope :exclude_ignored, -> do quoted_when = connection.quote_column_name('when') + # We want to ignore failed_but_allowed jobs where("allow_failure = ? OR status NOT IN (?)", false, [:failed, :canceled]). # We want to ignore skipped manual jobs -- cgit v1.2.1 From 705d210c737c53aa5df6a5ee21251c14db332208 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 3 Oct 2016 18:33:43 +0800 Subject: Style/EmptyLinesAroundBlockBody --- app/models/commit_status.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 0cf8d32453e..0ed87922177 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -24,9 +24,11 @@ class CommitStatus < ActiveRecord::Base scope :retried, -> { where.not(id: latest) } scope :ordered, -> { order(:name) } + scope :failed_but_allowed, -> do where(allow_failure: true, status: [:failed, :canceled]) end + scope :exclude_ignored, -> do quoted_when = connection.quote_column_name('when') # We want to ignore failed_but_allowed jobs @@ -38,6 +40,7 @@ class CommitStatus < ActiveRecord::Base where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped') end + scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) } scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) } -- cgit v1.2.1 From 4567e624a06358f0e3451be51d134f8dd22c199d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 15:03:51 +0200 Subject: Make pipeline processing asynchronous Conflicts: app/models/ci/pipeline.rb app/models/commit_status.rb --- app/models/ci/build.rb | 2 +- app/models/ci/pipeline.rb | 11 +++++++++-- app/models/commit_status.rb | 14 ++++++++++---- .../merge_requests/add_todo_when_build_fails_service.rb | 8 ++++---- app/services/merge_requests/base_service.rb | 15 ++++++--------- .../merge_requests/merge_when_build_succeeds_service.rb | 7 ++++--- app/workers/process_pipeline_worker.rb | 17 +++++++++++++++++ 7 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 app/workers/process_pipeline_worker.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5dbf66173de..7c9899334ad 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -67,7 +67,7 @@ module Ci environment: build.environment, status_event: 'enqueue' ) - MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) + MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build.pipeline) build.pipeline.mark_as_processable_after_stage(build.stage_idx) new_build end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 663c5b1e231..da0b9d83e1d 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -73,6 +73,14 @@ module Ci after_transition do |pipeline, transition| pipeline.execute_hooks unless transition.loopback? end + + after_transition [:created, :pending, :running] => :success do |pipeline| + MergeRequests::MergeWhenBuildSucceedsService.new(pipeline.project, nil).trigger(pipeline) + end + + after_transition any => :failed do |pipeline| + MergeRequests::AddTodoWhenBuildFailsService.new(pipeline.project, nil).execute(pipeline) + end end # ref can't be HEAD or SHA, can only be branch/tag name @@ -251,9 +259,8 @@ module Ci Ci::ProcessPipelineService.new(project, user).execute(self) end - def build_updated + def update_status with_lock do - reload case latest_builds_status when 'pending' then enqueue when 'running' then run diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 736db1ab0f6..3d5e449f403 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -74,10 +74,6 @@ class CommitStatus < ActiveRecord::Base true end - after_transition do |commit_status, transition| - commit_status.pipeline.try(:build_updated) unless transition.loopback? - end - after_transition [:created, :pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) end @@ -85,6 +81,16 @@ class CommitStatus < ActiveRecord::Base after_transition any => :failed do |commit_status| MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) end + + after_transition do |commit_status, transition| + if commit_status.pipeline && !transition.loopback? + ProcessPipelineWorker.perform_async( + commit_status.pipeline.id, + process: HasStatus.COMPLETED_STATUSES.include?(commit_status.status)) + end + + true + end end delegate :sha, :short_sha, to: :pipeline diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb index 566049525cb..45c73789886 100644 --- a/app/services/merge_requests/add_todo_when_build_fails_service.rb +++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb @@ -1,15 +1,15 @@ module MergeRequests class AddTodoWhenBuildFailsService < MergeRequests::BaseService # Adds a todo to the parent merge_request when a CI build fails - def execute(commit_status) - each_merge_request(commit_status) do |merge_request| + def execute(pipeline) + each_merge_request(pipeline) do |merge_request| todo_service.merge_request_build_failed(merge_request) end end # Closes any pending build failed todos for the parent MRs when a build is retried - def close(commit_status) - each_merge_request(commit_status) do |merge_request| + def close(pipeline) + each_merge_request(pipeline) do |merge_request| todo_service.merge_request_build_retried(merge_request) end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index d0d155b7ee1..95b4f0ff733 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -42,11 +42,11 @@ module MergeRequests super(:merge_request) end - def merge_request_from(commit_status) - branches = commit_status.ref + def merge_request_from(pipeline) + branches = pipeline.ref # This is for ref-less builds - branches ||= @project.repository.branch_names_contains(commit_status.sha) + branches ||= @project.repository.branch_names_contains(pipeline.sha) return [] if branches.blank? @@ -56,14 +56,11 @@ module MergeRequests merge_requests.uniq.select(&:source_project) end - def each_merge_request(commit_status) + def each_merge_request(pipeline) merge_request_from(commit_status).each do |merge_request| - pipeline = merge_request.pipeline + next unless pipeline == merge_request.pipeline - next unless pipeline - next unless pipeline.sha == commit_status.sha - - yield merge_request, pipeline + yield merge_request end end end diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index 4ad5fb08311..60d6085f9eb 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -19,11 +19,12 @@ module MergeRequests end # Triggers the automatic merge of merge_request once the build succeeds - def trigger(commit_status) - each_merge_request(commit_status) do |merge_request, pipeline| + def trigger(pipeline) + return unless pipeline.success? + + each_merge_request(pipeline) do |merge_request| next unless merge_request.merge_when_build_succeeds? next unless merge_request.mergeable? - next unless pipeline.success? MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) end diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/process_pipeline_worker.rb new file mode 100644 index 00000000000..fb59a1efb7a --- /dev/null +++ b/app/workers/process_pipeline_worker.rb @@ -0,0 +1,17 @@ +class ProcessPipelineWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id, params) + begin + pipeline = Ci::Pipeline.find(pipeline_id) + rescue ActiveRecord::RecordNotFound + return + end + + pipeline.process! if params[:process] + + pipeline.update_status + end +end -- cgit v1.2.1 From 9c2306006c58b42ba5d09bfca363bfcc22092a8e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 3 Oct 2016 18:35:36 +0800 Subject: Remove dead code --- app/models/concerns/has_status.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 21b2972b5a8..444a1610afa 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -68,8 +68,6 @@ module HasStatus scope :skipped, -> { where(status: 'skipped') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } - scope :exclude_ignored_jobs, -> do - end end def started? -- cgit v1.2.1 From 752a4cce514f04e0f364f615b817e64e9f40879b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 3 Oct 2016 18:56:32 +0800 Subject: Add test for CommitStatus.exclude_ignored --- app/models/commit_status.rb | 1 - spec/models/commit_status_spec.rb | 55 ++++++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 0ed87922177..99622014662 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -38,7 +38,6 @@ class CommitStatus < ActiveRecord::Base where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). # We want to ignore skipped on_failure where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped') - end scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) } diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 2f1baff5d66..80c2a1bc7a9 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -7,7 +7,11 @@ describe CommitStatus, models: true do create(:ci_pipeline, project: project, sha: project.commit.id) end - let(:commit_status) { create(:commit_status, pipeline: pipeline) } + let(:commit_status) { create_status } + + def create_status(args = {}) + create(:commit_status, args.merge(pipeline: pipeline)) + end it { is_expected.to belong_to(:pipeline) } it { is_expected.to belong_to(:user) } @@ -125,32 +129,53 @@ describe CommitStatus, models: true do describe '.latest' do subject { CommitStatus.latest.order(:id) } - before do - @commit1 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'running' - @commit2 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'cc', ref: 'cc', status: 'pending' - @commit3 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'cc', status: 'success' - @commit4 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'cc', ref: 'bb', status: 'success' - @commit5 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'success' + let(:statuses) do + [create_status(name: 'aa', ref: 'bb', status: 'running'), + create_status(name: 'cc', ref: 'cc', status: 'pending'), + create_status(name: 'aa', ref: 'cc', status: 'success'), + create_status(name: 'cc', ref: 'bb', status: 'success'), + create_status(name: 'aa', ref: 'bb', status: 'success')] end it 'returns unique statuses' do - is_expected.to eq([@commit4, @commit5]) + is_expected.to eq(statuses.values_at(3, 4)) end end describe '.running_or_pending' do subject { CommitStatus.running_or_pending.order(:id) } - before do - @commit1 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'running' - @commit2 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'cc', ref: 'cc', status: 'pending' - @commit3 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: nil, status: 'success' - @commit4 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'dd', ref: nil, status: 'failed' - @commit5 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'ee', ref: nil, status: 'canceled' + let(:statuses) do + [create_status(name: 'aa', ref: 'bb', status: 'running'), + create_status(name: 'cc', ref: 'cc', status: 'pending'), + create_status(name: 'aa', ref: nil, status: 'success'), + create_status(name: 'dd', ref: nil, status: 'failed'), + create_status(name: 'ee', ref: nil, status: 'canceled')] end it 'returns statuses that are running or pending' do - is_expected.to eq([@commit1, @commit2]) + is_expected.to eq(statuses.values_at(0, 1)) + end + end + + describe '.exclude_ignored' do + subject { CommitStatus.exclude_ignored.order(:id) } + + let(:statuses) do + [create_status(when: 'manual', status: 'skipped'), + create_status(when: 'manual', status: 'success'), + create_status(when: 'manual', status: 'failed'), + create_status(when: 'on_failure', status: 'skipped'), + create_status(when: 'on_failure', status: 'success'), + create_status(when: 'on_failure', status: 'failed'), + create_status(allow_failure: true, status: 'success'), + create_status(allow_failure: true, status: 'failed'), + create_status(allow_failure: false, status: 'success'), + create_status(allow_failure: false, status: 'failed')] + end + + it 'returns statuses without what we want to ignore' do + is_expected.to eq(statuses.values_at(1, 2, 4, 5, 6, 8, 9)) end end -- cgit v1.2.1 From 8cb9b3764a3e790399c6ee7923f5b5da1fa72171 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 29 Sep 2016 11:31:47 +0100 Subject: Fixes invalid html - table was inside an ul and had 2 tbody tags Adds hidden-xs class to the all column instead of the div inside it in order to remove extra whitespace when the actions are hidden Fixes columns width in pipeplines table Changes table parent element to not be an ul and reduces column width for the Build column Adds entry to CHANGELOG Removes added width for build table. Adds the CHANGELOG entry in the middle to avoid more conflicts with master --- CHANGELOG | 1 + app/assets/stylesheets/pages/pipelines.scss | 6 +- app/views/projects/builds/_table.html.haml | 2 +- app/views/projects/builds/index.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 65 +++++++++++----------- .../projects/ci/pipelines/_pipeline.html.haml | 55 +++++++++--------- app/views/projects/pipelines/index.html.haml | 16 +++--- 7 files changed, 75 insertions(+), 72 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6ce15ef28bf..ed25218236d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,7 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Fix broken repository 500 errors in project list + - Fix Pipeline list commit column width should be adjusted - Close todos when accepting merge requests via the API !6486 (tonygambone) v 8.12.4 (unreleased) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index b035bfc9f3c..6484e5c02a9 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -39,6 +39,10 @@ overflow: auto; } +.pipelines .table.builds .branch-commit { + width: 33%; +} + .table.builds { min-width: 900px; @@ -77,7 +81,7 @@ } .branch-commit { - + .branch-name { font-weight: bold; max-width: 150px; diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index c2bcfb773a6..f3747ba2a21 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -1,7 +1,7 @@ - admin = local_assigns.fetch(:admin, false) - if builds.blank? - %li + %div .nothing-here-block No builds to show - else .table-holder diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 5c60b7a7364..06070f12bbd 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -19,5 +19,5 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - %ul.content-list.builds-content-list + %div.content-list.builds-content-list = render "table", builds: @builds, project: @project diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 75192c48188..9248adfde80 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -13,45 +13,44 @@ - else = ci_status_with_icon(build.status) - %td - .branch-commit - - if can?(current_user, :read_build, build) - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span.build-link ##{build.id} - - else + %td.branch-commit + - if can?(current_user, :read_build, build) + = link_to namespace_project_build_url(build.project.namespace, build.project, build) do %span.build-link ##{build.id} + - else + %span.build-link ##{build.id} - - if ref - - if build.ref - .icon-container - = build.tag? ? icon('tag') : icon('code-fork') - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - - else - .light none + - if ref + - if build.ref .icon-container - = custom_icon("icon_commit") + = build.tag? ? icon('tag') : icon('code-fork') + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" + - else + .light none + .icon-container + = custom_icon("icon_commit") - - if commit_sha - = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" + - if commit_sha + = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" - - if build.stuck? - = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if retried - = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') + - if build.stuck? + = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') + - if retried + = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') - .label-container - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.try(:trigger_request) - %span.label.label-info triggered - - if build.try(:allow_failure) - %span.label.label-danger allowed to fail - - if retried - %span.label.label-warning retried - - if build.manual? - %span.label.label-info manual + .label-container + - if build.tags.any? + - build.tags.each do |tag| + %span.label.label-primary + = tag + - if build.try(:trigger_request) + %span.label.label-info triggered + - if build.try(:allow_failure) + %span.label.label-danger allowed to fail + - if retried + %span.label.label-warning retried + - if build.manual? + %span.label.label-info manual - if admin %td diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 04e48a4dc17..b87c7a485df 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -9,33 +9,32 @@ = ci_icon_for_status(status) - else = ci_status_with_icon(status) - %td - .branch-commit - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do - %span ##{pipeline.id} - - if pipeline.ref && show_branch - .icon-container - = pipeline.tag? ? icon('tag') : icon('code-fork') - = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" - - if show_commit - .icon-container - = custom_icon("icon_commit") - = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" - - if pipeline.latest? - %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest - - if pipeline.triggered? - %span.label.label-primary triggered - - if pipeline.yaml_errors.present? - %span.label.label-danger.has-tooltip{ title: "#{pipeline.yaml_errors}" } yaml invalid - - if pipeline.builds.any?(&:stuck?) - %span.label.label-warning stuck + %td.branch-commit + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do + %span ##{pipeline.id} + - if pipeline.ref && show_branch + .icon-container + = pipeline.tag? ? icon('tag') : icon('code-fork') + = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" + - if show_commit + .icon-container + = custom_icon("icon_commit") + = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" + - if pipeline.latest? + %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest + - if pipeline.triggered? + %span.label.label-primary triggered + - if pipeline.yaml_errors.present? + %span.label.label-danger.has-tooltip{ title: "#{pipeline.yaml_errors}" } yaml invalid + - if pipeline.builds.any?(&:stuck?) + %span.label.label-warning stuck - %p.commit-title - - if commit = pipeline.commit - = author_avatar(commit, size: 20) - = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message" - - else - Cant find HEAD commit for this branch + %p.commit-title + - if commit = pipeline.commit + = author_avatar(commit, size: 20) + = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message" + - else + Cant find HEAD commit for this branch - stages_status = pipeline.statuses.relevant.latest.stages_status @@ -58,8 +57,8 @@ = icon("calendar") #{time_ago_with_tooltip(pipeline.finished_at, short_format: false, skip_js: true)} - %td.pipeline-actions - .controls.hidden-xs.pull-right + %td.pipeline-actions.hidden-xs + .controls.pull-right - artifacts = pipeline.builds.latest.with_artifacts_not_expired - actions = pipeline.manual_actions - if artifacts.present? || actions.any? diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 50c7e5044b2..e1c983e1679 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -36,20 +36,20 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - %ul.content-list.pipelines + %div.content-list.pipelines - stages = @pipelines.stages - if @pipelines.blank? - %li + %div .nothing-here-block No pipelines to show - else .table-holder %table.table.builds - %tbody - %th Status - %th Pipeline - %th Stages - %th - %th + %thead + %th.col-xs-1.col-sm-1 Status + %th.col-xs-2.col-sm-4 Pipeline + %th.col-xs-2.col-sm-2 Stages + %th.col-xs-2.col-sm-2.commit-message + %th.hidden-xs.col-sm-3 = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages = paginate @pipelines, theme: 'gitlab' -- cgit v1.2.1 From afc0ae5cbef721dad867c2fca9734fa9748b02f2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 3 Oct 2016 19:35:53 +0800 Subject: Fix tests. Check 'success' first (default status) --- app/models/concerns/has_status.rb | 2 +- spec/models/concerns/has_status_spec.rb | 43 +++++++++++++++------------------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 444a1610afa..72ec5a8525b 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -22,8 +22,8 @@ module HasStatus canceled = scope.canceled.select('count(*)').to_sql "(CASE - WHEN (#{builds})=(#{created}) THEN 'created' WHEN (#{builds})=(#{success}) THEN 'success' + WHEN (#{builds})=(#{created}) THEN 'created' WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index e118432d098..87bffbdc54e 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -1,26 +1,17 @@ require 'spec_helper' describe HasStatus do - before do - @object = Object.new - @object.extend(HasStatus::ClassMethods) - end - describe '.status' do - before do - allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses)) - end - - subject { @object.status } + subject { CommitStatus.status } shared_examples 'build status summary' do context 'all successful' do - let(:statuses) { Array.new(2) { create(type, status: :success) } } + let!(:statuses) { Array.new(2) { create(type, status: :success) } } it { is_expected.to eq 'success' } end context 'at least one failed' do - let(:statuses) do + let!(:statuses) do [create(type, status: :success), create(type, status: :failed)] end @@ -28,7 +19,7 @@ describe HasStatus do end context 'at least one running' do - let(:statuses) do + let!(:statuses) do [create(type, status: :success), create(type, status: :running)] end @@ -36,7 +27,7 @@ describe HasStatus do end context 'at least one pending' do - let(:statuses) do + let!(:statuses) do [create(type, status: :success), create(type, status: :pending)] end @@ -44,7 +35,7 @@ describe HasStatus do end context 'success and failed but allowed to fail' do - let(:statuses) do + let!(:statuses) do [create(type, status: :success), create(type, status: :failed, allow_failure: true)] end @@ -53,12 +44,15 @@ describe HasStatus do end context 'one failed but allowed to fail' do - let(:statuses) { [create(type, status: :failed, allow_failure: true)] } + let!(:statuses) do + [create(type, status: :failed, allow_failure: true)] + end + it { is_expected.to eq 'success' } end context 'success and canceled' do - let(:statuses) do + let!(:statuses) do [create(type, status: :success), create(type, status: :canceled)] end @@ -66,7 +60,7 @@ describe HasStatus do end context 'one failed and one canceled' do - let(:statuses) do + let!(:statuses) do [create(type, status: :failed), create(type, status: :canceled)] end @@ -74,7 +68,7 @@ describe HasStatus do end context 'one failed but allowed to fail and one canceled' do - let(:statuses) do + let!(:statuses) do [create(type, status: :failed, allow_failure: true), create(type, status: :canceled)] end @@ -83,7 +77,7 @@ describe HasStatus do end context 'one running one canceled' do - let(:statuses) do + let!(:statuses) do [create(type, status: :running), create(type, status: :canceled)] end @@ -91,14 +85,15 @@ describe HasStatus do end context 'all canceled' do - let(:statuses) do + let!(:statuses) do [create(type, status: :canceled), create(type, status: :canceled)] end + it { is_expected.to eq 'canceled' } end context 'success and canceled but allowed to fail' do - let(:statuses) do + let!(:statuses) do [create(type, status: :success), create(type, status: :canceled, allow_failure: true)] end @@ -107,7 +102,7 @@ describe HasStatus do end context 'one finished and second running but allowed to fail' do - let(:statuses) do + let!(:statuses) do [create(type, status: :success), create(type, status: :running, allow_failure: true)] end @@ -118,11 +113,13 @@ describe HasStatus do context 'ci build statuses' do let(:type) { :ci_build } + it_behaves_like 'build status summary' end context 'generic commit statuses' do let(:type) { :generic_commit_status } + it_behaves_like 'build status summary' end end -- cgit v1.2.1 From 7fb6a73db505b73a5b092f7e60eb4221d32308e2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 3 Oct 2016 19:49:53 +0800 Subject: We don't need self. there. (sorry, can't resist anymore) --- app/models/concerns/has_status.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 72ec5a8525b..2dd2c5f2c0f 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -33,7 +33,7 @@ module HasStatus end def status - all.pluck(self.status_sql).first + all.pluck(status_sql).first end def started_at -- cgit v1.2.1 From 56f583915806aea422d1d2922fa378fc09f0ab1c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 3 Oct 2016 21:42:41 +0800 Subject: HasStatus.status is now already aware of that --- app/services/ci/process_pipeline_service.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index f81f44f6716..36c93dddadb 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -61,8 +61,7 @@ module Ci end def status_for_prior_stages(index) - pipeline.builds.exclude_ignored.where('stage_idx < ?', index). - latest.status || 'success' + pipeline.builds.where('stage_idx < ?', index).latest.status || 'success' end def stage_indexes_of_created_builds -- cgit v1.2.1 From f917bf80e81b433f5bbf06ea90bc3d3d0b880bc7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Oct 2016 15:07:29 +0100 Subject: Removed try method call on due date --- app/helpers/todos_helper.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 3d2e079c380..a9db8bb2b82 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -117,11 +117,19 @@ module TodosHelper def todo_due_date(todo) return unless todo.target.try(:due_date) - is_due_today = todo.target.due_date.try(:today?) - is_overdue = todo.target.try(:overdue?) + is_due_today = todo.target.due_date.today? + is_overdue = todo.target.overdue? + css_class = + if is_due_today + 'text-warning' + elsif is_overdue + 'text-danger' + else + '' + end html = "· ".html_safe - html << content_tag(:span, class: [('text-warning' if is_due_today), ('text-danger' if is_overdue)]) do + html << content_tag(:span, class: css_class) do "Due #{is_due_today ? "today" : todo.target.due_date.to_s(:medium)}" end end -- cgit v1.2.1 From 4a191c83d1db31aac16241dbf99ac45088efb99a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 3 Oct 2016 16:09:57 +0200 Subject: Enable Lint/StringConversionInInterpolation cop and autocorrect offenses --- .rubocop.yml | 4 ++++ .rubocop_todo.yml | 5 ----- app/controllers/concerns/spammable_actions.rb | 2 +- app/helpers/projects_helper.rb | 2 +- app/models/commit_range.rb | 2 +- app/services/system_hooks_service.rb | 2 +- app/services/system_note_service.rb | 2 +- spec/finders/access_requests_finder_spec.rb | 2 +- spec/lib/gitlab/import_export/attribute_configuration_spec.rb | 2 +- spec/services/members/approve_access_request_service_spec.rb | 2 +- spec/services/members/request_access_service_spec.rb | 2 +- 11 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5bd31ccf329..c84755859cb 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -639,6 +639,10 @@ Lint/RescueException: Lint/ShadowedException: Enabled: false +# Checks for Object#to_s usage in string interpolation. +Lint/StringConversionInInterpolation: + Enabled: true + # Do not use prefix `_` for a variable that is used. Lint/UnderscorePrefixedVariableName: Enabled: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 87520c67dd5..b6ed6e33397 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -27,11 +27,6 @@ Lint/Loop: Lint/ShadowingOuterLocalVariable: Enabled: false -# Offense count: 6 -# Cop supports --auto-correct. -Lint/StringConversionInInterpolation: - Enabled: false - # Offense count: 49 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index 29e243c66a3..99acd98ae13 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -7,7 +7,7 @@ module SpammableActions def mark_as_spam if SpamService.new(spammable).mark_as_spam! - redirect_to spammable, notice: "#{spammable.class.to_s} was submitted to Akismet successfully." + redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully." else redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.' end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 56477733ea2..e667c9e4e2e 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -139,7 +139,7 @@ module ProjectsHelper end options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) - content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe + content_tag(:select, options, name: "project[project_feature_attributes][#{field}]", id: "project_project_feature_attributes_#{field}", class: "pull-right form-control", data: { field: field }).html_safe end private diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 656a242c265..ac2477fd973 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -80,7 +80,7 @@ class CommitRange end def inspect - %(#<#{self.class}:#{object_id} #{to_s}>) + %(#<#{self.class}:#{object_id} #{self}>) end def to_s diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 1fb72cf89e9..a2bfa422c9d 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -72,7 +72,7 @@ class SystemHooksService return 'user_add_to_group' if event == :create return 'user_remove_from_group' if event == :destroy else - "#{model.class.name.downcase}_#{event.to_s}" + "#{model.class.name.downcase}_#{event}" end end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 5ccaa5275b7..bf251816e7e 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -246,7 +246,7 @@ module SystemNoteService 'deleted' end - body = "#{verb} #{branch_type.to_s} branch `#{branch}`".capitalize + body = "#{verb} #{branch_type} branch `#{branch}`".capitalize create_note(noteable: noteable, project: project, author: author, note: body) end diff --git a/spec/finders/access_requests_finder_spec.rb b/spec/finders/access_requests_finder_spec.rb index 626513200e4..8cfea9659cb 100644 --- a/spec/finders/access_requests_finder_spec.rb +++ b/spec/finders/access_requests_finder_spec.rb @@ -16,7 +16,7 @@ describe AccessRequestsFinder, services: true do access_requesters = described_class.new(source).public_send(method_name, user) expect(access_requesters.size).to eq(1) - expect(access_requesters.first).to be_a "#{source.class.to_s}Member".constantize + expect(access_requesters.first).to be_a "#{source.class}Member".constantize expect(access_requesters.first.user).to eq(access_requester) end end diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb index 2ba344092ce..2e19d590d83 100644 --- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb +++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb @@ -27,7 +27,7 @@ describe 'Import/Export attribute configuration', lib: true do relation_names.each do |relation_name| relation_class = relation_class_for_name(relation_name) - expect(safe_model_attributes[relation_class.to_s]).not_to be_nil, "Expected exported class #{relation_class.to_s} to exist in safe_model_attributes" + expect(safe_model_attributes[relation_class.to_s]).not_to be_nil, "Expected exported class #{relation_class} to exist in safe_model_attributes" current_attributes = parsed_attributes(relation_name, relation_class.attribute_names) safe_attributes = safe_model_attributes[relation_class.to_s] diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb index 6fca80b5613..03e296259f9 100644 --- a/spec/services/members/approve_access_request_service_spec.rb +++ b/spec/services/members/approve_access_request_service_spec.rb @@ -26,7 +26,7 @@ describe Members::ApproveAccessRequestService, services: true do it 'returns a Member' do member = described_class.new(source, user, params).execute - expect(member).to be_a "#{source.class.to_s}Member".constantize + expect(member).to be_a "#{source.class}Member".constantize expect(member.requested_at).to be_nil end diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb index dff5b4917ae..0d2d5f03199 100644 --- a/spec/services/members/request_access_service_spec.rb +++ b/spec/services/members/request_access_service_spec.rb @@ -19,7 +19,7 @@ describe Members::RequestAccessService, services: true do it 'returns a Member' do member = described_class.new(source, user).execute - expect(member).to be_a "#{source.class.to_s}Member".constantize + expect(member).to be_a "#{source.class}Member".constantize expect(member.requested_at).to be_present end end -- cgit v1.2.1 From 2a7f82f8e4b0a89653cd21a3a1c34b51f3b4c141 Mon Sep 17 00:00:00 2001 From: the-undefined Date: Sun, 2 Oct 2016 14:42:36 +0100 Subject: Add Container Registry on/off status to admin area Display the on/off icon under the "Features" section of the admin area for the Container Registry. Closes !22805 --- CHANGELOG | 1 + app/views/admin/dashboard/index.html.haml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 58f1e4a59c2..0be70f669c0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,6 +43,7 @@ v 8.13.0 (unreleased) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) + - Add Container Registry on/off status to Admin Area !6638 (the-undefined) v 8.12.4 (unreleased) diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index e6687f43816..90798c47d97 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -63,6 +63,11 @@ Reply by email %span.light.pull-right = boolean_to_icon Gitlab::IncomingEmail.enabled? + %p + Container Registry + %span.light.pull-right + = boolean_to_icon Gitlab.config.registry.enabled + .col-md-4 %h4 Components -- cgit v1.2.1 From 3158f57dba6dcef3e586ae8fced7deb6fdbd6dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 28 Jul 2016 19:31:17 +0200 Subject: Improve Members::DestroyService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/concerns/membership_actions.rb | 10 +- app/controllers/groups/group_members_controller.rb | 5 +- .../projects/project_members_controller.rb | 5 +- app/services/members/destroy_service.rb | 38 +++++-- lib/api/access_requests.rb | 4 +- lib/api/members.rb | 10 +- spec/requests/api/access_requests_spec.rb | 14 ++- spec/services/members/destroy_service_spec.rb | 112 +++++++++++++-------- 8 files changed, 131 insertions(+), 67 deletions(-) diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index b8ed2c159a7..e40c8cab4a9 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -15,18 +15,16 @@ module MembershipActions end def leave - @member = membershipable.members.find_by(user_id: current_user) || - membershipable.requesters.find_by(user_id: current_user) - Members::DestroyService.new(@member, current_user).execute + Members::DestroyService.new(membershipable, current_user, user_id: current_user.id).execute(:all) - source_type = @member.real_source_type.humanize(capitalize: false) + source_type = membershipable.class.to_s.humanize(capitalize: false) notice = if @member.request? "Your access request to the #{source_type} has been withdrawn." else - "You left the \"#{@member.source.human_name}\" #{source_type}." + "You left the \"#{membershipable.human_name}\" #{source_type}." end - redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize] + redirect_path = @member.request? ? @member.source : [:dashboard, membershipable.class.to_s.tableize] redirect_to redirect_path, notice: notice end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 9c323d7705a..f9368303d54 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -40,10 +40,7 @@ class Groups::GroupMembersController < Groups::ApplicationController end def destroy - @group_member = @group.members.find_by(id: params[:id]) || - @group.requesters.find_by(id: params[:id]) - - Members::DestroyService.new(@group_member, current_user).execute + Members::DestroyService.new(@group, current_user, user_id: params[:id]).execute(:all) respond_to do |format| format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 2343c7d20ec..1c07dd2e18b 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -55,10 +55,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def destroy - @project_member = @project.members.find_by(id: params[:id]) || - @project.requesters.find_by(id: params[:id]) - - Members::DestroyService.new(@project_member, current_user).execute + Members::DestroyService.new(@project, current_user, user_id: params[:id]).execute(:all) respond_to do |format| format.html do diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index 9a2bf82ef51..b3d79d577bd 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -1,17 +1,41 @@ module Members class DestroyService < BaseService - attr_accessor :member, :current_user + include MembersHelper - def initialize(member, current_user) - @member = member + attr_accessor :source + + ALLOWED_SCOPES = %i[members requesters all] + + def initialize(source, current_user, params = {}) + @source = source @current_user = current_user + @params = params end - def execute - unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member) - raise Gitlab::Access::AccessDeniedError - end + def execute(scope = :members) + raise "scope :#{scope} is not allowed!" unless ALLOWED_SCOPES.include?(scope) + + member = find_member(scope) + + raise Gitlab::Access::AccessDeniedError if cannot_destroy_member?(member) + AuthorizedDestroyService.new(member, current_user).execute end + + private + + def find_member(scope) + case scope + when :all + source.members.find_by(user_id: params[:user_id]) || + source.requesters.find_by!(user_id: params[:user_id]) + else + source.public_send(scope).find_by!(user_id: params[:user_id]) + end + end + + def cannot_destroy_member?(member) + !member || !can?(current_user, action_member_permission(:destroy, member), member) + end end end diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 7b9de7c9598..75be24efd59 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -75,9 +75,7 @@ module API required_attributes! [:user_id] source = find_source(source_type, params[:id]) - access_requester = source.requesters.find_by!(user_id: params[:user_id]) - - ::Members::DestroyService.new(access_requester, current_user).execute + ::Members::DestroyService.new(source, current_user, declared(params)).execute(:requesters) end end end diff --git a/lib/api/members.rb b/lib/api/members.rb index a18ce769e29..03dbf4eabb8 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -65,6 +65,14 @@ module API # for both project and group members in 9.0! conflict!('Member already exists') if source_type == 'group' && member + access_requester = source.requesters.find_by(user_id: params[:user_id]) + if access_requester + # We delete a potential access requester before creating the new member. + # We pass current_user = access_requester so that the requester doesn't + # receive a "access denied" email. + ::Members::DestroyService.new(source, access_requester.user, params).execute(:requesters) + end + unless member member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at]) end @@ -134,7 +142,7 @@ module API if member.nil? { message: "Access revoked", id: params[:user_id].to_i } else - ::Members::DestroyService.new(member, current_user).execute + ::Members::DestroyService.new(source, current_user, params).execute present member.user, with: Entities::Member, member: member end diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index 905a7311372..b7e5c2af82a 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -195,7 +195,7 @@ describe API::AccessRequests, api: true do end context 'when authenticated as the access requester' do - it 'returns 200' do + it 'deletes the access requester' do expect do delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}", access_requester) @@ -205,7 +205,7 @@ describe API::AccessRequests, api: true do end context 'when authenticated as a master/owner' do - it 'returns 200' do + it 'deletes the access requester' do expect do delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}", master) @@ -213,6 +213,16 @@ describe API::AccessRequests, api: true do end.to change { source.requesters.count }.by(-1) end + context 'user_id matches a member' do + it 'returns 404' do + expect do + delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{developer.id}", master) + + expect(response).to have_http_status(404) + end.not_to change { source.requesters.count } + end + end + context 'user_id does not match an existing access requester' do it 'returns 404' do expect do diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 2395445e7fd..06a6b0083c9 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -2,70 +2,102 @@ require 'spec_helper' describe Members::DestroyService, services: true do let(:user) { create(:user) } - let(:project) { create(:project) } - let!(:member) { create(:project_member, source: project) } + let(:member_user) { create(:user) } + let(:project) { create(:project, :public) } + let(:group) { create(:group, :public) } - context 'when member is nil' do - before do - project.team << [user, :developer] + shared_examples 'a service raising ActiveRecord::RecordNotFound' do + it 'raises ActiveRecord::RecordNotFound' do + expect { described_class.new(source, user, params).execute }.to raise_error(ActiveRecord::RecordNotFound) end + end - it 'does not destroy the member' do - expect { destroy_member(nil, user) }.to raise_error(Gitlab::Access::AccessDeniedError) + shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do + it 'raises Gitlab::Access::AccessDeniedError' do + expect { described_class.new(source, user, params).execute }.to raise_error(Gitlab::Access::AccessDeniedError) end end - context 'when current user cannot destroy the given member' do - before do - project.team << [user, :developer] + shared_examples 'a service destroying a member' do + it 'destroys the member' do + expect { described_class.new(source, user, params).execute }.to change { source.members.count }.by(-1) end - it 'does not destroy the member' do - expect { destroy_member(member, user) }.to raise_error(Gitlab::Access::AccessDeniedError) + context 'when the given member is an access requester' do + before do + source.members.find_by(user_id: member_user).destroy + source.request_access(member_user) + end + let(:access_requester) { source.requesters.find_by(user_id: member_user) } + + it_behaves_like 'a service raising ActiveRecord::RecordNotFound' + + %i[requesters all].each do |scope| + context "and #{scope} scope is passed" do + it 'destroys the access requester' do + expect { described_class.new(source, user, params).execute(scope) }.to change { source.requesters.count }.by(-1) + end + + it 'calls Member#after_decline_request' do + expect_any_instance_of(NotificationService).to receive(:decline_access_request).with(access_requester) + + described_class.new(source, user, params).execute(scope) + end + + context 'when current user is the member' do + it 'does not call Member#after_decline_request' do + expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(access_requester) + + described_class.new(source, member_user, params).execute(scope) + end + end + end + end end end - context 'when current user can destroy the given member' do - before do - project.team << [user, :master] + context 'when no member are found' do + let(:params) { { user_id: 42 } } + + it_behaves_like 'a service raising ActiveRecord::RecordNotFound' do + let(:source) { project } end - it 'destroys the member' do - destroy_member(member, user) + it_behaves_like 'a service raising ActiveRecord::RecordNotFound' do + let(:source) { group } + end + end - expect(member).to be_destroyed + context 'when a member is found' do + before do + project.team << [member_user, :developer] + group.add_developer(member_user) end + let(:params) { { user_id: member_user.id } } - context 'when the given member is a requester' do - before do - member.update_column(:requested_at, Time.now) + context 'when current user cannot destroy the given member' do + it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do + let(:source) { project } end - it 'calls Member#after_decline_request' do - expect_any_instance_of(NotificationService).to receive(:decline_access_request).with(member) - - destroy_member(member, user) + it_behaves_like 'a service raising Gitlab::Access::AccessDeniedError' do + let(:source) { group } end + end - context 'when current user is the member' do - it 'does not call Member#after_decline_request' do - expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member) - - destroy_member(member, member.user) - end + context 'when current user can destroy the given member' do + before do + project.team << [user, :master] + group.add_owner(user) end - context 'when current user is the member and ' do - it 'does not call Member#after_decline_request' do - expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member) + it_behaves_like 'a service destroying a member' do + let(:source) { project } + end - destroy_member(member, member.user) - end + it_behaves_like 'a service destroying a member' do + let(:source) { group } end end end - - def destroy_member(member, user) - Members::DestroyService.new(member, user).execute - end end -- cgit v1.2.1 From c8b1311934935c7ac7fd901558e19ac496fbad2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 9 Sep 2016 18:51:31 +0200 Subject: Fix a few things after the initial improvment to Members::DestroyService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/controllers/concerns/membership_actions.rb | 7 ++++--- app/controllers/groups/group_members_controller.rb | 2 +- app/controllers/projects/project_members_controller.rb | 3 ++- app/services/members/authorized_destroy_service.rb | 2 ++ app/services/members/destroy_service.rb | 11 ++++++----- lib/api/access_requests.rb | 3 ++- spec/controllers/groups/group_members_controller_spec.rb | 4 ++-- spec/controllers/projects/project_members_controller_spec.rb | 4 ++-- spec/services/members/destroy_service_spec.rb | 9 +++++++++ 9 files changed, 30 insertions(+), 15 deletions(-) diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index e40c8cab4a9..c13333641d3 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -15,16 +15,17 @@ module MembershipActions end def leave - Members::DestroyService.new(membershipable, current_user, user_id: current_user.id).execute(:all) + member = Members::DestroyService.new(membershipable, current_user, user_id: current_user.id). + execute(:all) source_type = membershipable.class.to_s.humanize(capitalize: false) notice = - if @member.request? + if member.request? "Your access request to the #{source_type} has been withdrawn." else "You left the \"#{membershipable.human_name}\" #{source_type}." end - redirect_path = @member.request? ? @member.source : [:dashboard, membershipable.class.to_s.tableize] + redirect_path = member.request? ? member.source : [:dashboard, membershipable.class.to_s.tableize] redirect_to redirect_path, notice: notice end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index f9368303d54..18cd800c619 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -40,7 +40,7 @@ class Groups::GroupMembersController < Groups::ApplicationController end def destroy - Members::DestroyService.new(@group, current_user, user_id: params[:id]).execute(:all) + Members::DestroyService.new(@group, current_user, id: params[:id]).execute(:all) respond_to do |format| format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 1c07dd2e18b..f56b256984b 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -55,7 +55,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def destroy - Members::DestroyService.new(@project, current_user, user_id: params[:id]).execute(:all) + Members::DestroyService.new(@project, current_user, params). + execute(:all) respond_to do |format| format.html do diff --git a/app/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb index ca9db59cac7..b7a244c2029 100644 --- a/app/services/members/authorized_destroy_service.rb +++ b/app/services/members/authorized_destroy_service.rb @@ -14,6 +14,8 @@ module Members if member.request? && member.user != user notification_service.decline_access_request(member) end + + member end end end diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index b3d79d577bd..ee072065523 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -15,7 +15,7 @@ module Members def execute(scope = :members) raise "scope :#{scope} is not allowed!" unless ALLOWED_SCOPES.include?(scope) - member = find_member(scope) + member = find_member!(scope) raise Gitlab::Access::AccessDeniedError if cannot_destroy_member?(member) @@ -24,13 +24,14 @@ module Members private - def find_member(scope) + def find_member!(scope) + condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] } case scope when :all - source.members.find_by(user_id: params[:user_id]) || - source.requesters.find_by!(user_id: params[:user_id]) + source.members.find_by(condition) || + source.requesters.find_by!(condition) else - source.public_send(scope).find_by!(user_id: params[:user_id]) + source.public_send(scope).find_by!(condition) end end diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 75be24efd59..d3db7740830 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -75,7 +75,8 @@ module API required_attributes! [:user_id] source = find_source(source_type, params[:id]) - ::Members::DestroyService.new(source, current_user, declared(params)).execute(:requesters) + ::Members::DestroyService.new(source, current_user, params). + execute(:requesters) end end end diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 92b97bf3d0c..a0870891cf4 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -87,10 +87,10 @@ describe Groups::GroupMembersController do context 'when member is not found' do before { sign_in(user) } - it 'returns 403' do + it 'returns 404' do delete :leave, group_id: group - expect(response).to have_http_status(403) + expect(response).to have_http_status(404) end end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 5e2a8cf3849..074f85157de 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -135,11 +135,11 @@ describe Projects::ProjectMembersController do context 'when member is not found' do before { sign_in(user) } - it 'returns 403' do + it 'returns 404' do delete :leave, namespace_id: project.namespace, project_id: project - expect(response).to have_http_status(403) + expect(response).to have_http_status(404) end end diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 06a6b0083c9..9995f3488af 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -98,6 +98,15 @@ describe Members::DestroyService, services: true do it_behaves_like 'a service destroying a member' do let(:source) { group } end + + context 'when given a :id' do + let(:params) { { id: project.members.find_by!(user_id: user.id).id } } + + it 'destroys the member' do + expect { described_class.new(project, user, params).execute }. + to change { project.members.count }.by(-1) + end + end end end end -- cgit v1.2.1 From e9d7b4f765605cfe25c2c4d8729e3d34cf5a979b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 16 Sep 2016 13:37:21 +0200 Subject: Invert method's naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/services/members/destroy_service.rb | 6 +++--- spec/requests/api/access_requests_spec.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index ee072065523..431da8372c9 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -17,7 +17,7 @@ module Members member = find_member!(scope) - raise Gitlab::Access::AccessDeniedError if cannot_destroy_member?(member) + raise Gitlab::Access::AccessDeniedError unless can_destroy_member?(member) AuthorizedDestroyService.new(member, current_user).execute end @@ -35,8 +35,8 @@ module Members end end - def cannot_destroy_member?(member) - !member || !can?(current_user, action_member_permission(:destroy, member), member) + def can_destroy_member?(member) + member && can?(current_user, action_member_permission(:destroy, member), member) end end end diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index b7e5c2af82a..b467890a403 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -213,7 +213,7 @@ describe API::AccessRequests, api: true do end.to change { source.requesters.count }.by(-1) end - context 'user_id matches a member' do + context 'user_id matches a member, not an access requester' do it 'returns 404' do expect do delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{developer.id}", master) -- cgit v1.2.1 From 735731a565e593fa8df445b3bb2ac2f768fb1fae Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Oct 2016 16:24:00 +0100 Subject: Tests update --- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- spec/features/issues/filter_issues_spec.rb | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index d537fd6712a..92d5cb307ae 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -15,7 +15,7 @@ - if selected - selected.each do |label| - = hidden_field_tag data_options[:field_name], use_id ? label.try(:id) : u(label.try(:title)), id: nil + = hidden_field_tag data_options[:field_name], use_id ? label.try(:id) : label.try(:title), id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 32aaa0f1037..78208aed46d 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -110,30 +110,37 @@ describe 'Filter issues', feature: true do end it "filters by `won't fix` and another label" do - find('.dropdown-menu-labels a', text: label.title).click page.within '.labels-filter' do - expect(page).to have_content wontfix.title click_link wontfix.title + expect(page).to have_content wontfix.title + click_link label.title end - expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title) + expect(find('.js-label-select .dropdown-toggle-text')).to have_content("#{wontfix.title} +1 more") end it "filters by `won't fix` label followed by another label after page load" do - find('.dropdown-menu-labels a', text: wontfix.title).click - # Close label dropdown to load + page.within '.labels-filter' do + click_link wontfix.title + expect(page).to have_content wontfix.title + end + find('body').click + expect(find('.filtered-labels')).to have_content(wontfix.title) find('.js-label-select').click wait_for_ajax find('.dropdown-menu-labels a', text: label.title).click - # Close label dropdown to load + find('body').click + + expect(find('.filtered-labels')).to have_content(wontfix.title) expect(find('.filtered-labels')).to have_content(label.title) find('.js-label-select').click wait_for_ajax + expect(find('.dropdown-menu-labels li', text: wontfix.title)).to have_css('.is-active') expect(find('.dropdown-menu-labels li', text: label.title)).to have_css('.is-active') end -- cgit v1.2.1 From bdb0af25091b2ca559d7bfebc00d0194d199af50 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 13 Sep 2016 11:25:50 -0500 Subject: Fix todos page mobile viewport layout --- CHANGELOG | 1 + app/assets/stylesheets/pages/todos.scss | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 58f1e4a59c2..0ab2bb7516c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.13.0 (unreleased) - Allow the Koding integration to be configured through the API - Added soft wrap button to repository file/blob editor - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) + - Fix todos page mobile viewport layout (ClemMakesApps) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Add configurable email subject suffix (Fu Xu) diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 68a5d1ae06c..ea76fe18876 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -51,6 +51,7 @@ -webkit-flex-direction: column; flex-direction: column; margin-left: 10px; + min-width: 55px; } .todo-item { @@ -120,6 +121,14 @@ } } +@media (max-width: $screen-sm-max) { + .todos-filters { + .dropdown-menu-toggle { + width: 135px; + } + } +} + @media (max-width: $screen-xs-max) { .todo { .avatar { @@ -141,4 +150,14 @@ padding-left: 10px; } } + + .todos-filters { + .row-content-block { + padding-bottom: 50px; + } + + .dropdown-menu-toggle { + width: 100%; + } + } } -- cgit v1.2.1 From ad2b6cae0b299868eba9f8acf76bafe919408ccd Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Thu, 1 Sep 2016 07:58:09 -0400 Subject: Append issue template to existing description Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/21733 Add two newlines before the template if the existing description isn't empty. This makes it easier to see where the template begins. Don't append the template when "Reset template" is selected, of course. Don't append template if it equals the existing description. This makes it so that selecting a template twice doesn't duplicate it. --- CHANGELOG | 1 + app/assets/javascripts/blob/template_selector.js | 9 +++++-- .../templates/issuable_template_selector.js.es6 | 14 +++++----- spec/features/projects/issuable_templates_spec.rb | 30 ++++++++++++++++++---- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 58f1e4a59c2..ca2998039db 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.13.0 (unreleased) - Only update issuable labels if they have been changed - Take filters in account in issuable counters. !6496 - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) + - Append issue template to existing description !6149 (Joseph Frazier) - Revoke button in Applications Settings underlines on hover. - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - Fix Long commit messages overflow viewport in file tree diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index 95352164d76..26852aadea5 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -72,8 +72,13 @@ // To be implemented on the extending class // e.g. // Api.gitignoreText item.name, @requestFileSuccess.bind(@) - TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) { - this.editor.setValue(file.content, 1); + TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus, append) { + var oldValue = this.editor.getValue(); + var newValue = file.content; + if (append && oldValue.length && oldValue !== newValue) { + newValue = oldValue + '\n\n' + newValue; + } + this.editor.setValue(newValue, 1); if (!skipFocus) this.editor.focus(); if (this.editor instanceof jQuery) { diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index c32ddf80219..a1ca1c1941c 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -16,7 +16,7 @@ if (initialQuery.name) this.requestFile(initialQuery); $('.reset-template', this.dropdown.parent()).on('click', () => { - if (this.currentTemplate) this.setInputValueToTemplateContent(); + if (this.currentTemplate) this.setInputValueToTemplateContent(false); }); } @@ -26,22 +26,24 @@ this.currentTemplate = currentTemplate; if (err) return; // Error handled by global AJAX error handler this.stopLoadingSpinner(); - this.setInputValueToTemplateContent(); + this.setInputValueToTemplateContent(true); }); return; } - setInputValueToTemplateContent() { + setInputValueToTemplateContent(append) { // `this.requestFileSuccess` sets the value of the description input field - // to the content of the template selected. + // to the content of the template selected. If `append` is true, the + // template content will be appended to the previous value of the field, + // separated by a blank line if the previous value is non-empty. if (this.titleInput.val() === '') { // If the title has not yet been set, focus the title input and // skip focusing the description input by setting `true` as the 2nd // argument to `requestFileSuccess`. - this.requestFileSuccess(this.currentTemplate, true); + this.requestFileSuccess(this.currentTemplate, true, append); this.titleInput.focus(); } else { - this.requestFileSuccess(this.currentTemplate); + this.requestFileSuccess(this.currentTemplate, false, append); } return; } diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index f76c4fe8b57..cd79c4f512d 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -26,7 +26,7 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects "bug" template' do select_template 'bug' wait_for_ajax - preview_template + preview_template(template_content) save_changes end @@ -42,6 +42,26 @@ feature 'issuable templates', feature: true, js: true do end end + context 'user creates an issue using templates, with a prior description' do + let(:prior_description) { 'test issue description' } + let(:template_content) { 'this is a test "bug" template' } + let(:issue) { create(:issue, author: user, assignee: user, project: project) } + + background do + project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false) + visit edit_namespace_project_issue_path project.namespace, project, issue + fill_in :'issue[title]', with: 'test issue title' + fill_in :'issue[description]', with: prior_description + end + + scenario 'user selects "bug" template' do + select_template 'bug' + wait_for_ajax + preview_template("#{prior_description}\n\n#{template_content}") + save_changes + end + end + context 'user creates a merge request using templates' do let(:template_content) { 'this is a test "feature-proposal" template' } let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) } @@ -55,7 +75,7 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects "feature-proposal" template' do select_template 'feature-proposal' wait_for_ajax - preview_template + preview_template(template_content) save_changes end end @@ -82,16 +102,16 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects template' do select_template 'feature-proposal' wait_for_ajax - preview_template + preview_template(template_content) save_changes end end end end - def preview_template + def preview_template(expected_content) click_link 'Preview' - expect(page).to have_content template_content + expect(page).to have_content expected_content end def save_changes -- cgit v1.2.1 From e12f7a3b71a7203397aceb78ba3b27f359a4e072 Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Thu, 1 Sep 2016 12:44:10 -0400 Subject: Combine requestFileSuccess arguments into `opts` See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6149#note_14830172 --- app/assets/javascripts/blob/template_selector.js | 9 ++++++--- .../javascripts/templates/issuable_template_selector.js.es6 | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index 26852aadea5..6d41442cdfc 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -72,14 +72,17 @@ // To be implemented on the extending class // e.g. // Api.gitignoreText item.name, @requestFileSuccess.bind(@) - TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus, append) { + TemplateSelector.prototype.requestFileSuccess = function(file, opts) { var oldValue = this.editor.getValue(); var newValue = file.content; - if (append && oldValue.length && oldValue !== newValue) { + if (opts == null) { + opts = {}; + } + if (opts.append && oldValue.length && oldValue !== newValue) { newValue = oldValue + '\n\n' + newValue; } this.editor.setValue(newValue, 1); - if (!skipFocus) this.editor.focus(); + if (!opts.skipFocus) this.editor.focus(); if (this.editor instanceof jQuery) { this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index a1ca1c1941c..017008c8438 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -38,12 +38,12 @@ // separated by a blank line if the previous value is non-empty. if (this.titleInput.val() === '') { // If the title has not yet been set, focus the title input and - // skip focusing the description input by setting `true` as the 2nd - // argument to `requestFileSuccess`. - this.requestFileSuccess(this.currentTemplate, true, append); + // skip focusing the description input by setting `true` as the + // `skipFocus` option to `requestFileSuccess`. + this.requestFileSuccess(this.currentTemplate, {skipFocus: true, append}); this.titleInput.focus(); } else { - this.requestFileSuccess(this.currentTemplate, false, append); + this.requestFileSuccess(this.currentTemplate, {skipFocus: false, append}); } return; } -- cgit v1.2.1 From 1592d57c18ba905d7bd1643ab6af56c902d709c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 3 Oct 2016 18:43:33 +0200 Subject: Remove useless code now that Member#add_user handles it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/members.rb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/api/members.rb b/lib/api/members.rb index 03dbf4eabb8..34df55fe192 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -65,14 +65,6 @@ module API # for both project and group members in 9.0! conflict!('Member already exists') if source_type == 'group' && member - access_requester = source.requesters.find_by(user_id: params[:user_id]) - if access_requester - # We delete a potential access requester before creating the new member. - # We pass current_user = access_requester so that the requester doesn't - # receive a "access denied" email. - ::Members::DestroyService.new(source, access_requester.user, params).execute(:requesters) - end - unless member member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at]) end -- cgit v1.2.1 From d221a2c068ef07c3452566add692115142bb313a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 3 Oct 2016 18:59:41 +0200 Subject: Fix markdown URL link --- doc/user/project/cycle_analytics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index 84231f879bf..00751099690 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -6,7 +6,7 @@ This the first iteration of Cycle Analytics, you can follow the following issue to track the changes that are coming to this feature: [#20975][ce-20975]. -Cycle Analytics measures the time it takes to go from [an idea to production] for +Cycle Analytics measures the time it takes to go from an [idea to production] for each project you have. This is achieved by not only indicating the total time it takes to reach at that point, but the total time is broken down into the multiple stages an idea has to pass through to be shipped. -- cgit v1.2.1 From 0c8b9edcd02f174facd4f38fa91797d0e670820f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 3 Oct 2016 19:00:09 +0200 Subject: Clarify what it means Test stage is not calculated in Total --- doc/user/project/cycle_analytics.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index 00751099690..632e40c1c9c 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -41,7 +41,10 @@ You can see that there are seven stages in total: - Median time from when the merge request got merged until the deploy to production (production is last stage/environment) - **Production** (Total) - - Sum of all the above stages excluding the Test (CI) time + - Sum of all the above stages excluding the Test (CI) time. To clarify, it's + not so much that CI time is "excluded", but rather CI time is already + counted in the review stage since CI is done automatically. Most of the + other stages are purely sequential, but **Test** is not. ## How the data is measured -- cgit v1.2.1 From f9b267d1b4ddcabfd96fbe761b513067b88ce142 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 3 Oct 2016 19:01:47 +0200 Subject: Clarify Plan and Code stages more --- doc/user/project/cycle_analytics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index 632e40c1c9c..40dafb0fae3 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -61,8 +61,8 @@ Below you can see in more detail what the various stages of Cycle Analytics mean | **Stage** | **Description** | | --------- | --------------- | | Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list][board] created for it. | -| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit is the one that triggers the separation between **Plan** and **Code** and at least one commit needs to contain the [issue closing pattern] in order to know what branch and commit is related to the issue (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this commit). The closing pattern doesn't necessarily need to be in the first commit message. If there is no commit containing the issue closing pattern, it is not considered to the measurement time of the stage. | -| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request related to that commit. The key to keep the process tracked is include the [issue closing pattern] to the description of the merge request. | +| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit of the branch is the one that triggers the separation between **Plan** and **Code**, and at least one of the commits in the branch needs to contain the related issue number (e.g., `#42`). If there is no commit containing the related issue number, it is not considered to the measurement time of the stage. | +| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request related to that commit. The key to keep the process tracked is to include the [issue closing pattern] to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the issue closing pattern is not present in the merge request description, the MR is not considered to the measurement time of the stage. | | Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. `master` is not excluded. It does not attempt to track time for any particular stages. | | Review | Measures the median time taken to review the merge request, between its creation and until it's merged. | | Staging | Measures the median time between merging the merge request until the very first deployment to production. It's tracked by the [environment] set to `production` in your GitLab CI configuration. If there isn't a `production` environment, this is not tracked. | -- cgit v1.2.1 From 548b919c3d7432b37e69281c090df63d29b4177a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 3 Oct 2016 19:05:33 +0200 Subject: Clarify that the `production` environment is case-sensitive Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/22533 --- doc/user/project/cycle_analytics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index 40dafb0fae3..eb9a0b8e2e3 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -65,7 +65,7 @@ Below you can see in more detail what the various stages of Cycle Analytics mean | Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request related to that commit. The key to keep the process tracked is to include the [issue closing pattern] to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the issue closing pattern is not present in the merge request description, the MR is not considered to the measurement time of the stage. | | Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. `master` is not excluded. It does not attempt to track time for any particular stages. | | Review | Measures the median time taken to review the merge request, between its creation and until it's merged. | -| Staging | Measures the median time between merging the merge request until the very first deployment to production. It's tracked by the [environment] set to `production` in your GitLab CI configuration. If there isn't a `production` environment, this is not tracked. | +| Staging | Measures the median time between merging the merge request until the very first deployment to production. It's tracked by the [environment] set to `production` (case-sensitive, `Production` won't work) in your GitLab CI configuration. If there isn't a `production` environment, this is not tracked. | | Production| The sum of all time taken to run the entire process, from issue creation to deploying the code to production. | --- -- cgit v1.2.1 From b39649d9758ec4039fdf4ce6c15be5a6956431db Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 3 Oct 2016 19:05:51 +0200 Subject: Take a new screenshot for Cycle Analytics --- .../project/img/cycle_analytics_landing_page.png | Bin 58203 -> 66080 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/user/project/img/cycle_analytics_landing_page.png b/doc/user/project/img/cycle_analytics_landing_page.png index 4fa42c87395..b212134d5ed 100644 Binary files a/doc/user/project/img/cycle_analytics_landing_page.png and b/doc/user/project/img/cycle_analytics_landing_page.png differ -- cgit v1.2.1 From 218331e6af8548eb796870de34e73dd43c78644c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 4 Oct 2016 02:36:16 +0800 Subject: Add an entry in CHANGELOG [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 7ffc74f4ec0..7c9cb58438b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ v 8.13.0 (unreleased) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) + - Fix that manual jobs would no longer block jobs in the next stage. !6604 - Use a ConnectionPool for Rails.cache on Sidekiq servers - Replace `alias_method_chain` with `Module#prepend` - Enable GitLab Import/Export for non-admin users. -- cgit v1.2.1 From 4ae845d3dd21d2848fc76a79370c048b08aa081c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 4 Oct 2016 02:55:08 +0800 Subject: Update plain text version failed email --- app/views/notify/pipeline_failed_email.text.erb | 45 ++++++++++++++++--------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb index 88779244a4b..f6d6f3119d3 100644 --- a/app/views/notify/pipeline_failed_email.text.erb +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -1,15 +1,30 @@ -Project: <%= @project.path_with_namespace %> -Branch: <%= @pipeline.ref %> -Commit: <%= @pipeline.short_sha %> (<%= @pipeline.sha %>) -Commit Message: <%= @pipeline.git_commit_message %> -Commit Author: <%= @pipeline.git_author_name %> -Pusher: <%= @pipeline.user.try(:name) %> -<% failed = @pipeline.statuses.latest.failed %> -Pipeline #<%= @pipeline.id %> had <%= failed.size %> failed <%= 'job'.pluralize(failed.size) %>. - -<% failed.each do |job| %> -ID: <%= job.id %> -Stage: <%= job.stage %> -Name: <%= job.name %> -Trace: <%= job.trace_with_state[:html] %> -<% end %> +Uh oh, your CI pipeline has failed. + +Project: <%= @project.name %> ( <%= namespace_project_url(@project.namespace, @project) %> ) +Branch: <%= @pipeline.ref %> ( <%= namespace_project_commits_url(@project.namespace, @project, @pipeline.ref) %> ) +<% if @merge_request -%> +Merge Request: <%= @merge_request.to_reference %> ( <%= namespace_project_merge_request_url(@project.namespace, @project, @merge_request) %> ) +<% end -%> + +Commit: <%= @pipeline.short_sha %> ( <%= namespace_project_commit_url(@project.namespace, @project, @pipeline.sha) %> ) +Commit Message: <%= @pipeline.git_commit_message.truncate(50) %> +<% commit = @pipeline.commit -%> +<% if commit.author.nil? -%> +Commit Author: <%= commit.author_name %> +<% else -%> +Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> ) +<% end -%> + +<% failed = @pipeline.statuses.latest.failed -%> +Pipeline #<%= @pipeline.id %> ( <%= namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id) %> ) had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>. + +<% failed.each do |build| -%> +Build #<%= build.id %> ( <%= namespace_project_build_url(@project.namespace, @project, build.id) %> ) +Stage: <%= build.stage %> +Name: <%= build.name %> +Trace: <%= build.trace_with_state(last_lines: 10)[:text] %> + +<% end -%> + +Manage all notifications: <%= profile_notifications_url %> +Help: <%= help_url %> -- cgit v1.2.1 From f6cef1337dd30b35b1334debe61303fc60aa39b6 Mon Sep 17 00:00:00 2001 From: Sander Dalemans Date: Mon, 3 Oct 2016 19:02:15 +0000 Subject: Fix small typo in using_docker_images.md, file is called `build_script` (using underscore) --- doc/ci/docker/using_docker_images.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index a849905ac6b..520c8b36a95 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -221,7 +221,7 @@ time. *Note: The following commands are run without root privileges. You should be able to run docker with your regular user account.* -First start with creating a file named `build script`: +First start with creating a file named `build_script`: ```bash cat < build_script -- cgit v1.2.1 From 0cc3fc562738b88875e9ab2e871fdd64cc616705 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 4 Oct 2016 04:16:14 +0800 Subject: Add pipeline_email_service. Fixes: https://gitlab.com/gitlab-org/gitlab-ce/builds/4710913 --- spec/lib/gitlab/import_export/all_models.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 006569254a6..9002c6bc6da 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -125,6 +125,7 @@ project: - drone_ci_service - emails_on_push_service - builds_email_service +- pipelines_email_service - irker_service - pivotaltracker_service - hipchat_service @@ -184,4 +185,4 @@ project: - project_feature award_emoji: - awardable -- user \ No newline at end of file +- user -- cgit v1.2.1 From f571aeb5ce38dce1a9e5f58d76360836d4a8f8a0 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 3 Oct 2016 15:54:52 -0600 Subject: Upgrade acts-as-taggable-on from 3.5.0 to 4.0.0. Changelog: https://github.com/mbleigh/acts-as-taggable-on/blob/master/CHANGELOG.md#400--2016-08-08 --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index d7de92bdcec..d54beedf67e 100644 --- a/Gemfile +++ b/Gemfile @@ -130,7 +130,7 @@ gem 'state_machines-activerecord', '~> 0.4.0' gem 'after_commit_queue', '~> 1.3.0' # Issue tags -gem 'acts-as-taggable-on', '~> 3.4' +gem 'acts-as-taggable-on', '~> 4.0' # Background jobs gem 'sidekiq', '~> 4.2' diff --git a/Gemfile.lock b/Gemfile.lock index ca06b21ae65..6f8a8236866 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -44,8 +44,8 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - acts-as-taggable-on (3.5.0) - activerecord (>= 3.2, < 5) + acts-as-taggable-on (4.0.0) + activerecord (>= 4.0) addressable (2.3.8) after_commit_queue (1.3.0) activerecord (>= 3.0) @@ -802,7 +802,7 @@ DEPENDENCIES RedCloth (~> 4.3.2) ace-rails-ap (~> 4.1.0) activerecord-session_store (~> 1.0.0) - acts-as-taggable-on (~> 3.4) + acts-as-taggable-on (~> 4.0) addressable (~> 2.3.8) after_commit_queue (~> 1.3.0) akismet (~> 2.0) @@ -986,4 +986,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.13.1 + 1.13.2 -- cgit v1.2.1 From 45bfadbcbf0eec5bf7c691b13751f684e3c62c5c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 30 Sep 2016 11:53:44 -0300 Subject: Fix bug when trying to cache closed issues from external issue trackers --- CHANGELOG | 1 + app/models/merge_request.rb | 4 +++ spec/models/merge_request_spec.rb | 24 +++++++++++++++ spec/services/merge_requests/merge_service_spec.rb | 36 ++++++++++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 58f1e4a59c2..b26f797fff2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.13.0 (unreleased) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) v 8.12.4 (unreleased) + - Fix type mismatch bug when closing Jira issue v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a431d46cc9e..071dfe54ef9 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -523,9 +523,13 @@ class MergeRequest < ActiveRecord::Base # `MergeRequestsClosingIssues` model. This is a performance optimization. # Calculating this information for a number of merge requests requires # running `ReferenceExtractor` on each of them separately. + # This optimization does not apply to issues from external sources. def cache_merge_request_closes_issues!(current_user = self.author) + return if project.has_external_issue_tracker? + transaction do self.merge_requests_closing_issues.delete_all + closes_issues(current_user).each do |issue| self.merge_requests_closing_issues.create!(issue: issue) end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 9d7be2429ed..556b4c1efc3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -86,6 +86,30 @@ describe MergeRequest, models: true do end end + describe '#cache_merge_request_closes_issues!' do + before do + subject.project.team << [subject.author, :developer] + subject.target_branch = subject.project.default_branch + end + + it 'caches closed issues' do + issue = create :issue, project: subject.project + commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") + allow(subject).to receive(:commits).and_return([commit]) + + expect { subject.cache_merge_request_closes_issues! }.to change(subject.merge_requests_closing_issues, :count).by(1) + end + + it 'does not cache issues from external trackers' do + subject.project.update_attribute(:has_external_issue_tracker, true) + issue = ExternalIssue.new('JIRA-123', subject.project) + commit = double('commit1', safe_message: "Fixes #{issue.to_reference}") + allow(subject).to receive(:commits).and_return([commit]) + + expect { subject.cache_merge_request_closes_issues! }.not_to change(subject.merge_requests_closing_issues, :count) + end + end + describe '#source_branch_sha' do let(:last_branch_commit) { subject.source_project.repository.commit(subject.source_branch) } diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 31167675d07..e49a0d5e553 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -38,6 +38,42 @@ describe MergeRequests::MergeService, services: true do end end + context 'closes related issues' do + let(:service) { described_class.new(project, user, commit_message: 'Awesome message') } + + before do + allow(project).to receive(:default_branch).and_return(merge_request.target_branch) + end + + it 'closes GitLab issue tracker issues' do + issue = create :issue, project: project + commit = double('commit', safe_message: "Fixes #{issue.to_reference}") + allow(merge_request).to receive(:commits).and_return([commit]) + + service.execute(merge_request) + + expect(issue.reload.closed?).to be_truthy + end + + context 'with JIRA integration' do + include JiraServiceHelper + + let(:jira_tracker) { project.create_jira_service } + + before { jira_service_settings } + + it 'closes issues on JIRA issue tracker' do + jira_issue = ExternalIssue.new('JIRA-123', project) + commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}") + allow(merge_request).to receive(:commits).and_return([commit]) + + expect_any_instance_of(JiraService).to receive(:close_issue).with(merge_request, jira_issue).once + + service.execute(merge_request) + end + end + end + context 'closes related todos' do let(:merge_request) { create(:merge_request, assignee: user, author: user) } let(:project) { merge_request.project } -- cgit v1.2.1 From 67dcc8db83aa06a70315011a841409bb6e5900dc Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Mon, 3 Oct 2016 16:22:41 -0700 Subject: update profile view --- app/assets/stylesheets/pages/profile.scss | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 0fcdaf94a21..020ef5339d2 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -94,7 +94,7 @@ .profile-user-bio { // Limits the width of the user bio for readability. max-width: 600px; - margin: 15px auto 0; + margin: 10px auto 0; padding: 0 16px; } @@ -220,13 +220,9 @@ margin: 0 auto; .avatar-holder { width: 90px; - display: inline-block; + margin: 0 auto 10px; } .user-info { - display: inline-block; - text-align: left; - vertical-align: middle; - margin-left: 15px; .handle { color: $gl-gray-light; } -- cgit v1.2.1 From d96a89f3b7221696c8964fbf777846b1c41ec239 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Mon, 3 Oct 2016 20:40:22 -0400 Subject: Remove SCSS rules for short hex chars. --- .csscomb.json | 2 +- .scss-lint.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.csscomb.json b/.csscomb.json index 741cc1488b5..aa6a17f7517 100644 --- a/.csscomb.json +++ b/.csscomb.json @@ -6,7 +6,7 @@ "always-semicolon": true, "color-case": "lower", "block-indent": " ", - "color-shorthand": true, + "color-shorthand": false, "element-case": "lower", "space-before-colon": "", "space-after-colon": " ", diff --git a/.scss-lint.yml b/.scss-lint.yml index 66f9975d4ce..71df6be6a15 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -79,7 +79,7 @@ linters: # HEX colors should use three-character values where possible. HexLength: - enabled: true + enabled: false # HEX color values should use lower-case colors to differentiate between # letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`. -- cgit v1.2.1 From d6e42e0ca1ab9962b7d43517c6f6beab5f1ade1c Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 13 Sep 2016 20:55:26 +0200 Subject: GrapeDSL for Namespace endpoint --- lib/api/namespaces.rb | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index 50d3729449e..fe981d7b9fa 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -4,20 +4,18 @@ module API before { authenticate! } resource :namespaces do - # Get a namespaces list - # - # Example Request: - # GET /namespaces + desc 'Get a namespaces list' do + success Entities::Namespace + end + params do + optional :search, type: String, desc: "Search query for namespaces" + end get do - @namespaces = if current_user.admin - Namespace.all - else - current_user.namespaces - end - @namespaces = @namespaces.search(params[:search]) if params[:search].present? - @namespaces = paginate @namespaces + namespaces = current_user.admin ? Namespace.all : current_user.namespaces + + namespaces = namespaces.search(params[:search]) if params[:search].present? - present @namespaces, with: Entities::Namespace + present paginate(namespaces), with: Entities::Namespace end end end -- cgit v1.2.1 From 19af4ca808f11ad1724cdcc2d1197b2dc2df1e3d Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:46:12 +0000 Subject: Add systemctl daemon-reload to fix Ubuntu 16.04.1 warning --- doc/update/8.12-to-8.13.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index 411e4837e20..9db753ccbd3 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -166,6 +166,10 @@ See [smtp_settings.rb.sample] as an example. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 9. Start application -- cgit v1.2.1 From 3a3063b11316f7e672643bf5d85a9f7b5a84178c Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:50:08 +0000 Subject: Update 8.11-to-8.12.md --- doc/update/8.11-to-8.12.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index ee9fb1a2a68..07743d050f7 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -166,6 +166,10 @@ See [smtp_settings.rb.sample] as an example. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 9. Start application -- cgit v1.2.1 From dcd26969dc963aa80775bd62eb6ac8766ee84ec3 Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:51:25 +0000 Subject: Update 8.10-to-8.11.md --- doc/update/8.10-to-8.11.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md index b24d338e3e0..119c5f475e4 100644 --- a/doc/update/8.10-to-8.11.md +++ b/doc/update/8.10-to-8.11.md @@ -158,6 +158,10 @@ See [smtp_settings.rb.sample] as an example. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 9. Start application -- cgit v1.2.1 From 302d426209ade9ebf1e0b915abf2bc30fb591a4c Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:51:58 +0000 Subject: Update 8.9-to-8.10.md --- doc/update/8.9-to-8.10.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md index a057a423e61..bb2c79fbb84 100644 --- a/doc/update/8.9-to-8.10.md +++ b/doc/update/8.9-to-8.10.md @@ -156,6 +156,10 @@ See [smtp_settings.rb.sample] as an example. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 9. Start application -- cgit v1.2.1 From 5976bff223a36bacfe0279307789301a0f724491 Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:52:30 +0000 Subject: Update 8.8-to-8.9.md --- doc/update/8.8-to-8.9.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.8-to-8.9.md b/doc/update/8.8-to-8.9.md index f078a2bece5..aa077316bbe 100644 --- a/doc/update/8.8-to-8.9.md +++ b/doc/update/8.8-to-8.9.md @@ -156,6 +156,10 @@ See [smtp_settings.rb.sample] as an example. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 9. Start application -- cgit v1.2.1 From 6219c36d086ac6ae0a713b7cd9153be7cfcdef14 Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:52:58 +0000 Subject: Update 8.7-to-8.8.md --- doc/update/8.7-to-8.8.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.7-to-8.8.md b/doc/update/8.7-to-8.8.md index 32906650f6f..8ce434e5f78 100644 --- a/doc/update/8.7-to-8.8.md +++ b/doc/update/8.7-to-8.8.md @@ -127,6 +127,10 @@ via [/etc/default/gitlab]. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 8. Start application -- cgit v1.2.1 From 61e9fb55f910de90617d4b011976ddb535e5e951 Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:53:27 +0000 Subject: Update 8.6-to-8.7.md --- doc/update/8.6-to-8.7.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md index cb66ef920bb..05ef4e61759 100644 --- a/doc/update/8.6-to-8.7.md +++ b/doc/update/8.6-to-8.7.md @@ -127,6 +127,10 @@ via [/etc/default/gitlab]. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 8. Start application -- cgit v1.2.1 From 7d46bd6128309e6ef1f3f4785889f5a0fef4de8f Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 30 Sep 2016 15:45:27 +0200 Subject: Save a fetchable ref per deployement --- CHANGELOG | 1 + app/models/deployment.rb | 10 ++++++++-- app/models/environment.rb | 4 ++++ doc/ci/environments.md | 13 +++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 806196d811d..e7c059c14d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.13.0 (unreleased) - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Speed-up group milestones show page + - Keep refs for each deployment - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 07d7e19e70d..cde6598fc7b 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -11,7 +11,7 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true - after_save :keep_around_commit + after_save :create_ref def commit project.commit(sha) @@ -30,7 +30,7 @@ class Deployment < ActiveRecord::Base end def keep_around_commit - project.repository.keep_around(self.sha) + project.repository.fetch_ref(project.repository.path_to_repo, ref, ref_path) end def manual_actions @@ -76,4 +76,10 @@ class Deployment < ActiveRecord::Base where.not(id: self.id). take end + + private + + def ref_path + "#{environment.ref_path}#{id}" + end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 49e0a20640c..843f85883a1 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -47,4 +47,8 @@ class Environment < ActiveRecord::Base def update_merge_request_metrics? self.name == "production" end + + def ref_path + "refs/environments/#{Shellwords.shellescape(name)}/" + end end diff --git a/doc/ci/environments.md b/doc/ci/environments.md index d85b8a34ced..081766d5ee9 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -14,6 +14,19 @@ Defining environments in a project's `.gitlab-ci.yml` lets developers track Deployments are created when [jobs] deploy versions of code to [environments]. +### Checkout deployments locally + +Since 8.13, a reference in the git repository is saved for each deployment. So +knowing what the state is of your current environments is only a `git fetch` +away. + +In your git config, append the `[remote ""] block with an extra +fetch line: + +``` +fetch = +refs/environments/*:refs/remotes/origin/environments/* +``` + ## Defining environments You can create and delete environments manually in the web interface, but we -- cgit v1.2.1 From 8fd91794382899e9e352af493a699198d7867f96 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 30 Sep 2016 16:40:56 +0000 Subject: Update method name --- app/models/deployment.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index cde6598fc7b..bfcfd4f2e11 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -29,7 +29,7 @@ class Deployment < ActiveRecord::Base self == environment.last_deployment end - def keep_around_commit + def create_ref project.repository.fetch_ref(project.repository.path_to_repo, ref, ref_path) end -- cgit v1.2.1 From 901c994b7a4481437f8fe91583d2ed3f19e4775e Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 3 Oct 2016 15:39:12 +0200 Subject: deployment refs in own folder, new method for creating refs --- app/models/deployment.rb | 4 ++-- app/models/environment.rb | 2 +- app/models/repository.rb | 4 ++++ doc/ci/environments.md | 2 +- spec/models/repository_spec.rb | 10 ++++++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index bfcfd4f2e11..82b27b78229 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -30,7 +30,7 @@ class Deployment < ActiveRecord::Base end def create_ref - project.repository.fetch_ref(project.repository.path_to_repo, ref, ref_path) + project.repository.create_ref(ref, ref_path) end def manual_actions @@ -80,6 +80,6 @@ class Deployment < ActiveRecord::Base private def ref_path - "#{environment.ref_path}#{id}" + File.join(environment.ref_path, 'deployments', id.to_s) end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 843f85883a1..f0f3ee23223 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -49,6 +49,6 @@ class Environment < ActiveRecord::Base end def ref_path - "refs/environments/#{Shellwords.shellescape(name)}/" + "refs/environments/#{Shellwords.shellescape(name)}" end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 51557228ab9..eb574555df6 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -997,6 +997,10 @@ class Repository Gitlab::Popen.popen(args, path_to_repo) end + def create_ref(ref, ref_path) + fetch_ref(path_to_repo, ref, ref_path) + end + def update_branch_with_hooks(current_user, branch) update_autocrlf_option diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 081766d5ee9..e070302fb82 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -20,7 +20,7 @@ Since 8.13, a reference in the git repository is saved for each deployment. So knowing what the state is of your current environments is only a `git fetch` away. -In your git config, append the `[remote ""] block with an extra +In your git config, append the `[remote ""]` block with an extra fetch line: ``` diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index db29f4d353b..98c64c079b9 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -320,6 +320,16 @@ describe Repository, models: true do end end + describe '#create_ref' do + it 'redirects the call to fetch_ref' do + ref, ref_path = '1', '2' + + expect(repository).to receive(:fetch_ref).with(repository.path_to_repo, ref, ref_path) + + repository.create_ref(ref, ref_path) + end + end + describe "#changelog" do before do repository.send(:cache).expire(:changelog) -- cgit v1.2.1 From c65af5d20e32e7573325ce0f7ee690b5b8baf1cf Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:54:55 +0000 Subject: Update 8.5-to-8.6.md --- doc/update/8.5-to-8.6.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.5-to-8.6.md b/doc/update/8.5-to-8.6.md index 6267f14eba4..a76346516b9 100644 --- a/doc/update/8.5-to-8.6.md +++ b/doc/update/8.5-to-8.6.md @@ -138,6 +138,10 @@ via [/etc/default/gitlab]. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 9. Start application -- cgit v1.2.1 From 6c02ce48f4d9e9b554b22904bd77341f853019f1 Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:55:30 +0000 Subject: Update 8.4-to-8.5.md --- doc/update/8.4-to-8.5.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.4-to-8.5.md b/doc/update/8.4-to-8.5.md index 0cb137a03cc..678cc69d773 100644 --- a/doc/update/8.4-to-8.5.md +++ b/doc/update/8.4-to-8.5.md @@ -119,6 +119,10 @@ via [/etc/default/gitlab]. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 8. Start application -- cgit v1.2.1 From 47a133d300281d55d86e2e14250060dbe6b24799 Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:56:08 +0000 Subject: Update 8.3-to-8.4.md --- doc/update/8.3-to-8.4.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.3-to-8.4.md b/doc/update/8.3-to-8.4.md index 9f6517d9487..e62d894609a 100644 --- a/doc/update/8.3-to-8.4.md +++ b/doc/update/8.3-to-8.4.md @@ -98,6 +98,10 @@ We updated the init script for GitLab in order to set a specific PATH for gitlab cd /home/git/gitlab sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ``` + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 8. Start application -- cgit v1.2.1 From c3aca56e051773e2c6a673f80a78e64b41eaa51d Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:57:30 +0000 Subject: Update 8.2-to-8.3.md --- doc/update/8.2-to-8.3.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.2-to-8.3.md b/doc/update/8.2-to-8.3.md index 9f5c6c4dc84..dd3fdafd8d1 100644 --- a/doc/update/8.2-to-8.3.md +++ b/doc/update/8.2-to-8.3.md @@ -158,6 +158,10 @@ it where the 'public' directory of GitLab is. cd /home/git/gitlab sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ``` + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 8. Use Redis v2.8.0+ -- cgit v1.2.1 From e56dab549e41df6f6e08ec934596223f32e9f3cc Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:58:50 +0000 Subject: Update 8.1-to-8.2.md --- doc/update/8.1-to-8.2.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md index 46dfa2232b4..7f36ce00e96 100644 --- a/doc/update/8.1-to-8.2.md +++ b/doc/update/8.1-to-8.2.md @@ -116,6 +116,10 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS # Update init.d script sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ``` + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 7. Update configuration files -- cgit v1.2.1 From d5ea7b6b8b402b00d06c95995d40e3bf84a940df Mon Sep 17 00:00:00 2001 From: fidomax Date: Tue, 4 Oct 2016 07:59:40 +0000 Subject: Update 8.0-to-8.1.md --- doc/update/8.0-to-8.1.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/update/8.0-to-8.1.md b/doc/update/8.0-to-8.1.md index d57c0d0674d..bfb83cf79b1 100644 --- a/doc/update/8.0-to-8.1.md +++ b/doc/update/8.0-to-8.1.md @@ -99,6 +99,10 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS # Update init.d script sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ``` + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload ### 7. Update configuration files -- cgit v1.2.1 From 912d7f7b68d5c75d801299a8c9363a257030e142 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 4 Oct 2016 10:01:32 +0200 Subject: Fix issues importing services via Import/Export --- CHANGELOG | 1 + app/models/service.rb | 1 + spec/models/service_spec.rb | 17 +++++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 806196d811d..309197c52be 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.13.0 (unreleased) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) v 8.12.4 (unreleased) + - Fix issues importing services via Import/Export - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. (lukehowell) v 8.12.3 diff --git a/app/models/service.rb b/app/models/service.rb index 80de7175565..66c804f2b06 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -136,6 +136,7 @@ class Service < ActiveRecord::Base end def #{arg}=(value) + self.properties ||= {} updated_properties['#{arg}'] = #{arg} unless #{arg}_changed? self.properties['#{arg}'] = value end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 05056a4bb47..ed1bc9271ae 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -203,6 +203,23 @@ describe Service, models: true do end end + describe 'initialize service with no properties' do + let(:service) do + GitlabIssueTrackerService.create( + project: create(:project), + title: 'random title' + ) + end + + it 'does not raise error' do + expect { service }.not_to raise_error + end + + it 'creates the properties' do + expect(service.properties).to eq({ "title" => "random title" }) + end + end + describe "callbacks" do let(:project) { create(:project) } let!(:service) do -- cgit v1.2.1 From 4ff345c488c993e9f67f34ad271bc98a1f3ffa61 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 29 Sep 2016 16:28:45 +0200 Subject: Simplify Mentionable concern instance methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We remove some arguments that are rarely used or used just to simplify setups on specs. Modified Mentionable#create_new_cross_references method we don’t need to calculate previous references to avoid the duplication because we do that at database level when creating references extracted from the current entity state. MergeRequests won’t create cross_references for commits that are included so we change a spec to use a different merge request to make references to commits to other branches --- CHANGELOG | 1 + app/models/concerns/mentionable.rb | 27 ++++++++++----------------- spec/models/concerns/mentionable_spec.rb | 22 +++++++++++++++------- spec/models/merge_request_spec.rb | 2 +- spec/support/mentionable_shared_examples.rb | 4 +++- 5 files changed, 30 insertions(+), 26 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 57f6034b744..da515dfc2ac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.13.0 (unreleased) - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references + - Simplify Mentionable concern instance methods - Fix permission for setting an issue's due date - Expose expires_at field when sharing project on API - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index ec9e0f1b1d0..eb2ff0428f6 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -43,19 +43,15 @@ module Mentionable self end - def all_references(current_user = nil, text = nil, extractor: nil) + def all_references(current_user = nil, extractor: nil) extractor ||= Gitlab::ReferenceExtractor. new(project, current_user) - if text - extractor.analyze(text, author: author) - else - self.class.mentionable_attrs.each do |attr, options| - text = __send__(attr) - options = options.merge(cache_key: [self, attr], author: author) + self.class.mentionable_attrs.each do |attr, options| + text = __send__(attr) + options = options.merge(cache_key: [self, attr], author: author) - extractor.analyze(text, options) - end + extractor.analyze(text, options) end extractor @@ -66,8 +62,8 @@ module Mentionable end # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. - def referenced_mentionables(current_user = self.author, text = nil) - refs = all_references(current_user, text) + def referenced_mentionables(current_user = self.author) + refs = all_references(current_user) refs = (refs.issues + refs.merge_requests + refs.commits) # We're using this method instead of Array diffing because that requires @@ -77,8 +73,8 @@ module Mentionable end # Create a cross-reference Note for each GFM reference to another Mentionable found in the +mentionable_attrs+. - def create_cross_references!(author = self.author, without = [], text = nil) - refs = referenced_mentionables(author, text) + def create_cross_references!(author = self.author, without = []) + refs = referenced_mentionables(author) # We're using this method instead of Array diffing because that requires # both of the object's `hash` values to be the same, which may not be the @@ -97,10 +93,7 @@ module Mentionable return if changes.empty? - original_text = changes.collect { |_, vals| vals.first }.join(' ') - - preexisting = referenced_mentionables(author, original_text) - create_cross_references!(author, preexisting) + create_cross_references!(author) end private diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 549b0042038..132858950d5 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -1,18 +1,27 @@ require 'spec_helper' describe Mentionable do - include Mentionable + class Example + include Mentionable - def author - nil + attr_accessor :project, :message + attr_mentionable :message + + def author + nil + end end describe 'references' do let(:project) { create(:project) } + let(:mentionable) { Example.new } it 'excludes JIRA references' do allow(project).to receive_messages(jira_tracker?: true) - expect(referenced_mentionables(project, 'JIRA-123')).to be_empty + + mentionable.project = project + mentionable.message = 'JIRA-123' + expect(mentionable.referenced_mentionables).to be_empty end end end @@ -39,9 +48,8 @@ describe Issue, "Mentionable" do let(:user) { create(:user) } def referenced_issues(current_user) - text = "#{private_issue.to_reference(project)} and #{public_issue.to_reference}" - - issue.referenced_mentionables(current_user, text) + issue.title = "#{private_issue.to_reference(project)} and #{public_issue.to_reference}" + issue.referenced_mentionables(current_user) end context 'when the current user can see the issue' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 9d7be2429ed..c29364466f0 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -522,7 +522,7 @@ describe MergeRequest, models: true do end it_behaves_like 'an editable mentionable' do - subject { create(:merge_request) } + subject { create(:merge_request, :simple) } let(:backref_text) { "merge request #{subject.to_reference}" } let(:set_mentionable_text) { ->(txt){ subject.description = txt } } diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index e876d44c166..f57c82809a6 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -9,7 +9,7 @@ shared_context 'mentionable context' do let(:author) { subject.author } let(:mentioned_issue) { create(:issue, project: project) } - let!(:mentioned_mr) { create(:merge_request, :simple, source_project: project) } + let!(:mentioned_mr) { create(:merge_request, source_project: project) } let(:mentioned_commit) { project.commit("HEAD~1") } let(:ext_proj) { create(:project, :public) } @@ -100,6 +100,7 @@ shared_examples 'an editable mentionable' do it 'creates new cross-reference notes when the mentionable text is edited' do subject.save + subject.create_cross_references! new_text = <<-MSG.strip_heredoc These references already existed: @@ -131,6 +132,7 @@ shared_examples 'an editable mentionable' do end # These two issues are new and should receive reference notes + # In the case of MergeRequests remember that cannot mention commits included in the MergeRequest new_issues.each do |newref| expect(SystemNoteService).to receive(:cross_reference). with(newref, subject.local_reference, author) -- cgit v1.2.1 From a6e17cf50ecba47148307f8b05d0a73e9656a4c7 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Fri, 30 Sep 2016 09:26:48 +0200 Subject: Use SELECT 1, instead SELECT COUNT(*) to ask for notes existency --- app/services/system_note_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index bf251816e7e..1ce66d50368 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -347,7 +347,7 @@ module SystemNoteService notes = notes.where(noteable_id: noteable.id) end - notes_for_mentioner(mentioner, noteable, notes).count > 0 + notes_for_mentioner(mentioner, noteable, notes).exists? end # Build an Array of lines detailing each commit added in a merge request -- cgit v1.2.1 From 7bf53ee8ea782b6816eea7125495c455481fca6d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 4 Oct 2016 11:15:29 +0200 Subject: Add example Cycle Analytics workflow [ci skip] --- doc/user/project/cycle_analytics.md | 58 +++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index eb9a0b8e2e3..7a40f3dad03 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -61,8 +61,8 @@ Below you can see in more detail what the various stages of Cycle Analytics mean | **Stage** | **Description** | | --------- | --------------- | | Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list][board] created for it. | -| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit of the branch is the one that triggers the separation between **Plan** and **Code**, and at least one of the commits in the branch needs to contain the related issue number (e.g., `#42`). If there is no commit containing the related issue number, it is not considered to the measurement time of the stage. | -| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request related to that commit. The key to keep the process tracked is to include the [issue closing pattern] to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the issue closing pattern is not present in the merge request description, the MR is not considered to the measurement time of the stage. | +| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit of the branch is the one that triggers the separation between **Plan** and **Code**, and at least one of the commits in the branch needs to contain the related issue number (e.g., `#42`). If none of the commits in the branch mention the related issue number, it is not considered to the measurement time of the stage. | +| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request (MR) related to that commit. The key to keep the process tracked is to include the [issue closing pattern] to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the issue closing pattern is not present in the merge request description, the MR is not considered to the measurement time of the stage. | | Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. `master` is not excluded. It does not attempt to track time for any particular stages. | | Review | Measures the median time taken to review the merge request, between its creation and until it's merged. | | Staging | Measures the median time between merging the merge request until the very first deployment to production. It's tracked by the [environment] set to `production` (case-sensitive, `Production` won't work) in your GitLab CI configuration. If there isn't a `production` environment, this is not tracked. | @@ -90,6 +90,54 @@ label present in the Issue Board or assigned a milestone or a project has no `production` environment, the Cycle Analytics dashboard won't present any data at all. +## Example workflow + +Below is a simple fictional workflow of a single cycle that happens in a +single day passing through all seven stages. Note that if a stage does not have +a start/stop mark, it is not measured and hence not calculated in the median +time. It is assumed that milestones are created and CI for testing and setting +environments is configured. + +1. Issue is created at 09:00 (start of **Issue** stage). +1. Issue is added to a milestone at 11:00 (stop of **Issue** stage / start of + **Plan** stage). +1. Start working on the issue, create a branch locally and make one commit at + 12:00. +1. Make a second commit to the branch which mentions the issue number at 12.30 + (stop of **Plan** stage / start of **Code** stage). +1. Push branch and create a merge request that contains the [issue closing pattern] + in its description at 14:00 (stop of **Code** stage / start of **Test** and + **Review** stages). +1. The test suite starts running and takes 5min (stop of **Test** stage). +1. Review merge request, ensure that everything is OK and merge the merge + request at 19:00. (stop of **Review** stage / start of **Staging** stage). +1. Now that the merge request is merged, a deployment to the `production` + environment starts and finishes at 19:30 (stop of **Staging** stage). +1. The cycle completes and the sum of it is shown in the **Production** stage. + +From the above example you can conclude the time it took each stage to complete +as long as their total time: + +- **Issue**: 2h (11:00 - 09:00) +- **Plan**: 1h (12:00 - 11:00) +- **Code**: 2h (14:00 - 12:00) +- **Test**: 5min +- **Review**: 5h (19:00 - 14:00) +- **Staging**: 30min (19:30 - 19:00) +- **Production**: 10h 30min (19:30 - 09:00) + +A few notes: + +- You can see that the **Test** stage is not calculated to the overall time of + the cycle since it is included in the **Review** process (every MR should be + tested). + +--- + +The example above was just one cycle of the seven stages. Add multiple cycles, +calculate their median time and the result is what the dashboard of Cycle +Analytics is showing. + ## Permissions The current permissions on the Cycle Analytics dashboard are: @@ -108,11 +156,11 @@ Learn more about Cycle Analytics in the following resources: - [Cycle Analytics feature highlight](https://about.gitlab.com/2016/09/21/cycle-analytics-feature-highlight/) +[board]: issue_board.md#creating-a-new-list [ce-5986]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5986 [ce-20975]: https://gitlab.com/gitlab-org/gitlab-ce/issues/20975 -[GitLab flow]: ../../workflow/gitlab_flow.md -[permissions]: ../permissions.md [environment]: ../../ci/yaml/README.md#environment -[board]: issue_board.md#creating-a-new-list +[GitLab flow]: ../../workflow/gitlab_flow.md [idea to production]: https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab [issue closing pattern]: issues/automatic_issue_closing.md +[permissions]: ../permissions.md -- cgit v1.2.1 From a34c0e5490c78402b72fab7196d43352ff719cbb Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 10:32:24 +0100 Subject: Border instead of hr --- app/assets/stylesheets/pages/members.scss | 5 +++++ app/views/projects/project_members/index.html.haml | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 72f31cb1168..187151fe26c 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -1,3 +1,8 @@ +.project-members-title { + padding-bottom: 10px; + border-bottom: 1px solid $border-color; +} + .member { .list-item-name { float: none; diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index f566748e95a..24e5a8e4015 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,10 +1,9 @@ - page_title "Members" .project-members-page.prepend-top-default - %h4.clearfix + %h4.project-members-title.clearfix Members = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project" - %hr - if can?(current_user, :admin_project_member, @project) .project-members-new.append-bottom-default %p.clearfix -- cgit v1.2.1 From 961c53c2ca46e6cfdb2bcaddc0570b1e210302d2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 4 Oct 2016 13:28:01 +0200 Subject: Update RuboCop to 0.43.0 and update configuration `Style/VariableNumber` is explicitly disabled because I don't think we care if we name a variable `var_1` or `var1`. --- .rubocop.yml | 4 ++ .rubocop_todo.yml | 171 ++++++++++++++++++++++++++++++++++++++---------------- Gemfile | 2 +- Gemfile.lock | 6 +- 4 files changed, 128 insertions(+), 55 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index c84755859cb..bec2464c740 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -453,6 +453,10 @@ Style/VariableName: EnforcedStyle: snake_case Enabled: true +# Use the configured style when numbering variables. +Style/VariableNumber: + Enabled: false + # Use when x then ... for one-line cases. Style/WhenThen: Enabled: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b6ed6e33397..11b34fafa2a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,21 +1,21 @@ # This configuration was generated by # `rubocop --auto-gen-config --exclude-limit 0` -# on 2016-09-14 15:44:53 -0400 using RuboCop version 0.42.0. +# on 2016-10-04 13:16:20 +0200 using RuboCop version 0.43.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 158 +# Offense count: 160 Lint/AmbiguousRegexpLiteral: Enabled: false -# Offense count: 41 +# Offense count: 40 # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Enabled: false -# Offense count: 16 +# Offense count: 18 Lint/HandleExceptions: Enabled: false @@ -23,11 +23,21 @@ Lint/HandleExceptions: Lint/Loop: Enabled: false -# Offense count: 16 +# Offense count: 19 Lint/ShadowingOuterLocalVariable: Enabled: false -# Offense count: 49 +# Offense count: 9 +# Cop supports --auto-correct. +Lint/UnifiedInteger: + Enabled: false + +# Offense count: 13 +# Cop supports --auto-correct. +Lint/UnneededSplatExpansion: + Enabled: false + +# Offense count: 69 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: @@ -39,32 +49,81 @@ Lint/UnusedBlockArgument: Lint/UnusedMethodArgument: Enabled: false -# Offense count: 9 -# Cop supports --auto-correct. -Performance/PushSplat: - Enabled: false - # Offense count: 2 # Cop supports --auto-correct. Performance/RedundantBlockCall: Enabled: false -# Offense count: 4 +# Offense count: 5 # Cop supports --auto-correct. Performance/RedundantMatch: Enabled: false -# Offense count: 27 +# Offense count: 26 # Cop supports --auto-correct. # Configuration parameters: MaxKeyValuePairs. Performance/RedundantMerge: Enabled: false -# Offense count: 61 +# Offense count: 7 +RSpec/BeEql: + Enabled: false + +# Offense count: 20 +# Configuration parameters: CustomIncludeMethods. +RSpec/EmptyExampleGroup: + Enabled: false + +# Offense count: 16 +RSpec/ExpectActual: + Enabled: false + +# Offense count: 34 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: implicit, each, example +RSpec/HookArgument: + Enabled: false + +# Offense count: 168 +RSpec/LeadingSubject: + Enabled: false + +# Offense count: 162 +RSpec/LetSetup: + Enabled: false + +# Offense count: 10 +RSpec/MessageChain: + Enabled: false + +# Offense count: 714 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: allow, expect +RSpec/MessageExpectation: + Enabled: false + +# Offense count: 2423 +RSpec/MultipleExpectations: + Max: 36 + +# Offense count: 1504 +RSpec/NamedSubject: + Enabled: false + +# Offense count: 1335 +# Configuration parameters: MaxNesting. +RSpec/NestedGroups: + Enabled: false + +# Offense count: 99 +RSpec/SubjectStub: + Enabled: false + +# Offense count: 64 Rails/OutputSafety: Enabled: false -# Offense count: 129 +# Offense count: 151 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: strict, flexible Rails/TimeZone: @@ -77,58 +136,63 @@ Rails/TimeZone: Rails/Validation: Enabled: false -# Offense count: 273 +# Offense count: 2 +# Cop supports --auto-correct. +Security/JSONLoad: + Enabled: false + +# Offense count: 284 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: with_first_parameter, with_fixed_indentation Style/AlignParameters: Enabled: false -# Offense count: 30 +# Offense count: 28 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: always, conditionals Style/AndOr: Enabled: false -# Offense count: 50 +# Offense count: 52 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: percent_q, bare_percent Style/BarePercentLiterals: Enabled: false -# Offense count: 289 +# Offense count: 291 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: braces, no_braces, context_dependent Style/BracesAroundHashParameters: Enabled: false -# Offense count: 5 +# Offense count: 6 Style/CaseEquality: Enabled: false -# Offense count: 19 +# Offense count: 26 # Cop supports --auto-correct. Style/ColonMethodCall: Enabled: false -# Offense count: 3 +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: Keywords. # Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW Style/CommentAnnotation: Enabled: false -# Offense count: 33 +# Offense count: 30 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Enabled: false -# Offense count: 881 +# Offense count: 957 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: leading, trailing @@ -139,12 +203,12 @@ Style/DotPosition: Style/DoubleNegation: Enabled: false -# Offense count: 4 +# Offense count: 6 # Cop supports --auto-correct. Style/EachWithObject: Enabled: false -# Offense count: 25 +# Offense count: 26 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: empty, nil, both @@ -156,24 +220,24 @@ Style/EmptyElse: Style/EmptyLiteral: Enabled: false -# Offense count: 135 +# Offense count: 140 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. Style/ExtraSpacing: Enabled: false -# Offense count: 7 +# Offense count: 6 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: format, sprintf, percent Style/FormatString: Enabled: false -# Offense count: 51 +# Offense count: 201 # Configuration parameters: MinBodyLength. Style/GuardClause: Enabled: false -# Offense count: 9 +# Offense count: 11 Style/IfInsideElse: Enabled: false @@ -183,21 +247,21 @@ Style/IfInsideElse: Style/IfUnlessModifier: Enabled: false -# Offense count: 52 +# Offense count: 53 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_brackets Style/IndentArray: Enabled: false -# Offense count: 97 +# Offense count: 95 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces Style/IndentHash: Enabled: false -# Offense count: 12 +# Offense count: 29 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: line_count_dependent, lambda, literal @@ -209,7 +273,7 @@ Style/Lambda: Style/LineEndConcatenation: Enabled: false -# Offense count: 13 +# Offense count: 15 # Cop supports --auto-correct. Style/MethodCallParentheses: Enabled: false @@ -218,7 +282,7 @@ Style/MethodCallParentheses: Style/MethodMissing: Enabled: false -# Offense count: 85 +# Offense count: 95 # Cop supports --auto-correct. Style/MutableConstant: Enabled: false @@ -235,14 +299,14 @@ Style/NestedParenthesizedCalls: Style/Next: Enabled: false -# Offense count: 8 +# Offense count: 12 # Cop supports --auto-correct. # Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles. # SupportedOctalStyles: zero_with_o, zero_only Style/NumericLiteralPrefix: Enabled: false -# Offense count: 64 +# Offense count: 53 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: predicate, comparison @@ -254,7 +318,7 @@ Style/NumericPredicate: Style/ParallelAssignment: Enabled: false -# Offense count: 264 +# Offense count: 294 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: @@ -272,7 +336,7 @@ Style/PercentQLiterals: Style/PerlBackrefs: Enabled: false -# Offense count: 35 +# Offense count: 38 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. # NamePrefix: is_, has_, have_ # NamePrefixBlacklist: is_, has_, have_ @@ -280,7 +344,7 @@ Style/PerlBackrefs: Style/PredicateName: Enabled: false -# Offense count: 27 +# Offense count: 26 # Cop supports --auto-correct. Style/PreferredHashMethods: Enabled: false @@ -312,12 +376,12 @@ Style/RedundantException: Style/RedundantFreeze: Enabled: false -# Offense count: 408 +# Offense count: 427 # Cop supports --auto-correct. Style/RedundantSelf: Enabled: false -# Offense count: 93 +# Offense count: 97 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed @@ -329,7 +393,12 @@ Style/RegexpLiteral: Style/RescueModifier: Enabled: false -# Offense count: 5 +# Offense count: 114 +# Cop supports --auto-correct. +Style/SafeNavigation: + Enabled: false + +# Offense count: 7 # Cop supports --auto-correct. Style/SelfAssignment: Enabled: false @@ -346,7 +415,7 @@ Style/SingleLineBlockParams: Style/SingleLineMethods: Enabled: false -# Offense count: 124 +# Offense count: 125 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: space, no_space @@ -359,19 +428,19 @@ Style/SpaceBeforeBlockBraces: Style/SpaceBeforeFirstArg: Enabled: false -# Offense count: 141 +# Offense count: 145 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space Style/SpaceInsideBlockBraces: Enabled: false -# Offense count: 96 +# Offense count: 99 # Cop supports --auto-correct. Style/SpaceInsideBrackets: Enabled: false -# Offense count: 62 +# Offense count: 65 # Cop supports --auto-correct. Style/SpaceInsideParens: Enabled: false @@ -381,21 +450,21 @@ Style/SpaceInsideParens: Style/SpaceInsidePercentLiteralDelimiters: Enabled: false -# Offense count: 40 +# Offense count: 41 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles. # SupportedStyles: use_perl_names, use_english_names Style/SpecialGlobalVars: EnforcedStyle: use_perl_names -# Offense count: 30 +# Offense count: 31 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: single_quotes, double_quotes Style/StringLiteralsInInterpolation: Enabled: false -# Offense count: 32 +# Offense count: 33 # Cop supports --auto-correct. # Configuration parameters: IgnoredMethods. # IgnoredMethods: respond_to, define_method @@ -409,7 +478,7 @@ Style/SymbolProc: Style/TernaryParentheses: Enabled: false -# Offense count: 24 +# Offense count: 29 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. # SupportedStyles: comma, consistent_comma, no_comma diff --git a/Gemfile b/Gemfile index d54beedf67e..18654a1e88c 100644 --- a/Gemfile +++ b/Gemfile @@ -295,7 +295,7 @@ group :development, :test do gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-teaspoon', '~> 0.0.2' - gem 'rubocop', '~> 0.42.0', require: false + gem 'rubocop', '~> 0.43.0', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'scss_lint', '~> 0.47.0', require: false gem 'haml_lint', '~> 0.18.2', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 6f8a8236866..3f756fec929 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -487,7 +487,7 @@ GEM orm_adapter (0.5.0) paranoia (2.1.4) activerecord (~> 4.0) - parser (2.3.1.2) + parser (2.3.1.4) ast (~> 2.2) pg (0.18.4) pkg-config (1.1.7) @@ -620,7 +620,7 @@ GEM rspec-retry (0.4.5) rspec-core rspec-support (3.5.0) - rubocop (0.42.0) + rubocop (0.43.0) parser (>= 2.3.1.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) @@ -938,7 +938,7 @@ DEPENDENCIES rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.5.0) rspec-retry (~> 0.4.5) - rubocop (~> 0.42.0) + rubocop (~> 0.43.0) rubocop-rspec (~> 1.5.0) ruby-fogbugz (~> 0.2.1) ruby-prof (~> 0.16.2) -- cgit v1.2.1 From 68774452a47ec71a490eb5eaeedd5a3222aa2c68 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 3 Oct 2016 12:45:34 +0200 Subject: Fix async pipeline and remove unrelated changes --- app/models/ci/build.rb | 2 +- app/models/ci/pipeline.rb | 8 -------- app/models/commit_status.rb | 7 +------ .../merge_requests/add_todo_when_build_fails_service.rb | 8 ++++---- app/services/merge_requests/base_service.rb | 15 +++++++++------ app/workers/process_pipeline_worker.rb | 2 +- 6 files changed, 16 insertions(+), 26 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7c9899334ad..5dbf66173de 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -67,7 +67,7 @@ module Ci environment: build.environment, status_event: 'enqueue' ) - MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build.pipeline) + MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) build.pipeline.mark_as_processable_after_stage(build.stage_idx) new_build end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index da0b9d83e1d..4c38a54ea3a 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -73,14 +73,6 @@ module Ci after_transition do |pipeline, transition| pipeline.execute_hooks unless transition.loopback? end - - after_transition [:created, :pending, :running] => :success do |pipeline| - MergeRequests::MergeWhenBuildSucceedsService.new(pipeline.project, nil).trigger(pipeline) - end - - after_transition any => :failed do |pipeline| - MergeRequests::AddTodoWhenBuildFailsService.new(pipeline.project, nil).execute(pipeline) - end end # ref can't be HEAD or SHA, can only be branch/tag name diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 3d5e449f403..b0718e2213c 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -69,11 +69,6 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - after_transition any => [:success, :failed, :canceled] do |commit_status| - commit_status.pipeline.try(:process!) - true - end - after_transition [:created, :pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) end @@ -86,7 +81,7 @@ class CommitStatus < ActiveRecord::Base if commit_status.pipeline && !transition.loopback? ProcessPipelineWorker.perform_async( commit_status.pipeline.id, - process: HasStatus.COMPLETED_STATUSES.include?(commit_status.status)) + process: HasStatus::COMPLETED_STATUSES.include?(commit_status.status)) end true diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb index 45c73789886..566049525cb 100644 --- a/app/services/merge_requests/add_todo_when_build_fails_service.rb +++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb @@ -1,15 +1,15 @@ module MergeRequests class AddTodoWhenBuildFailsService < MergeRequests::BaseService # Adds a todo to the parent merge_request when a CI build fails - def execute(pipeline) - each_merge_request(pipeline) do |merge_request| + def execute(commit_status) + each_merge_request(commit_status) do |merge_request| todo_service.merge_request_build_failed(merge_request) end end # Closes any pending build failed todos for the parent MRs when a build is retried - def close(pipeline) - each_merge_request(pipeline) do |merge_request| + def close(commit_status) + each_merge_request(commit_status) do |merge_request| todo_service.merge_request_build_retried(merge_request) end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 95b4f0ff733..d0d155b7ee1 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -42,11 +42,11 @@ module MergeRequests super(:merge_request) end - def merge_request_from(pipeline) - branches = pipeline.ref + def merge_request_from(commit_status) + branches = commit_status.ref # This is for ref-less builds - branches ||= @project.repository.branch_names_contains(pipeline.sha) + branches ||= @project.repository.branch_names_contains(commit_status.sha) return [] if branches.blank? @@ -56,11 +56,14 @@ module MergeRequests merge_requests.uniq.select(&:source_project) end - def each_merge_request(pipeline) + def each_merge_request(commit_status) merge_request_from(commit_status).each do |merge_request| - next unless pipeline == merge_request.pipeline + pipeline = merge_request.pipeline - yield merge_request + next unless pipeline + next unless pipeline.sha == commit_status.sha + + yield merge_request, pipeline end end end diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/process_pipeline_worker.rb index fb59a1efb7a..fa1251b8e9f 100644 --- a/app/workers/process_pipeline_worker.rb +++ b/app/workers/process_pipeline_worker.rb @@ -10,7 +10,7 @@ class ProcessPipelineWorker return end - pipeline.process! if params[:process] + pipeline.process! if params['process'] pipeline.update_status end -- cgit v1.2.1 From 578638742ab431a57f9add51684203b0d1c19348 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 3 Oct 2016 13:50:29 +0200 Subject: Use internal commit status API to check if finished --- app/models/commit_status.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index b0718e2213c..966a2449d2c 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -80,8 +80,8 @@ class CommitStatus < ActiveRecord::Base after_transition do |commit_status, transition| if commit_status.pipeline && !transition.loopback? ProcessPipelineWorker.perform_async( - commit_status.pipeline.id, - process: HasStatus::COMPLETED_STATUSES.include?(commit_status.status)) + commit_status.pipeline.id, process: commit_status.complete? + ) end true -- cgit v1.2.1 From 7d4767fb4830a88f345c3b71e78b07771d2f3f06 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 3 Oct 2016 16:30:11 +0200 Subject: Extract updating pipeline status to async worker --- app/models/commit_status.rb | 12 ++++++++---- app/workers/process_pipeline_worker.rb | 13 ++++--------- app/workers/update_pipeline_worker.rb | 12 ++++++++++++ 3 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 app/workers/update_pipeline_worker.rb diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 966a2449d2c..09aec6b40d0 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -78,10 +78,14 @@ class CommitStatus < ActiveRecord::Base end after_transition do |commit_status, transition| - if commit_status.pipeline && !transition.loopback? - ProcessPipelineWorker.perform_async( - commit_status.pipeline.id, process: commit_status.complete? - ) + return if transition.loopback? + + commit_status.pipeline.try(:id).try do |pipeline_id| + if commit_status.complete? + ProcessPipelineWorker.perform_async(pipeline_id) + end + + UpdatePipelineWorker.perform_async(pipeline_id) end true diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/process_pipeline_worker.rb index fa1251b8e9f..9aad8cf818f 100644 --- a/app/workers/process_pipeline_worker.rb +++ b/app/workers/process_pipeline_worker.rb @@ -3,15 +3,10 @@ class ProcessPipelineWorker sidekiq_options queue: :default - def perform(pipeline_id, params) - begin - pipeline = Ci::Pipeline.find(pipeline_id) - rescue ActiveRecord::RecordNotFound - return - end + def perform(pipeline_id) + pipeline = Ci::Pipeline.find_by(id: pipeline_id) + return unless pipeline - pipeline.process! if params['process'] - - pipeline.update_status + pipeline.process! end end diff --git a/app/workers/update_pipeline_worker.rb b/app/workers/update_pipeline_worker.rb new file mode 100644 index 00000000000..31d29d0ab1c --- /dev/null +++ b/app/workers/update_pipeline_worker.rb @@ -0,0 +1,12 @@ +class UpdatePipelineWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + pipeline = Ci::Pipeline.find_by(id: pipeline_id) + return unless pipeline + + pipeline.update_status + end +end -- cgit v1.2.1 From ebeee31100cb142dfc74ec02928d3c5433eab8d7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 3 Oct 2016 16:32:12 +0200 Subject: Fix pipeline fixtures and calls to removed method --- db/fixtures/development/14_pipelines.rb | 2 +- spec/features/projects/badges/coverage_spec.rb | 2 +- spec/lib/gitlab/badge/coverage/report_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 650b410595c..803cbca584d 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -34,7 +34,7 @@ class Gitlab::Seeder::Pipelines rescue ActiveRecord::RecordInvalid print 'F' ensure - pipeline.build_updated + pipeline.update_status end end end diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb index 5972e7f31c2..01a95bf49ac 100644 --- a/spec/features/projects/badges/coverage_spec.rb +++ b/spec/features/projects/badges/coverage_spec.rb @@ -59,7 +59,7 @@ feature 'test coverage badge' do create(:ci_pipeline, opts).tap do |pipeline| yield pipeline - pipeline.build_updated + pipeline.update_status end end diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb index ab0cce6e091..1547bd3228c 100644 --- a/spec/lib/gitlab/badge/coverage/report_spec.rb +++ b/spec/lib/gitlab/badge/coverage/report_spec.rb @@ -100,7 +100,7 @@ describe Gitlab::Badge::Coverage::Report do create(:ci_pipeline, opts).tap do |pipeline| yield pipeline - pipeline.build_updated + pipeline.update_status end end end -- cgit v1.2.1 From f57cfdb6b527fe20f64f8a76339264948c3b358b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 4 Oct 2016 12:25:06 +0200 Subject: Update order of build transition callbacks --- app/models/commit_status.rb | 25 +++++++++++----------- .../merge_when_build_succeeds_service.rb | 7 +++--- app/workers/process_pipeline_worker.rb | 7 +++--- app/workers/update_pipeline_worker.rb | 7 +++--- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 09aec6b40d0..9c4f86144d8 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -69,27 +69,28 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - after_transition [:created, :pending, :running] => :success do |commit_status| - MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) - end - - after_transition any => :failed do |commit_status| - MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) - end - after_transition do |commit_status, transition| - return if transition.loopback? + commit_status.pipeline.tap do |pipeline| + return if transition.loopback? + return unless pipeline - commit_status.pipeline.try(:id).try do |pipeline_id| if commit_status.complete? - ProcessPipelineWorker.perform_async(pipeline_id) + ProcessPipelineWorker.perform_async(pipeline.id) end - UpdatePipelineWorker.perform_async(pipeline_id) + UpdatePipelineWorker.perform_async(pipeline.id) end true end + + after_transition [:created, :pending, :running] => :success do |commit_status| + MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) + end + + after_transition any => :failed do |commit_status| + MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) + end end delegate :sha, :short_sha, to: :pipeline diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index 60d6085f9eb..4ad5fb08311 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -19,12 +19,11 @@ module MergeRequests end # Triggers the automatic merge of merge_request once the build succeeds - def trigger(pipeline) - return unless pipeline.success? - - each_merge_request(pipeline) do |merge_request| + def trigger(commit_status) + each_merge_request(commit_status) do |merge_request, pipeline| next unless merge_request.merge_when_build_succeeds? next unless merge_request.mergeable? + next unless pipeline.success? MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) end diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/process_pipeline_worker.rb index 9aad8cf818f..189cfa207ff 100644 --- a/app/workers/process_pipeline_worker.rb +++ b/app/workers/process_pipeline_worker.rb @@ -4,9 +4,8 @@ class ProcessPipelineWorker sidekiq_options queue: :default def perform(pipeline_id) - pipeline = Ci::Pipeline.find_by(id: pipeline_id) - return unless pipeline - - pipeline.process! + Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| + pipeline.process! + end end end diff --git a/app/workers/update_pipeline_worker.rb b/app/workers/update_pipeline_worker.rb index 31d29d0ab1c..9fd622a0970 100644 --- a/app/workers/update_pipeline_worker.rb +++ b/app/workers/update_pipeline_worker.rb @@ -4,9 +4,8 @@ class UpdatePipelineWorker sidekiq_options queue: :default def perform(pipeline_id) - pipeline = Ci::Pipeline.find_by(id: pipeline_id) - return unless pipeline - - pipeline.update_status + Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| + pipeline.update_status + end end end -- cgit v1.2.1 From 5ce5abdc1dc53e97cff88f5abfe0edd10211bf9c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 4 Oct 2016 12:30:57 +0200 Subject: Fix hipchat service specs after changes in pipeline --- app/models/commit_status.rb | 5 ++--- spec/models/project_services/hipchat_service_spec.rb | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 9c4f86144d8..4bb4211dfe3 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -70,9 +70,8 @@ class CommitStatus < ActiveRecord::Base end after_transition do |commit_status, transition| - commit_status.pipeline.tap do |pipeline| - return if transition.loopback? - return unless pipeline + commit_status.pipeline.try do |pipeline| + return false if transition.loopback? if commit_status.complete? ProcessPipelineWorker.perform_async(pipeline.id) diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index cf713684463..26dd95bdfec 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -283,7 +283,7 @@ describe HipchatService, models: true do context 'build events' do let(:pipeline) { create(:ci_empty_pipeline) } let(:build) { create(:ci_build, pipeline: pipeline) } - let(:data) { Gitlab::DataBuilder::Build.build(build) } + let(:data) { Gitlab::DataBuilder::Build.build(build.reload) } context 'for failed' do before { build.drop } -- cgit v1.2.1 From 4e4640b10bfae9bfa1da265775ede57ea72c358c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 4 Oct 2016 13:27:48 +0200 Subject: Add specs for new async ci pipeline workers --- spec/workers/process_pipeline_worker_spec.rb | 22 ++++++++++++++++++++++ spec/workers/update_pipeline_worker_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 spec/workers/process_pipeline_worker_spec.rb create mode 100644 spec/workers/update_pipeline_worker_spec.rb diff --git a/spec/workers/process_pipeline_worker_spec.rb b/spec/workers/process_pipeline_worker_spec.rb new file mode 100644 index 00000000000..7b5f98d5763 --- /dev/null +++ b/spec/workers/process_pipeline_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe ProcessPipelineWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'processes pipeline' do + expect_any_instance_of(Ci::Pipeline).to receive(:process!) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end diff --git a/spec/workers/update_pipeline_worker_spec.rb b/spec/workers/update_pipeline_worker_spec.rb new file mode 100644 index 00000000000..fadc42b22f0 --- /dev/null +++ b/spec/workers/update_pipeline_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe UpdatePipelineWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'updates pipeline status' do + expect_any_instance_of(Ci::Pipeline).to receive(:update_status) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 138e26b1fad6730aa048b8cc91f92a597e323162 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 14:06:44 +0100 Subject: Adds v-pre to code blocks in comments Closes #22911 --- CHANGELOG | 3 ++- lib/banzai/filter/sanitization_filter.rb | 2 +- lib/banzai/filter/syntax_highlight_filter.rb | 3 ++- spec/features/notes_on_merge_requests_spec.rb | 12 ++++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9f871360bf4..8c1b5235443 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Simplify Mentionable concern instance methods - Fix permission for setting an issue's due date - Expose expires_at field when sharing project on API + - Fix VueJS template tags being rendered in code comments - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - Allow the Koding integration to be configured through the API - Added soft wrap button to repository file/blob editor @@ -44,7 +45,7 @@ v 8.13.0 (unreleased) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) - - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) + - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) v 8.12.4 (unreleased) diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index 2470362e019..af1e575fc89 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -25,7 +25,7 @@ module Banzai return if customized?(whitelist[:transformers]) # Allow code highlighting - whitelist[:attributes]['pre'] = %w(class) + whitelist[:attributes]['pre'] = %w(class v-pre) whitelist[:attributes]['span'] = %w(class) # Allow table alignment diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index fcdb496aed2..bb6b4582e4f 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -30,7 +30,8 @@ module Banzai # users can still access an issue/comment/etc. end - highlighted = %(
    #{code}
    ) + highlighted = %(
    #{code}
    ) + puts highlighted # Extracted to a method to measure it replace_parent_pre_element(node, highlighted) diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index f1c522155d3..5d7247e2a62 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -240,6 +240,18 @@ describe 'Comments', feature: true do is_expected.to have_css('.notes_holder .note', count: 1) is_expected.to have_button('Reply...') end + + it 'adds code to discussion' do + click_button 'Reply...' + + page.within(first('.js-discussion-note-form')) do + fill_in 'note[note]', with: '```{{ test }}```' + + click_button('Comment') + end + + expect(page).to have_content('{{ test }}') + end end end end -- cgit v1.2.1 From cd2556e73792f018af71e8910b4f556e29de1985 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 14:28:57 +0100 Subject: Removed puts code :see_no_evil: --- lib/banzai/filter/syntax_highlight_filter.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index bb6b4582e4f..026b81ac175 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -31,7 +31,6 @@ module Banzai end highlighted = %(
    #{code}
    ) - puts highlighted # Extracted to a method to measure it replace_parent_pre_element(node, highlighted) -- cgit v1.2.1 From c2602aaff3f78ad12e1cc06136a7345699951454 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 14:40:03 +0100 Subject: Updated Ruby --- app/controllers/projects/group_links_controller.rb | 2 +- app/controllers/projects/project_members_controller.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 7b6f07465e0..2994d8c9666 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -1,6 +1,7 @@ class Projects::GroupLinksController < Projects::ApplicationController layout 'project_settings' before_action :authorize_admin_project! + before_action :authorize_admin_project_member!, only: [:update] def index @group_links = project.project_group_links.all @@ -21,7 +22,6 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - return render_403 unless can?(current_user, :admin_project_member, @project) @group_link.update_attributes(group_link_params) end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index eb1bf445a7d..870dc8abbd4 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -19,8 +19,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @groups = @project.project_group_links.where(group_id: group_ids) end - @project_members = @project_members.order('access_level DESC') - @project_members = @project_members.page(params[:page]) + @project_members = @project_members.order(access_level: :desc).page(params[:page]) @requesters = AccessRequestsFinder.new(@project).execute(current_user) @@ -40,6 +39,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController groups = Group.where(id: group_ids) groups.each do |group| + next unless can?(current_user, :read_group, group) + project.project_group_links.create( group: group, group_access: params[:access_level], -- cgit v1.2.1 From 194fbc3c3d4b068f191fca75488b986df88c5333 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 2 Sep 2016 14:30:19 +0100 Subject: Restrict failed login attempts for users with 2FA Copy logic from `Devise::Models::Lockable#valid_for_authentication?`, as our custom login flow with two pages doesn't call this method. This will increment the failed login counter, and lock the user's account once they exceed the number of failed attempts. Also ensure that users who are locked can't continue to submit 2FA codes. --- CHANGELOG | 3 +- .../concerns/authenticates_with_two_factor.rb | 15 +++++++-- app/models/user.rb | 16 +++++++++ spec/controllers/sessions_controller_spec.rb | 38 ++++++++++++++++++++++ 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bfc312ca19c..e4dc1e82a90 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -42,11 +42,12 @@ v 8.13.0 (unreleased) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) - - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) + - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) v 8.12.4 (unreleased) - Fix issues importing services via Import/Export + - Restrict failed login attempts for users with 2FA enabled - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. (lukehowell) v 8.12.3 diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index d5a8a962662..4c497711fc0 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -23,15 +23,24 @@ module AuthenticatesWithTwoFactor # # Returns nil def prompt_for_two_factor(user) + return locked_user_redirect(user) if user.access_locked? + session[:otp_user_id] = user.id setup_u2f_authentication(user) render 'devise/sessions/two_factor' end + def locked_user_redirect(user) + flash.now[:alert] = 'Invalid Login or password' + render 'devise/sessions/new' + end + def authenticate_with_two_factor user = self.resource = find_user - if user_params[:otp_attempt].present? && session[:otp_user_id] + if user.access_locked? + locked_user_redirect(user) + elsif user_params[:otp_attempt].present? && session[:otp_user_id] authenticate_with_two_factor_via_otp(user) elsif user_params[:device_response].present? && session[:otp_user_id] authenticate_with_two_factor_via_u2f(user) @@ -50,8 +59,9 @@ module AuthenticatesWithTwoFactor remember_me(user) if user_params[:remember_me] == '1' sign_in(user) else + user.increment_failed_attempts! flash.now[:alert] = 'Invalid two-factor code.' - render :two_factor + prompt_for_two_factor(user) end end @@ -65,6 +75,7 @@ module AuthenticatesWithTwoFactor remember_me(user) if user_params[:remember_me] == '1' sign_in(user) else + user.increment_failed_attempts! flash.now[:alert] = 'Authentication via U2F device failed.' prompt_for_two_factor(user) end diff --git a/app/models/user.rb b/app/models/user.rb index 6996740eebd..7f5a8562907 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -827,6 +827,22 @@ class User < ActiveRecord::Base todos_pending_count(force: true) end + # This is copied from Devise::Models::Lockable#valid_for_authentication?, as our auth + # flow means we don't call that automatically (and can't conveniently do so). + # + # See: + # + # + def increment_failed_attempts! + self.failed_attempts ||= 0 + self.failed_attempts += 1 + if attempts_exceeded? + lock_access! unless access_locked? + else + save(validate: false) + end + end + private def projects_union(min_access_level = nil) diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 8f27e616c3e..48d69377461 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -109,6 +109,44 @@ describe SessionsController do end end + context 'when the user is on their last attempt' do + before do + user.update(failed_attempts: User.maximum_attempts.pred) + end + + context 'when OTP is valid' do + it 'authenticates correctly' do + authenticate_2fa(otp_attempt: user.current_otp) + + expect(subject.current_user).to eq user + end + end + + context 'when OTP is invalid' do + before { authenticate_2fa(otp_attempt: 'invalid') } + + it 'does not authenticate' do + expect(subject.current_user).not_to eq user + end + + it 'warns about invalid login' do + expect(response).to set_flash.now[:alert] + .to /Invalid Login or password/ + end + + it 'locks the user' do + expect(user.reload).to be_access_locked + end + + it 'keeps the user locked on future login attempts' do + post(:create, user: { login: user.username, password: user.password }) + + expect(response) + .to set_flash.now[:alert].to /Invalid Login or password/ + end + end + end + context 'when another user does not have 2FA enabled' do let(:another_user) { create(:user) } -- cgit v1.2.1 From 7f270d041da55e1fd5c378dcf2291ba752a9114d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 4 Oct 2016 16:34:22 +0200 Subject: Do not return false in commit status transition --- app/models/commit_status.rb | 2 +- app/workers/process_pipeline_worker.rb | 5 ++--- app/workers/update_pipeline_worker.rb | 5 ++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 4bb4211dfe3..b0b4f94e070 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -71,7 +71,7 @@ class CommitStatus < ActiveRecord::Base after_transition do |commit_status, transition| commit_status.pipeline.try do |pipeline| - return false if transition.loopback? + break if transition.loopback? if commit_status.complete? ProcessPipelineWorker.perform_async(pipeline.id) diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/process_pipeline_worker.rb index 189cfa207ff..26ea5f1c24d 100644 --- a/app/workers/process_pipeline_worker.rb +++ b/app/workers/process_pipeline_worker.rb @@ -4,8 +4,7 @@ class ProcessPipelineWorker sidekiq_options queue: :default def perform(pipeline_id) - Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| - pipeline.process! - end + Ci::Pipeline.find_by(id: pipeline_id) + .try(:process!) end end diff --git a/app/workers/update_pipeline_worker.rb b/app/workers/update_pipeline_worker.rb index 9fd622a0970..6ef5678073e 100644 --- a/app/workers/update_pipeline_worker.rb +++ b/app/workers/update_pipeline_worker.rb @@ -4,8 +4,7 @@ class UpdatePipelineWorker sidekiq_options queue: :default def perform(pipeline_id) - Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| - pipeline.update_status - end + Ci::Pipeline.find_by(id: pipeline_id) + .try(:update_status) end end -- cgit v1.2.1 From 20408363eee3cea89b18e6d343a2ee12fb7158a6 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Tue, 4 Oct 2016 17:52:35 +0300 Subject: Fix dropdown title when No Label selected. --- app/assets/javascripts/labels_select.js | 61 ++++++++++++++++++++---------- app/assets/javascripts/milestone_select.js | 3 +- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 2324c166a79..7d88c979edb 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -79,7 +79,8 @@ if (data.labels.length) { template = labelHTMLTemplate(data); labelCount = data.labels.length; - } else { + } + else { template = labelNoneHTMLTemplate; } $value.removeAttr('style').html(template); @@ -96,7 +97,8 @@ } labelTooltipTitle = labelTitles.join(', '); - } else { + } + else { labelTooltipTitle = ''; $sidebarLabelTooltip.tooltip('destroy'); } @@ -200,14 +202,16 @@ return color + " " + percentFirst + "%," + color + " " + percentSecond + "% "; }).join(','); color = "linear-gradient(" + color + ")"; - } else { + } + else { if (label.color != null) { color = label.color[0]; } } if (color) { colorEl = ""; - } else { + } + else { colorEl = ''; } // We need to identify which items are actually labels @@ -227,23 +231,29 @@ filterable: true, selected: $dropdown.data('selected') || [], toggleLabel: function(selected, el) { - var isSelected = el !== null ? el.hasClass('is-active') : false, - title = selected.title; + var isSelected = el !== null ? el.hasClass('is-active') : false; + var title = selected.title; + var selectedLabels = this.selected; - if (isSelected) { + if (selected.id === 0) { + this.selected = []; + return 'No Label'; + } + else if (isSelected) { this.selected.push(title); - } else { + } + else { var index = this.selected.indexOf(title); this.selected.splice(index, 1); } - var selectedLabels = this.selected; - if (selectedLabels.length === 1) { return selectedLabels; - } else if (selectedLabels.length > 1) { + } + else if (selectedLabels.length) { return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more"; - } else { + } + else { return defaultLabel; } }, @@ -257,7 +267,8 @@ if ($dropdown.hasClass("js-filter-submit") && (label.isAny == null)) { return label.title; - } else { + } + else { return label.id; } }, @@ -281,9 +292,11 @@ if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { selectedLabels = $dropdown.closest('form').find("input:hidden[name='" + ($dropdown.data('fieldName')) + "']"); Issuable.filterResults($dropdown.closest('form')); - } else if ($dropdown.hasClass('js-filter-submit')) { + } + else if ($dropdown.hasClass('js-filter-submit')) { $dropdown.closest('form').submit(); - } else { + } + else { if (!$dropdown.hasClass('js-filter-bulk-update')) { saveLabelData(); } @@ -317,9 +330,11 @@ if (page === 'projects:boards:show') { if (label.isAny) { gl.issueBoards.BoardsStore.state.filters['label_name'] = []; - } else if ($el.hasClass('is-active')) { + } + else if ($el.hasClass('is-active')) { gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title); - } else { + } + else { var filters = gl.issueBoards.BoardsStore.state.filters['label_name']; filters = filters.filter(function (filteredLabel) { return filteredLabel !== label.title; @@ -330,17 +345,21 @@ gl.issueBoards.BoardsStore.updateFiltersUrl(); e.preventDefault(); return; - } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { + } + else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { if (!$dropdown.hasClass('js-multiselect')) { selectedLabel = label.title; return Issuable.filterResults($dropdown.closest('form')); } - } else if ($dropdown.hasClass('js-filter-submit')) { + } + else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); - } else { + } + else { if ($dropdown.hasClass('js-multiselect')) { - } else { + } + else { return saveLabelData(); } } diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 3d6a4732546..906b6b321a8 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -35,8 +35,7 @@ return $.ajax({ url: milestonesUrl }).done(function(data) { - var extraOptions; - extraOptions = []; + var extraOptions = []; if (showAny) { extraOptions.push({ id: 0, -- cgit v1.2.1 From 94d887485a00b15657478619324e4cdc39c5c251 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Tue, 4 Oct 2016 17:53:03 +0300 Subject: Tweak issuable dropdowns to open above the dropdown. --- app/assets/stylesheets/pages/issuable.scss | 28 ++++++++++++++++++++++ .../shared/issuable/_milestone_dropdown.html.haml | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 41079b6eeb5..1a5cb4facc3 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -419,3 +419,31 @@ } } } + +.issue-form { + + .dropdown-menu-labels { + top: -391px; + + &.is-loading { + top: -166px; + } + } + + .dropdown-menu-milestone { + top: -310px; + + &.is-loading { + top: -166px; + } + } + + .dropdown-menu-assignee { + top: -315px; + + &.is-loading { + top: -101px; + } + } + +} diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index ab537dc0ed7..7458afdaa58 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -3,7 +3,7 @@ - selected_text = selected.try(:title) - if selected.present? = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) -= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable", += dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list -- cgit v1.2.1 From 437bebb0ff6e7deba6fd157ec6b55112e125731f Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 4 Oct 2016 16:35:41 +0200 Subject: Don't send Private-Token headers to Sentry Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/22537 --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 6 +++--- config/application.rb | 2 ++ config/initializers/sentry.rb | 2 ++ 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c243920283c..84a6702907f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.13.0 (unreleased) v 8.12.4 (unreleased) - Set GitLab project exported file permissions to owner only + - Don't send Private-Token (API authentication) headers to Sentry v 8.12.2 (unreleased) - Fix Import/Export not recognising correctly the imported services. diff --git a/Gemfile b/Gemfile index 21b31e8f01d..921554286c3 100644 --- a/Gemfile +++ b/Gemfile @@ -233,7 +233,7 @@ gem 'net-ssh', '~> 3.0.1' gem 'base32', '~> 0.3.0' # Sentry integration -gem 'sentry-raven', '~> 1.1.0' +gem 'sentry-raven', '~> 2.0.0' gem 'premailer-rails', '~> 1.9.0' diff --git a/Gemfile.lock b/Gemfile.lock index 1db8c9dd8c8..66e566de3c1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -664,8 +664,8 @@ GEM activesupport (>= 3.1) select2-rails (3.5.9.3) thor (~> 0.14) - sentry-raven (1.1.0) - faraday (>= 0.7.6) + sentry-raven (2.0.2) + faraday (>= 0.7.6, < 0.10.x) settingslogic (2.0.9) sexp_processor (4.7.0) sham_rack (1.3.6) @@ -950,7 +950,7 @@ DEPENDENCIES sdoc (~> 0.3.20) seed-fu (~> 2.3.5) select2-rails (~> 3.5.9) - sentry-raven (~> 1.1.0) + sentry-raven (~> 2.0.0) settingslogic (~> 2.0.9) sham_rack (~> 1.3.6) shoulda-matchers (~> 2.8.0) diff --git a/config/application.rb b/config/application.rb index 4792f6670a8..f5c900da8cf 100644 --- a/config/application.rb +++ b/config/application.rb @@ -50,6 +50,7 @@ module Gitlab # - Build variables (:variables) # - GitLab Pages SSL cert/key info (:certificate, :encrypted_key) # - Webhook URLs (:hook) + # - GitLab-shell secret token (:secret_token) # - Sentry DSN (:sentry_dsn) # - Deploy keys (:key) config.filter_parameters += %i( @@ -62,6 +63,7 @@ module Gitlab password password_confirmation private_token + secret_token sentry_dsn variables ) diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index 5892c1de024..4f30d1265c8 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -18,6 +18,8 @@ if Rails.env.production? # Sanitize fields based on those sanitized from Rails. config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) + # Sanitize authentication headers + config.sanitize_http_headers = %w[Authorization Private-Token] config.tags = { program: Gitlab::Sentry.program_context } end end -- cgit v1.2.1 From f377f82651998dc56301cc6540867b5120da43f2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 16:13:55 +0100 Subject: Fixed banzai test failures --- spec/lib/banzai/filter/syntax_highlight_filter_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index b1370bca833..d265d29ee86 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -6,21 +6,21 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do context "when no language is specified" do it "highlights as plaintext" do result = filter('
    def fun end
    ') - expect(result.to_html).to eq('
    def fun end
    ') + expect(result.to_html).to eq('
    def fun end
    ') end end context "when a valid language is specified" do it "highlights as that language" do result = filter('
    def fun end
    ') - expect(result.to_html).to eq('
    def fun end
    ') + expect(result.to_html).to eq('
    def fun end
    ') end end context "when an invalid language is specified" do it "highlights as plaintext" do result = filter('
    This is a test
    ') - expect(result.to_html).to eq('
    This is a test
    ') + expect(result.to_html).to eq('
    This is a test
    ') end end @@ -31,7 +31,7 @@ describe Banzai::Filter::SyntaxHighlightFilter, lib: true do it "highlights as plaintext" do result = filter('
    This is a test
    ') - expect(result.to_html).to eq('
    This is a test
    ') + expect(result.to_html).to eq('
    This is a test
    ') end end end -- cgit v1.2.1 From b5f954177825fbea9245149805541cffb2e0de05 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 4 Oct 2016 02:40:03 -0300 Subject: Skip wiki creation when GitHub project has wiki enabled If the GitHub project repository has wiki, we should not create the default wiki. Otherwise the GitHub importer will fail because the wiki repository already exist. This bug was introduced here https://gitlab.com/gitlab-org/gitlab-ce/commit/892dea67717c0efbd6a28f763 9f34535ec0a8747 --- app/services/projects/create_service.rb | 8 ++++- lib/gitlab/github_import/importer.rb | 3 +- lib/gitlab/github_import/project_creator.rb | 35 +++++++++++++++------- .../gitlab/github_import/project_creator_spec.rb | 24 +++++++++++++-- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index be749ba4a1c..76266139d09 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -7,6 +7,8 @@ module Projects def execute forked_from_project_id = params.delete(:forked_from_project_id) import_data = params.delete(:import_data) + @skip_wiki = params.delete(:skip_wiki) + @project = Project.new(params) # Make sure that the user is allowed to use the specified visibility level @@ -80,7 +82,7 @@ module Projects log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") unless @project.gitlab_project_import? - @project.create_wiki if @project.feature_available?(:wiki, current_user) + @project.create_wiki unless skip_wiki? @project.build_missing_services @project.create_labels @@ -94,6 +96,10 @@ module Projects end end + def skip_wiki? + !@project.feature_available?(:wiki, current_user) || @skip_wiki + end + def save_project_and_import_data(import_data) Project.transaction do @project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index b8321244473..4b70f33a851 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -170,10 +170,9 @@ module Gitlab end def import_wiki - unless project.wiki_enabled? + unless project.wiki.repository_exists? wiki = WikiFormatter.new(project) gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url) - project.project.update_attribute(:wiki_access_level, ProjectFeature::ENABLED) end rescue Gitlab::Shell::Error => e # GitHub error message when the wiki repo has not been created, diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index 605abfabdab..a2410068845 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -1,7 +1,7 @@ module Gitlab module GithubImport class ProjectCreator - attr_reader :repo, :namespace, :current_user, :session_data + attr_reader :repo, :name, :namespace, :current_user, :session_data def initialize(repo, name, namespace, current_user, session_data) @repo = repo @@ -12,24 +12,37 @@ module Gitlab end def execute - project = ::Projects::CreateService.new( + ::Projects::CreateService.new( current_user, - name: @name, - path: @name, + name: name, + path: name, description: repo.description, namespace_id: namespace.id, - visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility, + visibility_level: visibility_level, import_type: "github", import_source: repo.full_name, - import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@") + import_url: import_url, + skip_wiki: skip_wiki ).execute + end + + private - # If repo has wiki we'll import it later - if repo.has_wiki? && project - project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) - end + def import_url + repo.clone_url.sub('https://', "https://#{session_data[:github_access_token]}@") + end + + def visibility_level + repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility + end - project + # + # If the GitHub project repository has wiki, we should not create the + # default wiki. Otherwise the GitHub importer will fail because the wiki + # repository already exist. + # + def skip_wiki + repo.has_wiki? end end end diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb index ab06b7bc5bb..a73b1f4ff5d 100644 --- a/spec/lib/gitlab/github_import/project_creator_spec.rb +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do expect(project.import_data.credentials).to eq(user: 'asdffg', password: nil) end - context 'when Github project is private' do + context 'when GitHub project is private' do it 'sets project visibility to private' do repo.private = true @@ -43,7 +43,7 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do end end - context 'when Github project is public' do + context 'when GitHub project is public' do before do allow_any_instance_of(ApplicationSetting).to receive(:default_project_visibility).and_return(Gitlab::VisibilityLevel::INTERNAL) end @@ -56,5 +56,25 @@ describe Gitlab::GithubImport::ProjectCreator, lib: true do expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) end end + + context 'when GitHub project has wiki' do + it 'does not create the wiki repository' do + allow(repo).to receive(:has_wiki?).and_return(true) + + project = service.execute + + expect(project.wiki.repository_exists?).to eq false + end + end + + context 'when GitHub project does not have wiki' do + it 'creates the wiki repository' do + allow(repo).to receive(:has_wiki?).and_return(false) + + project = service.execute + + expect(project.wiki.repository_exists?).to eq true + end + end end end -- cgit v1.2.1 From 0bb7bfd0828bd8d0bcdb62b1fe0576efc36ebb78 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 4 Oct 2016 11:50:53 -0300 Subject: Update CHANGELOG --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d654975d886..2f5be2da669 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -44,11 +44,12 @@ v 8.13.0 (unreleased) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) - - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) + - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) v 8.12.4 (unreleased) - Fix type mismatch bug when closing Jira issue + - Skip wiki creation when GitHub project has wiki enabled - Fix issues importing services via Import/Export - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. (lukehowell) -- cgit v1.2.1 From bd9bdddad3b24dbd22268ccc2e9fba208ed02d5b Mon Sep 17 00:00:00 2001 From: Wolfgang Faust Date: Tue, 4 Oct 2016 12:10:14 -0400 Subject: Update housekeeping docs for new GitLab UI. --- doc/administration/housekeeping.md | 2 +- doc/administration/img/housekeeping_settings.png | Bin 19347 -> 27420 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md index 34b4f1faa94..ad1fa98b63b 100644 --- a/doc/administration/housekeeping.md +++ b/doc/administration/housekeeping.md @@ -12,7 +12,7 @@ revisions (to reduce disk space and increase performance) and removing unreachable objects which may have been created from prior invocations of `git add`. -You can find this option under your **[Project] > Settings**. +You can find this option under your **[Project] > Edit Project**. --- diff --git a/doc/administration/img/housekeeping_settings.png b/doc/administration/img/housekeeping_settings.png index f72ad9a45d5..6ebc6205635 100644 Binary files a/doc/administration/img/housekeeping_settings.png and b/doc/administration/img/housekeeping_settings.png differ -- cgit v1.2.1 From 5c9ac560e7e916c8a082c99309fbd4031b1a6803 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 5 Oct 2016 00:10:23 +0800 Subject: Introduce all_state_names so that we could avoid NOT IN --- app/models/commit_status.rb | 4 ++-- app/models/concerns/has_status.rb | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 99622014662..ee3396abe04 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -32,8 +32,8 @@ class CommitStatus < ActiveRecord::Base scope :exclude_ignored, -> do quoted_when = connection.quote_column_name('when') # We want to ignore failed_but_allowed jobs - where("allow_failure = ? OR status NOT IN (?)", - false, [:failed, :canceled]). + where("allow_failure = ? OR status IN (?)", + false, all_state_names - [:failed, :canceled]). # We want to ignore skipped manual jobs where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). # We want to ignore skipped on_failure diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 2dd2c5f2c0f..9f64f76721d 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -43,6 +43,10 @@ module HasStatus def finished_at all.maximum(:finished_at) end + + def all_state_names + state_machines.values.flat_map(&:states).flat_map { |s| s.map(&:name) } + end end included do -- cgit v1.2.1 From b6b5033516210fc431a70f729b5678539c039b3f Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 23 Aug 2016 15:20:17 -0400 Subject: docs: clarify /projects endpoint description --- doc/api/projects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 869907b0dd7..a88a878428a 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -20,7 +20,7 @@ Constants for project visibility levels are next: ## List projects -Get a list of projects accessible by the authenticated user. +Get a list of projects for which the authenticated user is a member. ``` GET /projects -- cgit v1.2.1 From e956a24dfd45baaafe93a520df61015bba40a4da Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Tue, 23 Aug 2016 15:23:56 -0400 Subject: api: add /projects/visible API endpoint FIxes #19361, #3119. --- CHANGELOG | 1 + doc/api/projects.md | 129 +++++++++++++++++++++++++++++++++++++ lib/api/projects.rb | 15 +++++ spec/requests/api/projects_spec.rb | 11 ++++ 4 files changed, 156 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index bfc312ca19c..068a523f7f9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -155,6 +155,7 @@ v 8.12.0 - Test migration paths from 8.5 until current release !4874 - Replace animateEmoji timeout with eventListener (ClemMakesApps) - Show badges in Milestone tabs. !5946 (Dan Rowden) + - Add `/projects/visible` API endpoint (Ben Boeckel) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) diff --git a/doc/api/projects.md b/doc/api/projects.md index a88a878428a..409e67ea566 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -153,6 +153,135 @@ Parameters: ] ``` +Get a list of projects for which the authenticated user can see. + +``` +GET /projects/visible +``` + +Parameters: + +- `archived` (optional) - if passed, limit by archived status +- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private` +- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` +- `search` (optional) - Return list of authorized projects according to a search criteria + +```json +[ + { + "id": 4, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 0, + "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", + "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", + "web_url": "http://example.com/diaspora/diaspora-client", + "tag_list": [ + "example", + "disapora client" + ], + "owner": { + "id": 3, + "name": "Diaspora", + "created_at": "2013-09-30T13:46:02Z" + }, + "name": "Diaspora Client", + "name_with_namespace": "Diaspora / Diaspora Client", + "path": "diaspora-client", + "path_with_namespace": "diaspora/diaspora-client", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "builds_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "creator_id": 3, + "namespace": { + "created_at": "2013-09-30T13:46:02Z", + "description": "", + "id": 3, + "name": "Diaspora", + "owner_id": 1, + "path": "diaspora", + "updated_at": "2013-09-30T13:46:02Z" + }, + "archived": false, + "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png", + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 0, + "runners_token": "b8547b1dc37721d05889db52fa2f02", + "public_builds": true, + "shared_with_groups": [] + }, + { + "id": 6, + "description": null, + "default_branch": "master", + "public": false, + "visibility_level": 0, + "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", + "http_url_to_repo": "http://example.com/brightbox/puppet.git", + "web_url": "http://example.com/brightbox/puppet", + "tag_list": [ + "example", + "puppet" + ], + "owner": { + "id": 4, + "name": "Brightbox", + "created_at": "2013-09-30T13:46:02Z" + }, + "name": "Puppet", + "name_with_namespace": "Brightbox / Puppet", + "path": "puppet", + "path_with_namespace": "brightbox/puppet", + "issues_enabled": true, + "open_issues_count": 1, + "merge_requests_enabled": true, + "builds_enabled": true, + "wiki_enabled": true, + "snippets_enabled": false, + "container_registry_enabled": false, + "created_at": "2013-09-30T13:46:02Z", + "last_activity_at": "2013-09-30T13:46:02Z", + "creator_id": 3, + "namespace": { + "created_at": "2013-09-30T13:46:02Z", + "description": "", + "id": 4, + "name": "Brightbox", + "owner_id": 1, + "path": "brightbox", + "updated_at": "2013-09-30T13:46:02Z" + }, + "permissions": { + "project_access": { + "access_level": 10, + "notification_level": 3 + }, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + }, + "archived": false, + "avatar_url": null, + "shared_runners_enabled": true, + "forks_count": 0, + "star_count": 0, + "runners_token": "b8547b1dc37721d05889db52fa2f02", + "public_builds": true, + "shared_with_groups": [] + } +] +``` + ### List owned projects Get a list of projects which are owned by the authenticated user. diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 680055c95eb..52afbb4eef3 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -32,6 +32,21 @@ module API end end + # Get a list of visible projects for authenticated user + # + # Example Request: + # GET /projects/visible + get '/visible' do + @projects = ProjectsFinder.new.execute(current_user) + @projects = filter_projects(@projects) + @projects = paginate @projects + if params[:simple] + present @projects, with: Entities::BasicProjectDetails, user: current_user + else + present @projects, with: Entities::ProjectWithAccess, user: current_user + end + end + # Get an owned projects list for authenticated user # # Example Request: diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 4a0d727faea..5bf8b64b84b 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -175,6 +175,17 @@ describe API::API, api: true do end end + describe 'GET /projects/visible' do + let(:public_project) { create(:project, :public) } + + it 'returns the projects viewable by the user' do + get api('/projects/visible', user3) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project4.id) + end + end + describe 'GET /projects/starred' do let(:public_project) { create(:project, :public) } -- cgit v1.2.1 From 355f97f8c321e815b245021488ae4a2a5aa6c2c3 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 4 Oct 2016 17:24:31 +0100 Subject: Move /projects/visible entry to 8.13 --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 068a523f7f9..e0f57029f9d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) + - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 @@ -155,7 +156,6 @@ v 8.12.0 - Test migration paths from 8.5 until current release !4874 - Replace animateEmoji timeout with eventListener (ClemMakesApps) - Show badges in Milestone tabs. !5946 (Dan Rowden) - - Add `/projects/visible` API endpoint (Ben Boeckel) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) -- cgit v1.2.1 From 706737a004b67303f15ccbd1f8630b0d80f481e9 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 4 Oct 2016 13:21:26 +0200 Subject: Exclude system notes from Project.trending Having many system notes isn't really an indication of a project being trending. Including these notes would lead to projects with lots of commit cross references (for example) showing up in the trending projects list. --- app/models/project.rb | 1 + spec/models/project_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 507228606df..ecd742a17d5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -380,6 +380,7 @@ class Project < ActiveRecord::Base SELECT project_id, COUNT(*) AS amount FROM notes WHERE created_at >= #{sanitize(since)} + AND system IS FALSE GROUP BY project_id ) join_note_counts ON projects.id = join_note_counts.project_id" diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ef854a25321..1a316176f63 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -826,6 +826,14 @@ describe Project, models: true do expect(subject).to eq([project2, project1]) end end + + it 'does not take system notes into account' do + 10.times do + create(:note_on_commit, project: project2, system: true) + end + + expect(described_class.trending.to_a).to eq([project1, project2]) + end end describe '.visible_to_user' do -- cgit v1.2.1 From 42cb659726822d500086ce09f712b858228f4caa Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 4 Oct 2016 17:46:08 +0100 Subject: Update API docs and specs for /projects/visible --- doc/api/projects.md | 4 +++- spec/requests/api/projects_spec.rb | 23 +++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 409e67ea566..2a1054a5337 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -33,6 +33,7 @@ Parameters: - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `search` (optional) - Return list of authorized projects according to a search criteria +- `simple` (optional) - When set, return only the ID, URL, name, and path of each project ```json [ @@ -153,7 +154,7 @@ Parameters: ] ``` -Get a list of projects for which the authenticated user can see. +Get a list of projects which the authenticated user can see. ``` GET /projects/visible @@ -166,6 +167,7 @@ Parameters: - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `search` (optional) - Return list of authorized projects according to a search criteria +- `simple` (optional) - When set, return only the ID, URL, name, and path of each project ```json [ diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5bf8b64b84b..fafceb6ecfa 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -178,11 +178,30 @@ describe API::API, api: true do describe 'GET /projects/visible' do let(:public_project) { create(:project, :public) } + before do + public_project + project + project2 + project3 + project4 + end + it 'returns the projects viewable by the user' do - get api('/projects/visible', user3) + get api('/projects/visible', user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }). + to contain_exactly(public_project.id, project.id, project2.id, project3.id) + end + + it 'shows only public projects when the user only has access to those' do + get api('/projects/visible', user2) + expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project4.id) + expect(json_response.map { |project| project['id'] }). + to contain_exactly(public_project.id) end end -- cgit v1.2.1 From 1097a4a68e64ce11cd31335ec79cf2457d824573 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Tue, 4 Oct 2016 12:34:19 -0500 Subject: Fix section name for 8.12.2 version. --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 453e7e25f56..42862fb4ca2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,7 +59,7 @@ v 8.12.4 (unreleased) v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -v 8.12.2 (unreleased) +v 8.12.2 - Fix Import/Export not recognising correctly the imported services. - Fix snippets pagination - Fix "Create project" button layout when visibility options are restricted -- cgit v1.2.1 From c9bcfc631a79db97203d51d7c221a7549ba65adf Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 4 Oct 2016 17:22:58 +0200 Subject: Remove lease from Event#reset_project_activity Per GitLab.com's performance metrics this method could take up to 5 seconds of wall time to complete, while only taking 1-2 milliseconds of CPU time. Removing the Redis lease in favour of conditional updates allows us to work around this. A slight drawback is that this allows for multiple threads/processes to try and update the same row. However, only a single thread/process will ever win since the UPDATE query uses a WHERE condition to only update rows that were not updated in the last hour. Fixes gitlab-org/gitlab-ce#22473 --- CHANGELOG | 1 + app/models/event.rb | 19 +++++++------------ spec/models/event_spec.rb | 8 +++----- spec/models/project_spec.rb | 4 +--- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a47ec511452..3e66c4c8336 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,7 @@ v 8.13.0 (unreleased) - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? + - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - Add broadcast messages and alerts below sub-nav - Better empty state for Groups view - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) diff --git a/app/models/event.rb b/app/models/event.rb index 55a76e26f3c..633019fe0af 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -328,13 +328,15 @@ class Event < ActiveRecord::Base def reset_project_activity return unless project - # Don't even bother obtaining a lock if the last update happened less than - # 60 minutes ago. + # Don't bother updating if we know the project was updated recently. return if recent_update? - return unless try_obtain_lease - - project.update_column(:last_activity_at, created_at) + # At this point it's possible for multiple threads/processes to try to + # update the project. Only one query should actually perform the update, + # hence we add the extra WHERE clause for last_activity_at. + Project.unscoped.where(id: project_id). + where('last_activity_at > ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago). + update_all(last_activity_at: created_at) end private @@ -342,11 +344,4 @@ class Event < ActiveRecord::Base def recent_update? project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago end - - def try_obtain_lease - Gitlab::ExclusiveLease. - new("project:update_last_activity_at:#{project.id}", - timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i). - try_obtain - end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 8600eb4d2c4..af5002487cc 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -173,13 +173,11 @@ describe Event, models: true do it 'updates the project' do project.update(last_activity_at: 1.year.ago) - expect_any_instance_of(Gitlab::ExclusiveLease). - to receive(:try_obtain).and_return(true) + create_event(project, project.owner) - expect(project).to receive(:update_column). - with(:last_activity_at, a_kind_of(Time)) + project.reload - create_event(project, project.owner) + project.last_activity_at <= 1.minute.ago end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ef854a25321..3ab5ac78bba 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -308,8 +308,7 @@ describe Project, models: true do end describe 'last_activity methods' do - let(:timestamp) { Time.now - 2.hours } - let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) } + let(:project) { create(:project, last_activity_at: 2.hours.ago) } describe 'last_activity' do it 'alias last_activity to last_event' do @@ -321,7 +320,6 @@ describe Project, models: true do describe 'last_activity_date' do it 'returns the creation date of the project\'s last event if present' do - expect_any_instance_of(Event).to receive(:try_obtain_lease).and_return(true) new_event = create(:event, project: project, created_at: Time.now) expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i) -- cgit v1.2.1 From 1956bd5e9e44adcdf24ceaa45fddb7d02ba79b92 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Tue, 4 Oct 2016 22:21:06 +0300 Subject: Add position menu above ability to glDropdown. --- app/assets/javascripts/gl_dropdown.js | 13 ++++++++++ app/assets/javascripts/labels_select.js | 10 ++++++-- app/assets/javascripts/milestone_select.js | 10 ++++++-- app/assets/javascripts/users_select.js | 11 ++++++--- app/assets/stylesheets/pages/issuable.scss | 28 ---------------------- app/views/shared/issuable/_form.html.haml | 6 ++--- .../shared/issuable/_milestone_dropdown.html.haml | 2 +- 7 files changed, 41 insertions(+), 39 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 1b6db641200..d4403375643 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -443,6 +443,7 @@ var contentHtml; this.resetRows(); this.addArrowKeyEvent(); + if (this.options.setIndeterminateIds) { this.options.setIndeterminateIds.call(this); } @@ -460,9 +461,21 @@ if (this.options.filterable) { this.filterInput.focus(); } + + if (this.options.showMenuAbove) { + this.positionMenuAbove(); + } + return this.dropdown.trigger('shown.gl.dropdown'); }; + GitLabDropdown.prototype.positionMenuAbove = function() { + var $button = $(this.el); + var $menu = this.dropdown.find('.dropdown-menu'); + + $menu.css('top', ($button.height() + $menu.height()) * -1); + }; + GitLabDropdown.prototype.hidden = function(e) { var $input; this.resetRows(); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 7d88c979edb..69d9e0a4d79 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -4,7 +4,7 @@ var _this; _this = this; $('.js-label-select').each(function(i, dropdown) { - var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName; + var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove; $dropdown = $(dropdown); $toggleText = $dropdown.find('.dropdown-toggle-text'); projectId = $dropdown.data('project-id'); @@ -16,6 +16,7 @@ } showNo = $dropdown.data('show-no'); showAny = $dropdown.data('show-any'); + showMenuAbove = $dropdown.data('showMenuAbove'); defaultLabel = $dropdown.data('default-label'); abilityName = $dropdown.data('ability-name'); $selectbox = $dropdown.closest('.selectbox'); @@ -120,6 +121,7 @@ }); }; return $dropdown.glDropdown({ + showMenuAbove: showMenuAbove, data: function(term, callback) { return $.ajax({ url: labelUrl @@ -157,7 +159,11 @@ data = extraData.concat(data); } } - return callback(data); + + callback(data); + if (showMenuAbove) { + $dropdown.data('glDropdown').positionMenuAbove(); + } }); }, renderRow: function(label, instance) { diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 906b6b321a8..26cc6eb0e96 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -7,7 +7,7 @@ this.currentProject = JSON.parse(currentProject); } $('.js-milestone-select').each(function(i, dropdown) { - var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId; + var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove; $dropdown = $(dropdown); projectId = $dropdown.data('project-id'); milestonesUrl = $dropdown.data('milestones'); @@ -15,6 +15,7 @@ selectedMilestone = $dropdown.data('selected'); showNo = $dropdown.data('show-no'); showAny = $dropdown.data('show-any'); + showMenuAbove = $dropdown.data('showMenuAbove'); showUpcoming = $dropdown.data('show-upcoming'); useId = $dropdown.data('use-id'); defaultLabel = $dropdown.data('default-label'); @@ -31,6 +32,7 @@ collapsedSidebarLabelTemplate = _.template(' <%- title %> '); } return $dropdown.glDropdown({ + showMenuAbove: showMenuAbove, data: function(term, callback) { return $.ajax({ url: milestonesUrl @@ -60,7 +62,11 @@ if (extraOptions.length) { extraOptions.push('divider'); } - return callback(extraOptions.concat(data)); + + callback(extraOptions.concat(data)); + if (showMenuAbove) { + $dropdown.data('glDropdown').positionMenuAbove(); + } }); }, filterable: true, diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 235d1d34bf6..05056a73aaf 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -14,11 +14,12 @@ $('.js-user-search').each((function(_this) { return function(i, dropdown) { var options = {}; - var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser; + var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove; $dropdown = $(dropdown); options.projectId = $dropdown.data('project-id'); options.showCurrentUser = $dropdown.data('current-user'); showNullUser = $dropdown.data('null-user'); + showMenuAbove = $dropdown.data('showMenuAbove'); showAnyUser = $dropdown.data('any-user'); firstUser = $dropdown.data('first-user'); options.authorId = $dropdown.data('author-id'); @@ -73,6 +74,7 @@ collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <% } else { %> <% } %>'); assigneeTemplate = _.template('<% if (username) { %> <% if( avatar ) { %> <% } %> <%- name %> @<%- username %> <% } else { %> No assignee - assign yourself <% } %>'); return $dropdown.glDropdown({ + showMenuAbove: showMenuAbove, data: function(term, callback) { var isAuthorFilter; isAuthorFilter = $('.js-author-search'); @@ -116,8 +118,11 @@ if (showDivider) { users.splice(showDivider, 0, "divider"); } - // Send the data back - return callback(users); + + callback(users); + if (showMenuAbove) { + $dropdown.data('glDropdown').positionMenuAbove(); + } }); }, filterable: true, diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 1a5cb4facc3..41079b6eeb5 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -419,31 +419,3 @@ } } } - -.issue-form { - - .dropdown-menu-labels { - top: -391px; - - &.is-loading { - top: -166px; - } - } - - .dropdown-menu-milestone { - top: -310px; - - &.is-loading { - top: -166px; - } - } - - .dropdown-menu-assignee { - top: -315px; - - &.is-loading { - top: -101px; - } - } - -} diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 1c2324e7f75..c3f4e10c954 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -86,19 +86,19 @@ - if issuable.assignee_id = f.hidden_field :assignee_id = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee" } }) + placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input" + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input" .form-group - has_labels = issuable.project.labels.any? = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false } + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' } - if has_due_date .col-lg-6 .form-group diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 7458afdaa58..30d3c84666d 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -4,7 +4,7 @@ - if selected.present? = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", - placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do + placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project -- cgit v1.2.1 From 2a0c6e8a86823544c08183cb5ba0ced7f2510935 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 22:50:59 +0100 Subject: Improves CSS for `branch-commit` class --- app/assets/stylesheets/pages/pipelines.scss | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 6484e5c02a9..b2662b812b7 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -22,6 +22,11 @@ .table.builds { min-width: 1200px; + + .branch-commit { + width: 33%; + } + } } @@ -39,10 +44,6 @@ overflow: auto; } -.pipelines .table.builds .branch-commit { - width: 33%; -} - .table.builds { min-width: 900px; -- cgit v1.2.1 From d1c1b41ffe0888bd7c88a26a59c4b1fa98709fd5 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Tue, 4 Oct 2016 16:42:47 -0700 Subject: add spaces and fix handle styles --- app/assets/stylesheets/pages/profile.scss | 12 ++++++++---- app/views/users/show.html.haml | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 020ef5339d2..efb90772214 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -213,25 +213,29 @@ } .user-profile { + .cover-controls a { margin-left: 5px; } + .profile-header { margin: 0 auto; + .avatar-holder { width: 90px; margin: 0 auto 10px; } + .user-info { - .handle { - color: $gl-gray-light; - } + .member-date { margin-bottom: 4px; } } } + @media (max-width: $screen-xs-max) { + .cover-block { padding-top: 20px; } @@ -255,7 +259,7 @@ } .user-profile-nav { - margin-top: 15px; + margin-top: 10px; } table.u2f-registrations { diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 60fc0c0daf6..520f2ab7e04 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -40,10 +40,10 @@ .user-info .cover-title = @user.name - %span.handle - @#{@user.username} .cover-desc.member-date + %span.middle-dot-divider + @#{@user.username} %span.middle-dot-divider Member since #{@user.created_at.to_s(:medium)} -- cgit v1.2.1 From 27536459e53d5fd78f525465464743164a8c5820 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Tue, 4 Oct 2016 18:18:18 -0700 Subject: 19499 Update project dropdowns --- app/assets/stylesheets/pages/projects.scss | 3 ++- app/views/projects/buttons/_download.html.haml | 4 ++-- app/views/projects/buttons/_dropdown.html.haml | 3 ++- app/views/projects/show.html.haml | 5 ++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 78bc4b79e86..87548dcb590 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -146,7 +146,8 @@ } .project-repo-btn-group, - .notification-dropdown { + .notification-dropdown, + .project-dropdown { margin-left: 10px; } diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 24de020917a..9089586a89d 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,9 +1,9 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - %span.btn-group{class: 'hidden-xs hidden-sm btn-grouped'} + %span{class: 'hidden-xs hidden-sm'} .dropdown.inline %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') - %span.caret + = icon("caret-down") %span.sr-only Select Archive Format %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index ca907077c2b..6cd9b98a706 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -1,7 +1,8 @@ - if current_user - .btn-group + .dropdown.inline.project-dropdown %a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('plus') + = icon("caret-down") %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown - can_create_issue = can?(current_user, :create_issue, @project) - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 9adce776c1c..ea4deb6cb28 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -71,9 +71,8 @@ = render 'shared/members/access_request_buttons', source: @project = render "projects/buttons/koding" - .btn-group.project-repo-btn-group - = render 'projects/buttons/download', project: @project, ref: @ref - = render 'projects/buttons/dropdown' + = render 'projects/buttons/download', project: @project, ref: @ref + = render 'projects/buttons/dropdown' = render 'shared/notifications/button', notification_setting: @notification_setting - if @repository.commit -- cgit v1.2.1 From fea80aa12d6a0b55513e497b2b8e5d0aa86a7d76 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 4 Oct 2016 20:53:15 -0700 Subject: Fix project deletion when feature visibility is set to private Projects that are destroyed are put in the pending_delete state. The ProjectDestroyWorker checks whether the current user has access, but since the ProjectFeature class uses the default scope of the Project, it will not be able to find the right project. This was a regression in 8.12 that caused the following stack trace: ``` NoMethodError: undefined method `team' for nil:NilClass from app/models/project_feature.rb:62:in `get_permission' from app/models/project_feature.rb:34:in `feature_available?' from app/models/project.rb:21:in `feature_available?' from app/policies/project_policy.rb:170:in `disabled_features!' from app/policies/project_policy.rb:29:in `rules' from app/policies/base_policy.rb:82:in `block in abilities' from app/policies/base_policy.rb:113:in `collect_rules' from app/policies/base_policy.rb:82:in `abilities' from app/policies/base_policy.rb:50:in `abilities' from app/models/ability.rb:64:in `uncached_allowed' from app/models/ability.rb:58:in `allowed' from app/models/ability.rb:49:in `allowed?' from app/services/base_service.rb:11:in `can?' from lib/gitlab/metrics/instrumentation.rb:155:in `block in can?' from lib/gitlab/metrics/method_call.rb:23:in `measure' from lib/gitlab/metrics/instrumentation.rb:155:in `can?' from app/services/projects/destroy_service.rb:18:in `execute' ``` Closes #22948 --- CHANGELOG | 1 + app/models/project_feature.rb | 5 ++++- spec/services/projects/destroy_service_spec.rb | 23 ++++++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e2e3033615f..9635309b052 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -53,6 +53,7 @@ v 8.13.0 (unreleased) v 8.12.4 (unreleased) - Fix type mismatch bug when closing Jira issue - Skip wiki creation when GitHub project has wiki enabled + - Fix failed project deletion when feature visibility set to private - Fix issues importing services via Import/Export - Restrict failed login attempts for users with 2FA enabled - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. (lukehowell) diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 8c9534c3565..530f7d5a30e 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -20,7 +20,10 @@ class ProjectFeature < ActiveRecord::Base FEATURES = %i(issues merge_requests wiki snippets builds) - belongs_to :project + # Default scopes force us to unscope here since a service may need to check + # permissions for a project in pending_delete + # http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to + belongs_to :project, -> { unscope(where: :pending_delete) } default_value_for :builds_access_level, value: ENABLED, allows_nil: false default_value_for :issues_access_level, value: ENABLED, allows_nil: false diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 29341c5e57e..7dcd03496bb 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -5,6 +5,7 @@ describe Projects::DestroyService, services: true do let!(:project) { create(:project, namespace: user.namespace) } let!(:path) { project.repository.path_to_repo } let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") } + let!(:async) { false } # execute or async_execute context 'Sidekiq inline' do before do @@ -28,6 +29,22 @@ describe Projects::DestroyService, services: true do it { expect(Dir.exist?(remove_path)).to be_truthy } end + context 'async delete of project with private issue visibility' do + let!(:async) { true } + + before do + project.project_feature.update_attribute("issues_access_level", ProjectFeature::PRIVATE) + # Run sidekiq immediately to check that renamed repository will be removed + Sidekiq::Testing.inline! { destroy_project(project, user, {}) } + end + + it 'deletes the project' do + expect(Project.all).not_to include(project) + expect(Dir.exist?(path)).to be_falsey + expect(Dir.exist?(remove_path)).to be_falsey + end + end + context 'container registry' do before do stub_container_registry_config(enabled: true) @@ -52,6 +69,10 @@ describe Projects::DestroyService, services: true do end def destroy_project(project, user, params) - Projects::DestroyService.new(project, user, params).execute + if async + Projects::DestroyService.new(project, user, params).async_execute + else + Projects::DestroyService.new(project, user, params).execute + end end end -- cgit v1.2.1 From 35ced4dae480d61ddc4d73eb4695626ecc419e9c Mon Sep 17 00:00:00 2001 From: barthc Date: Thu, 1 Sep 2016 18:23:39 +0100 Subject: fix group links 404 --- app/assets/javascripts/api.js | 3 +- app/assets/javascripts/groups_select.js | 5 +-- app/assets/javascripts/project_select.js | 4 +-- app/assets/javascripts/search.js | 2 +- app/controllers/projects/group_links_controller.rb | 24 +++++++++----- app/helpers/selects_helper.rb | 6 ++-- app/views/projects/group_links/index.html.haml | 4 +-- lib/api/groups.rb | 3 ++ .../projects/group_links_controller_spec.rb | 37 +++++++++++++++++++++- spec/requests/api/groups_spec.rb | 10 ++++++ 10 files changed, 77 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 1cd2302111e..4f04392513f 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -23,12 +23,13 @@ }, // Return groups list. Filtered by query // Only active groups retrieved - groups: function(query, skip_ldap, callback) { + groups: function(query, skip_ldap, skip_groups, callback) { var url = Api.buildUrl(Api.groupsPath); return $.ajax({ url: url, data: { search: query, + skip_groups: skip_groups, per_page: 20 }, dataType: "json" diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index 7c2eebcdd44..5f06186504b 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -5,14 +5,15 @@ function GroupsSelect() { $('.ajax-groups-select').each((function(_this) { return function(i, select) { - var skip_ldap; + var skip_ldap, skip_groups; skip_ldap = $(select).hasClass('skip_ldap'); + skip_groups = $(select).data('skip-groups') || []; return $(select).select2({ placeholder: "Search for a group", multiple: $(select).hasClass('multiselect'), minimumInputLength: 0, query: function(query) { - return Api.groups(query.term, skip_ldap, function(groups) { + return Api.groups(query.term, skip_ldap, skip_groups, function(groups) { var data; data = { results: groups diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index 20b147500cf..4239ed2f889 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -23,7 +23,7 @@ data = groups.concat(projects); return finalCallback(data); }; - return Api.groups(term, false, groupsCallback); + return Api.groups(term, false, false, groupsCallback); }; } else { projectsCallback = finalCallback; @@ -72,7 +72,7 @@ data = groups.concat(projects); return finalCallback(data); }; - return Api.groups(query.term, false, groupsCallback); + return Api.groups(query.term, false, false, groupsCallback); }; } else { projectsCallback = finalCallback; diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index d34346f862b..8074a94f33e 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -10,7 +10,7 @@ filterable: true, fieldName: 'group_id', data: function(term, callback) { - return Api.groups(term, null, function(data) { + return Api.groups(term, false, false, function(data) { data.unshift({ name: 'Any' }); diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index d0c4550733c..7a7475a7345 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -4,17 +4,25 @@ class Projects::GroupLinksController < Projects::ApplicationController def index @group_links = project.project_group_links.all + + @skip_groups = @group_links.pluck(:group_id) + @skip_groups << project.group.try(:id) end def create - group = Group.find(params[:link_group_id]) - return render_404 unless can?(current_user, :read_group, group) - - project.project_group_links.create( - group: group, - group_access: params[:link_group_access], - expires_at: params[:expires_at] - ) + group = Group.find(params[:link_group_id]) if params[:link_group_id].present? + + if group + return render_404 unless can?(current_user, :read_group, group) + + project.project_group_links.create( + group: group, + group_access: params[:link_group_access], + expires_at: params[:expires_at] + ) + else + flash[:alert] = 'Please select a group.' + end redirect_to namespace_project_group_links_path(project.namespace, project) end diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index 5f27e33c6ad..8706876ae4a 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -49,12 +49,10 @@ module SelectsHelper end def select2_tag(id, opts = {}) - css_class = '' - css_class << 'multiselect ' if opts[:multiple] - css_class << (opts[:class] || '') + opts[:class] << ' multiselect' if opts[:multiple] value = opts[:selected] || '' - hidden_field_tag(id, value, class: css_class) + hidden_field_tag(id, value, opts) end private diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/index.html.haml index ca700cb3a3b..4c5dd9b88bf 100644 --- a/app/views/projects/group_links/index.html.haml +++ b/app/views/projects/group_links/index.html.haml @@ -8,10 +8,10 @@ .col-lg-9 %h5.prepend-top-0 Set a group to share - = form_tag namespace_project_group_links_path(@project.namespace, @project), method: :post do + = form_tag namespace_project_group_links_path(@project.namespace, @project), class: 'js-requires-input', method: :post do .form-group = label_tag :link_group_id, "Group", class: "label-light" - = groups_select_tag(:link_group_id, skip_group: @project.group.try(:path)) + = groups_select_tag(:link_group_id, data: { skip_groups: @skip_groups }, required: true) .form-group = label_tag :link_group_access, "Max access level", class: "label-light" .select-wrapper diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 953fa474e88..bfb89475025 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -6,6 +6,8 @@ module API resource :groups do # Get a groups list # + # Parameters: + # skip_groups (optional) - Array of group ids to exclude from list # Example Request: # GET /groups get do @@ -16,6 +18,7 @@ module API end @groups = @groups.search(params[:search]) if params[:search].present? + @groups = @groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? @groups = paginate @groups present @groups, with: Entities::Group end diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index fbe8758dda7..b9d9117c928 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' describe Projects::GroupLinksController do - let(:project) { create(:project, :private) } let(:group) { create(:group, :private) } + let(:group2) { create(:group, :private) } + let(:project) { create(:project, :private, group: group2) } let(:user) { create(:user) } before do @@ -46,5 +47,39 @@ describe Projects::GroupLinksController do expect(group.shared_projects).not_to include project end end + + context 'when project group id equal link group id' do + before do + post(:create, namespace_id: project.namespace.to_param, + project_id: project.to_param, + link_group_id: group2.id, + link_group_access: ProjectGroupLink.default_access) + end + + it 'does not share project with selected group' do + expect(group2.shared_projects).not_to include project + end + + it 'redirects to project group links page' do + expect(response).to redirect_to( + namespace_project_group_links_path(project.namespace, project) + ) + end + end + + context 'when link group id is not present' do + before do + post(:create, namespace_id: project.namespace.to_param, + project_id: project.to_param, + link_group_access: ProjectGroupLink.default_access) + end + + it 'redirects to project group links page' do + expect(response).to redirect_to( + namespace_project_group_links_path(project.namespace, project) + ) + expect(flash[:alert]).to eq('Please select a group.') + end + end end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 1f68ef1af8f..3ba257256a0 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -45,6 +45,16 @@ describe API::API, api: true do expect(json_response.length).to eq(2) end end + + context "when using skip_groups in request" do + it "returns all groups excluding skipped groups" do + get api("/groups", admin), skip_groups: [group2.id] + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + end + end end describe "GET /groups/:id" do -- cgit v1.2.1 From 8e3e1ea0a4111fbbf8f91946f67519116fb633f3 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Oct 2016 10:15:05 +0100 Subject: Removes class --- app/views/projects/pipelines/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index e1c983e1679..2d1df095bfa 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -48,7 +48,7 @@ %th.col-xs-1.col-sm-1 Status %th.col-xs-2.col-sm-4 Pipeline %th.col-xs-2.col-sm-2 Stages - %th.col-xs-2.col-sm-2.commit-message + %th.col-xs-2.col-sm-2 %th.hidden-xs.col-sm-3 = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages -- cgit v1.2.1 From 84b7dd763bd6a9a55b2a59039c57af588cc2519f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 29 Jul 2016 16:02:35 +0200 Subject: Use Grape DSL to document methods and their params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/access_requests.rb | 64 +++++++++++--------------- lib/api/members.rb | 96 +++++++++++++++++---------------------- spec/requests/api/members_spec.rb | 16 +++++-- 3 files changed, 79 insertions(+), 97 deletions(-) diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index d3db7740830..87915b19480 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -5,15 +5,14 @@ module API helpers ::API::Helpers::MembersHelpers %w[group project].each do |source_type| + params do + requires :id, type: String, desc: "The #{source_type} ID" + end resource source_type.pluralize do - # Get a list of group/project access requests viewable by the authenticated user. - # - # Parameters: - # id (required) - The group/project ID - # - # Example Request: - # GET /groups/:id/access_requests - # GET /projects/:id/access_requests + desc "Gets a list of access requests for a #{source_type}." do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::AccessRequester + end get ":id/access_requests" do source = find_source(source_type, params[:id]) @@ -23,14 +22,10 @@ module API present access_requesters.map(&:user), with: Entities::AccessRequester, source: source end - # Request access to the group/project - # - # Parameters: - # id (required) - The group/project ID - # - # Example Request: - # POST /groups/:id/access_requests - # POST /projects/:id/access_requests + desc "Requests access for the authenticated user to a #{source_type}." do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::AccessRequester + end post ":id/access_requests" do source = find_source(source_type, params[:id]) access_requester = source.request_access(current_user) @@ -42,37 +37,30 @@ module API end end - # Approve a group/project access request - # - # Parameters: - # id (required) - The group/project ID - # user_id (required) - The user ID of the access requester - # access_level (optional) - Access level - # - # Example Request: - # PUT /groups/:id/access_requests/:user_id/approve - # PUT /projects/:id/access_requests/:user_id/approve + desc 'Approves an access request for the given user.' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Member + end + params do + requires :user_id, type: Integer, desc: 'The user ID of the access requester' + optional :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)' + end put ':id/access_requests/:user_id/approve' do - required_attributes! [:user_id] source = find_source(source_type, params[:id]) - member = ::Members::ApproveAccessRequestService.new(source, current_user, params).execute + member = ::Members::ApproveAccessRequestService.new(source, current_user, declared(params)).execute status :created present member.user, with: Entities::Member, member: member end - # Deny a group/project access request - # - # Parameters: - # id (required) - The group/project ID - # user_id (required) - The user ID of the access requester - # - # Example Request: - # DELETE /groups/:id/access_requests/:user_id - # DELETE /projects/:id/access_requests/:user_id + desc 'Denies an access request for the given user.' do + detail 'This feature was introduced in GitLab 8.11.' + end + params do + requires :user_id, type: Integer, desc: 'The user ID of the access requester' + end delete ":id/access_requests/:user_id" do - required_attributes! [:user_id] source = find_source(source_type, params[:id]) ::Members::DestroyService.new(source, current_user, params). diff --git a/lib/api/members.rb b/lib/api/members.rb index 34df55fe192..b80818f0eb6 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -5,16 +5,16 @@ module API helpers ::API::Helpers::MembersHelpers %w[group project].each do |source_type| + params do + requires :id, type: String, desc: "The #{source_type} ID" + end resource source_type.pluralize do - # Get a list of group/project members viewable by the authenticated user. - # - # Parameters: - # id (required) - The group/project ID - # query - Query string - # - # Example Request: - # GET /groups/:id/members - # GET /projects/:id/members + desc 'Gets a list of group or project members viewable by the authenticated user.' do + success Entities::Member + end + params do + optional :query, type: String, desc: 'A query string to search for members' + end get ":id/members" do source = find_source(source_type, params[:id]) @@ -25,15 +25,12 @@ module API present users, with: Entities::Member, source: source end - # Get a group/project member - # - # Parameters: - # id (required) - The group/project ID - # user_id (required) - The user ID of the member - # - # Example Request: - # GET /groups/:id/members/:user_id - # GET /projects/:id/members/:user_id + desc 'Gets a member of a group or project.' do + success Entities::Member + end + params do + requires :user_id, type: Integer, desc: 'The user ID of the member' + end get ":id/members/:user_id" do source = find_source(source_type, params[:id]) @@ -43,26 +40,25 @@ module API present member.user, with: Entities::Member, member: member end - # Add a new group/project member - # - # Parameters: - # id (required) - The group/project ID - # user_id (required) - The user ID of the new member - # access_level (required) - A valid access level - # expires_at (optional) - Date string in the format YEAR-MONTH-DAY - # - # Example Request: - # POST /groups/:id/members - # POST /projects/:id/members + desc 'Adds a member to a group or project.' do + success Entities::Member + end + params do + requires :user_id, type: Integer, desc: 'The user ID of the new member' + requires :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)' + optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' + end post ":id/members" do source = find_source(source_type, params[:id]) authorize_admin_source!(source_type, source) - required_attributes! [:user_id, :access_level] member = source.members.find_by(user_id: params[:user_id]) - # This is to ensure back-compatibility but 409 behavior should be used - # for both project and group members in 9.0! + # We need this explicit check because `source.add_user` doesn't + # currently return the member created so it would return 201 even if + # the member already existed... + # The `source_type == 'group'` check is to ensure back-compatibility + # but 409 behavior should be used for both project and group members in 9.0! conflict!('Member already exists') if source_type == 'group' && member unless member @@ -79,21 +75,17 @@ module API end end - # Update a group/project member - # - # Parameters: - # id (required) - The group/project ID - # user_id (required) - The user ID of the member - # access_level (required) - A valid access level - # expires_at (optional) - Date string in the format YEAR-MONTH-DAY - # - # Example Request: - # PUT /groups/:id/members/:user_id - # PUT /projects/:id/members/:user_id + desc 'Updates a member of a group or project.' do + success Entities::Member + end + params do + requires :user_id, type: Integer, desc: 'The user ID of the new member' + requires :access_level, type: Integer, desc: 'A valid access level' + optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' + end put ":id/members/:user_id" do source = find_source(source_type, params[:id]) authorize_admin_source!(source_type, source) - required_attributes! [:user_id, :access_level] member = source.members.find_by!(user_id: params[:user_id]) attrs = attributes_for_keys [:access_level, :expires_at] @@ -108,18 +100,12 @@ module API end end - # Remove a group/project member - # - # Parameters: - # id (required) - The group/project ID - # user_id (required) - The user ID of the member - # - # Example Request: - # DELETE /groups/:id/members/:user_id - # DELETE /projects/:id/members/:user_id + desc 'Removes a user from a group or project.' + params do + requires :user_id, type: Integer, desc: 'The user ID of the member' + end delete ":id/members/:user_id" do source = find_source(source_type, params[:id]) - required_attributes! [:user_id] # This is to ensure back-compatibility but find_by! should be used # in that casse in 9.0! @@ -134,7 +120,7 @@ module API if member.nil? { message: "Access revoked", id: params[:user_id].to_i } else - ::Members::DestroyService.new(source, current_user, params).execute + ::Members::DestroyService.new(source, current_user, declared(params)).execute present member.user, with: Entities::Member, member: member end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 92032f09b17..d22e0595788 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -97,7 +97,10 @@ describe API::Members, api: true do shared_examples 'POST /:sources/:id/members' do |source_type| context "with :sources == #{source_type.pluralize}" do it_behaves_like 'a 404 response when source is private' do - let(:route) { post api("/#{source_type.pluralize}/#{source.id}/members", stranger) } + let(:route) do + post api("/#{source_type.pluralize}/#{source.id}/members", stranger), + user_id: access_requester.id, access_level: Member::MASTER + end end context 'when authenticated as a non-member or member with insufficient rights' do @@ -105,7 +108,8 @@ describe API::Members, api: true do context "as a #{type}" do it 'returns 403' do user = public_send(type) - post api("/#{source_type.pluralize}/#{source.id}/members", user) + post api("/#{source_type.pluralize}/#{source.id}/members", user), + user_id: access_requester.id, access_level: Member::MASTER expect(response).to have_http_status(403) end @@ -174,7 +178,10 @@ describe API::Members, api: true do shared_examples 'PUT /:sources/:id/members/:user_id' do |source_type| context "with :sources == #{source_type.pluralize}" do it_behaves_like 'a 404 response when source is private' do - let(:route) { put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) } + let(:route) do + put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger), + access_level: Member::MASTER + end end context 'when authenticated as a non-member or member with insufficient rights' do @@ -182,7 +189,8 @@ describe API::Members, api: true do context "as a #{type}" do it 'returns 403' do user = public_send(type) - put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user) + put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user), + access_level: Member::MASTER expect(response).to have_http_status(403) end -- cgit v1.2.1 From 2dc40f3c8061951624c1e922661311af650f2ef2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 12:20:55 +0200 Subject: Refactor BlobCiYamlSelector and BlobCiYamlSelectors to ES6. (Removes opts destructuring and inheritance boilerplate) --- app/assets/javascripts/blob/blob_ci_yaml.js | 46 ------------------------- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 32 +++++++++++++++++ 2 files changed, 32 insertions(+), 46 deletions(-) delete mode 100644 app/assets/javascripts/blob/blob_ci_yaml.js create mode 100644 app/assets/javascripts/blob/blob_ci_yaml.js.es6 diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js b/app/assets/javascripts/blob/blob_ci_yaml.js deleted file mode 100644 index 68758574967..00000000000 --- a/app/assets/javascripts/blob/blob_ci_yaml.js +++ /dev/null @@ -1,46 +0,0 @@ - -/*= require blob/template_selector */ - -(function() { - var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - hasProp = {}.hasOwnProperty; - - this.BlobCiYamlSelector = (function(superClass) { - extend(BlobCiYamlSelector, superClass); - - function BlobCiYamlSelector() { - return BlobCiYamlSelector.__super__.constructor.apply(this, arguments); - } - - BlobCiYamlSelector.prototype.requestFile = function(query) { - return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); - }; - - return BlobCiYamlSelector; - - })(TemplateSelector); - - this.BlobCiYamlSelectors = (function() { - function BlobCiYamlSelectors(opts) { - var ref; - this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitlab-ci-yml-selector'), this.editor = opts.editor; - this.$dropdowns.each((function(_this) { - return function(i, dropdown) { - var $dropdown; - $dropdown = $(dropdown); - return new BlobCiYamlSelector({ - pattern: /(.gitlab-ci.yml)/, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), - dropdown: $dropdown, - editor: _this.editor - }); - }; - })(this)); - } - - return BlobCiYamlSelectors; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 new file mode 100644 index 00000000000..9f219cecc23 --- /dev/null +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -0,0 +1,32 @@ +/*= require blob/template_selector */ + +class BlobCiYamlSelector extends TemplateSelector { + constructor(...args) { + super(...args); + } + + requestFile(query) { + return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); + }; +}; + +class BlobCiYamlSelectors { + constructor(opts) { + this.$dropdowns = opts.$dropdowns || $('.js-gitlab-ci-yml-selector'); + this.editor = opts.editor; + this.initSelectors(); + } + + initSelectors() { + this.$dropdowns.each((i, dropdown) => { + const $dropdown = $(dropdown); + return new BlobCiYamlSelector({ + pattern: /(.gitlab-ci.yml)/, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), + dropdown: $dropdown, + editor: this.editor + }); + }); + } +} -- cgit v1.2.1 From cfb03b3d06a3dbd16c6e193a128bb737a0a327fd Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 14:26:43 +0200 Subject: Refactor UserTabs to ES6. --- app/assets/javascripts/user.js.es6 | 2 +- app/assets/javascripts/user_tabs.js.es6 | 160 ++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/user_tabs.js.es6 diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index 6889d3a7491..c5893745d74 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -14,7 +14,7 @@ } initTabs() { - return new UserTabs({ + return new global.UserTabs({ parentEl: '.user-profile', action: this.opts.action }); diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 new file mode 100644 index 00000000000..b787700070e --- /dev/null +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -0,0 +1,160 @@ +/* +UserTabs + +Handles persisting and restoring the current tab selection and lazily-loading +content on the Users#show page. + +### Example Markup + + + +
    +
    + Activity Content +
    +
    + Groups Content +
    +
    + Contributed projects content +
    +
    + Projects content +
    +
    + Snippets content +
    +
    + +
    +
    + Loading Animation +
    +
    +*/ +(global => { + class UserTabs { + constructor (opts) { + this.loaded = {}; + this.defaultAction = opts.defaultAction || 'activity'; + this.action = opts.action || 'activity'; + this.$parentEl = $(opts.parentEl) || $(document); + this._location = window.location; + this.$parentEl.find('.nav-links a') + .each((i, navLink) => { + this.loaded[$(navLink).attr('data-action')] = false; + }); + this.actions = Object.keys(this.loaded); + this.bindEvents(); + + if (this.action === 'show') { + this.action = this.defaultAction; + } + + this.activateTab(this.action); + } + + bindEvents() { + return this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') + .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', (event) => this.tabShown(event)); + } + + tabShown(event) { + const $target = $(event.target); + const action = $target.data('action'); + const source = $target.attr('href'); + this.setTab(source, action); + return this.setCurrentAction(action); + } + + activateTab(action) { + return this.$parentEl.find(".nav-links .js-" + action + "-tab a") + .tab('show'); + } + + setTab(source, action) { + if (this.loaded[action]) { + return; + } + if (action === 'activity') { + this.loadActivities(source); + } + if (action === 'groups' || action === 'contributed' || action === 'projects' || action === 'snippets') { + return this.loadTab(source, action); + } + } + + loadTab(source, action) { + return $.ajax({ + beforeSend: () => this.toggleLoading(true), + complete: () => this.toggleLoading(false), + dataType: 'json', + type: 'GET', + url: source + ".json", + success: (data) => { + const tabSelector = 'div#' + action; + this.$parentEl.find(tabSelector).html(data.html); + this.loaded[action] = true; + return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); + } + }); + } + + loadActivities(source) { + if (this.loaded['activity']) { + return; + } + const $calendarWrap = this.$parentEl.find('.user-calendar'); + $calendarWrap.load($calendarWrap.data('href')); + new Activities(); + return this.loaded['activity'] = true; + } + + toggleLoading(status) { + return this.$parentEl.find('.loading-status .loading') + .toggle(status); + } + + setCurrentAction(action) { + const regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$'); + let new_state = this._location.pathname; + new_state = new_state.replace(/\/+$/, ""); + new_state = new_state.replace(regExp, ''); + if (action !== this.defaultAction) { + new_state += "/" + action; + } + new_state += this._location.search + this._location.hash; + history.replaceState({ + turbolinks: true, + url: new_state + }, document.title, new_state); + return new_state; + }; + } + global.UserTabs = UserTabs; +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 45ae34f883b777b83ce4ae319c23cb5396403b5e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 14:30:32 +0200 Subject: Properly scope BlobCiYamlSelector and BlobCiYamlSelectors. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 55 ++++++++++++++----------- app/assets/javascripts/blob_edit/edit_blob.js | 2 +- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index 9f219cecc23..5ae6f1a5940 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -1,32 +1,39 @@ /*= require blob/template_selector */ +((global) => { -class BlobCiYamlSelector extends TemplateSelector { - constructor(...args) { - super(...args); - } + class BlobCiYamlSelector extends TemplateSelector { + constructor(...args) { + super(...args); + } - requestFile(query) { - return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); + requestFile(query) { + return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); + }; }; -}; -class BlobCiYamlSelectors { - constructor(opts) { - this.$dropdowns = opts.$dropdowns || $('.js-gitlab-ci-yml-selector'); - this.editor = opts.editor; - this.initSelectors(); - } + global.BlobCiYamlSelector = BlobCiYamlSelector; + + class BlobCiYamlSelectors { + constructor(opts) { + this.$dropdowns = opts.$dropdowns || $('.js-gitlab-ci-yml-selector'); + this.editor = opts.editor; + this.initSelectors(); + } - initSelectors() { - this.$dropdowns.each((i, dropdown) => { - const $dropdown = $(dropdown); - return new BlobCiYamlSelector({ - pattern: /(.gitlab-ci.yml)/, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), - dropdown: $dropdown, - editor: this.editor + initSelectors() { + this.$dropdowns.each((i, dropdown) => { + const $dropdown = $(dropdown); + return new BlobCiYamlSelector({ + pattern: /(.gitlab-ci.yml)/, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), + dropdown: $dropdown, + editor: this.editor + }); }); - }); + } } -} + + global.BlobCiYamlSelectors = BlobCiYamlSelectors; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index de6cdd851be..0be4b6392bf 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -29,7 +29,7 @@ new BlobGitignoreSelectors({ editor: this.editor }); - new BlobCiYamlSelectors({ + new gl.BlobCiYamlSelectors({ editor: this.editor }); } -- cgit v1.2.1 From 8377c2fbc3f6b43b7cd3d965eca2a87e318ae227 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 15:13:15 +0200 Subject: Refactor Todos to ES6. --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/todos.js | 176 ----------------------------------- app/assets/javascripts/todos.js.es6 | 161 ++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 177 deletions(-) delete mode 100644 app/assets/javascripts/todos.js create mode 100644 app/assets/javascripts/todos.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ddf11ecf34c..fb2b6c19c46 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -40,7 +40,7 @@ new Milestone(); break; case 'dashboard:todos:index': - new Todos(); + new gl.Todos(); break; case 'projects:milestones:new': case 'projects:milestones:edit': diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js deleted file mode 100644 index 93421649ac7..00000000000 --- a/app/assets/javascripts/todos.js +++ /dev/null @@ -1,176 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Todos = (function() { - function Todos(opts) { - var ref; - if (opts == null) { - opts = {}; - } - this.allDoneClicked = bind(this.allDoneClicked, this); - this.doneClicked = bind(this.doneClicked, this); - this.el = (ref = opts.el) != null ? ref : $('.js-todos-options'); - this.perPage = this.el.data('perPage'); - this.clearListeners(); - this.initBtnListeners(); - this.initFilters(); - } - - Todos.prototype.clearListeners = function() { - $('.done-todo').off('click'); - $('.js-todos-mark-all').off('click'); - return $('.todo').off('click'); - }; - - Todos.prototype.initBtnListeners = function() { - $('.done-todo').on('click', this.doneClicked); - $('.js-todos-mark-all').on('click', this.allDoneClicked); - return $('.todo').on('click', this.goToTodoUrl); - }; - - Todos.prototype.initFilters = function() { - new UsersSelect(); - this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']); - this.initFilterDropdown($('.js-type-search'), 'type'); - this.initFilterDropdown($('.js-action-search'), 'action_id'); - - $('form.filter-form').on('submit', function (event) { - event.preventDefault(); - Turbolinks.visit(this.action + '&' + $(this).serialize()); - }); - }; - - Todos.prototype.initFilterDropdown = function($dropdown, fieldName, searchFields) { - $dropdown.glDropdown({ - selectable: true, - filterable: searchFields ? true : false, - fieldName: fieldName, - search: { fields: searchFields }, - data: $dropdown.data('data'), - clicked: function() { - return $dropdown.closest('form.filter-form').submit(); - } - }) - }; - - Todos.prototype.doneClicked = function(e) { - var $this; - e.preventDefault(); - e.stopImmediatePropagation(); - $this = $(e.currentTarget); - $this.disable(); - return $.ajax({ - type: 'POST', - url: $this.attr('href'), - dataType: 'json', - data: { - '_method': 'delete' - }, - success: (function(_this) { - return function(data) { - _this.redirectIfNeeded(data.count); - _this.clearDone($this.closest('li')); - return _this.updateBadges(data); - }; - })(this) - }); - }; - - Todos.prototype.allDoneClicked = function(e) { - var $this; - e.preventDefault(); - e.stopImmediatePropagation(); - $this = $(e.currentTarget); - $this.disable(); - return $.ajax({ - type: 'POST', - url: $this.attr('href'), - dataType: 'json', - data: { - '_method': 'delete' - }, - success: (function(_this) { - return function(data) { - $this.remove(); - $('.prepend-top-default').html('
    You\'re all done!
    '); - return _this.updateBadges(data); - }; - })(this) - }); - }; - - Todos.prototype.clearDone = function($row) { - var $ul; - $ul = $row.closest('ul'); - $row.remove(); - if (!$ul.find('li').length) { - return $ul.parents('.panel').remove(); - } - }; - - Todos.prototype.updateBadges = function(data) { - $('.todos-pending .badge, .todos-pending-count').text(data.count); - return $('.todos-done .badge').text(data.done_count); - }; - - Todos.prototype.getTotalPages = function() { - return this.el.data('totalPages'); - }; - - Todos.prototype.getCurrentPage = function() { - return this.el.data('currentPage'); - }; - - Todos.prototype.getTodosPerPage = function() { - return this.el.data('perPage'); - }; - - Todos.prototype.redirectIfNeeded = function(total) { - var currPage, currPages, newPages, pageParams, url; - currPages = this.getTotalPages(); - currPage = this.getCurrentPage(); - // Refresh if no remaining Todos - if (!total) { - location.reload(); - return; - } - // Do nothing if no pagination - if (!currPages) { - return; - } - newPages = Math.ceil(total / this.getTodosPerPage()); - // Includes query strings - url = location.href; - // If new total of pages is different than we have now - if (newPages !== currPages) { - // Redirect to previous page if there's one available - if (currPages > 1 && currPage === currPages) { - pageParams = { - page: currPages - 1 - }; - url = gl.utils.mergeUrlParams(pageParams, url); - } - return Turbolinks.visit(url); - } - }; - - Todos.prototype.goToTodoUrl = function(e) { - var todoLink; - todoLink = $(this).data('url'); - if (!todoLink) { - return; - } - // Allow Meta-Click or Mouse3-click to open in a new tab - if (e.metaKey || e.which === 2) { - e.preventDefault(); - return window.open(todoLink, '_blank'); - } else { - return Turbolinks.visit(todoLink); - } - }; - - return Todos; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 new file mode 100644 index 00000000000..63e4911400b --- /dev/null +++ b/app/assets/javascripts/todos.js.es6 @@ -0,0 +1,161 @@ +(global => { + + class Todos { + constructor(opts = {}) { + this.allDoneClicked = this.allDoneClicked.bind(this); + this.doneClicked = this.doneClicked.bind(this); + this.el = opts.el || $('.js-todos-options'); + this.perPage = this.el.data('perPage'); + this.clearListeners(); + this.initBtnListeners(); + this.initFilters(); + } + + clearListeners() { + $('.done-todo').off('click'); + $('.js-todos-mark-all').off('click'); + return $('.todo').off('click'); + } + + initBtnListeners() { + $('.done-todo').on('click', this.doneClicked); + $('.js-todos-mark-all').on('click', this.allDoneClicked); + return $('.todo').on('click', this.goToTodoUrl); + } + + initFilters() { + new UsersSelect(); + this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']); + this.initFilterDropdown($('.js-type-search'), 'type'); + this.initFilterDropdown($('.js-action-search'), 'action_id'); + + $('form.filter-form').on('submit', function (event) { + event.preventDefault(); + Turbolinks.visit(this.action + '&' + $(this).serialize()); + }); + } + + initFilterDropdown($dropdown, fieldName, searchFields) { + $dropdown.glDropdown({ + fieldName, + selectable: true, + filterable: searchFields ? true : false, + search: { fields: searchFields }, + data: $dropdown.data('data'), + clicked: function() { + return $dropdown.closest('form.filter-form').submit(); + } + }) + } + + doneClicked(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + const $target = $(e.currentTarget); + $target.disable(); + return $.ajax({ + type: 'POST', + url: $target.attr('href'), + dataType: 'json', + data: { + '_method': 'delete' + }, + success: data => { + this.redirectIfNeeded(data.count); + this.clearDone($target.closest('li')); + return this.updateBadges(data); + } + }); + } + + allDoneClicked(e) { + e.preventDefault(); + e.stopImmediatePropagation(); + $target = $(e.currentTarget); + $target.disable(); + return $.ajax({ + type: 'POST', + url: $target.attr('href'), + dataType: 'json', + data: { + '_method': 'delete' + }, + success: data => { + $target.remove(); + $('.prepend-top-default').html('
    You\'re all done!
    '); + return this.updateBadges(data); + } + }); + } + + clearDone($row) { + const $ul = $row.closest('ul'); + $row.remove(); + if (!$ul.find('li').length) { + return $ul.parents('.panel').remove(); + } + } + + updateBadges(data) { + $('.todos-pending .badge, .todos-pending-count').text(data.count); + return $('.todos-done .badge').text(data.done_count); + } + + getTotalPages() { + return this.el.data('totalPages'); + } + + getCurrentPage() { + return this.el.data('currentPage'); + } + + getTodosPerPage() { + return this.el.data('perPage'); + } + + redirectIfNeeded(total) { + let currPages = this.getTotalPages(); + currPage = this.getCurrentPage(); + + // Refresh if no remaining Todos + if (!total) { + window.location.reload(); + return; + } + // Do nothing if no pagination + if (!currPages) { + return; + } + + const newPages = Math.ceil(total / this.getTodosPerPage()); + let url = location.href; + + if (newPages !== currPages) { + // Redirect to previous page if there's one available + if (currPages > 1 && currPage === currPages) { + const pageParams = { + page: currPages - 1 + }; + url = gl.utils.mergeUrlParams(pageParams, url); + } + return Turbolinks.visit(url); + } + } + + goToTodoUrl(e) { + const todoLink = $(this).data('url'); + if (!todoLink) { + return; + } + // Allow Meta-Click or Mouse3-click to open in a new tab + if (e.metaKey || e.which === 2) { + e.preventDefault(); + return window.open(todoLink, '_blank'); + } else { + return Turbolinks.visit(todoLink); + } + } + } + + global.Todos = Todos; +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 01fdb521e2a4232ea857e9d85610be91b70f50fe Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 15:54:48 +0200 Subject: Fixup refs to currPage and currPages. --- app/assets/javascripts/todos.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index 63e4911400b..a0386dcc018 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -114,8 +114,8 @@ } redirectIfNeeded(total) { - let currPages = this.getTotalPages(); - currPage = this.getCurrentPage(); + const currPages = this.getTotalPages(); + const currPage = this.getCurrentPage(); // Refresh if no remaining Todos if (!total) { -- cgit v1.2.1 From b93ca73e223385c3689f28c1db1d0ea9e9e34153 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 15:59:33 +0200 Subject: Refactor SearchAutocomplete to ES6 class syntax. --- app/assets/javascripts/dispatcher.js | 3 +- app/assets/javascripts/search_autocomplete.js | 429 ---------------------- app/assets/javascripts/search_autocomplete.js.es6 | 424 +++++++++++++++++++++ 3 files changed, 426 insertions(+), 430 deletions(-) delete mode 100644 app/assets/javascripts/search_autocomplete.js create mode 100644 app/assets/javascripts/search_autocomplete.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index fb2b6c19c46..71af9a84710 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -279,7 +279,8 @@ Dispatcher.prototype.initSearch = function() { // Only when search form is present if ($('.search').length) { - return new SearchAutocomplete(); + debugger; + return new gl.SearchAutocomplete(); } }; diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js deleted file mode 100644 index 678d836f56f..00000000000 --- a/app/assets/javascripts/search_autocomplete.js +++ /dev/null @@ -1,429 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.SearchAutocomplete = (function() { - var KEYCODE; - - KEYCODE = { - ESCAPE: 27, - BACKSPACE: 8, - ENTER: 13, - UP: 38, - DOWN: 40 - }; - - function SearchAutocomplete(opts) { - var ref, ref1, ref2, ref3, ref4; - if (opts == null) { - opts = {}; - } - this.onSearchInputBlur = bind(this.onSearchInputBlur, this); - this.onClearInputClick = bind(this.onClearInputClick, this); - this.onSearchInputFocus = bind(this.onSearchInputFocus, this); - this.onSearchInputClick = bind(this.onSearchInputClick, this); - this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this); - this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this); - this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || ''; - // Dropdown Element - this.dropdown = this.wrap.find('.dropdown'); - this.dropdownContent = this.dropdown.find('.dropdown-content'); - this.locationBadgeEl = this.getElement('.location-badge'); - this.scopeInputEl = this.getElement('#scope'); - this.searchInput = this.getElement('.search-input'); - this.projectInputEl = this.getElement('#search_project_id'); - this.groupInputEl = this.getElement('#group_id'); - this.searchCodeInputEl = this.getElement('#search_code'); - this.repositoryInputEl = this.getElement('#repository_ref'); - this.clearInput = this.getElement('.js-clear-input'); - this.saveOriginalState(); - // Only when user is logged in - if (gon.current_user_id) { - this.createAutocomplete(); - } - this.searchInput.addClass('disabled'); - this.saveTextLength(); - this.bindEvents(); - } - - // Finds an element inside wrapper element - SearchAutocomplete.prototype.getElement = function(selector) { - return this.wrap.find(selector); - }; - - SearchAutocomplete.prototype.saveOriginalState = function() { - return this.originalState = this.serializeState(); - }; - - SearchAutocomplete.prototype.saveTextLength = function() { - return this.lastTextLength = this.searchInput.val().length; - }; - - SearchAutocomplete.prototype.createAutocomplete = function() { - return this.searchInput.glDropdown({ - filterInputBlur: false, - filterable: true, - filterRemote: true, - highlight: true, - enterCallback: false, - filterInput: 'input#search', - search: { - fields: ['text'] - }, - data: this.getData.bind(this), - selectable: true, - clicked: this.onClick.bind(this) - }); - }; - - SearchAutocomplete.prototype.getData = function(term, callback) { - var _this, contents, jqXHR; - _this = this; - if (!term) { - if (contents = this.getCategoryContents()) { - this.searchInput.data('glDropdown').filter.options.callback(contents); - this.enableAutocomplete(); - } - return; - } - // Prevent multiple ajax calls - if (this.loadingSuggestions) { - return; - } - this.loadingSuggestions = true; - return jqXHR = $.get(this.autocompletePath, { - project_id: this.projectId, - project_ref: this.projectRef, - term: term - }, function(response) { - var data, firstCategory, i, lastCategory, len, suggestion; - // Hide dropdown menu if no suggestions returns - if (!response.length) { - _this.disableAutocomplete(); - return; - } - data = []; - // List results - firstCategory = true; - for (i = 0, len = response.length; i < len; i++) { - suggestion = response[i]; - // Add group header before list each group - if (lastCategory !== suggestion.category) { - if (!firstCategory) { - data.push('separator'); - } - if (firstCategory) { - firstCategory = false; - } - data.push({ - header: suggestion.category - }); - lastCategory = suggestion.category; - } - data.push({ - id: (suggestion.category.toLowerCase()) + "-" + suggestion.id, - category: suggestion.category, - text: suggestion.label, - url: suggestion.url - }); - } - // Add option to proceed with the search - if (data.length) { - data.push('separator'); - data.push({ - text: "Result name contains \"" + term + "\"", - url: "/search?search=" + term + "&project_id=" + (_this.projectInputEl.val()) + "&group_id=" + (_this.groupInputEl.val()) - }); - } - return callback(data); - }).always(function() { - return _this.loadingSuggestions = false; - }); - }; - - SearchAutocomplete.prototype.getCategoryContents = function() { - var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils; - userId = gon.current_user_id; - utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions; - if (utils.isInGroupsPage() && groupOptions) { - options = groupOptions[utils.getGroupSlug()]; - } else if (utils.isInProjectPage() && projectOptions) { - options = projectOptions[utils.getProjectSlug()]; - } else if (dashboardOptions) { - options = dashboardOptions; - } - issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name; - items = [ - { - header: "" + name - }, { - text: 'Issues assigned to me', - url: issuesPath + "/?assignee_id=" + userId - }, { - text: "Issues I've created", - url: issuesPath + "/?author_id=" + userId - }, 'separator', { - text: 'Merge requests assigned to me', - url: mrPath + "/?assignee_id=" + userId - }, { - text: "Merge requests I've created", - url: mrPath + "/?author_id=" + userId - } - ]; - if (!name) { - items.splice(0, 1); - } - return items; - }; - - SearchAutocomplete.prototype.serializeState = function() { - return { - // Search Criteria - search_project_id: this.projectInputEl.val(), - group_id: this.groupInputEl.val(), - search_code: this.searchCodeInputEl.val(), - repository_ref: this.repositoryInputEl.val(), - scope: this.scopeInputEl.val(), - // Location badge - _location: this.locationBadgeEl.text() - }; - }; - - SearchAutocomplete.prototype.bindEvents = function() { - this.searchInput.on('keydown', this.onSearchInputKeyDown); - this.searchInput.on('keyup', this.onSearchInputKeyUp); - this.searchInput.on('click', this.onSearchInputClick); - this.searchInput.on('focus', this.onSearchInputFocus); - this.searchInput.on('blur', this.onSearchInputBlur); - this.clearInput.on('click', this.onClearInputClick); - return this.locationBadgeEl.on('click', (function(_this) { - return function() { - return _this.searchInput.focus(); - }; - })(this)); - }; - - SearchAutocomplete.prototype.enableAutocomplete = function() { - var _this; - // No need to enable anything if user is not logged in - if (!gon.current_user_id) { - return; - } - if (!this.dropdown.hasClass('open')) { - _this = this; - this.loadingSuggestions = false; - this.dropdown.addClass('open').trigger('shown.bs.dropdown'); - return this.searchInput.removeClass('disabled'); - } - }; - - SearchAutocomplete.prototype.onSearchInputKeyDown = function() { - // Saves last length of the entered text - return this.saveTextLength(); - }; - - SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) { - switch (e.keyCode) { - case KEYCODE.BACKSPACE: - // when trying to remove the location badge - if (this.lastTextLength === 0 && this.badgePresent()) { - this.removeLocationBadge(); - } - // When removing the last character and no badge is present - if (this.lastTextLength === 1) { - this.disableAutocomplete(); - } - // When removing any character from existin value - if (this.lastTextLength > 1) { - this.enableAutocomplete(); - } - break; - case KEYCODE.ESCAPE: - this.restoreOriginalState(); - break; - case KEYCODE.ENTER: - this.disableAutocomplete(); - break; - case KEYCODE.UP: - case KEYCODE.DOWN: - return; - default: - // Handle the case when deleting the input value other than backspace - // e.g. Pressing ctrl + backspace or ctrl + x - if (this.searchInput.val() === '') { - this.disableAutocomplete(); - } else { - // We should display the menu only when input is not empty - if (e.keyCode !== KEYCODE.ENTER) { - this.enableAutocomplete(); - } - } - } - this.wrap.toggleClass('has-value', !!e.target.value); - }; - - // Avoid falsy value to be returned - SearchAutocomplete.prototype.onSearchInputClick = function(e) { - // Prevents closing the dropdown menu - return e.stopImmediatePropagation(); - }; - - SearchAutocomplete.prototype.onSearchInputFocus = function() { - this.isFocused = true; - this.wrap.addClass('search-active'); - if (this.getValue() === '') { - return this.getData(); - } - }; - - SearchAutocomplete.prototype.getValue = function() { - return this.searchInput.val(); - }; - - SearchAutocomplete.prototype.onClearInputClick = function(e) { - e.preventDefault(); - return this.searchInput.val('').focus(); - }; - - SearchAutocomplete.prototype.onSearchInputBlur = function(e) { - this.isFocused = false; - this.wrap.removeClass('search-active'); - // If input is blank then restore state - if (this.searchInput.val() === '') { - return this.restoreOriginalState(); - } - }; - - SearchAutocomplete.prototype.addLocationBadge = function(item) { - var badgeText, category, value; - category = item.category != null ? item.category + ": " : ''; - value = item.value != null ? item.value : ''; - badgeText = "" + category + value; - this.locationBadgeEl.text(badgeText).show(); - return this.wrap.addClass('has-location-badge'); - }; - - SearchAutocomplete.prototype.hasLocationBadge = function() { - return this.wrap.is('.has-location-badge'); - }; - - SearchAutocomplete.prototype.restoreOriginalState = function() { - var i, input, inputs, len; - inputs = Object.keys(this.originalState); - for (i = 0, len = inputs.length; i < len; i++) { - input = inputs[i]; - this.getElement("#" + input).val(this.originalState[input]); - } - if (this.originalState._location === '') { - return this.locationBadgeEl.hide(); - } else { - return this.addLocationBadge({ - value: this.originalState._location - }); - } - }; - - SearchAutocomplete.prototype.badgePresent = function() { - return this.locationBadgeEl.length; - }; - - SearchAutocomplete.prototype.resetSearchState = function() { - var i, input, inputs, len, results; - inputs = Object.keys(this.originalState); - results = []; - for (i = 0, len = inputs.length; i < len; i++) { - input = inputs[i]; - // _location isnt a input - if (input === '_location') { - break; - } - results.push(this.getElement("#" + input).val('')); - } - return results; - }; - - SearchAutocomplete.prototype.removeLocationBadge = function() { - this.locationBadgeEl.hide(); - this.resetSearchState(); - this.wrap.removeClass('has-location-badge'); - return this.disableAutocomplete(); - }; - - SearchAutocomplete.prototype.disableAutocomplete = function() { - if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) { - this.searchInput.addClass('disabled'); - this.dropdown.removeClass('open').trigger('hidden.bs.dropdown'); - this.restoreMenu(); - } - }; - - SearchAutocomplete.prototype.restoreMenu = function() { - var html; - html = ""; - return this.dropdownContent.html(html); - }; - - SearchAutocomplete.prototype.onClick = function(item, $el, e) { - if (location.pathname.indexOf(item.url) !== -1) { - e.preventDefault(); - if (!this.badgePresent) { - if (item.category === 'Projects') { - this.projectInputEl.val(item.id); - this.addLocationBadge({ - value: 'This project' - }); - } - if (item.category === 'Groups') { - this.groupInputEl.val(item.id); - this.addLocationBadge({ - value: 'This group' - }); - } - } - $el.removeClass('is-active'); - this.disableAutocomplete(); - return this.searchInput.val('').focus(); - } - }; - - return SearchAutocomplete; - - })(); - - $(function() { - var $projectOptionsDataEl = $('.js-search-project-options'); - var $groupOptionsDataEl = $('.js-search-group-options'); - var $dashboardOptionsDataEl = $('.js-search-dashboard-options'); - - if ($projectOptionsDataEl.length) { - gl.projectOptions = gl.projectOptions || {}; - - var projectPath = $projectOptionsDataEl.data('project-path'); - - gl.projectOptions[projectPath] = { - name: $projectOptionsDataEl.data('name'), - issuesPath: $projectOptionsDataEl.data('issues-path'), - mrPath: $projectOptionsDataEl.data('mr-path') - }; - } - - if ($groupOptionsDataEl.length) { - gl.groupOptions = gl.groupOptions || {}; - - var groupPath = $groupOptionsDataEl.data('group-path'); - - gl.groupOptions[groupPath] = { - name: $groupOptionsDataEl.data('name'), - issuesPath: $groupOptionsDataEl.data('issues-path'), - mrPath: $groupOptionsDataEl.data('mr-path') - }; - } - - if ($dashboardOptionsDataEl.length) { - gl.dashboardOptions = { - issuesPath: $dashboardOptionsDataEl.data('issues-path'), - mrPath: $dashboardOptionsDataEl.data('mr-path') - }; - } - }); - -}).call(this); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 new file mode 100644 index 00000000000..781e0cd782c --- /dev/null +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -0,0 +1,424 @@ +(global => { + + const KEYCODE = { + ESCAPE: 27, + BACKSPACE: 8, + ENTER: 13, + UP: 38, + DOWN: 40 + }; + + class SearchAutocomplete { + constructor(opts = {}) { + this.onSearchInputBlur = this.onSearchInputBlur.bind(this); + this.onClearInputClick = this.onClearInputClick.bind(this); + this.onSearchInputFocus = this.onSearchInputFocus.bind(this); + this.onSearchInputClick = this.onSearchInputClick.bind(this); + this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); + this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); + this.wrap = opts.wrap || $('.search'); + this.optsEl = opts.optsEl || this.wrap.find('.search-autocomplete-opts'); + this.autocompletePath = opts.autocompletePath || this.optsEl.data('autocomplete-path') + this.projectId = opts.projectId || this.optsEl.data('autocomplete-project-id') || ''; + this.projectRef = opts.projectRef || this.optsEl.data('autocomplete-project-ref') || ''; + this.dropdown = this.wrap.find('.dropdown'); + this.dropdownContent = this.dropdown.find('.dropdown-content'); + this.locationBadgeEl = this.getElement('.location-badge'); + this.scopeInputEl = this.getElement('#scope'); + this.searchInput = this.getElement('.search-input'); + this.projectInputEl = this.getElement('#search_project_id'); + this.groupInputEl = this.getElement('#group_id'); + this.searchCodeInputEl = this.getElement('#search_code'); + this.repositoryInputEl = this.getElement('#repository_ref'); + this.clearInput = this.getElement('.js-clear-input'); + this.saveOriginalState(); + // Only when user is logged in + if (gon.current_user_id) { + this.createAutocomplete(); + } + this.searchInput.addClass('disabled'); + this.saveTextLength(); + this.bindEvents(); + } + + // Finds an element inside wrapper element + getElement(selector) { + return this.wrap.find(selector); + } + + saveOriginalState() { + return this.originalState = this.serializeState(); + } + + saveTextLength() { + return this.lastTextLength = this.searchInput.val().length; + } + + createAutocomplete() { + return this.searchInput.glDropdown({ + filterInputBlur: false, + filterable: true, + filterRemote: true, + highlight: true, + enterCallback: false, + filterInput: 'input#search', + search: { + fields: ['text'] + }, + data: this.getData.bind(this), + selectable: true, + clicked: this.onClick.bind(this) + }); + } + + getData(term, callback) { + var _this, contents, jqXHR; + _this = this; + if (!term) { + if (contents = this.getCategoryContents()) { + this.searchInput.data('glDropdown').filter.options.callback(contents); + this.enableAutocomplete(); + } + return; + } + // Prevent multiple ajax calls + if (this.loadingSuggestions) { + return; + } + this.loadingSuggestions = true; + return jqXHR = $.get(this.autocompletePath, { + project_id: this.projectId, + project_ref: this.projectRef, + term: term + }, function(response) { + var data, firstCategory, i, lastCategory, len, suggestion; + // Hide dropdown menu if no suggestions returns + if (!response.length) { + _this.disableAutocomplete(); + return; + } + data = []; + // List results + firstCategory = true; + for (i = 0, len = response.length; i < len; i++) { + suggestion = response[i]; + // Add group header before list each group + if (lastCategory !== suggestion.category) { + if (!firstCategory) { + data.push('separator'); + } + if (firstCategory) { + firstCategory = false; + } + data.push({ + header: suggestion.category + }); + lastCategory = suggestion.category; + } + data.push({ + id: (suggestion.category.toLowerCase()) + "-" + suggestion.id, + category: suggestion.category, + text: suggestion.label, + url: suggestion.url + }); + } + // Add option to proceed with the search + if (data.length) { + data.push('separator'); + data.push({ + text: "Result name contains \"" + term + "\"", + url: "/search?search=" + term + "&project_id=" + (_this.projectInputEl.val()) + "&group_id=" + (_this.groupInputEl.val()) + }); + } + return callback(data); + }).always(function() { + return _this.loadingSuggestions = false; + }); + } + + getCategoryContents() { + var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils; + userId = gon.current_user_id; + utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions; + if (utils.isInGroupsPage() && groupOptions) { + options = groupOptions[utils.getGroupSlug()]; + } else if (utils.isInProjectPage() && projectOptions) { + options = projectOptions[utils.getProjectSlug()]; + } else if (dashboardOptions) { + options = dashboardOptions; + } + issuesPath = options.issuesPath, mrPath = options.mrPath, name = options.name; + items = [ + { + header: "" + name + }, { + text: 'Issues assigned to me', + url: issuesPath + "/?assignee_id=" + userId + }, { + text: "Issues I've created", + url: issuesPath + "/?author_id=" + userId + }, 'separator', { + text: 'Merge requests assigned to me', + url: mrPath + "/?assignee_id=" + userId + }, { + text: "Merge requests I've created", + url: mrPath + "/?author_id=" + userId + } + ]; + if (!name) { + items.splice(0, 1); + } + return items; + } + + serializeState() { + return { + // Search Criteria + search_project_id: this.projectInputEl.val(), + group_id: this.groupInputEl.val(), + search_code: this.searchCodeInputEl.val(), + repository_ref: this.repositoryInputEl.val(), + scope: this.scopeInputEl.val(), + // Location badge + _location: this.locationBadgeEl.text() + }; + } + + bindEvents() { + this.searchInput.on('keydown', this.onSearchInputKeyDown); + this.searchInput.on('keyup', this.onSearchInputKeyUp); + this.searchInput.on('click', this.onSearchInputClick); + this.searchInput.on('focus', this.onSearchInputFocus); + this.searchInput.on('blur', this.onSearchInputBlur); + this.clearInput.on('click', this.onClearInputClick); + return this.locationBadgeEl.on('click', (function(_this) { + return function() { + return _this.searchInput.focus(); + }; + })(this)); + } + + enableAutocomplete() { + var _this; + // No need to enable anything if user is not logged in + if (!gon.current_user_id) { + return; + } + if (!this.dropdown.hasClass('open')) { + _this = this; + this.loadingSuggestions = false; + this.dropdown.addClass('open').trigger('shown.bs.dropdown'); + return this.searchInput.removeClass('disabled'); + } + }; + + // Saves last length of the entered text + onSearchInputKeyDown() { + return this.saveTextLength(); + } + + onSearchInputKeyUp(e) { + switch (e.keyCode) { + case KEYCODE.BACKSPACE: + // when trying to remove the location badge + if (this.lastTextLength === 0 && this.badgePresent()) { + this.removeLocationBadge(); + } + // When removing the last character and no badge is present + if (this.lastTextLength === 1) { + this.disableAutocomplete(); + } + // When removing any character from existin value + if (this.lastTextLength > 1) { + this.enableAutocomplete(); + } + break; + case KEYCODE.ESCAPE: + this.restoreOriginalState(); + break; + case KEYCODE.ENTER: + this.disableAutocomplete(); + break; + case KEYCODE.UP: + case KEYCODE.DOWN: + return; + default: + // Handle the case when deleting the input value other than backspace + // e.g. Pressing ctrl + backspace or ctrl + x + if (this.searchInput.val() === '') { + this.disableAutocomplete(); + } else { + // We should display the menu only when input is not empty + if (e.keyCode !== KEYCODE.ENTER) { + this.enableAutocomplete(); + } + } + } + this.wrap.toggleClass('has-value', !!e.target.value); + } + + // Avoid falsy value to be returned + onSearchInputClick(e) { + return e.stopImmediatePropagation(); + } + + onSearchInputFocus() { + this.isFocused = true; + this.wrap.addClass('search-active'); + if (this.getValue() === '') { + return this.getData(); + } + } + + getValue() { + return this.searchInput.val(); + } + + onClearInputClick(e) { + e.preventDefault(); + return this.searchInput.val('').focus(); + } + + onSearchInputBlur(e) { + this.isFocused = false; + this.wrap.removeClass('search-active'); + // If input is blank then restore state + if (this.searchInput.val() === '') { + return this.restoreOriginalState(); + } + } + + addLocationBadge(item) { + var badgeText, category, value; + category = item.category != null ? item.category + ": " : ''; + value = item.value != null ? item.value : ''; + badgeText = "" + category + value; + this.locationBadgeEl.text(badgeText).show(); + return this.wrap.addClass('has-location-badge'); + } + + hasLocationBadge() { + return this.wrap.is('.has-location-badge'); + }; + + restoreOriginalState() { + var i, input, inputs, len; + inputs = Object.keys(this.originalState); + for (i = 0, len = inputs.length; i < len; i++) { + input = inputs[i]; + this.getElement("#" + input).val(this.originalState[input]); + } + if (this.originalState._location === '') { + return this.locationBadgeEl.hide(); + } else { + return this.addLocationBadge({ + value: this.originalState._location + }); + } + } + + badgePresent() { + return this.locationBadgeEl.length; + } + + resetSearchState() { + var i, input, inputs, len, results; + inputs = Object.keys(this.originalState); + results = []; + for (i = 0, len = inputs.length; i < len; i++) { + input = inputs[i]; + // _location isnt a input + if (input === '_location') { + break; + } + results.push(this.getElement("#" + input).val('')); + } + return results; + } + + removeLocationBadge() { + this.locationBadgeEl.hide(); + this.resetSearchState(); + this.wrap.removeClass('has-location-badge'); + return this.disableAutocomplete(); + } + + disableAutocomplete() { + if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) { + this.searchInput.addClass('disabled'); + this.dropdown.removeClass('open').trigger('hidden.bs.dropdown'); + this.restoreMenu(); + } + } + + restoreMenu() { + var html; + html = ""; + return this.dropdownContent.html(html); + }; + + onClick(item, $el, e) { + if (location.pathname.indexOf(item.url) !== -1) { + e.preventDefault(); + if (!this.badgePresent) { + if (item.category === 'Projects') { + this.projectInputEl.val(item.id); + this.addLocationBadge({ + value: 'This project' + }); + } + if (item.category === 'Groups') { + this.groupInputEl.val(item.id); + this.addLocationBadge({ + value: 'This group' + }); + } + } + $el.removeClass('is-active'); + this.disableAutocomplete(); + return this.searchInput.val('').focus(); + } + }; + + } + + global.SearchAutocomplete = SearchAutocomplete; + + $(function() { + var $projectOptionsDataEl = $('.js-search-project-options'); + var $groupOptionsDataEl = $('.js-search-group-options'); + var $dashboardOptionsDataEl = $('.js-search-dashboard-options'); + + if ($projectOptionsDataEl.length) { + gl.projectOptions = gl.projectOptions || {}; + + var projectPath = $projectOptionsDataEl.data('project-path'); + + gl.projectOptions[projectPath] = { + name: $projectOptionsDataEl.data('name'), + issuesPath: $projectOptionsDataEl.data('issues-path'), + mrPath: $projectOptionsDataEl.data('mr-path') + }; + } + + if ($groupOptionsDataEl.length) { + gl.groupOptions = gl.groupOptions || {}; + + var groupPath = $groupOptionsDataEl.data('group-path'); + + gl.groupOptions[groupPath] = { + name: $groupOptionsDataEl.data('name'), + issuesPath: $groupOptionsDataEl.data('issues-path'), + mrPath: $groupOptionsDataEl.data('mr-path') + }; + } + + if ($dashboardOptionsDataEl.length) { + gl.dashboardOptions = { + issuesPath: $dashboardOptionsDataEl.data('issues-path'), + mrPath: $dashboardOptionsDataEl.data('mr-path') + }; + } + }); + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 37b3cac42c272ebda3924817e406e5e4ac669811 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 16:17:36 +0200 Subject: Fixup debugger ref left in Dispatcher. --- app/assets/javascripts/dispatcher.js | 1 - app/assets/javascripts/profile/profile.js | 106 -------------------------- app/assets/javascripts/profile/profile.js.es6 | 106 ++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 107 deletions(-) delete mode 100644 app/assets/javascripts/profile/profile.js create mode 100644 app/assets/javascripts/profile/profile.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 71af9a84710..3073f7a3d3a 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -279,7 +279,6 @@ Dispatcher.prototype.initSearch = function() { // Only when search form is present if ($('.search').length) { - debugger; return new gl.SearchAutocomplete(); } }; diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js deleted file mode 100644 index 60f9fba5777..00000000000 --- a/app/assets/javascripts/profile/profile.js +++ /dev/null @@ -1,106 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Profile = (function() { - function Profile(opts) { - var cropOpts, ref; - if (opts == null) { - opts = {}; - } - this.onSubmitForm = bind(this.onSubmitForm, this); - this.form = (ref = opts.form) != null ? ref : $('.edit-user'); - $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() { - return $(this).parents('form').submit(); - // Automatically submit the Preferences form when any of its radio buttons change - }); - $('#user_notification_email').on('change', function() { - return $(this).parents('form').submit(); - // Automatically submit email form when it changes - }); - $('.update-username').on('ajax:before', function() { - $('.loading-username').show(); - $(this).find('.update-success').hide(); - return $(this).find('.update-failed').hide(); - }); - $('.update-username').on('ajax:complete', function() { - $('.loading-username').hide(); - $(this).find('.btn-save').enable(); - return $(this).find('.loading-gif').hide(); - }); - $('.update-notifications').on('ajax:success', function(e, data) { - if (data.saved) { - return new Flash("Notification settings saved", "notice"); - } else { - return new Flash("Failed to save new settings", "alert"); - } - }); - this.bindEvents(); - cropOpts = { - filename: '.js-avatar-filename', - previewImage: '.avatar-image .avatar', - modalCrop: '.modal-profile-crop', - pickImageEl: '.js-choose-user-avatar-button', - uploadImageBtn: '.js-upload-user-avatar', - modalCropImg: '.modal-profile-crop-image' - }; - this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop'); - } - - Profile.prototype.bindEvents = function() { - return this.form.on('submit', this.onSubmitForm); - }; - - Profile.prototype.onSubmitForm = function(e) { - e.preventDefault(); - return this.saveForm(); - }; - - Profile.prototype.saveForm = function() { - var avatarBlob, formData, self; - self = this; - formData = new FormData(this.form[0]); - avatarBlob = this.avatarGlCrop.getBlob(); - if (avatarBlob != null) { - formData.append('user[avatar]', avatarBlob, 'avatar.png'); - } - return $.ajax({ - url: this.form.attr('action'), - type: this.form.attr('method'), - data: formData, - dataType: "json", - processData: false, - contentType: false, - success: function(response) { - return new Flash(response.message, 'notice'); - }, - error: function(jqXHR) { - return new Flash(jqXHR.responseJSON.message, 'alert'); - }, - complete: function() { - window.scrollTo(0, 0); - // Enable submit button after requests ends - return self.form.find(':input[disabled]').enable(); - } - }); - }; - - return Profile; - - })(); - - $(function() { - $(document).on('focusout.ssh_key', '#key_key', function() { - var $title, comment; - $title = $('#key_title'); - comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/); - if (comment && comment.length > 1 && $title.val() === '') { - return $title.val(comment[1]).change(); - } - // Extract the SSH Key title from its comment - }); - if (gl.utils.getPagePath() === 'profiles') { - return new Profile(); - } - }); - -}).call(this); diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 new file mode 100644 index 00000000000..60f9fba5777 --- /dev/null +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -0,0 +1,106 @@ +(function() { + var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + this.Profile = (function() { + function Profile(opts) { + var cropOpts, ref; + if (opts == null) { + opts = {}; + } + this.onSubmitForm = bind(this.onSubmitForm, this); + this.form = (ref = opts.form) != null ? ref : $('.edit-user'); + $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() { + return $(this).parents('form').submit(); + // Automatically submit the Preferences form when any of its radio buttons change + }); + $('#user_notification_email').on('change', function() { + return $(this).parents('form').submit(); + // Automatically submit email form when it changes + }); + $('.update-username').on('ajax:before', function() { + $('.loading-username').show(); + $(this).find('.update-success').hide(); + return $(this).find('.update-failed').hide(); + }); + $('.update-username').on('ajax:complete', function() { + $('.loading-username').hide(); + $(this).find('.btn-save').enable(); + return $(this).find('.loading-gif').hide(); + }); + $('.update-notifications').on('ajax:success', function(e, data) { + if (data.saved) { + return new Flash("Notification settings saved", "notice"); + } else { + return new Flash("Failed to save new settings", "alert"); + } + }); + this.bindEvents(); + cropOpts = { + filename: '.js-avatar-filename', + previewImage: '.avatar-image .avatar', + modalCrop: '.modal-profile-crop', + pickImageEl: '.js-choose-user-avatar-button', + uploadImageBtn: '.js-upload-user-avatar', + modalCropImg: '.modal-profile-crop-image' + }; + this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop'); + } + + Profile.prototype.bindEvents = function() { + return this.form.on('submit', this.onSubmitForm); + }; + + Profile.prototype.onSubmitForm = function(e) { + e.preventDefault(); + return this.saveForm(); + }; + + Profile.prototype.saveForm = function() { + var avatarBlob, formData, self; + self = this; + formData = new FormData(this.form[0]); + avatarBlob = this.avatarGlCrop.getBlob(); + if (avatarBlob != null) { + formData.append('user[avatar]', avatarBlob, 'avatar.png'); + } + return $.ajax({ + url: this.form.attr('action'), + type: this.form.attr('method'), + data: formData, + dataType: "json", + processData: false, + contentType: false, + success: function(response) { + return new Flash(response.message, 'notice'); + }, + error: function(jqXHR) { + return new Flash(jqXHR.responseJSON.message, 'alert'); + }, + complete: function() { + window.scrollTo(0, 0); + // Enable submit button after requests ends + return self.form.find(':input[disabled]').enable(); + } + }); + }; + + return Profile; + + })(); + + $(function() { + $(document).on('focusout.ssh_key', '#key_key', function() { + var $title, comment; + $title = $('#key_title'); + comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/); + if (comment && comment.length > 1 && $title.val() === '') { + return $title.val(comment[1]).change(); + } + // Extract the SSH Key title from its comment + }); + if (gl.utils.getPagePath() === 'profiles') { + return new Profile(); + } + }); + +}).call(this); -- cgit v1.2.1 From 4ae2a3026914a70eb34929a768570043a4e73022 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 17:18:35 +0200 Subject: Refactor Profile to use ES6. --- app/assets/javascripts/profile/profile.js.es6 | 117 +++++++++++++------------- 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index 60f9fba5777..c7498c68e7c 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,41 +1,14 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Profile = (function() { - function Profile(opts) { - var cropOpts, ref; - if (opts == null) { - opts = {}; - } - this.onSubmitForm = bind(this.onSubmitForm, this); - this.form = (ref = opts.form) != null ? ref : $('.edit-user'); - $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() { - return $(this).parents('form').submit(); - // Automatically submit the Preferences form when any of its radio buttons change - }); - $('#user_notification_email').on('change', function() { - return $(this).parents('form').submit(); - // Automatically submit email form when it changes - }); - $('.update-username').on('ajax:before', function() { - $('.loading-username').show(); - $(this).find('.update-success').hide(); - return $(this).find('.update-failed').hide(); - }); - $('.update-username').on('ajax:complete', function() { - $('.loading-username').hide(); - $(this).find('.btn-save').enable(); - return $(this).find('.loading-gif').hide(); - }); - $('.update-notifications').on('ajax:success', function(e, data) { - if (data.saved) { - return new Flash("Notification settings saved", "notice"); - } else { - return new Flash("Failed to save new settings", "alert"); - } - }); +(global => { + class Profile { + constructor(opts = {}) { + this.onSubmitForm = this.onSubmitForm.bind(this); + this.form = opts.form || $('.edit-user'); this.bindEvents(); - cropOpts = { + this.initAvatarGlCrop(); + } + + initAvatarGlCrop() { + const cropOpts = { filename: '.js-avatar-filename', previewImage: '.avatar-image .avatar', modalCrop: '.modal-profile-crop', @@ -46,23 +19,51 @@ this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop'); } - Profile.prototype.bindEvents = function() { - return this.form.on('submit', this.onSubmitForm); - }; + bindEvents() { + $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); + $('#user_notification_email').on('change', this.submitForm); + $('.update-username').on('ajax:before', this.beforeUpdateUsername); + $('.update-username').on('ajax:complete', this.afterUpdateUsername); + $('.update-notifications').on('ajax:success', this.onUpdateNotifs); + this.form.on('submit', this.onSubmitForm); + } + + submitForm() { + return $(this).parents('form').submit(); + } - Profile.prototype.onSubmitForm = function(e) { + onSubmitForm(e) { e.preventDefault(); return this.saveForm(); - }; + } + + beforeUpdateUsername() { + $('.loading-username').show(); + $(this).find('.update-success').hide(); + return $(this).find('.update-failed').hide(); + } + + afterUpdateUsername() { + $('.loading-username').hide(); + $(this).find('.btn-save').enable(); + return $(this).find('.loading-gif').hide(); + } + + onUpdateNotifs(e, data) { + return data.saved ? + new Flash("Notification settings saved", "notice") : + new Flash("Failed to save new settings", "alert"); + } + + saveForm() { + const self = this; + const formData = new FormData(this.form[0]); + const avatarBlob = this.avatarGlCrop.getBlob(); - Profile.prototype.saveForm = function() { - var avatarBlob, formData, self; - self = this; - formData = new FormData(this.form[0]); - avatarBlob = this.avatarGlCrop.getBlob(); if (avatarBlob != null) { formData.append('user[avatar]', avatarBlob, 'avatar.png'); } + return $.ajax({ url: this.form.attr('action'), type: this.form.attr('method'), @@ -70,37 +71,33 @@ dataType: "json", processData: false, contentType: false, - success: function(response) { + success: (response) => { return new Flash(response.message, 'notice'); }, - error: function(jqXHR) { + error: (jqXHR) => { return new Flash(jqXHR.responseJSON.message, 'alert'); }, - complete: function() { + complete: () => { window.scrollTo(0, 0); // Enable submit button after requests ends return self.form.find(':input[disabled]').enable(); } }); - }; - - return Profile; - - })(); + } + } $(function() { $(document).on('focusout.ssh_key', '#key_key', function() { - var $title, comment; - $title = $('#key_title'); - comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/); + const $title = $('#key_title'); + const comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/); if (comment && comment.length > 1 && $title.val() === '') { return $title.val(comment[1]).change(); } // Extract the SSH Key title from its comment }); - if (gl.utils.getPagePath() === 'profiles') { + if (global.utils.getPagePath() === 'profiles') { return new Profile(); } }); -}).call(this); +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From b690c19dbf9a74e356b75e1da63f7dcf237a8c81 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 8 Sep 2016 17:22:14 +0200 Subject: Use parentheses in IFFE's as per AirBnb styleguide. --- app/assets/javascripts/profile/profile.js.es6 | 3 ++- app/assets/javascripts/search_autocomplete.js.es6 | 2 +- app/assets/javascripts/todos.js.es6 | 2 +- app/assets/javascripts/user.js.es6 | 2 +- app/assets/javascripts/user_tabs.js.es6 | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index c7498c68e7c..e62e0a89867 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,4 +1,5 @@ -(global => { +((global) => { + class Profile { constructor(opts = {}) { this.onSubmitForm = this.onSubmitForm.bind(this); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index 781e0cd782c..d1e8c79336a 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -1,4 +1,4 @@ -(global => { +((global) => { const KEYCODE = { ESCAPE: 27, diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index a0386dcc018..fd85b7506ce 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,4 +1,4 @@ -(global => { +((global) => { class Todos { constructor(opts = {}) { diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index c5893745d74..6930d14094c 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -1,4 +1,4 @@ -(global => { +((global) => { global.User = class { constructor(opts) { this.opts = opts; diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index b787700070e..4a69a79118e 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -57,7 +57,7 @@ content on the Users#show page. */ -(global => { +((global) => { class UserTabs { constructor (opts) { this.loaded = {}; -- cgit v1.2.1 From 13182a9c5c97b9e104e9efcda203d8b566b72f28 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 16:57:13 +0200 Subject: Make use of destructuring options, clean up based on feedback. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 12 ++++---- app/assets/javascripts/profile/profile.js.es6 | 12 +++----- app/assets/javascripts/search_autocomplete.js.es6 | 35 ++++++++++++++--------- app/assets/javascripts/todos.js.es6 | 10 +++---- app/assets/javascripts/user.js.es6 | 6 ++-- app/assets/javascripts/user_tabs.js.es6 | 26 +++++++++-------- 6 files changed, 54 insertions(+), 47 deletions(-) diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index 5ae6f1a5940..46496153d7c 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -8,15 +8,15 @@ requestFile(query) { return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); - }; + } }; global.BlobCiYamlSelector = BlobCiYamlSelector; class BlobCiYamlSelectors { - constructor(opts) { - this.$dropdowns = opts.$dropdowns || $('.js-gitlab-ci-yml-selector'); - this.editor = opts.editor; + constructor({ editor, $dropdowns = $('.js-gitlab-ci-yml-selector') }) { + this.editor = editor; + this.$dropdowns = $dropdowns; this.initSelectors(); } @@ -24,11 +24,11 @@ this.$dropdowns.each((i, dropdown) => { const $dropdown = $(dropdown); return new BlobCiYamlSelector({ + editor, pattern: /(.gitlab-ci.yml)/, data: $dropdown.data('data'), wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), - dropdown: $dropdown, - editor: this.editor + dropdown: $dropdown }); }); } diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index e62e0a89867..5b1a5920c95 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,9 +1,9 @@ ((global) => { class Profile { - constructor(opts = {}) { + constructor({ form = $('.edit-user') }) { this.onSubmitForm = this.onSubmitForm.bind(this); - this.form = opts.form || $('.edit-user'); + this.form = form; this.bindEvents(); this.initAvatarGlCrop(); } @@ -72,12 +72,8 @@ dataType: "json", processData: false, contentType: false, - success: (response) => { - return new Flash(response.message, 'notice'); - }, - error: (jqXHR) => { - return new Flash(jqXHR.responseJSON.message, 'alert'); - }, + success: response => new Flash(response.message, 'notice'), + error: jqXHR => new Flash(jqXHR.responseJSON.message, 'alert'), complete: () => { window.scrollTo(0, 0); // Enable submit button after requests ends diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index d1e8c79336a..abd0748f6a3 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -9,19 +9,20 @@ }; class SearchAutocomplete { - constructor(opts = {}) { - this.onSearchInputBlur = this.onSearchInputBlur.bind(this); - this.onClearInputClick = this.onClearInputClick.bind(this); - this.onSearchInputFocus = this.onSearchInputFocus.bind(this); - this.onSearchInputClick = this.onSearchInputClick.bind(this); - this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); - this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); - this.wrap = opts.wrap || $('.search'); - this.optsEl = opts.optsEl || this.wrap.find('.search-autocomplete-opts'); - this.autocompletePath = opts.autocompletePath || this.optsEl.data('autocomplete-path') - this.projectId = opts.projectId || this.optsEl.data('autocomplete-project-id') || ''; - this.projectRef = opts.projectRef || this.optsEl.data('autocomplete-project-ref') || ''; - this.dropdown = this.wrap.find('.dropdown'); + constructor({ + wrap = $('.search'), + optsEl = wrap.find('.search-autocomplete-opts'), + autocompletePath = optsEl.data('autocomplete-path'), + projectId = (optsEl.data('autocomplete-project-id') || ''), + projectRef = (optsEl.data('autocomplete-project-ref') || '') + }) { + this.bindEventContext(); + this.wrap = wrap; + this.optsEl = optsEl; + this.autocompletePath = autocompletePath; + this.projectId = projectId; + this.projectRef = projectRef; + this.dropdown = wrap.find('.dropdown'); this.dropdownContent = this.dropdown.find('.dropdown-content'); this.locationBadgeEl = this.getElement('.location-badge'); this.scopeInputEl = this.getElement('#scope'); @@ -42,6 +43,14 @@ } // Finds an element inside wrapper element + bindEventContext() { + this.onSearchInputBlur = this.onSearchInputBlur.bind(this); + this.onClearInputClick = this.onClearInputClick.bind(this); + this.onSearchInputFocus = this.onSearchInputFocus.bind(this); + this.onSearchInputClick = this.onSearchInputClick.bind(this); + this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this); + this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this); + } getElement(selector) { return this.wrap.find(selector); } diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index fd85b7506ce..d8dca490e3e 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,11 +1,11 @@ ((global) => { class Todos { - constructor(opts = {}) { + constructor({ el = $('.js-todos-options') }) { this.allDoneClicked = this.allDoneClicked.bind(this); this.doneClicked = this.doneClicked.bind(this); - this.el = opts.el || $('.js-todos-options'); - this.perPage = this.el.data('perPage'); + this.el = el; + this.perPage = el.data('perPage'); this.clearListeners(); this.initBtnListeners(); this.initFilters(); @@ -60,7 +60,7 @@ data: { '_method': 'delete' }, - success: data => { + success: (data) => { this.redirectIfNeeded(data.count); this.clearDone($target.closest('li')); return this.updateBadges(data); @@ -80,7 +80,7 @@ data: { '_method': 'delete' }, - success: data => { + success: (data) => { $target.remove(); $('.prepend-top-default').html('
    You\'re all done!
    '); return this.updateBadges(data); diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6 index 6930d14094c..0f97924d94e 100644 --- a/app/assets/javascripts/user.js.es6 +++ b/app/assets/javascripts/user.js.es6 @@ -1,7 +1,7 @@ ((global) => { global.User = class { - constructor(opts) { - this.opts = opts; + constructor({ action }) { + this.action = action; this.placeProfileAvatarsToTop(); this.initTabs(); this.hideProjectLimitMessage(); @@ -16,7 +16,7 @@ initTabs() { return new global.UserTabs({ parentEl: '.user-profile', - action: this.opts.action + action: this.action }); } diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index 4a69a79118e..1ce0b31c01f 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -59,11 +59,11 @@ content on the Users#show page. */ ((global) => { class UserTabs { - constructor (opts) { + constructor ({ defaultAction = 'activity', action = defaultAction, parentEl }) { this.loaded = {}; - this.defaultAction = opts.defaultAction || 'activity'; - this.action = opts.action || 'activity'; - this.$parentEl = $(opts.parentEl) || $(document); + this.defaultAction = defaultAction; + this.action = action; + this.$parentEl = $(parentEl) || $(document); this._location = window.location; this.$parentEl.find('.nav-links a') .each((i, navLink) => { @@ -81,7 +81,7 @@ content on the Users#show page. bindEvents() { return this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]') - .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', (event) => this.tabShown(event)); + .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event)); } tabShown(event) { @@ -93,7 +93,7 @@ content on the Users#show page. } activateTab(action) { - return this.$parentEl.find(".nav-links .js-" + action + "-tab a") + return this.$parentEl.find(`.nav-links .js-${action}-tab a`) .tab('show'); } @@ -104,7 +104,9 @@ content on the Users#show page. if (action === 'activity') { this.loadActivities(source); } - if (action === 'groups' || action === 'contributed' || action === 'projects' || action === 'snippets') { + + const loadableActions = [ 'groups', 'contributed', 'projects', 'snippets' ]; + if (loadableActions.indexOf(action) > -1) { return this.loadTab(source, action); } } @@ -115,9 +117,9 @@ content on the Users#show page. complete: () => this.toggleLoading(false), dataType: 'json', type: 'GET', - url: source + ".json", + url: `${source}.json`, success: (data) => { - const tabSelector = 'div#' + action; + const tabSelector = `div#${action}`; this.$parentEl.find(tabSelector).html(data.html); this.loaded[action] = true; return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); @@ -141,12 +143,12 @@ content on the Users#show page. } setCurrentAction(action) { - const regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$'); + const regExp = new RegExp(`\/(${this.actions.join('|')})(\.html)?\/?$`); let new_state = this._location.pathname; - new_state = new_state.replace(/\/+$/, ""); + new_state = new_state.replace(/\/+$/, ''); new_state = new_state.replace(regExp, ''); if (action !== this.defaultAction) { - new_state += "/" + action; + new_state += `/${action}`; } new_state += this._location.search + this._location.hash; history.replaceState({ -- cgit v1.2.1 From 5bc3b7be601a234dfbf16a32b2e58b95d3aa677b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 17:49:13 +0200 Subject: Refactor GitLabCrop to ES6. --- app/assets/javascripts/profile/gl_crop.js | 177 -------------------------- app/assets/javascripts/profile/gl_crop.js.es6 | 172 +++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 177 deletions(-) delete mode 100644 app/assets/javascripts/profile/gl_crop.js create mode 100644 app/assets/javascripts/profile/gl_crop.js.es6 diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js deleted file mode 100644 index 30cd6f6e470..00000000000 --- a/app/assets/javascripts/profile/gl_crop.js +++ /dev/null @@ -1,177 +0,0 @@ -(function() { - var GitLabCrop, - bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - GitLabCrop = (function() { - var FILENAMEREGEX; - - // Matches everything but the file name - FILENAMEREGEX = /^.*[\\\/]/; - - function GitLabCrop(input, opts) { - var ref, ref1, ref2, ref3, ref4; - if (opts == null) { - opts = {}; - } - this.onUploadImageBtnClick = bind(this.onUploadImageBtnClick, this); - this.onModalHide = bind(this.onModalHide, this); - this.onModalShow = bind(this.onModalShow, this); - this.onPickImageClick = bind(this.onPickImageClick, this); - this.fileInput = $(input); - // We should rename to avoid spec to fail - // Form will submit the proper input filed with a file using FormData - this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger"); - // Set defaults - this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickImageEl, this.uploadImageBtn = opts.uploadImageBtn, this.modalCropImg = opts.modalCropImg; - // Required params - // Ensure needed elements are jquery objects - // If selector is provided we will convert them to a jQuery Object - this.filename = this.getElement(this.filename); - this.previewImage = this.getElement(this.previewImage); - this.pickImageEl = this.getElement(this.pickImageEl); - // Modal elements usually are outside the @form element - this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop; - this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn; - this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg; - this.cropActionsBtn = this.modalCrop.find('[data-method]'); - this.bindEvents(); - } - - GitLabCrop.prototype.getElement = function(selector) { - return $(selector, this.form); - }; - - GitLabCrop.prototype.bindEvents = function() { - var _this; - _this = this; - this.fileInput.on('change', function(e) { - return _this.onFileInputChange(e, this); - }); - this.pickImageEl.on('click', this.onPickImageClick); - this.modalCrop.on('shown.bs.modal', this.onModalShow); - this.modalCrop.on('hidden.bs.modal', this.onModalHide); - this.uploadImageBtn.on('click', this.onUploadImageBtnClick); - this.cropActionsBtn.on('click', function(e) { - var btn; - btn = this; - return _this.onActionBtnClick(btn); - }); - return this.croppedImageBlob = null; - }; - - GitLabCrop.prototype.onPickImageClick = function() { - return this.fileInput.trigger('click'); - }; - - GitLabCrop.prototype.onModalShow = function() { - var _this; - _this = this; - return this.modalCropImg.cropper({ - viewMode: 1, - center: false, - aspectRatio: 1, - modal: true, - scalable: false, - rotatable: false, - zoomable: true, - dragMode: 'move', - guides: false, - zoomOnTouch: false, - zoomOnWheel: false, - cropBoxMovable: false, - cropBoxResizable: false, - toggleDragModeOnDblclick: false, - built: function() { - var $image, container, cropBoxHeight, cropBoxWidth; - $image = $(this); - container = $image.cropper('getContainerData'); - cropBoxWidth = _this.cropBoxWidth; - cropBoxHeight = _this.cropBoxHeight; - return $image.cropper('setCropBoxData', { - width: cropBoxWidth, - height: cropBoxHeight, - left: (container.width - cropBoxWidth) / 2, - top: (container.height - cropBoxHeight) / 2 - }); - } - }); - }; - - GitLabCrop.prototype.onModalHide = function() { - return this.modalCropImg.attr('src', '').cropper('destroy'); - }; - - GitLabCrop.prototype.onUploadImageBtnClick = function(e) { // Remove attached image - e.preventDefault(); // Destroy cropper instance - this.setBlob(); - this.setPreview(); - this.modalCrop.modal('hide'); - return this.fileInput.val(''); - }; - - GitLabCrop.prototype.onActionBtnClick = function(btn) { - var data, result; - data = $(btn).data(); - if (this.modalCropImg.data('cropper') && data.method) { - return result = this.modalCropImg.cropper(data.method, data.option); - } - }; - - GitLabCrop.prototype.onFileInputChange = function(e, input) { - return this.readFile(input); - }; - - GitLabCrop.prototype.readFile = function(input) { - var _this, reader; - _this = this; - reader = new FileReader; - reader.onload = function() { - _this.modalCropImg.attr('src', reader.result); - return _this.modalCrop.modal('show'); - }; - return reader.readAsDataURL(input.files[0]); - }; - - GitLabCrop.prototype.dataURLtoBlob = function(dataURL) { - var array, binary, i, k, len, v; - binary = atob(dataURL.split(',')[1]); - array = []; - for (k = i = 0, len = binary.length; i < len; k = ++i) { - v = binary[k]; - array.push(binary.charCodeAt(k)); - } - return new Blob([new Uint8Array(array)], { - type: 'image/png' - }); - }; - - GitLabCrop.prototype.setPreview = function() { - var filename; - this.previewImage.attr('src', this.dataURL); - filename = this.fileInput.val().replace(FILENAMEREGEX, ''); - return this.filename.text(filename); - }; - - GitLabCrop.prototype.setBlob = function() { - this.dataURL = this.modalCropImg.cropper('getCroppedCanvas', { - width: 200, - height: 200 - }).toDataURL('image/png'); - return this.croppedImageBlob = this.dataURLtoBlob(this.dataURL); - }; - - GitLabCrop.prototype.getBlob = function() { - return this.croppedImageBlob; - }; - - return GitLabCrop; - - })(); - - $.fn.glCrop = function(opts) { - return this.each(function() { - return $(this).data('glcrop', new GitLabCrop(this, opts)); - }); - }; - -}).call(this); diff --git a/app/assets/javascripts/profile/gl_crop.js.es6 b/app/assets/javascripts/profile/gl_crop.js.es6 new file mode 100644 index 00000000000..a1b0126e857 --- /dev/null +++ b/app/assets/javascripts/profile/gl_crop.js.es6 @@ -0,0 +1,172 @@ +((global) => { + + // Matches everything but the file name + const FILENAMEREGEX = /^.*[\\\/]/; + + class GitLabCrop { + constructor(input, { filename, previewImage, modalCrop, pickImageEl, uploadImageBtn, modalCropImg, + exportWidth = 200, exportHeight = 200, cropBoxWidth = 200, cropBoxHeight = 200 } = {}) { + + this.onUploadImageBtnClick = this.onUploadImageBtnClick.bind(this); + this.onModalHide = this.onModalHide.bind(this); + this.onModalShow = this.onModalShow.bind(this); + this.onPickImageClick = this.onPickImageClick.bind(this); + this.fileInput = $(input); + this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg; + this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `this.fileInput.attr('id')-trigger`); + this.exportWidth = exportWidth; + this.exportHeight = exportHeight; + this.cropBoxWidth = cropBoxWidth; + this.cropBoxHeight = cropBoxHeight; + this.form = this.fileInput.parents('form'); + this.filename = filename; + this.previewImage = previewImage; + this.modalCrop = modalCrop; + this.pickImageEl = pickImageEl; + this.uploadImageBtn = uploadImageBtn; + this.modalCropImg = modalCropImg; + this.filename = this.getElement(filename); + this.previewImage = this.getElement(previewImage); + this.pickImageEl = this.getElement(pickImageEl); + this.modalCrop = _.isString(modalCrop) ? $(modalCrop) : modalCrop; + this.uploadImageBtn = _.isString(uploadImageBtn) ? $(uploadImageBtn) : uploadImageBtn; + this.modalCropImg = _.isString(modalCropImg) ? $(modalCropImg) : modalCropImg; + this.cropActionsBtn = this.modalCrop.find('[data-method]'); + this.bindEvents(); + } + + getElement(selector) { + return $(selector, this.form); + } + + bindEvents() { + var _this; + _this = this; + this.fileInput.on('change', function(e) { + return _this.onFileInputChange(e, this); + }); + this.pickImageEl.on('click', this.onPickImageClick); + this.modalCrop.on('shown.bs.modal', this.onModalShow); + this.modalCrop.on('hidden.bs.modal', this.onModalHide); + this.uploadImageBtn.on('click', this.onUploadImageBtnClick); + this.cropActionsBtn.on('click', function(e) { + var btn; + btn = this; + return _this.onActionBtnClick(btn); + }); + return this.croppedImageBlob = null; + } + + onPickImageClick() { + return this.fileInput.trigger('click'); + } + + onModalShow() { + var _this; + _this = this; + return this.modalCropImg.cropper({ + viewMode: 1, + center: false, + aspectRatio: 1, + modal: true, + scalable: false, + rotatable: false, + zoomable: true, + dragMode: 'move', + guides: false, + zoomOnTouch: false, + zoomOnWheel: false, + cropBoxMovable: false, + cropBoxResizable: false, + toggleDragModeOnDblclick: false, + built: function() { + var $image, container, cropBoxHeight, cropBoxWidth; + $image = $(this); + container = $image.cropper('getContainerData'); + cropBoxWidth = _this.cropBoxWidth; + cropBoxHeight = _this.cropBoxHeight; + return $image.cropper('setCropBoxData', { + width: cropBoxWidth, + height: cropBoxHeight, + left: (container.width - cropBoxWidth) / 2, + top: (container.height - cropBoxHeight) / 2 + }); + } + }); + } + + onModalHide() { + return this.modalCropImg.attr('src', '').cropper('destroy'); + } + + onUploadImageBtnClick(e) { + e.preventDefault(); + this.setBlob(); + this.setPreview(); + this.modalCrop.modal('hide'); + return this.fileInput.val(''); + } + + onActionBtnClick(btn) { + var data, result; + data = $(btn).data(); + if (this.modalCropImg.data('cropper') && data.method) { + return result = this.modalCropImg.cropper(data.method, data.option); + } + } + + onFileInputChange(e, input) { + return this.readFile(input); + } + + readFile(input) { + var _this, reader; + _this = this; + reader = new FileReader; + reader.onload = () => { + _this.modalCropImg.attr('src', reader.result); + return _this.modalCrop.modal('show'); + }; + return reader.readAsDataURL(input.files[0]); + } + + dataURLtoBlob(dataURL) { + var array, binary, i, k, len, v; + binary = atob(dataURL.split(',')[1]); + array = []; + for (k = i = 0, len = binary.length; i < len; k = ++i) { + v = binary[k]; + array.push(binary.charCodeAt(k)); + } + return new Blob([new Uint8Array(array)], { + type: 'image/png' + }); + } + + setPreview() { + var filename; + this.previewImage.attr('src', this.dataURL); + filename = this.fileInput.val().replace(FILENAMEREGEX, ''); + return this.filename.text(filename); + } + + setBlob() { + this.dataURL = this.modalCropImg.cropper('getCroppedCanvas', { + width: 200, + height: 200 + }).toDataURL('image/png'); + return this.croppedImageBlob = this.dataURLtoBlob(this.dataURL); + } + + getBlob() { + return this.croppedImageBlob; + } + } + + $.fn.glCrop = function(opts) { + return this.each(function() { + return $(this).data('glcrop', new GitLabCrop(this, opts)); + }); + } + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 7beb9f3483cf59a9717c2ccc6bd102cd9a97f906 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 17:49:48 +0200 Subject: Pass default opts. --- app/assets/javascripts/profile/profile.js.es6 | 2 +- app/assets/javascripts/search_autocomplete.js.es6 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index 5b1a5920c95..5f674c36de5 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,7 +1,7 @@ ((global) => { class Profile { - constructor({ form = $('.edit-user') }) { + constructor({ form = $('.edit-user') } = {}) { this.onSubmitForm = this.onSubmitForm.bind(this); this.form = form; this.bindEvents(); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index abd0748f6a3..3d710d17ef3 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -15,7 +15,8 @@ autocompletePath = optsEl.data('autocomplete-path'), projectId = (optsEl.data('autocomplete-project-id') || ''), projectRef = (optsEl.data('autocomplete-project-ref') || '') - }) { + } = {}) { + this.bindEventContext(); this.wrap = wrap; this.optsEl = optsEl; -- cgit v1.2.1 From b3917d4868120c5a62e4e5525bd3321cb152e2fd Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 18:31:26 +0200 Subject: Set defaults in constructor, in case opts are undefined. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 4 +- app/assets/javascripts/issues-bulk-assignment.js | 167 --------------------- .../javascripts/issues-bulk-assignment.js.es6 | 167 +++++++++++++++++++++ app/assets/javascripts/profile/profile.js.es6 | 4 +- app/assets/javascripts/search_autocomplete.js.es6 | 19 +-- app/assets/javascripts/todos.js.es6 | 4 +- app/assets/javascripts/user_tabs.js.es6 | 6 +- 7 files changed, 182 insertions(+), 189 deletions(-) delete mode 100644 app/assets/javascripts/issues-bulk-assignment.js create mode 100644 app/assets/javascripts/issues-bulk-assignment.js.es6 diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index 46496153d7c..bb9d0444a8c 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -14,9 +14,9 @@ global.BlobCiYamlSelector = BlobCiYamlSelector; class BlobCiYamlSelectors { - constructor({ editor, $dropdowns = $('.js-gitlab-ci-yml-selector') }) { + constructor({ editor, $dropdowns }) { this.editor = editor; - this.$dropdowns = $dropdowns; + this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector'); this.initSelectors(); } diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js deleted file mode 100644 index 62a7fc9a06c..00000000000 --- a/app/assets/javascripts/issues-bulk-assignment.js +++ /dev/null @@ -1,167 +0,0 @@ -(function() { - this.IssuableBulkActions = (function() { - function IssuableBulkActions(opts) { - // Set defaults - var ref, ref1, ref2; - if (opts == null) { - opts = {}; - } - this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li'); - // Save instance - this.form.data('bulkActions', this); - this.willUpdateLabels = false; - this.bindEvents(); - // Fixes bulk-assign not working when navigating through pages - Issuable.initChecks(); - } - - IssuableBulkActions.prototype.getElement = function(selector) { - return this.container.find(selector); - }; - - IssuableBulkActions.prototype.bindEvents = function() { - return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); - }; - - IssuableBulkActions.prototype.onFormSubmit = function(e) { - e.preventDefault(); - return this.submit(); - }; - - IssuableBulkActions.prototype.submit = function() { - var _this, xhr; - _this = this; - xhr = $.ajax({ - url: this.form.attr('action'), - method: this.form.attr('method'), - dataType: 'JSON', - data: this.getFormDataAsObject() - }); - xhr.done(function(response, status, xhr) { - return location.reload(); - }); - xhr.fail(function() { - return new Flash("Issue update failed"); - }); - return xhr.always(this.onFormSubmitAlways.bind(this)); - }; - - IssuableBulkActions.prototype.onFormSubmitAlways = function() { - return this.form.find('[type="submit"]').enable(); - }; - - IssuableBulkActions.prototype.getSelectedIssues = function() { - return this.issues.has('.selected_issue:checked'); - }; - - IssuableBulkActions.prototype.getLabelsFromSelection = function() { - var labels; - labels = []; - this.getSelectedIssues().map(function() { - var _labels; - _labels = $(this).data('labels'); - if (_labels) { - return _labels.map(function(labelId) { - if (labels.indexOf(labelId) === -1) { - return labels.push(labelId); - } - }); - } - }); - return labels; - }; - - - /** - * Will return only labels that were marked previously and the user has unmarked - * @return {Array} Label IDs - */ - - IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() { - var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result; - result = []; - labelsToKeep = []; - ref = this.getElement('.labels-filter .is-indeterminate'); - for (i = 0, len = ref.length; i < len; i++) { - el = ref[i]; - labelsToKeep.push($(el).data('labelId')); - } - ref1 = this.getLabelsFromSelection(); - for (j = 0, len1 = ref1.length; j < len1; j++) { - id = ref1[j]; - // Only the ones that we are not going to keep - if (labelsToKeep.indexOf(id) === -1) { - result.push(id); - } - } - return result; - }; - - - /** - * Simple form serialization, it will return just what we need - * Returns key/value pairs from form data - */ - - IssuableBulkActions.prototype.getFormDataAsObject = function() { - var formData; - formData = { - update: { - state_event: this.form.find('input[name="update[state_event]"]').val(), - assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), - milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), - issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), - subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), - add_label_ids: [], - remove_label_ids: [] - } - }; - if (this.willUpdateLabels) { - this.getLabelsToApply().map(function(id) { - return formData.update.add_label_ids.push(id); - }); - this.getLabelsToRemove().map(function(id) { - return formData.update.remove_label_ids.push(id); - }); - } - return formData; - }; - - IssuableBulkActions.prototype.getLabelsToApply = function() { - var $labels, labelIds; - labelIds = []; - $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); - $labels.each(function(k, label) { - if (label) { - return labelIds.push(parseInt($(label).val())); - } - }); - return labelIds; - }; - - - /** - * Returns Label IDs that will be removed from issue selection - * @return {Array} Array of labels IDs - */ - - IssuableBulkActions.prototype.getLabelsToRemove = function() { - var indeterminatedLabels, labelsToApply, result; - result = []; - indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); - labelsToApply = this.getLabelsToApply(); - indeterminatedLabels.map(function(id) { - // We need to exclude label IDs that will be applied - // By not doing this will cause issues from selection to not add labels at all - if (labelsToApply.indexOf(id) === -1) { - return result.push(id); - } - }); - return result; - }; - - return IssuableBulkActions; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues-bulk-assignment.js.es6 new file mode 100644 index 00000000000..62a7fc9a06c --- /dev/null +++ b/app/assets/javascripts/issues-bulk-assignment.js.es6 @@ -0,0 +1,167 @@ +(function() { + this.IssuableBulkActions = (function() { + function IssuableBulkActions(opts) { + // Set defaults + var ref, ref1, ref2; + if (opts == null) { + opts = {}; + } + this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li'); + // Save instance + this.form.data('bulkActions', this); + this.willUpdateLabels = false; + this.bindEvents(); + // Fixes bulk-assign not working when navigating through pages + Issuable.initChecks(); + } + + IssuableBulkActions.prototype.getElement = function(selector) { + return this.container.find(selector); + }; + + IssuableBulkActions.prototype.bindEvents = function() { + return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); + }; + + IssuableBulkActions.prototype.onFormSubmit = function(e) { + e.preventDefault(); + return this.submit(); + }; + + IssuableBulkActions.prototype.submit = function() { + var _this, xhr; + _this = this; + xhr = $.ajax({ + url: this.form.attr('action'), + method: this.form.attr('method'), + dataType: 'JSON', + data: this.getFormDataAsObject() + }); + xhr.done(function(response, status, xhr) { + return location.reload(); + }); + xhr.fail(function() { + return new Flash("Issue update failed"); + }); + return xhr.always(this.onFormSubmitAlways.bind(this)); + }; + + IssuableBulkActions.prototype.onFormSubmitAlways = function() { + return this.form.find('[type="submit"]').enable(); + }; + + IssuableBulkActions.prototype.getSelectedIssues = function() { + return this.issues.has('.selected_issue:checked'); + }; + + IssuableBulkActions.prototype.getLabelsFromSelection = function() { + var labels; + labels = []; + this.getSelectedIssues().map(function() { + var _labels; + _labels = $(this).data('labels'); + if (_labels) { + return _labels.map(function(labelId) { + if (labels.indexOf(labelId) === -1) { + return labels.push(labelId); + } + }); + } + }); + return labels; + }; + + + /** + * Will return only labels that were marked previously and the user has unmarked + * @return {Array} Label IDs + */ + + IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() { + var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result; + result = []; + labelsToKeep = []; + ref = this.getElement('.labels-filter .is-indeterminate'); + for (i = 0, len = ref.length; i < len; i++) { + el = ref[i]; + labelsToKeep.push($(el).data('labelId')); + } + ref1 = this.getLabelsFromSelection(); + for (j = 0, len1 = ref1.length; j < len1; j++) { + id = ref1[j]; + // Only the ones that we are not going to keep + if (labelsToKeep.indexOf(id) === -1) { + result.push(id); + } + } + return result; + }; + + + /** + * Simple form serialization, it will return just what we need + * Returns key/value pairs from form data + */ + + IssuableBulkActions.prototype.getFormDataAsObject = function() { + var formData; + formData = { + update: { + state_event: this.form.find('input[name="update[state_event]"]').val(), + assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), + milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), + issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), + subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), + add_label_ids: [], + remove_label_ids: [] + } + }; + if (this.willUpdateLabels) { + this.getLabelsToApply().map(function(id) { + return formData.update.add_label_ids.push(id); + }); + this.getLabelsToRemove().map(function(id) { + return formData.update.remove_label_ids.push(id); + }); + } + return formData; + }; + + IssuableBulkActions.prototype.getLabelsToApply = function() { + var $labels, labelIds; + labelIds = []; + $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); + $labels.each(function(k, label) { + if (label) { + return labelIds.push(parseInt($(label).val())); + } + }); + return labelIds; + }; + + + /** + * Returns Label IDs that will be removed from issue selection + * @return {Array} Array of labels IDs + */ + + IssuableBulkActions.prototype.getLabelsToRemove = function() { + var indeterminatedLabels, labelsToApply, result; + result = []; + indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); + labelsToApply = this.getLabelsToApply(); + indeterminatedLabels.map(function(id) { + // We need to exclude label IDs that will be applied + // By not doing this will cause issues from selection to not add labels at all + if (labelsToApply.indexOf(id) === -1) { + return result.push(id); + } + }); + return result; + }; + + return IssuableBulkActions; + + })(); + +}).call(this); diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index 5f674c36de5..c16eb93a2dd 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,9 +1,9 @@ ((global) => { class Profile { - constructor({ form = $('.edit-user') } = {}) { + constructor({ form }) { this.onSubmitForm = this.onSubmitForm.bind(this); - this.form = form; + this.form = form || $('.edit-user'); this.bindEvents(); this.initAvatarGlCrop(); } diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index 3d710d17ef3..b32199b9721 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -9,20 +9,13 @@ }; class SearchAutocomplete { - constructor({ - wrap = $('.search'), - optsEl = wrap.find('.search-autocomplete-opts'), - autocompletePath = optsEl.data('autocomplete-path'), - projectId = (optsEl.data('autocomplete-project-id') || ''), - projectRef = (optsEl.data('autocomplete-project-ref') || '') - } = {}) { - + constructor({ wrap, optsEl, autocompletePath, projectId, projectRef }) { this.bindEventContext(); - this.wrap = wrap; - this.optsEl = optsEl; - this.autocompletePath = autocompletePath; - this.projectId = projectId; - this.projectRef = projectRef; + this.wrap = wrap || $('.search'); + this.optsEl = optsEl || wrap.find('.search-autocomplete-opts'); + this.autocompletePath = autocompletePath || optsEl.data('autocomplete-path'); + this.projectId = projectId || (optsEl.data('autocomplete-project-id') || ''); + this.projectRef = projectRef || (optsEl.data('autocomplete-project-ref') || ''); this.dropdown = wrap.find('.dropdown'); this.dropdownContent = this.dropdown.find('.dropdown-content'); this.locationBadgeEl = this.getElement('.location-badge'); diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index d8dca490e3e..7e606196435 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,10 +1,10 @@ ((global) => { class Todos { - constructor({ el = $('.js-todos-options') }) { + constructor({ el }) { this.allDoneClicked = this.allDoneClicked.bind(this); this.doneClicked = this.doneClicked.bind(this); - this.el = el; + this.el = el || $('.js-todos-options'); this.perPage = el.data('perPage'); this.clearListeners(); this.initBtnListeners(); diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index 1ce0b31c01f..7cb38b8ec07 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -59,10 +59,10 @@ content on the Users#show page. */ ((global) => { class UserTabs { - constructor ({ defaultAction = 'activity', action = defaultAction, parentEl }) { + constructor ({ defaultAction, action, parentEl }) { this.loaded = {}; - this.defaultAction = defaultAction; - this.action = action; + this.defaultAction = defaultAction || 'activity'; + this.action = action || this.defaultAction; this.$parentEl = $(parentEl) || $(document); this._location = window.location; this.$parentEl.find('.nav-links a') -- cgit v1.2.1 From 603d60a2fcdca90ddbac1f1c53213848fb535aef Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 18:57:59 +0200 Subject: Refactor IssuesBulkAssignment to ES6. --- app/assets/javascripts/dispatcher.js | 2 +- .../javascripts/issues-bulk-assignment.js.es6 | 132 +++++++++------------ app/assets/javascripts/search_autocomplete.js.es6 | 12 +- 3 files changed, 63 insertions(+), 83 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 3073f7a3d3a..0685540f935 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -26,7 +26,7 @@ case 'projects:merge_requests:index': case 'projects:issues:index': Issuable.init(); - new IssuableBulkActions(); + new gl.IssuableBulkActions(); shortcut_handler = new ShortcutsNavigation(); break; case 'projects:issues:show': diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues-bulk-assignment.js.es6 index 62a7fc9a06c..012002a293a 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js.es6 +++ b/app/assets/javascripts/issues-bulk-assignment.js.es6 @@ -1,13 +1,10 @@ -(function() { - this.IssuableBulkActions = (function() { - function IssuableBulkActions(opts) { - // Set defaults - var ref, ref1, ref2; - if (opts == null) { - opts = {}; - } - this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li'); - // Save instance +((global) => { + + class IssuableBulkActions { + constructor({ container, form, issues } = {}) { + this.container = container || $('.content'), + this.form = form || this.getElement('.bulk-update'); + this.issues = issues || this.getElement('.issues-list .issue'); this.form.data('bulkActions', this); this.willUpdateLabels = false; this.bindEvents(); @@ -15,53 +12,46 @@ Issuable.initChecks(); } - IssuableBulkActions.prototype.getElement = function(selector) { + getElement(selector) { return this.container.find(selector); - }; + } - IssuableBulkActions.prototype.bindEvents = function() { + bindEvents() { return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); - }; + } - IssuableBulkActions.prototype.onFormSubmit = function(e) { + onFormSubmit(e) { e.preventDefault(); return this.submit(); - }; + } - IssuableBulkActions.prototype.submit = function() { - var _this, xhr; - _this = this; - xhr = $.ajax({ + submit() { + const _this = this; + const xhr = $.ajax({ url: this.form.attr('action'), method: this.form.attr('method'), dataType: 'JSON', data: this.getFormDataAsObject() }); - xhr.done(function(response, status, xhr) { - return location.reload(); - }); - xhr.fail(function() { - return new Flash("Issue update failed"); - }); + xhr.done(() => window.location.reload()); + xhr.fail(() => new Flash("Issue update failed")); return xhr.always(this.onFormSubmitAlways.bind(this)); - }; + } - IssuableBulkActions.prototype.onFormSubmitAlways = function() { + onFormSubmitAlways() { return this.form.find('[type="submit"]').enable(); - }; + } - IssuableBulkActions.prototype.getSelectedIssues = function() { + getSelectedIssues() { return this.issues.has('.selected_issue:checked'); - }; + } - IssuableBulkActions.prototype.getLabelsFromSelection = function() { - var labels; - labels = []; + getLabelsFromSelection() { + const labels = []; this.getSelectedIssues().map(function() { - var _labels; - _labels = $(this).data('labels'); - if (_labels) { - return _labels.map(function(labelId) { + const labelsData = $(this).data('labels'); + if (labelsData) { + return labelsData.map(function(labelId) { if (labels.indexOf(labelId) === -1) { return labels.push(labelId); } @@ -69,7 +59,7 @@ } }); return labels; - }; + } /** @@ -77,25 +67,19 @@ * @return {Array} Label IDs */ - IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() { - var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result; - result = []; - labelsToKeep = []; - ref = this.getElement('.labels-filter .is-indeterminate'); - for (i = 0, len = ref.length; i < len; i++) { - el = ref[i]; - labelsToKeep.push($(el).data('labelId')); - } - ref1 = this.getLabelsFromSelection(); - for (j = 0, len1 = ref1.length; j < len1; j++) { - id = ref1[j]; - // Only the ones that we are not going to keep - if (labelsToKeep.indexOf(id) === -1) { - result.push(id); - } - } + getUnmarkedIndeterminedLabels() { + const result = []; + const elements = this.getElement('.labels-filter .is-indeterminate'); + const labelsToKeep = elements.map((el) => labelsToKeep.push($(el).data('labelId'))); + const selectedLabels = this.getLabelsFromSelection() + .forEach(() => { + const id = selectedLabels[j]; + if (labelsToKeep.indexOf(id) === -1) { + result.push(id); + } + }); return result; - }; + } /** @@ -103,9 +87,8 @@ * Returns key/value pairs from form data */ - IssuableBulkActions.prototype.getFormDataAsObject = function() { - var formData; - formData = { + getFormDataAsObject() { + const formData = { update: { state_event: this.form.find('input[name="update[state_event]"]').val(), assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), @@ -125,19 +108,18 @@ }); } return formData; - }; + } - IssuableBulkActions.prototype.getLabelsToApply = function() { - var $labels, labelIds; - labelIds = []; - $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); + getLabelsToApply() { + const labelIds = []; + const $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); $labels.each(function(k, label) { if (label) { return labelIds.push(parseInt($(label).val())); } }); return labelIds; - }; + } /** @@ -145,11 +127,10 @@ * @return {Array} Array of labels IDs */ - IssuableBulkActions.prototype.getLabelsToRemove = function() { - var indeterminatedLabels, labelsToApply, result; - result = []; - indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); - labelsToApply = this.getLabelsToApply(); + getLabelsToRemove() { + const result = []; + const indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); + const labelsToApply = this.getLabelsToApply(); indeterminatedLabels.map(function(id) { // We need to exclude label IDs that will be applied // By not doing this will cause issues from selection to not add labels at all @@ -158,10 +139,9 @@ } }); return result; - }; - - return IssuableBulkActions; + } + } - })(); + global.IssuableBulkActions = IssuableBulkActions; -}).call(this); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index b32199b9721..b4c6226dc68 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -9,14 +9,14 @@ }; class SearchAutocomplete { - constructor({ wrap, optsEl, autocompletePath, projectId, projectRef }) { + constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) { this.bindEventContext(); this.wrap = wrap || $('.search'); - this.optsEl = optsEl || wrap.find('.search-autocomplete-opts'); - this.autocompletePath = autocompletePath || optsEl.data('autocomplete-path'); - this.projectId = projectId || (optsEl.data('autocomplete-project-id') || ''); - this.projectRef = projectRef || (optsEl.data('autocomplete-project-ref') || ''); - this.dropdown = wrap.find('.dropdown'); + this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts'); + this.autocompletePath = autocompletePath || this.optsEl.data('autocomplete-path'); + this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || ''); + this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || ''); + this.dropdown = this.wrap.find('.dropdown'); this.dropdownContent = this.dropdown.find('.dropdown-content'); this.locationBadgeEl = this.getElement('.location-badge'); this.scopeInputEl = this.getElement('#scope'); -- cgit v1.2.1 From 53541e269119d57016441b6493fab86f60eacf88 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 12 Sep 2016 19:05:47 +0200 Subject: Update LabelManager to ES6. --- app/assets/javascripts/LabelManager.js.es6 | 104 +++++++++++++++++++++++++++++ app/assets/javascripts/dispatcher.js | 3 +- 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/LabelManager.js.es6 diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/LabelManager.js.es6 new file mode 100644 index 00000000000..b655dfa7113 --- /dev/null +++ b/app/assets/javascripts/LabelManager.js.es6 @@ -0,0 +1,104 @@ +((global) => { + + class LabelManager { + constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { + this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); + this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); + this.otherLabels = otherLabels || $('.js-other-labels'); + this.errorMessage = 'Unable to update label prioritization at this time'; + this.prioritizedLabels.sortable({ + items: 'li', + placeholder: 'list-placeholder', + axis: 'y', + update: this.onPrioritySortUpdate.bind(this) + }); + this.bindEvents(); + } + + bindEvents() { + // TODO: Check if this is being bound correctly + return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); + } + + onTogglePriorityClick(e) { + e.preventDefault(); + const _this = e.data; + const $btn = $(e.currentTarget); + const $label = $(`#${$btn.data('domId')}`); + const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; + const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); + $tooltip.tooltip('destroy'); + return _this.toggleLabelPriority($label, action); + } + + toggleLabelPriority($label, action, persistState) { + if (persistState == null) { + persistState = true; + } + let xhr; + const _this = this; + const url = $label.find('.js-toggle-priority').data('url'); + let $target = this.prioritizedLabels; + let $from = this.otherLabels; + if (action === 'remove') { + $target = this.otherLabels; + $from = this.prioritizedLabels; + } + if ($from.find('li').length === 1) { + $from.find('.empty-message').removeClass('hidden'); + } + if (!$target.find('li').length) { + $target.find('.empty-message').addClass('hidden'); + } + $label.detach().appendTo($target); + if (!persistState) { + return; + } + if (action === 'remove') { + xhr = $.ajax({ + url, + type: 'DELETE' + }); + if (!$from.find('li').length) { + $from.find('.empty-message').removeClass('hidden'); + } + } else { + xhr = this.savePrioritySort($label, action); + } + return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); + } + + onPrioritySortUpdate() { + const xhr = this.savePrioritySort(); + return xhr.fail(function() { + return new Flash(this.errorMessage, 'alert'); + }); + } + + savePrioritySort() { + return $.post({ + url: this.prioritizedLabels.data('url'), + data: { + label_ids: this.getSortedLabelsIds() + } + }); + } + + rollbackLabelPosition($label, originalAction) { + const action = originalAction === 'remove' ? 'add' : 'remove'; + this.toggleLabelPriority($label, action, false); + return new Flash(this.errorMessage, 'alert'); + } + + getSortedLabelsIds() { + // TODO: Check that this works how you expect + return this.prioritizedLabels.find('li').map(function(item) { + return $(item).data('id'); + }); + } + } + + gl.LabelManager = LabelManager; + +})(window.gl || (window.gl = {})); + diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 0685540f935..ecb5812c015 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -164,8 +164,9 @@ new Labels(); break; case 'projects:labels:index': + debugger; if ($('.prioritized-labels').length) { - new LabelManager(); + new gl.LabelManager(); } break; case 'projects:network:show': -- cgit v1.2.1 From 2c5bd97dc4414b703d6eaadc0af586699c228004 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 12 Sep 2016 19:19:02 +0200 Subject: Convert BlobLicenseSelctors to ES6. --- .../javascripts/blob/blob_license_selectors.js | 25 ---------------------- .../javascripts/blob/blob_license_selectors.js.es6 | 21 ++++++++++++++++++ 2 files changed, 21 insertions(+), 25 deletions(-) delete mode 100644 app/assets/javascripts/blob/blob_license_selectors.js create mode 100644 app/assets/javascripts/blob/blob_license_selectors.js.es6 diff --git a/app/assets/javascripts/blob/blob_license_selectors.js b/app/assets/javascripts/blob/blob_license_selectors.js deleted file mode 100644 index 39237705e8d..00000000000 --- a/app/assets/javascripts/blob/blob_license_selectors.js +++ /dev/null @@ -1,25 +0,0 @@ -(function() { - this.BlobLicenseSelectors = (function() { - function BlobLicenseSelectors(opts) { - var ref; - this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-license-selector'), this.editor = opts.editor; - this.$dropdowns.each((function(_this) { - return function(i, dropdown) { - var $dropdown; - $dropdown = $(dropdown); - return new BlobLicenseSelector({ - pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i, - data: $dropdown.data('data'), - wrapper: $dropdown.closest('.js-license-selector-wrap'), - dropdown: $dropdown, - editor: _this.editor - }); - }; - })(this)); - } - - return BlobLicenseSelectors; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js.es6 new file mode 100644 index 00000000000..f8391919068 --- /dev/null +++ b/app/assets/javascripts/blob/blob_license_selectors.js.es6 @@ -0,0 +1,21 @@ +((global) => { + class BlobLicenseSelectors() { + constructor({ $dropdowns, editor }) { + this.$dropdowns = $('.js-license-selector'); + this.editor = editor; + this.$dropdowns.each((dropdown) => { + const $dropdown = $(dropdown); + return new BlobLicenseSelector({ + editor, + pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-license-selector-wrap'), + dropdown: $dropdown, + }); + }); + } + } + + global.BlobLicenseSelectors = BlobLicenseSelectors; + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 6fc9118cddf102e18b00156c2892450960ee2d17 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 19 Sep 2016 16:38:31 +0200 Subject: Remove super passthrough. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 4 ---- app/assets/javascripts/blob/blob_license_selectors.js.es6 | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index bb9d0444a8c..dee06a7196f 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -2,10 +2,6 @@ ((global) => { class BlobCiYamlSelector extends TemplateSelector { - constructor(...args) { - super(...args); - } - requestFile(query) { return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); } diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js.es6 index f8391919068..ebc1618816f 100644 --- a/app/assets/javascripts/blob/blob_license_selectors.js.es6 +++ b/app/assets/javascripts/blob/blob_license_selectors.js.es6 @@ -1,5 +1,5 @@ ((global) => { - class BlobLicenseSelectors() { + class BlobLicenseSelectors { constructor({ $dropdowns, editor }) { this.$dropdowns = $('.js-license-selector'); this.editor = editor; -- cgit v1.2.1 From 8fa3e576a6afa4c424b0e476d6213890491c97c1 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 19 Sep 2016 16:48:20 +0200 Subject: Remove unneeded semicolon. --- app/assets/javascripts/user_tabs.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index 7cb38b8ec07..63bce0a6f6f 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -156,7 +156,7 @@ content on the Users#show page. url: new_state }, document.title, new_state); return new_state; - }; + } } global.UserTabs = UserTabs; })(window.gl || (window.gl = {})); -- cgit v1.2.1 From c756f1cf7545ac50393829a418d61dd98248406a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 11:35:07 +0200 Subject: Update Changelog. --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 51515ac5ade..bcedb3fcec2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -83,6 +83,10 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 +v 8.12.1 (unreleased) + - Refactor remnants of CoffeeScript destructured opts and super !6261 + +v 8.12.0 (released) - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region -- cgit v1.2.1 From 47217666194d5767cab4e5c7ba3e0aaaae10f943 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 12:47:58 +0200 Subject: Fix test for SearchAutocomplete. --- spec/javascripts/search_autocomplete_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 00d9fc1302a..4470fbcb099 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -112,7 +112,7 @@ fixture.preload('search_autocomplete.html'); beforeEach(function() { fixture.load('search_autocomplete.html'); - return widget = new SearchAutocomplete; + return widget = new gl.SearchAutocomplete; }); it('should show Dashboard specific dropdown menu', function() { var list; -- cgit v1.2.1 From 87036a6acc6f857e026fe9e31eb4d1605fccacf5 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 12:57:49 +0200 Subject: Fix tests for Blob Selectors. --- app/assets/javascripts/blob_edit/edit_blob.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 0be4b6392bf..e248b557c96 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -23,10 +23,10 @@ })(this)); this.initModePanesAndLinks(); this.initSoftWrap(); - new BlobLicenseSelectors({ + new gl.BlobLicenseSelectors({ editor: this.editor }); - new BlobGitignoreSelectors({ + new gl.BlobGitignoreSelectors({ editor: this.editor }); new gl.BlobCiYamlSelectors({ -- cgit v1.2.1 From 014bef80bc6cded43ae695ba54376a532d9809e5 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 13:20:27 +0200 Subject: Fix up for issues bulk assignment test. --- app/assets/javascripts/issues-bulk-assignment.js.es6 | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues-bulk-assignment.js.es6 index 012002a293a..0808f538f01 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js.es6 +++ b/app/assets/javascripts/issues-bulk-assignment.js.es6 @@ -69,15 +69,17 @@ getUnmarkedIndeterminedLabels() { const result = []; - const elements = this.getElement('.labels-filter .is-indeterminate'); - const labelsToKeep = elements.map((el) => labelsToKeep.push($(el).data('labelId'))); - const selectedLabels = this.getLabelsFromSelection() - .forEach(() => { - const id = selectedLabels[j]; - if (labelsToKeep.indexOf(id) === -1) { - result.push(id); - } - }); + const labelsToKeep = []; + + this.getElement('.labels-filter .is-indeterminate') + .each((i, el) => labelsToKeep.push($(el).data('labelId'))); + + this.getLabelsFromSelection().forEach((id) => { + if (labelsToKeep.indexOf(id) === -1) { + result.push(id); + } + }); + return result; } -- cgit v1.2.1 From 07bf262ffeb319e162fe81529af61b781a774bc2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 13:30:37 +0200 Subject: Remove old LabelManager. --- app/assets/javascripts/LabelManager.js | 115 --------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 app/assets/javascripts/LabelManager.js diff --git a/app/assets/javascripts/LabelManager.js b/app/assets/javascripts/LabelManager.js deleted file mode 100644 index d4a4c7abaa1..00000000000 --- a/app/assets/javascripts/LabelManager.js +++ /dev/null @@ -1,115 +0,0 @@ -(function() { - this.LabelManager = (function() { - LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time'; - - function LabelManager(opts) { - // Defaults - var ref, ref1, ref2; - if (opts == null) { - opts = {}; - } - this.togglePriorityButton = (ref = opts.togglePriorityButton) != null ? ref : $('.js-toggle-priority'), this.prioritizedLabels = (ref1 = opts.prioritizedLabels) != null ? ref1 : $('.js-prioritized-labels'), this.otherLabels = (ref2 = opts.otherLabels) != null ? ref2 : $('.js-other-labels'); - this.prioritizedLabels.sortable({ - items: 'li', - placeholder: 'list-placeholder', - axis: 'y', - update: this.onPrioritySortUpdate.bind(this) - }); - this.bindEvents(); - } - - LabelManager.prototype.bindEvents = function() { - return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); - }; - - LabelManager.prototype.onTogglePriorityClick = function(e) { - var $btn, $label, $tooltip, _this, action; - e.preventDefault(); - _this = e.data; - $btn = $(e.currentTarget); - $label = $("#" + ($btn.data('domId'))); - action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; - // Make sure tooltip will hide - $tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby'))); - $tooltip.tooltip('destroy'); - return _this.toggleLabelPriority($label, action); - }; - - LabelManager.prototype.toggleLabelPriority = function($label, action, persistState) { - var $from, $target, _this, url, xhr; - if (persistState == null) { - persistState = true; - } - _this = this; - url = $label.find('.js-toggle-priority').data('url'); - $target = this.prioritizedLabels; - $from = this.otherLabels; - // Optimistic update - if (action === 'remove') { - $target = this.otherLabels; - $from = this.prioritizedLabels; - } - if ($from.find('li').length === 1) { - $from.find('.empty-message').removeClass('hidden'); - } - if (!$target.find('li').length) { - $target.find('.empty-message').addClass('hidden'); - } - $label.detach().appendTo($target); - // Return if we are not persisting state - if (!persistState) { - return; - } - if (action === 'remove') { - xhr = $.ajax({ - url: url, - type: 'DELETE' - }); - // Restore empty message - if (!$from.find('li').length) { - $from.find('.empty-message').removeClass('hidden'); - } - } else { - xhr = this.savePrioritySort($label, action); - } - return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); - }; - - LabelManager.prototype.onPrioritySortUpdate = function() { - var xhr; - xhr = this.savePrioritySort(); - return xhr.fail(function() { - return new Flash(this.errorMessage, 'alert'); - }); - }; - - LabelManager.prototype.savePrioritySort = function() { - return $.post({ - url: this.prioritizedLabels.data('url'), - data: { - label_ids: this.getSortedLabelsIds() - } - }); - }; - - LabelManager.prototype.rollbackLabelPosition = function($label, originalAction) { - var action; - action = originalAction === 'remove' ? 'add' : 'remove'; - this.toggleLabelPriority($label, action, false); - return new Flash(this.errorMessage, 'alert'); - }; - - LabelManager.prototype.getSortedLabelsIds = function() { - var sortedIds; - sortedIds = []; - this.prioritizedLabels.find('li').each(function() { - return sortedIds.push($(this).data('id')); - }); - return sortedIds; - }; - - return LabelManager; - - })(); - -}).call(this); -- cgit v1.2.1 From ed8f27e636b4ba5cb9f02c97de4d3a34a732b79b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 13:37:26 +0200 Subject: Fix profile test failure. --- app/assets/javascripts/profile/profile.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index c16eb93a2dd..b2307be73ad 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -1,7 +1,7 @@ ((global) => { class Profile { - constructor({ form }) { + constructor({ form } = {}) { this.onSubmitForm = this.onSubmitForm.bind(this); this.form = form || $('.edit-user'); this.bindEvents(); -- cgit v1.2.1 From e0d66819e99c678f1400caec1dd137d2c63790c0 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 13:40:31 +0200 Subject: Fix todos specs. --- app/assets/javascripts/todos.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index 7e606196435..945000c2f43 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,7 +1,7 @@ ((global) => { class Todos { - constructor({ el }) { + constructor({ el } = {}) { this.allDoneClicked = this.allDoneClicked.bind(this); this.doneClicked = this.doneClicked.bind(this); this.el = el || $('.js-todos-options'); -- cgit v1.2.1 From f80b8bd26c785aa07e0f0b0523d44af2808eb110 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 15:01:20 +0200 Subject: Remove CHANGELOG duplicate. --- CHANGELOG | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bcedb3fcec2..eefab58eccc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -77,15 +77,12 @@ v 8.12.2 - Only update issuable labels if they have been changed - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix resolve discussion buttons endpoint path + - Refactor remnants of CoffeeScript destructured opts and super !6261 v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix issue with search filter labels not displaying -v 8.12.0 -v 8.12.1 (unreleased) - - Refactor remnants of CoffeeScript destructured opts and super !6261 - v 8.12.0 (released) - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable -- cgit v1.2.1 From 5ef3e8669db802e2355fd8dea1bef68ea7a48726 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 16:58:36 +0200 Subject: Make fixes to get tests passing. --- app/assets/javascripts/LabelManager.js.es6 | 7 ++++--- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 2 +- app/assets/javascripts/blob/blob_license_selectors.js.es6 | 2 +- app/assets/javascripts/blob_edit/edit_blob.js | 2 +- app/assets/javascripts/todos.js.es6 | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/LabelManager.js.es6 index b655dfa7113..a66767825e3 100644 --- a/app/assets/javascripts/LabelManager.js.es6 +++ b/app/assets/javascripts/LabelManager.js.es6 @@ -91,10 +91,11 @@ } getSortedLabelsIds() { - // TODO: Check that this works how you expect - return this.prioritizedLabels.find('li').map(function(item) { - return $(item).data('id'); + const sortedIds = []; + this.prioritizedLabels.find('li').each(function() { + sortedIds.push($(this).data('id')); }); + return sortedIds; } } diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index dee06a7196f..a0fc9519aaa 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -10,7 +10,7 @@ global.BlobCiYamlSelector = BlobCiYamlSelector; class BlobCiYamlSelectors { - constructor({ editor, $dropdowns }) { + constructor({ editor, $dropdowns } = {}) { this.editor = editor; this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector'); this.initSelectors(); diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js.es6 index ebc1618816f..153ed457559 100644 --- a/app/assets/javascripts/blob/blob_license_selectors.js.es6 +++ b/app/assets/javascripts/blob/blob_license_selectors.js.es6 @@ -3,7 +3,7 @@ constructor({ $dropdowns, editor }) { this.$dropdowns = $('.js-license-selector'); this.editor = editor; - this.$dropdowns.each((dropdown) => { + this.$dropdowns.each((i, dropdown) => { const $dropdown = $(dropdown); return new BlobLicenseSelector({ editor, diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index e248b557c96..8db4f6a3b28 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -26,7 +26,7 @@ new gl.BlobLicenseSelectors({ editor: this.editor }); - new gl.BlobGitignoreSelectors({ + new BlobGitignoreSelectors({ editor: this.editor }); new gl.BlobCiYamlSelectors({ diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index 945000c2f43..055228c5df8 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -5,7 +5,7 @@ this.allDoneClicked = this.allDoneClicked.bind(this); this.doneClicked = this.doneClicked.bind(this); this.el = el || $('.js-todos-options'); - this.perPage = el.data('perPage'); + this.perPage = this.el.data('perPage'); this.clearListeners(); this.initBtnListeners(); this.initFilters(); -- cgit v1.2.1 From 14d681ebc8cb777cc03b0099d603c3e3dd2e7579 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 29 Sep 2016 19:58:09 +0200 Subject: Refactor TemplateSelector and fix for tests. --- app/assets/javascripts/blob/blob_ci_yaml.js.es6 | 9 +- .../javascripts/blob/blob_gitignore_selector.js | 2 +- .../javascripts/blob/blob_license_selector.js | 2 +- .../javascripts/blob/template_selector.js.es6 | 99 ++++++++++++++++++++++ .../templates/issuable_template_selector.js.es6 | 2 +- 5 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/blob/template_selector.js.es6 diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 index a0fc9519aaa..d6ea4f84f57 100644 --- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6 @@ -1,11 +1,15 @@ /*= require blob/template_selector */ ((global) => { - class BlobCiYamlSelector extends TemplateSelector { + class BlobCiYamlSelector extends gl.TemplateSelector { requestFile(query) { return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this)); } - }; + + requestFileSuccess(file) { + return super.requestFileSuccess(file); + } + } global.BlobCiYamlSelector = BlobCiYamlSelector; @@ -17,6 +21,7 @@ } initSelectors() { + const editor = this.editor; this.$dropdowns.each((i, dropdown) => { const $dropdown = $(dropdown); return new BlobCiYamlSelector({ diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js index 54a09e919f8..cd746b05cf6 100644 --- a/app/assets/javascripts/blob/blob_gitignore_selector.js +++ b/app/assets/javascripts/blob/blob_gitignore_selector.js @@ -18,6 +18,6 @@ return BlobGitignoreSelector; - })(TemplateSelector); + })(gl.TemplateSelector); }).call(this); diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js index 9a8ef08f4e5..2701df3e6de 100644 --- a/app/assets/javascripts/blob/blob_license_selector.js +++ b/app/assets/javascripts/blob/blob_license_selector.js @@ -23,6 +23,6 @@ return BlobLicenseSelector; - })(TemplateSelector); + })(gl.TemplateSelector); }).call(this); diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 new file mode 100644 index 00000000000..a86a2a7c2fc --- /dev/null +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -0,0 +1,99 @@ +((global) => { + class TemplateSelector { + constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) { + this.onClick = this.onClick.bind(this); + this.dropdown = dropdown; + this.data = data; + this.pattern = pattern; + this.wrapper = wrapper; + this.editor = editor; + this.fileEndpoint = fileEndpoint; + this.$input = $input || $('#file_name'); + this.dropdownIcon = $('.fa-chevron-down', this.dropdown); + this.buildDropdown(); + this.bindEvents(); + this.onFilenameUpdate(); + + this.autosizeUpdateEvent = document.createEvent('Event'); + this.autosizeUpdateEvent.initEvent('autosize:update', true, false); + } + + buildDropdown() { + return this.dropdown.glDropdown({ + data: this.data, + filterable: true, + selectable: true, + toggleLabel: this.toggleLabel, + search: { + fields: ['name'] + }, + clicked: this.onClick, + text: function(item) { + return item.name; + } + }); + } + + bindEvents() { + return this.$input.on('keyup blur', (function(_this) { + return function(e) { + return _this.onFilenameUpdate(); + }; + })(this)); + } + + toggleLabel(item) { + return item.name; + } + + onFilenameUpdate() { + var filenameMatches; + if (!this.$input.length) { + return; + } + filenameMatches = this.pattern.test(this.$input.val().trim()); + if (!filenameMatches) { + this.wrapper.addClass('hidden'); + return; + } + return this.wrapper.removeClass('hidden'); + } + + onClick(item, el, e) { + e.preventDefault(); + return this.requestFile(item); + } + + requestFile(item) { + // This `requestFile` method is an abstract method that should + // be added by all subclasses. + } + + // To be implemented on the extending class + // e.g. + // Api.gitignoreText item.name, @requestFileSuccess.bind(@) + requestFileSuccess(file, skipFocus) { + this.editor.setValue(file.content, 1); + if (!skipFocus) this.editor.focus(); + + if (this.editor instanceof jQuery) { + this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); + } + } + + startLoadingSpinner() { + this.dropdownIcon + .addClass('fa-spinner fa-spin') + .removeClass('fa-chevron-down'); + } + + stopLoadingSpinner() { + this.dropdownIcon + .addClass('fa-chevron-down') + .removeClass('fa-spinner fa-spin'); + } + } + + global.TemplateSelector = TemplateSelector; + })(window.gl || ( window.gl = {})); + diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index 017008c8438..f2a55f9caa0 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -1,7 +1,7 @@ /*= require ../blob/template_selector */ ((global) => { - class IssuableTemplateSelector extends TemplateSelector { + class IssuableTemplateSelector extends gl.TemplateSelector { constructor(...args) { super(...args); this.projectPath = this.dropdown.data('project-path'); -- cgit v1.2.1 From 3ca521d63366beede3df2d02aa4291344d1db43f Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:24:17 +0200 Subject: Remove 'released' from CHANGELOG. --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index eefab58eccc..5badb2f66ee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -83,7 +83,7 @@ v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix issue with search filter labels not displaying -v 8.12.0 (released) +v 8.12.0 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region -- cgit v1.2.1 From 6880fe840090c24ce60b6d388714b5c6087e977e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:35:59 +0200 Subject: Fix leftover or inadvertantly removed comments. --- app/assets/javascripts/LabelManager.js.es6 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/LabelManager.js.es6 index a66767825e3..bc68e53504f 100644 --- a/app/assets/javascripts/LabelManager.js.es6 +++ b/app/assets/javascripts/LabelManager.js.es6 @@ -16,7 +16,6 @@ } bindEvents() { - // TODO: Check if this is being bound correctly return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); } @@ -51,6 +50,7 @@ $target.find('.empty-message').addClass('hidden'); } $label.detach().appendTo($target); + // Return if we are not persisting state if (!persistState) { return; } @@ -59,6 +59,7 @@ url, type: 'DELETE' }); + // Restore empty message if (!$from.find('li').length) { $from.find('.empty-message').removeClass('hidden'); } -- cgit v1.2.1 From 6c37adcab93c2162ded987f72276d2eaa0e632c2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 3 Oct 2016 14:22:16 +0200 Subject: Remove debug flag. --- app/assets/javascripts/dispatcher.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index ecb5812c015..93183d0720a 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -164,7 +164,6 @@ new Labels(); break; case 'projects:labels:index': - debugger; if ($('.prioritized-labels').length) { new gl.LabelManager(); } -- cgit v1.2.1 From 16097bbedab5133dfc984ece1da64ca8cd50de03 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 4 Oct 2016 18:21:45 +0200 Subject: Delete leftover usertabs and template_selector es5 files. --- app/assets/javascripts/blob/template_selector.js | 108 ------------- app/assets/javascripts/user_tabs.js | 188 ----------------------- 2 files changed, 296 deletions(-) delete mode 100644 app/assets/javascripts/blob/template_selector.js delete mode 100644 app/assets/javascripts/user_tabs.js diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js deleted file mode 100644 index 6d41442cdfc..00000000000 --- a/app/assets/javascripts/blob/template_selector.js +++ /dev/null @@ -1,108 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.TemplateSelector = (function() { - function TemplateSelector(opts) { - var ref; - if (opts == null) { - opts = {}; - } - this.onClick = bind(this.onClick, this); - this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name'); - this.dropdownIcon = $('.fa-chevron-down', this.dropdown); - this.buildDropdown(); - this.bindEvents(); - this.onFilenameUpdate(); - - this.autosizeUpdateEvent = document.createEvent('Event'); - this.autosizeUpdateEvent.initEvent('autosize:update', true, false); - } - - TemplateSelector.prototype.buildDropdown = function() { - return this.dropdown.glDropdown({ - data: this.data, - filterable: true, - selectable: true, - toggleLabel: this.toggleLabel, - search: { - fields: ['name'] - }, - clicked: this.onClick, - text: function(item) { - return item.name; - } - }); - }; - - TemplateSelector.prototype.bindEvents = function() { - return this.$input.on('keyup blur', (function(_this) { - return function(e) { - return _this.onFilenameUpdate(); - }; - })(this)); - }; - - TemplateSelector.prototype.toggleLabel = function(item) { - return item.name; - }; - - TemplateSelector.prototype.onFilenameUpdate = function() { - var filenameMatches; - if (!this.$input.length) { - return; - } - filenameMatches = this.pattern.test(this.$input.val().trim()); - if (!filenameMatches) { - this.wrapper.addClass('hidden'); - return; - } - return this.wrapper.removeClass('hidden'); - }; - - TemplateSelector.prototype.onClick = function(item, el, e) { - e.preventDefault(); - return this.requestFile(item); - }; - - TemplateSelector.prototype.requestFile = function(item) { - // This `requestFile` method is an abstract method that should - // be added by all subclasses. - }; - - // To be implemented on the extending class - // e.g. - // Api.gitignoreText item.name, @requestFileSuccess.bind(@) - TemplateSelector.prototype.requestFileSuccess = function(file, opts) { - var oldValue = this.editor.getValue(); - var newValue = file.content; - if (opts == null) { - opts = {}; - } - if (opts.append && oldValue.length && oldValue !== newValue) { - newValue = oldValue + '\n\n' + newValue; - } - this.editor.setValue(newValue, 1); - if (!opts.skipFocus) this.editor.focus(); - - if (this.editor instanceof jQuery) { - this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); - } - }; - - TemplateSelector.prototype.startLoadingSpinner = function() { - this.dropdownIcon - .addClass('fa-spinner fa-spin') - .removeClass('fa-chevron-down'); - }; - - TemplateSelector.prototype.stopLoadingSpinner = function() { - this.dropdownIcon - .addClass('fa-chevron-down') - .removeClass('fa-spinner fa-spin'); - }; - - return TemplateSelector; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js deleted file mode 100644 index 8a657780eb6..00000000000 --- a/app/assets/javascripts/user_tabs.js +++ /dev/null @@ -1,188 +0,0 @@ -// UserTabs -// -// Handles persisting and restoring the current tab selection and lazily-loading -// content on the Users#show page. -// -// ### Example Markup -// -// -// -//
    -//
    -// Activity Content -//
    -//
    -// Groups Content -//
    -//
    -// Contributed projects content -//
    -//
    -// Projects content -//
    -//
    -// Snippets content -//
    -//
    -// -//
    -//
    -// Loading Animation -//
    -//
    -// -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.UserTabs = (function() { - function UserTabs(opts) { - this.tabShown = bind(this.tabShown, this); - var i, item, len, ref, ref1, ref2, ref3; - this.action = (ref = opts.action) != null ? ref : 'activity', this.defaultAction = (ref1 = opts.defaultAction) != null ? ref1 : 'activity', this.parentEl = (ref2 = opts.parentEl) != null ? ref2 : $(document); - // Make jQuery object if selector is provided - if (typeof this.parentEl === 'string') { - this.parentEl = $(this.parentEl); - } - // Store the `location` object, allowing for easier stubbing in tests - this._location = location; - // Set tab states - this.loaded = {}; - ref3 = this.parentEl.find('.nav-links a'); - for (i = 0, len = ref3.length; i < len; i++) { - item = ref3[i]; - this.loaded[$(item).attr('data-action')] = false; - } - // Actions - this.actions = Object.keys(this.loaded); - this.bindEvents(); - // Set active tab - if (this.action === 'show') { - this.action = this.defaultAction; - } - this.activateTab(this.action); - } - - UserTabs.prototype.bindEvents = function() { - // Toggle event listeners - return this.parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]').on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', this.tabShown); - }; - - UserTabs.prototype.tabShown = function(event) { - var $target, action, source; - $target = $(event.target); - action = $target.data('action'); - source = $target.attr('href'); - this.setTab(source, action); - return this.setCurrentAction(action); - }; - - UserTabs.prototype.activateTab = function(action) { - return this.parentEl.find(".nav-links .js-" + action + "-tab a").tab('show'); - }; - - UserTabs.prototype.setTab = function(source, action) { - if (this.loaded[action] === true) { - return; - } - if (action === 'activity') { - this.loadActivities(source); - } - if (action === 'groups' || action === 'contributed' || action === 'projects' || action === 'snippets') { - return this.loadTab(source, action); - } - }; - - UserTabs.prototype.loadTab = function(source, action) { - return $.ajax({ - beforeSend: (function(_this) { - return function() { - return _this.toggleLoading(true); - }; - })(this), - complete: (function(_this) { - return function() { - return _this.toggleLoading(false); - }; - })(this), - dataType: 'json', - type: 'GET', - url: source + ".json", - success: (function(_this) { - return function(data) { - var tabSelector; - tabSelector = 'div#' + action; - _this.parentEl.find(tabSelector).html(data.html); - _this.loaded[action] = true; - // Fix tooltips - return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); - }; - })(this) - }); - }; - - UserTabs.prototype.loadActivities = function(source) { - var $calendarWrap; - if (this.loaded['activity'] === true) { - return; - } - $calendarWrap = this.parentEl.find('.user-calendar'); - $calendarWrap.load($calendarWrap.data('href')); - new Activities(); - return this.loaded['activity'] = true; - }; - - UserTabs.prototype.toggleLoading = function(status) { - return this.parentEl.find('.loading-status .loading').toggle(status); - }; - - UserTabs.prototype.setCurrentAction = function(action) { - var new_state, regExp; - // Remove possible actions from URL - regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$'); - new_state = this._location.pathname; - // remove trailing slashes - new_state = new_state.replace(/\/+$/, ""); - new_state = new_state.replace(regExp, ''); - // Append the new action if we're on a tab other than 'activity' - if (action !== this.defaultAction) { - new_state += "/" + action; - } - // Ensure parameters and hash come along for the ride - new_state += this._location.search + this._location.hash; - history.replaceState({ - turbolinks: true, - url: new_state - }, document.title, new_state); - return new_state; - }; - - return UserTabs; - - })(); - -}).call(this); -- cgit v1.2.1 From b6d52a6f10d5f3d65459a953b53ba1c380fd39af Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 4 Oct 2016 18:33:19 +0200 Subject: Simplify TemplateSelector keyup/blur handler. --- app/assets/javascripts/blob/template_selector.js.es6 | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 index a86a2a7c2fc..ba60b3b2b98 100644 --- a/app/assets/javascripts/blob/template_selector.js.es6 +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -35,11 +35,7 @@ } bindEvents() { - return this.$input.on('keyup blur', (function(_this) { - return function(e) { - return _this.onFilenameUpdate(); - }; - })(this)); + return this.$input.on('keyup blur', (e) => this.onFilenameUpdate()); } toggleLabel(item) { -- cgit v1.2.1 From b189a34bf3ee9eb631ff6c56154638215725fbc7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Oct 2016 11:14:55 +0100 Subject: Updates Last Deployment column in environmnets list. Fixes deployment id to show the internal one --- app/views/projects/environments/_environment.html.haml | 2 +- app/views/projects/environments/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 36a6162a5a8..73a88dc38ba 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -7,7 +7,7 @@ %td - if last_deployment = user_avatar(user: last_deployment.user, size: 20) - %strong ##{last_deployment.id} + %span ##{last_deployment.iid} %td - if last_deployment diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index b3eb5b0011a..8da9221e08a 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -26,7 +26,7 @@ %table.table.builds.environments %tbody %th Environment - %th Last Deployment + %th Last Deployment ID %th Commit %th %th -- cgit v1.2.1 From 16ed9b6129daf51a296d4576580c5f232d043db6 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 4 Oct 2016 18:03:10 +0200 Subject: Refactor Gitlab::Identifier This refactors Gitlab::Identifier so it uses fewer queries and is actually tested. Queries are reduced by caching the output as well as using 1 query (instead of 2) to find a user using an SSH key. --- CHANGELOG | 1 + app/models/user.rb | 5 ++ lib/gitlab/identifier.rb | 58 ++++++++++++++--- spec/lib/gitlab/identifier_spec.rb | 123 +++++++++++++++++++++++++++++++++++++ spec/models/user_spec.rb | 17 +++++ spec/workers/post_receive_spec.rb | 4 +- 6 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 spec/lib/gitlab/identifier_spec.rb diff --git a/CHANGELOG b/CHANGELOG index a47ec511452..800b1a84f62 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -44,6 +44,7 @@ v 8.13.0 (unreleased) - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) + - Reduce queries needed to find users using their SSH keys when pushing commits - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) diff --git a/app/models/user.rb b/app/models/user.rb index 7f5a8562907..508efd85050 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -279,6 +279,11 @@ class User < ActiveRecord::Base find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i) end + # Returns a user for the given SSH key. + def find_by_ssh_key_id(key_id) + find_by(id: Key.unscoped.select(:user_id).where(id: key_id)) + end + def build_user(attrs = {}) User.new(attrs) end diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb index 3e5d728f3bc..f8809db21aa 100644 --- a/lib/gitlab/identifier.rb +++ b/lib/gitlab/identifier.rb @@ -5,19 +5,61 @@ module Gitlab def identify(identifier, project, newrev) if identifier.blank? # Local push from gitlab - email = project.commit(newrev).author_email rescue nil - User.find_by(email: email) if email - + identify_using_commit(project, newrev) elsif identifier =~ /\Auser-\d+\Z/ # git push over http - user_id = identifier.gsub("user-", "") - User.find_by(id: user_id) - + identify_using_user(identifier) elsif identifier =~ /\Akey-\d+\Z/ # git push over ssh - key_id = identifier.gsub("key-", "") - Key.find_by(id: key_id).try(:user) + identify_using_ssh_key(identifier) + end + end + + # Tries to identify a user based on a commit SHA. + def identify_using_commit(project, ref) + commit = project.commit(ref) + + return if !commit || !commit.author_email + + email = commit.author_email + + identify_with_cache(:email, email) do + User.find_by(email: email) end end + + # Tries to identify a user based on a user identifier (e.g. "user-123"). + def identify_using_user(identifier) + user_id = identifier.gsub("user-", "") + + identify_with_cache(:user, user_id) do + User.find_by(id: user_id) + end + end + + # Tries to identify a user based on an SSH key identifier (e.g. "key-123"). + def identify_using_ssh_key(identifier) + key_id = identifier.gsub("key-", "") + + identify_with_cache(:ssh_key, key_id) do + User.find_by_ssh_key_id(key_id) + end + end + + def identify_with_cache(category, key) + if identification_cache[category].key?(key) + identification_cache[category][key] + else + identification_cache[category][key] = yield + end + end + + def identification_cache + @identification_cache ||= { + email: {}, + user: {}, + ssh_key: {} + } + end end end diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb new file mode 100644 index 00000000000..47d6f1007d1 --- /dev/null +++ b/spec/lib/gitlab/identifier_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' + +describe Gitlab::Identifier do + let(:identifier) do + Class.new { include Gitlab::Identifier }.new + end + + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + let(:key) { create(:key, user: user) } + + describe '#identify' do + context 'without an identifier' do + it 'identifies the user using a commit' do + expect(identifier).to receive(:identify_using_commit). + with(project, '123') + + identifier.identify('', project, '123') + end + end + + context 'with a user identifier' do + it 'identifies the user using a user ID' do + expect(identifier).to receive(:identify_using_user). + with("user-#{user.id}") + + identifier.identify("user-#{user.id}", project, '123') + end + end + + context 'with an SSH key identifier' do + it 'identifies the user using an SSH key ID' do + expect(identifier).to receive(:identify_using_ssh_key). + with("key-#{key.id}") + + identifier.identify("key-#{key.id}", project, '123') + end + end + end + + describe '#identify_using_commit' do + it "returns the User for an existing commit author's Email address" do + commit = double(:commit, author_email: user.email) + + expect(project).to receive(:commit).with('123').and_return(commit) + + expect(identifier.identify_using_commit(project, '123')).to eq(user) + end + + it 'returns nil when no user could be found' do + allow(project).to receive(:commit).with('123').and_return(nil) + + expect(identifier.identify_using_commit(project, '123')).to be_nil + end + + it 'returns nil when the commit does not have an author Email' do + commit = double(:commit, author_email: nil) + + expect(project).to receive(:commit).with('123').and_return(commit) + + expect(identifier.identify_using_commit(project, '123')).to be_nil + end + + it 'caches the found users per Email' do + commit = double(:commit, author_email: user.email) + + expect(project).to receive(:commit).with('123').twice.and_return(commit) + expect(User).to receive(:find_by).once.and_call_original + + 2.times do + expect(identifier.identify_using_commit(project, '123')).to eq(user) + end + end + end + + describe '#identify_using_user' do + it 'returns the User for an existing ID in the identifier' do + found = identifier.identify_using_user("user-#{user.id}") + + expect(found).to eq(user) + end + + it 'returns nil for a non existing user ID' do + found = identifier.identify_using_user('user--1') + + expect(found).to be_nil + end + + it 'caches the found users per ID' do + expect(User).to receive(:find_by).once.and_call_original + + 2.times do + found = identifier.identify_using_user("user-#{user.id}") + + expect(found).to eq(user) + end + end + end + + describe '#identify_using_ssh_key' do + it 'returns the User for an existing SSH key' do + found = identifier.identify_using_ssh_key("key-#{key.id}") + + expect(found).to eq(user) + end + + it 'returns nil for an invalid SSH key' do + found = identifier.identify_using_ssh_key('key--1') + + expect(found).to be_nil + end + + it 'caches the found users per key' do + expect(User).to receive(:find_by_ssh_key_id).once.and_call_original + + 2.times do + found = identifier.identify_using_ssh_key("key-#{key.id}") + + expect(found).to eq(user) + end + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a1770d96f83..65b2896930a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -610,6 +610,23 @@ describe User, models: true do end end + describe '.find_by_ssh_key_id' do + context 'using an existing SSH key ID' do + let(:user) { create(:user) } + let(:key) { create(:key, user: user) } + + it 'returns the corresponding User' do + expect(described_class.find_by_ssh_key_id(key.id)).to eq(user) + end + end + + context 'using an invalid SSH key ID' do + it 'returns nil' do + expect(described_class.find_by_ssh_key_id(-1)).to be_nil + end + end + end + describe '.by_login' do let(:username) { 'John' } let!(:user) { create(:user, username: username) } diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 1d2cf7acddd..ffeaafe654a 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -79,7 +79,9 @@ describe PostReceive do end it "does not run if the author is not in the project" do - allow(Key).to receive(:find_by).with(hash_including(id: anything())) { nil } + allow_any_instance_of(Gitlab::GitPostReceive). + to receive(:identify_using_ssh_key). + and_return(nil) expect(project).not_to receive(:execute_hooks) -- cgit v1.2.1 From 2f01392d227810160ab037231c00da01a9efe62d Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Oct 2016 11:40:44 +0100 Subject: Updates Last Deployment column in environmnets list according to design --- app/assets/stylesheets/pages/environments.scss | 5 +++++ app/views/projects/environments/_environment.html.haml | 4 ++-- app/views/projects/environments/index.html.haml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index d01c60ee6ab..51bf0982e6c 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -1,4 +1,9 @@ .environments { + .deployment-column { + .avatar { + float: none; + } + } .commit-title { margin: 0; diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 73a88dc38ba..915e7fa4514 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -4,10 +4,10 @@ %td = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment) - %td + %td.deployment-column - if last_deployment + %span ##{last_deployment.iid} by = user_avatar(user: last_deployment.user, size: 20) - %span ##{last_deployment.iid} %td - if last_deployment diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 8da9221e08a..b3eb5b0011a 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -26,7 +26,7 @@ %table.table.builds.environments %tbody %th Environment - %th Last Deployment ID + %th Last Deployment %th Commit %th %th -- cgit v1.2.1 From 5e1c986b7da3ede00d50990689fbefb58328ca65 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Oct 2016 11:48:07 +0100 Subject: Adds Build column to environments list --- app/assets/stylesheets/pages/environments.scss | 1 + app/views/projects/environments/_environment.html.haml | 5 +++++ app/views/projects/environments/index.html.haml | 1 + 3 files changed, 7 insertions(+) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 51bf0982e6c..bf863632689 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -26,6 +26,7 @@ } } + .build-link, .branch-name { color: $gl-dark-link-color; } diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 915e7fa4514..d35e619678c 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -9,6 +9,11 @@ %span ##{last_deployment.iid} by = user_avatar(user: last_deployment.user, size: 20) + %td + - if last_deployment && last_deployment.deployable + = link_to [@project.namespace.becomes(Namespace), @project, last_deployment.deployable], class: 'build-link' do + = "#{last_deployment.deployable.name} (##{last_deployment.deployable.id})" + %td - if last_deployment = render 'projects/deployments/commit', deployment: last_deployment diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index b3eb5b0011a..2d5c0c9d54f 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -27,6 +27,7 @@ %tbody %th Environment %th Last Deployment + %th Build %th Commit %th %th -- cgit v1.2.1 From 2d41c3f5a04ae9879749bb8f0d903c28212f4298 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 5 Oct 2016 12:48:18 +0200 Subject: Restore inexplicably removed code from requestFileSuccess. (also clean up a few global refs) --- app/assets/javascripts/blob/template_selector.js.es6 | 11 +++++++++-- app/assets/javascripts/dispatcher.js | 4 ++-- .../javascripts/templates/issuable_template_selector.js.es6 | 2 +- .../javascripts/templates/issuable_template_selectors.js.es6 | 12 ++++++------ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 index ba60b3b2b98..4e309e480b0 100644 --- a/app/assets/javascripts/blob/template_selector.js.es6 +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -68,8 +68,15 @@ // To be implemented on the extending class // e.g. // Api.gitignoreText item.name, @requestFileSuccess.bind(@) - requestFileSuccess(file, skipFocus) { - this.editor.setValue(file.content, 1); + requestFileSuccess(file, { skipFocus, append } = {}) { + const oldValue = this.editor.getValue(); + let newValue = file.content; + + if (append && oldValue.length && oldValue !== newValue) { + newValue = oldValue + '\n\n' + newValue; + } + + this.editor.setValue(newValue, 1); if (!skipFocus) this.editor.focus(); if (this.editor instanceof jQuery) { diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 93183d0720a..ae910dbdcf0 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -59,7 +59,7 @@ shortcut_handler = new ShortcutsNavigation(); new GLForm($('.issue-form')); new IssuableForm($('.issue-form')); - new IssuableTemplateSelectors(); + new gl.IssuableTemplateSelectors(); break; case 'projects:merge_requests:new': case 'projects:merge_requests:edit': @@ -67,7 +67,7 @@ shortcut_handler = new ShortcutsNavigation(); new GLForm($('.merge-request-form')); new IssuableForm($('.merge-request-form')); - new IssuableTemplateSelectors(); + new gl.IssuableTemplateSelectors(); break; case 'projects:tags:new': new ZenMode(); diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index f2a55f9caa0..2ecf3b18975 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -50,4 +50,4 @@ } global.IssuableTemplateSelector = IssuableTemplateSelector; -})(window); +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 b/app/assets/javascripts/templates/issuable_template_selectors.js.es6 index bd8cdde033e..4e8247b89e1 100644 --- a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selectors.js.es6 @@ -1,12 +1,12 @@ ((global) => { class IssuableTemplateSelectors { - constructor(opts = {}) { - this.$dropdowns = opts.$dropdowns || $('.js-issuable-selector'); - this.editor = opts.editor || this.initEditor(); + constructor({ $dropdowns, editor } = {}) { + this.$dropdowns = $dropdowns || $('.js-issuable-selector'); + this.editor = editor || this.initEditor(); this.$dropdowns.each((i, dropdown) => { - let $dropdown = $(dropdown); - new IssuableTemplateSelector({ + const $dropdown = $(dropdown); + new gl.IssuableTemplateSelector({ pattern: /(\.md)/, data: $dropdown.data('data'), wrapper: $dropdown.closest('.js-issuable-selector-wrap'), @@ -26,4 +26,4 @@ } global.IssuableTemplateSelectors = IssuableTemplateSelectors; -})(window); +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 0a1baaa80f017ebf068a0a83d7efda45fbe2ef6c Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 5 Oct 2016 12:13:58 +0100 Subject: Tidy up project list actions --- lib/api/projects.rb | 52 ++++++++++++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 52afbb4eef3..c24e8e8bd9b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -22,14 +22,12 @@ module API # Example Request: # GET /projects get do - @projects = current_user.authorized_projects - @projects = filter_projects(@projects) - @projects = paginate @projects - if params[:simple] - present @projects, with: Entities::BasicProjectDetails, user: current_user - else - present @projects, with: Entities::ProjectWithAccess, user: current_user - end + projects = current_user.authorized_projects + projects = filter_projects(projects) + projects = paginate projects + entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess + + present projects, with: entity, user: current_user end # Get a list of visible projects for authenticated user @@ -37,14 +35,12 @@ module API # Example Request: # GET /projects/visible get '/visible' do - @projects = ProjectsFinder.new.execute(current_user) - @projects = filter_projects(@projects) - @projects = paginate @projects - if params[:simple] - present @projects, with: Entities::BasicProjectDetails, user: current_user - else - present @projects, with: Entities::ProjectWithAccess, user: current_user - end + projects = ProjectsFinder.new.execute(current_user) + projects = filter_projects(projects) + projects = paginate projects + entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess + + present projects, with: entity, user: current_user end # Get an owned projects list for authenticated user @@ -52,10 +48,10 @@ module API # Example Request: # GET /projects/owned get '/owned' do - @projects = current_user.owned_projects - @projects = filter_projects(@projects) - @projects = paginate @projects - present @projects, with: Entities::ProjectWithAccess, user: current_user + projects = current_user.owned_projects + projects = filter_projects(projects) + projects = paginate projects + present projects, with: Entities::ProjectWithAccess, user: current_user end # Gets starred project for the authenticated user @@ -63,10 +59,10 @@ module API # Example Request: # GET /projects/starred get '/starred' do - @projects = current_user.viewable_starred_projects - @projects = filter_projects(@projects) - @projects = paginate @projects - present @projects, with: Entities::Project, user: current_user + projects = current_user.viewable_starred_projects + projects = filter_projects(projects) + projects = paginate projects + present projects, with: Entities::Project, user: current_user end # Get all projects for admin user @@ -75,10 +71,10 @@ module API # GET /projects/all get '/all' do authenticated_as_admin! - @projects = Project.all - @projects = filter_projects(@projects) - @projects = paginate @projects - present @projects, with: Entities::ProjectWithAccess, user: current_user + projects = Project.all + projects = filter_projects(projects) + projects = paginate projects + present projects, with: Entities::ProjectWithAccess, user: current_user end # Get a single project -- cgit v1.2.1 From c7afe1e5790c71965366e26ed38234654ef81a60 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Oct 2016 12:17:34 +0100 Subject: Adds reference to the merge request in the commit column --- app/assets/stylesheets/pages/environments.scss | 6 ++++++ app/views/projects/deployments/_commit.html.haml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index bf863632689..0a7d3235d85 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -39,4 +39,10 @@ width: 20px; text-align: center; } + + .branch-commit { + .commit-id { + margin-right: 0px; + } + } } diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml index 28813babd7b..f71754597b6 100644 --- a/app/views/projects/deployments/_commit.html.haml +++ b/app/views/projects/deployments/_commit.html.haml @@ -1,3 +1,5 @@ +- merge_request = deployment.deployable.try(:merge_request) + %div.branch-commit - if deployment.ref .icon-container @@ -6,6 +8,10 @@ .icon-container = custom_icon("icon_commit") = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace" + + - if merge_request + %span in + = link_to_gfm merge_request.to_reference, merge_request_path(merge_request) %p.commit-title %span -- cgit v1.2.1 From 34efdef19e6c1bcbe0976e0f55f53bfbb12eaad2 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Oct 2016 13:06:35 +0100 Subject: Adds external url Changes build column in environment details --- app/assets/stylesheets/pages/environments.scss | 14 ++++++++++++++ app/views/projects/deployments/_actions.haml | 6 ++++++ app/views/projects/deployments/_deployment.html.haml | 7 ++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 0a7d3235d85..d8a28b5860d 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -14,6 +14,7 @@ width: 12px; } + .external-url, .dropdown-new { color: $table-text-gray; } @@ -30,6 +31,19 @@ .branch-name { color: $gl-dark-link-color; } + + .deployment { + .build-column { + + .build-link { + color: $gl-dark-link-color; + } + + .avatar { + float: none; + } + } + } } .table.builds.environments { diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 16d134eb6b6..9fd97a7d753 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -1,5 +1,11 @@ - if can?(current_user, :create_deployment, deployment) && deployment.deployable .pull-right + + - external_url = deployment.deployable.try(:external_url) + - if external_url + = link_to external_url, target: '_blank', class: 'btn external-url' do + = icon('external-link') + - actions = deployment.manual_actions - if actions.present? .inline diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index cd95841ca5a..2edbf7f5082 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -5,11 +5,12 @@ %td = render 'projects/deployments/commit', deployment: deployment - %td + %td.build-column - if deployment.deployable - = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do - = user_avatar(user: deployment.user, size: 20) + = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do = "#{deployment.deployable.name} (##{deployment.deployable.id})" + by + = user_avatar(user: deployment.user, size: 20) %td #{time_ago_with_tooltip(deployment.created_at)} -- cgit v1.2.1 From f223a41660f1a629614d3d559468b1a5488b96d8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Oct 2016 15:21:27 +0300 Subject: Split routes on multiple files Signed-off-by: Dmitriy Zaporozhets --- config/routes.rb | 819 +-------------------------------------------- config/routes/admin.rb | 102 ++++++ config/routes/dashboard.rb | 27 ++ config/routes/explore.rb | 16 + config/routes/group.rb | 18 + config/routes/import.rb | 42 +++ config/routes/profile.rb | 43 +++ config/routes/project.rb | 464 +++++++++++++++++++++++++ config/routes/uploads.rb | 21 ++ config/routes/user.rb | 26 ++ 10 files changed, 775 insertions(+), 803 deletions(-) create mode 100644 config/routes/admin.rb create mode 100644 config/routes/dashboard.rb create mode 100644 config/routes/explore.rb create mode 100644 config/routes/group.rb create mode 100644 config/routes/import.rb create mode 100644 config/routes/profile.rb create mode 100644 config/routes/project.rb create mode 100644 config/routes/uploads.rb create mode 100644 config/routes/user.rb diff --git a/config/routes.rb b/config/routes.rb index ba3864b92be..c364a963fb7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,6 +2,12 @@ require 'sidekiq/web' require 'sidekiq/cron/web' require 'api/api' +class ActionDispatch::Routing::Mapper + def draw(routes_name) + instance_eval(File.read(Rails.root.join("config/routes/#{routes_name}.rb"))) + end +end + Rails.application.routes.draw do if Gitlab::Sherlock.enabled? namespace :sherlock do @@ -94,14 +100,10 @@ Rails.application.routes.draw do get 'help/ui' => 'help#ui' get 'help/*path' => 'help#show', as: :help_page - # # Koding route - # get 'koding' => 'koding#index' - # # Global snippets - # resources :snippets, concerns: :awardable do member do get 'raw' @@ -111,9 +113,7 @@ Rails.application.routes.draw do get '/s/:username', to: redirect('/u/%{username}/snippets'), constraints: { username: /[a-zA-Z.0-9_\-]+(? 'explore/projects#index' - get 'public/projects' => 'explore/projects#index' - - # - # Admin Area - # - namespace :admin do - resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do - resources :keys, only: [:show, :destroy] - resources :identities, except: [:show] - - member do - get :projects - get :keys - get :groups - put :block - put :unblock - put :unlock - put :confirm - post :impersonate - patch :disable_two_factor - delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' - end - end - - resource :impersonation, only: :destroy - - resources :abuse_reports, only: [:index, :destroy] - resources :spam_logs, only: [:index, :destroy] do - member do - post :mark_as_ham - end - end - - resources :applications - - resources :groups, constraints: { id: /[^\/]+/ } do - member do - put :members_update - end - end - - resources :deploy_keys, only: [:index, :new, :create, :destroy] - - resources :hooks, only: [:index, :create, :destroy] do - get :test - end - - resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do - post :preview, on: :collection - end - - resource :logs, only: [:show] - resource :health_check, controller: 'health_check', only: [:show] - resource :background_jobs, controller: 'background_jobs', only: [:show] - resource :system_info, controller: 'system_info', only: [:show] - resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } - - resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - root to: 'projects#index', as: :projects - - resources(:projects, - path: '/', - constraints: { id: /[a-zA-Z.0-9_\-]+/ }, - only: [:index, :show]) do - root to: 'projects#show' - - member do - put :transfer - post :repository_check - end - - resources :runner_projects, only: [:create, :destroy] - end - end - - resource :appearances, only: [:show, :create, :update], path: 'appearance' do - member do - get :preview - delete :logo - delete :header_logos - end - end - - resource :application_settings, only: [:show, :update] do - resources :services, only: [:index, :edit, :update] - put :reset_runners_token - put :reset_health_check_token - put :clear_repository_check_states - end - - resources :labels - - resources :runners, only: [:index, :show, :update, :destroy] do - member do - get :resume - get :pause - end - end - - resources :builds, only: :index do - collection do - post :cancel_all - end - end - - root to: 'dashboard#index' - end - - # - # Profile Area - # - resource :profile, only: [:show, :update] do - member do - get :audit_log - get :applications, to: 'oauth/applications#index' - - put :reset_private_token - put :update_username - end - - scope module: :profiles do - resource :account, only: [:show] do - member do - delete :unlink - end - end - resource :notifications, only: [:show, :update] - resource :password, only: [:new, :create, :edit, :update] do - member do - put :reset - end - end - resource :preferences, only: [:show, :update] - resources :keys, only: [:index, :show, :new, :create, :destroy] - resources :emails, only: [:index, :create, :destroy] - resource :avatar, only: [:destroy] - - resources :personal_access_tokens, only: [:index, :create] do - member do - put :revoke - end - end - - resource :two_factor_auth, only: [:show, :create, :destroy] do - member do - post :create_u2f - post :codes - patch :skip - end - end - - resources :u2f_registrations, only: [:destroy] - end - end - - scope(path: 'u/:username', - as: :user, - constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error - get '/users/almost_there' => 'confirmations#almost_there' - end + get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } root to: "root#index" - - # - # Project Area - # - resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template - - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) - end - - scope do - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end - - scope do - get( - '/find_file/*id', - to: 'find_file#show', - constraints: { id: /.+/, format: /html/ }, - as: :find_file - ) - end - - scope do - get( - '/files/*id', - to: 'find_file#list', - constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, - as: :files - ) - end - - scope do - post( - '/create_dir/*id', - to: 'tree#create_dir', - constraints: { id: /.+/ }, - as: 'create_dir' - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/commits/*id', - to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, - as: :commits - ) - end - - resource :avatar, only: [:show, :destroy] - resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do - member do - get :branches - get :builds - get :pipelines - post :cancel_builds - post :retry_builds - post :revert - post :cherry_pick - get :diff_for_path - end - end - - resources :compare, only: [:index, :create] do - collection do - get :diff_for_path - end - end - - get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } - - # Don't use format parameter as file extension (old 3.0.x behavior) - # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments - scope format: false do - resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } - - resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do - member do - get :commits - get :ci - get :languages - end - end - end - - resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get 'raw' - end - end - - WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID - - scope do - # Order matters to give priority to these matches - get '/wikis/git_access', to: 'wikis#git_access' - get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' - post '/wikis', to: 'wikis#create' - - get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID - get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID - - get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID - delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID - put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID - post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' - end - - resource :repository, only: [:create] do - member do - get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } - end - end - - resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do - member do - get :test - end - end - - resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do - member do - put :enable - put :disable - end - end - - resources :forks, only: [:index, :new, :create] - resource :import, only: [:new, :create, :show] - - resources :refs, only: [] do - collection do - get 'switch' - end - - member do - # tree viewer logs - get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } - # Directories with leading dots erroneously get rejected if git - # ref regex used in constraints. Regex verification now done in controller. - get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { - id: /.*/, - path: /.*/ - } - end - end - - resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get :commits - get :diffs - get :conflicts - get :builds - get :pipelines - get :merge_check - post :merge - post :cancel_merge_when_build_succeeds - get :ci_status - post :toggle_subscription - post :remove_wip - get :diff_for_path - post :resolve_conflicts - end - - collection do - get :branch_from - get :branch_to - get :update_branches - get :diff_for_path - post :bulk_update - end - - resources :discussions, only: [], constraints: { id: /\h{40}/ } do - member do - post :resolve - delete :resolve, action: :unresolve - end - end - end - - resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do - resource :release, only: [:edit, :update] - end - - resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :variables, only: [:index, :show, :update, :create, :destroy] - resources :triggers, only: [:index, :create, :destroy] - - resources :pipelines, only: [:index, :new, :create, :show] do - collection do - resource :pipelines_settings, path: 'settings', only: [:show, :update] - end - - member do - post :cancel - post :retry - end - end - - resources :environments - - resource :cycle_analytics, only: [:show] - - resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do - collection do - post :cancel_all - - resources :artifacts, only: [] do - collection do - get :latest_succeeded, - path: '*ref_name_and_path', - format: false - end - end - end - - member do - get :status - post :cancel - post :retry - post :play - post :erase - get :trace - get :raw - end - - resource :artifacts, only: [] do - get :download - get :browse, path: 'browse(/*path)', format: false - get :file, path: 'file/*path', format: false - post :keep - end - end - - resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do - member do - get :test - end - end - - resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } - - resources :milestones, constraints: { id: /\d+/ } do - member do - put :sort_issues - put :sort_merge_requests - end - end - - resources :labels, except: [:show], constraints: { id: /\d+/ } do - collection do - post :generate - post :set_priorities - end - - member do - post :toggle_subscription - delete :remove_priority - end - end - - resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do - member do - post :toggle_subscription - post :mark_as_spam - get :referenced_merge_requests - get :related_branches - get :can_create_branch - end - collection do - post :bulk_update - end - end - - resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do - collection do - delete :leave - - # Used for import team - # from another project - get :import - post :apply_import - end - - member do - post :resend_invite - end - end - - resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } - - resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do - member do - delete :delete_attachment - post :resolve - delete :resolve, action: :unresolve - end - end - - resource :board, only: [:show] do - scope module: :boards do - resources :issues, only: [:update] - - resources :lists, only: [:index, :create, :update, :destroy] do - collection do - post :generate - end - - resources :issues, only: [:index] - end - end - end - - resources :todos, only: [:create] - - resources :uploads, only: [:create] do - collection do - get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } - end - end - - resources :runners, only: [:index, :edit, :update, :destroy, :show] do - member do - get :resume - get :pause - end - - collection do - post :toggle_shared_runners - end - end - - resources :runner_projects, only: [:create, :destroy] - resources :badges, only: [:index] do - collection do - scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do - constraints format: /svg/ do - get :build - get :coverage - end - end - end - end - end - end - end - - # Get all keys of user - get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - - get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } end diff --git a/config/routes/admin.rb b/config/routes/admin.rb new file mode 100644 index 00000000000..5ae985da561 --- /dev/null +++ b/config/routes/admin.rb @@ -0,0 +1,102 @@ +namespace :admin do + resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do + resources :keys, only: [:show, :destroy] + resources :identities, except: [:show] + + member do + get :projects + get :keys + get :groups + put :block + put :unblock + put :unlock + put :confirm + post :impersonate + patch :disable_two_factor + delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' + end + end + + resource :impersonation, only: :destroy + + resources :abuse_reports, only: [:index, :destroy] + resources :spam_logs, only: [:index, :destroy] do + member do + post :mark_as_ham + end + end + + resources :applications + + resources :groups, constraints: { id: /[^\/]+/ } do + member do + put :members_update + end + end + + resources :deploy_keys, only: [:index, :new, :create, :destroy] + + resources :hooks, only: [:index, :create, :destroy] do + get :test + end + + resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do + post :preview, on: :collection + end + + resource :logs, only: [:show] + resource :health_check, controller: 'health_check', only: [:show] + resource :background_jobs, controller: 'background_jobs', only: [:show] + resource :system_info, controller: 'system_info', only: [:show] + resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } + + resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do + root to: 'projects#index', as: :projects + + resources(:projects, + path: '/', + constraints: { id: /[a-zA-Z.0-9_\-]+/ }, + only: [:index, :show]) do + root to: 'projects#show' + + member do + put :transfer + post :repository_check + end + + resources :runner_projects, only: [:create, :destroy] + end + end + + resource :appearances, only: [:show, :create, :update], path: 'appearance' do + member do + get :preview + delete :logo + delete :header_logos + end + end + + resource :application_settings, only: [:show, :update] do + resources :services, only: [:index, :edit, :update] + put :reset_runners_token + put :reset_health_check_token + put :clear_repository_check_states + end + + resources :labels + + resources :runners, only: [:index, :show, :update, :destroy] do + member do + get :resume + get :pause + end + end + + resources :builds, only: :index do + collection do + post :cancel_all + end + end + + root to: 'dashboard#index' +end diff --git a/config/routes/dashboard.rb b/config/routes/dashboard.rb new file mode 100644 index 00000000000..fb20c63bc63 --- /dev/null +++ b/config/routes/dashboard.rb @@ -0,0 +1,27 @@ +resource :dashboard, controller: 'dashboard', only: [] do + get :issues + get :merge_requests + get :activity + + scope module: :dashboard do + resources :milestones, only: [:index, :show] + resources :labels, only: [:index] + + resources :groups, only: [:index] + resources :snippets, only: [:index] + + resources :todos, only: [:index, :destroy] do + collection do + delete :destroy_all + end + end + + resources :projects, only: [:index] do + collection do + get :starred + end + end + end + + root to: "dashboard/projects#index" +end diff --git a/config/routes/explore.rb b/config/routes/explore.rb new file mode 100644 index 00000000000..42ec5e8abec --- /dev/null +++ b/config/routes/explore.rb @@ -0,0 +1,16 @@ +namespace :explore do + resources :projects, only: [:index] do + collection do + get :trending + get :starred + end + end + + resources :groups, only: [:index] + resources :snippets, only: [:index] + root to: 'projects#trending' +end + +# Compatibility with old routing +get 'public' => 'explore/projects#index' +get 'public/projects' => 'explore/projects#index' diff --git a/config/routes/group.rb b/config/routes/group.rb new file mode 100644 index 00000000000..5b3e25d5e3d --- /dev/null +++ b/config/routes/group.rb @@ -0,0 +1,18 @@ +resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template + + scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + to: 'blob#destroy', + constraints: { id: /.+/, format: false } + ) + put( + '/blob/*id', + to: 'blob#update', + constraints: { id: /.+/, format: false } + ) + post( + '/blob/*id', + to: 'blob#create', + constraints: { id: /.+/, format: false } + ) + end + + scope do + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) + end + + scope do + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) + end + + scope do + get( + '/find_file/*id', + to: 'find_file#show', + constraints: { id: /.+/, format: /html/ }, + as: :find_file + ) + end + + scope do + get( + '/files/*id', + to: 'find_file#list', + constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, + as: :files + ) + end + + scope do + post( + '/create_dir/*id', + to: 'tree#create_dir', + constraints: { id: /.+/ }, + as: 'create_dir' + ) + end + + scope do + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) + end + + scope do + get( + '/commits/*id', + to: 'commits#show', + constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, + as: :commits + ) + end + + resource :avatar, only: [:show, :destroy] + resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do + member do + get :branches + get :builds + get :pipelines + post :cancel_builds + post :retry_builds + post :revert + post :cherry_pick + get :diff_for_path + end + end + + resources :compare, only: [:index, :create] do + collection do + get :diff_for_path + end + end + + get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } + + # Don't use format parameter as file extension (old 3.0.x behavior) + # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments + scope format: false do + resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } + + resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do + member do + get :commits + get :ci + get :languages + end + end + end + + resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do + member do + get 'raw' + end + end + + WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID + + scope do + # Order matters to give priority to these matches + get '/wikis/git_access', to: 'wikis#git_access' + get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' + post '/wikis', to: 'wikis#create' + + get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID + get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID + + get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID + delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID + put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID + post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' + end + + resource :repository, only: [:create] do + member do + get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } + end + end + + resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do + member do + get :test + end + end + + resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do + member do + put :enable + put :disable + end + end + + resources :forks, only: [:index, :new, :create] + resource :import, only: [:new, :create, :show] + + resources :refs, only: [] do + collection do + get 'switch' + end + + member do + # tree viewer logs + get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } + # Directories with leading dots erroneously get rejected if git + # ref regex used in constraints. Regex verification now done in controller. + get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { + id: /.*/, + path: /.*/ + } + end + end + + resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do + member do + get :commits + get :diffs + get :conflicts + get :builds + get :pipelines + get :merge_check + post :merge + post :cancel_merge_when_build_succeeds + get :ci_status + post :toggle_subscription + post :remove_wip + get :diff_for_path + post :resolve_conflicts + end + + collection do + get :branch_from + get :branch_to + get :update_branches + get :diff_for_path + post :bulk_update + end + + resources :discussions, only: [], constraints: { id: /\h{40}/ } do + member do + post :resolve + delete :resolve, action: :unresolve + end + end + end + + resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do + resource :release, only: [:edit, :update] + end + + resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :variables, only: [:index, :show, :update, :create, :destroy] + resources :triggers, only: [:index, :create, :destroy] + + resources :pipelines, only: [:index, :new, :create, :show] do + collection do + resource :pipelines_settings, path: 'settings', only: [:show, :update] + end + + member do + post :cancel + post :retry + end + end + + resources :environments + + resource :cycle_analytics, only: [:show] + + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do + collection do + post :cancel_all + + resources :artifacts, only: [] do + collection do + get :latest_succeeded, + path: '*ref_name_and_path', + format: false + end + end + end + + member do + get :status + post :cancel + post :retry + post :play + post :erase + get :trace + get :raw + end + + resource :artifacts, only: [] do + get :download + get :browse, path: 'browse(/*path)', format: false + get :file, path: 'file/*path', format: false + post :keep + end + end + + resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do + member do + get :test + end + end + + resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } + + resources :milestones, constraints: { id: /\d+/ } do + member do + put :sort_issues + put :sort_merge_requests + end + end + + resources :labels, except: [:show], constraints: { id: /\d+/ } do + collection do + post :generate + post :set_priorities + end + + member do + post :toggle_subscription + delete :remove_priority + end + end + + resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do + member do + post :toggle_subscription + post :mark_as_spam + get :referenced_merge_requests + get :related_branches + get :can_create_branch + end + collection do + post :bulk_update + end + end + + resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do + collection do + delete :leave + + # Used for import team + # from another project + get :import + post :apply_import + end + + member do + post :resend_invite + end + end + + resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } + + resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do + member do + delete :delete_attachment + post :resolve + delete :resolve, action: :unresolve + end + end + + resource :board, only: [:show] do + scope module: :boards do + resources :issues, only: [:update] + + resources :lists, only: [:index, :create, :update, :destroy] do + collection do + post :generate + end + + resources :issues, only: [:index] + end + end + end + + resources :todos, only: [:create] + + resources :uploads, only: [:create] do + collection do + get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } + end + end + + resources :runners, only: [:index, :edit, :update, :destroy, :show] do + member do + get :resume + get :pause + end + + collection do + post :toggle_shared_runners + end + end + + resources :runner_projects, only: [:create, :destroy] + resources :badges, only: [:index] do + collection do + scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do + constraints format: /svg/ do + get :build + get :coverage + end + end + end + end + end + end +end diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb new file mode 100644 index 00000000000..2b22148a134 --- /dev/null +++ b/config/routes/uploads.rb @@ -0,0 +1,21 @@ +scope path: :uploads do + # Note attachments and User/Group/Project avatars + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } + + # Appearance + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } + + # Project markdown uploads + get ":namespace_id/:project_id/:secret/:filename", + to: "projects/uploads#show", + constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } +end + +# Redirect old note attachments path to new uploads path. +get "files/note/:id/:filename", + to: redirect("uploads/note/attachment/%{id}/%{filename}"), + constraints: { filename: /[^\/]+/ } diff --git a/config/routes/user.rb b/config/routes/user.rb new file mode 100644 index 00000000000..b6a6a07e3c4 --- /dev/null +++ b/config/routes/user.rb @@ -0,0 +1,26 @@ +scope(path: 'u/:username', + as: :user, + constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error + get '/users/almost_there' => 'confirmations#almost_there' +end + +# Get all keys of user +get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } -- cgit v1.2.1 From 66c7f5cb46d446a1c6c89ba888efd9e5f74f876d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 5 Oct 2016 14:31:33 +0200 Subject: fix empty import URL errors --- app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 507228606df..4beec36242d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -494,7 +494,7 @@ class Project < ActiveRecord::Base end def import_url - if import_data && super + if import_data && super.present? import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials) import_url.full_url else -- cgit v1.2.1 From b3151828e74346f54b20711593829191b1a7a829 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Oct 2016 16:05:26 +0300 Subject: Put user keys routing back below project routing Signed-off-by: Dmitriy Zaporozhets --- config/routes.rb | 3 +++ config/routes/user.rb | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index c364a963fb7..75bc551feb5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -145,5 +145,8 @@ Rails.application.routes.draw do get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } + # Get all keys of user + get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } + root to: "root#index" end diff --git a/config/routes/user.rb b/config/routes/user.rb index b6a6a07e3c4..bbb30cedd4d 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -21,6 +21,3 @@ devise_scope :user do get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error get '/users/almost_there' => 'confirmations#almost_there' end - -# Get all keys of user -get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } -- cgit v1.2.1 From 485a2a7af4ddeb861861c0669be8158ff54d90c9 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 5 Oct 2016 15:03:08 +0200 Subject: Make flash-alert background transparent to match flash-notice. --- CHANGELOG | 1 + app/assets/stylesheets/framework/flash.scss | 9 ++------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d424fbe43b8..da32b2389aa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -33,6 +33,7 @@ v 8.13.0 (unreleased) - Only update issuable labels if they have been changed - Take filters in account in issuable counters. !6496 - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) + - Prevent flash alert text from being obscured when container is fluid - Append issue template to existing description !6149 (Joseph Frazier) - Revoke button in Applications Settings underlines on hover. - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 3ac1678dd05..a55dcf4a699 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -21,7 +21,8 @@ .flash-notice, .flash-alert { border-radius: $border-radius-default; - .container-fluid.container-limited.flash-text { + .container-fluid, + .container-fluid.container-limited { background: transparent; } } @@ -35,12 +36,6 @@ } } -.content-wrapper { - .flash-notice .container-fluid { - background-color: transparent; - } -} - @media (max-width: $screen-md-min) { ul.notes { .flash-container.timeline-content { -- cgit v1.2.1 From a0eaff14124b829ccc02df951bd7cb7d3abb7708 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 14:37:03 +0100 Subject: Updated Ruby variable name Fixed Ruby code based on review feedback --- app/controllers/projects/project_members_controller.rb | 8 +++----- app/views/projects/project_members/_groups.html.haml | 4 ++-- app/views/projects/project_members/index.html.haml | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 870dc8abbd4..b2c8656d124 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -5,7 +5,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index - @groups = @project.project_group_links + @group_links = @project.project_group_links @project_members = @project.project_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) @@ -14,9 +14,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController users = @project.users.search(params[:search]).to_a @project_members = @project_members.where(user_id: users) - group_ids = @groups.pluck(:group_id) - group_ids = Group.where(id: group_ids).search(params[:search]).to_a - @groups = @project.project_group_links.where(group_id: group_ids) + @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) end @project_members = @project_members.order(access_level: :desc).page(params[:page]) @@ -40,7 +38,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController groups.each do |group| next unless can?(current_user, :read_group, group) - + project.project_group_links.create( group: group, group_access: params[:access_level], diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index 340e4cd06b8..11f896006da 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -2,8 +2,8 @@ .panel-heading Groups with access to %strong #{@project.name} - %span.badge= groups.size + %span.badge= group_links.size %ul.content-list - - @groups.each do |group_link| + - group_links.each do |group_link| - group = group_link.group = render 'shared/members/group', group_link: group_link, group: group diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 24e5a8e4015..f1461444241 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -21,8 +21,8 @@ = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } = icon("search") - - if @groups.size > 0 - = render 'groups', groups: @groups + - if @group_links.size > 0 + = render 'groups', group_links: @group_links = render 'team', members: @project_members = paginate @project_members, theme: "gitlab" -- cgit v1.2.1 From a7352c35fcefeba7246801c6e179e389945d3c2a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Oct 2016 16:40:11 +0300 Subject: Put namespaces#show route below user ssh keys route Signed-off-by: Dmitriy Zaporozhets --- config/routes.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 75bc551feb5..525953449cb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -143,10 +143,10 @@ Rails.application.routes.draw do draw :user draw :project - get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } - # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } + get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } + root to: "root#index" end -- cgit v1.2.1 From a74c1243df63208095c39265c3f27fcbe86ae763 Mon Sep 17 00:00:00 2001 From: Jeroen Jacobs Date: Mon, 27 Jun 2016 14:35:30 +0200 Subject: Don't include archived projects when creating group milestones --- CHANGELOG | 1 + app/views/groups/milestones/new.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c3ed6011216..2d92d03de12 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -54,6 +54,7 @@ v 8.13.0 (unreleased) - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Don't include archived projects when creating group milestones (!4940) v 8.12.4 (unreleased) - Fix type mismatch bug when closing Jira issue diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index ca6c4326d1c..23d438b2aa1 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -33,8 +33,8 @@ .form-group = f.label :projects, "Projects", class: "control-label" .col-sm-10 - = f.collection_select :project_ids, @group.projects, :id, :name, - { selected: @group.projects.map(&:id) }, multiple: true, class: 'select2' + = f.collection_select :project_ids, @group.projects.non_archived, :id, :name, + { selected: @group.projects.non_archived.pluck(:id) }, multiple: true, class: 'select2' .col-md-6 .form-group -- cgit v1.2.1 From 69bb6976f271d8f40baeaef904d5869112f778d5 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 5 Oct 2016 16:25:55 +0200 Subject: added 245px max height and overflow scroll-y to .grouped-pipeline-dropdown --- app/assets/stylesheets/pages/pipelines.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index b2662b812b7..68fc6da6c1b 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -82,7 +82,7 @@ } .branch-commit { - + .branch-name { font-weight: bold; max-width: 150px; @@ -390,6 +390,8 @@ left: auto; right: -214px; top: -9px; + max-height: 245px; + overflow-y: scroll; a:hover { .ci-status-text { -- cgit v1.2.1 From e234fe96109b753574f16c69f20cc93efd19b981 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Wed, 5 Oct 2016 16:32:28 +0200 Subject: Grouped pipeline dropdown is a scrollable container added to CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c3ed6011216..81875cd194e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -54,6 +54,7 @@ v 8.13.0 (unreleased) - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Grouped pipeline dropdown is a scrollable container v 8.12.4 (unreleased) - Fix type mismatch bug when closing Jira issue -- cgit v1.2.1 From 154253cab55491d54dfe264fa946acb9c399398a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 4 Oct 2016 14:09:54 +0200 Subject: Refactor TrendingProjectsFinder to support caching == Public Projects This finder class now _only_ returns public projects. Previously this finder would also return private and internal projects. Including these projects makes caching data much harder and less efficient. Meanwhile including this data isn't very useful as very few users would be interested in seeing projects they have access to as trending. That is, the feature is more useful when you want to see what _other_ popular projects there are. == Caching The data returned by TrendingProjectsFinder is now cached for a day based on the number of months the data should be restricted to. The cache is not flushed explicitly, instead it's rebuilt whenever it expires. == Timings To measure the impact I changed the finder code to use the last 24 months instead of the last month. I then executed and measured 10 requests to the explore page. On the current "master" branch (commit 88fa5916ffa0aea491cd339272ed7437c3f52dc7) this would take an average of 2.43 seconds. Using the changes of this commit this was reduced to around 1.7 seconds. Fixes gitlab-org/gitlab-ce#22164 --- CHANGELOG | 1 + app/controllers/explore/projects_controller.rb | 2 +- app/finders/trending_projects_finder.rb | 13 +++++-- spec/finders/trending_projects_finder_spec.rb | 53 +++++++++++++++----------- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a47ec511452..11141b38a0e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.13.0 (unreleased) - Take filters in account in issuable counters. !6496 - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) - Append issue template to existing description !6149 (Joseph Frazier) + - Trending projects now only show public projects and the list of projects is cached for a day - Revoke button in Applications Settings underlines on hover. - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - Fix Long commit messages overflow viewport in file tree diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 88a0c18180b..38e5943eb76 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -21,7 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController end def trending - @projects = TrendingProjectsFinder.new.execute(current_user) + @projects = TrendingProjectsFinder.new.execute @projects = filter_projects(@projects) @projects = @projects.page(params[:page]) diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb index 81a12403801..c1e434d9926 100644 --- a/app/finders/trending_projects_finder.rb +++ b/app/finders/trending_projects_finder.rb @@ -1,11 +1,16 @@ +# Finder for retrieving public trending projects in a given time range. class TrendingProjectsFinder - def execute(current_user, start_date = 1.month.ago) - projects_for(current_user).trending(start_date) + # current_user - The currently logged in User, if any. + # last_months - The number of months to limit the trending data to. + def execute(months_limit = 1) + Rails.cache.fetch(cache_key_for(months_limit), expires_in: 1.day) do + Project.public_only.trending(months_limit.months.ago) + end end private - def projects_for(current_user) - ProjectsFinder.new.execute(current_user) + def cache_key_for(months) + "trending_projects/#{months}" end end diff --git a/spec/finders/trending_projects_finder_spec.rb b/spec/finders/trending_projects_finder_spec.rb index a49cbfd5160..cfe15b9defa 100644 --- a/spec/finders/trending_projects_finder_spec.rb +++ b/spec/finders/trending_projects_finder_spec.rb @@ -1,39 +1,48 @@ require 'spec_helper' describe TrendingProjectsFinder do - let(:user) { build(:user) } + let(:user) { create(:user) } + let(:public_project1) { create(:empty_project, :public) } + let(:public_project2) { create(:empty_project, :public) } + let(:private_project) { create(:empty_project, :private) } + let(:internal_project) { create(:empty_project, :internal) } + + before do + 3.times do + create(:note_on_commit, project: public_project1) + end - describe '#execute' do - describe 'without an explicit start date' do - subject { described_class.new } + 2.times do + create(:note_on_commit, project: public_project2, created_at: 5.weeks.ago) + end - it 'returns the trending projects' do - relation = double(:ar_relation) + create(:note_on_commit, project: private_project) + create(:note_on_commit, project: internal_project) + end - allow(subject).to receive(:projects_for) - .with(user) - .and_return(relation) + describe '#execute', caching: true do + context 'without an explicit time range' do + it 'returns public trending projects' do + projects = described_class.new.execute - allow(relation).to receive(:trending) - .with(an_instance_of(ActiveSupport::TimeWithZone)) + expect(projects).to eq([public_project1]) end end - describe 'with an explicit start date' do - let(:date) { 2.months.ago } + context 'with an explicit time range' do + it 'returns public trending projects' do + projects = described_class.new.execute(2) - subject { described_class.new } + expect(projects).to eq([public_project1, public_project2]) + end + end - it 'returns the trending projects' do - relation = double(:ar_relation) + it 'caches the list of projects' do + projects = described_class.new - allow(subject).to receive(:projects_for) - .with(user) - .and_return(relation) + expect(Project).to receive(:trending).once - allow(relation).to receive(:trending) - .with(date) - end + 2.times { projects.execute } end end end -- cgit v1.2.1 From ea2f9a6083b584a2e5050628ff17bed3c0576222 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 16:02:52 +0100 Subject: Correctly parse the date of artifact expiring Previously it was just replacing dashes which would remove the timezone offset causing the date to be invalid Closes #19600 --- app/assets/javascripts/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 78d21c0552a..f336bfc36d6 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -146,7 +146,7 @@ $date = $('.js-artifacts-remove'); if ($date.length) { date = $date.text(); - return $date.text($.timefor(new Date(date.replace(/-/g, '/')), ' ')); + return $date.text($.timefor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' ')); } }; -- cgit v1.2.1 From b9e259ddedd31eb600b42d414891e533134ad315 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 5 Oct 2016 18:43:47 +0300 Subject: Cleanup config/routes.rb even further by extracting more routes into separate files Signed-off-by: Dmitriy Zaporozhets --- config/routes.rb | 73 +++++--------------------------------------- config/routes/api.rb | 2 ++ config/routes/ci.rb | 15 +++++++++ config/routes/development.rb | 13 ++++++++ config/routes/help.rb | 4 +++ config/routes/sherlock.rb | 12 ++++++++ config/routes/sidekiq.rb | 4 +++ config/routes/snippets.rb | 8 +++++ 8 files changed, 65 insertions(+), 66 deletions(-) create mode 100644 config/routes/api.rb create mode 100644 config/routes/ci.rb create mode 100644 config/routes/development.rb create mode 100644 config/routes/help.rb create mode 100644 config/routes/sherlock.rb create mode 100644 config/routes/sidekiq.rb create mode 100644 config/routes/snippets.rb diff --git a/config/routes.rb b/config/routes.rb index 525953449cb..bf7c5b76128 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,33 +9,6 @@ class ActionDispatch::Routing::Mapper end Rails.application.routes.draw do - if Gitlab::Sherlock.enabled? - namespace :sherlock do - resources :transactions, only: [:index, :show] do - resources :queries, only: [:show] - resources :file_samples, only: [:show] - - collection do - delete :destroy_all - end - end - end - end - - if Rails.env.development? - # Make the built-in Rails routes available in development, otherwise they'd - # get swallowed by the `namespace/project` route matcher below. - # - # See https://git.io/va79N - get '/rails/mailers' => 'rails/mailers#index' - get '/rails/mailers/:path' => 'rails/mailers#preview' - get '/rails/info/properties' => 'rails/info#properties' - get '/rails/info/routes' => 'rails/info#routes' - get '/rails/info' => 'rails/info#index' - - mount LetterOpenerWeb::Engine, at: '/rails/letter_opener' - end - concern :access_requestable do post :request_access, on: :collection post :approve_access_request, on: :member @@ -45,21 +18,9 @@ Rails.application.routes.draw do post :toggle_award_emoji, on: :member end - namespace :ci do - # CI API - Ci::API::API.logger Rails.logger - mount Ci::API::API => '/api' - - resource :lint, only: [:show, :create] - - resources :projects, only: [:index, :show] do - member do - get :status, to: 'projects#badge' - end - end - - root to: 'projects#index' - end + draw :sherlock + draw :development + draw :ci use_doorkeeper do controllers applications: 'oauth/applications', @@ -82,36 +43,16 @@ Rails.application.routes.draw do # JSON Web Token get 'jwt/auth' => 'jwt#auth' - # API - API::API.logger Rails.logger - mount API::API => '/api' - - constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? } - constraints constraint do - mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq - end - # Health check get 'health_check(/:checks)' => 'health_check#index', as: :health_check - # Help - get 'help' => 'help#index' - get 'help/shortcuts' => 'help#shortcuts' - get 'help/ui' => 'help#ui' - get 'help/*path' => 'help#show', as: :help_page - # Koding route get 'koding' => 'koding#index' - # Global snippets - resources :snippets, concerns: :awardable do - member do - get 'raw' - end - end - - get '/s/:username', to: redirect('/u/%{username}/snippets'), - constraints: { username: /[a-zA-Z.0-9_\-]+(? '/api' diff --git a/config/routes/ci.rb b/config/routes/ci.rb new file mode 100644 index 00000000000..47a049d5b20 --- /dev/null +++ b/config/routes/ci.rb @@ -0,0 +1,15 @@ +namespace :ci do + # CI API + Ci::API::API.logger Rails.logger + mount Ci::API::API => '/api' + + resource :lint, only: [:show, :create] + + resources :projects, only: [:index, :show] do + member do + get :status, to: 'projects#badge' + end + end + + root to: 'projects#index' +end diff --git a/config/routes/development.rb b/config/routes/development.rb new file mode 100644 index 00000000000..9b2b47c6a21 --- /dev/null +++ b/config/routes/development.rb @@ -0,0 +1,13 @@ +if Rails.env.development? + # Make the built-in Rails routes available in development, otherwise they'd + # get swallowed by the `namespace/project` route matcher below. + # + # See https://git.io/va79N + get '/rails/mailers' => 'rails/mailers#index' + get '/rails/mailers/:path' => 'rails/mailers#preview' + get '/rails/info/properties' => 'rails/info#properties' + get '/rails/info/routes' => 'rails/info#routes' + get '/rails/info' => 'rails/info#index' + + mount LetterOpenerWeb::Engine, at: '/rails/letter_opener' +end diff --git a/config/routes/help.rb b/config/routes/help.rb new file mode 100644 index 00000000000..d53822da9ec --- /dev/null +++ b/config/routes/help.rb @@ -0,0 +1,4 @@ +get 'help' => 'help#index' +get 'help/shortcuts' => 'help#shortcuts' +get 'help/ui' => 'help#ui' +get 'help/*path' => 'help#show', as: :help_page diff --git a/config/routes/sherlock.rb b/config/routes/sherlock.rb new file mode 100644 index 00000000000..c9969f91c36 --- /dev/null +++ b/config/routes/sherlock.rb @@ -0,0 +1,12 @@ +if Gitlab::Sherlock.enabled? + namespace :sherlock do + resources :transactions, only: [:index, :show] do + resources :queries, only: [:show] + resources :file_samples, only: [:show] + + collection do + delete :destroy_all + end + end + end +end diff --git a/config/routes/sidekiq.rb b/config/routes/sidekiq.rb new file mode 100644 index 00000000000..d3e6bc4c292 --- /dev/null +++ b/config/routes/sidekiq.rb @@ -0,0 +1,4 @@ +constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? } +constraints constraint do + mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq +end diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb new file mode 100644 index 00000000000..1949f215c66 --- /dev/null +++ b/config/routes/snippets.rb @@ -0,0 +1,8 @@ +resources :snippets, concerns: :awardable do + member do + get 'raw' + end +end + +get '/s/:username', to: redirect('/u/%{username}/snippets'), + constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Mon, 3 Oct 2016 00:12:59 -0300 Subject: Added Issue Board API support - Includes documentation and tests --- CHANGELOG | 1 + doc/api/boards.md | 251 +++++++++++++++++++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/boards.rb | 115 ++++++++++++++++++ lib/api/entities.rb | 18 ++- spec/requests/api/boards_spec.rb | 192 ++++++++++++++++++++++++++++++ 6 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 doc/api/boards.md create mode 100644 lib/api/boards.rb create mode 100644 spec/requests/api/boards_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 2e19aa2534c..b6a4d58e0cb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.13.0 (unreleased) - Expose expires_at field when sharing project on API - Fix VueJS template tags being rendered in code comments - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) + - Add Issue Board API support (andrebsguedes) - Allow the Koding integration to be configured through the API - Added soft wrap button to repository file/blob editor - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) diff --git a/doc/api/boards.md b/doc/api/boards.md new file mode 100644 index 00000000000..28681719f43 --- /dev/null +++ b/doc/api/boards.md @@ -0,0 +1,251 @@ +# Boards + +Every API call to boards must be authenticated. + +If a user is not a member of a project and the project is private, a `GET` +request on that project will result to a `404` status code. + +## Project Board + +Lists Issue Boards in the given project. + +``` +GET /projects/:id/boards +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/boards +``` + +Example response: + +```json +[ + { + "id" : 1, + "lists" : [ + { + "id" : 1, + "label" : { + "name" : "Testing", + "color" : "#F0AD4E", + "description" : null + }, + "position" : 1 + }, + { + "id" : 2, + "label" : { + "name" : "Ready", + "color" : "#FF0000", + "description" : null + }, + "position" : 2 + }, + { + "id" : 3, + "label" : { + "name" : "Production", + "color" : "#FF5F00", + "description" : null + }, + "position" : 3 + } + ] + } +] +``` + +## List board lists + +Get a list of the board's lists. +Does not include `backlog` and `done` lists + +``` +GET /projects/:id/boards/:board_id/lists +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `board_id` | integer | yes | The ID of a board | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists +``` + +Example response: + +```json +[ + { + "id" : 1, + "label" : { + "name" : "Testing", + "color" : "#F0AD4E", + "description" : null + }, + "position" : 1 + }, + { + "id" : 2, + "label" : { + "name" : "Ready", + "color" : "#FF0000", + "description" : null + }, + "position" : 2 + }, + { + "id" : 3, + "label" : { + "name" : "Production", + "color" : "#FF5F00", + "description" : null + }, + "position" : 3 + } +] +``` + +## Single board list + +Get a single board list. + +``` +GET /projects/:id/boards/:board_id/lists/:list_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `board_id` | integer | yes | The ID of a board | +| `list_id`| integer | yes | The ID of a board's list | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists/1 +``` + +Example response: + +```json +{ + "id" : 1, + "label" : { + "name" : "Testing", + "color" : "#F0AD4E", + "description" : null + }, + "position" : 1 +} +``` + +## New board list + +Creates a new Issue Board list. + +If the operation is successful, a status code of `200` and the newly-created +list is returned. If an error occurs, an error number and a message explaining +the reason is returned. + +``` +POST /projects/:id/boards/:board_id/lists +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `board_id` | integer | yes | The ID of a board | +| `label_id` | integer | yes | The ID of a label | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists?label_id=5 +``` + +Example response: + +```json +{ + "id" : 1, + "label" : { + "name" : "Testing", + "color" : "#F0AD4E", + "description" : null + }, + "position" : 1 +} +``` + +## Edit board list + +Updates an existing Issue Board list. This call is used to change list position. + +If the operation is successful, a code of `200` and the updated board list is +returned. If an error occurs, an error number and a message explaining the +reason is returned. + +``` +PUT /projects/:id/boards/:board_id/lists/:list_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `board_id` | integer | yes | The ID of a board | +| `list_id` | integer | yes | The ID of a board's list | +| `position` | integer | yes | The position of the list | + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists/1?position=2 +``` + +Example response: + +```json +{ + "id" : 1, + "label" : { + "name" : "Testing", + "color" : "#F0AD4E", + "description" : null + }, + "position" : 1 +} +``` + +## Delete a board list + +Only for admins and project owners. Soft deletes the board list in question. +If the operation is successful, a status code `200` is returned. In case you cannot +destroy this board list, or it is not present, code `404` is given. + +``` +DELETE /projects/:id/boards/:board_id/lists/:list_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of a project | +| `board_id` | integer | yes | The ID of a board | +| `list_id` | integer | yes | The ID of a board's list | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/boards/1/lists/1 +``` +Example response: + +```json +{ + "id" : 1, + "label" : { + "name" : "Testing", + "color" : "#F0AD4E", + "description" : null + }, + "position" : 1 +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index cb47ec8f33f..0bbf73a1b63 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -43,6 +43,7 @@ module API mount ::API::Groups mount ::API::Internal mount ::API::Issues + mount ::API::Boards mount ::API::Keys mount ::API::Labels mount ::API::LicenseTemplates diff --git a/lib/api/boards.rb b/lib/api/boards.rb new file mode 100644 index 00000000000..4d5d144a02e --- /dev/null +++ b/lib/api/boards.rb @@ -0,0 +1,115 @@ +module API + # Boards API + class Boards < Grape::API + before { authenticate! } + + resource :projects do + # Get the project board + get ':id/boards' do + authorize!(:read_board, user_project) + present [user_project.board], with: Entities::Board + end + + segment ':id/boards/:board_id' do + helpers do + def project_board + board = user_project.board + if params[:board_id].to_i == board.id + board + else + not_found!('Board') + end + end + + def board_lists + project_board.lists.destroyable + end + end + + # Get the lists of a project board + # Does not include `backlog` and `done` lists + get '/lists' do + authorize!(:read_board, user_project) + present board_lists, with: Entities::List + end + + # Get a list of a project board + get '/lists/:list_id' do + authorize!(:read_board, user_project) + present board_lists.find(params[:list_id]), with: Entities::List + end + + # Create a new board list + # + # Parameters: + # id (required) - The ID of a project + # label_id (required) - The ID of an existing label + # Example Request: + # POST /projects/:id/boards/:board_id/lists + post '/lists' do + required_attributes! [:label_id] + + unless user_project.labels.exists?(params[:label_id]) + render_api_error!({ error: "Label not found!" }, 400) + end + + authorize!(:admin_list, user_project) + + list = ::Boards::Lists::CreateService.new(user_project, current_user, + { label_id: params[:label_id] }).execute + + if list.valid? + present list, with: Entities::List + else + render_validation_error!(list) + end + end + + # Moves a board list to a new position + # + # Parameters: + # id (required) - The ID of a project + # board_id (required) - The ID of a board + # position (required) - The position of the list + # Example Request: + # PUT /projects/:id/boards/:board_id/lists/:list_id + put '/lists/:list_id' do + list = project_board.lists.movable.find(params[:list_id]) + + authorize!(:admin_list, user_project) + + moved = ::Boards::Lists::MoveService.new(user_project, current_user, + { position: params[:position].to_i }).execute(list) + + if moved + present list, with: Entities::List + else + render_api_error!({ error: "List could not be moved!" }, 400) + end + end + + # Delete a board list + # + # Parameters: + # id (required) - The ID of a project + # board_id (required) - The ID of a board + # list_id (required) - The ID of a board list + # Example Request: + # DELETE /projects/:id/boards/:board_id/lists/:list_id + delete "/lists/:list_id" do + list = board_lists.find_by(id: params[:list_id]) + + authorize!(:admin_list, user_project) + + if list + destroyed_list = ::Boards::Lists::DestroyService.new( + user_project, current_user).execute(list) + present destroyed_list, with: Entities::List + else + not_found!('List') + end + end + end + end + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 04437322ec1..feaa0c213bf 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -432,8 +432,11 @@ module API end end - class Label < Grape::Entity + class LabelBasic < Grape::Entity expose :name, :color, :description + end + + class Label < LabelBasic expose :open_issues_count, :closed_issues_count, :open_merge_requests_count expose :subscribed do |label, options| @@ -441,6 +444,19 @@ module API end end + class List < Grape::Entity + expose :id + expose :label, using: Entities::LabelBasic + expose :position + end + + class Board < Grape::Entity + expose :id + expose :lists, using: Entities::List do |board| + board.lists.destroyable + end + end + class Compare < Grape::Entity expose :commit, using: Entities::RepoCommit do |compare, options| Commit.decorate(compare.commits, nil).last diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb new file mode 100644 index 00000000000..f4b04445c6c --- /dev/null +++ b/spec/requests/api/boards_spec.rb @@ -0,0 +1,192 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:non_member) { create(:user) } + let(:guest) { create(:user) } + let(:admin) { create(:user, :admin) } + let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } + + let!(:dev_label) do + create(:label, title: 'Development', color: '#FFAABB', project: project) + end + + let!(:test_label) do + create(:label, title: 'Testing', color: '#FFAACC', project: project) + end + + let!(:ux_label) do + create(:label, title: 'UX', color: '#FF0000', project: project) + end + + let!(:dev_list) do + create(:list, label: dev_label, position: 1) + end + + let!(:test_list) do + create(:list, label: test_label, position: 2) + end + + let!(:board) do + create(:board, project: project, lists: [dev_list, test_list]) + end + + before do + project.team << [user, :reporter] + project.team << [guest, :guest] + end + + describe "GET /projects/:id/boards" do + let(:base_url) { "/projects/#{project.id}/boards" } + + context "when unauthenticated" do + it "returns authentication error" do + get api(base_url) + + expect(response).to have_http_status(401) + end + end + + context "when authenticated" do + it "returns the project issue board" do + get api(base_url, user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(board.id) + expect(json_response.first['lists']).to be_an Array + expect(json_response.first['lists'].length).to eq(2) + expect(json_response.first['lists'].last).to have_key('position') + end + end + end + + describe "GET /projects/:id/boards/:board_id/lists" do + let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } + + it 'returns issue board lists' do + get api(base_url, user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + expect(json_response.first['label']['name']).to eq(dev_label.title) + end + + it 'returns 404 if board not found' do + get api("/projects/#{project.id}/boards/22343/lists", user) + + expect(response).to have_http_status(404) + end + end + + describe "GET /projects/:id/boards/:board_id/lists/:list_id" do + let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } + + it 'returns a list' do + get api("#{base_url}/#{dev_list.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(dev_list.id) + expect(json_response['label']['name']).to eq(dev_label.title) + expect(json_response['position']).to eq(1) + end + + it 'returns 404 if list not found' do + get api("#{base_url}/5324", user) + + expect(response).to have_http_status(404) + end + end + + describe "POST /projects/:id/board/lists" do + let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } + + it 'creates a new issue board list' do + post api(base_url, user), + label_id: ux_label.id + + expect(response).to have_http_status(201) + expect(json_response['label']['name']).to eq(ux_label.title) + expect(json_response['position']).to eq(3) + end + + it 'returns 400 when creating a new list if label_id is invalid' do + post api(base_url, user), + label_id: 23423 + + expect(response).to have_http_status(400) + end + + it "returns 403 for project members with guest role" do + put api("#{base_url}/#{test_list.id}", guest), + position: 1 + + expect(response).to have_http_status(403) + end + end + + describe "PUT /projects/:id/boards/:board_id/lists/:list_id to update only position" do + let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } + + it "updates a list" do + put api("#{base_url}/#{test_list.id}", user), + position: 1 + + expect(response).to have_http_status(200) + expect(json_response['position']).to eq(1) + end + + it "returns 404 error if list id not found" do + put api("#{base_url}/44444", user), + position: 1 + + expect(response).to have_http_status(404) + end + + it "returns 403 for project members with guest role" do + put api("#{base_url}/#{test_list.id}", guest), + position: 1 + + expect(response).to have_http_status(403) + end + end + + describe "DELETE /projects/:id/board/lists/:list_id" do + let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } + + it "rejects a non member from deleting a list" do + delete api("#{base_url}/#{dev_list.id}", non_member) + + expect(response).to have_http_status(403) + end + + it "rejects a user with guest role from deleting a list" do + delete api("#{base_url}/#{dev_list.id}", guest) + + expect(response).to have_http_status(403) + end + + it "returns 404 error if list id not found" do + delete api("#{base_url}/44444", user) + + expect(response).to have_http_status(404) + end + + context "when the user is project owner" do + let(:owner) { create(:user) } + let(:project) { create(:project, namespace: owner.namespace) } + + it "deletes the list if an admin requests it" do + delete api("#{base_url}/#{dev_list.id}", owner) + + expect(response).to have_http_status(200) + expect(json_response['position']).to eq(1) + end + end + end +end -- cgit v1.2.1 From 4d20083bd7e8839bfef64267535fd3c947a3b374 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Wed, 5 Oct 2016 18:13:49 +0200 Subject: Respond with 404 Not Found for non-existent tags Non-existent tags should be handled with 404 Not Found. --- CHANGELOG | 1 + app/controllers/projects/tags_controller.rb | 2 ++ spec/controllers/projects/tags_controller_spec.rb | 14 ++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e410d73d1f6..ea54f37a22e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) + - Respond with 404 Not Found for non-existent tags - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Add ability to fork to a specific namespace using API. (ritave) diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 6ea8ee62bc5..40899abf6ee 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) + return render_404 if @tag.nil? + @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index a6995145cc1..5e661c2c41d 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -17,4 +17,18 @@ describe Projects::TagsController do expect(assigns(:releases)).not_to include(invalid_release) end end + + describe 'GET show' do + before { get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: id } + + context "valid tag" do + let(:id) { 'v1.0.0' } + it { is_expected.to respond_with(:success) } + end + + context "invalid tag" do + let(:id) { 'latest' } + it { is_expected.to respond_with(:not_found) } + end + end end -- cgit v1.2.1 From 5c8c33c92dbc9afba077e4ae54a7bce39b591f68 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Wed, 5 Oct 2016 20:05:44 +0300 Subject: Set default value for show_menu_above variable. --- app/views/shared/issuable/_milestone_dropdown.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 30d3c84666d..ab3cc33d18f 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,10 +1,11 @@ - project = @target_project || @project - extra_class = extra_class || '' +- show_menu_above = show_menu_above || false - selected_text = selected.try(:title) - if selected.present? = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", - placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do + placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project -- cgit v1.2.1 From 6b9671388d523a03b058e1cc467de77d805fc7a2 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Wed, 5 Oct 2016 18:13:49 +0200 Subject: Respond with 404 Not Found for non-existent tags Non-existent tags should be handled with 404 Not Found. --- CHANGELOG | 2 ++ app/controllers/projects/tags_controller.rb | 2 ++ spec/controllers/projects/tags_controller_spec.rb | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 07b2b23003b..1b461f54729 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -90,6 +90,8 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 +v 8.12.0 (unreleased) + - Respond with 404 Not Found for non-existent tags - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 6ea8ee62bc5..40899abf6ee 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) + return render_404 if @tag.nil? + @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index a6995145cc1..5e661c2c41d 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -17,4 +17,18 @@ describe Projects::TagsController do expect(assigns(:releases)).not_to include(invalid_release) end end + + describe 'GET show' do + before { get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: id } + + context "valid tag" do + let(:id) { 'v1.0.0' } + it { is_expected.to respond_with(:success) } + end + + context "invalid tag" do + let(:id) { 'latest' } + it { is_expected.to respond_with(:not_found) } + end + end end -- cgit v1.2.1 From ff378e19e6dc385f1c85b7704263129a56778752 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Wed, 5 Oct 2016 19:31:33 +0200 Subject: Respond with 404 Not Found for non-existent tags Non-existent tags should be handled with 404 Not Found. --- CHANGELOG | 3 +-- app/controllers/projects/tags_controller.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1b461f54729..fd480bf87be 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) @@ -90,8 +91,6 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 -v 8.12.0 (unreleased) - - Respond with 404 Not Found for non-existent tags - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 40899abf6ee..8fea20cefef 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,7 +20,7 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) - return render_404 if @tag.nil? + return render_404 unless @tag @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) -- cgit v1.2.1 From a1ee8cf5ad07256807f15590bdb5f56152d55553 Mon Sep 17 00:00:00 2001 From: Marc Siegfriedt Date: Mon, 29 Aug 2016 23:58:32 +0000 Subject: multi-file commit add docs and tests - add additional validation allow move without content updated response --- CHANGELOG | 1 + app/models/repository.rb | 46 +++++ app/services/base_service.rb | 7 +- app/services/files/base_service.rb | 11 +- app/services/files/multi_service.rb | 124 +++++++++++++ app/services/files/update_service.rb | 6 - doc/api/commits.md | 87 +++++++++ lib/api/commits.rb | 36 ++++ spec/requests/api/commits_spec.rb | 273 ++++++++++++++++++++++++++++- spec/services/files/update_service_spec.rb | 4 +- 10 files changed, 575 insertions(+), 20 deletions(-) create mode 100644 app/services/files/multi_service.rb diff --git a/CHANGELOG b/CHANGELOG index 07b2b23003b..282ce42fd8f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.13.0 (unreleased) - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Simplify Mentionable concern instance methods - Fix permission for setting an issue's due date + - API: Multi-file commit !6096 (mahcsig) - Expose expires_at field when sharing project on API - Fix VueJS template tags being rendered in code comments - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) diff --git a/app/models/repository.rb b/app/models/repository.rb index eb574555df6..bf59b74495b 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -838,6 +838,52 @@ class Repository end end + def multi_action(user:, branch:, message:, actions:, author_email: nil, author_name: nil) + update_branch_with_hooks(user, branch) do |ref| + index = rugged.index + parents = [] + branch = find_branch(ref) + + if branch + last_commit = branch.target + index.read_tree(last_commit.raw_commit.tree) + parents = [last_commit.sha] + end + + actions.each do |action| + case action[:action] + when :create, :update, :move + mode = + case action[:action] + when :update + index.get(action[:file_path])[:mode] + when :move + index.get(action[:previous_path])[:mode] + end + mode ||= 0o100644 + + index.remove(action[:previous_path]) if action[:action] == :move + + content = action[:encoding] == 'base64' ? Base64.decode64(action[:content]) : action[:content] + oid = rugged.write(content, :blob) + + index.add(path: action[:file_path], oid: oid, mode: mode) + when :delete + index.remove(action[:file_path]) + end + end + + options = { + tree: index.write_tree(rugged), + message: message, + parents: parents + } + options.merge!(get_committer_and_author(user, email: author_email, name: author_name)) + + Rugged::Commit.create(rugged, options) + end + end + def get_committer_and_author(user, email: nil, name: nil) committer = user_to_committer(user) author = Gitlab::Git::committer_hash(email: email, name: name) || committer diff --git a/app/services/base_service.rb b/app/services/base_service.rb index 0c208150fb8..1a2bad77a02 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -56,9 +56,8 @@ class BaseService result end - def success - { - status: :success - } + def success(pass_back = {}) + pass_back[:status] = :success + pass_back end end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index e8465729d06..9bd4bd464f7 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -27,8 +27,9 @@ module Files create_target_branch end - if commit - success + result = commit + if result + success(result: result) else error('Something went wrong. Your changes were not committed') end @@ -42,6 +43,12 @@ module Files @source_branch != @target_branch || @source_project != @project end + def file_has_changed? + return false unless @last_commit_sha && last_commit + + @last_commit_sha != last_commit.sha + end + def raise_error(message) raise ValidationError.new(message) end diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb new file mode 100644 index 00000000000..d28912e1301 --- /dev/null +++ b/app/services/files/multi_service.rb @@ -0,0 +1,124 @@ +require_relative "base_service" + +module Files + class MultiService < Files::BaseService + class FileChangedError < StandardError; end + + def commit + repository.multi_action( + user: current_user, + branch: @target_branch, + message: @commit_message, + actions: params[:actions], + author_email: @author_email, + author_name: @author_name + ) + end + + private + + def validate + super + + params[:actions].each_with_index do |action, index| + unless action[:file_path].present? + raise_error("You must specify a file_path.") + end + + regex_check(action[:file_path]) + regex_check(action[:previous_path]) if action[:previous_path] + + if project.empty_repo? && action[:action] != :create + raise_error("No files to #{action[:action]}.") + end + + validate_file_exists(action) + + case action[:action] + when :create + validate_create(action) + when :update + validate_update(action) + when :delete + validate_delete(action) + when :move + validate_move(action, index) + else + raise_error("Unknown action type `#{action[:action]}`.") + end + end + end + + def validate_file_exists(action) + return if action[:action] == :create + + file_path = action[:file_path] + file_path = action[:previous_path] if action[:action] == :move + + blob = repository.blob_at_branch(params[:branch_name], file_path) + + unless blob + raise_error("File to be #{action[:action]}d `#{file_path}` does not exist.") + end + end + + def last_commit + Gitlab::Git::Commit.last_for_path(repository, @source_branch, @file_path) + end + + def regex_check(file) + if file =~ Gitlab::Regex.directory_traversal_regex + raise_error( + 'Your changes could not be committed, because the file name, `' + + file + + '` ' + + Gitlab::Regex.directory_traversal_regex_message + ) + end + + unless file =~ Gitlab::Regex.file_path_regex + raise_error( + 'Your changes could not be committed, because the file name, `' + + file + + '` ' + + Gitlab::Regex.file_path_regex_message + ) + end + end + + def validate_create(action) + return if project.empty_repo? + + if repository.blob_at_branch(params[:branch_name], action[:file_path]) + raise_error("Your changes could not be committed because a file with the name `#{action[:file_path]}` already exists.") + end + end + + def validate_delete(action) + end + + def validate_move(action, index) + if action[:previous_path].nil? + raise_error("You must supply the original file path when moving file `#{action[:file_path]}`.") + end + + blob = repository.blob_at_branch(params[:branch_name], action[:file_path]) + + if blob + raise_error("Move destination `#{action[:file_path]}` already exists.") + end + + if action[:content].nil? + blob = repository.blob_at_branch(params[:branch_name], action[:previous_path]) + blob.load_all_data!(repository) if blob.truncated? + params[:actions][index][:content] = blob.data + end + end + + def validate_update(action) + if file_has_changed? + raise FileChangedError.new("You are attempting to update a file `#{action[:file_path]}` that has changed since you started editing it.") + end + end + end +end diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 9e9b5b63f26..c17fdb8d1f1 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -23,12 +23,6 @@ module Files end end - def file_has_changed? - return false unless @last_commit_sha && last_commit - - @last_commit_sha != last_commit.sha - end - def last_commit @last_commit ||= Gitlab::Git::Commit. last_for_path(@source_project.repository, @source_branch, @file_path) diff --git a/doc/api/commits.md b/doc/api/commits.md index 682151d4b1d..3e20beefb8a 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -46,6 +46,91 @@ Example response: ] ``` +## Create a commit with multiple files and actions + +> [Introduced][ce-6096] in GitLab 8.13. + +Create a commit by posting a JSON payload + +``` +POST /projects/:id/repository/commits +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME | +| `branch_name` | string | yes | The name of a branch | +| `commit_message` | string | yes | Commit message | +| `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. | +| `author_email` | string | no | Specify the commit author's email address | +| `author_name` | string | no | Specify the commit author's name | + + +| `actions[]` Attribute | Type | Required | Description | +| --------------------- | ---- | -------- | ----------- | +| `action` | string | yes | The action to perform, `create`, `delete`, `move`, `update` | +| `file_path` | string | yes | Full path to the file. Ex. `lib/class.rb` | +| `previous_path` | string | no | Original full path to the file being moved. Ex. `lib/class1.rb` | +| `content` | string | no | File content, required for all except `delete`. Optional for `move` | +| `encoding` | string | no | `text` or `base64`. `text` is default. | + +```bash +PAYLOAD=$(cat << 'JSON' +{ + "branch_name": "master", + "commit_message": "some commit message", + "actions": [ + { + "action": "create", + "file_path": "foo/bar", + "content": "some content" + }, + { + "action": "delete", + "file_path": "foo/bar2", + }, + { + "action": "move", + "file_path": "foo/bar3", + "previous_path": "foo/bar4", + "content": "some content" + }, + { + "action": "update", + "file_path": "foo/bar5", + "content": "new content" + } + ] +} +JSON +) +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data "$PAYLOAD" https://gitlab.example.com/api/v3/projects/1/repository/commits +``` + +Example response: +```json +{ + "id": "ed899a2f4b50b4370feeea94676502b42383c746", + "short_id": "ed899a2f4b5", + "title": "some commit message", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dzaporozhets@sphereconsultinginc.com", + "created_at": "2016-09-20T09:26:24.000-07:00", + "message": "some commit message", + "parent_ids": [ + "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba" + ], + "committed_date": "2016-09-20T09:26:24.000-07:00", + "authored_date": "2016-09-20T09:26:24.000-07:00", + "stats": { + "additions": 2, + "deletions": 2, + "total": 4 + }, + "status": null +} +``` + ## Get a single commit Get a specific commit identified by the commit hash or name of a branch or tag. @@ -343,3 +428,5 @@ Example response: "finished_at" : "2016-01-19T09:05:50.365Z" } ``` + +[ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit" diff --git a/lib/api/commits.rb b/lib/api/commits.rb index b4eaf1813d4..14ddc8c9a62 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -29,6 +29,42 @@ module API present commits, with: Entities::RepoCommit end + desc 'Commit multiple file changes as one commit' do + detail 'This feature was introduced in GitLab 8.13' + end + + params do + requires :id, type: Integer, desc: 'The project ID' + requires :branch_name, type: String, desc: 'The name of branch' + requires :commit_message, type: String, desc: 'Commit message' + requires :actions, type: Array, desc: 'Actions to perform in commit' + optional :author_email, type: String, desc: 'Author email for commit' + optional :author_name, type: String, desc: 'Author name for commit' + end + + post ":id/repository/commits" do + authorize! :push_code, user_project + + attrs = declared(params) + attrs[:source_branch] = attrs[:branch_name] + attrs[:target_branch] = attrs[:branch_name] + attrs[:actions].map! do |action| + action[:action] = action[:action].to_sym + action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/') + action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/') + action + end + + result = ::Files::MultiService.new(user_project, current_user, attrs).execute + + if result[:status] == :success + commit_detail = user_project.repository.commits(result[:result], limit: 1).first + present commit_detail, with: Entities::RepoCommitDetail + else + render_api_error!(result[:message], 400) + end + end + # Get a specific commit of a project # # Parameters: diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 10f772c5b1a..aa610557056 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -5,7 +5,7 @@ describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } @@ -13,7 +13,7 @@ describe API::API, api: true do before { project.team << [user, :reporter] } - describe "GET /projects/:id/repository/commits" do + describe "List repository commits" do context "authorized user" do before { project.team << [user2, :reporter] } @@ -69,7 +69,268 @@ describe API::API, api: true do end end - describe "GET /projects:id/repository/commits/:sha" do + describe "Create a commit with multiple files and actions" do + let!(:url) { "/projects/#{project.id}/repository/commits" } + + it 'returns a 403 unauthorized for user without permissions' do + post api(url, user2) + + expect(response).to have_http_status(403) + end + + it 'returns a 400 bad request if no params are given' do + post api(url, user) + + expect(response).to have_http_status(400) + end + + context :create do + let(:message) { 'Created file' } + let!(:invalid_c_params) do + { + branch_name: 'master', + commit_message: message, + actions: [ + { + action: 'create', + file_path: 'files/ruby/popen.rb', + content: 'puts 8' + } + ] + } + end + let!(:valid_c_params) do + { + branch_name: 'master', + commit_message: message, + actions: [ + { + action: 'create', + file_path: 'foo/bar/baz.txt', + content: 'puts 8' + } + ] + } + end + + it 'a new file in project repo' do + post api(url, user), valid_c_params + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(message) + end + + it 'returns a 400 bad request if file exists' do + post api(url, user), invalid_c_params + + expect(response).to have_http_status(400) + end + end + + context :delete do + let(:message) { 'Deleted file' } + let!(:invalid_d_params) do + { + branch_name: 'markdown', + commit_message: message, + actions: [ + { + action: 'delete', + file_path: 'doc/api/projects.md' + } + ] + } + end + let!(:valid_d_params) do + { + branch_name: 'markdown', + commit_message: message, + actions: [ + { + action: 'delete', + file_path: 'doc/api/users.md' + } + ] + } + end + + it 'an existing file in project repo' do + post api(url, user), valid_d_params + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(message) + end + + it 'returns a 400 bad request if file does not exist' do + post api(url, user), invalid_d_params + + expect(response).to have_http_status(400) + end + end + + context :move do + let(:message) { 'Moved file' } + let!(:invalid_m_params) do + { + branch_name: 'feature', + commit_message: message, + actions: [ + { + action: 'move', + file_path: 'CHANGELOG', + previous_path: 'VERSION', + content: '6.7.0.pre' + } + ] + } + end + let!(:valid_m_params) do + { + branch_name: 'feature', + commit_message: message, + actions: [ + { + action: 'move', + file_path: 'VERSION.txt', + previous_path: 'VERSION', + content: '6.7.0.pre' + } + ] + } + end + + it 'an existing file in project repo' do + post api(url, user), valid_m_params + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(message) + end + + it 'returns a 400 bad request if file does not exist' do + post api(url, user), invalid_m_params + + expect(response).to have_http_status(400) + end + end + + context :update do + let(:message) { 'Updated file' } + let!(:invalid_u_params) do + { + branch_name: 'master', + commit_message: message, + actions: [ + { + action: 'update', + file_path: 'foo/bar.baz', + content: 'puts 8' + } + ] + } + end + let!(:valid_u_params) do + { + branch_name: 'master', + commit_message: message, + actions: [ + { + action: 'update', + file_path: 'files/ruby/popen.rb', + content: 'puts 8' + } + ] + } + end + + it 'an existing file in project repo' do + post api(url, user), valid_u_params + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(message) + end + + it 'returns a 400 bad request if file does not exist' do + post api(url, user), invalid_u_params + + expect(response).to have_http_status(400) + end + end + + context "multiple operations" do + let(:message) { 'Multiple actions' } + let!(:invalid_mo_params) do + { + branch_name: 'master', + commit_message: message, + actions: [ + { + action: 'create', + file_path: 'files/ruby/popen.rb', + content: 'puts 8' + }, + { + action: 'delete', + file_path: 'doc/api/projects.md' + }, + { + action: 'move', + file_path: 'CHANGELOG', + previous_path: 'VERSION', + content: '6.7.0.pre' + }, + { + action: 'update', + file_path: 'foo/bar.baz', + content: 'puts 8' + } + ] + } + end + let!(:valid_mo_params) do + { + branch_name: 'master', + commit_message: message, + actions: [ + { + action: 'create', + file_path: 'foo/bar/baz.txt', + content: 'puts 8' + }, + { + action: 'delete', + file_path: 'Gemfile.zip' + }, + { + action: 'move', + file_path: 'VERSION.txt', + previous_path: 'VERSION', + content: '6.7.0.pre' + }, + { + action: 'update', + file_path: 'files/ruby/popen.rb', + content: 'puts 8' + } + ] + } + end + + it 'are commited as one in project repo' do + post api(url, user), valid_mo_params + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(message) + end + + it 'return a 400 bad request if there are any issues' do + post api(url, user), invalid_mo_params + + expect(response).to have_http_status(400) + end + end + end + + describe "Get a single commit" do context "authorized user" do it "returns a commit by sha" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) @@ -122,7 +383,7 @@ describe API::API, api: true do end end - describe "GET /projects:id/repository/commits/:sha/diff" do + describe "Get the diff of a commit" do context "authorized user" do before { project.team << [user2, :reporter] } @@ -149,7 +410,7 @@ describe API::API, api: true do end end - describe 'GET /projects:id/repository/commits/:sha/comments' do + describe 'Get the comments of a commit' do context 'authorized user' do it 'returns merge_request comments' do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) @@ -174,7 +435,7 @@ describe API::API, api: true do end end - describe 'POST /projects:id/repository/commits/:sha/comments' do + describe 'Post comment to commit' do context 'authorized user' do it 'returns comment' do post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment' diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb index d019e50649f..d3c37c7820f 100644 --- a/spec/services/files/update_service_spec.rb +++ b/spec/services/files/update_service_spec.rb @@ -41,7 +41,7 @@ describe Files::UpdateService do it "returns a hash with the :success status " do results = subject.execute - expect(results).to match({ status: :success }) + expect(results[:status]).to match(:success) end it "updates the file with the new contents" do @@ -69,7 +69,7 @@ describe Files::UpdateService do it "returns a hash with the :success status " do results = subject.execute - expect(results).to match({ status: :success }) + expect(results[:status]).to match(:success) end it "updates the file with the new contents" do -- cgit v1.2.1 From b22e90b794fc740f8cec33f115fe1be3d1dea5ba Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Wed, 5 Oct 2016 22:04:37 +0300 Subject: Fix bad merge conflict resolution. --- app/assets/javascripts/dispatcher.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f39eb40f0f2..8d99b12102d 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -61,7 +61,6 @@ new IssuableForm($('.issue-form')); new LabelsSelect(); new MilestoneSelect(); - new IssuableTemplateSelectors(); new gl.IssuableTemplateSelectors(); break; case 'projects:merge_requests:new': @@ -72,7 +71,6 @@ new IssuableForm($('.merge-request-form')); new LabelsSelect(); new MilestoneSelect(); - new IssuableTemplateSelectors(); new gl.IssuableTemplateSelectors(); break; case 'projects:tags:new': -- cgit v1.2.1 From b4d614bdbcb50f506c56eaa180d7b3f3055884a8 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 29 Aug 2016 12:09:33 -0500 Subject: Fix inconsistent highlighting of already selected activity nav-links --- CHANGELOG | 1 + app/assets/javascripts/activities.js | 12 ++--- app/controllers/application_controller.rb | 3 +- app/views/shared/_event_filter.html.haml | 1 + lib/event_filter.rb | 23 +++++---- spec/javascripts/activities_spec.js.es6 | 61 ++++++++++++++++++++++++ spec/javascripts/fixtures/event_filter.html.haml | 21 ++++++++ spec/lib/event_filter_spec.rb | 49 +++++++++++++++++++ 8 files changed, 153 insertions(+), 18 deletions(-) create mode 100644 spec/javascripts/activities_spec.js.es6 create mode 100644 spec/javascripts/fixtures/event_filter.html.haml create mode 100644 spec/lib/event_filter_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 07b2b23003b..fbafda4d9ad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.13.0 (unreleased) - Added soft wrap button to repository file/blob editor - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) + - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index d5e11e22be5..f4f8cf04184 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -21,16 +21,14 @@ }; Activities.prototype.toggleFilter = function(sender) { - var event_filters, filter; + var filter = sender.attr("id").split("_")[0]; + $('.event-filter .active').removeClass("active"); - event_filters = $.cookie("event_filter"); - filter = sender.attr("id").split("_")[0]; - $.cookie("event_filter", (event_filters !== filter ? filter : ""), { + $.cookie("event_filter", filter, { path: gon.relative_url_root || '/' }); - if (event_filters !== filter) { - return sender.closest('li').toggleClass("active"); - } + + sender.closest('li').toggleClass("active"); }; return Activities; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bd4ba384b29..b3455e04c29 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -173,7 +173,8 @@ class ApplicationController < ActionController::Base end def event_filter - filters = cookies['event_filter'].split(',') if cookies['event_filter'].present? + # Split using comma to maintain backward compatibility Ex/ "filter1,filter2" + filters = cookies['event_filter'].split(',')[0] if cookies['event_filter'].present? @event_filter ||= EventFilter.new(filters) end diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 8824bcc158e..3480800369a 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,4 +1,5 @@ %ul.nav-links.event-filter.scrolling-tabs + = event_filter_link EventFilter.all, 'All' = event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.comments, 'Comments' diff --git a/lib/event_filter.rb b/lib/event_filter.rb index 668d2fa41b3..96e70e37e8f 100644 --- a/lib/event_filter.rb +++ b/lib/event_filter.rb @@ -2,8 +2,8 @@ class EventFilter attr_accessor :params class << self - def default_filter - %w{ push issues merge_requests team} + def all + 'all' end def push @@ -35,18 +35,21 @@ class EventFilter return events unless params.present? filter = params.dup - actions = [] - actions << Event::PUSHED if filter.include? 'push' - actions << Event::MERGED if filter.include? 'merged' - if filter.include? 'team' - actions << Event::JOINED - actions << Event::LEFT + case filter + when EventFilter.push + actions = [Event::PUSHED] + when EventFilter.merged + actions = [Event::MERGED] + when EventFilter.comments + actions = [Event::COMMENTED] + when EventFilter.team + actions = [Event::JOINED, Event::LEFT] + when EventFilter.all + actions = [Event::PUSHED, Event::MERGED, Event::COMMENTED, Event::JOINED, Event::LEFT] end - actions << Event::COMMENTED if filter.include? 'comments' - events.where(action: actions) end diff --git a/spec/javascripts/activities_spec.js.es6 b/spec/javascripts/activities_spec.js.es6 new file mode 100644 index 00000000000..743b15460c6 --- /dev/null +++ b/spec/javascripts/activities_spec.js.es6 @@ -0,0 +1,61 @@ +/*= require jquery.cookie.js */ +/*= require jquery.endless-scroll.js */ +/*= require pager */ +/*= require activities */ + +(() => { + window.gon || (window.gon = {}); + const fixtureTemplate = 'event_filter.html'; + const filters = [ + { + id: 'all', + }, { + id: 'push', + name: 'push events', + }, { + id: 'merged', + name: 'merge events', + }, { + id: 'comments', + },{ + id: 'team', + }]; + + function getEventName(index) { + let filter = filters[index]; + return filter.hasOwnProperty('name') ? filter.name : filter.id; + } + + function getSelector(index) { + let filter = filters[index]; + return `#${filter.id}_event_filter` + } + + describe('Activities', () => { + beforeEach(() => { + fixture.load(fixtureTemplate); + new Activities(); + }); + + for(let i = 0; i < filters.length; i++) { + ((i) => { + describe(`when selecting ${getEventName(i)}`, () => { + beforeEach(() => { + $(getSelector(i)).click(); + }); + + for(let x = 0; x < filters.length; x++) { + ((x) => { + let shouldHighlight = i === x; + let testName = shouldHighlight ? 'should highlight' : 'should not highlight'; + + it(`${testName} ${getEventName(x)}`, () => { + expect($(getSelector(x)).parent().hasClass('active')).toEqual(shouldHighlight); + }); + })(x); + } + }); + })(i); + } + }); +})(); diff --git a/spec/javascripts/fixtures/event_filter.html.haml b/spec/javascripts/fixtures/event_filter.html.haml new file mode 100644 index 00000000000..95e248cadf8 --- /dev/null +++ b/spec/javascripts/fixtures/event_filter.html.haml @@ -0,0 +1,21 @@ +%ul.nav-links.event-filter.scrolling-tabs + %li.active + %a.event-filter-link{ id: "all_event_filter", title: "Filter by all", href: "/dashboard/activity"} + %span + All + %li + %a.event-filter-link{ id: "push_event_filter", title: "Filter by push events", href: "/dashboard/activity"} + %span + Push events + %li + %a.event-filter-link{ id: "merged_event_filter", title: "Filter by merge events", href: "/dashboard/activity"} + %span + Merge events + %li + %a.event-filter-link{ id: "comments_event_filter", title: "Filter by comments", href: "/dashboard/activity"} + %span + Comments + %li + %a.event-filter-link{ id: "team_event_filter", title: "Filter by team", href: "/dashboard/activity"} + %span + Team \ No newline at end of file diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb new file mode 100644 index 00000000000..a6d8e6927e0 --- /dev/null +++ b/spec/lib/event_filter_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe EventFilter, lib: true do + describe '#apply_filter' do + let(:source_user) { create(:user) } + let!(:public_project) { create(:project, :public) } + + let!(:push_event) { create(:event, action: Event::PUSHED, project: public_project, target: public_project, author: source_user) } + let!(:merged_event) { create(:event, action: Event::MERGED, project: public_project, target: public_project, author: source_user) } + let!(:comments_event) { create(:event, action: Event::COMMENTED, project: public_project, target: public_project, author: source_user) } + let!(:joined_event) { create(:event, action: Event::JOINED, project: public_project, target: public_project, author: source_user) } + let!(:left_event) { create(:event, action: Event::LEFT, project: public_project, target: public_project, author: source_user) } + + it 'applies push filter' do + events = EventFilter.new(EventFilter.push).apply_filter(Event.all) + expect(events).to contain_exactly(push_event) + end + + it 'applies merged filter' do + events = EventFilter.new(EventFilter.merged).apply_filter(Event.all) + expect(events).to contain_exactly(merged_event) + end + + it 'applies comments filter' do + events = EventFilter.new(EventFilter.comments).apply_filter(Event.all) + expect(events).to contain_exactly(comments_event) + end + + it 'applies team filter' do + events = EventFilter.new(EventFilter.team).apply_filter(Event.all) + expect(events).to contain_exactly(joined_event, left_event) + end + + it 'applies all filter' do + events = EventFilter.new(EventFilter.all).apply_filter(Event.all) + expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event) + end + + it 'applies no filter' do + events = EventFilter.new(nil).apply_filter(Event.all) + expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event) + end + + it 'applies unknown filter' do + events = EventFilter.new('').apply_filter(Event.all) + expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event) + end + end +end -- cgit v1.2.1 From 0af5e3d50bd5492da1293070f6a93baf1f65833d Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 5 Oct 2016 16:48:12 -0500 Subject: move merge request link next to the commit sha and expose reference number --- app/views/notify/pipeline_failed_email.html.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 6b013055d45..fe6c526ec0d 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -94,10 +94,6 @@ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} %a.muted{:href => namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), :style => "color:#333333;text-decoration:none;"} = @pipeline.ref - - if @merge_request - ( - %a{:href => namespace_project_merge_request_url(@project.namespace, @project, @merge_request), :style => "color:#3084bb;text-decoration:none;white-space: nowrap;"}> merge request - ) %tr %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} @@ -109,6 +105,10 @@ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} %a{:href => namespace_project_commit_url(@project.namespace, @project, @pipeline.sha), :style => "color:#3084bb;text-decoration:none;"} = @pipeline.short_sha + - if @merge_request + in + %a{:href => namespace_project_merge_request_url(@project.namespace, @project, @merge_request), :style => "color:#3084bb;text-decoration:none;"} + = @merge_request.to_reference .commit{:style => "color:#5c5c5c;font-weight:300;"} = @pipeline.git_commit_message.truncate(50) %tr -- cgit v1.2.1 From 15ff8dcb251d93d578739bba51110257f311b177 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 28 Sep 2016 18:37:25 +0100 Subject: Document GIT_STRATEGY=none [ci skip] --- doc/ci/yaml/README.md | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 16868554c1f..cdf5ecc7a84 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -858,27 +858,45 @@ job: ## Git Strategy -> Introduced in GitLab 8.9 as an experimental feature. May change in future - releases or be removed completely. +> Introduced in GitLab 8.9 as an experimental feature. May change or be removed + completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner + v1.7+. + +You can set the `GIT_STRATEGY` used for getting recent application code, either +in the global [`variables`](#variables) section or the [`variables`](#job-variables) +section for individual jobs. If left unspecified, the default from project +settings will be used. -You can set the `GIT_STRATEGY` used for getting recent application code. `clone` -is slower, but makes sure you have a clean directory before every build. `fetch` -is faster. `GIT_STRATEGY` can be specified in the global `variables` section or -in the `variables` section for individual jobs. If it's not specified, then the -default from project settings will be used. +There are three possible values: `clone`, `fetch`, and `none`. + +`clone` is the slowest option. It clones the repository from scratch for every +job, ensuring that the project workspace is always pristine. ``` variables: GIT_STRATEGY: clone ``` -or +`fetch` is faster as it re-uses the project workspace (falling back to `clone` +if it doesn't exist). `git clean` is used to undo any changes made by the last +job, and `git fetch` is used to retrieve commits made since the last job ran. ``` variables: GIT_STRATEGY: fetch ``` +`none` also re-uses the project workspace, but skips all Git operations +(including GitLab Runner's pre-clone script, if present). It is mostly useful +for jobs that operate exclusively on artifacts (e.g., `deploy`). Git repository +data may be present, but it is certain to be out of date, so you should only +rely on files brought into the project workspace from cache or artifacts. + +``` +variables: + GIT_STRATEGY: none +``` + ## Shallow cloning > Introduced in GitLab 8.9 as an experimental feature. May change in future -- cgit v1.2.1 From e23c1d88e2d20ffd5cadde6bb06148b9295461b5 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 23 Aug 2016 11:02:52 -0500 Subject: Replace bootstrap caret with fontawesome caret --- CHANGELOG | 1 + app/assets/javascripts/pipeline.js.es6 | 11 ++++++++++- app/assets/stylesheets/framework/buttons.scss | 9 ++++++++- app/assets/stylesheets/framework/dropdowns.scss | 17 ----------------- app/assets/stylesheets/framework/forms.scss | 4 ++-- app/assets/stylesheets/framework/header.scss | 4 ++++ app/assets/stylesheets/framework/selects.scss | 9 ++++++++- app/assets/stylesheets/pages/pipelines.scss | 15 ++++----------- app/views/dashboard/todos/index.html.haml | 2 +- app/views/explore/groups/index.html.haml | 2 +- app/views/explore/projects/_filter.html.haml | 4 ++-- app/views/layouts/header/_default.html.haml | 2 +- app/views/projects/branches/index.html.haml | 2 +- app/views/projects/ci/pipelines/_pipeline.html.haml | 4 ++-- app/views/projects/commit/_commit_box.html.haml | 2 +- app/views/projects/commit/_pipeline.html.haml | 2 +- app/views/projects/deployments/_actions.haml | 2 +- app/views/projects/forks/index.html.haml | 2 +- app/views/projects/group_links/index.html.haml | 2 +- app/views/projects/issues/show.html.haml | 2 +- app/views/projects/labels/_label.html.haml | 2 +- app/views/projects/merge_requests/_show.html.haml | 2 +- .../projects/merge_requests/show/_mr_title.html.haml | 2 +- .../projects/merge_requests/show/_versions.html.haml | 4 ++-- .../merge_requests/widget/open/_accept.html.haml | 2 +- app/views/projects/snippets/_actions.html.haml | 2 +- app/views/projects/tags/index.html.haml | 2 +- app/views/shared/_new_project_item_select.html.haml | 2 +- app/views/shared/_sort_dropdown.html.haml | 2 +- app/views/shared/notifications/_button.html.haml | 2 +- app/views/snippets/_actions.html.haml | 2 +- 31 files changed, 63 insertions(+), 59 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 856fb2849f5..e3809b179e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -44,6 +44,7 @@ v 8.13.0 (unreleased) - Add broadcast messages and alerts below sub-nav - Better empty state for Groups view - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) + - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile - Fix resolved discussion display in side-by-side diff view !6575 diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index bf33eb10100..8813bb5dfef 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -3,12 +3,21 @@ const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); const $btnText = $(this).find('.toggle-btn-text'); + const $icon = $(this).find('.fa'); $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); + const expandIcon = 'fa-caret-down'; + const hideIcon = 'fa-caret-up'; - graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + if(graphCollapsed) { + $btnText.text('Expand'); + $icon.removeClass(hideIcon).addClass(expandIcon); + } else { + $btnText.text('Hide'); + $icon.removeClass(expandIcon).addClass(hideIcon); + } } $(document).on('click', '.toggle-pipeline-btn', toggleGraph); diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index ce489f7c3de..d11b2fe7ec2 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -194,10 +194,17 @@ pointer-events: none !important; } - .caret { + .fa-caret-down, + .fa-caret-up { margin-left: 5px; } + &.dropdown-toggle { + .fa-caret-down { + margin-left: 3px; + } + } + svg { height: 15px; width: 15px; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 4a87a73a68a..baa95711329 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -1,20 +1,3 @@ -.caret { - display: inline-block; - width: 0; - height: 0; - margin-left: 2px; - vertical-align: middle; - border-top: $caret-width-base dashed; - border-right: $caret-width-base solid transparent; - border-left: $caret-width-base solid transparent; -} - -.btn-group { - .caret { - margin-left: 0; - } -} - .dropdown { position: relative; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 37ff7e22ed1..a67d31de2f7 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -81,10 +81,10 @@ label { .select-wrapper { position: relative; - .caret { + .fa-caret-down { position: absolute; right: 10px; - top: $gl-padding; + top: 10px; color: $gray-darkest; pointer-events: none; } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index c748f856501..9823abdde1f 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -57,6 +57,10 @@ header { &:hover, &:focus, &:active { background-color: $background-color; } + + .fa-caret-down { + font-size: 15px; + } } .navbar-toggle { diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index c75dacf95d9..bcd60391543 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -21,7 +21,14 @@ padding-right: 10px; b { - @extend .caret; + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: $caret-width-base dashed; + border-right: $caret-width-base solid transparent; + border-left: $caret-width-base solid transparent; color: $gray-darkest; } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 68fc6da6c1b..a2779704eff 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -229,9 +229,12 @@ .fa { color: $table-text-gray; - margin-right: 6px; font-size: 14px; } + + svg, .fa { + margin-right: 0; + } } .btn-remove { @@ -272,18 +275,8 @@ .toggle-pipeline-btn { background-color: $gray-dark; - .caret { - border-top: none; - border-bottom: 4px solid; - } - &.graph-collapsed { background-color: $white-light; - - .caret { - border-bottom: none; - border-top: 4px solid; - } } } diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 9d31f31c639..2a0302638ba 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -55,7 +55,7 @@ = sort_options_hash[@sort] - else = sort_title_recently_created - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li = link_to todos_filter_path(sort: sort_value_priority) do diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index b8248a80a27..a1b39d9e1a0 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -23,7 +23,7 @@ = sort_options_hash[@sort] - else = sort_title_recently_created - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to explore_groups_path(sort: sort_value_recently_created) do diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 132bbe26fe0..4cff14b096b 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -7,7 +7,7 @@ = visibility_level_label(params[:visibility_level].to_i) - else Any - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(visibility_level: nil) do @@ -27,7 +27,7 @@ = params[:tag] - else Any - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(tag: nil) do diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 237280872f1..7faa8bded86 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -41,7 +41,7 @@ %li.header-user.dropdown = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar" - %span.caret + = icon('caret-down') .dropdown-menu-nav.dropdown-menu-align-right %ul %li diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index e889f29c816..84f38575e84 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -15,7 +15,7 @@ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} %span.light = projects_sort_options_hash[@sort] - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_branches_path(sort: sort_value_name) do diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index b87c7a485df..36eadbd2bf1 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -67,7 +67,7 @@ .btn-group %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} = custom_icon('icon_play') - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - actions.each do |build| %li @@ -78,7 +78,7 @@ .btn-group %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'} = icon("download") - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - artifacts.each do |build| %li diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 29d767e7769..0572acea007 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -14,7 +14,7 @@ .dropdown.inline %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } } %span.hidden-xs Options - %span.caret.commit-options-dropdown-caret + = icon('caret-down', class: ".commit-options-dropdown-caret") %ul.dropdown-menu.dropdown-menu-align-right %li.visible-xs-block.visible-sm-block = link_to namespace_project_tree_path(@project.namespace, @project, @commit) do diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 9258f4b3c25..da5b9832ba5 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -3,7 +3,7 @@ .btn.btn-grouped.btn-white.toggle-pipeline-btn %span.toggle-btn-text Hide %span pipeline graph - %span.caret + = icon('caret-up') - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 16d134eb6b6..99cb4222377 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -6,7 +6,7 @@ .dropdown %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} = custom_icon('icon_play') - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - actions.each do |action| %li diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index bacc5708e4b..abf4f697f86 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -15,7 +15,7 @@ = sort_options_hash[@sort] - else = sort_title_recently_created - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id] diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/index.html.haml index 4c5dd9b88bf..1b0dbbb8111 100644 --- a/app/views/projects/group_links/index.html.haml +++ b/app/views/projects/group_links/index.html.haml @@ -16,7 +16,7 @@ = label_tag :link_group_access, "Max access level", class: "label-light" .select-wrapper = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control" - %span.caret + = icon('caret-down') .form-group = label_tag :expires_at, 'Access expiration date', class: 'label-light' .clearable-input diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 3fb4191c60e..cbdea209847 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -23,7 +23,7 @@ .issuable-actions .clearfix.issue-btn-group.dropdown %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - %span.caret + = icon('caret-down') Options .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 73c6f2a046c..71f7f354d72 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -5,7 +5,7 @@ .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } } Options - %span.caret + = icon('caret-down') .dropdown-menu.dropdown-menu-align-right %ul %li diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 9f34ca9ff4e..46a2d862c91 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -22,7 +22,7 @@ %span.dropdown.inline.prepend-left-5 %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} } Download as - %span.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index e35291dff7d..9f2a0f5d99a 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -19,7 +19,7 @@ .issuable-actions .clearfix.issue-btn-group.dropdown %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - %span.caret + = icon('caret-down') Options .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 904452fcc4f..988ac0feae1 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -9,7 +9,7 @@ latest version - else version #{version_index(@merge_request_diff)} - %span.caret + = icon('caret-down') .dropdown-menu.dropdown-select.dropdown-menu-selectable .dropdown-title %span Version: @@ -39,7 +39,7 @@ version #{version_index(@start_version)} - else #{@merge_request.target_branch} - %span.caret + = icon('caret-down') .dropdown-menu.dropdown-select.dropdown-menu-selectable .dropdown-title %span Compared with: diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index bf2e76f0083..ce43ca3a286 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -12,7 +12,7 @@ Merge When Build Succeeds - unless @project.only_allow_merge_if_build_succeeds? = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do - %span.caret + = icon('caret-down') %span.sr-only Select Merge Moment %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' } diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index 9773b8438ec..32e1f8a21b0 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -12,7 +12,7 @@ .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } Options - %span.caret + = icon('caret-down') .dropdown-menu.dropdown-menu-full-width %ul - if can?(current_user, :create_project_snippet, @project) diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 6adbe9351dc..7a0d9dcc94f 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -14,7 +14,7 @@ %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } %span.light = @sort.humanize - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_tags_path(sort: nil) do diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml index 51622931e24..fbbf6f358c5 100644 --- a/app/views/shared/_new_project_item_select.html.haml +++ b/app/views/shared/_new_project_item_select.html.haml @@ -3,7 +3,7 @@ = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' } %a.btn.btn-new.new-project-item-select-button = local_assigns[:label] - %b.caret + = icon('caret-down') :javascript $('.new-project-item-select-button').on('click', function() { diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 36bbac6fbf5..68e05cb72e1 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -5,7 +5,7 @@ = sort_options_hash[@sort] - else = sort_title_recently_created - %b.caret + = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li = link_to page_filter_path(sort: sort_value_priority, label: true) do diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index ff1cf966a9b..feaa5570c21 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -11,7 +11,7 @@ = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting) } } - %span.caret + = icon('caret-down') .sr-only Toggle dropdown - else %button.dropdown-new.btn.btn-default.notifications-btn#notifications-button{ type: "button", data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting) } } diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index c446dc3bdc1..1d0e549ed3d 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -12,7 +12,7 @@ .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } Options - %span.caret + = icon('caret-down') .dropdown-menu.dropdown-menu-full-width %ul %li -- cgit v1.2.1 From 74dab34331d562be48964aa06d727e4826f554f1 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 5 Oct 2016 18:33:04 -0600 Subject: Don't allow flay to fail. --- .gitlab-ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5d2fad03f19..8645488335e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -207,9 +207,7 @@ rubocop: *exec rake haml_lint: *exec rake scss_lint: *exec rake brakeman: *exec -rake flay: - <<: *exec - allow_failure: yes +rake flay: *exec license_finder: *exec rake downtime_check: *exec -- cgit v1.2.1 From 004cd8df160a137b038ebc5f481e7d95d9931f4a Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 5 Oct 2016 22:19:53 -0500 Subject: update pipeline failed email margins --- .../ci_pipeline_notif_v1/icon-x-red-inverted.gif | Bin 591 -> 1013 bytes app/views/notify/pipeline_failed_email.html.haml | 10 ++++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif index 798c2531be0..4260e312929 100644 Binary files a/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif and b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif differ diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index fe6c526ec0d..179b1586e8f 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -37,7 +37,8 @@ border-left: 0 !important; border-right: 0 !important; border-radius: 0 !important; - padding: 0 10px !important; + padding-left: 10px !important; + padding-right: 10px !important; } } %body{:style => "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} @@ -53,7 +54,7 @@ %table.wrapper{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:0 25px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} %table.content{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;border-collapse:separate;border-spacing:0;"} %tbody %tr.alert @@ -62,8 +63,9 @@ %tbody %tr %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} - %img{:alt => "x", :height => "10", :src => image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), :style => "display:block;", :width => "10"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} Uh oh, your CI pipeline has failed. + %img{:alt => "x", :height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), :style => "display:block;", :width => "13"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} + Uh oh, your CI pipeline has failed. %tr.spacer %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   -- cgit v1.2.1 From 5cd95549d0511417a2974613177d24eb81d6108b Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 5 Oct 2016 22:20:34 -0500 Subject: add pipeline succeeded email notification --- .../icon-check-green-inverted.gif | Bin 0 -> 369 bytes app/views/notify/pipeline_success_email.html.haml | 179 ++++++++++++++++++--- 2 files changed, 153 insertions(+), 26 deletions(-) create mode 100644 app/assets/images/mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif diff --git a/app/assets/images/mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif new file mode 100644 index 00000000000..27a55b1d61f Binary files /dev/null and b/app/assets/images/mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif differ diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 64cf7cfe103..a21f71124cb 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -1,26 +1,153 @@ -.p - Project: - = @project.path_with_namespace -.p - Branch: - = @pipeline.ref -.p - Commit: - = @pipeline.short_sha - ( - = @pipeline.sha - ) -.p - Commit Message: - = @pipeline.git_commit_message -.p - Commit Author: - = @pipeline.git_author_name -.p - Pusher: - = @pipeline.user.try(:name) -- failed = @pipeline.statuses.latest.failed -.p - Pipeline # - = @pipeline.id - had succeeded. +!!! +%html{:lang => "en"} + %head + %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ + %meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/ + %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/ + %title + :css + /* CLIENT-SPECIFIC STYLES */ + body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } + img { -ms-interpolation-mode: bicubic; } + + /* iOS BLUE LINKS */ + a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + font-size: inherit !important; + font-family: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; + } + + /* ANDROID MARGIN HACK */ + body { margin:0 !important; } + div[style*="margin: 16px 0"] { margin:0 !important; } + + @media only screen and (max-width: 639px) { + body, #body { + min-width: 320px !important; + } + table.wrapper { + width: 100% !important; + min-width: 320px !important; + } + table.wrapper > tbody > tr > td { + border-left: 0 !important; + border-right: 0 !important; + border-radius: 0 !important; + padding-left: 10px !important; + padding-right: 10px !important; + } + } + %body{:style => "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} + %table#body{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;"} + %tbody + %tr.line + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;"}   + %tr.header + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} + %img{:alt => "GitLab", :height => "50", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), :width => "55"}/ + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} + %table.wrapper{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} + %table.content{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;border-collapse:separate;border-spacing:0;"} + %tbody + %tr.success + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;"} + %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;margin:0 auto;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} + %img{:alt => "✓", :height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), :style => "display:block;", :width => "13"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} + Success! Your CI pipeline has passed. + %tr.spacer + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} +   + %tr.section + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} + %table.info{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} + - if @project.group + %a.muted{:href => group_url(@project.group), :style => "color:#333333;text-decoration:none;"} + = @project.group.name + - else + %a.muted{:href => user_url(@project.namespace.owner), :style => "color:#333333;text-decoration:none;"} + = @project.namespace.owner.name + \/ + %a.muted{:href => namespace_project_url(@project.namespace, @project), :style => "color:#333333;text-decoration:none;"} + = @project.name + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img{:height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), :style => "display:block;", :width => "13"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %a.muted{:href => namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), :style => "color:#333333;text-decoration:none;"} + = @pipeline.ref + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %tbody + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img{:height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), :style => "display:block;", :width => "13"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %a{:href => namespace_project_commit_url(@project.namespace, @project, @pipeline.sha), :style => "color:#3084bb;text-decoration:none;"} + = @pipeline.short_sha + - if @merge_request + in + %a{:href => namespace_project_merge_request_url(@project.namespace, @project, @merge_request), :style => "color:#3084bb;text-decoration:none;"} + = @merge_request.to_reference + .commit{:style => "color:#5c5c5c;font-weight:300;"} + = @pipeline.git_commit_message.truncate(50) + %tr + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Author + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %tbody + %tr + - commit = @pipeline.commit + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img.avatar{:height => "24", :src => avatar_icon(commit.author || commit.author_email, 24), :style => "display:block;border-radius:12px;margin:-2px 0;", :width => "24"}/ + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + - if commit.author.nil? + %span + = commit.author_name + - else + %a.muted{:href => user_url(commit.author), :style => "color:#333333;text-decoration:none;"} + = commit.author.name + %tr.spacer + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} +   + %tr.success-message + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"} + - build_count = @pipeline.statuses.latest.size + - stage_count = @pipeline.stages.size + Pipeline + %a{:href => namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id), :style => "color:#3084bb;text-decoration:none;"} + = "\##{@pipeline.id}" + successfully completed + = "#{build_count} #{'build'.pluralize(build_count)}" + in + = "#{stage_count} #{'stage'.pluralize(stage_count)}." + %tr.footer + %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} + %img{:alt => "GitLab", :height => "33", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), :style => "display:block;margin:0 auto 1em;", :width => "90"}/ + %div + %a{:href => profile_notifications_url, :style => "color:#3084bb;text-decoration:none;"} Manage all notifications + · + %a{:href => help_url, :style => "color:#3084bb;text-decoration:none;"} Help + %div GitLab, 1233 Howard St 2F, San Francisco, CA 94103, USA -- cgit v1.2.1 From 585e02546fa08c62ae53a8f951b01bbc8f6fb08a Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 5 Oct 2016 23:19:28 -0500 Subject: update plain text version of pipeline success email --- app/views/notify/pipeline_success_email.text.erb | 30 ++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index 6821d1f4459..9a0f839d089 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -1,7 +1,23 @@ -Project: <%= @project.path_with_namespace %> -Branch: <%= @pipeline.ref %> -Commit: <%= @pipeline.short_sha %> (<%= @pipeline.sha %>) -Commit Message: <%= @pipeline.git_commit_message %> -Commit Author: <%= @pipeline.git_author_name %> -Pusher: <%= @pipeline.user.try(:name) %> -Pipeline #<%= @pipeline.id %> had succeeded. +Success! Your CI pipeline has passed. + +Project: <%= @project.name %> ( <%= namespace_project_url(@project.namespace, @project) %> ) +Branch: <%= @pipeline.ref %> ( <%= namespace_project_commits_url(@project.namespace, @project, @pipeline.ref) %> ) +<% if @merge_request -%> +Merge Request: <%= @merge_request.to_reference %> ( <%= namespace_project_merge_request_url(@project.namespace, @project, @merge_request) %> ) +<% end -%> + +Commit: <%= @pipeline.short_sha %> ( <%= namespace_project_commit_url(@project.namespace, @project, @pipeline.sha) %> ) +Commit Message: <%= @pipeline.git_commit_message.truncate(50) %> +<% commit = @pipeline.commit -%> +<% if commit.author.nil? -%> +Commit Author: <%= commit.author_name %> +<% else -%> +Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> ) +<% end -%> + +<% build_count = @pipeline.statuses.latest.size -%> +<% stage_count = @pipeline.stages.size -%> +Pipeline #<%= @pipeline.id %> ( <%= namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. + +Manage all notifications: <%= profile_notifications_url %> +Help: <%= help_url %> -- cgit v1.2.1 From 04e9d799933c85a45027dba58e0d3dc63c2681fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 6 Oct 2016 08:37:08 +0200 Subject: Add 8.12.4 CHANGELOG entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d72a5ad1ddd..9fe62b87cb8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,13 +59,21 @@ v 8.13.0 (unreleased) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container -v 8.12.4 (unreleased) - - Fix type mismatch bug when closing Jira issue - - Skip wiki creation when GitHub project has wiki enabled - - Fix failed project deletion when feature visibility set to private - - Fix issues importing services via Import/Export - - Restrict failed login attempts for users with 2FA enabled - - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. (lukehowell) +v 8.12.5 (unreleased) + +v 8.12.4 + - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) + - Fix padding in build sidebar. !6506 + - Changed compare dropdowns to dropdowns with isolated search input. !6550 + - Fix race condition on LFS Token. !6592 + - Fix type mismatch bug when closing Jira issue. !6619 + - Fix lint-doc error. !6623 + - Skip wiki creation when GitHub project has wiki enabled. !6665 + - Fix issues importing services via Import/Export. !6667 + - Restrict failed login attempts for users with 2FA enabled. !6668 + - Fix failed project deletion when feature visibility set to private. !6688 + - Prevent claiming associated model IDs via import. + - Set GitLab project exported file permissions to owner only v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -- cgit v1.2.1 From f90b5d5d438e77a6e849f1cc2a3b27fd1dac7ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 6 Oct 2016 10:11:24 +0200 Subject: Fix CHANGELOG and wrong conflict resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 8 -------- app/assets/javascripts/labels_select.js | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9fe62b87cb8..68962f20d0b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -94,14 +94,6 @@ v 8.12.2 - Fix resolve discussion buttons endpoint path - Refactor remnants of CoffeeScript destructured opts and super !6261 -v 8.12.4 (unreleased) - - Set GitLab project exported file permissions to owner only - - Don't send Private-Token (API authentication) headers to Sentry - -v 8.12.2 (unreleased) - - Fix Import/Export not recognising correctly the imported services. - - Respect the fork_project permission when forking projects - v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix issue with search filter labels not displaying diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 66cc3f99b0f..e356872624a 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -4,7 +4,7 @@ var _this; _this = this; $('.js-label-select').each(function(i, dropdown) { - var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove; + var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove; $dropdown = $(dropdown); $toggleText = $dropdown.find('.dropdown-toggle-text'); namespacePath = $dropdown.data('namespace-path'); -- cgit v1.2.1 From 217244074fbf78e2aabe1a5019ae0e9c0501e258 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 4 Oct 2016 15:59:11 +0200 Subject: Move MWBS trigger from build to pipeline event --- app/models/ci/pipeline.rb | 16 +++++++++------- app/models/commit_status.rb | 4 ---- .../add_todo_when_build_fails_service.rb | 4 ++-- app/services/merge_requests/base_service.rb | 21 ++++++++++----------- .../merge_when_build_succeeds_service.rb | 9 +++++---- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2cf9892edc5..040ba20eb09 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -56,6 +56,10 @@ module Ci pipeline.finished_at = Time.now end + before_transition do |pipeline| + pipeline.update_duration + end + after_transition [:created, :pending] => :running do |pipeline| MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) @@ -66,8 +70,8 @@ module Ci update_all(latest_build_finished_at: pipeline.finished_at) end - before_transition do |pipeline| - pipeline.update_duration + after_transition [:created, :pending, :running] => :success do |pipeline| + MergeRequests::MergeWhenBuildSucceedsService.new(pipeline.project, nil).trigger(pipeline) end after_transition do |pipeline, transition| @@ -292,11 +296,9 @@ module Ci # Merge requests for which the current pipeline is running against # the merge request's latest commit. def merge_requests - @merge_requests ||= - begin - project.merge_requests.where(source_branch: self.ref). - select { |merge_request| merge_request.pipeline.try(:id) == self.id } - end + @merge_requests ||= project.merge_requests + .where(source_branch: self.ref) + .select { |merge_request| merge_request.pipeline.try(:id) == self.id } end private diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 9fa8d17e74e..5a240bcf2c6 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -98,10 +98,6 @@ class CommitStatus < ActiveRecord::Base true end - after_transition [:created, :pending, :running] => :success do |commit_status| - MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) - end - after_transition any => :failed do |commit_status| MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) end diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb index 566049525cb..d572a928a42 100644 --- a/app/services/merge_requests/add_todo_when_build_fails_service.rb +++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb @@ -2,14 +2,14 @@ module MergeRequests class AddTodoWhenBuildFailsService < MergeRequests::BaseService # Adds a todo to the parent merge_request when a CI build fails def execute(commit_status) - each_merge_request(commit_status) do |merge_request| + commit_status_merge_requests(commit_status) do |merge_request| todo_service.merge_request_build_failed(merge_request) end end # Closes any pending build failed todos for the parent MRs when a build is retried def close(commit_status) - each_merge_request(commit_status) do |merge_request| + commit_status_merge_requests(commit_status) do |merge_request| todo_service.merge_request_build_retried(merge_request) end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index d0d155b7ee1..a726dd98697 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -42,11 +42,9 @@ module MergeRequests super(:merge_request) end - def merge_request_from(commit_status) - branches = commit_status.ref - + def merge_requests_for(branches, sha) # This is for ref-less builds - branches ||= @project.repository.branch_names_contains(commit_status.sha) + branches ||= @project.repository.branch_names_contains(sha) return [] if branches.blank? @@ -56,14 +54,15 @@ module MergeRequests merge_requests.uniq.select(&:source_project) end - def each_merge_request(commit_status) - merge_request_from(commit_status).each do |merge_request| - pipeline = merge_request.pipeline - - next unless pipeline - next unless pipeline.sha == commit_status.sha + def pipeline_merge_requests(pipeline) + merge_requests_for(pipeline.ref, pipeline.sha).each do |merge_request| + yield merge_request + end + end - yield merge_request, pipeline + def commit_status_merge_requests(commit_status) + merge_requests_for(commit_status.ref, commit_status.sha).each do |merge_request| + yield merge_request end end end diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index 4ad5fb08311..dc159de0058 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -18,12 +18,13 @@ module MergeRequests merge_request.save end - # Triggers the automatic merge of merge_request once the build succeeds - def trigger(commit_status) - each_merge_request(commit_status) do |merge_request, pipeline| + # Triggers the automatic merge of merge_request once the pipeline succeeds + def trigger(pipeline) + return unless pipeline.success? + + pipeline_merge_requests(pipeline) do |merge_request| next unless merge_request.merge_when_build_succeeds? next unless merge_request.mergeable? - next unless pipeline.success? MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) end -- cgit v1.2.1 From 25c26f603fbeafcbe21bd1b81d9b1fe8c3b66296 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 6 Oct 2016 10:48:19 +0200 Subject: Clarify some more the Cycle Analytics workflow example [ci skip] --- doc/user/project/cycle_analytics.md | 38 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index 7a40f3dad03..c16058165d7 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -41,8 +41,8 @@ You can see that there are seven stages in total: - Median time from when the merge request got merged until the deploy to production (production is last stage/environment) - **Production** (Total) - - Sum of all the above stages excluding the Test (CI) time. To clarify, it's - not so much that CI time is "excluded", but rather CI time is already + - Sum of all the above stages' times excluding the Test (CI) time. To clarify, + it's not so much that CI time is "excluded", but rather CI time is already counted in the review stage since CI is done automatically. Most of the other stages are purely sequential, but **Test** is not. @@ -66,20 +66,19 @@ Below you can see in more detail what the various stages of Cycle Analytics mean | Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. `master` is not excluded. It does not attempt to track time for any particular stages. | | Review | Measures the median time taken to review the merge request, between its creation and until it's merged. | | Staging | Measures the median time between merging the merge request until the very first deployment to production. It's tracked by the [environment] set to `production` (case-sensitive, `Production` won't work) in your GitLab CI configuration. If there isn't a `production` environment, this is not tracked. | -| Production| The sum of all time taken to run the entire process, from issue creation to deploying the code to production. | +| Production| The sum of all time (medians) taken to run the entire process, from issue creation to deploying the code to production. | --- Here's a little explanation of how this works behind the scenes: 1. Issues and merge requests are grouped together in pairs, such that for each - `` pair, the merge request has `Fixes #xxx` for the - corresponding issue. All other issues and merge requests are **not** considered. - + `` pair, the merge request has the [issue closing pattern] + for the corresponding issue. All other issues and merge requests are **not** + considered. 1. Then the pairs are filtered out. Any merge request that has **not** been deployed to production in the last XX days (specified by the UI - default is 90 days) prohibits these pairs from being considered. - 1. For the remaining `` pairs, we check the information that we need for the stages, like issue creation date, merge request merge time, etc. @@ -108,12 +107,15 @@ environments is configured. 1. Push branch and create a merge request that contains the [issue closing pattern] in its description at 14:00 (stop of **Code** stage / start of **Test** and **Review** stages). -1. The test suite starts running and takes 5min (stop of **Test** stage). +1. The CI starts running your scripts defined in [`.gitlab-ci.yml`][yml] and + takes 5min (stop of **Test** stage). 1. Review merge request, ensure that everything is OK and merge the merge request at 19:00. (stop of **Review** stage / start of **Staging** stage). 1. Now that the merge request is merged, a deployment to the `production` environment starts and finishes at 19:30 (stop of **Staging** stage). -1. The cycle completes and the sum of it is shown in the **Production** stage. +1. The cycle completes and the sum of the median times of the previous stages + is recorded to the **Production** stage. That is the time between creating an + issue and deploying its relevant merge request to production. From the above example you can conclude the time it took each stage to complete as long as their total time: @@ -124,19 +126,22 @@ as long as their total time: - **Test**: 5min - **Review**: 5h (19:00 - 14:00) - **Staging**: 30min (19:30 - 19:00) -- **Production**: 10h 30min (19:30 - 09:00) +- **Production**: Since this stage measures the sum of median time off all + previous stages, we cannot calculate it if we don't know the status of the + stages before. In case this is the very first cycle that is run in the project, + then the **Production** time is 10h 30min (19:30 - 09:00) A few notes: +- In the above example we demonstrated that it doesn't matter if your first + commit doesn't mention the issue number, you can do this later in any commit + of the branch you are working on. - You can see that the **Test** stage is not calculated to the overall time of the cycle since it is included in the **Review** process (every MR should be tested). - ---- - -The example above was just one cycle of the seven stages. Add multiple cycles, -calculate their median time and the result is what the dashboard of Cycle -Analytics is showing. +- The example above was just **one cycle** of the seven stages. Add multiple + cycles, calculate their median time and the result is what the dashboard of + Cycle Analytics is showing. ## Permissions @@ -164,3 +169,4 @@ Learn more about Cycle Analytics in the following resources: [idea to production]: https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab [issue closing pattern]: issues/automatic_issue_closing.md [permissions]: ../permissions.md +[yml]: ../../ci/yaml/README.md -- cgit v1.2.1 From 640a4c88341f790ede78f845d29a37634030581f Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 3 Oct 2016 16:42:02 +0200 Subject: Use higher size on Gitlab::Redis connection pool on Sidekiq servers --- CHANGELOG | 1 + lib/gitlab/redis.rb | 12 +++++++++++- spec/lib/gitlab/redis_spec.rb | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..649215db31c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,7 @@ v 8.13.0 (unreleased) - Append issue template to existing description !6149 (Joseph Frazier) - Trending projects now only show public projects and the list of projects is cached for a day - Revoke button in Applications Settings underlines on hover. + - Use higher size on Gitlab::Redis connection pool on Sidekiq servers - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index 3faab937726..c649da8c426 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -24,10 +24,20 @@ module Gitlab end def with - @pool ||= ConnectionPool.new { ::Redis.new(params) } + @pool ||= ConnectionPool.new(size: pool_size) { ::Redis.new(params) } @pool.with { |redis| yield redis } end + def pool_size + if Sidekiq.server? + # the pool will be used in a multi-threaded context + Sidekiq.options[:concurrency] + 5 + else + # probably this is a Unicorn process, so single threaded + 5 + end + end + def _raw_config return @_raw_config if defined?(@_raw_config) diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb index cb54c020b31..74ff85e132a 100644 --- a/spec/lib/gitlab/redis_spec.rb +++ b/spec/lib/gitlab/redis_spec.rb @@ -88,6 +88,34 @@ describe Gitlab::Redis do end end + describe '.with' do + before { clear_pool } + after { clear_pool } + + context 'when running not on sidekiq workers' do + before { allow(Sidekiq).to receive(:server?).and_return(false) } + + it 'instantiates a connection pool with size 5' do + expect(ConnectionPool).to receive(:new).with(size: 5).and_call_original + + described_class.with { |_redis| true } + end + end + + context 'when running on sidekiq workers' do + before do + allow(Sidekiq).to receive(:server?).and_return(true) + allow(Sidekiq).to receive(:options).and_return({ concurrency: 18 }) + end + + it 'instantiates a connection pool with a size based on the concurrency of the worker' do + expect(ConnectionPool).to receive(:new).with(size: 18 + 5).and_call_original + + described_class.with { |_redis| true } + end + end + end + describe '#raw_config_hash' do it 'returns default redis url when no config file is present' do expect(subject).to receive(:fetch_config) { false } @@ -114,4 +142,10 @@ describe Gitlab::Redis do rescue NameError # raised if @_raw_config was not set; ignore end + + def clear_pool + described_class.remove_instance_variable(:@pool) + rescue NameError + # raised if @pool was not set; ignore + end end -- cgit v1.2.1 From 333c02a8c822dfab1b98c55db8fb8daa4c53bd51 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Wed, 5 Oct 2016 21:20:00 +0200 Subject: Fix broken handling of certain calls in GitHub importer client Closes #22998 --- lib/gitlab/github_import/client.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb index e33ac61f5ae..7f424b74efb 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -102,9 +102,19 @@ module Gitlab def request(method, *args, &block) sleep rate_limit_sleep_time if rate_limit_exceed? - data = api.send(method, *args, &block) - yield data + data = api.send(method, *args) + return data unless data.is_a?(Array) + if block_given? + yield data + each_response_page(&block) + else + each_response_page { |page| data.concat(page) } + data + end + end + + def each_response_page last_response = api.last_response while last_response.rels[:next] -- cgit v1.2.1 From c65ea7a5c30ee44c859ac711f1054dab734bdb89 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 16 Sep 2016 10:45:23 +0100 Subject: Merge request tabs stick when scrolling page Closes #20548 --- CHANGELOG | 1 + app/assets/javascripts/merge_request_tabs.js | 38 +++++++++++++++++++++++ app/assets/stylesheets/pages/merge_requests.scss | 9 ++++++ app/views/projects/merge_requests/_show.html.haml | 2 +- spec/features/merge_requests/sticky_tabs_spec.rb | 26 ++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 spec/features/merge_requests/sticky_tabs_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..5b68a670507 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -194,6 +194,7 @@ v 8.12.0 - Remove prefixes from transition CSS property (ClemMakesApps) - Add Sentry logging to API calls - Add BroadcastMessage API + - Merge request tabs are fixed when scrolling page - Use 'git update-ref' for safer web commits !6130 - Sort pipelines requested through the API - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index bec11a198a1..f685af7d97e 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -68,6 +68,7 @@ this._location = location; this.bindEvents(); this.activateTab(this.opts.action); + this.initAffix(); } MergeRequestTabs.prototype.bindEvents = function() { @@ -367,6 +368,43 @@ // Only when sidebar is collapsed }; + MergeRequestTabs.prototype.initAffix = function () { + // Screen space on small screens is usually very sparse + // So we dont affix the tabs on these + if (Breakpoints.get().getBreakpointSize() === 'xs') return; + + var $tabs = $('.js-tabs-affix'), + tabsWidth = $tabs.outerWidth(), + $diffTabs = $('#diff-notes-app'), + offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); + + $tabs.off('affix.bs.affix affix-top.bs.affix') + .affix({ + offset: offsetTop + }).on('affix.bs.affix', function () { + $tabs.css({ + left: $tabs.offset().left, + width: tabsWidth + }); + $diffTabs.css({ + marginTop: $tabs.height() + }); + }).on('affix-top.bs.affix', function () { + $tabs.css({ + left: '', + width: '' + }); + $diffTabs.css({ + marginTop: '' + }); + }); + + // Fix bug when reloading the page already scrolling + if ($tabs.hasClass('affix')) { + $tabs.trigger('affix.bs.affix'); + } + }; + return MergeRequestTabs; })(); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..d01aaa4be51 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -421,3 +421,12 @@ margin-bottom: 20px; } } + +.merge-request-tabs { + background-color: #fff; + + &.affix { + top: 100px; + z-index: 9; + } +} diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 9f34ca9ff4e..351c9d6ff91 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -47,7 +47,7 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits_count.nonzero? - %ul.merge-request-tabs.nav-links.no-top.no-bottom + %ul.merge-request-tabs.nav-links.no-top.no-bottom.js-tabs-affix %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion diff --git a/spec/features/merge_requests/sticky_tabs_spec.rb b/spec/features/merge_requests/sticky_tabs_spec.rb new file mode 100644 index 00000000000..6f8c3dc55f4 --- /dev/null +++ b/spec/features/merge_requests/sticky_tabs_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +feature 'Merge request tabs', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } + + before do + project.team << [user, :master] + login_as user + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'affixes to top of page when scrolling' do + page.execute_script "window.scrollBy(0,10000)" + expect(page).to have_selector('.js-tabs-affix.affix') + end + + it 'removes affix when scrolling to top' do + page.execute_script "window.scrollBy(0,10000)" + expect(page).to have_selector('.js-tabs-affix.affix') + + page.execute_script "window.scrollBy(0,-10000)" + expect(page).to have_selector('.js-tabs-affix.affix-top') + end +end -- cgit v1.2.1 From 8962a3359c4282c755fff0fa08deeb64d74543a0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 16 Sep 2016 11:21:45 +0100 Subject: Fixed merge request tab JS spec --- spec/javascripts/merge_request_tabs_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 395032a7416..96ee5235acf 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -1,5 +1,6 @@ /*= require merge_request_tabs */ +//= require breakpoints (function() { describe('MergeRequestTabs', function() { -- cgit v1.2.1 From 97dc95b18e5a5c29daab84e0d65522b3d3158dc1 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 16 Sep 2016 14:32:43 +0100 Subject: Fixed tests --- app/assets/javascripts/merge_request_tabs.js | 15 +++++++++------ spec/features/merge_requests/sticky_tabs_spec.rb | 10 +++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index f685af7d97e..1a214cdd00e 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -369,18 +369,21 @@ }; MergeRequestTabs.prototype.initAffix = function () { + var $tabs = $('.js-tabs-affix'); + // Screen space on small screens is usually very sparse // So we dont affix the tabs on these - if (Breakpoints.get().getBreakpointSize() === 'xs') return; + if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; - var $tabs = $('.js-tabs-affix'), - tabsWidth = $tabs.outerWidth(), - $diffTabs = $('#diff-notes-app'), - offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); + var tabsWidth = $tabs.outerWidth(), + $diffTabs = $('#diff-notes-app'), + offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); $tabs.off('affix.bs.affix affix-top.bs.affix') .affix({ - offset: offsetTop + offset: { + top: offsetTop + } }).on('affix.bs.affix', function () { $tabs.css({ left: $tabs.offset().left, diff --git a/spec/features/merge_requests/sticky_tabs_spec.rb b/spec/features/merge_requests/sticky_tabs_spec.rb index 6f8c3dc55f4..e78f3f134d5 100644 --- a/spec/features/merge_requests/sticky_tabs_spec.rb +++ b/spec/features/merge_requests/sticky_tabs_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Merge request tabs', js: true, feature: true do + include WaitForAjax + let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } @@ -9,18 +11,20 @@ feature 'Merge request tabs', js: true, feature: true do project.team << [user, :master] login_as user visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + + wait_for_ajax end it 'affixes to top of page when scrolling' do - page.execute_script "window.scrollBy(0,10000)" + page.execute_script "window.scroll(0,10000)" expect(page).to have_selector('.js-tabs-affix.affix') end it 'removes affix when scrolling to top' do - page.execute_script "window.scrollBy(0,10000)" + page.execute_script "window.scroll(0,10000)" expect(page).to have_selector('.js-tabs-affix.affix') - page.execute_script "window.scrollBy(0,-10000)" + page.execute_script "window.scroll(0,-10000)" expect(page).to have_selector('.js-tabs-affix.affix-top') end end -- cgit v1.2.1 From ef1b5988af68a2811c079f785351e30fa8afd347 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Oct 2016 09:50:50 +0100 Subject: Tests update --- spec/features/merge_requests/sticky_tabs_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/features/merge_requests/sticky_tabs_spec.rb b/spec/features/merge_requests/sticky_tabs_spec.rb index e78f3f134d5..3e8bd768324 100644 --- a/spec/features/merge_requests/sticky_tabs_spec.rb +++ b/spec/features/merge_requests/sticky_tabs_spec.rb @@ -17,14 +17,17 @@ feature 'Merge request tabs', js: true, feature: true do it 'affixes to top of page when scrolling' do page.execute_script "window.scroll(0,10000)" + expect(page).to have_selector('.js-tabs-affix.affix') end it 'removes affix when scrolling to top' do page.execute_script "window.scroll(0,10000)" + expect(page).to have_selector('.js-tabs-affix.affix') page.execute_script "window.scroll(0,-10000)" + expect(page).to have_selector('.js-tabs-affix.affix-top') end end -- cgit v1.2.1 From ca3c0c6cd915d44ec2d409b04ab05d964bd5a403 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 11 Aug 2016 09:00:17 +0200 Subject: MergeRequest new form load diff asynchronously --- CHANGELOG | 1 + app/assets/javascripts/lib/utils/common_utils.js | 5 ++ app/assets/javascripts/merge_request_tabs.js | 23 ++++++-- .../projects/merge_requests_controller.rb | 66 ++++++++++++++-------- app/models/merge_request.rb | 4 +- app/views/projects/diffs/_diffs.html.haml | 3 +- .../projects/merge_requests/_new_diffs.html.haml | 1 + .../projects/merge_requests/_new_submit.html.haml | 28 +++++---- config/routes/project.rb | 1 + features/project/source/browse_files.feature | 17 ++++-- features/steps/project/source/browse_files.rb | 4 ++ 11 files changed, 101 insertions(+), 52 deletions(-) create mode 100644 app/views/projects/merge_requests/_new_diffs.html.haml diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..91925847cab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.13.0 (unreleased) - Replace `alias_method_chain` with `Module#prepend` - Enable GitLab Import/Export for non-admin users. - Preserve label filters when sorting !6136 (Joseph Frazier) + - MergeRequest#new form load diff asynchronously - Only update issuable labels if they have been changed - Take filters in account in issuable counters. !6496 - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 9299d0eabd2..b170e26eebf 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -38,6 +38,11 @@ gl.utils.getPagePath = function() { return $('body').data('page').split(':')[0]; }; + gl.utils.parseUrl = function (url) { + var parser = document.createElement('a'); + parser.href = url; + return parser; + }; return jQuery.timefor = function(time, suffix, expiredLabel) { var suffixFromNow, timefor; if (!time) { diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index bec11a198a1..8045d24a1bb 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -61,6 +61,9 @@ function MergeRequestTabs(opts) { this.opts = opts != null ? opts : {}; this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true; + + this.buildsLoaded = this.opts.buildsLoaded || false; + this.setCurrentAction = bind(this.setCurrentAction, this); this.tabShown = bind(this.tabShown, this); this.showTab = bind(this.showTab, this); @@ -93,7 +96,7 @@ this.loadCommits($target.attr('href')); this.expandView(); this.resetViewContainer(); - } else if (action === 'diffs') { + } else if (this.isDiffAction(action)) { this.loadDiff($target.attr('href')); if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') { this.shrinkView(); @@ -170,8 +173,9 @@ action = 'notes'; } this.currentAction = action; - // Remove a trailing '/commits' or '/diffs' - new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines)(\.html)?\/?$/, ''); + // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs' + new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); + // Append the new action if we're on a tab other than 'notes' if (action !== 'notes') { new_state += "/" + action; @@ -210,8 +214,13 @@ if (this.diffsLoaded) { return; } + + // We extract pathname for the current Changes tab anchor href + // some pages like MergeRequestsController#new has query parameters on that anchor + var url = gl.utils.parseUrl(source); + return this._get({ - url: (source + ".json") + this._location.search, + url: (url.pathname + ".json") + this._location.search, success: (function(_this) { return function(data) { $('#diffs').html(data.html); @@ -223,7 +232,7 @@ gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); $('#diffs .js-syntax-highlight').syntaxHighlight(); $('#diffs .diff-file').singleFileDiff(); - if (_this.diffViewType() === 'parallel' && _this.currentAction === 'diffs') { + if (_this.diffViewType() === 'parallel' && (_this.isDiffAction(_this.currentAction)) ) { _this.expandViewContainer(); } _this.diffsLoaded = true; @@ -324,6 +333,10 @@ return $('.inline-parallel-buttons a.active').data('view-type'); }; + MergeRequestTabs.prototype.isDiffAction = function(action) { + return action === 'diffs' || action === 'new/diffs' + }; + MergeRequestTabs.prototype.expandViewContainer = function() { var $wrapper = $('.content-wrapper .container-fluid'); if (this.fixedLayoutPref === null) { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8c8c56228ad..ffd9833e3b1 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -19,6 +19,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :define_diff_comment_vars, only: [:diffs] before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] + before_action :apply_diff_view_cookie!, only: [:new_diffs] + before_action :build_merge_request, only: [:new, :new_diffs] # Allow read any merge_request before_action :authorize_read_merge_request! @@ -210,29 +212,26 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def new - apply_diff_view_cookie! - - build_merge_request - @noteable = @merge_request - - @target_branches = if @merge_request.target_project - @merge_request.target_project.repository.branch_names - else - [] - end - - @target_project = merge_request.target_project - @source_project = merge_request.source_project - @commits = @merge_request.compare_commits.reverse - @commit = @merge_request.diff_head_commit - @base_commit = @merge_request.diff_base_commit - @diffs = @merge_request.diffs(diff_options) if @merge_request.compare - @diff_notes_disabled = true - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses.relevant if @pipeline + define_new_vars + end - @note_counts = Note.where(commit_id: @commits.map(&:id)). - group(:commit_id).count + def new_diffs + respond_to do |format| + format.html do + define_new_vars + render "new" + end + format.json do + @diffs = if @merge_request.can_be_created + @merge_request.diffs(diff_options) + else + [] + end + @diff_notes_disabled = true + + render json: { html: view_to_html_string('projects/merge_requests/_new_diffs', diffs: @diffs) } + end + end end def create @@ -490,6 +489,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController ) end + def define_new_vars + @noteable = @merge_request + + @target_branches = if @merge_request.target_project + @merge_request.target_project.repository.branch_names + else + [] + end + + @target_project = merge_request.target_project + @source_project = merge_request.source_project + @commits = @merge_request.compare_commits.reverse + @commit = @merge_request.diff_head_commit + @base_commit = @merge_request.diff_base_commit + + @pipeline = @merge_request.pipeline + @statuses = @pipeline.statuses.relevant if @pipeline + @note_counts = Note.where(commit_id: @commits.map(&:id)). + group(:commit_id).count + end + def invalid_mr # Render special view for MR with removed target branch render 'invalid' @@ -521,7 +541,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def build_merge_request params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) - @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute + @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute end def compared_diff_version diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 071dfe54ef9..a743bf313ae 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -31,7 +31,7 @@ class MergeRequest < ActiveRecord::Base # Temporary fields to store compare vars # when creating new merge request - attr_accessor :can_be_created, :compare_commits, :compare + attr_accessor :can_be_created, :compare_commits, :diff_options, :compare state_machine :state, initial: :opened do event :close do @@ -196,7 +196,7 @@ class MergeRequest < ActiveRecord::Base end def diff_size - merge_request_diff.size + diffs(diff_options).size end def diff_base_commit diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 576e7ef021a..067cf595da3 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,4 +1,5 @@ - show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true) +- can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project) - diff_files = diffs.diff_files .content-block.oneline-block.files-changed @@ -20,7 +21,7 @@ - if diff_files.overflow? = render 'projects/diffs/warning', diff_files: diff_files -.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, diffs.project))}} +.files{ data: { can_create_note: can_create_note } } - diff_files.each_with_index do |diff_file, index| - diff_commit = commit_for_diff(diff_file) - blob = diff_file.blob(diff_commit) diff --git a/app/views/projects/merge_requests/_new_diffs.html.haml b/app/views/projects/merge_requests/_new_diffs.html.haml new file mode 100644 index 00000000000..74367ab9b7b --- /dev/null +++ b/app/views/projects/merge_requests/_new_diffs.html.haml @@ -0,0 +1 @@ += render "projects/diffs/diffs", diffs: @diffs, show_whitespace_toggle: false diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 00bd4e143df..88d8013a0d1 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -19,34 +19,32 @@ .mr-compare.merge-request %ul.merge-request-tabs.nav-links.no-top.no-bottom - %li.commits-tab + %li.commits-tab.active = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do Commits %span.badge= @commits.size - if @pipeline - %li.builds-tab.active + %li.builds-tab = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do Builds %span.badge= @statuses.size - %li.diffs-tab.active - = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do + %li.diffs-tab + = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do Changes - %span.badge= @diffs.real_size + %span.badge= @merge_request.diff_size .tab-content - #commits.commits.tab-pane + #commits.commits.tab-pane.active = render "projects/merge_requests/show/commits" - #diffs.diffs.tab-pane.active - - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - .alert.alert-danger - %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. - %p To preserve performance the line changes are not shown. - - else - = render "projects/diffs/diffs", diffs: @diffs, show_whitespace_toggle: false + #diffs.diffs.tab-pane + - # This tab is always loaded via AJAX - if @pipeline #builds.builds.tab-pane = render "projects/merge_requests/show/builds" + .mr-loading-status + = spinner + :javascript $('.assign-to-me-link').on('click', function(e){ $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); @@ -54,6 +52,6 @@ }); :javascript var merge_request = new MergeRequest({ - action: "#{(@show_changes_tab ? 'diffs' : 'new')}", - setUrl: false + action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}", + buildsLoaded: "#{@pipeline ? 'true' : 'false'}" }); diff --git a/config/routes/project.rb b/config/routes/project.rb index 224ec7e8324..0c188478ed1 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -285,6 +285,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: get :update_branches get :diff_for_path post :bulk_update + get :new_diffs, path: 'new/diffs' end resources :discussions, only: [], constraints: { id: /\h{40}/ } do diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index fdffd71de85..d4b91fec6e8 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -71,6 +71,7 @@ Feature: Project Source Browse Files And I fill the new branch name And I click on "Commit Changes" Then I am redirected to the new merge request page + When I click on "Changes" tab And I should see its new content @javascript @@ -80,9 +81,10 @@ Feature: Project Source Browse Files And I fill the upload file commit message And I fill the new branch name And I click on "Upload file" - Then I can see the new text file + Then I can see the new commit message And I am redirected to the new merge request page - And I can see the new commit message + When I click on "Changes" tab + Then I can see the new text file @javascript Scenario: I can upload file and commit when I don't have write access @@ -93,9 +95,10 @@ Feature: Project Source Browse Files And I upload a new text file And I fill the upload file commit message And I click on "Upload file" - Then I can see the new text file + Then I can see the new commit message And I am redirected to the fork's new merge request page - And I can see the new commit message + When I click on "Changes" tab + Then I can see the new text file @javascript Scenario: I can replace file and commit @@ -119,9 +122,10 @@ Feature: Project Source Browse Files And I replace it with a text file And I fill the replace file commit message And I click on "Replace file" - Then I can see the new text file - And I am redirected to the fork's new merge request page And I can see the replacement commit message + And I am redirected to the fork's new merge request page + When I click on "Changes" tab + Then I can see the new text file @javascript Scenario: If I enter an illegal file name I see an error message @@ -191,6 +195,7 @@ Feature: Project Source Browse Files And I fill the new branch name And I click on "Commit Changes" Then I am redirected to the new merge request page + Then I click on "Changes" tab And I should see its new content @javascript @wip diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index bb79424ee08..1cc9e37b075 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -105,6 +105,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps click_button 'Commit Changes' end + step 'I click on "Changes" tab' do + click_link 'Changes' + end + step 'I click on "Create directory"' do click_button 'Create directory' end -- cgit v1.2.1 From c8c9ccbd1994668f65cbac3fbdf9dcc4863b3467 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 10:46:51 +0100 Subject: Adds verification beore showing user avatar --- app/views/projects/deployments/_deployment.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index 2edbf7f5082..2ea3b477bb7 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -9,8 +9,9 @@ - if deployment.deployable = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do = "#{deployment.deployable.name} (##{deployment.deployable.id})" - by - = user_avatar(user: deployment.user, size: 20) + - if deployment.user + by + = user_avatar(user: deployment.user, size: 20) %td #{time_ago_with_tooltip(deployment.created_at)} -- cgit v1.2.1 From 4241c2906c9531ab7ddb43740b222a102f5508fa Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Oct 2016 13:38:12 +0100 Subject: Add new issue form to lists Closes #21219 --- .../javascripts/boards/components/board.js.es6 | 8 +++++- .../boards/components/board_list.js.es6 | 7 ++++-- .../boards/components/board_new_issue.js.es6 | 24 ++++++++++++++++++ app/assets/stylesheets/pages/boards.scss | 26 +++++++++++++++++++ .../projects/boards/components/_board.html.haml | 29 ++++++++++++++++++++-- 5 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/boards/components/board_new_issue.js.es6 diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6 index 7e86f001f44..cacb36a897f 100644 --- a/app/assets/javascripts/boards/components/board.js.es6 +++ b/app/assets/javascripts/boards/components/board.js.es6 @@ -21,7 +21,8 @@ }, data () { return { - filters: Store.state.filters + filters: Store.state.filters, + showIssueForm: false }; }, watch: { @@ -33,6 +34,11 @@ deep: true } }, + methods: { + showNewIssueForm() { + this.showIssueForm = !this.showIssueForm; + } + }, ready () { const options = gl.issueBoards.getBoardSortableDefaultOptions({ disabled: this.disabled, diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 474805c1437..d2e905ae062 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -1,4 +1,5 @@ //= require ./board_card +//= require ./board_new_issue (() => { const Store = gl.issueBoards.BoardsStore; @@ -8,14 +9,16 @@ gl.issueBoards.BoardList = Vue.extend({ components: { - 'board-card': gl.issueBoards.BoardCard + 'board-card': gl.issueBoards.BoardCard, + 'board-new-issue': gl.issueBoards.BoardNewIssue }, props: { disabled: Boolean, list: Object, issues: Array, loading: Boolean, - issueLinkBase: String + issueLinkBase: String, + showIssueForm: Boolean }, data () { return { diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 new file mode 100644 index 00000000000..057844f3ebb --- /dev/null +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -0,0 +1,24 @@ +(() => { + window.gl = window.gl || {}; + + gl.issueBoards.BoardNewIssue = Vue.extend({ + props: { + showIssueForm: Boolean + }, + data() { + return { + title: '' + }; + }, + methods: { + submit(e) { + e.preventDefault(); + + this.title = ''; + }, + cancel() { + this.showIssueForm = false; + } + } + }); +})(); diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index ecc5b24e360..46e92b8a187 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -233,3 +233,29 @@ lex margin-right: 5px; } } + +.board-issue-count-holder { + margin-top: -3px; + + .btn { + line-height: 12px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +} + +.board-issue-count { + padding-right: 10px; + padding-left: 10px; + line-height: 21px; + border-radius: $border-radius-base; + border-color: $border-color; + border-style: solid; + border-width: 1px 1px 1px 1px; + + &.has-btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-width: 1px 0 1px 1px; + } +} diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 73066150fb3..6c95812aebd 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -12,8 +12,15 @@ %header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } %h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" } {{ list.title }} - %span.pull-right{ "v-if" => "list.type !== 'blank'" } - {{ list.issuesSize }} + .board-issue-count-holder.pull-right.clearfix + %span.board-issue-count.pull-left{ "v-if" => "list.type !== 'blank'", + ":class" => "{ 'has-btn': list.type !== 'done' }" } + {{ list.issuesSize }} + - if can? current_user, :create_issue, @project + %button.btn.btn-small.btn-default.pull-right{ type: "button", + "@click" => "showNewIssueForm", + "v-if" => "list.type !== 'done'" } + = icon("plus") - if can?(current_user, :admin_list, @project) %board-delete{ "inline-template" => true, ":list" => "list", @@ -26,12 +33,30 @@ ":issues" => "list.issues", ":loading" => "list.loading", ":disabled" => "disabled", + ":show-issue-form.sync" => "showIssueForm", ":issue-link-base" => "issueLinkBase" } .board-list-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") %ul.board-list{ "v-el:list" => true, "v-show" => "!loading", ":data-board" => "list.id" } + - if can? current_user, :create_issue, @project + %board-new-issue{ "inline-template" => true, + ":show-issue-form.sync" => "showIssueForm", + "v-if" => "list.type !== 'done' && showIssueForm" } + %li.card + %form{ "@submit" => "submit($event)" } + %label.label-light + Title + %input.form-control{ type: "text", + "v-model" => "title" } + .clearfix.prepend-top-10 + %button.btn.btn-success.pull-left{ type: "submit", + ":disabled" => "title === ''" } + Submit issue + %button.btn.btn-default.pull-right{ type: "button", + "@click" => "cancel" } + Cancel = render "projects/boards/components/card" %li.board-list-count.text-center{ "v-if" => "showCount" } = icon("spinner spin", "v-show" => "list.loadingMore" ) -- cgit v1.2.1 From 4d9f76c15115d3fd48d61e998edca86917fb1ccf Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Oct 2016 14:29:21 +0100 Subject: Added ability to save the new issue --- .../javascripts/boards/components/board_list.js.es6 | 2 +- .../javascripts/boards/components/board_new_issue.js.es6 | 15 ++++++++++++++- .../boards/mixins/sortable_default_options.js.es6 | 2 +- app/assets/javascripts/boards/models/list.js.es6 | 11 +++++++++++ .../javascripts/boards/services/board_service.js.es6 | 8 ++++++++ app/controllers/projects/boards/issues_controller.rb | 13 +++++++++++++ app/views/projects/boards/components/_board.html.haml | 8 +++++--- config/routes/project.rb | 2 +- 8 files changed, 54 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index d2e905ae062..208aac504fd 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -76,7 +76,7 @@ group: 'issues', sort: false, disabled: this.disabled, - filter: '.board-list-count', + filter: '.board-list-count, .board-new-issue-form', onStart: (e) => { const card = this.$refs.issue[e.oldIndex]; diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 index 057844f3ebb..60c13afefeb 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -3,6 +3,7 @@ gl.issueBoards.BoardNewIssue = Vue.extend({ props: { + list: Object, showIssueForm: Boolean }, data() { @@ -10,14 +11,26 @@ title: '' }; }, + watch: { + showIssueForm () { + this.$els.input.focus(); + } + }, methods: { submit(e) { e.preventDefault(); + const issue = new ListIssue({ + title: this.title, + labels: [this.list.label] + }); - this.title = ''; + this.list.newIssue(issue); + + this.cancel(); }, cancel() { this.showIssueForm = false; + this.title = ''; } } }); diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 index 44addb3ea98..f629d45c587 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 @@ -21,7 +21,7 @@ fallbackClass: 'is-dragging', fallbackOnBody: true, ghostClass: 'is-ghost', - filter: '.has-tooltip', + filter: '.has-tooltip, .btn', delay: gl.issueBoards.touchEnabled ? 100 : 0, scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100, scrollSpeed: 20, diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index 91fd620fdb3..14e42db4cd2 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -87,6 +87,17 @@ class List { }); } + newIssue (issue) { + this.addIssue(issue); + this.issuesSize++; + + gl.boardService.newIssue(this.id, issue) + .then((resp) => { + const data = resp.json(); + issue.id = data.iid; + }); + } + createIssues (data) { data.forEach((issueObj) => { this.addIssue(new ListIssue(issueObj)); diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6 index 9b80fb2e99f..c8b1d4f2b5a 100644 --- a/app/assets/javascripts/boards/services/board_service.js.es6 +++ b/app/assets/javascripts/boards/services/board_service.js.es6 @@ -58,4 +58,12 @@ class BoardService { to_list_id }); } + + newIssue (id, issue) { + return this.issues.save({ id }, { + issue: { + title: issue.title + } + }); + } }; diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 4aa7982eab4..41794193784 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -19,6 +19,15 @@ module Projects } end + def create + list = project.board.lists.find(params[:list_id]) + + issue = Issues::CreateService.new(project, current_user, issue_params.merge(request: request)).execute + issue.labels << list.label + + render json: issue.to_json + end + def update service = ::Boards::Issues::MoveService.new(project, current_user, move_params) @@ -54,6 +63,10 @@ module Projects def move_params params.permit(:id, :from_list_id, :to_list_id) end + + def issue_params + params.require(:issue).permit(:title) + end end end end diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 6c95812aebd..bd60e56a340 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -42,14 +42,16 @@ ":data-board" => "list.id" } - if can? current_user, :create_issue, @project %board-new-issue{ "inline-template" => true, + ":list" => "list", ":show-issue-form.sync" => "showIssueForm", - "v-if" => "list.type !== 'done' && showIssueForm" } - %li.card + "v-show" => "list.type !== 'done' && showIssueForm" } + %li.card.board-new-issue-form %form{ "@submit" => "submit($event)" } %label.label-light Title %input.form-control{ type: "text", - "v-model" => "title" } + "v-model" => "title", + "v-el:input" => true } .clearfix.prepend-top-10 %button.btn.btn-success.pull-left{ type: "submit", ":disabled" => "title === ''" } diff --git a/config/routes/project.rb b/config/routes/project.rb index 224ec7e8324..14d986c9475 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -424,7 +424,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: post :generate end - resources :issues, only: [:index] + resources :issues, only: [:index, :create] end end end -- cgit v1.2.1 From 674844d0e98e3e9a730f3e67f17d688ec769f0e5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Oct 2016 14:54:42 +0100 Subject: Fixed issue with backlog list not saving Fixed issue when dragging cards --- app/assets/javascripts/boards/components/board_list.js.es6 | 4 ++-- app/assets/javascripts/boards/components/board_new_issue.js.es6 | 3 ++- app/controllers/projects/boards/issues_controller.rb | 2 +- app/views/projects/boards/components/_card.html.haml | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 208aac504fd..bc74a7b23c9 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -78,7 +78,7 @@ disabled: this.disabled, filter: '.board-list-count, .board-new-issue-form', onStart: (e) => { - const card = this.$refs.issue[e.oldIndex]; + const card = this.$refs.issue[e.oldIndex - 1]; Store.moving.issue = card.issue; Store.moving.list = card.list; @@ -89,7 +89,7 @@ gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); }, onRemove: (e) => { - this.$refs.issue[e.oldIndex].$destroy(true); + this.$refs.issue[e.oldIndex - 1].$destroy(true); } }); diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 index 60c13afefeb..eb26ed9721d 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -19,9 +19,10 @@ methods: { submit(e) { e.preventDefault(); + const labels = this.list.label ? [this.list.label] : []; const issue = new ListIssue({ title: this.title, - labels: [this.list.label] + labels }); this.list.newIssue(issue); diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 41794193784..3b1b236a89a 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -23,7 +23,7 @@ module Projects list = project.board.lists.find(params[:list_id]) issue = Issues::CreateService.new(project, current_user, issue_params.merge(request: request)).execute - issue.labels << list.label + issue.labels << list.label if list.label render json: issue.to_json end diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index e8b60b54d80..25701b0a99e 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -15,7 +15,7 @@ ":title" => "issue.title" } {{ issue.title }} .card-footer - %span.card-number + %span.card-number{ "v-if" => "issue.id" } = precede '#' do {{ issue.id }} %button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels", -- cgit v1.2.1 From a9c98be056d1c493670e846771b08cf8985bf7a6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 08:04:03 +0100 Subject: Moved form outside of list This was causing issues with moving from the done list --- .../boards/components/board_list.js.es6 | 4 +-- app/assets/stylesheets/pages/boards.scss | 10 +++++- .../projects/boards/components/_board.html.haml | 41 +++++++++++----------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index bc74a7b23c9..208aac504fd 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -78,7 +78,7 @@ disabled: this.disabled, filter: '.board-list-count, .board-new-issue-form', onStart: (e) => { - const card = this.$refs.issue[e.oldIndex - 1]; + const card = this.$refs.issue[e.oldIndex]; Store.moving.issue = card.issue; Store.moving.list = card.list; @@ -89,7 +89,7 @@ gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); }, onRemove: (e) => { - this.$refs.issue[e.oldIndex - 1].$destroy(true); + this.$refs.issue[e.oldIndex].$destroy(true); } }); diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 46e92b8a187..6e6cb521dbc 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -162,6 +162,10 @@ lex list-style: none; overflow-y: scroll; overflow-x: hidden; + + &.is-smaller { + height: calc(100% - 185px); + } } .board-list-loading { @@ -234,6 +238,10 @@ lex } } +.board-new-issue-form { + margin: 5px; +} + .board-issue-count-holder { margin-top: -3px; @@ -251,7 +259,7 @@ lex border-radius: $border-radius-base; border-color: $border-color; border-style: solid; - border-width: 1px 1px 1px 1px; + border-width: 1px; &.has-btn { border-top-right-radius: 0; diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index bd60e56a340..efec465bbb2 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -37,28 +37,29 @@ ":issue-link-base" => "issueLinkBase" } .board-list-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") + - if can? current_user, :create_issue, @project + %board-new-issue{ "inline-template" => true, + ":list" => "list", + ":show-issue-form.sync" => "showIssueForm", + "v-show" => "list.type !== 'done' && showIssueForm" } + .card.board-new-issue-form + %form{ "@submit" => "submit($event)" } + %label.label-light + Title + %input.form-control{ type: "text", + "v-model" => "title", + "v-el:input" => true } + .clearfix.prepend-top-10 + %button.btn.btn-success.pull-left{ type: "submit", + ":disabled" => "title === ''" } + Submit issue + %button.btn.btn-default.pull-right{ type: "button", + "@click" => "cancel" } + Cancel %ul.board-list{ "v-el:list" => true, "v-show" => "!loading", - ":data-board" => "list.id" } - - if can? current_user, :create_issue, @project - %board-new-issue{ "inline-template" => true, - ":list" => "list", - ":show-issue-form.sync" => "showIssueForm", - "v-show" => "list.type !== 'done' && showIssueForm" } - %li.card.board-new-issue-form - %form{ "@submit" => "submit($event)" } - %label.label-light - Title - %input.form-control{ type: "text", - "v-model" => "title", - "v-el:input" => true } - .clearfix.prepend-top-10 - %button.btn.btn-success.pull-left{ type: "submit", - ":disabled" => "title === ''" } - Submit issue - %button.btn.btn-default.pull-right{ type: "button", - "@click" => "cancel" } - Cancel + ":data-board" => "list.id", + ":class" => "{ 'is-smaller': showIssueForm }" } = render "projects/boards/components/card" %li.board-list-count.text-center{ "v-if" => "showCount" } = icon("spinner spin", "v-show" => "list.loadingMore" ) -- cgit v1.2.1 From 284af578f37ffb9ec7bcc44ae9d8897be6e68d2f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 12:53:39 +0100 Subject: Added tests --- .../projects/boards/components/_board.html.haml | 2 +- spec/features/boards/new_issue_spec.rb | 80 ++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 spec/features/boards/new_issue_spec.rb diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index efec465bbb2..069b8bd9e55 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -19,7 +19,7 @@ - if can? current_user, :create_issue, @project %button.btn.btn-small.btn-default.pull-right{ type: "button", "@click" => "showNewIssueForm", - "v-if" => "list.type !== 'done'" } + "v-if" => "list.type !== 'done' && list.type !== 'blank'" } = icon("plus") - if can?(current_user, :admin_list, @project) %board-delete{ "inline-template" => true, diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb new file mode 100644 index 00000000000..c046e6b8d79 --- /dev/null +++ b/spec/features/boards/new_issue_spec.rb @@ -0,0 +1,80 @@ +require 'rails_helper' + +describe 'Issue Boards new issue', feature: true, js: true do + include WaitForAjax + include WaitForVueResource + + let(:project) { create(:project_with_board, :public) } + let(:user) { create(:user) } + + context 'authorized user' do + before do + project.team << [user, :master] + + login_as(user) + + visit namespace_project_board_path(project.namespace, project) + wait_for_vue_resource + + expect(page).to have_selector('.board', count: 3) + end + + it 'displays new issue button' do + expect(page).to have_selector('.board-issue-count-holder .btn', count: 1) + end + + it 'does not display new issue button in done list' do + page.within('.board:nth-child(3)') do + expect(page).not_to have_selector('.board-issue-count-holder .btn') + end + end + + it 'shows form when clicking button' do + page.within(first('.board')) do + find('.board-issue-count-holder .btn').click + + expect(page).to have_selector('.board-new-issue-form') + end + end + + it 'hides form when clicking cancel' do + page.within(first('.board')) do + find('.board-issue-count-holder .btn').click + + expect(page).to have_selector('.board-new-issue-form') + + click_button 'Cancel' + + expect(page).to have_selector('.board-new-issue-form', visible: false) + end + end + + it 'creates new issue' do + page.within(first('.board')) do + find('.board-issue-count-holder .btn').click + end + + page.within(first('.board-new-issue-form')) do + find('.form-control').set('bug') + click_button 'Submit issue' + end + + wait_for_vue_resource + + page.within(first('.board .board-issue-count')) do + expect(page).to have_content('1') + end + end + end + + context 'unauthorized user' do + before do + visit namespace_project_board_path(project.namespace, project) + wait_for_vue_resource + end + + it 'does not display new issue button' do + expect(page).to have_selector('.board-issue-count-holder .btn', count: 0) + end + end +end -- cgit v1.2.1 From da8998f940fde0b193261c6d357af9d701919293 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 14:24:49 +0100 Subject: Changed how button is hidden when blank list Added for attribute to title label --- app/views/projects/boards/components/_board.html.haml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 069b8bd9e55..f2c9ac809c1 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -12,14 +12,14 @@ %header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" } %h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" } {{ list.title }} - .board-issue-count-holder.pull-right.clearfix - %span.board-issue-count.pull-left{ "v-if" => "list.type !== 'blank'", - ":class" => "{ 'has-btn': list.type !== 'done' }" } + .board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" } + %span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" } {{ list.issuesSize }} - if can? current_user, :create_issue, @project %button.btn.btn-small.btn-default.pull-right{ type: "button", "@click" => "showNewIssueForm", - "v-if" => "list.type !== 'done' && list.type !== 'blank'" } + "v-if" => "list.type !== 'done'", + "aria-label" => "Show new issue form" } = icon("plus") - if can?(current_user, :admin_list, @project) %board-delete{ "inline-template" => true, @@ -44,11 +44,12 @@ "v-show" => "list.type !== 'done' && showIssueForm" } .card.board-new-issue-form %form{ "@submit" => "submit($event)" } - %label.label-light + %label.label-light{ ":for" => "list.id + '-title'" } Title %input.form-control{ type: "text", "v-model" => "title", - "v-el:input" => true } + "v-el:input" => true, + ":id" => "list.id + '-title'" } .clearfix.prepend-top-10 %button.btn.btn-success.pull-left{ type: "submit", ":disabled" => "title === ''" } -- cgit v1.2.1 From 8af7c32526b9b22b62013a682352a0ea1d3345ae Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 14:27:21 +0100 Subject: Made the CSS a bit more reasonable Changed how the data is passed into Vue resource --- app/assets/javascripts/boards/services/board_service.js.es6 | 4 +--- app/assets/stylesheets/pages/boards.scss | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6 index c8b1d4f2b5a..2b825c3949f 100644 --- a/app/assets/javascripts/boards/services/board_service.js.es6 +++ b/app/assets/javascripts/boards/services/board_service.js.es6 @@ -61,9 +61,7 @@ class BoardService { newIssue (id, issue) { return this.issues.save({ id }, { - issue: { - title: issue.title - } + issue }); } }; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 6e6cb521dbc..6e81c12aa55 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -164,7 +164,7 @@ lex overflow-x: hidden; &.is-smaller { - height: calc(100% - 185px); + height: calc(100% - 185px); } } @@ -257,9 +257,7 @@ lex padding-left: 10px; line-height: 21px; border-radius: $border-radius-base; - border-color: $border-color; - border-style: solid; - border-width: 1px; + border: 1px solid $border-color; &.has-btn { border-top-right-radius: 0; -- cgit v1.2.1 From 3397a8a6b5022c10701b6d9ef9b045ca05caf829 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 14:46:01 +0100 Subject: Added tooltip to new issue button --- app/views/projects/boards/components/_board.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index f2c9ac809c1..5cbc4eadcd0 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -16,10 +16,12 @@ %span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" } {{ list.issuesSize }} - if can? current_user, :create_issue, @project - %button.btn.btn-small.btn-default.pull-right{ type: "button", + %button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button", "@click" => "showNewIssueForm", "v-if" => "list.type !== 'done'", - "aria-label" => "Show new issue form" } + "aria-label" => "Add an issue", + "title" => "Add an issue", + data: { placement: "top", container: "body" } } = icon("plus") - if can?(current_user, :admin_list, @project) %board-delete{ "inline-template" => true, -- cgit v1.2.1 From 905af8471691fc0bb991aca5276185796dbe28c9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 16:10:44 +0100 Subject: Changed new issue button permissions This is because contributors can't create issues with labels --- app/views/projects/boards/components/_board.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 5cbc4eadcd0..4d7d8319204 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -15,7 +15,7 @@ .board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" } %span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" } {{ list.issuesSize }} - - if can? current_user, :create_issue, @project + - if can?(current_user, :admin_issue, @project) %button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button", "@click" => "showNewIssueForm", "v-if" => "list.type !== 'done'", -- cgit v1.2.1 From e7a4bbb04a86259a569f6ac239ecb35ad36f39b5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 4 Oct 2016 15:37:13 -0300 Subject: Add authorization to Projects::Boards::IssuesController#create action --- app/controllers/projects/boards/issues_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 3b1b236a89a..fea7a35232d 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -2,6 +2,7 @@ module Projects module Boards class IssuesController < Boards::ApplicationController before_action :authorize_read_issue!, only: [:index] + before_action :authorize_create_issue!, only: [:create] before_action :authorize_update_issue!, only: [:update] def index @@ -52,6 +53,10 @@ module Projects return render_403 unless can?(current_user, :read_issue, project) end + def authorize_create_issue! + return render_403 unless can?(current_user, :admin_issue, project) + end + def authorize_update_issue! return render_403 unless can?(current_user, :update_issue, issue) end -- cgit v1.2.1 From 97ec0c05f66e40a6de6620efcf5c92b7c2979f95 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 4 Oct 2016 17:41:00 -0300 Subject: Add service to create a new issue in a board list --- .../projects/boards/issues_controller.rb | 29 +++++++++++-------- app/services/boards/issues/create_service.rb | 16 +++++++++++ spec/services/boards/issues/create_service_spec.rb | 33 ++++++++++++++++++++++ 3 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 app/services/boards/issues/create_service.rb create mode 100644 spec/services/boards/issues/create_service_spec.rb diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index fea7a35232d..095af6c35eb 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -10,23 +10,21 @@ module Projects issues = issues.page(params[:page]) render json: { - issues: issues.as_json( - only: [:iid, :title, :confidential], - include: { - assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, - labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] } - }), + issues: serialize_as_json(issues), size: issues.total_count } end def create list = project.board.lists.find(params[:list_id]) + service = ::Boards::Issues::CreateService.new(project, current_user, issue_params) + issue = service.execute(list) - issue = Issues::CreateService.new(project, current_user, issue_params.merge(request: request)).execute - issue.labels << list.label if list.label - - render json: issue.to_json + if issue.valid? + render json: serialize_as_json(issue) + else + render json: issue.errors, status: :unprocessable_entity + end end def update @@ -70,7 +68,16 @@ module Projects end def issue_params - params.require(:issue).permit(:title) + params.require(:issue).permit(:title).merge(request: request) + end + + def serialize_as_json(resource) + resource.as_json( + only: [:iid, :title, :confidential], + include: { + assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, + labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] } + }) end end end diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb new file mode 100644 index 00000000000..3701afd441f --- /dev/null +++ b/app/services/boards/issues/create_service.rb @@ -0,0 +1,16 @@ +module Boards + module Issues + class CreateService < Boards::BaseService + def execute(list) + params.merge!(label_ids: [list.label_id]) + create_issue + end + + private + + def create_issue + ::Issues::CreateService.new(project, current_user, params).execute + end + end + end +end diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb new file mode 100644 index 00000000000..33e10e79f6d --- /dev/null +++ b/spec/services/boards/issues/create_service_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Boards::Issues::CreateService, services: true do + describe '#execute' do + let(:project) { create(:project_with_board) } + let(:board) { project.board } + let(:user) { create(:user) } + let(:label) { create(:label, project: project, name: 'in-progress') } + let!(:list) { create(:list, board: board, label: label, position: 0) } + + subject(:service) { described_class.new(project, user, title: 'New issue') } + + before do + project.team << [user, :developer] + end + + it 'delegates the create proceedings to Issues::CreateService' do + expect_any_instance_of(Issues::CreateService).to receive(:execute).once + + service.execute(list) + end + + it 'creates a new issue' do + expect { service.execute(list) }.to change(project.issues, :count).by(1) + end + + it 'adds the label of the list to the issue' do + issue = service.execute(list) + + expect(issue.labels).to eq [label] + end + end +end -- cgit v1.2.1 From fe3f1657ab88cbb60281681771c4b9dd870b65c6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 4 Oct 2016 17:41:24 -0300 Subject: Add tests to Projects::Boards::IssuesController#create action --- .../projects/boards/issues_controller_spec.rb | 64 +++++++++++++++++++--- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 2896636db5a..566658b508d 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe Projects::Boards::IssuesController do let(:project) { create(:project_with_board) } let(:user) { create(:user) } + let(:guest) { create(:user) } let(:planning) { create(:label, project: project, name: 'Planning') } let(:development) { create(:label, project: project, name: 'Development') } @@ -12,6 +13,7 @@ describe Projects::Boards::IssuesController do before do project.team << [user, :master] + project.team << [guest, :guest] end describe 'GET index' do @@ -61,6 +63,60 @@ describe Projects::Boards::IssuesController do end end + describe 'POST create' do + context 'with valid params' do + it 'returns a successful 200 response' do + create_issue user: user, list: list1, title: 'New issue' + + expect(response).to have_http_status(200) + end + + it 'returns the created issue' do + create_issue user: user, list: list1, title: 'New issue' + + expect(response).to match_response_schema('issue') + end + end + + context 'with invalid params' do + context 'when title is nil' do + it 'returns an unprocessable entity 422 response' do + create_issue user: user, list: list1, title: nil + + expect(response).to have_http_status(422) + end + end + + context 'when list does not belongs to project board' do + it 'returns a not found 404 response' do + list = create(:list) + + create_issue user: user, list: list, title: 'New issue' + + expect(response).to have_http_status(404) + end + end + end + + context 'with unauthorized user' do + it 'returns a forbidden 403 response' do + create_issue user: guest, list: list1, title: 'New issue' + + expect(response).to have_http_status(403) + end + end + + def create_issue(user:, list:, title:) + sign_in(user) + + post :create, namespace_id: project.namespace.to_param, + project_id: project.to_param, + list_id: list.to_param, + issue: { title: title }, + format: :json + end + end + describe 'PATCH update' do let(:issue) { create(:labeled_issue, project: project, labels: [planning]) } @@ -93,13 +149,7 @@ describe Projects::Boards::IssuesController do end context 'with unauthorized user' do - let(:guest) { create(:user) } - - before do - project.team << [guest, :guest] - end - - it 'returns a successful 403 response' do + it 'returns a forbidden 403 response' do move user: guest, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(403) -- cgit v1.2.1 From b9ede4f1a5aa213f0342daff5e7281758e989a8e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 4 Oct 2016 17:43:05 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 649215db31c..37dbf56c3e9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ v 8.13.0 (unreleased) - Fix VueJS template tags being rendered in code comments - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - Allow the Koding integration to be configured through the API + - Add new issue button to each list on Issues Board - Added soft wrap button to repository file/blob editor - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) -- cgit v1.2.1 From a68f1fdd2975a9e7cab39ab7d40e3eae2cc676db Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 14:27:29 +0100 Subject: Fix form not re-enabling thanks to jQuery Stop issue being dragged if it doesn't have an ID --- app/assets/javascripts/boards/components/board_list.js.es6 | 2 +- app/assets/javascripts/boards/components/board_new_issue.js.es6 | 6 +++++- app/assets/javascripts/boards/models/list.js.es6 | 2 +- app/views/projects/boards/components/_board.html.haml | 3 ++- app/views/projects/boards/components/_card.html.haml | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 208aac504fd..7022a29e818 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -76,7 +76,7 @@ group: 'issues', sort: false, disabled: this.disabled, - filter: '.board-list-count, .board-new-issue-form', + filter: '.board-list-count, .is-disabled', onStart: (e) => { const card = this.$refs.issue[e.oldIndex]; diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 index eb26ed9721d..315c16ee242 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -25,7 +25,11 @@ labels }); - this.list.newIssue(issue); + this.list.newIssue(issue) + .then(() => { + // Need this because our jQuery very kindly disables buttons on ALL form submissions + $(this.$els.submitButton).enable(); + }); this.cancel(); }, diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index 14e42db4cd2..5d0a561cdba 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -91,7 +91,7 @@ class List { this.addIssue(issue); this.issuesSize++; - gl.boardService.newIssue(this.id, issue) + return gl.boardService.newIssue(this.id, issue) .then((resp) => { const data = resp.json(); issue.id = data.iid; diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 4d7d8319204..26b2236b581 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -54,7 +54,8 @@ ":id" => "list.id + '-title'" } .clearfix.prepend-top-10 %button.btn.btn-success.pull-left{ type: "submit", - ":disabled" => "title === ''" } + ":disabled" => "title === ''", + "v-el:submit-button" => true } Submit issue %button.btn.btn-default.pull-right{ type: "button", "@click" => "cancel" } diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index 25701b0a99e..f15c87c8185 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -7,7 +7,7 @@ ":issue-link-base" => "issueLinkBase", ":disabled" => "disabled", "track-by" => "id" } - %li.card{ ":class" => "{ 'user-can-drag': !disabled }", + %li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id }", ":index" => "index" } %h4.card-title = icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential") -- cgit v1.2.1 From 2f53a8d08f3b1595dfa48c308e408083b89651f4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 14:58:29 +0100 Subject: Shows error if response returns an error Added validation so the user shouldnt be able to submit the form without the title present --- .../boards/components/board_new_issue.js.es6 | 21 +++++++++++++++++++-- .../projects/boards/components/_board.html.haml | 3 +++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 index 315c16ee242..58cc30b05af 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -8,7 +8,8 @@ }, data() { return { - title: '' + title: '', + error: false }; }, watch: { @@ -19,6 +20,10 @@ methods: { submit(e) { e.preventDefault(); + if (this.title.trim() === '') return; + + this.error = false; + const labels = this.list.label ? [this.list.label] : []; const issue = new ListIssue({ title: this.title, @@ -26,9 +31,21 @@ }); this.list.newIssue(issue) - .then(() => { + .then((data) => { + // Need this because our jQuery very kindly disables buttons on ALL form submissions + $(this.$els.submitButton).enable(); + }) + .catch(() => { // Need this because our jQuery very kindly disables buttons on ALL form submissions $(this.$els.submitButton).enable(); + + // Remove issue with no ID + const issue = this.list.findIssue(undefined); + this.list.removeIssue(issue); + + // Show error message + this.error = true; + this.showIssueForm = true; }); this.cancel(); diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml index 26b2236b581..ba1502c97b6 100644 --- a/app/views/projects/boards/components/_board.html.haml +++ b/app/views/projects/boards/components/_board.html.haml @@ -46,6 +46,9 @@ "v-show" => "list.type !== 'done' && showIssueForm" } .card.board-new-issue-form %form{ "@submit" => "submit($event)" } + .flash-container{ "v-if" => "error" } + .flash-alert + An error occured. Please try again. %label.label-light{ ":for" => "list.id + '-title'" } Title %input.form-control{ type: "text", -- cgit v1.2.1 From 9824a69c8a4028b757a3d13d7ed4251589d20589 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 6 Oct 2016 12:19:50 +0200 Subject: Fix tests and code for MWBS event move to pipeline --- app/services/merge_requests/base_service.rb | 6 +++ .../merge_when_build_succeeds_service_spec.rb | 48 +++++++++++----------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index a726dd98697..4f4c1c77ec3 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -56,12 +56,18 @@ module MergeRequests def pipeline_merge_requests(pipeline) merge_requests_for(pipeline.ref, pipeline.sha).each do |merge_request| + next unless pipeline == merge_request.pipeline + yield merge_request end end def commit_status_merge_requests(commit_status) merge_requests_for(commit_status.ref, commit_status.sha).each do |merge_request| + pipeline = merge_request.pipeline + next unless pipeline + next unless pipeline.sha == commit_status.sha + yield merge_request end end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 520e906b21f..df8f8b61df5 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -58,42 +58,44 @@ describe MergeRequests::MergeWhenBuildSucceedsService do end describe "#trigger" do - context 'build with ref' do - let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } + let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch } + let(:merge_request_head) do + project.commit(mr_merge_if_green_enabled.source_branch).id + end - it "merges all merge requests with merge when build succeeds enabled" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) - allow(pipeline).to receive(:success?).and_return(true) + context 'when triggered by pipeline with valid ref and sha' do + let(:triggering_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: merge_request_head, status: 'success') + end + it "merges all merge requests with merge when build succeeds enabled" do expect(MergeWorker).to receive(:perform_async) - service.trigger(build) + service.trigger(triggering_pipeline) end end - context 'triggered by an old build' do - let(:old_build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } - let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } - - it "merges all merge requests with merge when build succeeds enabled" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) - allow(pipeline).to receive(:success?).and_return(true) - allow(old_build).to receive(:sha).and_return('1234abcdef') + context 'when triggered by an old pipeline' do + let(:old_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: '1234abcdef', status: 'success') + end + it 'it does not merge merge request' do expect(MergeWorker).not_to receive(:perform_async) - service.trigger(old_build) + service.trigger(old_pipeline) end end - context 'commit status without ref' do - let(:commit_status) { create(:generic_commit_status, status: 'success') } - - before { mr_merge_if_green_enabled } - - it "doesn't merge a requests for status on other branch" do - allow(project.repository).to receive(:branch_names_contains).with(commit_status.sha).and_return([]) + context 'when triggered by pipeline from a different branch' do + let(:unrelated_pipeline) do + create(:ci_pipeline, project: project, ref: 'feature', + sha: merge_request_head, status: 'success') + end + it 'does not merge request' do expect(MergeWorker).not_to receive(:perform_async) - service.trigger(commit_status) + service.trigger(unrelated_pipeline) end it 'discovers branches and merges all merge requests when status is success' do -- cgit v1.2.1 From cd58410811564130e6975c62fe77d2c9fccce46f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 6 Oct 2016 12:31:39 +0200 Subject: Remove support for branch-less builds in MWBS See !6675#note_16580143 --- app/services/merge_requests/base_service.rb | 20 ++++++++++---------- .../merge_when_build_succeeds_service_spec.rb | 13 +------------ 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 4f4c1c77ec3..58f69a41e14 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -42,20 +42,19 @@ module MergeRequests super(:merge_request) end - def merge_requests_for(branches, sha) - # This is for ref-less builds - branches ||= @project.repository.branch_names_contains(sha) + def merge_requests_for(branch) + origin_merge_requests = @project.origin_merge_requests + .opened.where(source_branch: branch).to_a - return [] if branches.blank? + fork_merge_requests = @project.fork_merge_requests + .opened.where(source_branch: branch).to_a - merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a - merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a - - merge_requests.uniq.select(&:source_project) + (origin_merge_requests + fork_merge_requests) + .uniq.select(&:source_project) end def pipeline_merge_requests(pipeline) - merge_requests_for(pipeline.ref, pipeline.sha).each do |merge_request| + merge_requests_for(pipeline.ref).each do |merge_request| next unless pipeline == merge_request.pipeline yield merge_request @@ -63,8 +62,9 @@ module MergeRequests end def commit_status_merge_requests(commit_status) - merge_requests_for(commit_status.ref, commit_status.sha).each do |merge_request| + merge_requests_for(commit_status.ref).each do |merge_request| pipeline = merge_request.pipeline + next unless pipeline next unless pipeline.sha == commit_status.sha diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index df8f8b61df5..03345ed9c15 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -97,20 +97,9 @@ describe MergeRequests::MergeWhenBuildSucceedsService do expect(MergeWorker).not_to receive(:perform_async) service.trigger(unrelated_pipeline) end - - it 'discovers branches and merges all merge requests when status is success' do - allow(project.repository).to receive(:branch_names_contains). - with(commit_status.sha).and_return([mr_merge_if_green_enabled.source_branch]) - allow(pipeline).to receive(:success?).and_return(true) - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) - allow(pipeline).to receive(:success?).and_return(true) - - expect(MergeWorker).to receive(:perform_async) - service.trigger(commit_status) - end end - context 'properly handles multiple stages' do + context 'when there are multiple stages in the pipeline' do let(:ref) { mr_merge_if_green_enabled.source_branch } let!(:build) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') } let!(:test) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') } -- cgit v1.2.1 From 28f85cefde05abd2fde7938a892c2487172312df Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 11:32:58 +0100 Subject: Removes 'hidden-xs' class from user_avatar helper in order for it to be visible in environments list. Allows a class as an option to keep the hidden avatar behaviour in all the places it is needed. --- app/helpers/avatars_helper.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index df41473543b..b7e0ff8ecd0 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -4,15 +4,18 @@ module AvatarsHelper user: commit_or_event.author, user_name: commit_or_event.author_name, user_email: commit_or_event.author_email, + css_class: 'hidden-xs' })) end def user_avatar(options = {}) avatar_size = options[:size] || 16 user_name = options[:user].try(:name) || options[:user_name] + css_class = options[:css_class] || '' + avatar = image_tag( avatar_icon(options[:user] || options[:user_email], avatar_size), - class: "avatar has-tooltip hidden-xs s#{avatar_size}", + class: "avatar has-tooltip s#{avatar_size} #{css_class}", alt: "#{user_name}'s avatar", title: user_name, data: { container: 'body' } -- cgit v1.2.1 From 8de767c40cd94da47673e264f3dbb4d0243d85c4 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 11:37:13 +0100 Subject: Verifies if user is available --- app/views/projects/environments/_environment.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index d35e619678c..53df563d9cf 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -6,8 +6,10 @@ %td.deployment-column - if last_deployment - %span ##{last_deployment.iid} by - = user_avatar(user: last_deployment.user, size: 20) + %span ##{last_deployment.iid} + - if last_deployment.user + by + = user_avatar(user: last_deployment.user, size: 20) %td - if last_deployment && last_deployment.deployable -- cgit v1.2.1 From 389630409672d12cb88c1b534ed11340f132c1d7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 11:40:08 +0100 Subject: Fixes linter problems --- app/assets/stylesheets/pages/environments.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index d8a28b5860d..1981c15e75c 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -56,7 +56,7 @@ .branch-commit { .commit-id { - margin-right: 0px; + margin-right: 0; } } } -- cgit v1.2.1 From 2347d0e66ccdf3224b8a98c7655ade7b78d78842 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 11:52:54 +0100 Subject: Adds responsive behaviour equal to the one in Pipelines and Builds table --- app/assets/stylesheets/pages/environments.scss | 7 +++- .../projects/deployments/_deployment.html.haml | 2 +- .../projects/environments/_environment.html.haml | 2 +- app/views/projects/environments/index.html.haml | 47 +++++++++++----------- app/views/projects/environments/show.html.haml | 43 ++++++++++---------- 5 files changed, 54 insertions(+), 47 deletions(-) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 1981c15e75c..3f19e920166 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -1,3 +1,9 @@ +.environments-container, +.deployments-container { + width: 100%; + overflow: auto; +} + .environments { .deployment-column { .avatar { @@ -47,7 +53,6 @@ } .table.builds.environments { - min-width: 500px; .icon-container { width: 20px; diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index 2ea3b477bb7..ca0005abd0c 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -16,5 +16,5 @@ %td #{time_ago_with_tooltip(deployment.created_at)} - %td + %td.hidden-xs = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 53df563d9cf..251694e897c 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -27,5 +27,5 @@ - if last_deployment #{time_ago_with_tooltip(last_deployment.created_at)} - %td + %td.hidden-xs = render 'projects/deployments/actions', deployment: last_deployment diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 2d5c0c9d54f..ab801409722 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -9,26 +9,27 @@ = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment - - if @environments.blank? - .blank-state.blank-state-no-icon - %h2.blank-state-title - You don't have any environments right now. - %p.blank-state-text - Environments are places where code gets deployed, such as staging or production. - %br - = succeed "." do - = link_to "Read more about environments", help_page_path("ci/environments") - - if can?(current_user, :create_environment, @project) - = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do - New environment - - else - .table-holder - %table.table.builds.environments - %tbody - %th Environment - %th Last Deployment - %th Build - %th Commit - %th - %th - = render @environments + .environments-container + - if @environments.blank? + .blank-state.blank-state-no-icon + %h2.blank-state-title + You don't have any environments right now. + %p.blank-state-text + Environments are places where code gets deployed, such as staging or production. + %br + = succeed "." do + = link_to "Read more about environments", help_page_path("ci/environments") + - if can?(current_user, :create_environment, @project) + = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do + New environment + - else + .table-holder + %table.table.builds.environments + %tbody + %th Environment + %th Last Deployment + %th Build + %th Commit + %th + %th.hidden-xs + = render @environments diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 8f8c1c4ce22..7a8d196cf4e 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -12,26 +12,27 @@ = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - - if @deployments.blank? - .blank-state.blank-state-no-icon - %h2.blank-state-title - You don't have any deployments right now. - %p.blank-state-text - Define environments in the deploy stage(s) in - %code .gitlab-ci.yml - to track deployments here. - = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success" - - else - .table-holder - %table.table.builds.environments - %thead - %tr - %th ID - %th Commit - %th Build - %th - %th + .deployments-container + - if @deployments.blank? + .blank-state.blank-state-no-icon + %h2.blank-state-title + You don't have any deployments right now. + %p.blank-state-text + Define environments in the deploy stage(s) in + %code .gitlab-ci.yml + to track deployments here. + = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success" + - else + .table-holder + %table.table.builds.environments + %thead + %tr + %th ID + %th Commit + %th Build + %th + %th.hidden-xs - = render @deployments + = render @deployments - = paginate @deployments, theme: 'gitlab' + = paginate @deployments, theme: 'gitlab' -- cgit v1.2.1 From 735b2b5769cb170b466bd2f5ddc116a997ac75a5 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 10:45:42 +0100 Subject: Adds link to close environment in a merge request --- app/assets/stylesheets/pages/merge_requests.scss | 9 +++++++++ app/views/projects/merge_requests/widget/_heading.html.haml | 3 +++ 2 files changed, 12 insertions(+) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..f2c2e06ad3e 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -177,6 +177,15 @@ .ci-coverage { float: right; } + + .close-env-container { + color: $gl-text-color; + float: right; + + a { + color: $gl-text-color; + } + } } .mr_source_commit, diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index b5f5e11d4c3..c56283836a9 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -57,3 +57,6 @@ - if external_url = link_to external_url, target: '_blank' do = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) + %span.close-env-container + = link_to '#', class: 'close-evn-link' do + = icon('stop-circle-o', text: 'Stop environment') \ No newline at end of file -- cgit v1.2.1 From 10ea760251af3bcb38f03ca07df8728b0f125c12 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 13:59:31 +0100 Subject: Adds new line in the end of the file --- app/views/projects/merge_requests/widget/_heading.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index c56283836a9..7d9ed7dd5a1 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -59,4 +59,4 @@ = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) %span.close-env-container = link_to '#', class: 'close-evn-link' do - = icon('stop-circle-o', text: 'Stop environment') \ No newline at end of file + = icon('stop-circle-o', text: 'Stop environment') -- cgit v1.2.1 From b60e8309c58fc24c6aaa86adc7c8ddab2aac54e0 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 14:11:04 +0100 Subject: Adds variables to controller --- app/controllers/projects/environments_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 58678f96879..d5de9e796df 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -6,7 +6,12 @@ class Projects::EnvironmentsController < Projects::ApplicationController before_action :environment, only: [:show, :edit, :update, :destroy] def index + @scope = params[:scope] @environments = project.environments + + # TODO: fix the values of this vars to show the correct results + @available_environments_count = project.environments.count + @stopped_environments_count = project.environments.count end def show -- cgit v1.2.1 From 08e708579213b5ee2ed80f1fd8d3966a897ed6ff Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 14:11:41 +0100 Subject: Adds tabs to environments list --- app/views/projects/environments/index.html.haml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index b3eb5b0011a..393f9e0d57b 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -5,6 +5,19 @@ %div{ class: container_class } - if can?(current_user, :create_environment, @project) && !@environments.blank? .top-area + %ul.nav-links + %li{class: ('active' if @scope.nil?)} + = link_to project_environments_path(@project) do + Availabe + %span.badge.js-avaibale-environments-count + = number_with_delimiter(@available_environments_count) + + %li{class: ('active' if @scope == 'stopped')} + = link_to project_environments_path(@project, scope: :stopped) do + Stopped + %span.badge.js-stopped-environments-count + = number_with_delimiter(@stopped_environments_count) + .nav-controls = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment -- cgit v1.2.1 From bc49974d94c8ddec59fbc3ebaf16a0e00269e647 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 21:26:33 +0100 Subject: Adds stop environment button to environments list --- app/assets/stylesheets/pages/environments.scss | 8 ++++++++ app/views/projects/deployments/_actions.haml | 5 +++++ app/views/projects/environments/_environment.html.haml | 5 +++-- app/views/projects/environments/show.html.haml | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index d01c60ee6ab..2e14bdb0eb3 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -24,6 +24,14 @@ .branch-name { color: $gl-dark-link-color; } + + .close-env-link { + color: $table-text-gray; + + .close-env-icon { + font-size: 14px; + } + } } .table.builds.environments { diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 16d134eb6b6..a1d9da285c0 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -14,6 +14,11 @@ = custom_icon('icon_play') %span= action.name.humanize + - if :can_be_stopped? + .inline + %a.close-env-link.btn + = icon('stop', class: 'close-env-icon') + - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do - if deployment.last? diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 36a6162a5a8..27b7254fcfb 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -1,4 +1,5 @@ - last_deployment = environment.last_deployment +- can_be_stopped = true %tr.environment %td @@ -20,5 +21,5 @@ - if last_deployment #{time_ago_with_tooltip(last_deployment.created_at)} - %td - = render 'projects/deployments/actions', deployment: last_deployment + %td.hidden-xs + = render 'projects/deployments/actions', deployment: last_deployment, can_be_stopped: can_be_stopped diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 8f8c1c4ce22..6412c809d52 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -30,7 +30,7 @@ %th Commit %th Build %th - %th + %th.hidden-xs = render @deployments -- cgit v1.2.1 From ed975fdcd4bb2ecb47753e2e1c017175c211adbe Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 21:33:50 +0100 Subject: Replaces destroy button with close one --- app/views/projects/environments/show.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 6412c809d52..5ca35bb92b1 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -10,7 +10,8 @@ .nav-controls - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete + / TODO: Confirm if the method is :delete + = link_to 'Close', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? .blank-state.blank-state-no-icon -- cgit v1.2.1 From 34a980851ebdaeff1734c2adb031253fdfe7b46f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 21:41:36 +0100 Subject: Adds close button to builds list --- app/views/projects/deployments/_actions.haml | 7 +++---- app/views/projects/deployments/_deployment.html.haml | 2 +- app/views/projects/environments/_environment.html.haml | 3 +-- app/views/projects/environments/index.html.haml | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index a1d9da285c0..01b33be719f 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -14,10 +14,9 @@ = custom_icon('icon_play') %span= action.name.humanize - - if :can_be_stopped? - .inline - %a.close-env-link.btn - = icon('stop', class: 'close-env-icon') + .inline + %a.close-env-link.btn + = icon('stop', class: 'close-env-icon') - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index cd95841ca5a..fce7b1cb34e 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -14,5 +14,5 @@ %td #{time_ago_with_tooltip(deployment.created_at)} - %td + %td.hidden-xs = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 27b7254fcfb..2e9c395f08a 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -1,5 +1,4 @@ - last_deployment = environment.last_deployment -- can_be_stopped = true %tr.environment %td @@ -22,4 +21,4 @@ #{time_ago_with_tooltip(last_deployment.created_at)} %td.hidden-xs - = render 'projects/deployments/actions', deployment: last_deployment, can_be_stopped: can_be_stopped + = render 'projects/deployments/actions', deployment: last_deployment diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 393f9e0d57b..949f0855777 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -42,5 +42,5 @@ %th Last Deployment %th Commit %th - %th + %th.hidden-xs = render @environments -- cgit v1.2.1 From f82d6c180d171d09d6822aaffb447b4c048749a6 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 4 Oct 2016 22:33:37 +0100 Subject: Updates failing test --- spec/features/environments_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 4309a726917..3b38a7f5007 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -175,12 +175,12 @@ feature 'Environments', feature: true do before do visit namespace_project_environment_path(project.namespace, project, environment) end - + context 'when logged as master' do given(:role) { :master } - scenario 'does delete environment' do - click_link 'Destroy' + scenario 'does close environment' do + click_link 'Close' expect(page).not_to have_link(environment.name) end end @@ -188,8 +188,8 @@ feature 'Environments', feature: true do context 'when logged as developer' do given(:role) { :developer } - scenario 'does not have a Destroy link' do - expect(page).not_to have_link('Destroy') + scenario 'does not have a Close link' do + expect(page).not_to have_link('Close') end end end -- cgit v1.2.1 From 2c01b19f8f55c7105506cdbe964d24d0a3c6dfc4 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 5 Oct 2016 10:49:17 +0100 Subject: Fixes space between buttons --- app/views/projects/deployments/_actions.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 01b33be719f..6e5e1288722 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -19,7 +19,7 @@ = icon('stop', class: 'close-env-icon') - if local_assigns.fetch(:allow_rollback, false) - = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do + = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn' do - if deployment.last? Re-deploy - else -- cgit v1.2.1 From 3f85c3ef1629870e22f6a676585e8de80e5120c3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 6 Oct 2016 13:10:50 +0200 Subject: Initial support for closing environments --- app/models/ci/build.rb | 4 ++++ app/models/deployment.rb | 13 +++++++++++++ app/models/environment.rb | 18 ++++++++++++++++++ app/services/create_deployment_service.rb | 12 +++++++++++- db/migrate/20161006104309_add_state_to_environment.rb | 9 +++++++++ lib/gitlab/ci/config/node/environment.rb | 4 +++- 6 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20161006104309_add_state_to_environment.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5dbf66173de..aea881d9aa8 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -110,6 +110,10 @@ module Ci project.builds_enabled? && commands.present? && manual? && skipped? end + def closes_environment?(name) + environment == name && options.fetch(:environment, {}).fetch(:close, false) + end + def play(current_user = nil) # Try to queue a current build if self.enqueue diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 82b27b78229..070c76339b1 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -77,6 +77,19 @@ class Deployment < ActiveRecord::Base take end + def close_action + return nil unless manual_actions + + @close_action ||= + manual_actions.find do |manual_action| + manual_action.try(:closes_environment?, deployable.environment) + end + end + + def closeable? + close_action.present? + end + private def ref_path diff --git a/app/models/environment.rb b/app/models/environment.rb index f0f3ee23223..6ec498ea2b7 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -19,6 +19,24 @@ class Environment < ActiveRecord::Base allow_nil: true, addressable_url: true + delegate :closeable?, :close_action, to: :last_deployment, allow_nil: true + + scope :opened, -> { where(state: [:opened]) } + scope :closed, -> { where(state: [:closed]) } + + state_machine :state, initial: :opened do + event :close do + transition opened: :closed + end + + event :reopen do + transition closed: :opened + end + + state :opened + state :closed + end + def last_deployment deployments.last end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 799ad3e1bd0..c87542e57a2 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -4,6 +4,13 @@ class CreateDeploymentService < BaseService def execute(deployable = nil) environment = find_or_create_environment + if close? + environment.close + return + end + + environment.reopen + deployment = project.deployments.create( environment: environment, ref: params[:ref], @@ -14,7 +21,6 @@ class CreateDeploymentService < BaseService ) deployment.update_merge_request_metrics! - deployment end @@ -44,6 +50,10 @@ class CreateDeploymentService < BaseService options[:url] end + def close? + options[:close] + end + def options params[:options] || {} end diff --git a/db/migrate/20161006104309_add_state_to_environment.rb b/db/migrate/20161006104309_add_state_to_environment.rb new file mode 100644 index 00000000000..2a6fd1a6a93 --- /dev/null +++ b/db/migrate/20161006104309_add_state_to_environment.rb @@ -0,0 +1,9 @@ +class AddStateToEnvironment < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :environments, :state, :string + end +end diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index d388ab6b879..daa115f9017 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -8,7 +8,7 @@ module Gitlab class Environment < Entry include Validatable - ALLOWED_KEYS = %i[name url] + ALLOWED_KEYS = %i[name url close] validations do validate do @@ -35,6 +35,8 @@ module Gitlab length: { maximum: 255 }, addressable_url: true, allow_nil: true + + validates :close, boolean: true, allow_nil: true end end -- cgit v1.2.1 From fd4e0703e81de31aaabdfc983b013382841fcaa3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 6 Oct 2016 13:13:47 +0200 Subject: Add play type --- app/models/ci/build.rb | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index aea881d9aa8..09c5223c60d 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -110,8 +110,12 @@ module Ci project.builds_enabled? && commands.present? && manual? && skipped? end + def close_environment? + options.fetch(:environment, {}).fetch(:close, false) + end + def closes_environment?(name) - environment == name && options.fetch(:environment, {}).fetch(:close, false) + environment == name && close_environment? end def play(current_user = nil) @@ -125,6 +129,16 @@ module Ci end end + def play_type + return nil unless playable? + + if close_environment? + :close + else + :play + end + end + def retryable? project.builds_enabled? && commands.present? && complete? end -- cgit v1.2.1 From c319cc5ab5064459aaf803ee719a08e9663726ea Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 6 Oct 2016 13:45:45 +0200 Subject: Make environments to be close able --- .../projects/environments_controller.rb | 11 ++++---- app/models/project.rb | 2 +- app/views/projects/deployments/_actions.haml | 8 ++++-- .../projects/environments/_environment.html.haml | 2 +- app/views/projects/environments/index.html.haml | 32 +++++++++++----------- app/views/projects/environments/show.html.haml | 7 +++-- .../merge_requests/widget/_heading.html.haml | 5 ++-- .../20161006104309_add_state_to_environment.rb | 10 +++++-- 8 files changed, 45 insertions(+), 32 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index d5de9e796df..4fe8c3a1889 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -7,11 +7,12 @@ class Projects::EnvironmentsController < Projects::ApplicationController def index @scope = params[:scope] - @environments = project.environments - - # TODO: fix the values of this vars to show the correct results - @available_environments_count = project.environments.count - @stopped_environments_count = project.environments.count + @all_environments = project.environments + @environments = + case @scope + when 'closed' then @all_environments.closed + else @all_environments.opened + end end def show diff --git a/app/models/project.rb b/app/models/project.rb index ecd742a17d5..12705f9ae48 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1302,7 +1302,7 @@ class Project < ActiveRecord::Base environment_ids.where(ref: ref) end - environments.where(id: environment_ids).select do |environment| + environments.opened.where(id: environment_ids).select do |environment| environment.includes_commit?(commit) end end diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 6e5e1288722..2d9c229ed5b 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -14,9 +14,11 @@ = custom_icon('icon_play') %span= action.name.humanize - .inline - %a.close-env-link.btn - = icon('stop', class: 'close-env-icon') + - if local_assigns.fetch(:allow_close, false) && deployment.closeable? + .inline + %a.close-env-link.btn + = link_to [:play, @project.namespace.becomes(Namespace), @project, deployment.close_action], method: :post, rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + = icon('stop', class: 'close-env-icon') - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn' do diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 2e9c395f08a..205392b1e81 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -21,4 +21,4 @@ #{time_ago_with_tooltip(last_deployment.created_at)} %td.hidden-xs - = render 'projects/deployments/actions', deployment: last_deployment + = render 'projects/deployments/actions', deployment: last_deployment, allow_close: environment.opened? diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 949f0855777..702cccd6ab3 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -3,26 +3,26 @@ = render "projects/pipelines/head" %div{ class: container_class } - - if can?(current_user, :create_environment, @project) && !@environments.blank? - .top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to project_environments_path(@project) do - Availabe - %span.badge.js-avaibale-environments-count - = number_with_delimiter(@available_environments_count) - - %li{class: ('active' if @scope == 'stopped')} - = link_to project_environments_path(@project, scope: :stopped) do - Stopped - %span.badge.js-stopped-environments-count - = number_with_delimiter(@stopped_environments_count) + .top-area + %ul.nav-links + %li{class: ('active' if @scope.nil?)} + = link_to project_environments_path(@project) do + Availabe + %span.badge.js-avaibale-environments-count + = number_with_delimiter(@all_environments.opened.count) - .nav-controls + %li{class: ('active' if @scope == 'closed')} + = link_to project_environments_path(@project, scope: :stopped) do + Stopped + %span.badge.js-stopped-environments-count + = number_with_delimiter(@all_environments.closed.count) + + .nav-controls + - if can?(current_user, :create_environment, @project) && !@all_environments.blank? = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment - - if @environments.blank? + - if @all_environments.blank? .blank-state.blank-state-no-icon %h2.blank-state-title You don't have any environments right now. diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 5ca35bb92b1..5f4a0ef082c 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -2,6 +2,8 @@ - page_title "Environments" = render "projects/pipelines/head" +- last_deployment = @environment.last_deployment + %div{ class: container_class } .top-area .col-md-9 @@ -10,8 +12,9 @@ .nav-controls - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - / TODO: Confirm if the method is :delete - = link_to 'Close', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete + - if @environment.opened? && last_deployment.try(:close_action) + = link_to 'Close', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? .blank-state.blank-state-no-icon diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 7d9ed7dd5a1..a92f0f9f481 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -58,5 +58,6 @@ = link_to external_url, target: '_blank' do = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) %span.close-env-container - = link_to '#', class: 'close-evn-link' do - = icon('stop-circle-o', text: 'Stop environment') + - if environment.closeable? + = link_to [:play, @project.namespace.becomes(Namespace), @project, environment.close_action], method: :post, class: 'close-evn-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + = icon('stop-circle-o', text: 'Stop environment') diff --git a/db/migrate/20161006104309_add_state_to_environment.rb b/db/migrate/20161006104309_add_state_to_environment.rb index 2a6fd1a6a93..83dff07069f 100644 --- a/db/migrate/20161006104309_add_state_to_environment.rb +++ b/db/migrate/20161006104309_add_state_to_environment.rb @@ -1,9 +1,15 @@ class AddStateToEnvironment < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + DOWNTIME = false - def change - add_column :environments, :state, :string + def up + add_column_with_default(:environments, :state, :string, default: :opened) + end + + def down + remove_column(:environments, :state) end end -- cgit v1.2.1 From 858839383bed0023f11ad3fd9aceba8632bddb68 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 6 Oct 2016 14:12:31 +0200 Subject: Use gitlab-workhorse 0.8.4 Fixes Go 1.5 compatibility. --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- doc/update/8.12-to-8.13.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 100435be135..b60d71966ae 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.8.2 +0.8.4 diff --git a/doc/install/installation.md b/doc/install/installation.md index 68ed20ef5bf..378ab6857b8 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of 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.8.2 + sudo -u git -H git checkout v0.8.4 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index 411e4837e20..e3f61247be5 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -84,7 +84,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.8.2 +sudo -u git -H git checkout v0.8.4 sudo -u git -H make ``` -- cgit v1.2.1 From 0e1f39d8cee3a6d23fccb195f8257178df840805 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 6 Oct 2016 14:16:03 +0200 Subject: Allow to close environments --- app/views/projects/deployments/_actions.haml | 5 ++--- app/views/projects/environments/index.html.haml | 4 ++-- app/views/projects/environments/show.html.haml | 1 - db/fixtures/development/14_pipelines.rb | 1 + db/schema.rb | 5 +++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 2d9c229ed5b..7b7e37353e3 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -16,9 +16,8 @@ - if local_assigns.fetch(:allow_close, false) && deployment.closeable? .inline - %a.close-env-link.btn - = link_to [:play, @project.namespace.becomes(Namespace), @project, deployment.close_action], method: :post, rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do - = icon('stop', class: 'close-env-icon') + = link_to [:play, @project.namespace.becomes(Namespace), @project, deployment.close_action], method: :post, class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + = icon('stop', class: 'close-env-icon') - if local_assigns.fetch(:allow_rollback, false) = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn' do diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 702cccd6ab3..11e2ff506b4 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -7,12 +7,12 @@ %ul.nav-links %li{class: ('active' if @scope.nil?)} = link_to project_environments_path(@project) do - Availabe + Available %span.badge.js-avaibale-environments-count = number_with_delimiter(@all_environments.opened.count) %li{class: ('active' if @scope == 'closed')} - = link_to project_environments_path(@project, scope: :stopped) do + = link_to project_environments_path(@project, scope: :closed) do Stopped %span.badge.js-stopped-environments-count = number_with_delimiter(@all_environments.closed.count) diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 5f4a0ef082c..4981b95a19b 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -14,7 +14,6 @@ = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - if @environment.opened? && last_deployment.try(:close_action) = link_to 'Close', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? .blank-state.blank-state-no-icon diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 803cbca584d..d3fabe111a1 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -17,6 +17,7 @@ class Gitlab::Seeder::Pipelines { name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running }, { name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled }, { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success }, + { name: 'close staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped, options: { environment: { close: true } } }, { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped }, { name: 'slack', stage: 'notify', when: 'manual', status: :created }, ] diff --git a/db/schema.rb b/db/schema.rb index ad62c249b3f..b9dc5b5f895 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160926145521) do +ActiveRecord::Schema.define(version: 20161006104309) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -392,11 +392,12 @@ ActiveRecord::Schema.define(version: 20160926145521) do create_table "environments", force: :cascade do |t| t.integer "project_id" - t.string "name", null: false + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" t.string "external_url" t.string "environment_type" + t.string "state", default: "opened", null: false end add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree -- cgit v1.2.1 From 492b4332a46fa0ae7d6547fe7417977f34c77b99 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2016 23:30:01 +0100 Subject: Added link to bulk assign issues to MR author. (Issue #18876) --- CHANGELOG | 1 + .../projects/merge_requests_controller.rb | 21 ++++++++- app/helpers/merge_requests_helper.rb | 13 ++++++ .../merge_requests/assign_issues_service.rb | 35 +++++++++++++++ .../projects/merge_requests/widget/_open.html.haml | 1 + config/routes/project.rb | 1 + .../projects/merge_requests_controller_spec.rb | 31 +++++++++++++ spec/features/merge_requests/assign_issues_spec.rb | 51 ++++++++++++++++++++++ .../merge_requests/assign_issues_service_spec.rb | 49 +++++++++++++++++++++ 9 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 app/services/merge_requests/assign_issues_service.rb create mode 100644 spec/features/merge_requests/assign_issues_spec.rb create mode 100644 spec/services/merge_requests/assign_issues_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..a273e58f65c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -55,6 +55,7 @@ v 8.13.0 (unreleased) - Fix broken repository 500 errors in project list - Fix Pipeline list commit column width should be adjusted - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8c8c56228ad..8bbf3ec67b3 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, - :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts + :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] @@ -355,6 +355,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController render layout: false end + def assign_related_issues + result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute + + respond_to do |format| + format.html do + case result[:count] + when 0 + flash[:error] = "Failed to assign you issues related to the merge request" + when 1 + flash[:notice] = "1 issue has been assigned to you" + else + flash[:notice] = "#{result[:count]} issues have been assigned to you" + end + + redirect_to(merge_request_path(@merge_request)) + end + end + end + def ci_status pipeline = @merge_request.pipeline if pipeline diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 8abe7865fed..b0a76765d97 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -72,6 +72,19 @@ module MergeRequestsHelper ) end + def mr_assign_issues_link + issues = MergeRequests::AssignIssuesService.new(@project, + current_user, + merge_request: @merge_request, + closes_issues: mr_closes_issues + ).assignable_issues + path = assign_related_issues_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + if issues.present? + pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue" + link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post + end + end + def source_branch_with_namespace(merge_request) branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch)) diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb new file mode 100644 index 00000000000..2e84f844449 --- /dev/null +++ b/app/services/merge_requests/assign_issues_service.rb @@ -0,0 +1,35 @@ +module MergeRequests + class AssignIssuesService < BaseService + def assignable_issues + @assignable_issues ||= begin + if current_user == merge_request.author + closes_issues. + reject { |issue| issue.assignee_id? }. + select { |issue| can?(current_user, :admin_issue, issue) } + else + [] + end + end + end + + def execute + assignable_issues.each do |issue| + Issues::UpdateService.new(issue.project, current_user, assignee_id: current_user.id).execute(issue) + end + + { + count: assignable_issues.count + } + end + + private + + def merge_request + params[:merge_request] + end + + def closes_issues + @closes_issues ||= params[:closes_issues] || merge_request.closes_issues(current_user) + end + end +end diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 6f5ee5f16c5..842b6df310d 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -35,3 +35,4 @@ Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)} = succeed '.' do != markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author + = mr_assign_issues_link diff --git a/config/routes/project.rb b/config/routes/project.rb index 224ec7e8324..6c0c2a1184f 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -277,6 +277,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: post :remove_wip get :diff_for_path post :resolve_conflicts + post :assign_related_issues end collection do diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 742edd8ba3d..18041bce482 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -708,4 +708,35 @@ describe Projects::MergeRequestsController do end end end + + describe 'POST assign_related_issues' do + let(:issue1) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project) } + + def post_assign_issues + merge_request.update!(description: "Closes #{issue1.to_reference} and #{issue2.to_reference}", + author: user, + source_branch: 'feature', + target_branch: 'master') + + post :assign_related_issues, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid + end + + it 'shows a flash message on success' do + post_assign_issues + + expect(flash[:notice]).to eq '2 issues have been assigned to you' + end + + it 'correctly pluralizes flash message on success' do + issue2.update!(assignee: user) + + post_assign_issues + + expect(flash[:notice]).to eq '1 issue has been assigned to you' + end + end end diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb new file mode 100644 index 00000000000..43cc6f2a2a7 --- /dev/null +++ b/spec/features/merge_requests/assign_issues_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +feature 'Merge request issue assignment', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue1) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue1.to_reference} and #{issue2.to_reference}") } + let(:service) { MergeRequests::AssignIssuesService.new(merge_request, user, user, project) } + + before do + project.team << [user, :developer] + end + + def visit_merge_request(current_user = nil) + login_as(current_user || user) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + context 'logged in as author' do + scenario 'updates related issues' do + visit_merge_request + click_link "Assign yourself to these issues" + + expect(page).to have_content "2 issues have been assigned to you" + end + + it 'returns user to the merge request' do + visit_merge_request + click_link "Assign yourself to these issues" + + expect(page).to have_content merge_request.description + end + + it "doesn't display if related issues are already assigned" do + [issue1, issue2].each { |issue| issue.update!(assignee: user) } + + visit_merge_request + + expect(page).not_to have_content "Assign yourself" + end + end + + context 'not MR author' do + it "doesn't not show assignment link" do + visit_merge_request(create(:user)) + + expect(page).not_to have_content "Assign yourself" + end + end +end diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb new file mode 100644 index 00000000000..7aeb95a15ea --- /dev/null +++ b/spec/services/merge_requests/assign_issues_service_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe MergeRequests::AssignIssuesService, services: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue.to_reference}") } + let(:service) { described_class.new(project, user, merge_request: merge_request) } + + before do + project.team << [user, :developer] + end + + it 'finds unassigned issues fixed in merge request' do + expect(service.assignable_issues.map(&:id)).to include(issue.id) + end + + it 'ignores issues already assigned to any user' do + issue.update!(assignee: create(:user)) + + expect(service.assignable_issues).to be_empty + end + + it 'ignores issues the user cannot update assignee on' do + project.team.truncate + + expect(service.assignable_issues).to be_empty + end + + it 'ignores all issues unless current_user is merge_request.author' do + merge_request.update!(author: create(:user)) + + expect(service.assignable_issues).to be_empty + end + + it 'accepts precomputed data for closes_issues' do + issue2 = create(:issue, project: project) + service2 = described_class.new(project, + user, + merge_request: merge_request, + closes_issues: [issue, issue2]) + + expect(service2.assignable_issues.count).to eq 2 + end + + it 'assigns these to the merge request owner' do + expect { service.execute }.to change { issue.reload.assignee }.to(user) + end +end -- cgit v1.2.1 From a43baa056e69827c342e705e2d5ea8cfc67bfd9c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 6 Oct 2016 14:52:00 +0200 Subject: Rename pipeline workers to match current convention --- app/models/commit_status.rb | 4 ++-- app/workers/pipeline_process_worker.rb | 10 ++++++++++ app/workers/pipeline_update_worker.rb | 10 ++++++++++ app/workers/process_pipeline_worker.rb | 10 ---------- app/workers/update_pipeline_worker.rb | 10 ---------- spec/workers/pipeline_proccess_worker_spec.rb | 22 ++++++++++++++++++++++ spec/workers/pipeline_update_worker_spec.rb | 22 ++++++++++++++++++++++ spec/workers/process_pipeline_worker_spec.rb | 22 ---------------------- spec/workers/update_pipeline_worker_spec.rb | 22 ---------------------- 9 files changed, 66 insertions(+), 66 deletions(-) create mode 100644 app/workers/pipeline_process_worker.rb create mode 100644 app/workers/pipeline_update_worker.rb delete mode 100644 app/workers/process_pipeline_worker.rb delete mode 100644 app/workers/update_pipeline_worker.rb create mode 100644 spec/workers/pipeline_proccess_worker_spec.rb create mode 100644 spec/workers/pipeline_update_worker_spec.rb delete mode 100644 spec/workers/process_pipeline_worker_spec.rb delete mode 100644 spec/workers/update_pipeline_worker_spec.rb diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 5a240bcf2c6..3b575e5627d 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -89,10 +89,10 @@ class CommitStatus < ActiveRecord::Base break if transition.loopback? if commit_status.complete? - ProcessPipelineWorker.perform_async(pipeline.id) + PipelineProcessWorker.perform_async(pipeline.id) end - UpdatePipelineWorker.perform_async(pipeline.id) + PipelineUpdateWorker.perform_async(pipeline.id) end true diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb new file mode 100644 index 00000000000..f44227d7086 --- /dev/null +++ b/app/workers/pipeline_process_worker.rb @@ -0,0 +1,10 @@ +class PipelineProcessWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id) + .try(:process!) + end +end diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb new file mode 100644 index 00000000000..44a7f24e401 --- /dev/null +++ b/app/workers/pipeline_update_worker.rb @@ -0,0 +1,10 @@ +class PipelineUpdateWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id) + .try(:update_status) + end +end diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/process_pipeline_worker.rb deleted file mode 100644 index 26ea5f1c24d..00000000000 --- a/app/workers/process_pipeline_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class ProcessPipelineWorker - include Sidekiq::Worker - - sidekiq_options queue: :default - - def perform(pipeline_id) - Ci::Pipeline.find_by(id: pipeline_id) - .try(:process!) - end -end diff --git a/app/workers/update_pipeline_worker.rb b/app/workers/update_pipeline_worker.rb deleted file mode 100644 index 6ef5678073e..00000000000 --- a/app/workers/update_pipeline_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class UpdatePipelineWorker - include Sidekiq::Worker - - sidekiq_options queue: :default - - def perform(pipeline_id) - Ci::Pipeline.find_by(id: pipeline_id) - .try(:update_status) - end -end diff --git a/spec/workers/pipeline_proccess_worker_spec.rb b/spec/workers/pipeline_proccess_worker_spec.rb new file mode 100644 index 00000000000..86e9d7f6684 --- /dev/null +++ b/spec/workers/pipeline_proccess_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe PipelineProcessWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'processes pipeline' do + expect_any_instance_of(Ci::Pipeline).to receive(:process!) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb new file mode 100644 index 00000000000..0b456cfd0da --- /dev/null +++ b/spec/workers/pipeline_update_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe PipelineUpdateWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'updates pipeline status' do + expect_any_instance_of(Ci::Pipeline).to receive(:update_status) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end diff --git a/spec/workers/process_pipeline_worker_spec.rb b/spec/workers/process_pipeline_worker_spec.rb deleted file mode 100644 index 7b5f98d5763..00000000000 --- a/spec/workers/process_pipeline_worker_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -describe ProcessPipelineWorker do - describe '#perform' do - context 'when pipeline exists' do - let(:pipeline) { create(:ci_pipeline) } - - it 'processes pipeline' do - expect_any_instance_of(Ci::Pipeline).to receive(:process!) - - described_class.new.perform(pipeline.id) - end - end - - context 'when pipeline does not exist' do - it 'does not raise exception' do - expect { described_class.new.perform(123) } - .not_to raise_error - end - end - end -end diff --git a/spec/workers/update_pipeline_worker_spec.rb b/spec/workers/update_pipeline_worker_spec.rb deleted file mode 100644 index fadc42b22f0..00000000000 --- a/spec/workers/update_pipeline_worker_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -describe UpdatePipelineWorker do - describe '#perform' do - context 'when pipeline exists' do - let(:pipeline) { create(:ci_pipeline) } - - it 'updates pipeline status' do - expect_any_instance_of(Ci::Pipeline).to receive(:update_status) - - described_class.new.perform(pipeline.id) - end - end - - context 'when pipeline does not exist' do - it 'does not raise exception' do - expect { described_class.new.perform(123) } - .not_to raise_error - end - end - end -end -- cgit v1.2.1 From f8da5eb8ef7cb757b88fd1c8d3c2dea7671e7daa Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 11:10:03 -0300 Subject: Revert "Label list shows all issues (opened or closed) with that label" --- app/services/boards/issues/list_service.rb | 7 +------ doc/user/project/issue_board.md | 7 ++++--- spec/services/boards/issues/list_service_spec.rb | 8 ++++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 34efd09ed9f..435a8c6e681 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -36,12 +36,7 @@ module Boards end def set_state - params[:state] = - case list.list_type.to_sym - when :backlog then 'opened' - when :done then 'closed' - else 'all' - end + params[:state] = list.done? ? 'closed' : 'opened' end def board_label_ids diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index cac926b3e28..4a6c0d88241 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -31,9 +31,10 @@ Below is a table of the definitions used for GitLab's Issue Board. There are three types of lists, the ones you create based on your labels, and two default: -- **Backlog** (default): shows all opened issues that do not fall in one of the other lists. Always appears on the very left. -- **Done** (default): shows all closed issues that do not fall in one of the other lists. Always appears on the very right. -- Label list: a list based on a label. It shows all opened or closed issues with that label. +- **Backlog** (default): shows all issues that do not fall in one of the other lists. Always appears on the very left. +- **Done** (default): shows all closed issues. Always appears on the very right. +Label list: a list based on a label. It shows all issues with that label. +- Label list: a list based on a label. It shows all opened issues with that label. ![GitLab Issue Board](img/issue_board.png) diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index e65da15aca8..5b9f454fd2d 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -30,7 +30,7 @@ describe Boards::Issues::ListService, services: true do let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug]) } let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3]) } let!(:closed_issue3) { create(:issue, :closed, project: project) } - let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1, development]) } + let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1]) } before do project.team << [user, :developer] @@ -58,15 +58,15 @@ describe Boards::Issues::ListService, services: true do issues = described_class.new(project, user, params).execute - expect(issues).to eq [closed_issue2, closed_issue3, closed_issue1] + expect(issues).to eq [closed_issue4, closed_issue2, closed_issue3, closed_issue1] end - it 'returns opened/closed issues that have label list applied when listing issues from a label list' do + it 'returns opened issues that have label list applied when listing issues from a label list' do params = { id: list1.id } issues = described_class.new(project, user, params).execute - expect(issues).to eq [closed_issue4, list1_issue3, list1_issue1, list1_issue2] + expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2] end end end -- cgit v1.2.1 From 0afe899ba3b5f85450bb02c0d1c44f91233f05b2 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 11:10:11 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 027c263eb44..4f9816b150d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.13.0 (unreleased) - Simplify Mentionable concern instance methods - Fix permission for setting an issue's due date - API: Multi-file commit !6096 (mahcsig) + - Revert "Label list shows all issues (opened or closed) with that label" - Expose expires_at field when sharing project on API - Fix VueJS template tags being rendered in code comments - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) -- cgit v1.2.1 From babb78d1adc7fb19615d9ed4ba974a83b75c5a8b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 6 Oct 2016 15:22:41 +0200 Subject: Remove doctoc from administration/container_registry --- doc/administration/container_registry.md | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index c5ba777d093..25c5474745b 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -16,26 +16,6 @@ have its own space to store its Docker images. You can read more about Docker Registry at https://docs.docker.com/registry/introduction/. ---- - - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Enable the Container Registry](#enable-the-container-registry) -- [Container Registry domain configuration](#container-registry-domain-configuration) - - [Configure Container Registry under an existing GitLab domain](#configure-container-registry-under-an-existing-gitlab-domain) - - [Configure Container Registry under its own domain](#configure-container-registry-under-its-own-domain) -- [Disable Container Registry site-wide](#disable-container-registry-site-wide) -- [Disable Container Registry per project](#disable-container-registry-per-project) -- [Disable Container Registry for new projects site-wide](#disable-container-registry-for-new-projects-site-wide) -- [Container Registry storage path](#container-registry-storage-path) -- [Container Registry storage driver](#container-registry-storage-driver) -- [Storage limitations](#storage-limitations) -- [Changelog](#changelog) - - - ## Enable the Container Registry **Omnibus GitLab installations** -- cgit v1.2.1 From 8ad923695e54c7ac6e9c47c37c505a7ac6b34017 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 14:52:36 +0100 Subject: Remove the code finding an issue by undefined --- app/assets/javascripts/boards/components/board_new_issue.js.es6 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 index 58cc30b05af..a4fad422eca 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ b/app/assets/javascripts/boards/components/board_new_issue.js.es6 @@ -39,8 +39,7 @@ // Need this because our jQuery very kindly disables buttons on ALL form submissions $(this.$els.submitButton).enable(); - // Remove issue with no ID - const issue = this.list.findIssue(undefined); + // Remove the issue this.list.removeIssue(issue); // Show error message -- cgit v1.2.1 From 9802d3883fb42dd31f91d71018ac87ba0c32d1c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 6 Oct 2016 15:59:21 +0200 Subject: Re-add CHANGELOG entries removed by merge (9cc700315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 864683efcdb..0ebf9084e6a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -62,6 +62,23 @@ v 8.13.0 (unreleased) - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Grouped pipeline dropdown is a scrollable container + +v 8.12.5 (unreleased) + +v 8.12.4 + - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) + - Fix padding in build sidebar. !6506 + - Changed compare dropdowns to dropdowns with isolated search input. !6550 + - Fix race condition on LFS Token. !6592 + - Fix type mismatch bug when closing Jira issue. !6619 + - Fix lint-doc error. !6623 + - Skip wiki creation when GitHub project has wiki enabled. !6665 + - Fix issues importing services via Import/Export. !6667 + - Restrict failed login attempts for users with 2FA enabled. !6668 + - Fix failed project deletion when feature visibility set to private. !6688 + - Prevent claiming associated model IDs via import. + - Set GitLab project exported file permissions to owner only v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -- cgit v1.2.1 From a483477091d0ffd2a39bc310bf18aa12796fc69e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Sat, 1 Oct 2016 14:33:52 +0200 Subject: Only hide deploy status external link on small screens. --- CHANGELOG | 1 + app/views/projects/merge_requests/widget/_heading.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0ebf9084e6a..58690a0453f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -51,6 +51,7 @@ v 8.13.0 (unreleased) - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile + - Fix deploy status responsiveness error !6633 - Fix resolved discussion display in side-by-side diff view !6575 - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index b5f5e11d4c3..7ba590d4ff5 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -49,11 +49,11 @@ .mr-widget-heading .ci_widget.ci-success = ci_icon_for_status("success") - %span.hidden-sm + %span Deployed to = succeed '.' do = link_to environment.name, environment_path(environment), class: 'environment' - external_url = environment.external_url - if external_url - = link_to external_url, target: '_blank' do + = link_to external_url, target: '_blank', class: 'hidden-xs' do = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) -- cgit v1.2.1 From 01af0aab4a02fca9f47006aae4b4578787c55870 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 6 Oct 2016 10:56:49 +0200 Subject: Only hide external link text, not icon/link. --- app/views/projects/merge_requests/widget/_heading.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 7ba590d4ff5..5b7f83c344f 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -55,5 +55,6 @@ = link_to environment.name, environment_path(environment), class: 'environment' - external_url = environment.external_url - if external_url - = link_to external_url, target: '_blank', class: 'hidden-xs' do - = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true) + = link_to external_url, target: '_blank' do + %span.hidden-xs View on #{external_url.gsub(/\A.*?:\/\//, '')} + = icon('external-link', right: true) -- cgit v1.2.1 From 86c0c0869dd4b9de301822a7d598bc4528604e5a Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 30 Sep 2016 12:21:05 +0100 Subject: Switch from request to env in ::API::Helpers Per https://gitlab.com/gitlab-org/gitlab-ce/issues/22820, this helper is mixed in to classes that lack a `request` method. They do include `env`, so use it instead. --- lib/api/helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 8b8c4eb4d46..281a8f13531 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -25,7 +25,7 @@ module API # Until CSRF protection is added to the API, disallow this method for # state-changing endpoints def find_user_from_warden - warden.try(:authenticate) if request.get? || request.head? + warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) end def find_user_by_private_token -- cgit v1.2.1 From 6865c46c6649118e09e60dd29dfa060470010aa9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 15:41:00 +0100 Subject: Changed how collections are rendered Used variables in haml for replicated checks Fixed broken conflict --- .../projects/project_members_controller.rb | 15 - app/views/projects/group_links/update.js.haml | 2 +- .../projects/project_members/_groups.html.haml | 4 +- app/views/projects/project_members/_team.html.haml | 3 +- app/views/projects/project_members/index.html.haml | 2 +- app/views/shared/members/_group.html.haml | 9 +- app/views/shared/members/_member.html.haml | 10 +- config/routes.rb | 794 --------------------- 8 files changed, 15 insertions(+), 824 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 67b41c4573a..37a86ed0523 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -98,21 +98,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController notice: notice) end - def options - users = User.all - users = users.search(params[:search]) if params[:search].present? - users = users.page(1) - - groups = Group.all - groups = groups.search(params[:search]) if params[:search].present? - groups = groups.page(1) - - render json: { - Groups: groups.as_json(only: [:id, :name], methods: [:avatar_url]), - Users: users.as_json(only: [:id, :name, :username], methods: [:avatar_url]), - } - end - protected def member_params diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml index 231d5a79723..af9a5b19060 100644 --- a/app/views/projects/group_links/update.js.haml +++ b/app/views/projects/group_links/update.js.haml @@ -1,3 +1,3 @@ :plain - var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link, group: @group_link.group))}'); + var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link))}'); $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index 11f896006da..d7f5fa96527 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -4,6 +4,4 @@ %strong #{@project.name} %span.badge= group_links.size %ul.content-list - - group_links.each do |group_link| - - group = group_link.group - = render 'shared/members/group', group_link: group_link, group: group + = render partial: 'shared/members/group', collection: group_links, as: :group_link diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index ff54035cfe1..c1e894d8f40 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -4,5 +4,4 @@ %strong #{@project.name} %span.badge= @project_members.total_count %ul.content-list - - members.each do |member| - = render 'shared/members/member', member: member + = render partial: 'shared/members/member', collection: members, as: :member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index f1461444241..bdeb704b6da 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -21,7 +21,7 @@ = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } = icon("search") - - if @group_links.size > 0 + - if @group_links.any? = render 'groups', group_links: @group_links = render 'team', members: @project_members diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 171a388b233..1c0346bbc78 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -1,5 +1,6 @@ -- group = local_assigns[:group] - group_link = local_assigns[:group_link] +- group = group_link.group +- can_admin_member = can?(current_user, :admin_project_member, @project) %li.member.group_member{ id: "group_member_#{group_link.id}" } %span{ class: "list-item-name" } = image_tag group_icon(group), class: "avatar s40", alt: '' @@ -13,11 +14,11 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can_admin_member .prepend-left-5.clearable-input.member-form-control - = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member %i.clear-icon.js-clear-input - - if can?(current_user, :admin_project_member, @project) + - if can_admin_member = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 80e52bf5637..432047a1c4e 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -2,6 +2,7 @@ - show_controls = local_assigns.fetch(:show_controls, true) - user = local_assigns.fetch(:user, member.user) - source = member.source +- can_admin_member = can?(current_user, action_member_permission(:update, member), member) %li.member{ class: dom_class(member), id: dom_id(member) } %span.list-item-name @@ -47,18 +48,19 @@ - if show_controls - if user != current_user = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member .prepend-left-5.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member %i.clear-icon.js-clear-input - else %span.member-access-text= member.human_access - - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + + - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source) = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, class: 'btn btn-default prepend-left-10' - - if member.request? && can?(current_user, action_member_permission(:update, member), member) + - elsif member.request? && can_admin_member = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), method: :post, class: 'btn btn-success prepend-left-10', diff --git a/config/routes.rb b/config/routes.rb index f93c406e692..bf7c5b76128 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,799 +74,6 @@ Rails.application.routes.draw do # Notification settings resources :notification_settings, only: [:create, :update] -<<<<<<< HEAD - # - # Import - # - namespace :import do - resource :github, only: [:create, :new], controller: :github do - post :personal_access_token - get :status - get :callback - get :jobs - end - - resource :gitlab, only: [:create], controller: :gitlab do - get :status - get :callback - get :jobs - end - - resource :bitbucket, only: [:create], controller: :bitbucket do - get :status - get :callback - get :jobs - end - - resource :google_code, only: [:create, :new], controller: :google_code do - get :status - post :callback - get :jobs - - get :new_user_map, path: :user_map - post :create_user_map, path: :user_map - end - - resource :fogbugz, only: [:create, :new], controller: :fogbugz do - get :status - post :callback - get :jobs - - get :new_user_map, path: :user_map - post :create_user_map, path: :user_map - end - - resource :gitlab_project, only: [:create, :new] do - post :create - end - end - - # - # Uploads - # - - scope path: :uploads do - # Note attachments and User/Group/Project avatars - get ":model/:mounted_as/:id/:filename", - to: "uploads#show", - constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } - - # Appearance - get ":model/:mounted_as/:id/:filename", - to: "uploads#show", - constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } - - # Project markdown uploads - get ":namespace_id/:project_id/:secret/:filename", - to: "projects/uploads#show", - constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } - end - - # Redirect old note attachments path to new uploads path. - get "files/note/:id/:filename", - to: redirect("uploads/note/attachment/%{id}/%{filename}"), - constraints: { filename: /[^\/]+/ } - - # - # Explore area - # - namespace :explore do - resources :projects, only: [:index] do - collection do - get :trending - get :starred - end - end - - resources :groups, only: [:index] - resources :snippets, only: [:index] - root to: 'projects#trending' - end - - # Compatibility with old routing - get 'public' => 'explore/projects#index' - get 'public/projects' => 'explore/projects#index' - - # - # Admin Area - # - namespace :admin do - resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do - resources :keys, only: [:show, :destroy] - resources :identities, except: [:show] - - member do - get :projects - get :keys - get :groups - put :block - put :unblock - put :unlock - put :confirm - post :impersonate - patch :disable_two_factor - delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' - end - end - - resource :impersonation, only: :destroy - - resources :abuse_reports, only: [:index, :destroy] - resources :spam_logs, only: [:index, :destroy] do - member do - post :mark_as_ham - end - end - - resources :applications - - resources :groups, constraints: { id: /[^\/]+/ } do - member do - put :members_update - end - end - - resources :deploy_keys, only: [:index, :new, :create, :destroy] - - resources :hooks, only: [:index, :create, :destroy] do - get :test - end - - resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do - post :preview, on: :collection - end - - resource :logs, only: [:show] - resource :health_check, controller: 'health_check', only: [:show] - resource :background_jobs, controller: 'background_jobs', only: [:show] - resource :system_info, controller: 'system_info', only: [:show] - resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } - - resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - root to: 'projects#index', as: :projects - - resources(:projects, - path: '/', - constraints: { id: /[a-zA-Z.0-9_\-]+/ }, - only: [:index, :show]) do - root to: 'projects#show' - - member do - put :transfer - post :repository_check - end - - resources :runner_projects, only: [:create, :destroy] - end - end - - resource :appearances, only: [:show, :create, :update], path: 'appearance' do - member do - get :preview - delete :logo - delete :header_logos - end - end - - resource :application_settings, only: [:show, :update] do - resources :services, only: [:index, :edit, :update] - put :reset_runners_token - put :reset_health_check_token - put :clear_repository_check_states - end - - resources :labels - - resources :runners, only: [:index, :show, :update, :destroy] do - member do - get :resume - get :pause - end - end - - resources :builds, only: :index do - collection do - post :cancel_all - end - end - - root to: 'dashboard#index' - end - - # - # Profile Area - # - resource :profile, only: [:show, :update] do - member do - get :audit_log - get :applications, to: 'oauth/applications#index' - - put :reset_private_token - put :update_username - end - - scope module: :profiles do - resource :account, only: [:show] do - member do - delete :unlink - end - end - resource :notifications, only: [:show, :update] - resource :password, only: [:new, :create, :edit, :update] do - member do - put :reset - end - end - resource :preferences, only: [:show, :update] - resources :keys, only: [:index, :show, :new, :create, :destroy] - resources :emails, only: [:index, :create, :destroy] - resource :avatar, only: [:destroy] - - resources :personal_access_tokens, only: [:index, :create] do - member do - put :revoke - end - end - - resource :two_factor_auth, only: [:show, :create, :destroy] do - member do - post :create_u2f - post :codes - patch :skip - end - end - - resources :u2f_registrations, only: [:destroy] - end - end - - scope(path: 'u/:username', - as: :user, - constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error - get '/users/almost_there' => 'confirmations#almost_there' - end - - root to: "root#index" - - # - # Project Area - # - resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template - - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) - end - - scope do - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end - - scope do - get( - '/find_file/*id', - to: 'find_file#show', - constraints: { id: /.+/, format: /html/ }, - as: :find_file - ) - end - - scope do - get( - '/files/*id', - to: 'find_file#list', - constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, - as: :files - ) - end - - scope do - post( - '/create_dir/*id', - to: 'tree#create_dir', - constraints: { id: /.+/ }, - as: 'create_dir' - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/commits/*id', - to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, - as: :commits - ) - end - - resource :avatar, only: [:show, :destroy] - resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do - member do - get :branches - get :builds - get :pipelines - post :cancel_builds - post :retry_builds - post :revert - post :cherry_pick - get :diff_for_path - end - end - - resources :compare, only: [:index, :create] do - collection do - get :diff_for_path - end - end - - get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } - - # Don't use format parameter as file extension (old 3.0.x behavior) - # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments - scope format: false do - resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } - - resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do - member do - get :commits - get :ci - get :languages - end - end - end - - resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get 'raw' - end - end - - WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID - - scope do - # Order matters to give priority to these matches - get '/wikis/git_access', to: 'wikis#git_access' - get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' - post '/wikis', to: 'wikis#create' - - get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID - get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID - - get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID - delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID - put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID - post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' - end - - resource :repository, only: [:create] do - member do - get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } - end - end - - resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do - member do - get :test - end - end - - resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do - member do - put :enable - put :disable - end - end - - resources :forks, only: [:index, :new, :create] - resource :import, only: [:new, :create, :show] - - resources :refs, only: [] do - collection do - get 'switch' - end - - member do - # tree viewer logs - get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } - # Directories with leading dots erroneously get rejected if git - # ref regex used in constraints. Regex verification now done in controller. - get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { - id: /.*/, - path: /.*/ - } - end - end - - resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get :commits - get :diffs - get :conflicts - get :builds - get :pipelines - get :merge_check - post :merge - post :cancel_merge_when_build_succeeds - get :ci_status - post :toggle_subscription - post :remove_wip - get :diff_for_path - post :resolve_conflicts - end - - collection do - get :branch_from - get :branch_to - get :update_branches - get :diff_for_path - post :bulk_update - end - - resources :discussions, only: [], constraints: { id: /\h{40}/ } do - member do - post :resolve - delete :resolve, action: :unresolve - end - end - end - - resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do - resource :release, only: [:edit, :update] - end - - resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :variables, only: [:index, :show, :update, :create, :destroy] - resources :triggers, only: [:index, :create, :destroy] - - resources :pipelines, only: [:index, :new, :create, :show] do - collection do - resource :pipelines_settings, path: 'settings', only: [:show, :update] - end - - member do - post :cancel - post :retry - end - end - - resources :environments - - resource :cycle_analytics, only: [:show] - - resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do - collection do - post :cancel_all - - resources :artifacts, only: [] do - collection do - get :latest_succeeded, - path: '*ref_name_and_path', - format: false - end - end - end - - member do - get :status - post :cancel - post :retry - post :play - post :erase - get :trace - get :raw - end - - resource :artifacts, only: [] do - get :download - get :browse, path: 'browse(/*path)', format: false - get :file, path: 'file/*path', format: false - post :keep - end - end - - resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do - member do - get :test - end - end - - resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } - - resources :milestones, constraints: { id: /\d+/ } do - member do - put :sort_issues - put :sort_merge_requests - end - end - - resources :labels, except: [:show], constraints: { id: /\d+/ } do - collection do - post :generate - post :set_priorities - end - - member do - post :toggle_subscription - delete :remove_priority - end - end - - resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do - member do - post :toggle_subscription - post :mark_as_spam - get :referenced_merge_requests - get :related_branches - get :can_create_branch - end - collection do - post :bulk_update - end - end - - resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do - collection do - delete :leave - - # Used for import team - # from another project - get :import - post :apply_import - end - - member do - post :resend_invite - end - end - - resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ } - - resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do - member do - delete :delete_attachment - post :resolve - delete :resolve, action: :unresolve - end - end - - resource :board, only: [:show] do - scope module: :boards do - resources :issues, only: [:update] - - resources :lists, only: [:index, :create, :update, :destroy] do - collection do - post :generate - end - - resources :issues, only: [:index] - end - end - end - - resources :todos, only: [:create] - - resources :uploads, only: [:create] do - collection do - get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } - end - end - - resources :runners, only: [:index, :edit, :update, :destroy, :show] do - member do - get :resume - get :pause - end - - collection do - post :toggle_shared_runners - end - end - - resources :runner_projects, only: [:create, :destroy] - resources :badges, only: [:index] do - collection do - scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do - constraints format: /svg/ do - get :build - get :coverage - end - end - end - end - end - end - end -======= draw :import draw :uploads draw :explore @@ -876,7 +83,6 @@ Rails.application.routes.draw do draw :group draw :user draw :project ->>>>>>> master # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } -- cgit v1.2.1 From b2e5240af3d14bd67900ef194226274639537888 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 15:42:48 +0100 Subject: Changed jQuery to be in single line Removed un-required CSS --- app/assets/javascripts/members.js.es6 | 5 +---- app/assets/stylesheets/pages/members.scss | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 1b4b3f38838..5dc6990bdb6 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -31,10 +31,7 @@ } formSubmit() { - $(this).closest('form') - .trigger("submit.rails") - .end() - .disable(); + $(this).closest('form').trigger("submit.rails").end().disable(); } formSuccess() { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 187151fe26c..ff4fd751f26 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -5,8 +5,6 @@ .member { .list-item-name { - float: none; - @media (min-width: $screen-sm-min) { float: left; width: 50%; -- cgit v1.2.1 From 3e13a45a4cb099093fa8e065224f06d4ee9cebc1 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 15:53:39 +0100 Subject: Removes Merge Request reference from commit column due to performance issues --- app/views/projects/deployments/_commit.html.haml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml index f71754597b6..dfd03bc466b 100644 --- a/app/views/projects/deployments/_commit.html.haml +++ b/app/views/projects/deployments/_commit.html.haml @@ -8,10 +8,6 @@ .icon-container = custom_icon("icon_commit") = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace" - - - if merge_request - %span in - = link_to_gfm merge_request.to_reference, merge_request_path(merge_request) %p.commit-title %span -- cgit v1.2.1 From 60caeb14a75f6700a3df6b73b7320737c4256ab2 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 6 Oct 2016 10:09:24 -0500 Subject: update subject and email header message --- app/mailers/emails/pipelines.rb | 6 ++++-- app/views/notify/pipeline_failed_email.html.haml | 2 +- app/views/notify/pipeline_failed_email.text.erb | 2 +- app/views/notify/pipeline_success_email.html.haml | 2 +- app/views/notify/pipeline_success_email.text.erb | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 2b2b7a4f08c..9aae39fa220 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -34,8 +34,10 @@ module Emails end def pipeline_subject(status) - subject( - "Pipeline #{status} for #{@project.name}", @pipeline.short_sha) + ref = @pipeline.short_sha + ref << " in #{@merge_request.to_reference}" if @merge_request + + subject("Pipeline ##{@pipeline.id} has #{status}", ref) end end end diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 179b1586e8f..3b3212e675e 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -65,7 +65,7 @@ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} %img{:alt => "x", :height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), :style => "display:block;", :width => "13"}/ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} - Uh oh, your CI pipeline has failed. + Your pipeline has failed. %tr.spacer %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb index f6d6f3119d3..373d5020b28 100644 --- a/app/views/notify/pipeline_failed_email.text.erb +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -1,4 +1,4 @@ -Uh oh, your CI pipeline has failed. +Your pipeline has failed. Project: <%= @project.name %> ( <%= namespace_project_url(@project.namespace, @project) %> ) Branch: <%= @pipeline.ref %> ( <%= namespace_project_commits_url(@project.namespace, @project, @pipeline.ref) %> ) diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index a21f71124cb..ff9c3059acc 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -65,7 +65,7 @@ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} %img{:alt => "✓", :height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), :style => "display:block;", :width => "13"}/ %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} - Success! Your CI pipeline has passed. + Your pipeline has passed. %tr.spacer %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index 9a0f839d089..c38a738f523 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -1,4 +1,4 @@ -Success! Your CI pipeline has passed. +Your pipeline has passed. Project: <%= @project.name %> ( <%= namespace_project_url(@project.namespace, @project) %> ) Branch: <%= @pipeline.ref %> ( <%= namespace_project_commits_url(@project.namespace, @project, @pipeline.ref) %> ) -- cgit v1.2.1 From f4c2f3a46652e4bb64814d327d96be5c7fcab669 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 6 Oct 2016 09:08:11 -0500 Subject: Change issue board defaults --- app/assets/javascripts/boards/components/board_blank_state.js.es6 | 6 ++---- app/services/boards/lists/generate_service.rb | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/boards/components/board_blank_state.js.es6 b/app/assets/javascripts/boards/components/board_blank_state.js.es6 index 63d72d857d9..ff90f2d6d75 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.js.es6 +++ b/app/assets/javascripts/boards/components/board_blank_state.js.es6 @@ -8,10 +8,8 @@ data () { return { predefinedLabels: [ - new ListLabel({ title: 'Development', color: '#5CB85C' }), - new ListLabel({ title: 'Testing', color: '#F0AD4E' }), - new ListLabel({ title: 'Production', color: '#FF5F00' }), - new ListLabel({ title: 'Ready', color: '#FF0000' }) + new ListLabel({ title: 'To Do', color: '#F0AD4E' }), + new ListLabel({ title: 'Doing', color: '#5CB85C' }) ] } }, diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 1c48b9786e4..830e386c98b 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -25,10 +25,8 @@ module Boards def label_params [ - { name: 'Development', color: '#5CB85C' }, - { name: 'Testing', color: '#F0AD4E' }, - { name: 'Production', color: '#FF5F00' }, - { name: 'Ready', color: '#FF0000' } + { name: 'To Do', color: '#F0AD4E' }, + { name: 'Doing', color: '#5CB85C' } ] end end -- cgit v1.2.1 From 9c931c63c9faad1f16d08aff001dc5e97cee1fda Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 6 Oct 2016 10:12:59 -0500 Subject: include pipeline ref in subject line --- app/mailers/emails/pipelines.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 9aae39fa220..941ba2643ab 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -34,10 +34,10 @@ module Emails end def pipeline_subject(status) - ref = @pipeline.short_sha - ref << " in #{@merge_request.to_reference}" if @merge_request + commit = @pipeline.short_sha + commit << " in #{@merge_request.to_reference}" if @merge_request - subject("Pipeline ##{@pipeline.id} has #{status}", ref) + subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit) end end end -- cgit v1.2.1 From fe46e4eb35dd6728a8a5ebcee9cc05a4613effbf Mon Sep 17 00:00:00 2001 From: Justin DiPierro Date: Thu, 29 Sep 2016 12:46:54 -0400 Subject: Load Github::Shell's secret token from file on initialization instead of every request. --- CHANGELOG | 1 + config/initializers/gitlab_shell_secret_token.rb | 2 +- lib/api/helpers.rb | 2 +- lib/gitlab/backend/shell.rb | 46 ++++++++++++++++-------- lib/tasks/gitlab/shell.rake | 2 +- spec/lib/gitlab/backend/shell_spec.rb | 8 +++-- 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3bbf580b67e..c442ac4e058 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -42,6 +42,7 @@ v 8.13.0 (unreleased) - Prevent flash alert text from being obscured when container is fluid - Append issue template to existing description !6149 (Joseph Frazier) - Trending projects now only show public projects and the list of projects is cached for a day + - Memoize Gitlab Shell's secret token (!6599, Justin DiPierro) - Revoke button in Applications Settings underlines on hover. - Use higher size on Gitlab::Redis connection pool on Sidekiq servers - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb index 7454c33c9dd..529dcdd4644 100644 --- a/config/initializers/gitlab_shell_secret_token.rb +++ b/config/initializers/gitlab_shell_secret_token.rb @@ -1 +1 @@ -Gitlab::Shell.new.generate_and_link_secret_token +Gitlab::Shell.ensure_secret_token! diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 8b8c4eb4d46..e3b947adcc3 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -433,7 +433,7 @@ module API end def secret_token - File.read(Gitlab.config.gitlab_shell.secret_file).chomp + Gitlab::Shell.secret_token end def send_git_blob(repository, blob) diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 79eac66b364..d0060fbaca1 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -17,6 +17,18 @@ module Gitlab end class << self + def secret_token + @secret_token ||= begin + File.read(Gitlab.config.gitlab_shell.secret_file).chomp + end + end + + def ensure_secret_token! + return if File.exist?(File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret')) + + generate_and_link_secret_token + end + def version_required @version_required ||= File.read(Rails.root. join('GITLAB_SHELL_VERSION')).strip @@ -25,6 +37,25 @@ module Gitlab def strip_key(key) key.split(/ /)[0, 2].join(' ') end + + private + + # Create (if necessary) and link the secret token file + def generate_and_link_secret_token + secret_file = Gitlab.config.gitlab_shell.secret_file + shell_path = Gitlab.config.gitlab_shell.path + + unless File.size?(secret_file) + # Generate a new token of 16 random hexadecimal characters and store it in secret_file. + token = SecureRandom.hex(16) + File.write(secret_file, token) + end + + link_path = File.join(shell_path, '.gitlab_shell_secret') + if File.exist?(shell_path) && !File.exist?(link_path) + FileUtils.symlink(secret_file, link_path) + end + end end # Init new repository @@ -201,21 +232,6 @@ module Gitlab File.exist?(full_path(storage, dir_name)) end - # Create (if necessary) and link the secret token file - def generate_and_link_secret_token - secret_file = Gitlab.config.gitlab_shell.secret_file - unless File.size?(secret_file) - # Generate a new token of 16 random hexadecimal characters and store it in secret_file. - token = SecureRandom.hex(16) - File.write(secret_file, token) - end - - link_path = File.join(gitlab_shell_path, '.gitlab_shell_secret') - if File.exist?(gitlab_shell_path) && !File.exist?(link_path) - FileUtils.symlink(secret_file, link_path) - end - end - protected def gitlab_shell_path diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index bb7eb852f1b..210899882b4 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -78,7 +78,7 @@ namespace :gitlab do f.puts "PATH=#{ENV['PATH']}" end - Gitlab::Shell.new.generate_and_link_secret_token + Gitlab::Shell.ensure_secret_token! end desc "GitLab | Setup gitlab-shell" diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb index 07407f212aa..c9c7ef8f479 100644 --- a/spec/lib/gitlab/backend/shell_spec.rb +++ b/spec/lib/gitlab/backend/shell_spec.rb @@ -22,15 +22,14 @@ describe Gitlab::Shell, lib: true do it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") } - describe 'generate_and_link_secret_token' do + describe 'memoized secret_token' do let(:secret_file) { 'tmp/tests/.secret_shell_test' } let(:link_file) { 'tmp/tests/shell-secret-test/.gitlab_shell_secret' } before do - allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test') allow(Gitlab.config.gitlab_shell).to receive(:secret_file).and_return(secret_file) + allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test') FileUtils.mkdir('tmp/tests/shell-secret-test') - gitlab_shell.generate_and_link_secret_token end after do @@ -39,7 +38,10 @@ describe Gitlab::Shell, lib: true do end it 'creates and links the secret token file' do + secret_token = Gitlab::Shell.secret_token + expect(File.exist?(secret_file)).to be(true) + expect(File.read(secret_file).chomp).to eq(secret_token) expect(File.symlink?(link_file)).to be(true) expect(File.readlink(link_file)).to eq(secret_file) end -- cgit v1.2.1 From d89c07e064e8ef7f7023660f66e56aec20d6455b Mon Sep 17 00:00:00 2001 From: Justin DiPierro Date: Thu, 6 Oct 2016 11:35:03 -0400 Subject: Add tooltip to project fork count --- CHANGELOG | 1 + app/views/projects/buttons/_fork.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3bbf580b67e..b70332e3114 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.13.0 (unreleased) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 - Add configurable email subject suffix (Fu Xu) + - Added tooltip to fork count on project show page. (Justin DiPierro) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Replace `alias_method_chain` with `Module#prepend` - Enable GitLab Import/Export for non-admin users. diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 22db33498f1..29d549a60f5 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -5,10 +5,10 @@ = custom_icon('icon_fork') %span Fork - else - = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do + = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn has-tooltip' do = custom_icon('icon_fork') %span Fork %div.count-with-arrow %span.arrow - = link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do + = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count has-tooltip' do = @project.forks_count -- cgit v1.2.1 From f7303dd669765745729d947f7fc410230d17c35c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 16:49:29 +0100 Subject: Adds CHANGELOG entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 51515ac5ade..5f5aa957044 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -50,6 +50,7 @@ v 8.13.0 (unreleased) - Fix broken repository 500 errors in project list - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) + - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) v 8.12.4 (unreleased) -- cgit v1.2.1 From 8522fc15c1c3a1a383492e0601136586bf1e621a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 6 Oct 2016 17:59:18 +0200 Subject: Add notice about HTTPS cloning for CI --- doc/user/project/new_ci_build_permissions_model.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index e73f60023b5..5253825d507 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -98,6 +98,9 @@ As an Administrator, you can verify that the user is a member of the group or project they're trying to have access to, and you can impersonate the user to retry the failing build in order to verify that everything is correct. +You need to make sure that your installation has HTTPS cloning enabled. +HTTPS support is required by GitLab CI to clone all sources. + ## Build triggers [Build triggers][triggers] do not support the new permission model. -- cgit v1.2.1 From 5c7904d786c5f176d94753b2db5c9efaa23a80fe Mon Sep 17 00:00:00 2001 From: Akram FARES Date: Mon, 26 Sep 2016 15:46:30 +0000 Subject: Add tag shortcut from the Commit page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/views/projects/commit/_commit_box.html.haml | 2 ++ features/project/commits/commits.feature | 5 +++++ features/steps/project/commits/commits.rb | 8 ++++++++ 4 files changed, 16 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 3bbf580b67e..30c8e801ea1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.13.0 (unreleased) - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Speed-up group milestones show page - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) + - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 29d767e7769..026910abb90 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -24,6 +24,8 @@ = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false) %li.clearfix = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false) + %li.clearfix + = link_to "Tag", new_namespace_project_tag_path(@project.namespace, @project, ref: @commit) %li.divider %li.dropdown-header Download diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index 8b0cb90765e..1776c07e60e 100644 --- a/features/project/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -37,6 +37,11 @@ Feature: Project Commits Then I see commit info And I see side-by-side diff button + Scenario: I browse commit from list and create a new tag + Given I click on commit link + And I click on tag link + Then I see commit SHA pre-filled + Scenario: I browse commit with ci from list Given commit has ci status And repository contains ".gitlab-ci.yml" file diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index bea9f9d198b..b8264f97687 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -24,6 +24,14 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(body).to have_selector("entry summary", text: commit.description[0..10]) end + step 'I click on tag link' do + click_link "Tag" + end + + step 'I see commit SHA pre-filled' do + expect(page).to have_selector("input[value='#{sample_commit.id}']") + end + step 'I click on commit link' do visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id) end -- cgit v1.2.1 From db77289bcb0a075d8de5c7052ef93a91616afb76 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 6 Oct 2016 18:42:30 +0200 Subject: Document how to change the Registry's internal port Closes https://gitlab.com/gitlab-org/omnibus-gitlab/issues/1433 --- doc/administration/container_registry.md | 85 +++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 23 deletions(-) diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index 25c5474745b..d7cfb464f74 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -1,31 +1,32 @@ -# GitLab Container Registry Administration +# GitLab Container Registry administration > [Introduced][ce-4040] in GitLab 8.8. --- -> **Note** -Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker -versions earlier than 1.10. -> -This document is about the admin guide. To learn how to use GitLab Container -Registry [user documentation](../user/project/container_registry.md). +> **Notes:** +- Container Registry manifest `v1` support was added in GitLab 8.9 to support + Docker versions earlier than 1.10. +- This document is about the admin guide. To learn how to use GitLab Container + Registry [user documentation](../user/project/container_registry.md). -With the Docker Container Registry integrated into GitLab, every project can -have its own space to store its Docker images. +With the Container Registry integrated into GitLab, every project can have its +own space to store its Docker images. -You can read more about Docker Registry at https://docs.docker.com/registry/introduction/. +You can read more about the Container Registry at +https://docs.docker.com/registry/introduction/. ## Enable the Container Registry **Omnibus GitLab installations** All you have to do is configure the domain name under which the Container -Registry will listen to. Read [#container-registry-domain-configuration](#container-registry-domain-configuration) +Registry will listen to. Read +[#container-registry-domain-configuration](#container-registry-domain-configuration) and pick one of the two options that fits your case. >**Note:** -The container Registry works under HTTPS by default. Using HTTP is possible +The container registry works under HTTPS by default. Using HTTP is possible but not recommended and out of the scope of this document. Read the [insecure Registry documentation][docker-insecure] if you want to implement this. @@ -36,7 +37,7 @@ implement this. If you have installed GitLab from source: -1. You will have to [install Docker Registry][registry-deploy] by yourself. +1. You will have to [install Registry][registry-deploy] by yourself. 1. After the installation is complete, you will have to configure the Registry's settings in `gitlab.yml` in order to enable it. 1. Use the sample NGINX configuration file that is found under @@ -69,11 +70,13 @@ where: | `issuer` | This should be the same value as configured in Registry's `issuer`. Read the [token auth configuration documentation][token-config]. | >**Note:** -GitLab does not ship with a Registry init file. Hence, [restarting GitLab][restart gitlab] -will not restart the Registry should you modify its settings. Read the upstream -documentation on how to achieve that. +A Registry init file is not shipped with GitLab if you install it from source. +Hence, [restarting GitLab][restart gitlab] will not restart the Registry should +you modify its settings. Read the upstream documentation on how to achieve that. -The Docker Registry configuration will need `container_registry` as the service and `https://gitlab.example.com/jwt/auth` as the realm: +At the absolute minimum, make sure your [Registry configuration][registry-auth] +has `container_registry` as the service and `https://gitlab.example.com/jwt/auth` +as the realm: ``` auth: @@ -264,12 +267,6 @@ Registry application itself. 1. Save the file and [restart GitLab][] for the changes to take effect. -## Disable Container Registry per project - -If Registry is enabled in your GitLab instance, but you don't need it for your -project, you can disable it from your project's settings. Read the user guide -on how to achieve that. - ## Disable Container Registry for new projects site-wide If the Container Registry is enabled, then it will be available on all new @@ -425,6 +422,46 @@ storage: enabled: true ``` +## Change the registry's internal port + +> **Note:** +This is not to be confused with the port that GitLab itself uses to expose +the Registry to the world. + +The Registry server listens on localhost at port `5000` by default, +which is the address for which the Registry server should accept connections. +In the examples below we set the Registry's port to `5001`. + +**Omnibus GitLab** + +1. Open `/etc/gitlab/gitlab.rb` and set `registry['registry_http_addr']`: + + ```ruby + registry['registry_http_addr'] = "localhost:5001" + ``` + +1. Save the file and [reconfigure GitLab][] for the changes to take effect. + +--- + +**Installations from source** + +1. Open the configuration file of your Registry server and edit the + [`http:addr`][registry-http-config] value: + + ``` + http + addr: localhost:5001 + ``` + +1. Save the file and restart the Registry server. + +## Disable Container Registry per project + +If Registry is enabled in your GitLab instance, but you don't need it for your +project, you can disable it from your project's settings. Read the user guide +on how to achieve that. + ## Storage limitations Currently, there is no storage limitation, which means a user can upload an @@ -444,6 +481,8 @@ configurable in future releases. [docker-insecure]: https://docs.docker.com/registry/insecure/ [registry-deploy]: https://docs.docker.com/registry/deploying/ [storage-config]: https://docs.docker.com/registry/configuration/#storage +[registry-http-config]: https://docs.docker.com/registry/configuration/#http +[registry-auth]: https://docs.docker.com/registry/configuration/#auth [token-config]: https://docs.docker.com/registry/configuration/#token [8-8-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-8-stable/doc/administration/container_registry.md [registry-ssl]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/nginx/registry-ssl -- cgit v1.2.1 From 67b85665777c7ee4d808a58bff8fbe199cfdbab2 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 6 Oct 2016 15:37:25 +0100 Subject: Try tmpfs for repository storage, etc --- .gitlab-ci.yml | 2 ++ spec/spec_helper.rb | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8645488335e..cb6f691058e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,8 @@ variables: before_script: - source ./scripts/prepare_build.sh - cp config/gitlab.yml.example config/gitlab.yml + - mkdir -p tmp/tests + - mount -t tmpfs tmpfs tmp/tests || echo "tmpfs mount failed, falling back to disc" - bundle --version - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - retry gem install knapsack diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b19f5824236..f313bd4f249 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -50,6 +50,11 @@ RSpec.configure do |config| example.run Rails.cache = caching_store end + + config.after(:each) do + FileUtils.rm_rf("tmp/tests/repositories") + FileUtils.mkdir_p("tmp/tests/repositories") + end end FactoryGirl::SyntaxRunner.class_eval do -- cgit v1.2.1 From 2fc6e85dfcdac71f8eab888eaa818e97466eb04b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 6 Oct 2016 18:44:09 +0200 Subject: Change link to point to new location [ci skip] --- doc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..2357bdb5be2 100644 --- a/doc/README.md +++ b/doc/README.md @@ -6,7 +6,7 @@ - [API](api/README.md) Automate GitLab via a simple and powerful API. - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. -- [Container Registry](container_registry/README.md) Learn how to use GitLab Container Registry. +- [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry. - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). - [Importing and exporting projects between instances](user/project/settings/import_export.md). -- cgit v1.2.1 From 6b90ccb9fd57401912e1978cbad28cc693a2e0a1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 6 Oct 2016 15:14:24 +0300 Subject: Change user & group landing page routing from /u/:name & /groups/:name to /:name Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/assets/javascripts/user_tabs.js.es6 | 11 ++----- app/assets/javascripts/users_select.js | 4 +-- .../projects/boards/components/_card.html.haml | 2 +- app/views/users/show.html.haml | 2 +- config/routes/group.rb | 8 +++++ config/routes/user.rb | 35 ++++++++++++++-------- lib/constraints/group_url_constrainer.rb | 7 +++++ lib/constraints/namespace_url_constrainer.rb | 13 ++++++++ lib/constraints/user_url_constrainer.rb | 7 +++++ spec/features/users_spec.rb | 22 ++++++++++++++ spec/helpers/projects_helper_spec.rb | 2 +- .../constraints/namespace_url_constrainer_spec.rb | 26 ++++++++++++++++ spec/routing/routing_spec.rb | 4 ++- spec/services/system_note_service_spec.rb | 2 +- 15 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 lib/constraints/group_url_constrainer.rb create mode 100644 lib/constraints/namespace_url_constrainer.rb create mode 100644 lib/constraints/user_url_constrainer.rb create mode 100644 spec/lib/constraints/namespace_url_constrainer_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 30c8e801ea1..43713d9c9e2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,6 +82,7 @@ v 8.12.4 - Fix failed project deletion when feature visibility set to private. !6688 - Prevent claiming associated model IDs via import. - Set GitLab project exported file permissions to owner only + - Change user & group landing page routing from /u/:username to /:username v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6 index 63bce0a6f6f..dfdfa1e7f75 100644 --- a/app/assets/javascripts/user_tabs.js.es6 +++ b/app/assets/javascripts/user_tabs.js.es6 @@ -89,7 +89,7 @@ content on the Users#show page. const action = $target.data('action'); const source = $target.attr('href'); this.setTab(source, action); - return this.setCurrentAction(action); + return this.setCurrentAction(source, action); } activateTab(action) { @@ -142,14 +142,9 @@ content on the Users#show page. .toggle(status); } - setCurrentAction(action) { - const regExp = new RegExp(`\/(${this.actions.join('|')})(\.html)?\/?$`); - let new_state = this._location.pathname; + setCurrentAction(source, action) { + let new_state = source new_state = new_state.replace(/\/+$/, ''); - new_state = new_state.replace(regExp, ''); - if (action !== this.defaultAction) { - new_state += `/${action}`; - } new_state += this._location.search + this._location.hash; history.replaceState({ turbolinks: true, diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 05056a73aaf..bcabda3ceb2 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -71,8 +71,8 @@ return $collapsedSidebar.html(collapsedAssigneeTemplate(user)); }); }; - collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <% } else { %> <% } %>'); - assigneeTemplate = _.template('<% if (username) { %> <% if( avatar ) { %> <% } %> <%- name %> @<%- username %> <% } else { %> No assignee - assign yourself <% } %>'); + collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <% } else { %> <% } %>'); + assigneeTemplate = _.template('<% if (username) { %> <% if( avatar ) { %> <% } %> <%- name %> @<%- username %> <% } else { %> No assignee - assign yourself <% } %>'); return $dropdown.glDropdown({ showMenuAbove: showMenuAbove, data: function(term, callback) { diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index f15c87c8185..d8f16022407 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -26,7 +26,7 @@ ":title" => "label.description", data: { container: 'body' } } {{ label.title }} - %a.has-tooltip{ ":href" => "'/u/' + issue.assignee.username", + %a.has-tooltip{ ":href" => "'/' + issue.assignee.username", ":title" => "'Assigned to ' + issue.assignee.name", "v-if" => "issue.assignee", data: { container: 'body' } } diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 60fc0c0daf6..19759a33add 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -82,7 +82,7 @@ %ul.nav-links.center.user-profile-nav %li.js-activity-tab - = link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do + = link_to user_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do Activity %li.js-groups-tab = link_to user_groups_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do diff --git a/config/routes/group.rb b/config/routes/group.rb index 5b3e25d5e3d..47a8a0a53d4 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -1,3 +1,11 @@ +require 'constraints/group_url_constrainer' + +constraints(GroupUrlConstrainer.new) do + scope(path: ':id', as: :group, controller: :groups) do + get '/', action: :show + end +end + resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error get '/users/almost_there' => 'confirmations#almost_there' end + +constraints(UserUrlConstrainer.new) do + scope(path: ':username', as: :user, controller: :users) do + get '/', action: :show + end +end + +scope(path: 'u/:username', + as: :user, + constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Thu, 6 Oct 2016 15:34:30 +0300 Subject: Fix users feature spec Signed-off-by: Dmitriy Zaporozhets --- spec/features/users_spec.rb | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index f2c0aa7784a..6498b7317b4 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -46,14 +46,8 @@ feature 'Users', feature: true do scenario '/u/user1 redirects to user page' do visit '/u/user1' - expect_user_show_page - end - - - scenario '/users/user1 redirects to user page' do - visit '/users/user1' - - expect_user_show_page + expect(current_path).to eq user_path(user) + expect(page).to have_text(user.name) end end @@ -64,9 +58,4 @@ feature 'Users', feature: true do def number_of_errors_on_page(page) page.find('#error_explanation').find('ul').all('li').count end - - def expect_user_show_page - expect(current_path).to eq user_path(user) - expect(page).to have_text(user.name) - end end -- cgit v1.2.1 From 42c6555bab47eb042749be1f24d486797eb8d8ee Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 6 Oct 2016 16:59:22 +0300 Subject: Make user constrainer lookup same as controller and add more constrainer tests Signed-off-by: Dmitriy Zaporozhets --- lib/constraints/user_url_constrainer.rb | 2 +- spec/lib/constraints/group_url_constrainer_spec.rb | 10 ++++++++++ spec/lib/constraints/namespace_url_constrainer_spec.rb | 1 - spec/lib/constraints/user_url_constrainer_spec.rb | 10 ++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 spec/lib/constraints/group_url_constrainer_spec.rb create mode 100644 spec/lib/constraints/user_url_constrainer_spec.rb diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb index ef38d47d5c6..504a0f5d93e 100644 --- a/lib/constraints/user_url_constrainer.rb +++ b/lib/constraints/user_url_constrainer.rb @@ -2,6 +2,6 @@ require 'constraints/namespace_url_constrainer' class UserUrlConstrainer < NamespaceUrlConstrainer def find_resource(id) - User.find_by_username(id) + User.find_by('lower(username) = ?', id.downcase) end end diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb new file mode 100644 index 00000000000..f0b75a664f2 --- /dev/null +++ b/spec/lib/constraints/group_url_constrainer_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +describe GroupUrlConstrainer, lib: true do + let!(:username) { create(:group, path: 'gitlab-org') } + + describe '#find_resource' do + it { expect(!!subject.find_resource('gitlab-org')).to be_truthy } + it { expect(!!subject.find_resource('gitlab-com')).to be_falsey } + end +end diff --git a/spec/lib/constraints/namespace_url_constrainer_spec.rb b/spec/lib/constraints/namespace_url_constrainer_spec.rb index 8940fd6b94e..a5feaacb8ee 100644 --- a/spec/lib/constraints/namespace_url_constrainer_spec.rb +++ b/spec/lib/constraints/namespace_url_constrainer_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe NamespaceUrlConstrainer, lib: true do let!(:group) { create(:group, path: 'gitlab') } - subject { NamespaceUrlConstrainer.new } describe '#matches?' do context 'existing namespace' do diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb new file mode 100644 index 00000000000..4b26692672f --- /dev/null +++ b/spec/lib/constraints/user_url_constrainer_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +describe UserUrlConstrainer, lib: true do + let!(:username) { create(:user, username: 'dz') } + + describe '#find_resource' do + it { expect(!!subject.find_resource('dz')).to be_truthy } + it { expect(!!subject.find_resource('john')).to be_falsey } + end +end -- cgit v1.2.1 From b356c7ddad1ada1d78136386536f6676ca8b1df4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 6 Oct 2016 18:08:29 +0300 Subject: Update user routing spec after constrainer logic changed Signed-off-by: Dmitriy Zaporozhets --- spec/routing/routing_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 3608aa70f65..0dd00af878d 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -9,7 +9,7 @@ require 'spec_helper' # user_calendar_activities GET /u/:username/calendar_activities(.:format) describe UsersController, "routing" do it "to #show" do - allow(User).to receive(:find_by_username).and_return(true) + allow(User).to receive(:find_by).and_return(true) expect(get("/User")).to route_to('users#show', username: 'User') end -- cgit v1.2.1 From 90f49cc6969ec522457ddce8e864169f5c91e6aa Mon Sep 17 00:00:00 2001 From: Tjaart van der Walt Date: Thu, 6 Oct 2016 11:53:53 -0500 Subject: Update mail_room gem --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 30c8e801ea1..67fe6aa4e95 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -68,6 +68,7 @@ v 8.13.0 (unreleased) - Grouped pipeline dropdown is a scrollable container v 8.12.5 (unreleased) + - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread v 8.12.4 - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) diff --git a/Gemfile b/Gemfile index 3e8ce8b2fc5..0cc98ad9210 100644 --- a/Gemfile +++ b/Gemfile @@ -323,7 +323,7 @@ gem 'newrelic_rpm', '~> 3.16' gem 'octokit', '~> 4.3.0' -gem 'mail_room', '~> 0.8' +gem 'mail_room', '~> 0.8.1' gem 'email_reply_parser', '~> 0.5.8' diff --git a/Gemfile.lock b/Gemfile.lock index 96b49faf727..817c1725879 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -399,7 +399,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.8.0) + mail_room (0.8.1) method_source (0.8.2) mime-types (2.99.3) mimemagic (0.3.0) @@ -886,7 +886,7 @@ DEPENDENCIES license_finder (~> 2.1.0) licensee (~> 8.0.0) loofah (~> 2.0.3) - mail_room (~> 0.8) + mail_room (~> 0.8.1) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) -- cgit v1.2.1 From 8443cee049068d8d69633e57df5e15e6d999f073 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 18:10:18 +0100 Subject: Fixes external_url link Adds tests for: - external_url link in environmnets list - must show deployment internal id in environments list - must show build name and id in environments list --- app/views/projects/deployments/_actions.haml | 2 +- spec/features/environments_spec.rb | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 9fd97a7d753..8cb0b5825e0 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -1,7 +1,7 @@ - if can?(current_user, :create_deployment, deployment) && deployment.deployable .pull-right - - external_url = deployment.deployable.try(:external_url) + - external_url = deployment.environment.try(:external_url) - if external_url = link_to external_url, target: '_blank', class: 'btn external-url' do = icon('external-link') diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 4309a726917..d4ecccf5f12 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -44,6 +44,10 @@ feature 'Environments', feature: true do scenario 'does show deployment SHA' do expect(page).to have_link(deployment.short_sha) end + + scenario 'does show deployment internal id' do + expect(page).to have_content(deployment.iid) + end context 'with build and manual actions' do given(:pipeline) { create(:ci_pipeline, project: project) } @@ -61,6 +65,20 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end + + scenario 'does show build name and id' do + expect(page).to have_link("#{build.name} (##{build.id})") + end + + context 'with external_url' do + given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } + given(:build) { create(:ci_build, pipeline: pipeline) } + given(:deployment) { create(:deployment, environment: environment, deployable: build) } + + scenario 'does show an external link button' do + expect(page).to have_selector('.btn.external-url') + end + end end end end -- cgit v1.2.1 From f415a32eceabe07e125cc2c26d922b618aef89e9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 18:13:42 +0100 Subject: Adds test for external link in environment details --- spec/features/environments_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index d4ecccf5f12..a4d2460f595 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -140,6 +140,16 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end + + context 'with external_url' do + given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } + given(:build) { create(:ci_build, pipeline: pipeline) } + given(:deployment) { create(:deployment, environment: environment, deployable: build) } + + scenario 'does show an external link button' do + expect(page).to have_selector('.btn.external-url') + end + end end end end -- cgit v1.2.1 From 1845c33d711ca50bd7ef8d85b8aae5c0f016611c Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Thu, 6 Oct 2016 10:20:44 -0700 Subject: spacing tweaks --- app/assets/stylesheets/pages/profile.scss | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index efb90772214..c7eac5cf4b9 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -94,7 +94,7 @@ .profile-user-bio { // Limits the width of the user bio for readability. max-width: 600px; - margin: 10px auto 0; + margin: 10px auto; padding: 0 16px; } @@ -225,13 +225,6 @@ width: 90px; margin: 0 auto 10px; } - - .user-info { - - .member-date { - margin-bottom: 4px; - } - } } @media (max-width: $screen-xs-max) { @@ -258,10 +251,6 @@ } } -.user-profile-nav { - margin-top: 10px; -} - table.u2f-registrations { th:not(:last-child), td:not(:last-child) { border-right: solid 1px transparent; -- cgit v1.2.1 From ce368e7435539c371764cb66944f43fed9b49524 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 18:46:13 +0100 Subject: Removes unused variable --- app/views/projects/deployments/_commit.html.haml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml index dfd03bc466b..28813babd7b 100644 --- a/app/views/projects/deployments/_commit.html.haml +++ b/app/views/projects/deployments/_commit.html.haml @@ -1,5 +1,3 @@ -- merge_request = deployment.deployable.try(:merge_request) - %div.branch-commit - if deployment.ref .icon-container -- cgit v1.2.1 From e369455f73f34d74bb1ecbaacbb1fbf07292eda6 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 6 Oct 2016 18:49:26 +0100 Subject: Removes `try` --- app/views/projects/deployments/_actions.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 8cb0b5825e0..d7367890e99 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -1,7 +1,7 @@ - if can?(current_user, :create_deployment, deployment) && deployment.deployable .pull-right - - external_url = deployment.environment.try(:external_url) + - external_url = deployment.environment.external_url - if external_url = link_to external_url, target: '_blank', class: 'btn external-url' do = icon('external-link') -- cgit v1.2.1 From 5766e0661dd39198bdafeb53678830df4c3c5e3e Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 6 Oct 2016 19:14:57 +0100 Subject: Memoize ActiveRecord::Migrator.migrations in tests --- config/initializers/ar_speed_up_migration_checking.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 config/initializers/ar_speed_up_migration_checking.rb diff --git a/config/initializers/ar_speed_up_migration_checking.rb b/config/initializers/ar_speed_up_migration_checking.rb new file mode 100644 index 00000000000..1fe5defc01d --- /dev/null +++ b/config/initializers/ar_speed_up_migration_checking.rb @@ -0,0 +1,18 @@ +if Rails.env.test? + require 'active_record/migration' + + module ActiveRecord + class Migrator + class << self + alias_method :migrations_unmemoized, :migrations + + # This method is called a large number of times per rspec example, and + # it reads + parses `db/migrate/*` each time. Memoizing it can save 0.5 + # seconds per spec. + def migrations(paths) + @migrations ||= migrations_unmemoized(paths) + end + end + end + end +end -- cgit v1.2.1 From aada01030cd23719a54a4e499b72c12f95ce0d24 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 6 Oct 2016 12:09:55 -0700 Subject: Improve issue load time performance by avoiding ORDER BY in find_by call The Sortable concern has a default scope that adds ORDER BY to all queries. EXPLAIN ANALYZE shows that this additional ORDER BY statement causes the SQL optimizer to use the wrong index, which leads to a load time of 2.9 s vs 0.073 ms just for the SELECT call. The minimal change here is to re-implement find_by using where and reorder to remove the ORDER BY clause in IssuesController#index. Closes #23075 --- CHANGELOG | 1 + app/controllers/projects/issues_controller.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 0da60766e33..52ba6036083 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Update runner version only when updating contacted_at - Add link from system note to compare with previous version + - Improve issue load time performance by avoiding ORDER BY in find_by call - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Fix centering of custom header logos (Ashley Dumaine) - AbstractReferenceFilter caches project_refs on RequestStore when active diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index ef13e0677d2..96041b07647 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -159,7 +159,8 @@ class Projects::IssuesController < Projects::ApplicationController protected def issue - @noteable = @issue ||= @project.issues.find_by(iid: params[:id]) || redirect_old + # The Sortable default scope causes performance issues when used with find_by + @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old end alias_method :subscribable_resource, :issue alias_method :issuable, :issue -- cgit v1.2.1 From 7cf68093275cc3052e75ccd9473cf0ae5d384595 Mon Sep 17 00:00:00 2001 From: victorwu416 Date: Thu, 6 Oct 2016 15:35:37 -0400 Subject: Truncate long labels with ellipsis in labels page --- CHANGELOG | 1 + app/assets/stylesheets/pages/labels.scss | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0da60766e33..2293a19a476 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Truncate long labels with ellipsis in labels page - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 38c7cd98e41..822830706a5 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -59,6 +59,13 @@ width: 200px; margin-bottom: 0; } + + .label { + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + max-width: 100%; + } } .label-description { -- cgit v1.2.1 From c9a5aee8a6034e262f787bf25fcc4c5881bc9047 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 6 Oct 2016 22:08:41 +0200 Subject: Link to Registry docs from project settings --- app/views/projects/edit.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index a04d53e02bf..d19422c8657 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -100,7 +100,8 @@ = f.check_box :container_registry_enabled %strong Container Registry %br - %span.descr Enable Container Registry for this repository + %span.descr Enable Container Registry for this project + = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank' = render 'merge_request_settings', f: f %hr -- cgit v1.2.1 From 1d35c5b3aed565f1da0fc696f078642540584ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 21 Sep 2016 02:09:31 -0300 Subject: Improve project policy spec --- app/policies/project_policy.rb | 18 +++-- spec/policies/project_policy_spec.rb | 147 +++++++++++++++++++++++++++++++---- 2 files changed, 143 insertions(+), 22 deletions(-) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index be25c750d67..a806cf83782 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -98,7 +98,6 @@ class ProjectPolicy < BasePolicy can! :admin_milestone can! :admin_project_snippet can! :admin_project_member - can! :admin_merge_request can! :admin_note can! :admin_wiki can! :admin_project @@ -139,11 +138,18 @@ class ProjectPolicy < BasePolicy def team_access!(user) access = project.team.max_member_access(user.id) - guest_access! if access >= Gitlab::Access::GUEST - reporter_access! if access >= Gitlab::Access::REPORTER - team_member_reporter_access! if access >= Gitlab::Access::REPORTER - developer_access! if access >= Gitlab::Access::DEVELOPER - master_access! if access >= Gitlab::Access::MASTER + return if access < Gitlab::Access::GUEST + guest_access! + + return if access < Gitlab::Access::REPORTER + reporter_access! + team_member_reporter_access! + + return if access < Gitlab::Access::DEVELOPER + developer_access! + + return if access < Gitlab::Access::MASTER + master_access! end def archived_access! diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index a7a06744428..43c8d884a47 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1,20 +1,68 @@ require 'spec_helper' describe ProjectPolicy, models: true do - let(:project) { create(:empty_project, :public) } let(:guest) { create(:user) } let(:reporter) { create(:user) } let(:dev) { create(:user) } let(:master) { create(:user) } let(:owner) { create(:user) } - let(:admin) { create(:admin) } + let(:project) { create(:empty_project, :public, namespace: owner.namespace) } - let(:users_ordered_by_permissions) do - [nil, guest, reporter, dev, master, owner, admin] + let(:guest_permissions) do + [ + :read_project, :read_board, :read_list, :read_wiki, :read_issue, :read_label, + :read_milestone, :read_project_snippet, :read_project_member, + :read_merge_request, :read_note, :create_project, :create_issue, :create_note, + :upload_file + ] end - let(:users_permissions) do - users_ordered_by_permissions.map { |u| Ability.allowed(u, project).size } + let(:reporter_permissions) do + [ + :download_code, :fork_project, :create_project_snippet, :update_issue, + :admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build, + :read_container_image, :read_pipeline, :read_environment, :read_deployment + ] + end + + let(:team_member_reporter_permissions) do + [ + :build_download_code, :build_read_container_image + ] + end + + let(:developer_permissions) do + [ + :admin_merge_request, :update_merge_request, :create_commit_status, + :update_commit_status, :create_build, :update_build, :create_pipeline, + :update_pipeline, :create_merge_request, :create_wiki, :push_code, + :resolve_note, :create_container_image, :update_container_image, + :create_environment, :create_deployment + ] + end + + let(:master_permissions) do + [ + :push_code_to_protected_branches, :update_project_snippet, :update_environment, + :update_deployment, :admin_milestone, :admin_project_snippet, + :admin_project_member, :admin_note, :admin_wiki, :admin_project, + :admin_commit_status, :admin_build, :admin_container_image, + :admin_pipeline, :admin_environment, :admin_deployment + ] + end + + let(:public_permissions) do + [ + :download_code, :fork_project, :read_commit_status, :read_pipeline, + :read_container_image, :build_download_code, :build_read_container_image + ] + end + + let(:owner_permissions) do + [ + :change_namespace, :change_visibility_level, :rename_project, :remove_project, + :archive_project, :remove_fork_project, :destroy_merge_request, :destroy_issue + ] end before do @@ -22,16 +70,6 @@ describe ProjectPolicy, models: true do project.team << [master, :master] project.team << [dev, :developer] project.team << [reporter, :reporter] - - group = create(:group) - project.project_group_links.create( - group: group, - group_access: Gitlab::Access::MASTER) - group.add_owner(owner) - end - - it 'returns increasing permissions for each level' do - expect(users_permissions).to eq(users_permissions.sort.uniq) end it 'does not include the read_issue permission when the issue author is not a member of the private project' do @@ -46,4 +84,81 @@ describe ProjectPolicy, models: true do expect(Ability.allowed?(user, :read_issue, project)).to be_falsy end + + context 'abilities for non-public projects' do + let(:project) { create(:empty_project, namespace: owner.namespace) } + + subject { described_class.abilities(current_user, project).to_set } + + context 'with no user' do + let(:current_user) { nil } + + it { is_expected.to be_empty } + end + + context 'guests' do + let(:current_user) { guest } + + it do + is_expected.to include(*guest_permissions) + is_expected.not_to include(*reporter_permissions) + is_expected.not_to include(*team_member_reporter_permissions) + is_expected.not_to include(*developer_permissions) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'reporter' do + let(:current_user) { reporter } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.to include(*team_member_reporter_permissions) + is_expected.not_to include(*developer_permissions) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'developer' do + let(:current_user) { dev } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.to include(*team_member_reporter_permissions) + is_expected.to include(*developer_permissions) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'master' do + let(:current_user) { master } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.to include(*team_member_reporter_permissions) + is_expected.to include(*developer_permissions) + is_expected.to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'owner' do + let(:current_user) { owner } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.not_to include(*team_member_reporter_permissions) + is_expected.to include(*developer_permissions) + is_expected.to include(*master_permissions) + is_expected.to include(*owner_permissions) + end + end + end end -- cgit v1.2.1 From 35e231b5cce463c793a76cf1961e23bf63f82c7c Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 6 Oct 2016 09:46:07 -0500 Subject: Update issue board spec --- spec/features/boards/boards_spec.rb | 4 ++-- spec/services/boards/lists/generate_service_spec.rb | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 26ea06e002b..470e2bdbb9b 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -34,14 +34,14 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates default lists' do - lists = ['Backlog', 'Development', 'Testing', 'Production', 'Ready', 'Done'] + lists = ['Backlog', 'To Do', 'Doing', 'Done'] page.within(find('.board-blank-state')) do click_button('Add default lists') end wait_for_vue_resource - expect(page).to have_selector('.board', count: 6) + expect(page).to have_selector('.board', count: 4) page.all('.board').each_with_index do |list, i| expect(list.find('.board-title')).to have_content(lists[i]) diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 9fd39122737..4171e4d816c 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -10,7 +10,7 @@ describe Boards::Lists::GenerateService, services: true do context 'when board lists is empty' do it 'creates the default lists' do - expect { service.execute }.to change(board.lists, :count).by(4) + expect { service.execute }.to change(board.lists, :count).by(2) end end @@ -24,16 +24,15 @@ describe Boards::Lists::GenerateService, services: true do context 'when project labels does not contains any list label' do it 'creates labels' do - expect { service.execute }.to change(project.labels, :count).by(4) + expect { service.execute }.to change(project.labels, :count).by(2) end end context 'when project labels contains some of list label' do it 'creates the missing labels' do - create(:label, project: project, name: 'Development') - create(:label, project: project, name: 'Ready') + create(:label, project: project, name: 'Doing') - expect { service.execute }.to change(project.labels, :count).by(2) + expect { service.execute }.to change(project.labels, :count).by(1) end end end -- cgit v1.2.1 From e94cd6fdfe43d9128d37a539cf84f4388c5cf970 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 6 Oct 2016 22:17:11 +0100 Subject: Add markdown cache columns to the database, but don't use them yet This commit adds a number of _html columns and, with the exception of Note, starts updating them whenever the content of their partner fields changes. Note has a collision with the note_html attr_accessor; that will be fixed later A background worker for clearing these cache columns is also introduced - use `rake cache:clear` to set it off. You can clear the database or Redis caches separately by running `rake cache:clear:db` or `rake cache:clear:redis`, respectively. --- app/helpers/gitlab_markdown_helper.rb | 30 +++- app/models/abuse_report.rb | 7 + app/models/appearance.rb | 4 + app/models/application_setting.rb | 7 + app/models/broadcast_message.rb | 3 + app/models/concerns/cache_markdown_field.rb | 131 +++++++++++++++ app/models/concerns/issuable.rb | 4 + app/models/global_label.rb | 4 + app/models/global_milestone.rb | 5 + app/models/label.rb | 3 + app/models/milestone.rb | 4 + app/models/namespace.rb | 3 + app/models/project.rb | 3 + app/models/release.rb | 4 + app/models/snippet.rb | 10 ++ app/workers/clear_database_cache_worker.rb | 23 +++ config/initializers/ar5_batching.rb | 41 +++++ .../20160829114652_add_markdown_cache_columns.rb | 38 +++++ db/schema.rb | 36 +++- lib/banzai.rb | 4 + lib/banzai/renderer.rb | 28 ++++ lib/tasks/cache.rake | 43 +++-- spec/factories/projects.rb | 7 + spec/lib/banzai/renderer_spec.rb | 74 +++++++++ .../import_export/attribute_configuration_spec.rb | 3 +- spec/models/abuse_report_spec.rb | 4 + spec/models/concerns/cache_markdown_field_spec.rb | 181 +++++++++++++++++++++ spec/models/project_spec.rb | 2 +- spec/models/service_spec.rb | 2 +- spec/models/snippet_spec.rb | 7 + spec/requests/api/projects_spec.rb | 4 +- spec/services/git_push_service_spec.rb | 2 + spec/services/merge_requests/merge_service_spec.rb | 5 +- spec/services/system_note_service_spec.rb | 12 +- 34 files changed, 689 insertions(+), 49 deletions(-) create mode 100644 app/models/concerns/cache_markdown_field.rb create mode 100644 app/workers/clear_database_cache_worker.rb create mode 100644 config/initializers/ar5_batching.rb create mode 100644 db/migrate/20160829114652_add_markdown_cache_columns.rb create mode 100644 spec/lib/banzai/renderer_spec.rb create mode 100644 spec/models/concerns/cache_markdown_field_spec.rb diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 1a259656f31..d24680b8617 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -51,17 +51,15 @@ module GitlabMarkdownHelper context[:project] ||= @project html = Banzai.render(text, context) + banzai_postprocess(html, context) + end - context.merge!( - current_user: (current_user if defined?(current_user)), + def markdown_field(object, field) + object = object.for_display if object.respond_to?(:for_display) + return "" unless object.present? - # RelativeLinkFilter - requested_path: @path, - project_wiki: @project_wiki, - ref: @ref - ) - - Banzai.post_process(html, context) + html = Banzai.render_field(object, field) + banzai_postprocess(html, object.banzai_render_context(field)) end def asciidoc(text) @@ -196,4 +194,18 @@ module GitlabMarkdownHelper icon(options[:icon]) end end + + # Calls Banzai.post_process with some common context options + def banzai_postprocess(html, context) + context.merge!( + current_user: (current_user if defined?(current_user)), + + # RelativeLinkFilter + requested_path: @path, + project_wiki: @project_wiki, + ref: @ref + ) + + Banzai.post_process(html, context) + end end diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index b01a244032d..2340453831e 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -1,4 +1,8 @@ class AbuseReport < ActiveRecord::Base + include CacheMarkdownField + + cache_markdown_field :message, pipeline: :single_line + belongs_to :reporter, class_name: 'User' belongs_to :user @@ -7,6 +11,9 @@ class AbuseReport < ActiveRecord::Base validates :message, presence: true validates :user_id, uniqueness: { message: 'has already been reported' } + # For CacheMarkdownField + alias_method :author, :reporter + def remove_user(deleted_by:) user.block DeleteUserWorker.perform_async(deleted_by.id, user.id, delete_solo_owned_groups: true) diff --git a/app/models/appearance.rb b/app/models/appearance.rb index 4cf8dd9a8ce..e4106e1c2e9 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -1,4 +1,8 @@ class Appearance < ActiveRecord::Base + include CacheMarkdownField + + cache_markdown_field :description + validates :title, presence: true validates :description, presence: true validates :logo, file_size: { maximum: 1.megabyte } diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 55d2e07de08..c99aa7772bb 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1,5 +1,7 @@ class ApplicationSetting < ActiveRecord::Base + include CacheMarkdownField include TokenAuthenticatable + add_authentication_token_field :runners_registration_token add_authentication_token_field :health_check_access_token @@ -17,6 +19,11 @@ class ApplicationSetting < ActiveRecord::Base serialize :domain_whitelist, Array serialize :domain_blacklist, Array + cache_markdown_field :sign_in_text + cache_markdown_field :help_page_text + cache_markdown_field :shared_runners_text, pipeline: :plain_markdown + cache_markdown_field :after_sign_up_text + attr_accessor :domain_whitelist_raw, :domain_blacklist_raw validates :session_expire_delay, diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 61498140f27..cb40f33932a 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -1,6 +1,9 @@ class BroadcastMessage < ActiveRecord::Base + include CacheMarkdownField include Sortable + cache_markdown_field :message, pipeline: :broadcast_message + validates :message, presence: true validates :starts_at, presence: true validates :ends_at, presence: true diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb new file mode 100644 index 00000000000..90bd6490a02 --- /dev/null +++ b/app/models/concerns/cache_markdown_field.rb @@ -0,0 +1,131 @@ +# This module takes care of updating cache columns for Markdown-containing +# fields. Use like this in the body of your class: +# +# include CacheMarkdownField +# cache_markdown_field :foo +# cache_markdown_field :bar +# cache_markdown_field :baz, pipeline: :single_line +# +# Corresponding foo_html, bar_html and baz_html fields should exist. +module CacheMarkdownField + # Knows about the relationship between markdown and html field names, and + # stores the rendering contexts for the latter + class FieldData + extend Forwardable + + def initialize + @data = {} + end + + def_delegators :@data, :[], :[]= + def_delegator :@data, :keys, :markdown_fields + + def html_field(markdown_field) + "#{markdown_field}_html" + end + + def html_fields + markdown_fields.map {|field| html_field(field) } + end + end + + # Dynamic registries don't really work in Rails as it's not guaranteed that + # every class will be loaded, so hardcode the list. + CACHING_CLASSES = %w[ + AbuseReport + Appearance + ApplicationSetting + BroadcastMessage + Issue + Label + MergeRequest + Milestone + Namespace + Note + Project + Release + Snippet + ] + + def self.caching_classes + CACHING_CLASSES.map(&:constantize) + end + + extend ActiveSupport::Concern + + included do + cattr_reader :cached_markdown_fields do + FieldData.new + end + + # Returns the default Banzai render context for the cached markdown field. + def banzai_render_context(field) + raise ArgumentError.new("Unknown field: #{field.inspect}") unless + cached_markdown_fields.markdown_fields.include?(field) + + # Always include a project key, or Banzai complains + project = self.project if self.respond_to?(:project) + context = cached_markdown_fields[field].merge(project: project) + + # Banzai is less strict about authors, so don't always have an author key + context[:author] = self.author if self.respond_to?(:author) + + context + end + + # Allow callers to look up the cache field name, rather than hardcoding it + def markdown_cache_field_for(field) + raise ArgumentError.new("Unknown field: #{field}") unless + cached_markdown_fields.markdown_fields.include?(field) + + cached_markdown_fields.html_field(field) + end + + # Always exclude _html fields from attributes (including serialization). + # They contain unredacted HTML, which would be a security issue + alias_method :attributes_before_markdown_cache, :attributes + def attributes + attrs = attributes_before_markdown_cache + + cached_markdown_fields.html_fields.each do |field| + attrs.delete(field) + end + + attrs + end + end + + class_methods do + private + + # Specify that a field is markdown. Its rendered output will be cached in + # a corresponding _html field. Any custom rendering options may be provided + # as a context. + def cache_markdown_field(markdown_field, context = {}) + raise "Add #{self} to CacheMarkdownField::CACHING_CLASSES" unless + CacheMarkdownField::CACHING_CLASSES.include?(self.to_s) + + cached_markdown_fields[markdown_field] = context + + html_field = cached_markdown_fields.html_field(markdown_field) + cache_method = "#{markdown_field}_cache_refresh".to_sym + invalidation_method = "#{html_field}_invalidated?".to_sym + + define_method(cache_method) do + html = Banzai::Renderer.cacheless_render_field(self, markdown_field) + __send__("#{html_field}=", html) + true + end + + # The HTML becomes invalid if any dependent fields change. For now, assume + # author and project invalidate the cache in all circumstances. + define_method(invalidation_method) do + changed_fields = changed_attributes.keys + invalidations = changed_fields & [markdown_field.to_s, "author", "project"] + !invalidations.empty? + end + + before_save cache_method, if: invalidation_method + end + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index ff465d2c745..c4b42ad82c7 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -6,6 +6,7 @@ # module Issuable extend ActiveSupport::Concern + include CacheMarkdownField include Participable include Mentionable include Subscribable @@ -13,6 +14,9 @@ module Issuable include Awardable included do + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :description + belongs_to :author, class_name: "User" belongs_to :assignee, class_name: "User" belongs_to :updated_by, class_name: "User" diff --git a/app/models/global_label.rb b/app/models/global_label.rb index ddd4bad5c21..698a7bbd327 100644 --- a/app/models/global_label.rb +++ b/app/models/global_label.rb @@ -4,6 +4,10 @@ class GlobalLabel delegate :color, :description, to: :@first_label + def for_display + @first_label + end + def self.build_collection(labels) labels = labels.group_by(&:title) diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index bda2b5c5d5d..cde4a568577 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -4,6 +4,10 @@ class GlobalMilestone attr_accessor :title, :milestones alias_attribute :name, :title + def for_display + @first_milestone + end + def self.build_collection(milestones) milestones = milestones.group_by(&:title) @@ -17,6 +21,7 @@ class GlobalMilestone @title = title @name = title @milestones = milestones + @first_milestone = milestones.find {|m| m.description.present? } || milestones.first end def safe_title diff --git a/app/models/label.rb b/app/models/label.rb index a23140b7d64..e8e12e2904e 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -1,4 +1,5 @@ class Label < ActiveRecord::Base + include CacheMarkdownField include Referable include Subscribable @@ -8,6 +9,8 @@ class Label < ActiveRecord::Base None = LabelStruct.new('No Label', 'No Label') Any = LabelStruct.new('Any Label', '') + cache_markdown_field :description, pipeline: :single_line + DEFAULT_COLOR = '#428BCA' default_value_for :color, DEFAULT_COLOR diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 44c3cbb2c73..23aecbfa3a6 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -6,12 +6,16 @@ class Milestone < ActiveRecord::Base Any = MilestoneStruct.new('Any Milestone', '', -1) Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2) + include CacheMarkdownField include InternalId include Sortable include Referable include StripAttribute include Milestoneish + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :description + belongs_to :project has_many :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 919b3b1f095..b7f2b2bbe61 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -1,9 +1,12 @@ class Namespace < ActiveRecord::Base acts_as_paranoid + include CacheMarkdownField include Sortable include Gitlab::ShellAdapter + cache_markdown_field :description, pipeline: :description + has_many :projects, dependent: :destroy belongs_to :owner, class_name: "User" diff --git a/app/models/project.rb b/app/models/project.rb index ecd742a17d5..88e4bd14860 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -6,6 +6,7 @@ class Project < ActiveRecord::Base include Gitlab::VisibilityLevel include Gitlab::CurrentSettings include AccessRequestable + include CacheMarkdownField include Referable include Sortable include AfterCommitQueue @@ -17,6 +18,8 @@ class Project < ActiveRecord::Base UNKNOWN_IMPORT_URL = 'http://unknown.git' + cache_markdown_field :description, pipeline: :description + delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, to: :project_feature, allow_nil: true default_value_for :archived, false diff --git a/app/models/release.rb b/app/models/release.rb index e196b84eb18..c936899799e 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -1,4 +1,8 @@ class Release < ActiveRecord::Base + include CacheMarkdownField + + cache_markdown_field :description + belongs_to :project validates :description, :project, :tag, presence: true diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 8a1730f3f36..2373b445009 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -1,11 +1,21 @@ class Snippet < ActiveRecord::Base include Gitlab::VisibilityLevel include Linguist::BlobHelper + include CacheMarkdownField include Participable include Referable include Sortable include Awardable + cache_markdown_field :title, pipeline: :single_line + cache_markdown_field :content + + # If file_name changes, it invalidates content + alias_method :default_content_html_invalidator, :content_html_invalidated? + def content_html_invalidated? + default_content_html_invalidator || file_name_changed? + end + default_value_for :visibility_level, Snippet::PRIVATE belongs_to :author, class_name: 'User' diff --git a/app/workers/clear_database_cache_worker.rb b/app/workers/clear_database_cache_worker.rb new file mode 100644 index 00000000000..c541daba50e --- /dev/null +++ b/app/workers/clear_database_cache_worker.rb @@ -0,0 +1,23 @@ +# This worker clears all cache fields in the database, working in batches. +class ClearDatabaseCacheWorker + include Sidekiq::Worker + + BATCH_SIZE = 1000 + + def perform + CacheMarkdownField.caching_classes.each do |kls| + fields = kls.cached_markdown_fields.html_fields + clear_cache_fields = fields.each_with_object({}) do |field, memo| + memo[field] = nil + end + + Rails.logger.debug("Clearing Markdown cache for #{kls}: #{fields.inspect}") + + kls.unscoped.in_batches(of: BATCH_SIZE) do |relation| + relation.update_all(clear_cache_fields) + end + end + + nil + end +end diff --git a/config/initializers/ar5_batching.rb b/config/initializers/ar5_batching.rb new file mode 100644 index 00000000000..35e8b3808e2 --- /dev/null +++ b/config/initializers/ar5_batching.rb @@ -0,0 +1,41 @@ +# Port ActiveRecord::Relation#in_batches from ActiveRecord 5. +# https://github.com/rails/rails/blob/ac027338e4a165273607dccee49a3d38bc836794/activerecord/lib/active_record/relation/batches.rb#L184 +# TODO: this can be removed once we're using AR5. +raise "Vendored ActiveRecord 5 code! Delete #{__FILE__}!" if ActiveRecord::VERSION::MAJOR >= 5 + +module ActiveRecord + module Batches + # Differences from upstream: enumerator support was removed, and custom + # order/limit clauses are ignored without a warning. + def in_batches(of: 1000, start: nil, finish: nil, load: false) + raise "Must provide a block" unless block_given? + + relation = self.reorder(batch_order).limit(of) + relation = relation.where(arel_table[primary_key].gteq(start)) if start + relation = relation.where(arel_table[primary_key].lteq(finish)) if finish + batch_relation = relation + + loop do + if load + records = batch_relation.records + ids = records.map(&:id) + yielded_relation = self.where(primary_key => ids) + yielded_relation.load_records(records) + else + ids = batch_relation.pluck(primary_key) + yielded_relation = self.where(primary_key => ids) + end + + break if ids.empty? + + primary_key_offset = ids.last + raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset + + yield yielded_relation + + break if ids.length < of + batch_relation = relation.where(arel_table[primary_key].gt(primary_key_offset)) + end + end + end +end diff --git a/db/migrate/20160829114652_add_markdown_cache_columns.rb b/db/migrate/20160829114652_add_markdown_cache_columns.rb new file mode 100644 index 00000000000..8753e55e058 --- /dev/null +++ b/db/migrate/20160829114652_add_markdown_cache_columns.rb @@ -0,0 +1,38 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddMarkdownCacheColumns < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + COLUMNS = { + abuse_reports: [:message], + appearances: [:description], + application_settings: [ + :sign_in_text, + :help_page_text, + :shared_runners_text, + :after_sign_up_text + ], + broadcast_messages: [:message], + issues: [:title, :description], + labels: [:description], + merge_requests: [:title, :description], + milestones: [:title, :description], + namespaces: [:description], + notes: [:note], + projects: [:description], + releases: [:description], + snippets: [:title, :content], + } + + def change + COLUMNS.each do |table, columns| + columns.each do |column| + add_column table, "#{column}_html", :text + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index ad62c249b3f..56da70b3c02 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -23,6 +23,7 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.text "message" t.datetime "created_at" t.datetime "updated_at" + t.text "message_html" end create_table "appearances", force: :cascade do |t| @@ -30,8 +31,9 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.text "description" t.string "header_logo" t.string "logo" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "description_html" end create_table "application_settings", force: :cascade do |t| @@ -92,6 +94,10 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.text "domain_blacklist" t.boolean "koding_enabled" t.string "koding_url" + t.text "sign_in_text_html" + t.text "help_page_text_html" + t.text "shared_runners_text_html" + t.text "after_sign_up_text_html" end create_table "audit_events", force: :cascade do |t| @@ -128,13 +134,14 @@ ActiveRecord::Schema.define(version: 20160926145521) do add_index "boards", ["project_id"], name: "index_boards_on_project_id", using: :btree create_table "broadcast_messages", force: :cascade do |t| - t.text "message", null: false + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" t.datetime "created_at" t.datetime "updated_at" t.string "color" t.string "font" + t.text "message_html" end create_table "ci_application_settings", force: :cascade do |t| @@ -457,18 +464,20 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 + t.integer "position", default: 0 t.string "branch_name" t.text "description" t.integer "milestone_id" t.string "state" t.integer "iid" t.integer "updated_by_id" - t.boolean "confidential", default: false + t.boolean "confidential", default: false t.datetime "deleted_at" t.date "due_date" t.integer "moved_to_id" t.integer "lock_version" + t.text "title_html" + t.text "description_html" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -514,9 +523,10 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "template", default: false + t.boolean "template", default: false t.string "description" t.integer "priority" + t.text "description_html" end add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree @@ -632,6 +642,8 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.datetime "deleted_at" t.string "in_progress_merge_commit_sha" t.integer "lock_version" + t.text "title_html" + t.text "description_html" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -658,14 +670,16 @@ ActiveRecord::Schema.define(version: 20160926145521) do add_index "merge_requests_closing_issues", ["merge_request_id"], name: "index_merge_requests_closing_issues_on_merge_request_id", using: :btree create_table "milestones", force: :cascade do |t| - t.string "title", null: false - t.integer "project_id", null: false + t.string "title", null: false + t.integer "project_id", null: false t.text "description" t.date "due_date" t.datetime "created_at" t.datetime "updated_at" t.string "state" t.integer "iid" + t.text "title_html" + t.text "description_html" end add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} @@ -689,6 +703,7 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.boolean "request_access_enabled", default: true, null: false t.datetime "deleted_at" t.boolean "lfs_enabled" + t.text "description_html" end add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree @@ -721,6 +736,7 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.integer "resolved_by_id" t.string "discussion_id" t.string "original_discussion_id" + t.text "note_html" end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -872,6 +888,7 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.boolean "request_access_enabled", default: true, null: false t.boolean "has_external_wiki" t.boolean "lfs_enabled" + t.text "description_html" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree @@ -922,6 +939,7 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" + t.text "description_html" end add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree @@ -976,6 +994,8 @@ ActiveRecord::Schema.define(version: 20160926145521) do t.string "file_name" t.string "type" t.integer "visibility_level", default: 0, null: false + t.text "title_html" + t.text "content_html" end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree diff --git a/lib/banzai.rb b/lib/banzai.rb index 9ebe379f454..35ca234c1ba 100644 --- a/lib/banzai.rb +++ b/lib/banzai.rb @@ -3,6 +3,10 @@ module Banzai Renderer.render(text, context) end + def self.render_field(object, field) + Renderer.render_field(object, field) + end + def self.cache_collection_render(texts_and_contexts) Renderer.cache_collection_render(texts_and_contexts) end diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index a4ae27eefd8..6924a293da8 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -31,6 +31,34 @@ module Banzai end end + # Convert a Markdown-containing field on an object into an HTML-safe String + # of HTML. This method is analogous to calling render(object.field), but it + # can cache the rendered HTML in the object, rather than Redis. + # + # The context to use is learned from the passed-in object by calling + # #banzai_render_context(field), and cannot be changed. Use #render, passing + # it the field text, if a custom rendering is needed. The generated context + # is returned along with the HTML. + def render_field(object, field) + html_field = object.markdown_cache_field_for(field) + + html = object.__send__(html_field) + return html if html.present? + + html = cacheless_render_field(object, field) + object.update_column(html_field, html) unless object.new_record? || object.destroyed? + + html + end + + # Same as +render_field+, but without consulting or updating the cache field + def cacheless_render_field(object, field) + text = object.__send__(field) + context = object.banzai_render_context(field) + + cacheless_render(text, context) + end + # Perform multiple render from an Array of Markdown String into an # Array of HTML-safe String of HTML. # diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake index 2214f855200..a95a3455a4a 100644 --- a/lib/tasks/cache.rake +++ b/lib/tasks/cache.rake @@ -1,22 +1,33 @@ namespace :cache do - CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000 - REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan + namespace :clear do + REDIS_CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000 + REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan - desc "GitLab | Clear redis cache" - task :clear => :environment do - Gitlab::Redis.with do |redis| - cursor = REDIS_SCAN_START_STOP - loop do - cursor, keys = redis.scan( - cursor, - match: "#{Gitlab::Redis::CACHE_NAMESPACE}*", - count: CLEAR_BATCH_SIZE - ) - - redis.del(*keys) if keys.any? - - break if cursor == REDIS_SCAN_START_STOP + desc "GitLab | Clear redis cache" + task redis: :environment do + Gitlab::Redis.with do |redis| + cursor = REDIS_SCAN_START_STOP + loop do + cursor, keys = redis.scan( + cursor, + match: "#{Gitlab::Redis::CACHE_NAMESPACE}*", + count: REDIS_CLEAR_BATCH_SIZE + ) + + redis.del(*keys) if keys.any? + + break if cursor == REDIS_SCAN_START_STOP + end end end + + desc "GitLab | Clear database cache (in the background)" + task db: :environment do + ClearDatabaseCacheWorker.perform_async + end + + task all: [:db, :redis] end + + task clear: 'cache:clear:all' end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 873d3fcb5af..331172445e4 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -9,6 +9,9 @@ FactoryGirl.define do namespace creator + # Behaves differently to nil due to cache_has_external_issue_tracker + has_external_issue_tracker false + trait :public do visibility_level Gitlab::VisibilityLevel::PUBLIC end @@ -92,6 +95,8 @@ FactoryGirl.define do end factory :redmine_project, parent: :project do + has_external_issue_tracker true + after :create do |project| project.create_redmine_service( active: true, @@ -105,6 +110,8 @@ FactoryGirl.define do end factory :jira_project, parent: :project do + has_external_issue_tracker true + after :create do |project| project.create_jira_service( active: true, diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb new file mode 100644 index 00000000000..aaa6b12e67e --- /dev/null +++ b/spec/lib/banzai/renderer_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe Banzai::Renderer do + def expect_render(project = :project) + expected_context = { project: project } + expect(renderer).to receive(:cacheless_render) { :html }.with(:markdown, expected_context) + end + + def expect_cache_update + expect(object).to receive(:update_column).with("field_html", :html) + end + + def fake_object(*features) + markdown = :markdown if features.include?(:markdown) + html = :html if features.include?(:html) + + object = double( + "object", + banzai_render_context: { project: :project }, + field: markdown, + field_html: html + ) + + allow(object).to receive(:markdown_cache_field_for).with(:field).and_return("field_html") + allow(object).to receive(:new_record?).and_return(features.include?(:new)) + allow(object).to receive(:destroyed?).and_return(features.include?(:destroyed)) + + object + end + + describe "#render_field" do + let(:renderer) { Banzai::Renderer } + let(:subject) { renderer.render_field(object, :field) } + + context "with an empty cache" do + let(:object) { fake_object(:markdown) } + it "caches and returns the result" do + expect_render + expect_cache_update + expect(subject).to eq(:html) + end + end + + context "with a filled cache" do + let(:object) { fake_object(:markdown, :html) } + + it "uses the cache" do + expect_render.never + expect_cache_update.never + should eq(:html) + end + end + + context "new object" do + let(:object) { fake_object(:new, :markdown) } + + it "doesn't cache the result" do + expect_render + expect_cache_update.never + expect(subject).to eq(:html) + end + end + + context "destroyed object" do + let(:object) { fake_object(:destroyed, :markdown) } + + it "doesn't cache the result" do + expect_render + expect_cache_update.never + expect(subject).to eq(:html) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb index 2e19d590d83..ea65a5dfed1 100644 --- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb +++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb @@ -26,10 +26,11 @@ describe 'Import/Export attribute configuration', lib: true do it 'has no new columns' do relation_names.each do |relation_name| relation_class = relation_class_for_name(relation_name) + relation_attributes = relation_class.new.attributes.keys expect(safe_model_attributes[relation_class.to_s]).not_to be_nil, "Expected exported class #{relation_class} to exist in safe_model_attributes" - current_attributes = parsed_attributes(relation_name, relation_class.attribute_names) + current_attributes = parsed_attributes(relation_name, relation_attributes) safe_attributes = safe_model_attributes[relation_class.to_s] new_attributes = current_attributes - safe_attributes diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index 305f8bc88cc..c4486a32082 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -9,6 +9,10 @@ RSpec.describe AbuseReport, type: :model do describe 'associations' do it { is_expected.to belong_to(:reporter).class_name('User') } it { is_expected.to belong_to(:user) } + + it "aliases reporter to author" do + expect(subject.author).to be(subject.reporter) + end end describe 'validations' do diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb new file mode 100644 index 00000000000..15cd3a7ed70 --- /dev/null +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -0,0 +1,181 @@ +require 'spec_helper' + +describe CacheMarkdownField do + CacheMarkdownField::CACHING_CLASSES << "ThingWithMarkdownFields" + + # The minimum necessary ActiveModel to test this concern + class ThingWithMarkdownFields + include ActiveModel::Model + include ActiveModel::Dirty + + include ActiveModel::Serialization + + class_attribute :attribute_names + self.attribute_names = [] + + def attributes + attribute_names.each_with_object({}) do |name, hsh| + hsh[name.to_s] = send(name) + end + end + + extend ActiveModel::Callbacks + define_model_callbacks :save + + include CacheMarkdownField + cache_markdown_field :foo + cache_markdown_field :baz, pipeline: :single_line + + def self.add_attr(attr_name) + self.attribute_names += [attr_name] + define_attribute_methods(attr_name) + attr_reader(attr_name) + define_method("#{attr_name}=") do |val| + send("#{attr_name}_will_change!") unless val == send(attr_name) + instance_variable_set("@#{attr_name}", val) + end + end + + [:foo, :foo_html, :bar, :baz, :baz_html].each do |attr_name| + add_attr(attr_name) + end + + def initialize(*) + super + + # Pretend new is load + clear_changes_information + end + + def save + run_callbacks :save do + changes_applied + end + end + end + + CacheMarkdownField::CACHING_CLASSES.delete("ThingWithMarkdownFields") + + def thing_subclass(new_attr) + Class.new(ThingWithMarkdownFields) { add_attr(new_attr) } + end + + let(:markdown) { "`Foo`" } + let(:html) { "

    Foo

    " } + + let(:updated_markdown) { "`Bar`" } + let(:updated_html) { "

    Bar

    " } + + subject { ThingWithMarkdownFields.new(foo: markdown, foo_html: html) } + + describe ".attributes" do + it "excludes cache attributes" do + expect(thing_subclass(:qux).new.attributes.keys.sort).to eq(%w[bar baz foo qux]) + end + end + + describe ".cache_markdown_field" do + it "refuses to allow untracked classes" do + expect { thing_subclass(:qux).__send__(:cache_markdown_field, :qux) }.to raise_error(RuntimeError) + end + end + + context "an unchanged markdown field" do + before do + subject.foo = subject.foo + subject.save + end + + it { expect(subject.foo).to eq(markdown) } + it { expect(subject.foo_html).to eq(html) } + it { expect(subject.foo_html_changed?).not_to be_truthy } + end + + context "a changed markdown field" do + before do + subject.foo = updated_markdown + subject.save + end + + it { expect(subject.foo_html).to eq(updated_html) } + end + + context "a non-markdown field changed" do + before do + subject.bar = "OK" + subject.save + end + + it { expect(subject.bar).to eq("OK") } + it { expect(subject.foo).to eq(markdown) } + it { expect(subject.foo_html).to eq(html) } + end + + describe '#banzai_render_context' do + it "sets project to nil if the object lacks a project" do + context = subject.banzai_render_context(:foo) + expect(context).to have_key(:project) + expect(context[:project]).to be_nil + end + + it "excludes author if the object lacks an author" do + context = subject.banzai_render_context(:foo) + expect(context).not_to have_key(:author) + end + + it "raises if the context for an unrecognised field is requested" do + expect{subject.banzai_render_context(:not_found)}.to raise_error(ArgumentError) + end + + it "includes the pipeline" do + context = subject.banzai_render_context(:baz) + expect(context[:pipeline]).to eq(:single_line) + end + + it "returns copies of the context template" do + template = subject.cached_markdown_fields[:baz] + copy = subject.banzai_render_context(:baz) + expect(copy).not_to be(template) + end + + context "with a project" do + subject { thing_subclass(:project).new(foo: markdown, foo_html: html, project: :project) } + + it "sets the project in the context" do + context = subject.banzai_render_context(:foo) + expect(context).to have_key(:project) + expect(context[:project]).to eq(:project) + end + + it "invalidates the cache when project changes" do + subject.project = :new_project + allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + + subject.save + + expect(subject.foo_html).to eq(updated_html) + expect(subject.baz_html).to eq(updated_html) + end + end + + context "with an author" do + subject { thing_subclass(:author).new(foo: markdown, foo_html: html, author: :author) } + + it "sets the author in the context" do + context = subject.banzai_render_context(:foo) + expect(context).to have_key(:author) + expect(context[:author]).to eq(:author) + end + + it "invalidates the cache when author changes" do + subject.author = :new_author + allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + + subject.save + + expect(subject.foo_html).to eq(updated_html) + expect(subject.baz_html).to eq(updated_html) + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e52d4aaf884..381d14ed21a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -518,7 +518,7 @@ describe Project, models: true do end describe '#cache_has_external_issue_tracker' do - let(:project) { create(:project) } + let(:project) { create(:project, has_external_issue_tracker: nil) } it 'stores true if there is any external_issue_tracker' do services = double(:service, external_issue_trackers: [RedmineService.new]) diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index ed1bc9271ae..43937a54b2c 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -238,7 +238,7 @@ describe Service, models: true do it "updates the has_external_issue_tracker boolean" do expect do service.save! - end.to change { service.project.has_external_issue_tracker }.from(nil).to(true) + end.to change { service.project.has_external_issue_tracker }.from(false).to(true) end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index e6bc5296398..f62f6bacbaa 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -46,6 +46,13 @@ describe Snippet, models: true do end end + describe "#content_html_invalidated?" do + let(:snippet) { create(:snippet, content: "md", content_html: "html", file_name: "foo.md") } + it "invalidates the HTML cache of content when the filename changes" do + expect { snippet.file_name = "foo.rb" }.to change { snippet.content_html_invalidated? }.from(false).to(true) + end + end + describe '.search' do let(:snippet) { create(:snippet) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 4a0d727faea..861eb12b94c 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -232,7 +232,7 @@ describe API::API, api: true do post api('/projects', user), project project.each_pair do |k, v| - next if %i{ issues_enabled merge_requests_enabled wiki_enabled }.include?(k) + next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k) expect(json_response[k.to_s]).to eq(v) end @@ -360,7 +360,7 @@ describe API::API, api: true do post api("/projects/user/#{user.id}", admin), project project.each_pair do |k, v| - next if k == :path + next if %i[has_external_issue_tracker path].include?(k) expect(json_response[k.to_s]).to eq(v) end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 22991c5bc86..8e3e12114f2 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -448,6 +448,8 @@ describe GitPushService, services: true do let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } before do + # project.create_jira_service doesn't seem to invalidate the cache here + project.has_external_issue_tracker = true jira_service_settings WebMock.stub_request(:post, jira_api_transition_url) diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index e49a0d5e553..ee53e110aee 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -60,7 +60,10 @@ describe MergeRequests::MergeService, services: true do let(:jira_tracker) { project.create_jira_service } - before { jira_service_settings } + before do + project.update_attributes!(has_external_issue_tracker: true) + jira_service_settings + end it 'closes issues on JIRA issue tracker' do jira_issue = ExternalIssue.new('JIRA-123', project) diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index d1a47ea9b6f..304d4e62396 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -531,12 +531,12 @@ describe SystemNoteService, services: true do include JiraServiceHelper describe 'JIRA integration' do - let(:project) { create(:project) } + let(:project) { create(:jira_project) } let(:author) { create(:user) } let(:issue) { create(:issue, project: project) } let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) } let(:jira_issue) { ExternalIssue.new("JIRA-1", project)} - let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } + let(:jira_tracker) { project.jira_service } let(:commit) { project.commit } context 'in JIRA issue tracker' do @@ -545,10 +545,6 @@ describe SystemNoteService, services: true do WebMock.stub_request(:post, jira_api_comment_url) end - after do - jira_tracker.destroy! - end - describe "new reference" do before do WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments) @@ -578,10 +574,6 @@ describe SystemNoteService, services: true do WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments) end - after do - jira_tracker.destroy! - end - subject { described_class.cross_reference(jira_issue, issue, author) } it { is_expected.to eq(jira_status_message) } -- cgit v1.2.1 From 109816c42fbe44fca108b52308a5fa4366876216 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 6 Oct 2016 22:52:44 +0100 Subject: Use CacheMarkdownField for notes --- app/models/note.rb | 5 ++- app/views/projects/notes/_note.html.haml | 2 +- lib/banzai/note_renderer.rb | 5 ++- lib/banzai/object_renderer.rb | 53 +++++++++++++-------------- spec/lib/banzai/note_renderer_spec.rb | 3 +- spec/lib/banzai/object_renderer_spec.rb | 62 ++++++++++++-------------------- 6 files changed, 55 insertions(+), 75 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index f2656df028b..2d644b03e4d 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -6,10 +6,13 @@ class Note < ActiveRecord::Base include Awardable include Importable include FasterCacheKeys + include CacheMarkdownField + + cache_markdown_field :note, pipeline: :note # Attribute containing rendered and redacted Markdown as generated by # Banzai::ObjectRenderer. - attr_accessor :note_html + attr_accessor :redacted_note_html # An Array containing the number of visible references as generated by # Banzai::ObjectRenderer diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 788be4a0047..73fe6a715fa 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -61,7 +61,7 @@ .note-body{class: note_editable ? 'js-task-list-container' : ''} .note-text.md = preserve do - = note.note_html + = note.redacted_note_html = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) - if note_editable = render 'projects/notes/edit_form', note: note diff --git a/lib/banzai/note_renderer.rb b/lib/banzai/note_renderer.rb index bab6a9934d1..2b7c10f1a0e 100644 --- a/lib/banzai/note_renderer.rb +++ b/lib/banzai/note_renderer.rb @@ -3,7 +3,7 @@ module Banzai # Renders a collection of Note instances. # # notes - The notes to render. - # project - The project to use for rendering/redacting. + # project - The project to use for redacting. # user - The user viewing the notes. # path - The request path. # wiki - The project's wiki. @@ -13,8 +13,7 @@ module Banzai user, requested_path: path, project_wiki: wiki, - ref: git_ref, - pipeline: :note) + ref: git_ref) renderer.render(notes, :note) end diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb index 9aef807c152..9f8eb0931b8 100644 --- a/lib/banzai/object_renderer.rb +++ b/lib/banzai/object_renderer.rb @@ -1,28 +1,32 @@ module Banzai - # Class for rendering multiple objects (e.g. Note instances) in a single pass. + # Class for rendering multiple objects (e.g. Note instances) in a single pass, + # using +render_field+ to benefit from caching in the database. Rendering and + # redaction are both performed. # - # Rendered Markdown is stored in an attribute in every object based on the - # name of the attribute containing the Markdown. For example, when the - # attribute `note` is rendered the HTML is stored in `note_html`. + # The unredacted HTML is generated according to the usual +render_field+ + # policy, so specify the pipeline and any other context options on the model. + # + # The *redacted* (i.e., suitable for use) HTML is placed in an attribute + # named "redacted_", where is the name of the cache field for the + # chosen attribute. + # + # As an example, rendering the attribute `note` would place the unredacted + # HTML into `note_html` and the redacted HTML into `redacted_note_html`. class ObjectRenderer attr_reader :project, :user - # Make sure to set the appropriate pipeline in the `raw_context` attribute - # (e.g. `:note` for Note instances). - # - # project - A Project to use for rendering and redacting Markdown. + # project - A Project to use for redacting Markdown. # user - The user viewing the Markdown/HTML documents, if any. - # context - A Hash containing extra attributes to use in the rendering - # pipeline. - def initialize(project, user = nil, raw_context = {}) + # context - A Hash containing extra attributes to use during redaction + def initialize(project, user = nil, redaction_context = {}) @project = project @user = user - @raw_context = raw_context + @redaction_context = redaction_context end # Renders and redacts an Array of objects. # - # objects - The objects to render + # objects - The objects to render. # attribute - The attribute containing the raw Markdown to render. # # Returns the same input objects. @@ -32,7 +36,7 @@ module Banzai objects.each_with_index do |object, index| redacted_data = redacted[index] - object.__send__("#{attribute}_html=", redacted_data[:document].to_html.html_safe) + object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html.html_safe) object.user_visible_reference_count = redacted_data[:visible_reference_count] end end @@ -53,12 +57,8 @@ module Banzai # Returns a Banzai context for the given object and attribute. def context_for(object, attribute) - context = base_context.merge(cache_key: [object, attribute]) - - if object.respond_to?(:author) - context[:author] = object.author - end - + context = base_context.dup + context = context.merge(object.banzai_render_context(attribute)) context end @@ -66,21 +66,16 @@ module Banzai # # Returns an Array of `Nokogiri::HTML::Document`. def render_attributes(objects, attribute) - strings_and_contexts = objects.map do |object| + objects.map do |object| + string = Banzai.render_field(object, attribute) context = context_for(object, attribute) - string = object.__send__(attribute) - - { text: string, context: context } - end - - Banzai.cache_collection_render(strings_and_contexts).each_with_index.map do |html, index| - Banzai::Pipeline[:relative_link].to_document(html, strings_and_contexts[index][:context]) + Banzai::Pipeline[:relative_link].to_document(string, context) end end def base_context - @base_context ||= @raw_context.merge(current_user: user, project: project) + @base_context ||= @redaction_context.merge(current_user: user, project: project) end end end diff --git a/spec/lib/banzai/note_renderer_spec.rb b/spec/lib/banzai/note_renderer_spec.rb index 98f76f36fd5..49556074278 100644 --- a/spec/lib/banzai/note_renderer_spec.rb +++ b/spec/lib/banzai/note_renderer_spec.rb @@ -12,8 +12,7 @@ describe Banzai::NoteRenderer do with(project, user, requested_path: 'foo', project_wiki: wiki, - ref: 'bar', - pipeline: :note). + ref: 'bar'). and_call_original expect_any_instance_of(Banzai::ObjectRenderer). diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index bcdb95250ca..f5ff236105e 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -4,10 +4,18 @@ describe Banzai::ObjectRenderer do let(:project) { create(:empty_project) } let(:user) { project.owner } + def fake_object(attrs = {}) + object = double(attrs.merge("new_record?": true, "destroyed?": true)) + allow(object).to receive(:markdown_cache_field_for).with(:note).and_return(:note_html) + allow(object).to receive(:banzai_render_context).with(:note).and_return(project: nil, author: nil) + allow(object).to receive(:update_column).with(:note_html, anything).and_return(true) + object + end + describe '#render' do it 'renders and redacts an Array of objects' do renderer = described_class.new(project, user) - object = double(:object, note: 'hello', note_html: nil) + object = fake_object(note: 'hello', note_html: nil) expect(renderer).to receive(:render_objects).with([object], :note). and_call_original @@ -16,7 +24,7 @@ describe Banzai::ObjectRenderer do with(an_instance_of(Array)). and_call_original - expect(object).to receive(:note_html=).with('

    hello

    ') + expect(object).to receive(:redacted_note_html=).with('

    hello

    ') expect(object).to receive(:user_visible_reference_count=).with(0) renderer.render([object], :note) @@ -25,7 +33,7 @@ describe Banzai::ObjectRenderer do describe '#render_objects' do it 'renders an Array of objects' do - object = double(:object, note: 'hello') + object = fake_object(note: 'hello', note_html: nil) renderer = described_class.new(project, user) @@ -57,49 +65,29 @@ describe Banzai::ObjectRenderer do end describe '#context_for' do - let(:object) { double(:object, note: 'hello') } + let(:object) { fake_object(note: 'hello') } let(:renderer) { described_class.new(project, user) } it 'returns a Hash' do expect(renderer.context_for(object, :note)).to be_an_instance_of(Hash) end - it 'includes the cache key' do + it 'includes the banzai render context for the object' do + expect(object).to receive(:banzai_render_context).with(:note).and_return(foo: :bar) context = renderer.context_for(object, :note) - - expect(context[:cache_key]).to eq([object, :note]) - end - - context 'when the object responds to "author"' do - it 'includes the author in the context' do - expect(object).to receive(:author).and_return('Alice') - - context = renderer.context_for(object, :note) - - expect(context[:author]).to eq('Alice') - end - end - - context 'when the object does not respond to "author"' do - it 'does not include the author in the context' do - context = renderer.context_for(object, :note) - - expect(context.key?(:author)).to eq(false) - end + expect(context).to have_key(:foo) + expect(context[:foo]).to eq(:bar) end end describe '#render_attributes' do it 'renders the attribute of a list of objects' do - objects = [double(:doc, note: 'hello'), double(:doc, note: 'bye')] - renderer = described_class.new(project, user, pipeline: :note) + objects = [fake_object(note: 'hello', note_html: nil), fake_object(note: 'bye', note_html: nil)] + renderer = described_class.new(project, user) - expect(Banzai).to receive(:cache_collection_render). - with([ - { text: 'hello', context: renderer.context_for(objects[0], :note) }, - { text: 'bye', context: renderer.context_for(objects[1], :note) } - ]). - and_call_original + objects.each do |object| + expect(Banzai).to receive(:render_field).with(object, :note).and_call_original + end docs = renderer.render_attributes(objects, :note) @@ -114,17 +102,13 @@ describe Banzai::ObjectRenderer do objects = [] renderer = described_class.new(project, user, pipeline: :note) - expect(Banzai).to receive(:cache_collection_render). - with([]). - and_call_original - expect(renderer.render_attributes(objects, :note)).to eq([]) end end describe '#base_context' do let(:context) do - described_class.new(project, user, pipeline: :note).base_context + described_class.new(project, user, foo: :bar).base_context end it 'returns a Hash' do @@ -132,7 +116,7 @@ describe Banzai::ObjectRenderer do end it 'includes the custom attributes' do - expect(context[:pipeline]).to eq(:note) + expect(context[:foo]).to eq(:bar) end it 'includes the current user' do -- cgit v1.2.1 From dd159a750b294ee89cb8a4143284ff9788b639fc Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 6 Oct 2016 22:55:27 +0100 Subject: Make search results use the markdown cache columns, treating them consistently Truncato is introduced as a dependency to intelligently shorten the rendered HTML to 200 characters; the previous approach could have resulted in invalid HTML being rendered. --- Gemfile | 1 + Gemfile.lock | 4 ++++ app/helpers/search_helper.rb | 14 ++++++++++++-- app/views/search/results/_issue.html.haml | 2 +- app/views/search/results/_merge_request.html.haml | 2 +- app/views/search/results/_milestone.html.haml | 2 +- app/views/search/results/_note.html.haml | 2 +- 7 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 3e8ce8b2fc5..426b824901e 100644 --- a/Gemfile +++ b/Gemfile @@ -110,6 +110,7 @@ gem 'creole', '~> 0.5.0' gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 1.5.2' gem 'rouge', '~> 2.0' +gem 'truncato', '~> 0.7.8' # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM diff --git a/Gemfile.lock b/Gemfile.lock index 96b49faf727..b98c3acf948 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -745,6 +745,9 @@ GEM tilt (2.0.5) timecop (0.8.1) timfel-krb5-auth (0.8.3) + truncato (0.7.8) + htmlentities (~> 4.3.1) + nokogiri (~> 1.6.1) turbolinks (2.5.3) coffee-rails tzinfo (1.2.2) @@ -971,6 +974,7 @@ DEPENDENCIES test_after_commit (~> 0.4.2) thin (~> 1.7.0) timecop (~> 0.8.0) + truncato (~> 0.7.8) turbolinks (~> 2.5.0) u2f (~> 0.2.1) uglifier (~> 2.7.2) diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 8a7446b7cc7..aba3a3f9c5d 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -153,8 +153,18 @@ module SearchHelper search_path(options) end - # Sanitize html generated after parsing markdown from issue description or comment - def search_md_sanitize(html) + # Sanitize a HTML field for search display. Most tags are stripped out and the + # maximum length is set to 200 characters. + def search_md_sanitize(object, field) + html = markdown_field(object, field) + html = Truncato.truncate( + html, + count_tags: false, + count_tail: false, + max_length: 200 + ) + + # Truncato's filtered_tags and filtered_attributes are not quite the same sanitize(html, tags: %w(a p ol ul li pre code)) end end diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index 8f68d6d1b87..e010f21de5a 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -7,7 +7,7 @@ - if issue.description.present? .description.term = preserve do - = search_md_sanitize(markdown(truncate(issue.description, length: 200, separator: " "), { project: issue.project, author: issue.author })) + = search_md_sanitize(issue, :description) %span.light #{issue.project.name_with_namespace} - if issue.closed? diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 6331c2bd6b0..07b17bc69c0 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -6,7 +6,7 @@ - if merge_request.description.present? .description.term = preserve do - = search_md_sanitize(markdown(merge_request.description, { project: merge_request.project, author: merge_request.author })) + = search_md_sanitize(merge_request, :description) %span.light #{merge_request.project.name_with_namespace} .pull-right diff --git a/app/views/search/results/_milestone.html.haml b/app/views/search/results/_milestone.html.haml index b31595d8d1c..9664f65a36e 100644 --- a/app/views/search/results/_milestone.html.haml +++ b/app/views/search/results/_milestone.html.haml @@ -6,4 +6,4 @@ - if milestone.description.present? .description.term = preserve do - = search_md_sanitize(markdown(milestone.description)) + = search_md_sanitize(milestone, :description) diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml index e0400083870..f3701b89bb4 100644 --- a/app/views/search/results/_note.html.haml +++ b/app/views/search/results/_note.html.haml @@ -23,4 +23,4 @@ .note-search-result .term = preserve do - = search_md_sanitize(markdown(note.note, {no_header_anchors: true, author: note.author})) + = search_md_sanitize(note, :note) -- cgit v1.2.1 From 9920551536bb4f78dffeaaf3a194b92f54c34a47 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 6 Oct 2016 23:01:42 +0100 Subject: Enable CacheMarkdownField for the remaining models This commit alters views for the following models to use the markdown cache if present: * AbuseReport * Appearance * ApplicationSetting * BroadcastMessage * Group * Issue * Label * MergeRequest * Milestone * Project At the same time, calls to `escape_once` have been moved into the `single_line` Banzai pipeline, so they can't be missed out by accident and the work is done at save, rather than render, time. --- app/controllers/admin/broadcast_messages_controller.rb | 2 +- app/helpers/appearances_helper.rb | 2 +- app/helpers/application_settings_helper.rb | 12 ------------ app/helpers/broadcast_messages_helper.rb | 6 +++--- app/helpers/gitlab_markdown_helper.rb | 14 ++++++-------- app/views/admin/abuse_reports/_abuse_report.html.haml | 2 +- app/views/admin/broadcast_messages/_form.html.haml | 5 ++++- app/views/admin/broadcast_messages/preview.js.haml | 2 +- app/views/admin/groups/_group.html.haml | 2 +- app/views/admin/labels/_label.html.haml | 2 +- app/views/admin/projects/index.html.haml | 2 +- app/views/devise/confirmations/almost_there.haml | 4 ++-- app/views/groups/show.html.haml | 2 +- app/views/help/index.html.haml | 2 +- app/views/layouts/devise.html.haml | 4 ++-- app/views/projects/_home_panel.html.haml | 2 +- app/views/projects/commit/_commit_box.html.haml | 4 ++-- app/views/projects/commits/_commit.html.haml | 2 +- app/views/projects/issues/show.html.haml | 4 ++-- app/views/projects/merge_requests/show/_mr_box.html.haml | 4 ++-- app/views/projects/milestones/show.html.haml | 4 ++-- app/views/projects/pipelines/_info.html.haml | 4 ++-- app/views/projects/repositories/_feed.html.haml | 2 +- app/views/projects/runners/_shared_runners.html.haml | 4 ++-- app/views/projects/tags/_tag.html.haml | 2 +- app/views/projects/tags/show.html.haml | 2 +- app/views/shared/_label_row.html.haml | 2 +- app/views/shared/groups/_group.html.haml | 2 +- app/views/shared/milestones/_labels_tab.html.haml | 2 +- app/views/shared/milestones/_top.html.haml | 3 +-- app/views/shared/projects/_project.html.haml | 2 +- app/views/shared/snippets/_blob.html.haml | 7 +++++-- app/views/shared/snippets/_header.html.haml | 2 +- lib/banzai/filter/html_entity_filter.rb | 12 ++++++++++++ lib/banzai/pipeline/single_line_pipeline.rb | 1 + spec/helpers/broadcast_messages_helper_spec.rb | 4 ++-- spec/lib/banzai/filter/html_entity_filter_spec.rb | 14 ++++++++++++++ 37 files changed, 83 insertions(+), 65 deletions(-) create mode 100644 lib/banzai/filter/html_entity_filter.rb create mode 100644 spec/lib/banzai/filter/html_entity_filter_spec.rb diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb index 82055006ac0..762e36ee2e9 100644 --- a/app/controllers/admin/broadcast_messages_controller.rb +++ b/app/controllers/admin/broadcast_messages_controller.rb @@ -37,7 +37,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController end def preview - @message = broadcast_message_params[:message] + @broadcast_message = BroadcastMessage.new(broadcast_message_params) end protected diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index de13e7a1fc2..16136d02530 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -16,7 +16,7 @@ module AppearancesHelper end def brand_text - markdown(brand_item.description) + markdown_field(brand_item, :description) end def brand_item diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 6de25bea654..6229384817b 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -11,18 +11,6 @@ module ApplicationSettingsHelper current_application_settings.signin_enabled? end - def extra_sign_in_text - current_application_settings.sign_in_text - end - - def after_sign_up_text - current_application_settings.after_sign_up_text - end - - def shared_runners_text - current_application_settings.shared_runners_text - end - def user_oauth_applications? current_application_settings.user_oauth_applications end diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb index 43a29c96bca..eb03ced67eb 100644 --- a/app/helpers/broadcast_messages_helper.rb +++ b/app/helpers/broadcast_messages_helper.rb @@ -3,7 +3,7 @@ module BroadcastMessagesHelper return unless message.present? content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do - icon('bullhorn') << ' ' << render_broadcast_message(message.message) + icon('bullhorn') << ' ' << render_broadcast_message(message) end end @@ -32,7 +32,7 @@ module BroadcastMessagesHelper end end - def render_broadcast_message(message) - Banzai.render(message, pipeline: :broadcast_message).html_safe + def render_broadcast_message(broadcast_message) + Banzai.render_field(broadcast_message, :message).html_safe end end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index d24680b8617..0772d848289 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -13,14 +13,12 @@ module GitlabMarkdownHelper def link_to_gfm(body, url, html_options = {}) return "" if body.blank? - escaped_body = if body.start_with?(' + class HTMLEntityFilter < HTML::Pipeline::TextFilter + def call + ERB::Util.html_escape(text) + end + end + end +end diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb index ba2555df98d..30bc035d085 100644 --- a/lib/banzai/pipeline/single_line_pipeline.rb +++ b/lib/banzai/pipeline/single_line_pipeline.rb @@ -3,6 +3,7 @@ module Banzai class SingleLinePipeline < GfmPipeline def self.filters @filters ||= FilterArray[ + Filter::HTMLEntityFilter, Filter::SanitizationFilter, Filter::EmojiFilter, diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb index 157cc4665a2..c6e3c5c2368 100644 --- a/spec/helpers/broadcast_messages_helper_spec.rb +++ b/spec/helpers/broadcast_messages_helper_spec.rb @@ -7,7 +7,7 @@ describe BroadcastMessagesHelper do end it 'includes the current message' do - current = double(message: 'Current Message') + current = BroadcastMessage.new(message: 'Current Message') allow(helper).to receive(:broadcast_message_style).and_return(nil) @@ -15,7 +15,7 @@ describe BroadcastMessagesHelper do end it 'includes custom style' do - current = double(message: 'Current Message') + current = BroadcastMessage.new(message: 'Current Message') allow(helper).to receive(:broadcast_message_style).and_return('foo') diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb new file mode 100644 index 00000000000..6dc4a970071 --- /dev/null +++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Banzai::Filter::HTMLEntityFilter, lib: true do + include FilterSpecHelper + + let(:unescaped) { 'foo &&&' } + let(:escaped) { 'foo <strike attr="foo">&&&</strike>' } + + it 'converts common entities to their HTML-escaped equivalents' do + output = filter(unescaped) + + expect(output).to eq(escaped) + end +end -- cgit v1.2.1 From 110e15da260f96baffb48bebb8cba796374fbb1e Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 6 Oct 2016 23:06:50 +0100 Subject: Add a CHANGELOG for CacheMarkdownField --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c20def1c092..0c57e2fe3a4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.13.0 (unreleased) - Keep refs for each deployment - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) + - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Simplify Mentionable concern instance methods - Fix permission for setting an issue's due date -- cgit v1.2.1 From 1c462cf7d6d61aebac9b909102f0e794cc9e409a Mon Sep 17 00:00:00 2001 From: Justin DiPierro Date: Thu, 6 Oct 2016 22:40:04 -0400 Subject: Call ensure_secret_token! in secret token test's before block since it would be called in an initializer. --- spec/lib/gitlab/backend/shell_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb index c9c7ef8f479..f826d0d1b04 100644 --- a/spec/lib/gitlab/backend/shell_spec.rb +++ b/spec/lib/gitlab/backend/shell_spec.rb @@ -30,6 +30,7 @@ describe Gitlab::Shell, lib: true do allow(Gitlab.config.gitlab_shell).to receive(:secret_file).and_return(secret_file) allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test') FileUtils.mkdir('tmp/tests/shell-secret-test') + Gitlab::Shell.ensure_secret_token! end after do -- cgit v1.2.1 From 57e72cbac99d58fe4dbb23439d6f9f2a333feccc Mon Sep 17 00:00:00 2001 From: dev-chris Date: Fri, 7 Oct 2016 04:19:03 +0000 Subject: Update user whitelist reject message + Don't expose all whitelisted domains --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 508efd85050..892ac28d5b3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -902,7 +902,7 @@ class User < ActiveRecord::Base if domain_matches?(allowed_domains, self.email) valid = true else - error = "is not whitelisted. Email domains valid for registration are: #{allowed_domains.join(', ')}" + error = "domain is not authorized for sign-up" valid = false end end -- cgit v1.2.1 From 1662640985b56390a4d22dab1fee7fd04ccd5bc8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 6 Oct 2016 15:50:29 -0700 Subject: Fix Event#reset_project_activity updates !6678 removed the lease from Event#reset_project_activity, but it wasn't actually updating the project's last_activity_at timestamp properly. The WHERE clause would always return no matching projects. The spec passed occasionally because the created_at timestamp was automatically set to last_activity_at. --- app/models/event.rb | 2 +- spec/models/project_spec.rb | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index 633019fe0af..314d5ba438f 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -335,7 +335,7 @@ class Event < ActiveRecord::Base # update the project. Only one query should actually perform the update, # hence we add the extra WHERE clause for last_activity_at. Project.unscoped.where(id: project_id). - where('last_activity_at > ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago). + where('last_activity_at <= ?', RESET_PROJECT_ACTIVITY_INTERVAL.ago). update_all(last_activity_at: created_at) end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e52d4aaf884..67a968aaf11 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -308,7 +308,9 @@ describe Project, models: true do end describe 'last_activity methods' do - let(:project) { create(:project, last_activity_at: 2.hours.ago) } + let(:timestamp) { 2.hours.ago } + # last_activity_at gets set to created_at upon creation + let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) } describe 'last_activity' do it 'alias last_activity to last_event' do @@ -322,6 +324,7 @@ describe Project, models: true do it 'returns the creation date of the project\'s last event if present' do new_event = create(:event, project: project, created_at: Time.now) + project.reload expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i) end -- cgit v1.2.1 From a98e4081d1b122d001438d17036f395fc82b9d5c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 09:32:29 +0200 Subject: Process MWBS in successful pipeline asynchronously --- app/models/ci/pipeline.rb | 2 +- app/workers/pipeline_success_worker.rb | 12 ++++++++++++ spec/workers/pipeline_success_worker_spec.rb | 24 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 app/workers/pipeline_success_worker.rb create mode 100644 spec/workers/pipeline_success_worker_spec.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 040ba20eb09..d3a6e0a11f0 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -71,7 +71,7 @@ module Ci end after_transition [:created, :pending, :running] => :success do |pipeline| - MergeRequests::MergeWhenBuildSucceedsService.new(pipeline.project, nil).trigger(pipeline) + PipelineSuccessWorker.perform_async(pipeline.id) end after_transition do |pipeline, transition| diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb new file mode 100644 index 00000000000..5dd443fea59 --- /dev/null +++ b/app/workers/pipeline_success_worker.rb @@ -0,0 +1,12 @@ +class PipelineSuccessWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| + MergeRequests::MergeWhenBuildSucceedsService + .new(pipeline.project, nil) + .trigger(pipeline) + end + end +end diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb new file mode 100644 index 00000000000..5e31cc2c8e7 --- /dev/null +++ b/spec/workers/pipeline_success_worker_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe PipelineSuccessWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline, status: 'success') } + + it 'performs "merge when pipeline succeeds"' do + expect_any_instance_of( + MergeRequests::MergeWhenBuildSucceedsService + ).to receive(:trigger) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 2f66969e43d25192017ebbe4ff56df213a2dae3f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 09:49:43 +0200 Subject: Improve merge when builds succeeds specs readability --- .../merge_when_build_succeeds_spec.rb | 44 ++++++++++++++-------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 60bc07bd1a0..8b6544269a7 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -2,18 +2,26 @@ require 'spec_helper' feature 'Merge When Build Succeeds', feature: true, js: true do let(:user) { create(:user) } + let(:project) { create(:project, :public) } - let(:project) { create(:project, :public) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, + author: user, + title: 'Bug NS-04') + end - before do - project.team << [user, :master] - project.enable_ci + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch) end - context "Active build for Merge Request" do - let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) } - let!(:ci_build) { create(:ci_build, pipeline: pipeline) } + before { project.team << [user, :master] } + + context 'when there is active build for merge request' do + background do + create(:ci_build, pipeline: pipeline) + end before do login_as user @@ -41,21 +49,25 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end end - context 'When it is enabled' do + context 'when merge when build succeeds is enabled' do let(:merge_request) do - create(:merge_request_with_diffs, :simple, source_project: project, author: user, - merge_user: user, title: "MepMep", merge_when_build_succeeds: true) + create(:merge_request_with_diffs, :simple, source_project: project, + author: user, + merge_user: user, + title: 'MepMep', + merge_when_build_succeeds: true) end - let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) } - let!(:ci_build) { create(:ci_build, pipeline: pipeline) } + background do + create(:ci_build, pipeline: pipeline) + end before do login_as user visit_merge_request(merge_request) end - it 'cancels the automatic merge' do + it 'allows to cancel the automatic merge' do click_link "Cancel Automatic Merge" expect(page).to have_button "Merge When Build Succeeds" @@ -72,8 +84,8 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end end - context 'Build is not active' do - it "does not allow for enabling" do + context 'when build is not active' do + it "does not allow to enable merge when build succeeds" do visit_merge_request(merge_request) expect(page).not_to have_link "Merge When Build Succeeds" end -- cgit v1.2.1 From e351ed2d11b611bf06eba8d64d2549fbf60fb605 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 10:10:46 +0200 Subject: Add test that checks actual merge for MWBS feature --- .../merge_requests/merge_when_build_succeeds_spec.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 8b6544269a7..bc2b0ff3e2c 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -58,7 +58,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do merge_when_build_succeeds: true) end - background do + let!(:build) do create(:ci_build, pipeline: pipeline) end @@ -72,7 +72,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do expect(page).to have_button "Merge When Build Succeeds" - visit_merge_request(merge_request) # Needed to refresh the page + visit_merge_request(merge_request) # refresh the page expect(page).to have_content "Canceled the automatic merge" end @@ -82,6 +82,17 @@ feature 'Merge When Build Succeeds', feature: true, js: true do click_link "Remove Source Branch When Merged" expect(page).to have_content "The source branch will be removed" end + + context 'when build succeeds' do + background { build.success } + + it 'merges merge request' do + visit_merge_request(merge_request) # refresh the page + + expect(page).to have_content 'The changes were merged' + expect(merge_request.reload).to be_merged + end + end end context 'when build is not active' do -- cgit v1.2.1 From 7623ab0cf4f966ee809845341842d0af5ce69061 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 7 Oct 2016 09:16:15 +0100 Subject: Make projects API docs match parameter style --- doc/api/projects.md | 336 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 196 insertions(+), 140 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 2a1054a5337..27436a052da 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -28,12 +28,14 @@ GET /projects Parameters: -- `archived` (optional) - if passed, limit by archived status -- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private` -- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` -- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` -- `search` (optional) - Return list of authorized projects according to a search criteria -- `simple` (optional) - When set, return only the ID, URL, name, and path of each project +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `archived` | boolean | no | Limit by archived status | +| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` | +| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | +| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | +| `search` | string | no | Return list of authorized projects matching the search criteria | +| `simple` | boolean | no | Return only the ID, URL, name, and path of each project | ```json [ @@ -162,12 +164,14 @@ GET /projects/visible Parameters: -- `archived` (optional) - if passed, limit by archived status -- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private` -- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` -- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` -- `search` (optional) - Return list of authorized projects according to a search criteria -- `simple` (optional) - When set, return only the ID, URL, name, and path of each project +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `archived` | boolean | no | Limit by archived status | +| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` | +| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | +| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | +| `search` | string | no | Return list of authorized projects matching the search criteria | +| `simple` | boolean | no | Return only the ID, URL, name, and path of each project | ```json [ @@ -294,11 +298,13 @@ GET /projects/owned Parameters: -- `archived` (optional) - if passed, limit by archived status -- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private` -- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` -- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` -- `search` (optional) - Return list of authorized projects according to a search criteria +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `archived` | boolean | no | Limit by archived status | +| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` | +| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | +| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | +| `search` | string | no | Return list of authorized projects matching the search criteria | ### List starred projects @@ -310,11 +316,13 @@ GET /projects/starred Parameters: -- `archived` (optional) - if passed, limit by archived status -- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private` -- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` -- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` -- `search` (optional) - Return list of authorized projects according to a search criteria +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `archived` | boolean | no | Limit by archived status | +| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` | +| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | +| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | +| `search` | string | no | Return list of authorized projects matching the search criteria | ### List ALL projects @@ -326,11 +334,13 @@ GET /projects/all Parameters: -- `archived` (optional) - if passed, limit by archived status -- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private` -- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` -- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` -- `search` (optional) - Return list of authorized projects according to a search criteria +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `archived` | boolean | no | Limit by archived status | +| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` | +| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | +| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | +| `search` | string | no | Return list of authorized projects matching the search criteria | ### Get single project @@ -343,7 +353,9 @@ GET /projects/:id Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project | ```json { @@ -432,7 +444,9 @@ GET /projects/:id/events Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project | ```json [ @@ -570,24 +584,26 @@ POST /projects Parameters: -- `name` (required) - new project name -- `path` (optional) - custom repository name for new project. By default generated based on name -- `namespace_id` (optional) - namespace for the new project (defaults to user) -- `description` (optional) - short project description -- `issues_enabled` (optional) -- `merge_requests_enabled` (optional) -- `builds_enabled` (optional) -- `wiki_enabled` (optional) -- `snippets_enabled` (optional) -- `container_registry_enabled` (optional) -- `shared_runners_enabled` (optional) -- `public` (optional) - if `true` same as setting visibility_level = 20 -- `visibility_level` (optional) -- `import_url` (optional) -- `public_builds` (optional) -- `only_allow_merge_if_build_succeeds` (optional) -- `lfs_enabled` (optional) -- `request_access_enabled` (optional) - Allow users to request member access. +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | yes | The name of the new project | +| `path` | string | no | Custom repository name for new project. By default generated based on name | +| `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) | +| `description` | string | no | Short project description | +| `issues_enabled` | boolean | no | Enable issues for this project | +| `merge_requests_enabled` | boolean | no | Enable merge requests for this project | +| `builds_enabled` | boolean | no | Enable builds for this project | +| `wiki_enabled` | boolean | no | Enable wiki for this project | +| `snippets_enabled` | boolean | no | Enable snippets for this project | +| `container_registry_enabled` | boolean | no | Enable container registry for this project | +| `shared_runners_enabled` | boolean | no | Enable shared runners for this project | +| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 | +| `visibility_level` | integer | no | See [project visibility level][#project-visibility-level] | +| `import_url` | string | no | URL to import repository from | +| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | +| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | +| `lfs_enabled` | boolean | no | Enable LFS | +| `request_access_enabled` | boolean | no | Allow users to request member access | ### Create project for user @@ -599,23 +615,27 @@ POST /projects/user/:user_id Parameters: -- `user_id` (required) - user_id of owner -- `name` (required) - new project name -- `description` (optional) - short project description -- `issues_enabled` (optional) -- `merge_requests_enabled` (optional) -- `builds_enabled` (optional) -- `wiki_enabled` (optional) -- `snippets_enabled` (optional) -- `container_registry_enabled` (optional) -- `shared_runners_enabled` (optional) -- `public` (optional) - if `true` same as setting visibility_level = 20 -- `visibility_level` (optional) -- `import_url` (optional) -- `public_builds` (optional) -- `only_allow_merge_if_build_succeeds` (optional) -- `lfs_enabled` (optional) -- `request_access_enabled` (optional) - Allow users to request member access. +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `user_id` | integer | yes | The user ID of the project owner | +| `name` | string | yes | The name of the new project | +| `path` | string | no | Custom repository name for new project. By default generated based on name | +| `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) | +| `description` | string | no | Short project description | +| `issues_enabled` | boolean | no | Enable issues for this project | +| `merge_requests_enabled` | boolean | no | Enable merge requests for this project | +| `builds_enabled` | boolean | no | Enable builds for this project | +| `wiki_enabled` | boolean | no | Enable wiki for this project | +| `snippets_enabled` | boolean | no | Enable snippets for this project | +| `container_registry_enabled` | boolean | no | Enable container registry for this project | +| `shared_runners_enabled` | boolean | no | Enable shared runners for this project | +| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 | +| `visibility_level` | integer | no | See [project visibility level][#project-visibility-level] | +| `import_url` | string | no | URL to import repository from | +| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | +| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | +| `lfs_enabled` | boolean | no | Enable LFS | +| `request_access_enabled` | boolean | no | Allow users to request member access | ### Edit project @@ -627,24 +647,26 @@ PUT /projects/:id Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project -- `name` (optional) - project name -- `path` (optional) - repository name for project -- `description` (optional) - short project description -- `default_branch` (optional) -- `issues_enabled` (optional) -- `merge_requests_enabled` (optional) -- `builds_enabled` (optional) -- `wiki_enabled` (optional) -- `snippets_enabled` (optional) -- `container_registry_enabled` (optional) -- `shared_runners_enabled` (optional) -- `public` (optional) - if `true` same as setting visibility_level = 20 -- `visibility_level` (optional) -- `public_builds` (optional) -- `only_allow_merge_if_build_succeeds` (optional) -- `lfs_enabled` (optional) -- `request_access_enabled` (optional) - Allow users to request member access. +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project | +| `name` | string | yes | The name of the project | +| `path` | string | no | Custom repository name for the project. By default generated based on name | +| `description` | string | no | Short project description | +| `issues_enabled` | boolean | no | Enable issues for this project | +| `merge_requests_enabled` | boolean | no | Enable merge requests for this project | +| `builds_enabled` | boolean | no | Enable builds for this project | +| `wiki_enabled` | boolean | no | Enable wiki for this project | +| `snippets_enabled` | boolean | no | Enable snippets for this project | +| `container_registry_enabled` | boolean | no | Enable container registry for this project | +| `shared_runners_enabled` | boolean | no | Enable shared runners for this project | +| `public` | boolean | no | If `true`, the same as setting `visibility_level` to 20 | +| `visibility_level` | integer | no | See [project visibility level][#project-visibility-level] | +| `import_url` | string | no | URL to import repository from | +| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members | +| `only_allow_merge_if_build_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds | +| `lfs_enabled` | boolean | no | Enable LFS | +| `request_access_enabled` | boolean | no | Allow users to request member access | On success, method returns 200 with the updated project. If parameters are invalid, 400 is returned. @@ -659,8 +681,10 @@ POST /projects/fork/:id Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked -- `namespace` (optional) - The ID or path of the namespace that the project will be forked to +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project | +| `namespace` | integer/string | yes | The ID or path of the namespace that the project will be forked to | ### Star a project @@ -671,9 +695,11 @@ Stars a given project. Returns status code `201` and the project on success and POST /projects/:id/star ``` +Parameters: + | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" @@ -741,7 +767,7 @@ DELETE /projects/:id/star | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star" @@ -813,7 +839,7 @@ POST /projects/:id/archive | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive" @@ -901,7 +927,7 @@ POST /projects/:id/unarchive | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive" @@ -984,7 +1010,9 @@ DELETE /projects/:id Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ## Uploads @@ -998,8 +1026,10 @@ POST /projects/:id/uploads Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked -- `file` (required) - The file to be uploaded +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `file` | string | yes | The file to be uploaded | ```json { @@ -1027,10 +1057,12 @@ POST /projects/:id/share Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked -- `group_id` (required) - The ID of a group -- `group_access` (required) - Level of permissions for sharing -- `expires_at` - Share expiration date in ISO 8601 format: 2016-09-26 +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `group_id` | integer | yes | The ID of the group to share with | +| `group_access` | integer | yes | The permissions level to grant the group | +| `expires_at` | string | no | Share expiration date in ISO 8601 format: 2016-09-26 | ## Hooks @@ -1047,7 +1079,9 @@ GET /projects/:id/hooks Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ### Get project hook @@ -1059,8 +1093,10 @@ GET /projects/:id/hooks/:hook_id Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project -- `hook_id` (required) - The ID of a project hook +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `hook_id` | integer | yes | The ID of a project hook | ```json { @@ -1090,17 +1126,19 @@ POST /projects/:id/hooks Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project -- `url` (required) - The hook URL -- `push_events` - Trigger hook on push events -- `issues_events` - Trigger hook on issues events -- `merge_requests_events` - Trigger hook on merge_requests events -- `tag_push_events` - Trigger hook on push_tag events -- `note_events` - Trigger hook on note events -- `build_events` - Trigger hook on build events -- `pipeline_events` - Trigger hook on pipeline events -- `wiki_page_events` - Trigger hook on wiki page events -- `enable_ssl_verification` - Do SSL verification when triggering the hook +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `url` | string | yes | The hook URL | +| `push_events` | boolean | no | Trigger hook on push events | +| `issues_events` | boolean | no | Trigger hook on issues events | +| `merge_requests_events` | boolean | no | Trigger hook on merge requests events | +| `tag_push_events` | boolean | no | Trigger hook on tag push events | +| `note_events` | boolean | no | Trigger hook on note events | +| `build_events` | boolean | no | Trigger hook on build events | +| `pipeline_events` | boolean | no | Trigger hook on pipeline events | +| `wiki_events` | boolean | no | Trigger hook on wiki events | +| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook | ### Edit project hook @@ -1112,18 +1150,20 @@ PUT /projects/:id/hooks/:hook_id Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project -- `hook_id` (required) - The ID of a project hook -- `url` (required) - The hook URL -- `push_events` - Trigger hook on push events -- `issues_events` - Trigger hook on issues events -- `merge_requests_events` - Trigger hook on merge_requests events -- `tag_push_events` - Trigger hook on push_tag events -- `note_events` - Trigger hook on note events -- `build_events` - Trigger hook on build events -- `pipeline_events` - Trigger hook on pipeline events -- `wiki_page_events` - Trigger hook on wiki page events -- `enable_ssl_verification` - Do SSL verification when triggering the hook +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `hook_id` | integer | yes | The ID of the project hook | +| `url` | string | yes | The hook URL | +| `push_events` | boolean | no | Trigger hook on push events | +| `issues_events` | boolean | no | Trigger hook on issues events | +| `merge_requests_events` | boolean | no | Trigger hook on merge requests events | +| `tag_push_events` | boolean | no | Trigger hook on tag push events | +| `note_events` | boolean | no | Trigger hook on note events | +| `build_events` | boolean | no | Trigger hook on build events | +| `pipeline_events` | boolean | no | Trigger hook on pipeline events | +| `wiki_events` | boolean | no | Trigger hook on wiki events | +| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook | ### Delete project hook @@ -1136,8 +1176,10 @@ DELETE /projects/:id/hooks/:hook_id Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project -- `hook_id` (required) - The ID of hook to delete +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `hook_id` | integer | yes | The ID of the project hook | Note the JSON response differs if the hook is available or not. If the project hook is available before it is returned in the JSON response or an empty response is returned. @@ -1156,7 +1198,9 @@ GET /projects/:id/repository/branches Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ```json [ @@ -1211,10 +1255,12 @@ GET /projects/:id/repository/branches/:branch Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project -- `branch` (required) - The name of the branch. -- `developers_can_push` - Flag if developers can push to the branch. -- `developers_can_merge` - Flag if developers can merge to the branch. +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `branch` | string | yes | The name of the branch | +| `developers_can_push` | boolean | no | Flag if developers can push to the branch | +| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch | ### Protect single branch @@ -1226,8 +1272,10 @@ PUT /projects/:id/repository/branches/:branch/protect Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project -- `branch` (required) - The name of the branch. +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `branch` | string | yes | The name of the branch | ### Unprotect single branch @@ -1239,8 +1287,10 @@ PUT /projects/:id/repository/branches/:branch/unprotect Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project -- `branch` (required) - The name of the branch. +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `branch` | string | yes | The name of the branch | ## Admin fork relation @@ -1254,8 +1304,10 @@ POST /projects/:id/fork/:forked_from_id Parameters: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked -- `forked_from_id:` (required) - The ID of the project that was forked from +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | +| `forked_from_id` | ID | yes | The ID of the project that was forked from | ### Delete an existing forked from relationship @@ -1265,7 +1317,9 @@ DELETE /projects/:id/fork Parameter: -- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of the project to be forked +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME | ## Search for projects by name @@ -1277,8 +1331,10 @@ GET /projects/search/:query Parameters: -- `query` (required) - A string contained in the project name -- `per_page` (optional) - number of projects to return per page -- `page` (optional) - the page to retrieve -- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields -- `sort` (optional) - Return requests sorted in `asc` or `desc` order +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `query` (required) - A string contained in the project name +| `per_page` (optional) - number of projects to return per page +| `page` (optional) - the page to retrieve +| `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields +| `sort` | string | no | Return requests sorted in `asc` or `desc` order | -- cgit v1.2.1 From bc57fc7cb29b40ae8111e391dfcbd6c55eb6c963 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 10:23:18 +0200 Subject: Update documentation according to changes in MWBS --- .../merge_requests/merge_when_build_succeeds.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md index 011f9cbc381..c138061fd40 100644 --- a/doc/user/project/merge_requests/merge_when_build_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md @@ -1,16 +1,16 @@ # Merge When Build Succeeds When reviewing a merge request that looks ready to merge but still has one or -more CI builds running, you can set it to be merged automatically when all -builds succeed. This way, you don't have to wait for the builds to finish and -remember to merge the request manually. +more CI builds running, you can set it to be merged automatically when the +builds pipeline succeed. This way, you don't have to wait for the builds to +finish and remember to merge the request manually. ![Enable](img/merge_when_build_succeeds_enable.png) When you hit the "Merge When Build Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait -for the build to succeed and want to merge immediately, this option is available -in the dropdown menu on the right of the main button. +for the pipeline to succeed and want to merge immediately, this option is +available in the dropdown menu on the right of the main button. Both team developers and the author of the merge request have the option to cancel the automatic merge if they find a reason why it shouldn't be merged @@ -18,9 +18,9 @@ after all. ![Status](img/merge_when_build_succeeds_status.png) -When the build succeeds, the merge request will automatically be merged. When -the build fails, the author gets a chance to retry any failed builds, or to -push new commits to fix the failure. +When the pipeline succeeds, the merge request will automatically be merged. +When the pipeline fails, the author gets a chance to retry any failed builds, +or to push new commits to fix the failure. When the builds are retried and succeed on the second try, the merge request will automatically be merged after all. When the merge request is updated with @@ -40,7 +40,7 @@ hit **Save** for the changes to take effect. ![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) -From now on, every time the build fails you will not be able to merge the merge -request from the UI, until you make the build pass. +From now on, every time the pipelinefails you will not be able to merge the +merge request from the UI, until you make all relevant builds pass. ![Only allow merge if build succeeds msg](img/merge_when_build_succeeds_only_if_succeeds_msg.png) -- cgit v1.2.1 From 6f7afaa8a0b5594b88cb5a4a2fe2a822ee1334a9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 10:30:40 +0200 Subject: Add Changelog entry for MWBS trigger improvements --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..909b2002e66 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) -- cgit v1.2.1 From 908b37bc88d9e671b508e064287564cc484d3c29 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 7 Oct 2016 11:48:43 +0100 Subject: Updates test in order to look for link --- spec/features/environments_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index a4d2460f595..05925f93220 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -76,7 +76,7 @@ feature 'Environments', feature: true do given(:deployment) { create(:deployment, environment: environment, deployable: build) } scenario 'does show an external link button' do - expect(page).to have_selector('.btn.external-url') + expect(page).to have_link(nil, href: environment.external_url) end end end @@ -147,9 +147,10 @@ feature 'Environments', feature: true do given(:deployment) { create(:deployment, environment: environment, deployable: build) } scenario 'does show an external link button' do - expect(page).to have_selector('.btn.external-url') + expect(page).to have_link(nil, href: environment.external_url) end end + end end end -- cgit v1.2.1 From 680efd4f8d6fb3a1036edc135f5761a512f8eea7 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 24 Aug 2016 23:44:58 +0100 Subject: Added subnav to labels edit+new and milestones edit+new+show Added subnav to blame show, blob edit, builds show, commit builds, commit show, environments edit and pipelines show Added subnav to new enviro view Added sidebar top position calculation logic Added sidebar translation to follow the subnav up when the body is scrolled until a certain limit --- app/assets/javascripts/build.js | 36 ++++++++-- app/assets/stylesheets/pages/builds.scss | 6 ++ app/views/projects/blame/show.html.haml | 87 +++++++++++------------ app/views/projects/blob/edit.html.haml | 43 ++++++------ app/views/projects/builds/show.html.haml | 95 +++++++++++++------------- app/views/projects/commit/builds.html.haml | 11 +-- app/views/projects/commit/show.html.haml | 25 ++++--- app/views/projects/environments/edit.html.haml | 11 +-- app/views/projects/environments/new.html.haml | 11 +-- app/views/projects/labels/edit.html.haml | 11 +-- app/views/projects/labels/new.html.haml | 11 +-- app/views/projects/milestones/edit.html.haml | 12 ++-- app/views/projects/milestones/new.html.haml | 11 +-- app/views/projects/milestones/show.html.haml | 83 +++++++++++----------- app/views/projects/pipelines/_head.html.haml | 2 +- app/views/projects/pipelines/show.html.haml | 13 ++-- 16 files changed, 268 insertions(+), 200 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index f336bfc36d6..97462a5959c 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -15,18 +15,17 @@ this.hideSidebar = bind(this.hideSidebar, this); this.toggleSidebar = bind(this.toggleSidebar, this); this.updateDropdown = bind(this.updateDropdown, this); + this.$document = $(document); clearInterval(Build.interval); // Init breakpoint checker this.bp = Breakpoints.get(); - $('.js-build-sidebar').niceScroll(); + this.initSidebar(); this.populateJobs(this.build_stage); this.updateStageDropdownText(this.build_stage); - this.hideSidebar(); - $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); - $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); + this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); $('#js-build-scroll > a').off('click').on('click', this.stepTrace); this.updateArtifactRemoveDate(); if ($('#build-trace').length) { @@ -62,6 +61,21 @@ } } + Build.prototype.initSidebar = function() { + this.$sidebar = $('.js-build-sidebar'); + this.sidebarTranslationLimits = { + min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + } + this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight(); + this.$sidebar.css({ + top: this.sidebarTranslationLimits.max + }); + this.$sidebar.niceScroll(); + this.hideSidebar(); + this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); + this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this)); + }; + Build.prototype.getInitialBuildTrace = function() { var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'] @@ -129,15 +143,23 @@ Build.prototype.toggleSidebar = function() { if (this.shouldHideSidebar()) { - return $('.js-build-sidebar').toggleClass('right-sidebar-expanded right-sidebar-collapsed'); + return this.$sidebar.toggleClass('right-sidebar-expanded right-sidebar-collapsed'); } }; + Build.prototype.translateSidebar = function(e) { + var newPosition = this.sidebarTranslationLimits.max - document.body.scrollTop; + if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min; + this.$sidebar.css({ + top: newPosition + }); + }; + Build.prototype.hideSidebar = function() { if (this.shouldHideSidebar()) { - return $('.js-build-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); + return this.$sidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); } else { - return $('.js-build-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); + return this.$sidebar.removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); } }; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 194a39a8377..fcaba954615 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -233,3 +233,9 @@ right: 0; margin-top: -17px; } + +@media (min-width: $screen-md-min) { + .sub-nav.build { + width: calc(100% + #{$gutter_width}); + } +} diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 5a98e258b22..dfb96305f48 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -1,45 +1,48 @@ +- @no_container = true - page_title "Blame", @blob.path, @ref += render "projects/commits/head" -%h3.page-title Blame view +%div{ class: container_class } + %h3.page-title Blame view -#blob-content-holder.tree-holder - .file-holder - .file-title - = blob_icon @blob.mode, @blob.name - %strong - = @path - %small= number_to_human_size @blob.size - .file-actions - = render "projects/blob/actions" - .table-responsive.file-content.blame.code.js-syntax-highlight - %table - - current_line = 1 - - @blame_groups.each do |blame_group| - %tr - %td.blame-commit - .commit - - commit = blame_group[:commit] - = author_avatar(commit, size: 36) - .commit-row-title - %strong - = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" - .pull-right - = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "monospace" -   - .light - = commit_author_link(commit, avatar: false) - authored - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} - %td.line-numbers - - line_count = blame_group[:lines].count - - (current_line...(current_line + line_count)).each do |i| - %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} - = icon("link") - = i - \ - - current_line += line_count - %td.lines - %pre.code.highlight - %code - - blame_group[:lines].each do |line| - #{line} + #blob-content-holder.tree-holder + .file-holder + .file-title + = blob_icon @blob.mode, @blob.name + %strong + = @path + %small= number_to_human_size @blob.size + .file-actions + = render "projects/blob/actions" + .table-responsive.file-content.blame.code.js-syntax-highlight + %table + - current_line = 1 + - @blame_groups.each do |blame_group| + %tr + %td.blame-commit + .commit + - commit = blame_group[:commit] + = author_avatar(commit, size: 36) + .commit-row-title + %strong + = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" + .pull-right + = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "monospace" +   + .light + = commit_author_link(commit, avatar: false) + authored + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} + %td.line-numbers + - line_count = blame_group[:lines].count + - (current_line...(current_line + line_count)).each do |i| + %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} + = icon("link") + = i + \ + - current_line += line_count + %td.lines + %pre.code.highlight + %code + - blame_group[:lines].each do |line| + #{line} diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 680e95ac6b5..2a0352a71b7 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,28 +1,31 @@ +- @no_container = true - page_title "Edit", @blob.path, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('blob_edit/blob_edit_bundle.js') += render "projects/commits/head" -- if @conflict - .alert.alert-danger - Someone edited the file the same time you did. Please check out - = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank" - and make sure your changes will not unintentionally remove theirs. +%div{ class: container_class } + - if @conflict + .alert.alert-danger + Someone edited the file the same time you did. Please check out + = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank" + and make sure your changes will not unintentionally remove theirs. -.file-editor - %ul.nav-links.no-bottom.js-edit-mode - %li.active - = link_to '#editor' do - Edit File + .file-editor + %ul.nav-links.no-bottom.js-edit-mode + %li.active + = link_to '#editor' do + Edit File - %li - = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do - = editing_preview_title(@blob.name) + %li + = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do + = editing_preview_title(@blob.name) - = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do - = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data - = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" - = hidden_field_tag 'last_commit_sha', @last_commit_sha - = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] - = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) + = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do + = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data + = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" + = hidden_field_tag 'last_commit_sha', @last_commit_sha + = hidden_field_tag 'content', '', id: "file-content" + = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] + = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index e4d41288aa6..b5e8b0bf6eb 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,56 +1,59 @@ +- @no_container = true - page_title "#{@build.name} (##{@build.id})", "Builds" - trace_with_state = @build.trace_with_state - header_title project_title(@project, "Builds", project_builds_path(@project)) += render "projects/pipelines/head", build_subnav: true -.build-page - = render "header" +%div{ class: container_class } + .build-page + = render "header" - - if @build.stuck? - - unless @build.any_runners_online? - .bs-callout.bs-callout-warning - %p - - if no_runners_for_project?(@build.project) - This build is stuck, because the project doesn't have any runners online assigned to it. - - elsif @build.tags.any? - This build is stuck, because you don't have any active runners online with any of these tags assigned to them: - - @build.tags.each do |tag| - %span.label.label-primary - = tag - - else - This build is stuck, because you don't have any active runners that can run this build. + - if @build.stuck? + - unless @build.any_runners_online? + .bs-callout.bs-callout-warning + %p + - if no_runners_for_project?(@build.project) + This build is stuck, because the project doesn't have any runners online assigned to it. + - elsif @build.tags.any? + This build is stuck, because you don't have any active runners online with any of these tags assigned to them: + - @build.tags.each do |tag| + %span.label.label-primary + = tag + - else + This build is stuck, because you don't have any active runners that can run this build. - %br - Go to - = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do - Runners page + %br + Go to + = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do + Runners page - .prepend-top-default - - if @build.active? - .autoscroll-container - %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll - - if @build.erased? - .erased.alert.alert-warning - - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by - Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} - - else - #js-build-scroll.scroll-controls - = link_to '#build-trace', class: 'btn' do - %i.fa.fa-angle-up - = link_to '#down-build-trace', class: 'btn' do - %i.fa.fa-angle-down - %pre.build-trace#build-trace - %code.bash.js-build-output - = icon("refresh spin", class: "js-build-refresh") + .prepend-top-default + - if @build.active? + .autoscroll-container + %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll + - if @build.erased? + .erased.alert.alert-warning + - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by + Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} + - else + #js-build-scroll.scroll-controls + = link_to '#build-trace', class: 'btn' do + %i.fa.fa-angle-up + = link_to '#down-build-trace', class: 'btn' do + %i.fa.fa-angle-down + %pre.build-trace#build-trace + %code.bash.js-build-output + = icon("refresh spin", class: "js-build-refresh") - #down-build-trace + #down-build-trace -= render "sidebar" + = render "sidebar" -:javascript - new Build({ - page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}", - build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", - build_status: "#{@build.status}", - build_stage: "#{@build.stage}", - state1: "#{trace_with_state[:state]}" - }) + :javascript + new Build({ + page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}", + build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", + build_status: "#{@build.status}", + build_stage: "#{@build.stage}", + state1: "#{trace_with_state[:state]}" + }) diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml index 2f051fb90e0..f9d7eac3542 100644 --- a/app/views/projects/commit/builds.html.haml +++ b/app/views/projects/commit/builds.html.haml @@ -1,7 +1,10 @@ +- @no_container = true - page_title "Builds", "#{@commit.title} (#{@commit.short_id})", "Commits" += render "projects/commits/head" -.prepend-top-default - = render "commit_box" +%div{ class: container_class } + .prepend-top-default + = render "commit_box" -= render "ci_menu" -= render "builds" + = render "ci_menu" + = render "builds" diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index ed44d86a687..cebf58d63df 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,14 +1,17 @@ +- @no_container = true - page_title "#{@commit.title} (#{@commit.short_id})", "Commits" - page_description @commit.description += render "projects/commits/head" -.prepend-top-default - = render "commit_box" -- if @commit.status - = render "ci_menu" -- else - %div.block-connector -= render "projects/diffs/diffs", diffs: @diffs -= render "projects/notes/notes_with_form" -- if can_collaborate_with_project? - - %w(revert cherry-pick).each do |type| - = render "projects/commit/change", type: type, commit: @commit, title: @commit.title +%div{ class: container_class } + .prepend-top-default + = render "commit_box" + - if @commit.status + = render "ci_menu" + - else + %div.block-connector + = render "projects/diffs/diffs", diffs: @diffs + = render "projects/notes/notes_with_form" + - if can_collaborate_with_project? + - %w(revert cherry-pick).each do |type| + = render "projects/commit/change", type: type, commit: @commit, title: @commit.title diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml index 6d1bdb9320f..3871165763c 100644 --- a/app/views/projects/environments/edit.html.haml +++ b/app/views/projects/environments/edit.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title "Edit", @environment.name, "Environments" += render "projects/pipelines/head" -%h3.page-title - Edit environment -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + Edit environment + %hr + = render 'form' diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index e51667ade2d..24638c77cbb 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title 'New Environment' += render "projects/pipelines/head" -%h3.page-title - New environment -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + New environment + %hr + = render 'form' diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 6901ba13ab7..52b187e7e58 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title "Edit", @label.name, "Labels" += render "projects/issues/head" -%h3.page-title - Edit Label -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + Edit Label + %hr + = render 'form' diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index 49ddf901619..a1bb66cfb6c 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title "New Label" += render "projects/issues/head" -%h3.page-title - New Label -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + New Label + %hr + = render 'form' diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml index be682226ab6..11f41e75e63 100644 --- a/app/views/projects/milestones/edit.html.haml +++ b/app/views/projects/milestones/edit.html.haml @@ -1,8 +1,12 @@ +- @no_container = true - page_title "Edit", @milestone.title, "Milestones" += render "projects/issues/head" -%h3.page-title - Edit Milestone ##{@milestone.iid} +%div{ class: container_class } -%hr + %h3.page-title + Edit Milestone ##{@milestone.iid} -= render "form" + %hr + + = render "form" diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml index 7f372b41698..cda093ade81 100644 --- a/app/views/projects/milestones/new.html.haml +++ b/app/views/projects/milestones/new.html.haml @@ -1,8 +1,11 @@ +- @no_container = true - page_title "New Milestone" += render "projects/issues/head" -%h3.page-title - New Milestone +%div{ class: container_class } + %h3.page-title + New Milestone -%hr + %hr -= render "form" + = render "form" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index e62f810a521..c83818e9199 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,49 +1,52 @@ +- @no_container = true - page_title @milestone.title, "Milestones" - page_description @milestone.description += render "projects/issues/head" -.detail-page-header - .status-box{ class: status_box_class(@milestone) } - - if @milestone.closed? - Closed - - elsif @milestone.expired? - Past due - - else - Open - %span.identifier - Milestone ##{@milestone.iid} - - if @milestone.expires_at - %span.creator - · - = @milestone.expires_at - .pull-right - - if can?(current_user, :admin_milestone, @project) - - if @milestone.active? - = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" +%div{ class: container_class } + .detail-page-header + .status-box{ class: status_box_class(@milestone) } + - if @milestone.closed? + Closed + - elsif @milestone.expired? + Past due - else - = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" + Open + %span.identifier + Milestone ##{@milestone.iid} + - if @milestone.expires_at + %span.creator + · + = @milestone.expires_at + .pull-right + - if can?(current_user, :admin_milestone, @project) + - if @milestone.active? + = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" + - else + = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" - = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do - Edit + = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do + Edit - = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do - Delete + = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do + Delete -.detail-page-description.milestone-detail - %h2.title - = markdown_field(@milestone, :title) - %div - - if @milestone.description.present? - .description - .wiki - = preserve do - = markdown_field(@milestone, :description) + .detail-page-description.milestone-detail + %h2.title + = markdown_field(@milestone, :title) + %div + - if @milestone.description.present? + .description + .wiki + = preserve do + = markdown_field(@milestone, :description) -- if @milestone.total_items_count(current_user).zero? - .alert.alert-success.prepend-top-default - %span Assign some issues to this milestone. -- elsif @milestone.complete?(current_user) && @milestone.active? - .alert.alert-success.prepend-top-default - %span All issues for this milestone are closed. You may close this milestone now. + - if @milestone.total_items_count(current_user).zero? + .alert.alert-success.prepend-top-default + %span Assign some issues to this milestone. + - elsif @milestone.complete?(current_user) && @milestone.active? + .alert.alert-success.prepend-top-default + %span All issues for this milestone are closed. You may close this milestone now. -= render 'shared/milestones/summary', milestone: @milestone, project: @project -= render 'shared/milestones/tabs', milestone: @milestone + = render 'shared/milestones/summary', milestone: @milestone, project: @project + = render 'shared/milestones/tabs', milestone: @milestone diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index 7d421c0e740..b10dd47709f 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -1,7 +1,7 @@ = content_for :sub_nav do .scrolling-tabs-container.sub-nav-scroll = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs + .nav-links.sub-nav.scrolling-tabs{ class: ('build' if local_assigns.fetch(:build_subnav, false)) } %ul{ class: (container_class) } - if project_nav_tab? :pipelines = nav_link(controller: :pipelines) do diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 75943c64276..688535ad764 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -1,8 +1,11 @@ +- @no_container = true - page_title "Pipeline" += render "projects/pipelines/head" -.prepend-top-default - - if @commit - = render "projects/pipelines/info" - %div.block-connector +%div{ class: container_class } + .prepend-top-default + - if @commit + = render "projects/pipelines/info" + %div.block-connector -= render "projects/commit/pipeline", pipeline: @pipeline + = render "projects/commit/pipeline", pipeline: @pipeline -- cgit v1.2.1 From f5631ff262fcf84b79c35c34d5a5337440aa4621 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 14:52:30 +0200 Subject: Fix ci pipeline processing with async jobs --- app/models/commit_status.rb | 31 ++++++++++++++++------------- app/services/ci/process_pipeline_service.rb | 2 ++ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 9fa8d17e74e..38554e7a0ca 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,6 +1,7 @@ class CommitStatus < ActiveRecord::Base include HasStatus include Importable + include AfterCommitQueue self.table_name = 'ci_builds' @@ -84,20 +85,6 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - after_transition do |commit_status, transition| - commit_status.pipeline.try do |pipeline| - break if transition.loopback? - - if commit_status.complete? - ProcessPipelineWorker.perform_async(pipeline.id) - end - - UpdatePipelineWorker.perform_async(pipeline.id) - end - - true - end - after_transition [:created, :pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) end @@ -105,10 +92,26 @@ class CommitStatus < ActiveRecord::Base after_transition any => :failed do |commit_status| MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) end + + after_transition do: :schedule_pipeline_update end delegate :sha, :short_sha, to: :pipeline + def schedule_pipeline_update + run_after_commit(:process_pipeline!) + end + + def process_pipeline! + pipeline.try do |pipeline| + if complete? + ProcessPipelineWorker.perform_async(pipeline.id) + else + UpdatePipelineWorker.perform_async(pipeline.id) + end + end + end + def before_sha pipeline.before_sha || Gitlab::Git::BLANK_SHA end diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 36c93dddadb..d3dd30b2588 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -16,6 +16,8 @@ module Ci process_stage(index) end + @pipeline.update_status + # Return a flag if a when builds got enqueued new_builds.flatten.any? end -- cgit v1.2.1 From 443619300d067a83cbd53872635c2c3cfd1f6655 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 23 Sep 2016 15:18:33 +0100 Subject: Added `issuable_filters_present` to check for active filters before rendering the reset button Added tests --- app/helpers/issuables_helper.rb | 4 ++++ app/views/shared/issuable/_filter.html.haml | 5 +++-- spec/features/issues/reset_filters_spec.rb | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 692fadd505f..03b2db1bc91 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -124,6 +124,10 @@ module IssuablesHelper end end + def issuable_filters_present + params[:search] || params[:author_id] || params[:assignee_id] || params[:milestone_title] || params[:label_name] + end + def issuables_count_for_state(issuable_type, state) issuables_finder = public_send("#{issuable_type}_finder") issuables_finder.params[:state] = state diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 31620297be0..6b43a76c404 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -29,8 +29,9 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - .filter-item.inline.reset-filters - %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters + - if issuable_filters_present + .filter-item.inline.reset-filters + %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters .pull-right - if boards_page diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb index f4d0f13c3d5..c9a3ecf16ea 100644 --- a/spec/features/issues/reset_filters_spec.rb +++ b/spec/features/issues/reset_filters_spec.rb @@ -75,6 +75,14 @@ feature 'Issues filter reset button', feature: true, js: true do end end + context 'when no filters have been applied' do + it 'the reset link should not be visible' do + visit_issues(project) + expect(page).to have_css('.issue', count: 2) + expect(page).not_to have_css '.reset_filters' + end + end + def reset_filters find('.reset-filters').click end -- cgit v1.2.1 From 2f7e28d1f700e1cdafb7771d4a61d8f71b68df04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 7 Oct 2016 15:28:15 +0200 Subject: Improve the contribution and MR review guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CONTRIBUTING.md | 49 ++++++++++++++++++------------------------ doc/development/code_review.md | 7 ++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5e15bfce14..0cdcb54b0ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -226,8 +226,7 @@ a feedback issue (if there isn't one already) and leave a comment asking for it to be marked as `Accepting merge requests`. Please include screenshots or wireframes if the feature will also change the UI. -Merge requests can be filed either at [GitLab.com][gitlab-mr-tracker] or at -[github.com][github-mr-tracker]. +Merge requests should be opened at [GitLab.com][gitlab-mr-tracker]. If you are new to GitLab development (or web development in general), see the [I want to contribute!](#i-want-to-contribute) section to get you started with @@ -246,10 +245,17 @@ tests are least likely to receive timely feedback. The workflow to make a merge request is as follows: 1. Fork the project into your personal space on GitLab.com -1. Create a feature branch, branch away from `master`. +1. Create a feature branch, branch away from `master` 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code -1. Add your changes to the [CHANGELOG](CHANGELOG) -1. If you are writing documentation, make sure to read the [documentation styleguide][doc-styleguide] +1. Add your changes to the [CHANGELOG](CHANGELOG): + 1. If you are fixing a ~regression issue, you can add your entry to the next + patch release (e.g. `8.12.5` if current version is `8.12.4`) + 1. Otherwise, add your entry to the next minor release (e.g. `8.13.0` if + current version is `8.12.4` + 1. Please add your entry at a random place among the entries of the targeted + release +1. If you are writing documentation, make sure to follow the + [documentation styleguide][doc-styleguide] 1. If you have multiple commits please combine them into one commit by [squashing them][git-squash] 1. Push the commit(s) to your fork @@ -258,7 +264,7 @@ request is as follows: 1. The MR description should give a motive for your change and the method you used to achieve it, see the [merge request description format] (#merge-request-description-format) -1. If the MR changes the UI it should include before and after screenshots +1. If the MR changes the UI it should include *Before* and *After* screenshots 1. If the MR changes CSS classes please include the list of affected pages, `grep css-class ./app -R` 1. Link any relevant [issues][ce-tracker] in the merge request description and @@ -270,7 +276,9 @@ request is as follows: [shell command guidelines](doc/development/shell_commands.md) 1. If your code creates new files on disk please read the [shared files guidelines](doc/development/shared_files.md). -1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/). +1. When writing commit messages please follow + [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) + [guidelines](http://chris.beams.io/posts/git-commit/). 1. If your merge request adds one or more migrations, make sure to execute all migrations on a fresh database before the MR is reviewed. If the review leads to large changes in the MR, do this again once the review is complete. @@ -305,23 +313,6 @@ Please ensure that your merge request meets the contribution acceptance criteria When having your code reviewed and when reviewing merge requests please take the [code review guidelines](doc/development/code_review.md) into account. -### Merge request description format - -Please submit merge requests using the following template in the merge request -description area. Copy-paste it to retain the markdown format. - -``` -## What does this MR do? - -## Are there points in the code the reviewer needs to double check? - -## Why was this MR needed? - -## What are the relevant issue numbers? - -## Screenshots (if relevant) -``` - ### Contribution acceptance criteria 1. The change is as small as possible @@ -333,8 +324,8 @@ description area. Copy-paste it to retain the markdown format. aforementioned failing test 1. Your MR initially contains a single commit (please use `git rebase -i` to squash commits) -1. Your changes can merge without problems (if not please merge `master`, never - rebase commits pushed to the remote server) +1. Your changes can merge without problems (if not please rebase if you're the + only one working on your feature branch, otherwise, merge `master`) 1. Does not break any existing functionality 1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed) @@ -352,7 +343,10 @@ description area. Copy-paste it to retain the markdown format. entire line to follow it. This prevents linting tools from generating warnings. - Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications may leave style non-compliant. -1. If the merge request adds any new libraries (gems, JavaScript libraries, etc.), they should conform to our [Licensing guidelines][license-finder-doc]. See the instructions in that document for help if your MR fails the "license-finder" test with a "Dependencies that need approval" error. +1. If the merge request adds any new libraries (gems, JavaScript libraries, + etc.), they should conform to our [Licensing guidelines][license-finder-doc]. + See the instructions in that document for help if your MR fails the + "license-finder" test with a "Dependencies that need approval" error. ## Changes for Stable Releases @@ -468,7 +462,6 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests [accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests [gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests -[github-mr-tracker]: https://github.com/gitlabhq/gitlabhq/pulls [gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit [git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits [closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 40ae55ab905..98279948888 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -34,6 +34,10 @@ request is up to one of our merge request "endbosses", denoted on the ## Having your code reviewed +Please keep in mind that code review is a process that can take multiple +iterations, and reviewer may spot things later that they may not have seen the +first time. + - 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 @@ -55,6 +59,7 @@ request is up to one of our merge request "endbosses", denoted on the Understand why the change is necessary (fixes a bug, improves the user experience, refactors the existing code). Then: +- Try to be thorough in your reviews to reduce the number of iterations. - 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 @@ -66,6 +71,8 @@ experience, refactors the existing code). Then: "LGTM :thumbsup:", or "Just a couple things to address." - Avoid accepting a merge request before the build succeeds ("Merge when build succeeds" is fine). +- If you set the MR to "Merge when build succeeds", you should take over + subsequent revisions for anything that would be spotted after that. ## Credits -- cgit v1.2.1 From 9afb2dac5c9f49f9f7943053b50a3808a99fdf6b Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Fri, 7 Oct 2016 11:11:02 +0200 Subject: ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup We use Sidekiq::Client.push_bulk to avoid Redis round trips --- CHANGELOG | 1 + app/workers/expire_build_artifacts_worker.rb | 11 ++-- .../expire_build_instance_artifacts_worker.rb | 11 ++++ spec/workers/expire_build_artifacts_worker_spec.rb | 51 +++++----------- .../expire_build_instance_artifacts_worker_spec.rb | 69 ++++++++++++++++++++++ 5 files changed, 100 insertions(+), 43 deletions(-) create mode 100644 app/workers/expire_build_instance_artifacts_worker.rb create mode 100644 spec/workers/expire_build_instance_artifacts_worker_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 8bfc5c97820..fec8d5fba29 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.13.0 (unreleased) - Improve issue load time performance by avoiding ORDER BY in find_by call - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Fix centering of custom header logos (Ashley Dumaine) + - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb index c64ea108d52..174eabff9fd 100644 --- a/app/workers/expire_build_artifacts_worker.rb +++ b/app/workers/expire_build_artifacts_worker.rb @@ -2,12 +2,11 @@ class ExpireBuildArtifactsWorker include Sidekiq::Worker def perform - Rails.logger.info 'Cleaning old build artifacts' + Rails.logger.info 'Scheduling removal of build artifacts' - builds = Ci::Build.with_expired_artifacts - builds.find_each(batch_size: 50).each do |build| - Rails.logger.debug "Removing artifacts build #{build.id}..." - build.erase_artifacts! - end + build_ids = Ci::Build.with_expired_artifacts.pluck(:id) + build_ids = build_ids.map { |build_id| [build_id] } + + Sidekiq::Client.push_bulk('class' => ExpireBuildInstanceArtifactsWorker, 'args' => build_ids ) end end diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb new file mode 100644 index 00000000000..916c2e633c1 --- /dev/null +++ b/app/workers/expire_build_instance_artifacts_worker.rb @@ -0,0 +1,11 @@ +class ExpireBuildInstanceArtifactsWorker + include Sidekiq::Worker + + def perform(build_id) + build = Ci::Build.with_expired_artifacts.reorder(nil).find_by(id: build_id) + return unless build + + Rails.logger.info "Removing artifacts build #{build.id}..." + build.erase_artifacts! + end +end diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb index 7d6668920c0..73cbadc13d9 100644 --- a/spec/workers/expire_build_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_artifacts_worker_spec.rb @@ -5,65 +5,42 @@ describe ExpireBuildArtifactsWorker do let(:worker) { described_class.new } + before { Sidekiq::Worker.clear_all } + describe '#perform' do before { build } - subject! { worker.perform } + subject! do + Sidekiq::Testing.fake! { worker.perform } + end context 'with expired artifacts' do let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) } - it 'does expire' do - expect(build.reload.artifacts_expired?).to be_truthy - end - - it 'does remove files' do - expect(build.reload.artifacts_file.exists?).to be_falsey - end - - it 'does nullify artifacts_file column' do - expect(build.reload.artifacts_file_identifier).to be_nil + it 'enqueues that build' do + expect(jobs_enqueued.size).to eq(1) + expect(jobs_enqueued[0]["args"]).to eq([build.id]) end end context 'with not yet expired artifacts' do let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) } - it 'does not expire' do - expect(build.reload.artifacts_expired?).to be_falsey - end - - it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy - end - - it 'does not nullify artifacts_file column' do - expect(build.reload.artifacts_file_identifier).not_to be_nil + it 'does not enqueue that build' do + expect(jobs_enqueued.size).to eq(0) end end context 'without expire date' do let(:build) { create(:ci_build, :artifacts) } - it 'does not expire' do - expect(build.reload.artifacts_expired?).to be_falsey - end - - it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy - end - - it 'does not nullify artifacts_file column' do - expect(build.reload.artifacts_file_identifier).not_to be_nil + it 'does not enqueue that build' do + expect(jobs_enqueued.size).to eq(0) end end - context 'for expired artifacts' do - let(:build) { create(:ci_build, artifacts_expire_at: Time.now - 7.days) } - - it 'is still expired' do - expect(build.reload.artifacts_expired?).to be_truthy - end + def jobs_enqueued + Sidekiq::Queues.jobs_by_worker['ExpireBuildInstanceArtifactsWorker'] end end end diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb new file mode 100644 index 00000000000..2b140f2ba28 --- /dev/null +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe ExpireBuildInstanceArtifactsWorker do + include RepoHelpers + + let(:worker) { described_class.new } + + describe '#perform' do + before { build } + + subject! { worker.perform(build.id) } + + context 'with expired artifacts' do + let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) } + + it 'does expire' do + expect(build.reload.artifacts_expired?).to be_truthy + end + + it 'does remove files' do + expect(build.reload.artifacts_file.exists?).to be_falsey + end + + it 'does nullify artifacts_file column' do + expect(build.reload.artifacts_file_identifier).to be_nil + end + end + + context 'with not yet expired artifacts' do + let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) } + + it 'does not expire' do + expect(build.reload.artifacts_expired?).to be_falsey + end + + it 'does not remove files' do + expect(build.reload.artifacts_file.exists?).to be_truthy + end + + it 'does not nullify artifacts_file column' do + expect(build.reload.artifacts_file_identifier).not_to be_nil + end + end + + context 'without expire date' do + let(:build) { create(:ci_build, :artifacts) } + + it 'does not expire' do + expect(build.reload.artifacts_expired?).to be_falsey + end + + it 'does not remove files' do + expect(build.reload.artifacts_file.exists?).to be_truthy + end + + it 'does not nullify artifacts_file column' do + expect(build.reload.artifacts_file_identifier).not_to be_nil + end + end + + context 'for expired artifacts' do + let(:build) { create(:ci_build, artifacts_expire_at: Time.now - 7.days) } + + it 'is still expired' do + expect(build.reload.artifacts_expired?).to be_truthy + end + end + end +end -- cgit v1.2.1 From 81d8a0dd6a338ce06b3ec67810c8c82c97246878 Mon Sep 17 00:00:00 2001 From: Sean Packham Date: Fri, 7 Oct 2016 14:53:09 +0100 Subject: Added missing content and improved layout --- doc/university/README.md | 282 ++++++++++++++++++++++++++++++----------------- 1 file changed, 179 insertions(+), 103 deletions(-) diff --git a/doc/university/README.md b/doc/university/README.md index 6ca1c20c9b2..8b3538d5616 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -1,139 +1,215 @@ +# GitLab University -## What is GitLab University +GitLab University is the best place to learn about **Version Control with Git and GitLab**. -_GitLab University_ has as a goal to teach the fundamentals of **Version Control with Git and GitLab** through courses that cover topics which can be mastered in around 2 hours. +It doesn't replace, but accompanies our great [Documentation](http://docs.gitlab.com) +and [Blog Articles](https://about.gitlab.com/blog/). -_University materials don't replace our [Documentation](http://docs.gitlab.com) or [Blog Articles](https://about.gitlab.com/blog/)._ +Would you like to contribute to GitLab University? Then please take a look at our contribution [process](/process) for more information. ---- +## Gitlab University Curriculum + +The curriculum is composed of GitLab videos, screencasts, presentations, projects and external GitLab content hosted on other services and has been organized into the following sections. -### On this page - -+ [GITx] Git -+ [OPSx] DevOps -+ [GLBx] GitLab Basics -+ [INTx] GitLab Integrations -+ [GLFx] GitLab Workflows -+ [GLEx] GitLab Enterprise Edition extra features -+ [GCIx] GitLab CI -+ [ECO] Ecosystem -+ [COM] Competition comparison -+ [SPTx] Support Bootcamp -+ [SLSx] Sales Bootcamp -+ [TRAx] Trainings +1. [GitLab Beginner](#beginner) +1. [GitLab Intermediate](#intermediate) +1. [GitLab Advanced](#advanced) +1. [External Articles](#external) +1. [Resources for GitLab Team Members](#team) --- -+ [GIT1] [Version Control Systems](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit#slide=id.g72f2e4906_2_29) -+ [GIT2] [Operating Systems and How Git Works](https://drive.google.com/a/gitlab.com/file/d/0B41DBToSSIG_OVYxVFJDOGI3Vzg/view?usp=sharing) -+ [GIT3] [Intro to Git](https://www.codeschool.com/account/courses/try-git) +### 1. GitLab Beginner ---- +#### 1.1. Version Control and Git -+ [OPS1] [What is Omnibus](https://www.youtube.com/watch?v=XTmpKudd-Oo) -+ [OPS2] [Installing GitLab](https://www.youtube.com/watch?v=Q69YaOjqNhg) -+ [OPS3] [Configuring an external PostgreSQL database](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#using-a-non-packaged-postgresql-database-management-server) -+ [OPS5] [Importing from Other Tools or SVN](http://doc.gitlab.com/ee/workflow/importing/) -+ [OPS6] [High Availability Documentation](https://about.gitlab.com/high-availability/) -+ [OPS7] [Managing LDAP, Active Directory](https://www.youtube.com/watch?v=HPMjM-14qa8) -+ [OPS8] [Scalability and High Availability](https://www.youtube.com/watch?v=cXRMJJb6sp4&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=2) -+ [OPS9] [High Availability on AWS](high-availability/aws/README.md) +1. [Version Control Systems](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit#slide=id.g72f2e4906_2_29) +1. [Operating Systems and How Git Works](https://drive.google.com/a/gitlab.com/file/d/0B41DBToSSIG_OVYxVFJDOGI3Vzg/view?usp=sharing) +1. [Code School: An Introduction to Git](https://www.codeschool.com/account/courses/try-git) ---- +#### 1.2. GitLab Basics -+ [GLB1] [Terminology](glossary/README.md) -+ [GLB2] [GitLab Basics](http://doc.gitlab.com/ce/gitlab-basics/README.html) -+ [GLB3] [Demo of GitLab.com](https://www.youtube.com/watch?v=WaiL5DGEMR4) -+ [GLB4] [Create and Add your SSH key to GitLab](https://www.youtube.com/watch?v=54mxyLo3Mqk) -+ [GLB5] [Repositories, Projects and Groups](https://www.youtube.com/watch?v=4TWfh1aKHHw&index=1&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e) -+ [GLB6] [Creating a Project in GitLab](https://www.youtube.com/watch?v=7p0hrpNaJ14) -+ [GLB7] [Issues and Merge Requests](https://www.youtube.com/watch?v=raXvuwet78M) -+ [GLB8] [Big files in Git (Git LFS, Annex)](https://gitlab.com/gitlab-org/University/blob/master/classes/git_lfs_and_annex.md) +1. [An Overview of GitLab.com - Video](https://www.youtube.com/watch?v=WaiL5DGEMR4) +1. [Why Use Git and GitLab - Slides](https://docs.google.com/a/gitlab.com/presentation/d/1RcZhFmn5VPvoFu6UMxhMOy7lAsToeBZRjLRn0LIdaNc/edit?usp=drive_web) +1. [GitLab Basics - Article](http://doc.gitlab.com/ce/gitlab-basics/README.html) +1. [Git and GitLab Basics - Video](https://www.youtube.com/watch?v=03wb9FvO4Ak&index=5&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e) +1. [Git and GitLab Basics - Online Course](https://courses.platzi.com/classes/git-gitlab/concepto/part-1/part-23370/material/) +1. [Comparison of GitLab Versions](https://about.gitlab.com/features/#compare) ---- +#### 1.3. Your GitLab Account -+ [INT1] [JIRA and Jenkins integrations in GitLab](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415) -+ [INT2] [Integrating JIRA with GitLab](http://doc.gitlab.com/ee/integration/jira.html) -+ [INT3] [Integrating Jenkins with GitLab](http://doc.gitlab.com/ee/integration/jenkins.html) -+ [INT4] [Integrating Bamboo with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/project_services/bamboo.md) -+ [INT5] [Documentation on Integrating Slack with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/slack.md) +1. [Create a GitLab Account - Online Course](https://courses.platzi.com/classes/git-gitlab/concepto/first-steps/create-an-account-on-gitlab/material/) +1. [Create and Add your SSH key to GitLab - Video](https://www.youtube.com/watch?v=54mxyLo3Mqk) ---- +#### 1.4. GitLab Projects -+ [GLF1] [GitLab Flow](https://www.youtube.com/watch?v=UGotqAUACZA) +1. [Repositories, Projects and Groups - Video](https://www.youtube.com/watch?v=4TWfh1aKHHw&index=1&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e) +1. [Creating a Project in GitLab - Video](https://www.youtube.com/watch?v=7p0hrpNaJ14) +1. [How to Create Files and Directories](https://about.gitlab.com/2016/02/10/feature-highlight-create-files-and-directories-from-files-page/) +1. [GitLab Todos](https://about.gitlab.com/2016/03/02/gitlab-todos-feature-highlight/) +1. [GitLab's Work in Progress (WIP) Flag](https://about.gitlab.com/2016/01/08/feature-highlight-wip/) ---- +#### 1.5. Migrating from other Source Control -+ [GLE1] [Configuring an external MySQL database](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#using-a-mysql-database-management-server-enterprise-edition-only) -+ [GLE2] [Managing Permissions within EE](https://www.youtube.com/watch?v=DjUoIrkiNuM) -+ [GLE3] [Upcoming in EE and Big files in Git (Git LFS, Annex)](https://gitlab.com/gitlab-org/University/blob/master/classes/upcoming_in_ee.md) +1. [Migrating from BitBucket/Stash](http://doc.gitlab.com/ee/workflow/importing/import_projects_from_bitbucket.html) +1. [Migrating from GitHub](http://doc.gitlab.com/ee/workflow/importing/import_projects_from_github.html) +1. [Migrating from SVN](http://doc.gitlab.com/ee/workflow/importing/migrating_from_svn.html) +1. [Migrating from Fogbugz](http://doc.gitlab.com/ee/workflow/importing/import_projects_from_fogbugz.html) ---- +#### 1.6. GitLab Inc. -+ [GCI1] [GitLab CI product page](https://about.gitlab.com/gitlab-ci/) -+ [GCI2] [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/) +1. [About GitLab](https://about.gitlab.com/about/) +1. [GitLab Direction](https://about.gitlab.com/direction/) +1. [GitLab Master Plan](https://about.gitlab.com/2016/09/13/gitlab-master-plan/) +1. [Making GitLab Great for Everyone - Video](https://www.youtube.com/watch?v=GGC40y4vMx0) - Response to "Dear GitHub" letter +1. [Using Innersourcing to Improve Collaboration](https://about.gitlab.com/2014/09/05/innersourcing-using-the-open-source-workflow-to-improve-collaboration-within-an-organization/) +1. [The Software Development Market and GitLab - Video](https://www.youtube.com/watch?v=sXlhgPK1NTY&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=6) - [Slides](https://docs.google.com/presentation/d/1vCU-NbZWz8NTNK8Vu3y4zGMAHb5DpC8PE5mHtw1PWfI/edit) ---- +#### 1.7 Community and Support -+ [COM1] [GitLab compared to other tools](https://about.gitlab.com/comparison/) -+ [COM2] [Compare GitLab versions](https://about.gitlab.com/features/#compare) -+ [COM3] [Innersourcing article](https://about.gitlab.com/2014/09/05/innersourcing-using-the-open-source-workflow-to-improve-collaboration-within-an-organization/) +1. [Getting Help](/getting-help/) + - Proposing Features and Reporting and Tracking bugs for GitLab + - The GitLab IRC channel, Gitter Chat Room, Community Forum and Mailing List + - Getting Technical Support + - Being part of our Great Community and Contributing to GitLab +1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/) +1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/) +1. [GitLab Training Workshops](/training) ---- +#### 1.8 GitLab Training Material -+ [ECO1] [Ecosystem Overview](https://www.youtube.com/watch?v=sXlhgPK1NTY&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=6) -+ [ECO2] [Positioning FAQ](https://about.gitlab.com/handbook/positioning-faq) -+ [ECO3] [GitLab Ecosystem slides](https://docs.google.com/presentation/d/1vCU-NbZWz8NTNK8Vu3y4zGMAHb5DpC8PE5mHtw1PWfI/edit) -+ [ECO4] [Customer Use-Cases](https://about.gitlab.com/handbook/use-cases/) +1. [Git and GitLab Terminology](/glossary/) +1. [Git and GitLab Workshop - Slides](https://docs.google.com/presentation/d/1JzTYD8ij9slejV2-TO-NzjCvlvj6mVn9BORePXNJoMI/edit?usp=drive_web) +1. [Git and GitLab Revision](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/university/training/end-user) --- -+ [SPT1] [Support Path](support/README.md) -+ [SPT2] [End User Training Material](https://gitlab.com/gitlab-org/University/blob/master/training/user_training.md) -+ [SPT3] [Materials for Training Sessions](https://gitlab.com/gitlab-org/University/tree/master/training/topics) +### 2. GitLab Intermediate + +#### 2.1 GitLab Pages + +1. [Using any Static Site Generator with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) +1. [Securing GitLab Pages with SSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/) +1. [GitLab Pages Documentation](http://doc.gitlab.com/ee/pages/README.html) + +#### 2.2. GitLab Issues + +1. [Markdown in GitLab](http://doc.gitlab.com/ce/markdown/markdown.html) +1. [Issues and Merge Requests - Video](https://www.youtube.com/watch?v=raXvuwet78M) +1. [Due Dates and Milestones fro GitLab Issues](https://about.gitlab.com/2016/08/05/feature-highlight-set-dates-for-issues/) +1. [How to Use GitLab Labels](https://about.gitlab.com/2016/08/17/using-gitlab-labels/) +1. [Applying GitLab Labels Automatically](https://about.gitlab.com/2016/08/19/applying-gitlab-labels-automatically/) +1. [GitLab Issue Board - Product Page](https://about.gitlab.com/solutions/issueboard/) +1. [An Overview of GitLab Issue Board](https://about.gitlab.com/2016/08/22/announcing-the-gitlab-issue-board/) +1. [Designing GitLab Issue Board](https://about.gitlab.com/2016/08/31/designing-issue-boards/) +1. [From Idea to Production with GitLab - Video](https://www.youtube.com/watch?v=25pHyknRgEo&index=14&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e) + +#### 2.3. Continuous Integration + +1. [Operating Systems, Servers, VMs, Containers and Unix - Video](https://www.youtube.com/watch?v=V61kL6IC-zY&index=8&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e) +1. [GitLab CI - Product Page](https://about.gitlab.com/gitlab-ci/) +1. [Getting started with GitLab and GitLab CI](https://about.gitlab.com/2015/12/14/getting-started-with-gitlab-and-gitlab-ci/) +1. [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/) +1. [GitLab and Docker - Video](https://www.youtube.com/watch?v=ugOrCcbdHko&index=12&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e) +1. [How we scale GitLab with built in Docker](https://about.gitlab.com/2016/06/21/how-we-scale-gitlab-by-having-docker-built-in/) +1. [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) +1. [Deployments and Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) +1. [Sequential, Parallel or Custom Pipelines](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/) +1. [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/) +1. [Setting up GitLab Runner on DigitalOcean](https://about.gitlab.com/2016/04/19/how-to-set-up-gitlab-runner-on-digitalocean/) +1. [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) +1. [IBM: Continuous Delivery vs Continuous Deployment - Video](https://www.youtube.com/watch?v=igwFj8PPSnw) +1. [Amazon: Transition to Continuous Delivery - Video](https://www.youtube.com/watch?v=esEFaY0FDKc) +1. See **[Integrations](#integrations)** for integrations with other CI services. + +#### 2.4. Workflow + +1. [GitLab Flow - Video](https://youtu.be/enMumwvLAug?list=PLFGfElNsQthZnwMUFi6rqkyUZkI00OxIV) +1. [GitLab Flow vs Forking in GitLab - Video](https://www.youtube.com/watch?v=UGotqAUACZA) +1. [GitLab Flow Overview](https://about.gitlab.com/2014/09/29/gitlab-flow/) +1. [Always Start with an Issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/) +1. [GitLab Flow Documentation](http://doc.gitlab.com/ee/workflow/gitlab_flow.html) + +#### 2.5. GitLab Comparisons + +1. [GitLab Compared to Other Tools](https://about.gitlab.com/comparison/) +1. [Comparing GitLab Terminology](https://about.gitlab.com/2016/01/27/comparing-terms-gitlab-github-bitbucket/) +1. [GitLab Compared to Atlassian (Recording 2016-03-03) ](https://youtu.be/Nbzp1t45ERo) +1. [GitLab Position FAQ](https://about.gitlab.com/handbook/positioning-faq) +1. [Customer review of GitLab with points on why they prefer GitLab](https://www.enovate.co.uk/web-design-blog/2015/11/25/gitlab-review/) --- -+ [SLS1] [Sales Path (redirect to sales handbook)](https://about.gitlab.com/handbook/sales-onboarding/) -+ [SLS2] [GitLab Direction](https://about.gitlab.com/direction/) +### 3. GitLab Advanced + +#### 3.1. Dev Ops + +1. [Xebia Labs: Dev Ops Terminology](https://xebialabs.com/glossary/) +1. [Xebia Labs: Periodic Table of DevOps Tools](https://xebialabs.com/periodic-table-of-devops-tools/) +1. [Puppet Labs: State of Dev Ops 2015 - Book](https://puppetlabs.com/sites/default/files/2015-state-of-devops-report.pdf) + +#### 3.2. Installing GitLab with Omnibus + +1. [What is Omnibus - Video](https://www.youtube.com/watch?v=XTmpKudd-Oo) +1. [How to Install GitLab with Omnibus - Video](https://www.youtube.com/watch?v=Q69YaOjqNhg) +1. [Installing GitLab - Online Course](https://courses.platzi.com/classes/git-gitlab/concepto/part-1/part-3/material/) +1. [Using a Non-Packaged PostgreSQL Database](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#using-a-non-packaged-postgresql-database-management-server) +1. [Using a MySQL Database](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#using-a-mysql-database-management-server-enterprise-edition-only) +1. [Installing GitLab on Microsoft Azure](https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/) +1. [Installing GitLab on Digital Ocean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/) + +#### 3.3. Permissions + +1. [How to Manage Permissions in GitLab EE - Video](https://www.youtube.com/watch?v=DjUoIrkiNuM) + +#### 3.4. Large Files + +1. [Big files in Git (Git LFS, Annex) - Video](https://www.youtube.com/watch?v=DawznUxYDe4) + +#### 3.5. LDAP and Active Directory + +1. [How to Manage LDAP, Active Directory in GitLab - Video](https://www.youtube.com/watch?v=HPMjM-14qa8) + +#### 3.6 Custom Languages + +1. [How to add Syntax Highlighting Support for Custom Langauges to GitLab - Video](how to add support for your favorite language to GitLab) + +#### 3.7. Scalability and High Availability + +1. [Scalability and High Availability - Video](https://www.youtube.com/watch?v=cXRMJJb6sp4&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=2) +1. [High Availability - Video](https://www.youtube.com/watch?v=36KS808u6bE&index=15&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e) +1. [High Availability Documentation](https://about.gitlab.com/high-availability/) + +#### 3.8 Cycle Analytics + +1. [GitLab Cycle Analytics Overview](https://about.gitlab.com/2016/09/21/cycle-analytics-feature-highlight/) +1. [GitLab Cycle Analytics - Product Page](https://about.gitlab.com/solutions/cycle-analytics/) + +#### 3.9. Integrations + +1. [How to Integrate JIRA and Jenkins with GitLab - Video](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415) +1. [How to Integrate Jira with GitLab](http://doc.gitlab.com/ee/integration/jira.html) +1. [How to Integrate Jenkins with GitLab](http://doc.gitlab.com/ee/integration/jenkins.html) +1. [How to Integrate Bamboo with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/project_services/bamboo.md) +1. [How to Integrate Slack with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/slack.md) +1. [How to Integrate Convox with GitLab](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) +1. [Getting Started with GitLab and Shippable CI](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/) --- -+ [TRA1] [End User Training](training/end-user/README.md) +## 4. External Articles + +1. [2011 WSJ article by Mark Andreeson - Software is Eating the World](http://www.wsj.com/articles/SB10001424053111903480904576512250915629460) +1. [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/) +1. [2015 Venture Beat article - Actually, Open Source is Eating the World](http://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/) --- -### External Resources - -+ [DOC] GitLab Documentation - + [Set up and use GitLab Pages](http://doc.gitlab.com/ee/pages/README.html) - + [Markdown Reference](http://doc.gitlab.com/ce/markdown/markdown.html) - -+ [GLW] GitLab Workshop (@ Platzi) - + [GitLab Workshop Part 1: Basics of Git and GitLab](https://courses.platzi.com/classes/git-gitlab/) - + [Create a GitLab Account](https://courses.platzi.com/classes/git-gitlab/concepto/first-steps/create-an-account-on-gitlab/material/) - -+ [GLY] GitLab YouTube Videos - + [Making GitLab Great for Everyone, our response to the Dear GitHub letter](https://www.youtube.com/watch?v=GGC40y4vMx0) - + [Compared to Atlassian (Recorded on 2016-03-03) ](https://youtu.be/Nbzp1t45ERo) - -+ [GLI] GitLab Team-Only Access - + [GitLab architecture for noobs](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/development/architecture.md) - + [Client Assessment of GitLab versus GitHub](https://docs.google.com/a/gitlab.com/spreadsheets/d/18cRF9Y5I6I7Z_ab6qhBEW55YpEMyU4PitZYjomVHM-M/edit?usp=sharing) - -+ [KNT] Slides & Keynotes by GitLabbers & other individuals - + [Why Git and GitLab slide deck](https://docs.google.com/a/gitlab.com/presentation/d/1RcZhFmn5VPvoFu6UMxhMOy7lAsToeBZRjLRn0LIdaNc/) - + [Git Workshop](https://docs.google.com/presentation/d/1JzTYD8ij9slejV2-TO-NzjCvlvj6mVn9BORePXNJoMI/) - -+ Others (not created by GitLab) - + [Dev Ops terminology](https://xebialabs.com/glossary/) - + [Continuous Delivery vs Continuous Deployment](https://www.youtube.com/watch?v=igwFj8PPSnw) - + [Periodic Table of DevOps Tools](https://xebialabs.com/periodic-table-of-devops-tools/) - + [State of Dev Ops 2015 Report by Puppet Labs](https://puppetlabs.com/sites/default/files/2015-state-of-devops-report.pdf) Insightful Chapters to understand the Impact of Continuous Delivery on Performance (Chapter 4), the Application Architecture (Chapter 5) and How IT Managers can help their teams win (Chapter 6). - + [2011 WSJ article by Mark Andreeson - Software is Eating the World](http://www.wsj.com/articles/SB10001424053111903480904576512250915629460) - + [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/) - + [2015 Venture Beat article - Actually, Open Source is Eating the World](http://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/) - + [Customer review of GitLab with talking points on why they prefer GitLab](https://www.enovate.co.uk/web-design-blog/2015/11/25/gitlab-review/) - + [3rd party tool comparison](http://technologyconversations.com/2015/10/16/github-vs-gitlabs-vs-bitbucket-server-formerly-stash/) - + [Amazon's transition to Continuous Delivery](https://www.youtube.com/watch?v=esEFaY0FDKc) - + [Article on Continuous Integration from ThoughtWorks](https://www.thoughtworks.com/continuous-integration) +## 5. Resources for GitLab Team Members + +*Some content can only be accessed by GitLab team members* + +1. [Support Path](/support/) +1. [Sales Path (redirect to sales handbook)](https://about.gitlab.com/handbook/sales-onboarding/) +1. [GitLab architecture for noobs](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/development/architecture.md) +1. [Client Assessment of GitLab versus GitHub](https://docs.google.com/a/gitlab.com/spreadsheets/d/18cRF9Y5I6I7Z_ab6qhBEW55YpEMyU4PitZYjomVHM-M/edit?usp=sharing) -- cgit v1.2.1 From 52ca9bf600235459721fbcf16e4f582b839b3b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 7 Oct 2016 16:17:28 +0200 Subject: Fix typo and add he MWBS accronym for "Merge When Build Succeeds" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/development/code_review.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 98279948888..c5c23b5c0b8 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -35,7 +35,7 @@ request is up to one of our merge request "endbosses", denoted on the ## Having your code reviewed Please keep in mind that code review is a process that can take multiple -iterations, and reviewer may spot things later that they may not have seen the +iterations, and reviewers may spot things later that they may not have seen the first time. - The first reviewer of your code is _you_. Before you perform that first push @@ -69,9 +69,9 @@ experience, refactors the existing code). Then: 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). -- If you set the MR to "Merge when build succeeds", you should take over +- Avoid accepting a merge request before the build succeeds. Of course, "Merge + When Build Succeeds" (MWBS) is fine. +- If you set the MR to "Merge When Build Succeeds", you should take over subsequent revisions for anything that would be spotted after that. ## Credits -- cgit v1.2.1 From 328ca8c47054a6d1d284bc8e16b9b0600a137916 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 6 Oct 2016 20:00:51 +0200 Subject: Optimize the `award_user_list` helper spec According to https://gitlab.com/gitlab-org/gitlab-ce/issues/23034#note_16586657, each test for this helper generated 1,833 queries. Now we only generate stubbed records, and only as many as we need for each test. This also corrects a slight logic bug in the helper itself. When the number of awards was greater than the limit (9 by default), _and_ the current user was one of them, we actually included 10 names, including "You", plus the remaining count. Now we return the correct number regardless. --- app/helpers/issues_helper.rb | 5 ++--- spec/helpers/issues_helper_spec.rb | 36 +++++++++++++++++++++++------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 8b212b0327a..1644c346dd8 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -113,14 +113,13 @@ module IssuesHelper end end - def award_user_list(awards, current_user) + def award_user_list(awards, current_user, limit: 10) names = awards.map do |award| award.user == current_user ? 'You' : award.user.name end - # Take first 9 OR current user + first 9 current_user_name = names.delete('You') - names = names.first(9).insert(0, current_user_name).compact + names = names.insert(0, current_user_name).compact.first(limit) names << "#{awards.size - names.size} more." if awards.size > names.size diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 67bac782591..abe08d95ece 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -63,28 +63,38 @@ describe IssuesHelper do end describe '#award_user_list' do - let!(:awards) { build_list(:award_emoji, 15) } + it "returns a comma-separated list of the first X users" do + user = build_stubbed(:user, name: 'Joe') + awards = Array.new(3, build_stubbed(:award_emoji, user: user)) - it "returns a comma seperated list of 1-9 users" do - expect(award_user_list(awards.first(9), nil)).to eq(awards.first(9).map { |a| a.user.name }.to_sentence) + expect(award_user_list(awards, nil, limit: 3)) + .to eq('Joe, Joe, and Joe') end it "displays the current user's name as 'You'" do - expect(award_user_list(awards.first(1), awards[0].user)).to eq('You') - end + user = build_stubbed(:user, name: 'Joe') + award = build_stubbed(:award_emoji, user: user) - it "truncates lists of larger than 9 users" do - expect(award_user_list(awards, nil)).to eq(awards.first(9).map { |a| a.user.name }.join(', ') + ", and 6 more.") + expect(award_user_list([award], user)).to eq('You') + expect(award_user_list([award], nil)).to eq 'Joe' end - it "displays the current user in front of 0-9 other users" do - expect(award_user_list(awards, awards[0].user)). - to eq("You, " + awards[1..9].map { |a| a.user.name }.join(', ') + ", and 5 more.") + it "truncates lists" do + user = build_stubbed(:user, name: 'Jane') + awards = Array.new(5, build_stubbed(:award_emoji, user: user)) + + expect(award_user_list(awards, nil, limit: 3)) + .to eq('Jane, Jane, Jane, and 2 more.') end - it "displays the current user in front regardless of position in the list" do - expect(award_user_list(awards, awards[12].user)). - to eq("You, " + awards[0..8].map { |a| a.user.name }.join(', ') + ", and 5 more.") + it "displays the current user in front of other users" do + current_user = build_stubbed(:user) + my_award = build_stubbed(:award_emoji, user: current_user) + award = build_stubbed(:award_emoji, user: build_stubbed(:user, name: 'Jane')) + awards = Array.new(5, award).push(my_award) + + expect(award_user_list(awards, current_user, limit: 2)). + to eq("You, Jane, and 4 more.") end end -- cgit v1.2.1 From 031777339e3f0d07551b2976eb471069f77506e6 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 7 Oct 2016 15:56:37 +0100 Subject: Deletes extra empty line breaking the build --- spec/features/environments_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 05925f93220..68ea4eeae31 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -150,7 +150,6 @@ feature 'Environments', feature: true do expect(page).to have_link(nil, href: environment.external_url) end end - end end end -- cgit v1.2.1 From 4e34a495875aae0a9021da69330c292fd95a6955 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 14:17:27 +0200 Subject: Debounce GfmAutoComplete setup and simplify code somewhat. --- CHANGELOG | 1 + app/assets/javascripts/gfm_auto_complete.js.es6 | 38 +++++++++---------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 392a356c79a..16ce50c944e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -110,6 +110,7 @@ v 8.12.2 - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix resolve discussion buttons endpoint path - Refactor remnants of CoffeeScript destructured opts and super !6261 + - Prevent running GfmAutocomplete setup for each diff note !6569 v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index d0786bf0053..845313b6b38 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -52,37 +52,27 @@ } } }, - setup: function(input) { + setup: _.debounce(function(input) { // Add GFM auto-completion to all input fields, that accept GFM input. this.input = input || $('.js-gfm-input'); // destroy previous instances this.destroyAtWho(); // set up instances this.setupAtWho(); - if (this.dataSource) { - if (!this.dataLoading && !this.cachedData) { - this.dataLoading = true; - setTimeout((function(_this) { - return function() { - var fetch; - fetch = _this.fetchData(_this.dataSource); - return fetch.done(function(data) { - _this.dataLoading = false; - return _this.loadData(data); - }); - }; - // We should wait until initializations are done - // and only trigger the last .setup since - // The previous .dataSource belongs to the previous issuable - // and the last one will have the **proper** .dataSource property - // TODO: Make this a singleton and turn off events when moving to another page - })(this), 1000); - } - if (this.cachedData != null) { - return this.loadData(this.cachedData); - } + + if (this.dataSource && !this.dataLoading && !this.cachedData) { + this.dataLoading = true; + return this.fetchData(this.dataSource) + .done((data) => { + this.dataLoading = false; + this.loadData(data); + }); + }; + + if (this.cachedData != null) { + return this.loadData(this.cachedData); } - }, + }, 1000), setupAtWho: function() { // Emoji this.input.atwho({ -- cgit v1.2.1 From 8d2de73a83b98741dbbbc21fe2cbcdaf7840996d Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Fri, 7 Oct 2016 17:16:42 +0100 Subject: fixup! Added link to bulk assign issues to MR author. (Issue #18876) --- app/controllers/projects/merge_requests_controller.rb | 2 ++ app/services/merge_requests/assign_issues_service.rb | 6 +++--- .../projects/merge_requests_controller_spec.rb | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8bbf3ec67b3..17dc89f717b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -29,6 +29,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController # Allow modify merge_request before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort] + before_action :authenticate_user!, only: [:assign_related_issues] + before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts] def index diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb index 2e84f844449..f636e5fec4f 100644 --- a/app/services/merge_requests/assign_issues_service.rb +++ b/app/services/merge_requests/assign_issues_service.rb @@ -3,9 +3,9 @@ module MergeRequests def assignable_issues @assignable_issues ||= begin if current_user == merge_request.author - closes_issues. - reject { |issue| issue.assignee_id? }. - select { |issue| can?(current_user, :admin_issue, issue) } + closes_issues.select do |issue| + !issue.assignee_id? && can?(current_user, :admin_issue, issue) + end else [] end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 18041bce482..2a68e5a2c9b 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -738,5 +738,22 @@ describe Projects::MergeRequestsController do expect(flash[:notice]).to eq '1 issue has been assigned to you' end + + it 'calls MergeRequests::AssignIssuesService' do + expect(MergeRequests::AssignIssuesService).to receive(:new). + with(project, user, merge_request: merge_request). + and_return(double(execute: {count: 1})) + + post_assign_issues + end + + it 'is skipped when not signed in' do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + sign_out(:user) + + expect(MergeRequests::AssignIssuesService).not_to receive(:new) + + post_assign_issues + end end end -- cgit v1.2.1 From 3530489761fbf892adbf335fe65c0c73b978da39 Mon Sep 17 00:00:00 2001 From: Georg G Date: Fri, 7 Oct 2016 19:15:14 +0200 Subject: Use defined colour for a language when available This commit changes the colours of languages in the language graph to the ones Linguist has defined. When there is no colour defined for a language in Linguist, it will fall back to the old method of finding a colour. --- app/controllers/projects/graphs_controller.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 092ef32e6e3..f4f2e5841a0 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -35,15 +35,19 @@ class Projects::GraphsController < Projects::ApplicationController def languages @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages total = @languages.map(&:last).sum + colors = Linguist::Language.colors. + select { |lang| @languages.include? lang.name }. + map { |lang| [lang.name, lang.color] }. + to_h @languages = @languages.map do |language| name, share = language - color = Digest::SHA256.hexdigest(name)[0...6] + color = colors[name] || "##{Digest::SHA256.hexdigest(name)[0...6]}" { value: (share.to_f * 100 / total).round(2), label: name, - color: "##{color}", - highlight: "##{color}" + color: color, + highlight: color } end -- cgit v1.2.1 From 43438c3695ac06e0d12e24824f2c191f9608a51b Mon Sep 17 00:00:00 2001 From: Ismael Arenzana Date: Fri, 7 Oct 2016 17:29:48 +0000 Subject: Changed gitlab-shell version to avoid warning when precompiling the assets. --- doc/update/8.11-to-8.12.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 07743d050f7..cddfa7e3e01 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-12-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v3.6.0 +sudo -u git -H git checkout v3.6.1 ``` ### 6. Update gitlab-workhorse -- cgit v1.2.1 From 4f1de5faacb6824bad2624b75537e9f4ddbb1207 Mon Sep 17 00:00:00 2001 From: Will Starms Date: Thu, 25 Aug 2016 11:48:08 -0500 Subject: Correct namespace validation to forbid bad names #21077 Adds .git and .atom to the master namespace regex Updates existing group tests and adds two new ones Updates path cleaning to also forbid .atom --- CHANGELOG | 1 + app/models/namespace.rb | 14 ++++++-------- lib/gitlab/regex.rb | 4 ++-- spec/features/groups_spec.rb | 32 +++++++++++++++++++++++++++++++- spec/models/namespace_spec.rb | 1 + 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5e775cec6d4..00db2f2d40f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.13.0 (unreleased) - Allow the Koding integration to be configured through the API - Add new issue button to each list on Issues Board - Added soft wrap button to repository file/blob editor + - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index b7f2b2bbe61..b67049f0f55 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -61,15 +61,13 @@ class Namespace < ActiveRecord::Base def clean_path(path) path = path.dup # Get the email username by removing everything after an `@` sign. - path.gsub!(/@.*\z/, "") - # Usernames can't end in .git, so remove it. - path.gsub!(/\.git\z/, "") - # Remove dashes at the start of the username. - path.gsub!(/\A-+/, "") - # Remove periods at the end of the username. - path.gsub!(/\.+\z/, "") + path.gsub!(/@.*\z/, "") # Remove everything that's not in the list of allowed characters. - path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + # Remove trailing violations ('.atom', '.git', or '.') + path.gsub!(/(\.atom|\.git|\.)*\z/, "") + # Remove leading violations ('-') + path.gsub!(/\A\-+/, "") # Users with the great usernames of "." or ".." would end up with a blank username. # Work around that by setting their username to "blank", followed by a counter. diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 776bbcbb5d0..0d30e1bb92e 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -2,7 +2,7 @@ module Gitlab module Regex extend self - NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])'.freeze + NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])(? Date: Fri, 7 Oct 2016 15:47:54 -0300 Subject: Update Gitlab Shell to fix errors moving projects between storages --- CHANGELOG | 1 + GITLAB_SHELL_VERSION | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5e775cec6d4..6405b0cf23d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.13.0 (unreleased) - Keep refs for each deployment - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) + - Update Gitlab Shell to fix some problems with moving projects between storages - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Simplify Mentionable concern instance methods diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 4a788a01dad..0f44168a4d5 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.6.3 +3.6.4 -- cgit v1.2.1 From 1fe08610518858d86dddb1b118d121a138de3f84 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 7 Oct 2016 17:05:31 -0500 Subject: remove corporate address from email footer and add reference to domain of origin --- app/views/notify/pipeline_failed_email.html.haml | 5 ++++- app/views/notify/pipeline_failed_email.text.erb | 1 + app/views/notify/pipeline_success_email.html.haml | 5 ++++- app/views/notify/pipeline_success_email.text.erb | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 3b3212e675e..a2ef4a92e03 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -173,4 +173,7 @@ %a{:href => profile_notifications_url, :style => "color:#3084bb;text-decoration:none;"} Manage all notifications · %a{:href => help_url, :style => "color:#3084bb;text-decoration:none;"} Help - %div GitLab, 1233 Howard St 2F, San Francisco, CA 94103, USA + %div + You're receiving this email because of your account on + = succeed "." do + %a{:href => root_url, :style => "color:#3084bb;text-decoration:none;"}= Gitlab.config.gitlab.host diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb index 373d5020b28..56379411be4 100644 --- a/app/views/notify/pipeline_failed_email.text.erb +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -26,5 +26,6 @@ Trace: <%= build.trace_with_state(last_lines: 10)[:text] %> <% end -%> +You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>. Manage all notifications: <%= profile_notifications_url %> Help: <%= help_url %> diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index ff9c3059acc..5425b638ac3 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -150,4 +150,7 @@ %a{:href => profile_notifications_url, :style => "color:#3084bb;text-decoration:none;"} Manage all notifications · %a{:href => help_url, :style => "color:#3084bb;text-decoration:none;"} Help - %div GitLab, 1233 Howard St 2F, San Francisco, CA 94103, USA + %div + You're receiving this email because of your account on + = succeed "." do + %a{:href => root_url, :style => "color:#3084bb;text-decoration:none;"}= Gitlab.config.gitlab.host diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index c38a738f523..29b290d9704 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -19,5 +19,6 @@ Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> ) <% stage_count = @pipeline.stages.size -%> Pipeline #<%= @pipeline.id %> ( <%= namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. +You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>. Manage all notifications: <%= profile_notifications_url %> Help: <%= help_url %> -- cgit v1.2.1 From a4d63a91a24780f77a28d122a60545936e8346e9 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 7 Oct 2016 18:30:42 -0500 Subject: prevent pipeline emails from using the normal layout wrapper --- app/mailers/emails/pipelines.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 941ba2643ab..5296e6fc61b 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -19,7 +19,10 @@ module Emails target_branch: @project.default_branch) add_headers - mail(to: to, subject: pipeline_subject(status)) + mail(to: to, subject: pipeline_subject(status)) do |format| + format.html { render layout: false } + format.text + end end def add_headers -- cgit v1.2.1 From 6b50c21140187ddf1ebfde4e958902398a9a02bf Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 8 Oct 2016 00:58:49 -0500 Subject: skip the premailer compilation step since css is already inlined for the pipeline emails --- app/mailers/emails/pipelines.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 5296e6fc61b..903d123705a 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -19,7 +19,7 @@ module Emails target_branch: @project.default_branch) add_headers - mail(to: to, subject: pipeline_subject(status)) do |format| + mail(to: to, subject: pipeline_subject(status), skip_premailer: true) do |format| format.html { render layout: false } format.text end -- cgit v1.2.1 From 66223856012c12b16ed022053af0c82beaa4a14c Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 8 Oct 2016 01:28:11 -0500 Subject: manually generate XHTML doctype since haml :format config cannot be overridden --- app/views/notify/pipeline_failed_email.html.haml | 2 +- app/views/notify/pipeline_success_email.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index a2ef4a92e03..e66ffa82c11 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -1,4 +1,4 @@ -!!! + %html{:lang => "en"} %head %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 5425b638ac3..49153ab33cd 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -1,4 +1,4 @@ -!!! + %html{:lang => "en"} %head %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ -- cgit v1.2.1 From 355ffec76314c0d5688460e51ff22b604333d0bc Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 8 Oct 2016 01:28:17 -0500 Subject: add message subject to the title tag --- app/views/notify/pipeline_failed_email.html.haml | 2 +- app/views/notify/pipeline_success_email.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index e66ffa82c11..220684ccddf 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -4,7 +4,7 @@ %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/ %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/ - %title + %title= message.subject :css /* CLIENT-SPECIFIC STYLES */ body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 49153ab33cd..7ff2d31d1b7 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -4,7 +4,7 @@ %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/ %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/ - %title + %title= message.subject :css /* CLIENT-SPECIFIC STYLES */ body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } -- cgit v1.2.1 From 720968cc8799f665f4f4392e80bf8dfe88fdd69b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 8 Oct 2016 15:31:26 +0800 Subject: Fix tests for subject updates --- spec/services/ci/send_pipeline_notification_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/services/ci/send_pipeline_notification_service_spec.rb b/spec/services/ci/send_pipeline_notification_service_spec.rb index f6cf1039287..6a32357b72c 100644 --- a/spec/services/ci/send_pipeline_notification_service_spec.rb +++ b/spec/services/ci/send_pipeline_notification_service_spec.rb @@ -32,14 +32,14 @@ describe Ci::SendPipelineNotificationService, services: true do context 'with success pipeline' do let(:status) { 'success' } - let(:email_subject) { 'Pipeline succeeded for' } + let(:email_subject) { "Pipeline ##{pipeline.id} has succeeded" } it_behaves_like 'sending emails' end context 'with failed pipeline' do let(:status) { 'failed' } - let(:email_subject) { 'Pipeline failed for' } + let(:email_subject) { "Pipeline ##{pipeline.id} has failed" } it_behaves_like 'sending emails' end -- cgit v1.2.1 From 4695bb8829679a75f66178b2ebf16059d90f6f33 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 8 Oct 2016 05:17:01 -0700 Subject: Use Hash rocket syntax to maintain Ruby 2.1 compatibility in spec --- spec/lib/banzai/object_renderer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index f5ff236105e..90da78a67dd 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -5,7 +5,7 @@ describe Banzai::ObjectRenderer do let(:user) { project.owner } def fake_object(attrs = {}) - object = double(attrs.merge("new_record?": true, "destroyed?": true)) + object = double(attrs.merge("new_record?" => true, "destroyed?" => true)) allow(object).to receive(:markdown_cache_field_for).with(:note).and_return(:note_html) allow(object).to receive(:banzai_render_context).with(:note).and_return(project: nil, author: nil) allow(object).to receive(:update_column).with(:note_html, anything).and_return(true) -- cgit v1.2.1 From e72f2cfcb23c80f3bf634cc339ed2a468d4e9fce Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 8 Oct 2016 09:25:06 -0700 Subject: Fix missing constraints causing route failures when usernames with periods are used Closes #23131 --- config/routes/user.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/routes/user.rb b/config/routes/user.rb index c287039ba26..54bbcb18f6a 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -15,7 +15,10 @@ devise_scope :user do end constraints(UserUrlConstrainer.new) do - scope(path: ':username', as: :user, controller: :users) do + scope(path: ':username', + as: :user, + constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Fri, 9 Sep 2016 11:59:36 -0500 Subject: Remove redundant mixins --- CHANGELOG | 1 + app/assets/stylesheets/framework/avatar.scss | 4 ++-- app/assets/stylesheets/framework/blocks.scss | 2 +- app/assets/stylesheets/framework/buttons.scss | 8 ++++---- app/assets/stylesheets/framework/forms.scss | 2 +- app/assets/stylesheets/framework/issue_box.scss | 2 +- app/assets/stylesheets/framework/markdown_area.scss | 2 +- app/assets/stylesheets/framework/mixins.scss | 11 ----------- app/assets/stylesheets/framework/mobile.scss | 2 +- app/assets/stylesheets/framework/selects.scss | 14 +++++++------- app/assets/stylesheets/framework/sidebar.scss | 6 +++--- app/assets/stylesheets/framework/typography.scss | 2 +- app/assets/stylesheets/pages/cycle_analytics.scss | 2 +- app/assets/stylesheets/pages/editor.scss | 2 +- app/assets/stylesheets/pages/events.scss | 2 +- app/assets/stylesheets/pages/labels.scss | 4 ++-- app/assets/stylesheets/pages/login.scss | 6 +++--- app/assets/stylesheets/pages/merge_requests.scss | 2 +- app/assets/stylesheets/pages/notes.scss | 2 +- app/assets/stylesheets/pages/profiles/preferences.scss | 4 ++-- app/assets/stylesheets/pages/projects.scss | 8 ++++---- app/assets/stylesheets/pages/status.scss | 2 +- 22 files changed, 40 insertions(+), 50 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8bfc5c97820..722731030d0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.13.0 (unreleased) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) + - Remove redundant mixins (ClemMakesApps) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index c79b22d4d21..98e301d3799 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -4,7 +4,7 @@ width: 40px; height: 40px; padding: 0; - @include border-radius($avatar_radius); + border-radius: $avatar_radius; border: 1px solid rgba(0, 0, 0, .1); &.avatar-inline { @@ -17,7 +17,7 @@ } &.avatar-tile { - @include border-radius(0); + border-radius: 0; border: none; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index d315db4cb32..8002e56724b 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -133,7 +133,7 @@ } .identicon { - @include border-radius(50%); + border-radius: 50%; } } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index d11b2fe7ec2..a7c8d782e9b 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -1,5 +1,5 @@ @mixin btn-default { - @include border-radius(3px); + border-radius: 3px; font-size: $gl-font-size; font-weight: 500; padding: $gl-vert-padding $gl-btn-padding; @@ -8,7 +8,7 @@ &:active { outline: none; background-color: $btn-active-gray; - @include box-shadow($gl-btn-active-background); + box-shadow: $gl-btn-active-background; } } @@ -43,7 +43,7 @@ &:active, &.active { - @include box-shadow ($gl-btn-active-background); + box-shadow: $gl-btn-active-background; background-color: $dark; border-color: $border-dark; @@ -279,7 +279,7 @@ } .active { - @include box-shadow($gl-btn-active-background); + box-shadow: $gl-btn-active-background; border: 1px solid #c6cacf !important; background-color: #e4e7ed !important; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index a67d31de2f7..05e8ee0190d 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -73,7 +73,7 @@ label { } .form-control { - @include box-shadow(none); + box-shadow: none; border-radius: 3px; padding: $gl-vert-padding $gl-input-padding; } diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 8bfc0d583c5..ba3930e03bd 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -16,7 +16,7 @@ margin-top: 5px; } - @include border-radius(3px); + border-radius: 3px; display: block; float: left; margin-right: 10px; diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index edea4ad00eb..6d28d98b283 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -86,7 +86,7 @@ } .markdown-area { - @include border-radius(0); + border-radius: 0; background: #fff; border: 1px solid #ddd; min-height: 140px; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1ec08cdef23..7c207969b0a 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -1,14 +1,3 @@ -/** - * Generic mixins - */ -@mixin box-shadow($shadow) { - box-shadow: $shadow; -} - -@mixin border-radius($radius) { - border-radius: $radius; -} - /** * Prefilled mixins * Mixins with fixed values diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 76b93b23b95..9fe390eb09d 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -133,5 +133,5 @@ font-size: 20px; color: #777; z-index: 100; - @include box-shadow(0 1px 2px #ddd); + box-shadow: 0 1px 2px #ddd; } diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index bcd60391543..79cd26714a3 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -46,8 +46,8 @@ } .select2-drop { - @include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0); - @include border-radius ($border-radius-default); + box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0; + border-radius: $border-radius-default; border: none; min-width: 175px; } @@ -72,7 +72,7 @@ .select2-container-active { .select2-choice, .select2-choices { - @include box-shadow(none); + box-shadow: none; } } @@ -82,13 +82,13 @@ outline: 0; background-image: none; background-color: $white-dark; - @include box-shadow($gl-btn-active-gradient); + box-shadow: $gl-btn-active-gradient; } } .select2-container-multi { .select2-choices { - @include border-radius($border-radius-default); + border-radius: $border-radius-default; border-color: $input-border; background: none; @@ -123,7 +123,7 @@ &.select2-container-active .select2-choices, &.select2-dropdown-open .select2-choices { border-color: $border-white-normal; - @include box-shadow($gl-btn-active-gradient); + box-shadow: $gl-btn-active-gradient; } } @@ -157,7 +157,7 @@ background-repeat: no-repeat; background-position: right 0 bottom 6px; border: 1px solid $input-border; - @include border-radius($border-radius-default); + border-radius: $border-radius-default; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; &:focus { diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 557ef7291cf..ec52f326eb9 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -4,7 +4,7 @@ &.page-sidebar-pinned { .sidebar-wrapper { - @include box-shadow(none); + box-shadow: none; } } @@ -17,7 +17,7 @@ width: 0; overflow: hidden; transition: width $sidebar-transition-duration; - @include box-shadow(2px 0 16px 0 $black-transparent); + box-shadow: 2px 0 16px 0 $black-transparent; } } @@ -100,7 +100,7 @@ .count { float: right; padding: 0 8px; - @include border-radius(6px); + border-radius: 6px; } } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 9f2d53d5206..d099a884f54 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -116,7 +116,7 @@ font-size: 13px; line-height: 1.6em; overflow-x: auto; - @include border-radius(2px); + border-radius: 2px; } p > code { diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 778471a34d7..d732008de3d 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -50,7 +50,7 @@ .bordered-box { border: 1px solid $border-color; - @include border-radius($border-radius-default); + border-radius: $border-radius-default; } diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index e1304335271..fcc5f32c738 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -1,7 +1,7 @@ .file-editor { #editor { border: none; - @include border-radius(0); + border-radius: 0; height: 500px; margin: 0; padding: 0; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 1d00da1266c..789d6237df8 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -91,7 +91,7 @@ float: right; border: 1px solid #eee; padding: 5px; - @include border-radius(5px); + border-radius: 5px; background: $gray-light; margin-left: 10px; top: -6px; diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 38c7cd98e41..d35e01f0de3 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -1,7 +1,7 @@ .suggest-colors { margin-top: 5px; a { - @include border-radius(4px); + border-radius: 4px; width: 30px; height: 30px; display: inline-block; @@ -17,7 +17,7 @@ overflow: hidden; a { - @include border-radius(0); + border-radius: 0; width: (100% / 7); margin-right: 0; margin-bottom: -5px; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 403171d4532..a5ca509163d 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -73,12 +73,12 @@ height: auto; &.top { - @include border-radius(5px 5px 0 0); + border-radius: 5px 5px 0 0; margin-bottom: 0; } &.bottom { - @include border-radius(0 0 5px 5px); + border-radius: 0 0 5px 5px; border-top: 0; margin-bottom: 20px; } @@ -86,7 +86,7 @@ &.middle { border-top: 0; margin-bottom: 0; - @include border-radius(0); + border-radius: 0; } &:active, &:focus { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..043f3f3afe1 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -6,7 +6,7 @@ background: $background-color; color: $gl-gray; border: 1px solid $border-color; - @include border-radius(2px); + border-radius: 2px; form { margin-bottom: 0; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 54124a3d658..d399f84a2ff 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -334,7 +334,7 @@ ul.notes { .add-diff-note { margin-top: -4px; - @include border-radius(40px); + border-radius: 40px; background: #fff; padding: 4px; font-size: 16px; diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index e5859fe7384..f8da0983b77 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -4,7 +4,7 @@ text-align: center; .preview { - @include border-radius(4px); + border-radius: 4px; height: 80px; margin-bottom: 10px; @@ -47,7 +47,7 @@ width: 160px; img { - @include border-radius(4px); + border-radius: 4px; max-width: 100%; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 87548dcb590..530fb0c0d05 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -354,7 +354,7 @@ a.deploy-project-label { justify-content: flex-start; .fork-thumbnail { - @include border-radius($border-radius-base); + border-radius: $border-radius-base; background-color: $white-light; border: 1px solid $border-white-light; height: 202px; @@ -371,7 +371,7 @@ a.deploy-project-label { background-color: $gray-light; border: 1px solid $gray-dark; margin: 0 auto; - @include border-radius(50%); + border-radius: 50%; i { font-size: 100px; color: $gray-dark; @@ -390,7 +390,7 @@ a.deploy-project-label { } img { - @include border-radius(50%); + border-radius: 50%; max-width: 100px; } } @@ -496,7 +496,7 @@ pre.light-well { } .light-well { - @include border-radius (2px); + border-radius: 2px; color: #5b6169; font-size: 13px; diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 0ee7ceecae5..c05f3d5ff32 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -4,7 +4,7 @@ margin-right: 10px; border: 1px solid #eee; white-space: nowrap; - @include border-radius(4px); + border-radius: 4px; &:hover { text-decoration: none; -- cgit v1.2.1 From 15433bc5d6a93e55f143dcb34dc3a807f6d4f02b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 15 Sep 2016 11:45:16 -0500 Subject: Fix inconsistent options dropdown caret on mobile viewports --- CHANGELOG | 1 + app/views/projects/issues/show.html.haml | 2 +- app/views/projects/merge_requests/show/_mr_title.html.haml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8bfc5c97820..d9ac4692a7d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.13.0 (unreleased) - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Speed-up group milestones show page + - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index cbdea209847..9eac5e795d7 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -23,8 +23,8 @@ .issuable-actions .clearfix.issue-btn-group.dropdown %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - = icon('caret-down') Options + = icon('caret-down') .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul - if can?(current_user, :create_issue, @project) diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 9f2a0f5d99a..e7c5bca6a37 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -19,8 +19,8 @@ .issuable-actions .clearfix.issue-btn-group.dropdown %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - = icon('caret-down') Options + = icon('caret-down') .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul %li{ class: merge_request_button_visibility(@merge_request, true) } -- cgit v1.2.1 From 04afdb613e2282fc6a2debb301a32dad08427f8a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 8 Oct 2016 20:24:43 +0200 Subject: Improve spec for merge when build succeeds feature --- .../merge_when_build_succeeds_service_spec.rb | 24 +++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 520e906b21f..9a29e400654 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -110,9 +110,21 @@ describe MergeRequests::MergeWhenBuildSucceedsService do context 'properly handles multiple stages' do let(:ref) { mr_merge_if_green_enabled.source_branch } - let!(:build) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') } - let!(:test) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') } - let(:pipeline) { create(:ci_empty_pipeline, ref: mr_merge_if_green_enabled.source_branch, project: project) } + let(:sha) { project.commit(ref).id } + + let(:pipeline) do + create(:ci_empty_pipeline, ref: ref, sha: sha, project: project) + end + + let!(:build) do + create(:ci_build, :created, pipeline: pipeline, ref: ref, + name: 'build', stage: 'build') + end + + let!(:test) do + create(:ci_build, :created, pipeline: pipeline, ref: ref, + name: 'test', stage: 'test') + end before do # This behavior of MergeRequest: we instantiate a new object @@ -121,14 +133,16 @@ describe MergeRequests::MergeWhenBuildSucceedsService do end end - it "doesn't merge if some stages failed" do + it "doesn't merge if any of stages failed" do expect(MergeWorker).not_to receive(:perform_async) + build.success test.drop end - it 'merge when all stages succeeded' do + it 'merges when all stages succeeded' do expect(MergeWorker).to receive(:perform_async) + build.success test.success end -- cgit v1.2.1 From 74fd5cab155cb83ef47ba8f4e78ccfd714f73211 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 8 Oct 2016 20:25:16 +0200 Subject: Improve transitions and run hooks after transaction --- app/models/commit_status.rb | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 38554e7a0ca..81f041cf29e 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -85,33 +85,35 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end + after_transition do |commit_status| + commit_status.run_after_commit do + pipeline.try do |pipeline| + if complete? + ProcessPipelineWorker.perform_async(pipeline.id) + else + UpdatePipelineWorker.perform_async(pipeline.id) + end + end + end + end + after_transition [:created, :pending, :running] => :success do |commit_status| - MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) + commit_status.run_after_commit do + MergeRequests::MergeWhenBuildSucceedsService + .new(pipeline.project, nil).trigger(self) + end end after_transition any => :failed do |commit_status| - MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) + commit_status.run_after_commit do + MergeRequests::AddTodoWhenBuildFailsService + .new(pipeline.project, nil).execute(self) + end end - - after_transition do: :schedule_pipeline_update end delegate :sha, :short_sha, to: :pipeline - def schedule_pipeline_update - run_after_commit(:process_pipeline!) - end - - def process_pipeline! - pipeline.try do |pipeline| - if complete? - ProcessPipelineWorker.perform_async(pipeline.id) - else - UpdatePipelineWorker.perform_async(pipeline.id) - end - end - end - def before_sha pipeline.before_sha || Gitlab::Git::BLANK_SHA end -- cgit v1.2.1 From 3fb4d86c6dbb2298f6d5b0010ae0f6e26905b934 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 8 Oct 2016 20:34:45 +0200 Subject: Add temporary fix for race condition in MWBS --- app/models/commit_status.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 81f041cf29e..c8b168f5526 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -99,6 +99,9 @@ class CommitStatus < ActiveRecord::Base after_transition [:created, :pending, :running] => :success do |commit_status| commit_status.run_after_commit do + # TODO, temporary fix for race condition + UpdatePipelineWorker.new.perform(pipeline.id) + MergeRequests::MergeWhenBuildSucceedsService .new(pipeline.project, nil).trigger(self) end -- cgit v1.2.1 From 904de2d64b1b63a82cfba92e02b6c25ff94b725b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 8 Oct 2016 20:42:09 +0200 Subject: Check for transition loopback in commit status --- app/models/commit_status.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index c8b168f5526..5d6d534cd31 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -85,7 +85,9 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - after_transition do |commit_status| + after_transition do |commit_status, transition| + return if transition.loopback? + commit_status.run_after_commit do pipeline.try do |pipeline| if complete? -- cgit v1.2.1 From ef7f16faaa2e640d7b5453174c1af72decd08217 Mon Sep 17 00:00:00 2001 From: Lemures Lemniscati Date: Sun, 9 Oct 2016 22:42:11 +0900 Subject: Fix a typo in doc/api/labels.md --- CHANGELOG | 1 + doc/api/labels.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..07f2e4ee1f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -77,6 +77,7 @@ v 8.13.0 (unreleased) - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container + - Fix a typo in doc/api/labels.md v 8.12.5 (unreleased) diff --git a/doc/api/labels.md b/doc/api/labels.md index 3653ccf304a..656232cc940 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -148,7 +148,7 @@ PUT /projects/:id/labels | --------------- | ------- | --------------------------------- | ------------------------------- | | `id` | integer | yes | The ID of the project | | `name` | string | yes | The name of the existing label | -| `new_name` | string | yes if `color` if not provided | The new name of the label | +| `new_name` | string | yes if `color` is not provided | The new name of the label | | `color` | string | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign | | `description` | string | no | The new description of the label | -- cgit v1.2.1 From 90b0162c3b070a7e1fa88e5b0ebaad66e94d3fca Mon Sep 17 00:00:00 2001 From: Greg Laubenstein Date: Fri, 23 Sep 2016 12:29:32 -0500 Subject: reword html titles for merge requests and issues --- CHANGELOG | 1 + app/views/projects/issues/edit.html.haml | 2 +- app/views/projects/issues/show.html.haml | 2 +- app/views/projects/merge_requests/_show.html.haml | 2 +- app/views/projects/merge_requests/edit.html.haml | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..6b19e9ecf1e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -77,6 +77,7 @@ v 8.13.0 (unreleased) - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container + - Cleanup issue and merge request page titles (Greg Laubenstein) v 8.12.5 (unreleased) diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml index 7cf1923456e..3a6fbbc7fbc 100644 --- a/app/views/projects/issues/edit.html.haml +++ b/app/views/projects/issues/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues" +- page_title "Edit", "#{@issue.to_reference} #{@issue.title}", "Issues" %h3.page-title Edit Issue ##{@issue.iid} diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index b94d6f8633c..e8c673d48bd 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@issue.title} (##{@issue.iid})", "Issues" +- page_title "#{@issue.to_reference} #{@issue.title}", "Issues" - page_description @issue.description - page_card_attributes @issue.card_attributes diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 46a2d862c91..47dd51639b5 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" +- page_title "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests" - page_description @merge_request.description - page_card_attributes @merge_request.card_attributes - content_for :page_specific_javascripts do diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml index 03159f123f3..7c3ac6652ee 100644 --- a/app/views/projects/merge_requests/edit.html.haml +++ b/app/views/projects/merge_requests/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" +- page_title "Edit", "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests" %h3.page-title Edit Merge Request #{@merge_request.to_reference} -- cgit v1.2.1 From 40fa1b6e6fa13c859351a6c8b6978d7ca3a4cf58 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Sun, 9 Oct 2016 20:31:28 +0500 Subject: Use user from let instead recreate in before --- spec/controllers/projects/blob_controller_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 9444a50b1ce..52d13fb6f9e 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -5,7 +5,6 @@ describe Projects::BlobController do let(:user) { create(:user) } before do - user = create(:user) project.team << [user, :master] sign_in(user) -- cgit v1.2.1 From 2de8fc3e13b64dfc50e872486aad0c33b35df8a6 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 19 Sep 2016 12:31:51 +0100 Subject: removes inconsistency regarding tagging immediately as merged once you create a branch using new branch button and adds changelog entry --- CHANGELOG | 2 ++ app/models/repository.rb | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..841861293c8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -119,6 +119,8 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 +v 8.12.0 (unreleased) + - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region diff --git a/app/models/repository.rb b/app/models/repository.rb index bf59b74495b..1bf6e58b9db 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1013,13 +1013,17 @@ class Repository branch_commit = commit(branch_name) root_ref_commit = commit(root_ref) - if branch_commit + if branch_commit && !same_head?(branch_commit.id, root_ref_commit.id) is_ancestor?(branch_commit.id, root_ref_commit.id) else nil end end + def same_head?(first_commit_id, second_commit_id) + first_commit_id == second_commit_id + end + def merge_base(first_commit_id, second_commit_id) first_commit_id = commit(first_commit_id).try(:id) || first_commit_id second_commit_id = commit(second_commit_id).try(:id) || second_commit_id -- cgit v1.2.1 From ff076d88df70a70f6534faefefdca92b059318bf Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Tue, 27 Sep 2016 16:39:29 +0100 Subject: writes tests to verify the issue is solved and fixes breaking issues. --- spec/models/repository_spec.rb | 16 +++++++++++----- spec/support/test_env.rb | 4 +++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 98c64c079b9..6dd3f91be17 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -115,10 +115,16 @@ describe Repository, models: true do describe '#merged_to_root_ref?' do context 'merged branch' do - subject { repository.merged_to_root_ref?('improve/awesome') } + subject { repository.merged_to_root_ref?('branch-merged') } it { is_expected.to be_truthy } end + + context 'not merged branch' do + subject { repository.merged_to_root_ref?('not-merged-branch') } + + it { is_expected.to be_falsey } + end end describe '#can_be_merged?' do @@ -316,7 +322,7 @@ describe Repository, models: true do subject { results.first } it { is_expected.to be_an String } - it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") } + it { expect(subject.lines[2]).to eq("master:CHANGELOG:190: - Feature: Replace teams with group membership\n") } end end @@ -960,10 +966,10 @@ describe Repository, models: true do context 'cherry-picking a merge commit' do it 'cherry-picks the changes' do - expect(repository.blob_at_branch('master', 'foo/bar/.gitkeep')).to be_nil + expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).to be_nil - repository.cherry_pick(user, pickable_merge, 'master') - expect(repository.blob_at_branch('master', 'foo/bar/.gitkeep')).not_to be_nil + repository.cherry_pick(user, pickable_merge, 'improve/awesome') + expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).not_to be_nil end end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 0097dbf8fad..d56274d0979 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -5,6 +5,8 @@ module TestEnv # When developing the seed repository, comment out the branch you will modify. BRANCH_SHA = { + 'not-merged-branch' => 'b83d6e3', + 'branch-merged' => '498214d', 'empty-branch' => '7efb185', 'ends-with.json' => '98b0d8b', 'flatten-dir' => 'e56497b', @@ -14,7 +16,7 @@ module TestEnv 'improve/awesome' => '5937ac0', 'markdown' => '0ed8c6c', 'lfs' => 'be93687', - 'master' => '5937ac0', + 'master' => 'b83d6e3', "'test'" => 'e56497b', 'orphaned-branch' => '45127a9', 'binary-encoding' => '7b1cf43', -- cgit v1.2.1 From 1cf2b9a6b8ff2359f7fad099dcda5632289854a6 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Mon, 10 Oct 2016 10:11:46 +0200 Subject: Make searching for commits case insensitive. Fixes #21800. --- CHANGELOG | 1 + app/models/repository.rb | 6 ++++-- spec/models/repository_spec.rb | 18 +++++++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..38677024a50 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -64,6 +64,7 @@ v 8.13.0 (unreleased) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile - Fix deploy status responsiveness error !6633 + - Make searching for commits case insensitive - Fix resolved discussion display in side-by-side diff view !6575 - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) diff --git a/app/models/repository.rb b/app/models/repository.rb index bf59b74495b..4da1933c189 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -111,8 +111,10 @@ class Repository def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0) ref ||= root_ref - # Limited to 1000 commits for now, could be parameterized? - args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query}) + args = %W( + #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} + --max-count #{limit} --grep=#{query} --regexp-ignore-case + ) args = args.concat(%W(-- #{path})) if path.present? git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 98c64c079b9..4641f297465 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -97,12 +97,20 @@ describe Repository, models: true do end describe '#find_commits_by_message' do - subject { repository.find_commits_by_message('submodule').map{ |k| k.id } } + it 'returns commits with messages containing a given string' do + commit_ids = repository.find_commits_by_message('submodule').map(&:id) - it { is_expected.to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } - it { is_expected.to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } - it { is_expected.to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') } - it { is_expected.not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') } + expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + expect(commit_ids).to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + expect(commit_ids).to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') + expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') + end + + it 'is case insensitive' do + commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id) + + expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + end end describe '#blob_at' do -- cgit v1.2.1 From f3c55164d21b64de13a75a9004b48005cc7c901a Mon Sep 17 00:00:00 2001 From: Brennan Roberts Date: Sun, 2 Oct 2016 15:11:08 -0700 Subject: Prevent conflict b/w search field and its dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop the global search form's default "action" from fighting with dropdown items when using the keyboard to navigate the dropdown. `e.preventDefault()` is now called on the enter key when a dropdown item is already selected. Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/assets/javascripts/gl_dropdown.js | 1 + spec/javascripts/search_autocomplete_spec.js | 21 ++++++++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..11c75f064eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -63,6 +63,7 @@ v 8.13.0 (unreleased) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile + - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - Fix deploy status responsiveness error !6633 - Fix resolved discussion display in side-by-side diff view !6575 - Optimize GitHub importing for speed and memory diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index d4403375643..e034ca68645 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -738,6 +738,7 @@ return false; } if (currentKeyCode === 13 && currentIndex !== -1) { + e.preventDefault(); _this.selectRowAtIndex(); } }; diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 4470fbcb099..333128782a2 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -5,6 +5,8 @@ /*= require lib/utils/common_utils */ /*= require lib/utils/type_utility */ /*= require fuzzaldrin-plus */ +/*= require turbolinks */ +/*= require jquery.turbolinks */ (function() { var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget; @@ -138,7 +140,7 @@ list = widget.wrap.find('.dropdown-menu').find('ul'); return assertLinks(list, projectIssuesPath, projectMRsPath); }); - return it('should not show category related menu if there is text in the input', function() { + it('should not show category related menu if there is text in the input', function() { var link, list; addBodyAttributes('project'); mockProjectOptions(); @@ -148,6 +150,23 @@ link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']"; return expect(list.find(link).length).toBe(0); }); + return it('should not submit the search form when selecting an autocomplete row with the keyboard', function() { + var ENTER = 13; + var DOWN = 40; + addBodyAttributes(); + mockDashboardOptions(true); + var submitSpy = spyOnEvent('form', 'submit'); + widget.searchInput.focus(); + widget.wrap.trigger($.Event('keydown', { which: DOWN })); + var enterKeyEvent = $.Event('keydown', { which: ENTER }); + widget.searchInput.trigger(enterKeyEvent); + // This does not currently catch failing behavior. For security reasons, + // browsers will not trigger default behavior (form submit, in this + // example) on JavaScript-created keypresses. + expect(submitSpy).not.toHaveBeenTriggered(); + // Does a worse job at capturing the intent of the test, but works. + expect(enterKeyEvent.isDefaultPrevented()).toBe(true); + }); }); }).call(this); -- cgit v1.2.1 From ed24cf05fa5a3f1d6e4a2aa2740c25ee029e50a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 10 Oct 2016 10:41:52 +0200 Subject: Fix wrong icon in CI build detail sidebar: right-arrow => arrow-right MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/views/projects/builds/_sidebar.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index f5344091cae..966633f1f89 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -128,7 +128,7 @@ - builds.select{|build| build.status == build_status}.each do |build| .build-job{class: ('active' if build == @build), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do - = icon('right-arrow') + = icon('arrow-right') = ci_icon_for_status(build.status) %span - if build.name -- cgit v1.2.1 From 8592e992a0da4b966f9407ffc91f88ecd7fd750c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 10 Oct 2016 09:59:35 +0100 Subject: Added copy file path button to diffs Closes #23108 --- CHANGELOG | 1 + app/helpers/button_helper.rb | 3 ++- app/views/projects/diffs/_file.html.haml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..45a554c3749 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.13.0 (unreleased) - Revert "Label list shows all issues (opened or closed) with that label" - Expose expires_at field when sharing project on API - Fix VueJS template tags being rendered in code comments + - Added copy file path button to merge request diff files - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - Add Issue Board API support (andrebsguedes) - Allow the Koding integration to be configured through the API diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index b478580978b..a695aceea76 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -15,10 +15,11 @@ module ButtonHelper # # See http://clipboardjs.com/#usage def clipboard_button(data = {}) + css_class = data[:class] || 'btn-clipboard' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), - class: "btn btn-clipboard", + class: "btn #{css_class}", data: data, type: :button, title: "Copy to Clipboard" diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index d07de45fdde..257e0a855bd 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -8,7 +8,7 @@ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = icon('comment') \ - + = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-file-option') - if editable_diff?(diff_file) - link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {} = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, -- cgit v1.2.1 From 237c8f66e6608420629503280aaea555ee980022 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 7 Oct 2016 15:24:09 +0200 Subject: Precalculate trending projects This commit introduces a Sidekiq worker that precalculates the list of trending projects on a daily basis. The resulting set is stored in a database table that is then queried by Project.trending. This setup means that Unicorn workers no longer _may_ have to calculate the list of trending projects. Furthermore it supports filtering without any complex caching mechanisms. The data in the "trending_projects" table is inserted in the same order as the project ranking. This means that getting the projects in the correct order is simply a matter of: SELECT projects.* FROM projects INNER JOIN trending_projects ON trending_projects.project_id = projects.id ORDER BY trending_projects.id ASC; Such a query will only take a few milliseconds at most (as measured on GitLab.com), opposed to a few seconds for the query used for calculating the project ranks. The migration in this commit does not require downtime and takes care of populating an initial list of trending projects. --- app/controllers/explore/projects_controller.rb | 3 +- app/finders/trending_projects_finder.rb | 16 ------- app/models/project.rb | 16 ++----- app/models/trending_project.rb | 35 ++++++++++++++ app/workers/trending_projects_worker.rb | 11 +++++ config/initializers/1_settings.rb | 4 ++ ...0161007133303_precalculate_trending_projects.rb | 38 +++++++++++++++ db/schema.rb | 9 +++- spec/finders/trending_projects_finder_spec.rb | 48 ------------------- spec/models/project_spec.rb | 26 ++-------- spec/models/trending_project_spec.rb | 56 ++++++++++++++++++++++ spec/workers/trending_projects_worker_spec.rb | 11 +++++ 12 files changed, 171 insertions(+), 102 deletions(-) delete mode 100644 app/finders/trending_projects_finder.rb create mode 100644 app/models/trending_project.rb create mode 100644 app/workers/trending_projects_worker.rb create mode 100644 db/migrate/20161007133303_precalculate_trending_projects.rb delete mode 100644 spec/finders/trending_projects_finder_spec.rb create mode 100644 spec/models/trending_project_spec.rb create mode 100644 spec/workers/trending_projects_worker_spec.rb diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 38e5943eb76..a62c6211372 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -21,8 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController end def trending - @projects = TrendingProjectsFinder.new.execute - @projects = filter_projects(@projects) + @projects = filter_projects(Project.trending) @projects = @projects.page(params[:page]) respond_to do |format| diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb deleted file mode 100644 index c1e434d9926..00000000000 --- a/app/finders/trending_projects_finder.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Finder for retrieving public trending projects in a given time range. -class TrendingProjectsFinder - # current_user - The currently logged in User, if any. - # last_months - The number of months to limit the trending data to. - def execute(months_limit = 1) - Rails.cache.fetch(cache_key_for(months_limit), expires_in: 1.day) do - Project.public_only.trending(months_limit.months.ago) - end - end - - private - - def cache_key_for(months) - "trending_projects/#{months}" - end -end diff --git a/app/models/project.rb b/app/models/project.rb index 88e4bd14860..74d54e69648 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -375,19 +375,9 @@ class Project < ActiveRecord::Base %r{(?#{name_pattern}/#{name_pattern})} end - def trending(since = 1.month.ago) - # By counting in the JOIN we don't expose the GROUP BY to the outer query. - # This means that calls such as "any?" and "count" just return a number of - # the total count, instead of the counts grouped per project as a Hash. - join_body = "INNER JOIN ( - SELECT project_id, COUNT(*) AS amount - FROM notes - WHERE created_at >= #{sanitize(since)} - AND system IS FALSE - GROUP BY project_id - ) join_note_counts ON projects.id = join_note_counts.project_id" - - joins(join_body).reorder('join_note_counts.amount DESC') + def trending + joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id'). + reorder('trending_projects.id ASC') end def cached_count diff --git a/app/models/trending_project.rb b/app/models/trending_project.rb new file mode 100644 index 00000000000..27e3732da17 --- /dev/null +++ b/app/models/trending_project.rb @@ -0,0 +1,35 @@ +class TrendingProject < ActiveRecord::Base + belongs_to :project + + # The number of months to include in the trending calculation. + MONTHS_TO_INCLUDE = 1 + + # The maximum number of projects to include in the trending set. + PROJECTS_LIMIT = 100 + + # Populates the trending projects table with the current list of trending + # projects. + def self.refresh! + # The calculation **must** run in a transaction. If the removal of data and + # insertion of new data were to run separately a user might end up with an + # empty list of trending projects for a short period of time. + transaction do + delete_all + + timestamp = connection.quote(MONTHS_TO_INCLUDE.months.ago) + + connection.execute <<-EOF.strip_heredoc + INSERT INTO #{table_name} (project_id) + SELECT project_id + FROM notes + INNER JOIN projects ON projects.id = notes.project_id + WHERE notes.created_at >= #{timestamp} + AND notes.system IS FALSE + AND projects.visibility_level = #{Gitlab::VisibilityLevel::PUBLIC} + GROUP BY project_id + ORDER BY count(*) DESC + LIMIT #{PROJECTS_LIMIT}; + EOF + end + end +end diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb new file mode 100644 index 00000000000..df4c4a6628b --- /dev/null +++ b/app/workers/trending_projects_worker.rb @@ -0,0 +1,11 @@ +class TrendingProjectsWorker + include Sidekiq::Worker + + sidekiq_options queue: :trending_projects + + def perform + Rails.logger.info('Refreshing trending projects') + + TrendingProject.refresh! + end +end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index c5ed2162c92..efe0ac9c965 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -304,6 +304,10 @@ Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '* */6 * * *' Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker' +Settings.cron_jobs['trending_projects_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['trending_projects_worker']['cron'] = '0 1 * * *' +Settings.cron_jobs['trending_projects_worker']['job_class'] = 'TrendingProjectsWorker' + # # GitLab Shell # diff --git a/db/migrate/20161007133303_precalculate_trending_projects.rb b/db/migrate/20161007133303_precalculate_trending_projects.rb new file mode 100644 index 00000000000..b324cd94268 --- /dev/null +++ b/db/migrate/20161007133303_precalculate_trending_projects.rb @@ -0,0 +1,38 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class PrecalculateTrendingProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + create_table :trending_projects do |t| + t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: false + end + + timestamp = connection.quote(1.month.ago) + + # We're hardcoding the visibility level (public) here so that if it ever + # changes this query doesn't suddenly use the new value (which may break + # later migrations). + visibility = 20 + + execute <<-EOF.strip_heredoc + INSERT INTO trending_projects (project_id) + SELECT project_id + FROM notes + INNER JOIN projects ON projects.id = notes.project_id + WHERE notes.created_at >= #{timestamp} + AND notes.system IS FALSE + AND projects.visibility_level = #{visibility} + GROUP BY project_id + ORDER BY count(*) DESC + LIMIT 100; + EOF + end + + def down + drop_table :trending_projects + end +end diff --git a/db/schema.rb b/db/schema.rb index 56da70b3c02..c5ddf9eee32 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160926145521) do +ActiveRecord::Schema.define(version: 20161007133303) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1070,6 +1070,12 @@ ActiveRecord::Schema.define(version: 20160926145521) do add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree + create_table "trending_projects", force: :cascade do |t| + t.integer "project_id", null: false + end + + add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree + create_table "u2f_registrations", force: :cascade do |t| t.text "certificate" t.string "key_handle" @@ -1212,5 +1218,6 @@ ActiveRecord::Schema.define(version: 20160926145521) do add_foreign_key "personal_access_tokens", "users" add_foreign_key "protected_branch_merge_access_levels", "protected_branches" add_foreign_key "protected_branch_push_access_levels", "protected_branches" + add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" end diff --git a/spec/finders/trending_projects_finder_spec.rb b/spec/finders/trending_projects_finder_spec.rb deleted file mode 100644 index cfe15b9defa..00000000000 --- a/spec/finders/trending_projects_finder_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -describe TrendingProjectsFinder do - let(:user) { create(:user) } - let(:public_project1) { create(:empty_project, :public) } - let(:public_project2) { create(:empty_project, :public) } - let(:private_project) { create(:empty_project, :private) } - let(:internal_project) { create(:empty_project, :internal) } - - before do - 3.times do - create(:note_on_commit, project: public_project1) - end - - 2.times do - create(:note_on_commit, project: public_project2, created_at: 5.weeks.ago) - end - - create(:note_on_commit, project: private_project) - create(:note_on_commit, project: internal_project) - end - - describe '#execute', caching: true do - context 'without an explicit time range' do - it 'returns public trending projects' do - projects = described_class.new.execute - - expect(projects).to eq([public_project1]) - end - end - - context 'with an explicit time range' do - it 'returns public trending projects' do - projects = described_class.new.execute(2) - - expect(projects).to eq([public_project1, public_project2]) - end - end - - it 'caches the list of projects' do - projects = described_class.new - - expect(Project).to receive(:trending).once - - 2.times { projects.execute } - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8aadfcb439b..dae546a0cdc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -800,32 +800,14 @@ describe Project, models: true do end create(:note_on_commit, project: project2) - end - - describe 'without an explicit start date' do - subject { described_class.trending.to_a } - it 'sorts Projects by the amount of notes in descending order' do - expect(subject).to eq([project1, project2]) - end + TrendingProject.refresh! end - describe 'with an explicit start date' do - let(:date) { 2.months.ago } + subject { described_class.trending.to_a } - subject { described_class.trending(date).to_a } - - before do - 2.times do - # Little fix for special issue related to Fractional Seconds support for MySQL. - # See: https://github.com/rails/rails/pull/14359/files - create(:note_on_commit, project: project2, created_at: date + 1) - end - end - - it 'sorts Projects by the amount of notes in descending order' do - expect(subject).to eq([project2, project1]) - end + it 'sorts projects by the amount of notes in descending order' do + expect(subject).to eq([project1, project2]) end it 'does not take system notes into account' do diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb new file mode 100644 index 00000000000..cc28c6d4004 --- /dev/null +++ b/spec/models/trending_project_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe TrendingProject do + let(:user) { create(:user) } + let(:public_project1) { create(:empty_project, :public) } + let(:public_project2) { create(:empty_project, :public) } + let(:public_project3) { create(:empty_project, :public) } + let(:private_project) { create(:empty_project, :private) } + let(:internal_project) { create(:empty_project, :internal) } + + before do + 3.times do + create(:note_on_commit, project: public_project1) + end + + 2.times do + create(:note_on_commit, project: public_project2) + end + + create(:note_on_commit, project: public_project3, created_at: 5.weeks.ago) + create(:note_on_commit, project: private_project) + create(:note_on_commit, project: internal_project) + end + + describe '.refresh!' do + before do + described_class.refresh! + end + + it 'populates the trending projects table' do + expect(described_class.count).to eq(2) + end + + it 'removes existing rows before populating the table' do + described_class.refresh! + + expect(described_class.count).to eq(2) + end + + it 'stores the project IDs for every trending project' do + rows = described_class.order(id: :asc).all + + expect(rows[0].project_id).to eq(public_project1.id) + expect(rows[1].project_id).to eq(public_project2.id) + end + + it 'does not store projects that fall out of the trending time range' do + expect(described_class.where(project_id: public_project3).any?).to eq(false) + end + + it 'stores only public projects' do + expect(described_class.where(project_id: [public_project1.id, public_project2.id]).count).to eq(2) + expect(described_class.where(project_id: [private_project.id, internal_project.id]).count).to eq(0) + end + end +end diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb new file mode 100644 index 00000000000..c3c6fdcf2d5 --- /dev/null +++ b/spec/workers/trending_projects_worker_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe TrendingProjectsWorker do + describe '#perform' do + it 'refreshes the trending projects' do + expect(TrendingProject).to receive(:refresh!) + + described_class.new.perform + end + end +end -- cgit v1.2.1 From 33ce1976451ee8fc0275e0ae012755053d50ec34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 10 Oct 2016 13:35:26 +0200 Subject: API: New /users/:id/events endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + doc/api/users.md | 146 ++++++++++++++++++++++++++++++++++++++++ lib/api/users.rb | 20 ++++++ spec/requests/api/users_spec.rb | 55 +++++++++++++++ 4 files changed, 222 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e6cdc09b9c6..4c257b36175 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.13.0 (unreleased) - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - Add broadcast messages and alerts below sub-nav - Better empty state for Groups view + - API: New /users/:id/events endpoint - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 diff --git a/doc/api/users.md b/doc/api/users.md index 9be4f2e6ec3..15202010b7b 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -627,3 +627,149 @@ Parameters: Will return `200 OK` on success, `404 User Not Found` is user cannot be found or `403 Forbidden` when trying to unblock a user blocked by LDAP synchronization. + +### Get user contribution events + +Get the contribution events for the specified user, sorted from newest to latest. + +``` +GET /users/:id/events +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the user | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/user/:id/events +``` + +Example response: + +```json +[ + { + "title": null, + "project_id": 15, + "action_name": "closed", + "target_id": 830, + "target_type": "Issue", + "author_id": 1, + "data": null, + "target_title": "Public project search field", + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "root" + }, + { + "title": null, + "project_id": 15, + "action_name": "opened", + "target_id": null, + "target_type": null, + "author_id": 1, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "john", + "data": { + "before": "50d4420237a9de7be1304607147aec22e4a14af7", + "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", + "ref": "refs/heads/master", + "user_id": 1, + "user_name": "Dmitriy Zaporozhets", + "repository": { + "name": "gitlabhq", + "url": "git@dev.gitlab.org:gitlab/gitlabhq.git", + "description": "GitLab: self hosted Git management software. \r\nDistributed under the MIT License.", + "homepage": "https://dev.gitlab.org/gitlab/gitlabhq" + }, + "commits": [ + { + "id": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", + "message": "Add simple search to projects in public area", + "timestamp": "2013-05-13T18:18:08+00:00", + "url": "https://dev.gitlab.org/gitlab/gitlabhq/commit/c5feabde2d8cd023215af4d2ceeb7a64839fc428", + "author": { + "name": "Dmitriy Zaporozhets", + "email": "dmitriy.zaporozhets@gmail.com" + } + } + ], + "total_commits_count": 1 + }, + "target_title": null + }, + { + "title": null, + "project_id": 15, + "action_name": "closed", + "target_id": 840, + "target_type": "Issue", + "author_id": 1, + "data": null, + "target_title": "Finish & merge Code search PR", + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "root" + }, + { + "title": null, + "project_id": 15, + "action_name": "commented on", + "target_id": 1312, + "target_type": "Note", + "author_id": 1, + "data": null, + "target_title": null, + "created_at": "2015-12-04T10:33:58.089Z", + "note": { + "id": 1312, + "body": "What an awesome day!", + "attachment": null, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "created_at": "2015-12-04T10:33:56.698Z", + "system": false, + "upvote": false, + "downvote": false, + "noteable_id": 377, + "noteable_type": "Issue" + }, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "root" + } +] +``` diff --git a/lib/api/users.rb b/lib/api/users.rb index 18c4cad09ae..e868f628404 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -321,6 +321,26 @@ module API user.activate end end + + desc 'Get contribution events of a specified user' do + detail 'This feature was introduced in GitLab 8.13.' + success Entities::Event + end + params do + requires :id, type: String, desc: 'The user ID' + end + get ':id/events' do + user = User.find_by(id: declared(params).id) + not_found!('User') unless user + + events = user.recent_events. + merge(ProjectsFinder.new.execute(current_user)). + references(:project). + with_associations. + page(params[:page]) + + present paginate(events), with: Entities::Event + end end resource :user do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f4ea3bebb4c..56b2ec6271e 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -895,4 +895,59 @@ describe API::API, api: true do expect{put api("/users/ASDF/block", admin) }.to raise_error(ActionController::RoutingError) end end + + describe 'GET /user/:id/events' do + let(:user) { create(:user) } + let(:lambda_user) { create(:user) } + let(:project) { create(:empty_project) } + let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) } + + before do + project.add_user(user, :developer) + EventCreateService.new.leave_note(note, user) + end + + context "as a user than cannot see the event's project" do + it 'returns no events' do + get api("/users/#{user.id}/events", lambda_user) + + expect(response).to have_http_status(200) + expect(json_response).to be_empty + end + end + + context "as a user than can see the event's project" do + it_behaves_like 'a paginated resources' do + let(:request) { get api("/users/#{user.id}/events", user) } + end + + context 'joined event' do + it 'returns the "joined" event' do + get api("/users/#{user.id}/events", user) + + first_event = json_response.first + + expect(first_event['action_name']).to eq('commented on') + expect(first_event['project_id'].to_i).to eq(project.id) + expect(first_event['author_username']).to eq(user.username) + expect(first_event['note']['id']).to eq(note.id) + expect(first_event['note']['body']).to eq('What an awesome day!') + + last_event = json_response.last + + expect(last_event['action_name']).to eq('joined') + expect(last_event['project_id'].to_i).to eq(project.id) + expect(last_event['author_username']).to eq(user.username) + expect(last_event['author']['name']).to eq(user.name) + end + end + end + + it 'returns a 404 error if not found' do + get api('/users/42/events', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 User Not Found') + end + end end -- cgit v1.2.1 From 517895da4c1ca6201f952e443a579e4f2845e6e0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 10 Oct 2016 13:13:07 +0100 Subject: Don't run affix tabs in test env This was messing up other tests --- app/views/projects/merge_requests/_show.html.haml | 2 +- spec/features/merge_requests/sticky_tabs_spec.rb | 33 ----------------------- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 spec/features/merge_requests/sticky_tabs_spec.rb diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 351c9d6ff91..72ad96aa217 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -47,7 +47,7 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits_count.nonzero? - %ul.merge-request-tabs.nav-links.no-top.no-bottom.js-tabs-affix + %ul.merge-request-tabs.nav-links.no-top.no-bottom{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion diff --git a/spec/features/merge_requests/sticky_tabs_spec.rb b/spec/features/merge_requests/sticky_tabs_spec.rb deleted file mode 100644 index 3e8bd768324..00000000000 --- a/spec/features/merge_requests/sticky_tabs_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'spec_helper' - -feature 'Merge request tabs', js: true, feature: true do - include WaitForAjax - - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } - - before do - project.team << [user, :master] - login_as user - visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) - - wait_for_ajax - end - - it 'affixes to top of page when scrolling' do - page.execute_script "window.scroll(0,10000)" - - expect(page).to have_selector('.js-tabs-affix.affix') - end - - it 'removes affix when scrolling to top' do - page.execute_script "window.scroll(0,10000)" - - expect(page).to have_selector('.js-tabs-affix.affix') - - page.execute_script "window.scroll(0,-10000)" - - expect(page).to have_selector('.js-tabs-affix.affix-top') - end -end -- cgit v1.2.1 From 7336e73b69040ecb6e8f474d029c8b741090efb2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 10 Oct 2016 15:22:09 +0200 Subject: Add link to test coverage report to README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8236f986b56..a6b30aff5a0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # GitLab -[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) -[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) +[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) +[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) -- cgit v1.2.1 From 66c32cab1af621caa6ae3cb24b82b344d43512a5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 14:17:06 +0300 Subject: Remove NamespacesController The main purpose of this controller was redirect to group or user page when URL like https://gitlab.com/gitlab-org was used. Now this functionality is handled by contrainers and take user to correct controller right from the start Signed-off-by: Dmitriy Zaporozhets --- app/controllers/namespaces_controller.rb | 25 ------ config/routes.rb | 2 - spec/controllers/namespaces_controller_spec.rb | 118 ------------------------- 3 files changed, 145 deletions(-) delete mode 100644 app/controllers/namespaces_controller.rb delete mode 100644 spec/controllers/namespaces_controller_spec.rb diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb deleted file mode 100644 index 83eec1bf4a2..00000000000 --- a/app/controllers/namespaces_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -class NamespacesController < ApplicationController - skip_before_action :authenticate_user! - - def show - namespace = Namespace.find_by(path: params[:id]) - - if namespace - if namespace.is_a?(Group) - group = namespace - else - user = namespace.owner - end - end - - if user - redirect_to user_path(user) - elsif group && can?(current_user, :read_group, group) - redirect_to group_path(group) - elsif current_user.nil? - authenticate_user! - else - render_404 - end - end -end diff --git a/config/routes.rb b/config/routes.rb index bf7c5b76128..83c3a42c19f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -87,7 +87,5 @@ Rails.application.routes.draw do # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } - root to: "root#index" end diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb deleted file mode 100644 index 2b334ed1172..00000000000 --- a/spec/controllers/namespaces_controller_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -require 'spec_helper' - -describe NamespacesController do - let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } - - describe "GET show" do - context "when the namespace belongs to a user" do - let!(:other_user) { create(:user) } - - it "redirects to the user's page" do - get :show, id: other_user.username - - expect(response).to redirect_to(user_path(other_user)) - end - end - - context "when the namespace belongs to a group" do - let!(:group) { create(:group) } - - context "when the group is public" do - context "when not signed in" do - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - - context "when signed in" do - before do - sign_in(user) - end - - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - end - - context "when the group is private" do - before do - group.update_attribute(:visibility_level, Group::PRIVATE) - end - - context "when not signed in" do - it "redirects to the sign in page" do - get :show, id: group.path - expect(response).to redirect_to(new_user_session_path) - end - end - - context "when signed in" do - before do - sign_in(user) - end - - context "when the user has access to the group" do - before do - group.add_developer(user) - end - - context "when the user is blocked" do - before do - user.block - end - - it "redirects to the sign in page" do - get :show, id: group.path - - expect(response).to redirect_to(new_user_session_path) - end - end - - context "when the user isn't blocked" do - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - end - - context "when the user doesn't have access to the group" do - it "responds with status 404" do - get :show, id: group.path - - expect(response).to have_http_status(404) - end - end - end - end - end - - context "when the namespace doesn't exist" do - context "when signed in" do - before do - sign_in(user) - end - - it "responds with status 404" do - get :show, id: "doesntexist" - - expect(response).to have_http_status(404) - end - end - - context "when not signed in" do - it "redirects to the sign in page" do - get :show, id: "doesntexist" - - expect(response).to redirect_to(new_user_session_path) - end - end - end - end -end -- cgit v1.2.1 From 68ab7047dae98172a0bd8b92956f2ee51b9167a0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 14:53:49 +0300 Subject: Update git over http test to match new routing Signed-off-by: Dmitriy Zaporozhets --- spec/requests/git_http_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index c0c1e62e910..413d06715b3 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -412,10 +412,9 @@ describe 'Git HTTP requests', lib: true do context "when the params are anything else" do let(:params) { { service: 'git-implode-pack' } } - before { get path, params } - it "redirects to the sign-in page" do - expect(response).to redirect_to(new_user_session_path) + it "fails to find a route" do + expect { get(path, params) }.to raise_error(ActionController::RoutingError) end end end -- cgit v1.2.1 From d6cfc0042ed2ce9a33f31a6c44661c136e861b98 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 16:39:57 +0300 Subject: Catch any undefined API routing and return 400 Bad Request Signed-off-by: Dmitriy Zaporozhets --- lib/api/api.rb | 4 ++++ spec/requests/api/users_spec.rb | 4 ++-- spec/routing/routing_spec.rb | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/api/api.rb b/lib/api/api.rb index 0bbf73a1b63..95a64f14ac7 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -73,5 +73,9 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + + route :any, '*path' do + error!('400 Bad Request', 400) + end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f4ea3bebb4c..c040000e8bb 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -90,9 +90,9 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 404 if invalid ID" do + it "returns a 400 if invalid ID" do get api("/users/1ASDF", user) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 0dd00af878d..0ee1c811dfb 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -266,7 +266,9 @@ describe "Groups", "routing" do end it "also display group#show on the short path" do - expect(get('/1')).to route_to('namespaces#show', id: '1') + allow(Group).to receive(:find_by_path).and_return(true) + + expect(get('/1')).to route_to('groups#show', id: '1') end end -- cgit v1.2.1 From fdfc93679d1ca91d4666095ba2ca732fdb273947 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 18:39:04 +0300 Subject: Fix API specs behaviour for invalid routing Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + doc/api/README.md | 13 +++++++++ spec/requests/api/project_hooks_spec.rb | 5 ++-- spec/requests/api/users_spec.rb | 52 +++++++++++++++++++++------------ 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 72e146f4f3f..aec47dd66f2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,6 +82,7 @@ v 8.13.0 (unreleased) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md + - API: all unknown routing will be handled with 400 Bad Request v 8.12.5 (unreleased) diff --git a/doc/api/README.md b/doc/api/README.md index bbd5bcfb386..8004a00659c 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -355,6 +355,19 @@ follows: } ``` +## Bad request + +When you try to access API URL that does not exist you will receive 400 Bad Request. + +``` +HTTP/1.1 400 Bad Request +Content-Type: application/json +{ + "error": "400 Bad Request" +} +``` + + ## Clients There are many unofficial GitLab API Clients for most of the popular diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 765dc8a8f66..5d739802095 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -163,9 +163,10 @@ describe API::API, 'ProjectHooks', api: true do expect(response).to have_http_status(404) end - it "returns a 405 error if hook id not given" do + it "returns a 400 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(405) + + expect(response).to have_http_status(400) end it "returns a 404 if a user attempts to delete project hooks he/she does not own" do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c040000e8bb..9537b0ec83d 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -92,6 +92,7 @@ describe API::API, api: true do it "returns a 400 if invalid ID" do get api("/users/1ASDF", user) + expect(response).to have_http_status(400) end end @@ -340,8 +341,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "raises error for invalid ID" do - expect{put api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 if invalid ID" do + put api("/users/ASDF", admin) + + expect(response).to have_http_status(400) end it 'returns 400 error if user does not validate' do @@ -525,9 +528,10 @@ describe API::API, api: true do expect(json_response.first['email']).to eq(email.email) end - it "raises error for invalid ID" do + it "returns a 400 for invalid ID" do put api("/users/ASDF/emails", admin) - expect(response).to have_http_status(405) + + expect(response).to have_http_status(400) end end end @@ -566,8 +570,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Email Not Found') end - it "raises error for invalid ID" do - expect{delete api("/users/ASDF/emails/bar", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/ASDF/emails/bar", admin) + + expect(response).to have_http_status(400) end end end @@ -600,8 +606,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "raises error for invalid ID" do - expect{delete api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/ASDF", admin) + + expect(response).to have_http_status(400) end end @@ -667,9 +675,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 404 for invalid ID" do + it "returns 400 for invalid ID" do get api("/users/keys/ASDF", admin) - expect(response).to have_http_status(404) + + expect(response).to have_http_status(400) end end @@ -727,8 +736,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "raises error for invalid ID" do - expect{delete api("/users/keys/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/keys/ASDF", admin) + + expect(response).to have_http_status(400) end end @@ -776,9 +787,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 404 for invalid ID" do + it "returns 400 for invalid ID" do get api("/users/emails/ASDF", admin) - expect(response).to have_http_status(404) + + expect(response).to have_http_status(400) end end @@ -825,8 +837,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "raises error for invalid ID" do - expect{delete api("/users/emails/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/emails/ASDF", admin) + + expect(response).to have_http_status(400) end end @@ -891,8 +905,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "raises error for invalid ID" do - expect{put api("/users/ASDF/block", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + put api("/users/ASDF/block", admin) + + expect(response).to have_http_status(400) end end end -- cgit v1.2.1 From 137ebcfb3cb013174f2885776a47264cffd193a6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 20:18:02 +0300 Subject: Replace undefined Grape routing code from 400 to 404 Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + doc/api/README.md | 6 ++-- lib/api/api.rb | 2 +- spec/requests/api/project_hooks_spec.rb | 4 +-- spec/requests/api/users_spec.rb | 50 +++++++++++++++++---------------- 5 files changed, 33 insertions(+), 30 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index aec47dd66f2..fc5e02cfd73 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -83,6 +83,7 @@ v 8.13.0 (unreleased) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 400 Bad Request + - API: all unknown routing will be handled with 404 Not Found v 8.12.5 (unreleased) diff --git a/doc/api/README.md b/doc/api/README.md index 8004a00659c..d47e537debc 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -357,13 +357,13 @@ follows: ## Bad request -When you try to access API URL that does not exist you will receive 400 Bad Request. +When you try to access API URL that does not exist you will receive 404 Not Found. ``` -HTTP/1.1 400 Bad Request +HTTP/1.1 404 Not Found Content-Type: application/json { - "error": "400 Bad Request" + "error": "404 Not Found" } ``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 95a64f14ac7..99722a0a65c 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -75,7 +75,7 @@ module API mount ::API::Variables route :any, '*path' do - error!('400 Bad Request', 400) + error!('404 Not Found', 404) end end end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 5d739802095..cfcdcad74cd 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -163,10 +163,10 @@ describe API::API, 'ProjectHooks', api: true do expect(response).to have_http_status(404) end - it "returns a 400 error if hook id not given" do + it "returns a 404 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end it "returns a 404 if a user attempts to delete project hooks he/she does not own" do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 9537b0ec83d..f0dd0592adb 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -90,10 +90,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 400 if invalid ID" do + it "returns a 404 for invalid ID" do get api("/users/1ASDF", user) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -341,10 +341,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 400 if invalid ID" do + it "returns a 404 if invalid ID" do put api("/users/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end it 'returns 400 error if user does not validate' do @@ -410,9 +410,9 @@ describe API::API, api: true do end.to change{ user.keys.count }.by(1) end - it "returns 400 for invalid ID" do + it "returns 404 for invalid ID" do post api("/users/999999/keys", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -496,9 +496,10 @@ describe API::API, api: true do end.to change{ user.emails.count }.by(1) end - it "raises error for invalid ID" do + it "returns a 404 for invalid ID" do post api("/users/999999/emails", admin) - expect(response).to have_http_status(400) + + expect(response).to have_http_status(404) end end @@ -528,10 +529,10 @@ describe API::API, api: true do expect(json_response.first['email']).to eq(email.email) end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do put api("/users/ASDF/emails", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end end @@ -570,10 +571,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Email Not Found') end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/ASDF/emails/bar", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end end @@ -606,10 +607,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -662,6 +663,7 @@ describe API::API, api: true do it "returns 404 Not Found within invalid ID" do get api("/user/keys/42", user) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -675,10 +677,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 400 for invalid ID" do + it "returns 404 for invalid ID" do get api("/users/keys/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -736,10 +738,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/keys/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -787,10 +789,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 400 for invalid ID" do + it "returns 404 for invalid ID" do get api("/users/emails/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -837,10 +839,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/emails/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -905,10 +907,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do put api("/users/ASDF/block", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end end -- cgit v1.2.1 From 3e49123dd5f8cb3c04184ce53e95f598e82a9f88 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 21:09:41 +0300 Subject: Fix api users spec for post request with invalid id Signed-off-by: Dmitriy Zaporozhets --- spec/requests/api/users_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f0dd0592adb..b002949b41b 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -410,9 +410,9 @@ describe API::API, api: true do end.to change{ user.keys.count }.by(1) end - it "returns 404 for invalid ID" do + it "returns 400 for invalid ID" do post api("/users/999999/keys", admin) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end @@ -496,10 +496,10 @@ describe API::API, api: true do end.to change{ user.emails.count }.by(1) end - it "returns a 404 for invalid ID" do + it "returns a 400 for invalid ID" do post api("/users/999999/emails", admin) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end -- cgit v1.2.1 From dc37ef6139ba96fe0b90f7c0188161b298269fef Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 10 Oct 2016 16:04:05 +0300 Subject: Better wording in API readme Signed-off-by: Dmitriy Zaporozhets --- doc/api/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/README.md b/doc/api/README.md index d47e537debc..9e907689c80 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -355,9 +355,9 @@ follows: } ``` -## Bad request +## Unknown route -When you try to access API URL that does not exist you will receive 404 Not Found. +When you try to access an API URL that does not exist you will receive 404 Not Found. ``` HTTP/1.1 404 Not Found -- cgit v1.2.1 From 42a6f5c1acb22f75c231a8f82019044e19235782 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 10 Oct 2016 03:32:46 +0100 Subject: Changed placeholder to 'Commit hash' Changed 'Commit hash' to 'Git revision' --- app/views/projects/network/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index b2ece44d966..29df1bab04e 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -8,7 +8,7 @@ .project-network .controls = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f| - = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha' + = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha' = button_tag class: 'btn btn-success' do = icon('search') .inline.prepend-left-20 -- cgit v1.2.1 From 4fff9d27d24ef1d959784cfa64d9a3189351c6ce Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 10 Oct 2016 14:44:00 +0100 Subject: Changed 'Compare branches, tags or commit ranges' to 'Compare Git revisions' --- app/views/projects/compare/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index e9ff8e90dd5..45be6581cfc 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -4,7 +4,7 @@ %div{ class: container_class } .sub-header-block - Compare branches, tags or commit ranges. + Compare Git revisions. %br Fill input field with commit id like %code.label-branch 4eedf23 -- cgit v1.2.1 From 87ee876307c76419954d99b15fd0f71f09a65699 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 7 Oct 2016 16:12:23 -0500 Subject: Add separate pipeline and commit columns --- app/assets/stylesheets/pages/pipelines.scss | 28 +++++--------- .../projects/ci/pipelines/_pipeline.html.haml | 45 ++++++++++++---------- app/views/projects/commit/_pipelines_list.haml | 1 + app/views/projects/pipelines/index.html.haml | 11 +++--- 4 files changed, 41 insertions(+), 44 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a2779704eff..582035e5c26 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -23,10 +23,9 @@ .table.builds { min-width: 1200px; - .branch-commit { - width: 33%; + .pipeline-id { + color: $black; } - } } @@ -81,7 +80,13 @@ } } + .avatar { + margin-left: 0; + float: none; + } + .branch-commit { + width: 30%; .branch-name { font-weight: bold; @@ -118,10 +123,6 @@ text-overflow: ellipsis; } - .avatar { - margin-left: 0; - } - .label { margin-right: 4px; } @@ -137,18 +138,7 @@ .icon-container { display: inline-block; - text-align: right; - width: 15px; - - .fa { - position: relative; - right: 3px; - } - - svg { - position: relative; - right: 1px; - } + width: 10px; } .stage-cell { diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 36eadbd2bf1..77ba8ae07f1 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -9,17 +9,13 @@ = ci_icon_for_status(status) - else = ci_status_with_icon(status) - %td.branch-commit + + %td = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do - %span ##{pipeline.id} - - if pipeline.ref && show_branch - .icon-container - = pipeline.tag? ? icon('tag') : icon('code-fork') - = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" - - if show_commit - .icon-container - = custom_icon("icon_commit") - = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" + %span.pipeline-id ##{pipeline.id} + %span by + - if commit = pipeline.commit + = author_avatar(commit, size: 20) - if pipeline.latest? %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest - if pipeline.triggered? @@ -29,6 +25,16 @@ - if pipeline.builds.any?(&:stuck?) %span.label.label-warning stuck + %td.branch-commit + - if pipeline.ref && show_branch + .icon-container + = pipeline.tag? ? icon('tag') : icon('code-fork') + = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" + - if show_commit + .icon-container + = custom_icon("icon_commit") + = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" + %p.commit-title - if commit = pipeline.commit = author_avatar(commit, size: 20) @@ -36,16 +42,15 @@ - else Cant find HEAD commit for this branch - - - stages_status = pipeline.statuses.relevant.latest.stages_status - %td.stage-cell - - stages.each do |stage| - - status = stages_status[stage] - - tooltip = "#{stage.titleize}: #{status || 'not found'}" - - if status - .stage-container - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do - = ci_icon_for_status(status) + - stages_status = pipeline.statuses.relevant.latest.stages_status + %td.stage-cell + - stages.each do |stage| + - status = stages_status[stage] + - tooltip = "#{stage.titleize}: #{status || 'not found'}" + - if status + .stage-container + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do + = ci_icon_for_status(status) %td - if pipeline.duration diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 998812793a2..4a122424901 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -8,6 +8,7 @@ %tbody %th Status %th Pipeline + %th Commit %th Stages %th %th diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 2d1df095bfa..82e28443605 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -45,11 +45,12 @@ .table-holder %table.table.builds %thead - %th.col-xs-1.col-sm-1 Status - %th.col-xs-2.col-sm-4 Pipeline - %th.col-xs-2.col-sm-2 Stages - %th.col-xs-2.col-sm-2 - %th.hidden-xs.col-sm-3 + %th Status + %th Pipeline + %th Commit + %th Stages + %th + %th.hidden-xs = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages = paginate @pipelines, theme: 'gitlab' -- cgit v1.2.1 From b24c57d03d5d0e7665e708238450d957422ff711 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 7 Oct 2016 16:21:08 -0500 Subject: Fix icon alignment --- app/assets/stylesheets/pages/pipelines.scss | 5 +++++ app/views/projects/ci/pipelines/_pipeline.html.haml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 582035e5c26..8dc6707d85c 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -139,6 +139,11 @@ .icon-container { display: inline-block; width: 10px; + + &.commit-icon { + width: 15px; + text-align: center; + } } .stage-cell { diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 77ba8ae07f1..1bd7ee76f61 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -31,7 +31,7 @@ = pipeline.tag? ? icon('tag') : icon('code-fork') = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name" - if show_commit - .icon-container + .icon-container.commit-icon = custom_icon("icon_commit") = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace" -- cgit v1.2.1 From ee2d392255ceb4669f87ef0589535f0e61f2430f Mon Sep 17 00:00:00 2001 From: Gennady Trafimenkov Date: Fri, 5 Aug 2016 09:31:45 +0300 Subject: Clarify which token should be used to delete a runner --- CHANGELOG | 1 + doc/api/ci/runners.md | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bee5bc769ca..26ec441c27f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -483,6 +483,7 @@ v 8.11.0 - Add pipeline events hook - Bump gitlab_git to speedup DiffCollection iterations - Rewrite description of a blocked user in admin settings. (Elias Werberich) + - Clarify documentation for Runners API (Gennady Trafimenkov) - Make branches sortable without push permission !5462 (winniehell) - Check for Ci::Build artifacts at database level on pipeline partial - Convert image diff background image to CSS (ClemMakesApps) diff --git a/doc/api/ci/runners.md b/doc/api/ci/runners.md index ecec53fde03..16028d1f124 100644 --- a/doc/api/ci/runners.md +++ b/doc/api/ci/runners.md @@ -12,7 +12,9 @@ communication channel. For the consumer API see the This API uses two types of authentication: 1. Unique Runner's token, which is the token assigned to the Runner after it - has been registered. + has been registered. This token can be found on the Runner's edit page (go to + **Project > Runners**, select one of the Runners listed under **Runners activated for + this project**). 2. Using Runners' registration token. This is a token that can be found in project's settings. @@ -48,7 +50,7 @@ DELETE /ci/api/v1/runners/delete | Attribute | Type | Required | Description | | --------- | ------- | --------- | ----------- | -| `token` | string | yes | Runner's registration token | +| `token` | string | yes | Unique Runner's token | Example request: -- cgit v1.2.1 From 09ccf6cce3e191f5971c96f1ce8b227a8f3682af Mon Sep 17 00:00:00 2001 From: Georg G Date: Mon, 10 Oct 2016 16:18:26 +0200 Subject: Use Linguist::Language[] instead of creating a hash --- CHANGELOG | 1 + app/controllers/projects/graphs_controller.rb | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 392a356c79a..e81df2fbf11 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.13.0 (unreleased) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 - Add configurable email subject suffix (Fu Xu) + - Use defined colour for a language when available !6748 (nilsding) - Added tooltip to fork count on project show page. (Justin DiPierro) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Replace `alias_method_chain` with `Module#prepend` diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index f4f2e5841a0..923e7340e69 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -35,14 +35,10 @@ class Projects::GraphsController < Projects::ApplicationController def languages @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages total = @languages.map(&:last).sum - colors = Linguist::Language.colors. - select { |lang| @languages.include? lang.name }. - map { |lang| [lang.name, lang.color] }. - to_h @languages = @languages.map do |language| name, share = language - color = colors[name] || "##{Digest::SHA256.hexdigest(name)[0...6]}" + color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}" { value: (share.to_f * 100 / total).round(2), label: name, -- cgit v1.2.1 From c69b81839430500de49d089df2049e778c8b7ef8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 10 Oct 2016 17:37:08 +0300 Subject: Fix duplicate entry in CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fc5e02cfd73..54f73419571 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,7 +82,6 @@ v 8.13.0 (unreleased) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md - - API: all unknown routing will be handled with 400 Bad Request - API: all unknown routing will be handled with 404 Not Found v 8.12.5 (unreleased) -- cgit v1.2.1 From 094828d01d2c146f2ba56af953333c2186ba9ba6 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 08:46:28 -0500 Subject: Trigger updates --- app/assets/stylesheets/pages/pipelines.scss | 10 ++++++++-- app/views/projects/ci/builds/_build.html.haml | 2 +- app/views/projects/ci/pipelines/_pipeline.html.haml | 4 ++-- app/views/projects/deployments/_commit.html.haml | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 8dc6707d85c..385ac07fdaf 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -26,6 +26,14 @@ .pipeline-id { color: $black; } + + .branch-commit { + width: 30%; + + .branch-name { + width: 195px + } + } } } @@ -86,7 +94,6 @@ } .branch-commit { - width: 30%; .branch-name { font-weight: bold; @@ -112,7 +119,6 @@ .commit-id { color: $gl-link-color; - margin-right: 8px; } .commit-title { diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 9248adfde80..c9b0017b28b 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -27,7 +27,7 @@ = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - else .light none - .icon-container + .icon-container.commit-icon = custom_icon("icon_commit") - if commit_sha diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 1bd7ee76f61..263a3ff61f7 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -14,8 +14,8 @@ = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do %span.pipeline-id ##{pipeline.id} %span by - - if commit = pipeline.commit - = author_avatar(commit, size: 20) + - if pipeline.user + = user_avatar(user: pipeline.user, size: 20) - if pipeline.latest? %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest - if pipeline.triggered? diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml index 28813babd7b..ff250eeca50 100644 --- a/app/views/projects/deployments/_commit.html.haml +++ b/app/views/projects/deployments/_commit.html.haml @@ -3,7 +3,7 @@ .icon-container = deployment.tag? ? icon('tag') : icon('code-fork') = link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace branch-name" - .icon-container + .icon-container.commit-icon = custom_icon("icon_commit") = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace" -- cgit v1.2.1 From aac2af5bc42fbca355d7d5f5ab03ef662b876219 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 10 Oct 2016 15:46:26 +0100 Subject: HTMLEntityFilter -> HtmlEntityFilter --- lib/banzai/filter/html_entity_filter.rb | 2 +- lib/banzai/pipeline/single_line_pipeline.rb | 2 +- spec/lib/banzai/filter/html_entity_filter_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb index 4ef8b3b6dcf..e008fd428b0 100644 --- a/lib/banzai/filter/html_entity_filter.rb +++ b/lib/banzai/filter/html_entity_filter.rb @@ -3,7 +3,7 @@ require 'erb' module Banzai module Filter # Text filter that escapes these HTML entities: & " < > - class HTMLEntityFilter < HTML::Pipeline::TextFilter + class HtmlEntityFilter < HTML::Pipeline::TextFilter def call ERB::Util.html_escape(text) end diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb index 30bc035d085..1929099931b 100644 --- a/lib/banzai/pipeline/single_line_pipeline.rb +++ b/lib/banzai/pipeline/single_line_pipeline.rb @@ -3,7 +3,7 @@ module Banzai class SingleLinePipeline < GfmPipeline def self.filters @filters ||= FilterArray[ - Filter::HTMLEntityFilter, + Filter::HtmlEntityFilter, Filter::SanitizationFilter, Filter::EmojiFilter, diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb index 6dc4a970071..4c68ce6d6e4 100644 --- a/spec/lib/banzai/filter/html_entity_filter_spec.rb +++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Banzai::Filter::HTMLEntityFilter, lib: true do +describe Banzai::Filter::HtmlEntityFilter, lib: true do include FilterSpecHelper let(:unescaped) { 'foo &&&' } -- cgit v1.2.1 From 36bf0b67e56af452b6a4ba9b03a1ee684cab2a78 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Fri, 7 Oct 2016 01:01:32 +0900 Subject: Remove Ci::ApplicationController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/controllers/ci/application_controller.rb | 7 ------- app/controllers/ci/lints_controller.rb | 2 +- app/controllers/ci/projects_controller.rb | 2 +- 4 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 app/controllers/ci/application_controller.rb diff --git a/CHANGELOG b/CHANGELOG index 26ec441c27f..7081dfacace 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,6 +82,7 @@ v 8.13.0 (unreleased) - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container + - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fix a typo in doc/api/labels.md v 8.12.5 (unreleased) diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb deleted file mode 100644 index 5bb7d499cdc..00000000000 --- a/app/controllers/ci/application_controller.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Ci - class ApplicationController < ::ApplicationController - def self.railtie_helpers_paths - "app/helpers/ci" - end - end -end diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index 78012960252..3eb485de9db 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -1,5 +1,5 @@ module Ci - class LintsController < ApplicationController + class LintsController < ::ApplicationController before_action :authenticate_user! def show diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb index aa894fde36b..ff297d6ff13 100644 --- a/app/controllers/ci/projects_controller.rb +++ b/app/controllers/ci/projects_controller.rb @@ -1,5 +1,5 @@ module Ci - class ProjectsController < Ci::ApplicationController + class ProjectsController < ::ApplicationController before_action :project before_action :no_cache, only: [:badge] before_action :authorize_read_project!, except: [:badge, :index] -- cgit v1.2.1 From ee9b75fd37dd29e062cedeafefe1e02fa1b65914 Mon Sep 17 00:00:00 2001 From: Jerdog Date: Mon, 19 Sep 2016 15:54:49 -0500 Subject: Changes to make Git basics more intuitive - updated verbiage where appropriate - changed "git config" commands to include quotes for variables to be more in line with standard practive and to avoid issues with spaces - updated CHANGELOG as part of commit --- CHANGELOG | 1 + doc/gitlab-basics/start-using-git.md | 20 +++++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bee5bc769ca..3a32622ad55 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -323,6 +323,7 @@ v 8.11.7 - Avoid conflict with admin labels when importing GitHub labels. !6158 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - Allow the Rails cookie to be used for API authentication. + - Updating verbiage on git basics to be more intuitive v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md index b61f436c1a4..4f7015f7dbc 100644 --- a/doc/gitlab-basics/start-using-git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -1,11 +1,10 @@ # Start using Git on the command line -If you want to start using a Git and GitLab, make sure that you have created an -account on GitLab. +If you want to start using Git and GitLab, make sure that you have created and/or signed into an [account on GitLab](https://gitlab.com/users/sign_in). ## Open a shell -Depending on your operating system, find the shell of your preference. Here are some suggestions. +Depending on your operating system, you will need to use a shell of your preference. Here are some suggestions: - [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX @@ -22,19 +21,19 @@ Type the following command and then press enter: git --version ``` -You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). +You should receive a message that will tell you which Git version you have on your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window. -After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed. +After you are finished installing, open a new shell and type "git --version" again to verify that it was correctly installed. ## Add your Git username and set your email -It is important because every Git commit that you create will use this information. +It is important to configure your Git username and email address as every Git commit that you create will use this information to identify who has submitted the commit. On your shell, type the following command to add your username: ``` -git config --global user.name ADD YOUR USERNAME +git config --global user.name "your_username_as_you_want_it_to_appear" ``` Then verify that you have the correct username: @@ -44,7 +43,7 @@ git config --global user.name To set your email address, type the following command: ``` -git config --global user.email ADD YOUR EMAIL +git config --global user.email "your_email_address@domain" ``` To verify that you entered your email correctly, type: @@ -52,7 +51,7 @@ To verify that you entered your email correctly, type: git config --global user.email ``` -You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project. +You'll need to do this only once as you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project. ## Check your information @@ -76,7 +75,7 @@ git pull REMOTE NAME-OF-BRANCH -u (REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch) ### Create a branch -Spaces won't be recognized, so you need to use a hyphen or underscore. +Spaces won't be recognized, so you will need to use a hyphen or underscore. ``` git checkout -b NAME-OF-BRANCH ``` @@ -127,4 +126,3 @@ You need to be in the master branch. git checkout master git merge NAME-OF-BRANCH ``` - -- cgit v1.2.1 From db32660450b4768e86af07388e11a72b11ebb929 Mon Sep 17 00:00:00 2001 From: Jerdog Date: Mon, 19 Sep 2016 16:18:03 -0500 Subject: Updating changes based on feedback from @connorshea --- doc/gitlab-basics/start-using-git.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md index 4f7015f7dbc..42cd8bb3e48 100644 --- a/doc/gitlab-basics/start-using-git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -1,6 +1,6 @@ # Start using Git on the command line -If you want to start using Git and GitLab, make sure that you have created and/or signed into an [account on GitLab](https://gitlab.com/users/sign_in). +If you want to start using Git and GitLab, make sure that you have created and/or signed into an account on GitLab. ## Open a shell @@ -29,11 +29,11 @@ After you are finished installing, open a new shell and type "git --version" aga ## Add your Git username and set your email -It is important to configure your Git username and email address as every Git commit that you create will use this information to identify who has submitted the commit. +It is important to configure your Git username and email address as every Git commit will use this information to identify you as the author. On your shell, type the following command to add your username: ``` -git config --global user.name "your_username_as_you_want_it_to_appear" +git config --global user.name "YOUR_USERNAME" ``` Then verify that you have the correct username: @@ -43,7 +43,7 @@ git config --global user.name To set your email address, type the following command: ``` -git config --global user.email "your_email_address@domain" +git config --global user.email "your_email_address@example.com" ``` To verify that you entered your email correctly, type: @@ -51,7 +51,7 @@ To verify that you entered your email correctly, type: git config --global user.email ``` -You'll need to do this only once as you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project. +You'll need to do this only once as you are using the `--global` option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the `--global` option when you’re in that project. ## Check your information -- cgit v1.2.1 From 4917bbd7ffc9e7e67d93a08650d95d02a0a67081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 10 Oct 2016 17:03:34 +0200 Subject: Speed up specs for GET /projects/:id/events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 8.15s to 4.55s by grouping expectations Signed-off-by: Rémy Coutable --- spec/requests/api/projects_spec.rb | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5f19638b460..85717d274aa 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -588,37 +588,39 @@ describe API::API, api: true do before do note = create(:note_on_issue, note: 'What an awesome day!', project: project) EventCreateService.new.leave_note(note, note.author) - get api("/projects/#{project.id}/events", user) end - it { expect(response).to have_http_status(200) } + it 'returns all events' do + get api("/projects/#{project.id}/events", user) - context 'joined event' do - let(:json_event) { json_response[1] } + expect(response).to have_http_status(200) - it { expect(json_event['action_name']).to eq('joined') } - it { expect(json_event['project_id'].to_i).to eq(project.id) } - it { expect(json_event['author_username']).to eq(user3.username) } - it { expect(json_event['author']['name']).to eq(user3.name) } - end + first_event = json_response.first - context 'comment event' do - let(:json_event) { json_response.first } + expect(first_event['action_name']).to eq('commented on') + expect(first_event['note']['body']).to eq('What an awesome day!') - it { expect(json_event['action_name']).to eq('commented on') } - it { expect(json_event['note']['body']).to eq('What an awesome day!') } + last_event = json_response.last + + expect(last_event['action_name']).to eq('joined') + expect(last_event['project_id'].to_i).to eq(project.id) + expect(last_event['author_username']).to eq(user3.username) + expect(last_event['author']['name']).to eq(user3.name) end end it 'returns a 404 error if not found' do get api('/projects/42/events', user) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'returns a 404 error if user is not a member' do other_user = create(:user) + get api("/projects/#{project.id}/events", other_user) + expect(response).to have_http_status(404) end end -- cgit v1.2.1 From 6606642f8f352267d9f645778a789b79d98a6ca8 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Mon, 10 Oct 2016 16:19:46 +0100 Subject: fixup! Added link to bulk assign issues to MR author. (Issue #18876) --- spec/controllers/projects/merge_requests_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 2a68e5a2c9b..84298f8bef4 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -742,7 +742,7 @@ describe Projects::MergeRequestsController do it 'calls MergeRequests::AssignIssuesService' do expect(MergeRequests::AssignIssuesService).to receive(:new). with(project, user, merge_request: merge_request). - and_return(double(execute: {count: 1})) + and_return(double(execute: { count: 1 })) post_assign_issues end -- cgit v1.2.1 From 7aa9675fbd7ca2e45403adb444fc03680f0e5d7a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 17:35:57 +0200 Subject: Add registry to skipped data in backup raketask docs [ci skip] --- doc/raketasks/backup_restore.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 91c2df7d464..26baffdf792 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -32,15 +32,17 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` You can specify that portions of the application data be skipped using the -environment variable `SKIP`. You can skip: -- `db` +environment variable `SKIP`. You can skip: + +- `db` (database) - `uploads` (attachments) -- `repositories` +- `repositories` (Git repositories data) - `builds` (CI build output logs) - `artifacts` (CI build artifacts) - `lfs` (LFS objects) +- `registry` (Container Registry images) -Separate multiple data types to skip using a comma. For example: +Separate multiple data types to skip using a comma. For example: ``` sudo gitlab-rake gitlab:backup:create SKIP=db,uploads -- cgit v1.2.1 From cc8ad60fc42e9a115da63f0e0eca71c4b2567c35 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 12 Sep 2016 12:19:20 +0200 Subject: Update pipeline graph styles to match mockup --- app/assets/javascripts/pipeline.js.es6 | 9 ++++++++ app/assets/stylesheets/pages/pipelines.scss | 36 +++++++++++++++++------------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 8813bb5dfef..d3ed9757afe 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,4 +1,12 @@ (function() { + + function addMarginToBuild () { + const $secondChild = $('.build:nth-child(2)'); + if ($secondChild.length) { + $secondChild.closest('.stage-column').addClass('left-margin'); + } + } + function toggleGraph() { const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); @@ -21,4 +29,5 @@ } $(document).on('click', '.toggle-pipeline-btn', toggleGraph); + $(document).on('ready', addMarginToBuild); })(); diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a2779704eff..e7e1a2a9b18 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -303,7 +303,13 @@ .stage-column { display: inline-block; vertical-align: top; - margin-right: 65px; + margin-right: 48px; + + &.left-margin { + &:not(:first-child) { + margin-left: 48px; + } + } li { list-style: none; @@ -321,9 +327,9 @@ .build { border: 1px solid $border-color; position: relative; - padding: 6px 10px; + padding: 8px 10px; border-radius: 30px; - width: 150px; + width: 186px; margin-bottom: 10px; &.playable { @@ -443,9 +449,9 @@ content: ''; position: absolute; top: 50%; - right: -69px; + right: -48px; border-top: 2px solid $border-color; - width: 69px; + width: 48px; height: 1px; } } @@ -457,22 +463,22 @@ top: -47px; position: absolute; border-bottom: 2px solid $border-color; - width: 20px; + width: 25px; height: 65px; } // Right connecting curves &::after { - right: -20px; + right: -25px; border-right: 2px solid $border-color; - border-radius: 0 0 15px; + border-radius: 0 0 20px; } // Left connecting curves &::before { - left: -20px; + left: -25px; border-left: 2px solid $border-color; - border-radius: 0 0 0 15px; + border-radius: 0 0 0 20px; } } @@ -538,20 +544,20 @@ width: 21px; height: 25px; position: absolute; - top: -29px; + top: -30px; border-top: 2px solid $border-color; } &::after { - left: -39px; + left: -44px; border-right: 2px solid $border-color; - border-radius: 0 15px; + border-radius: 0 20px; } &::before { - right: -39px; + right: -44px; border-left: 2px solid $border-color; - border-radius: 15px 0 0; + border-radius: 20px 0 0; } } } -- cgit v1.2.1 From ff0f70c0fe75773d060ecf5cf75d6fc5f879283b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 14 Sep 2016 13:51:36 +0200 Subject: Change length of connecting lines based on number of builds --- app/assets/javascripts/pipeline.js.es6 | 9 ++++++--- app/assets/stylesheets/pages/pipelines.scss | 24 +++++++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index d3ed9757afe..f501761b1ec 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,9 +1,12 @@ (function() { function addMarginToBuild () { - const $secondChild = $('.build:nth-child(2)'); - if ($secondChild.length) { - $secondChild.closest('.stage-column').addClass('left-margin'); + const $secondChildBuildNode = $('.build:nth-child(2)'); + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + // const $previousBuildColumn = $secondChildBuildNode.closest('.stage-column').prev('.stage-column'); + if ($secondChildBuildNode.length) { + $secondChildBuildNode.closest('.stage-column').addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e7e1a2a9b18..37df702ea13 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -303,11 +303,26 @@ .stage-column { display: inline-block; vertical-align: top; - margin-right: 48px; + + &:not(:last-child) { + margin-right: 44px; + } &.left-margin { &:not(:first-child) { - margin-left: 48px; + margin-left: 44px; + + .left-connector { + &::before { + content: ''; + position: absolute; + top: 50%; + left: -48px; + border-top: 2px solid $border-color; + width: 48px; + height: 1px; + } + } } } @@ -348,7 +363,10 @@ } .build-content { - width: 130px; + width: 164px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; .ci-status-text { width: 110px; -- cgit v1.2.1 From 65e482e7e9b7385e6d8ee72c102eca6e79ee97fa Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 16 Sep 2016 09:44:00 +0200 Subject: Change size of pipeline status icons and dropdowns --- app/assets/stylesheets/pages/pipelines.scss | 47 ++++++++++++++-------- .../projects/ci/builds/_build_pipeline.html.haml | 6 ++- .../commit/_pipeline_status_group.html.haml | 3 +- .../_generic_commit_status_pipeline.html.haml | 6 ++- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 37df702ea13..547b9742dff 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -331,9 +331,9 @@ } .stage-name { - margin-bottom: 15px; + margin: 0 0 15px 10px; font-weight: bold; - width: 150px; + width: 176px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -364,12 +364,17 @@ .build-content { width: 164px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + + .ci-status-icon { + + svg { + height: 20px; + width: 20px; + } + } .ci-status-text { - width: 110px; + width: 135px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -397,27 +402,37 @@ color: $layout-link-gray; .ci-status-text { - width: 80px; + width: 112px; } } .grouped-pipeline-dropdown { padding: 8px 0; - width: 200px; + width: 168px; left: auto; - right: -214px; + right: -180px; top: -9px; max-height: 245px; overflow-y: scroll; - a:hover { - .ci-status-text { - text-decoration: none; + a { + padding: 7px 8px; + margin: 0 8px; + + &:hover { + .ci-status-text { + text-decoration: none; + } } } + svg { + width: 14px; + height: 14px; + } + .ci-status-text { - width: 145px; + width: 112px; } .arrow { @@ -482,7 +497,7 @@ position: absolute; border-bottom: 2px solid $border-color; width: 25px; - height: 65px; + height: 69px; } // Right connecting curves @@ -504,7 +519,7 @@ &:nth-child(2) { &::after, &::before { height: 29px; - top: -10px; + top: -7px; } .curve { display: block; @@ -562,7 +577,7 @@ width: 21px; height: 25px; position: absolute; - top: -30px; + top: -31.5px; border-top: 2px solid $border-color; } diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 547bc0c9c19..017d3ff6af2 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -5,8 +5,10 @@ .ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do - = render_status_with_link('build', subject.status) + %span.ci-status-icon + = render_status_with_link('build', subject.status) .ci-status-text= subject.name - else - = render_status_with_link('build', subject.status) + %span.ci-status-icon + = render_status_with_link('build', subject.status) = ci_icon_for_status(subject.status) diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 4e7a6f1af08..2064242ab54 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,5 +1,6 @@ - group_status = CommitStatus.where(id: subject).status -= render_status_with_link('build', group_status) +%span.ci-status-icon + = render_status_with_link('build', group_status) .dropdown.inline %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } %span.ci-status-text diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 409f4701e4b..0a66d60accc 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,7 +1,9 @@ - if subject.target_url = link_to subject.target_url do - = render_status_with_link('commit status', subject.status) + %span.ci-status-icon + = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name - else - = render_status_with_link('commit status', subject.status) + %span.ci-status-icon + = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name -- cgit v1.2.1 From eb55ac7d4d13a2c404d24c68cef10675ce29cf3c Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 21 Sep 2016 01:45:39 +0100 Subject: Added final changes from handover --- app/assets/stylesheets/framework/variables.scss | 5 +++ app/assets/stylesheets/pages/pipelines.scss | 38 +++++++++++++--------- .../projects/commit/_pipeline_stage.html.haml | 2 +- .../commit/_pipeline_status_group.html.haml | 17 +++++----- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 14ec310de2d..4c34ed3ebf7 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -17,8 +17,10 @@ $white-normal: #ededed; $white-dark: #ececec; $gray-light: #fafafa; +$gray-lighter: #f9f9f9; $gray-normal: #f5f5f5; $gray-dark: #ededed; +$gray-darker: #eee; $gray-darkest: #c9c9c9; $green-light: #38ae67; @@ -33,6 +35,8 @@ $blue-medium-light: #3498cb; $blue-medium: #2f8ebf; $blue-medium-dark: #2d86b4; +$blue-light-transparent: rgba(44, 159, 216, 0.05); + $orange-light: #fc8a51; $orange-normal: #e75e40; $orange-dark: #ce5237; @@ -91,6 +95,7 @@ $table-text-gray: #8f8f8f; $gl-font-size: 15px; $gl-title-color: #333; $gl-text-color: #5c5c5c; +$gl-text-color-light: #8c8c8c; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 547b9742dff..36c57f3ca30 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -342,11 +342,18 @@ .build { border: 1px solid $border-color; position: relative; - padding: 8px 10px; + padding: 7px 10px 8px; border-radius: 30px; width: 186px; margin-bottom: 10px; + &:hover { + background-color: $gray-lighter; + .dropdown-menu-toggle { + background-color: transparent; + } + } + &.playable { background-color: $gray-light; @@ -366,7 +373,6 @@ width: 164px; .ci-status-icon { - svg { height: 20px; width: 20px; @@ -385,41 +391,40 @@ } a { - color: $layout-link-gray; + color: $gl-text-color-light; text-decoration: none; - - &:hover { - .ci-status-text { - text-decoration: underline; - } - } } .dropdown-menu-toggle { border: none; width: auto; padding: 0; - color: $layout-link-gray; + color: $gl-text-color-light; .ci-status-text { - width: 112px; + max-width: 112px; + width: auto; } } .grouped-pipeline-dropdown { padding: 8px 0; - width: 168px; + width: 186px; left: auto; - right: -180px; + right: -197px; top: -9px; max-height: 245px; overflow-y: scroll; a { - padding: 7px 8px; + color: $gl-text-color; + padding: 7px 8px 8px; margin: 0 8px; &:hover { + background-color: $blue-light-transparent; + border-radius: 3px; + .ci-status-text { text-decoration: none; } @@ -465,9 +470,10 @@ } .badge { - background-color: $gray-dark; - color: $layout-link-gray; + background-color: $gray-darker; + color: $gl-text-color-light; font-weight: normal; + margin-left: $btn-xs-side-margin; } } diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 23c5c51fbc2..68d42126bf6 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -10,5 +10,5 @@ - else %li.build .curve - .build-content + .dropdown.inline.build-content{ type: 'button', data: { toggle: 'dropdown' } } = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 2064242ab54..bff65bff653 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,12 +1,11 @@ - group_status = CommitStatus.where(id: subject).status %span.ci-status-icon = render_status_with_link('build', group_status) -.dropdown.inline - %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } - %span.ci-status-text - = name - %span.badge= subject.size - %ul.dropdown-menu.grouped-pipeline-dropdown - .arrow - - subject.each do |status| - = render "projects/#{status.to_partial_path}_pipeline", subject: status +%button.dropdown-menu-toggle + %span.ci-status-text + = name + %span.badge= subject.size +%ul.dropdown-menu.grouped-pipeline-dropdown + .arrow + - subject.each do |status| + = render "projects/#{status.to_partial_path}_pipeline", subject: status -- cgit v1.2.1 From 5defad2d21b6481c07fb4a77f0a56ed7c19ff899 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 29 Sep 2016 22:24:37 +0100 Subject: Finished up margin JS logic --- app/assets/javascripts/dispatcher.js | 3 + app/assets/javascripts/pipeline.js.es6 | 60 ++++++++++---------- app/assets/stylesheets/pages/pipelines.scss | 4 ++ app/views/projects/commit/_pipeline.html.haml | 79 ++++++++++++++------------- 4 files changed, 79 insertions(+), 67 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 8d99b12102d..45494afe7aa 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -126,6 +126,9 @@ new TreeView(); } break; + case 'projects:pipelines:show': + new window.gl.Pipelines(); + break; case 'groups:activity': new Activities(); break; diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index f501761b1ec..6299ba2269d 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,36 +1,40 @@ -(function() { - - function addMarginToBuild () { - const $secondChildBuildNode = $('.build:nth-child(2)'); - const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); - // const $previousBuildColumn = $secondChildBuildNode.closest('.stage-column').prev('.stage-column'); - if ($secondChildBuildNode.length) { - $secondChildBuildNode.closest('.stage-column').addClass('left-margin'); - $firstChildBuildNode.addClass('left-connector'); +((global) => { + + class Pipelines { + constructor() { + $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); + $(document).off('ready.addMarginToBuildColumns').on('ready.addMarginToBuildColumns', this.addMarginToBuildColumns); } - } - function toggleGraph() { - const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); - const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); - const $btnText = $(this).find('.toggle-btn-text'); - const $icon = $(this).find('.fa'); + toggleGraph() { + const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); + const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); + const $btnText = $(this).find('.toggle-btn-text'); - $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); + $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - const expandIcon = 'fa-caret-down'; - const hideIcon = 'fa-caret-up'; + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - if(graphCollapsed) { - $btnText.text('Expand'); - $icon.removeClass(hideIcon).addClass(expandIcon); - } else { - $btnText.text('Hide'); - $icon.removeClass(expandIcon).addClass(hideIcon); + graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + } + + addMarginToBuildColumns() { + const $secondChildBuildNode = $('.build:nth-child(2)'); + if ($secondChildBuildNode.length) { + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); + const $previousColumn = $multiBuildColumn.prev('.stage-column'); + $multiBuildColumn.addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); + $previousColumn.each(function() { + $this = $(this); + if ($('.build', $this).length === 1) $this.addClass('no-margin'); + }); + } + $('.pipeline-graph-container').removeClass('hidden'); } } - $(document).on('click', '.toggle-pipeline-btn', toggleGraph); - $(document).on('ready', addMarginToBuild); -})(); + global.Pipelines = Pipelines; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 36c57f3ca30..f4211ea3b2d 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -326,6 +326,10 @@ } } + &.no-margin { + margin: 0; + } + li { list-style: none; } diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index da5b9832ba5..bdf3c0a2aba 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,45 +1,46 @@ -.row-content-block.build-content.middle-block.pipeline-actions - .pull-right - .btn.btn-grouped.btn-white.toggle-pipeline-btn - %span.toggle-btn-text Hide - %span pipeline graph - = icon('caret-up') - - if can?(current_user, :update_pipeline, pipeline.project) - - if pipeline.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post +.pipeline-graph-container.hidden + .row-content-block.build-content.middle-block.pipeline-actions + .pull-right + .btn.btn-grouped.btn-white.toggle-pipeline-btn + %span.toggle-btn-text Hide + %span pipeline graph + %span.caret + - if can?(current_user, :update_pipeline, pipeline.project) + - if pipeline.builds.latest.failed.any?(&:retryable?) + = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post - - if pipeline.builds.running_or_pending.any? - = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + - if pipeline.builds.running_or_pending.any? + = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - .oneline.clearfix - - if defined?(pipeline_details) && pipeline_details - Pipeline - = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace" - with - = pluralize pipeline.statuses.count(:id), "build" - - if pipeline.ref - for - = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace" - - if defined?(link_to_commit) && link_to_commit - for commit - = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace" - - if pipeline.duration - in - = time_interval_in_words pipeline.duration + .oneline.clearfix + - if defined?(pipeline_details) && pipeline_details + Pipeline + = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace" + with + = pluralize pipeline.statuses.count(:id), "build" + - if pipeline.ref + for + = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace" + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace" + - if pipeline.duration + in + = time_interval_in_words pipeline.duration -.row-content-block.build-content.middle-block.pipeline-graph - .pipeline-visualization - %ul.stage-column-list - - stages = pipeline.stages_with_latest_statuses - - stages.each do |stage, statuses| - %li.stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - %ul - = render "projects/commit/pipeline_stage", statuses: statuses + .row-content-block.build-content.middle-block.pipeline-graph + .pipeline-visualization + %ul.stage-column-list + - stages = pipeline.stages_with_latest_statuses + - stages.each do |stage, statuses| + %li.stage-column + .stage-name + %a{name: stage} + - if stage + = stage.titleize + .builds-container + %ul + = render "projects/commit/pipeline_stage", statuses: statuses - if pipeline.yaml_errors.present? -- cgit v1.2.1 From c2deaa7e0db808abad86e87bac9524784b02e602 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 4 Oct 2016 13:59:57 -0500 Subject: Move hidden class to graph itself; remove background color from play node & align icon --- app/assets/javascripts/pipeline.js.es6 | 2 +- app/assets/stylesheets/pages/pipelines.scss | 5 ++--- app/views/projects/commit/_pipeline.html.haml | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 6299ba2269d..68a34dda2af 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -31,7 +31,7 @@ if ($('.build', $this).length === 1) $this.addClass('no-margin'); }); } - $('.pipeline-graph-container').removeClass('hidden'); + $('.pipeline-graph').removeClass('hidden'); } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index f4211ea3b2d..4e53c9765df 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -359,11 +359,10 @@ } &.playable { - background-color: $gray-light; svg { - height: 12px; - width: 12px; + height: 13px; + width: 20px; position: relative; top: 1px; diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index bdf3c0a2aba..288c06d9b67 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,4 +1,4 @@ -.pipeline-graph-container.hidden +.pipeline-graph-container .row-content-block.build-content.middle-block.pipeline-actions .pull-right .btn.btn-grouped.btn-white.toggle-pipeline-btn @@ -28,7 +28,7 @@ in = time_interval_in_words pipeline.duration - .row-content-block.build-content.middle-block.pipeline-graph + .row-content-block.build-content.middle-block.pipeline-graph.hidden .pipeline-visualization %ul.stage-column-list - stages = pipeline.stages_with_latest_statuses -- cgit v1.2.1 From 113050c5709a204f58e0395b08e1582b3c862d66 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 6 Oct 2016 13:35:13 -0500 Subject: Fix ul html --- app/assets/stylesheets/pages/pipelines.scss | 1 - app/views/projects/commit/_pipeline_status_group.html.haml | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 4e53c9765df..9ce5bee466f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -422,7 +422,6 @@ a { color: $gl-text-color; padding: 7px 8px 8px; - margin: 0 8px; &:hover { background-color: $blue-light-transparent; diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index bff65bff653..b26de822450 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -6,6 +6,7 @@ = name %span.badge= subject.size %ul.dropdown-menu.grouped-pipeline-dropdown - .arrow + %li.arrow - subject.each do |status| - = render "projects/#{status.to_partial_path}_pipeline", subject: status + %li + = render "projects/#{status.to_partial_path}_pipeline", subject: status -- cgit v1.2.1 From 6a7b673035752890023e630755f27ec0d412a129 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 6 Oct 2016 18:00:06 +0100 Subject: JS review changes - Removed window from window.gl in dispatcher Added page:load event as ready isnt fired by turbolinks Fix dropdown menu link click registration --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/pipeline.js.es6 | 1 + app/assets/stylesheets/pages/pipelines.scss | 4 ++++ app/views/projects/commit/_pipeline_stage.html.haml | 2 +- app/views/projects/commit/_pipeline_status_group.html.haml | 2 +- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 45494afe7aa..adff73af79c 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -127,7 +127,7 @@ } break; case 'projects:pipelines:show': - new window.gl.Pipelines(); + new gl.Pipelines(); break; case 'groups:activity': new Activities(); diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 68a34dda2af..1030447f74a 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -4,6 +4,7 @@ constructor() { $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); $(document).off('ready.addMarginToBuildColumns').on('ready.addMarginToBuildColumns', this.addMarginToBuildColumns); + $(document).off('page:load.addMarginToBuildColumns').on('page:load.addMarginToBuildColumns', this.addMarginToBuildColumns); } toggleGraph() { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 9ce5bee466f..0c3c1e404af 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -373,6 +373,9 @@ } .build-content { + display: -ms-flexbox; + display: -webkit-flex; + display: flex; width: 164px; .ci-status-icon { @@ -403,6 +406,7 @@ width: auto; padding: 0; color: $gl-text-color-light; + flex-grow: 1; .ci-status-text { max-width: 112px; diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 68d42126bf6..289aa5178b1 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -10,5 +10,5 @@ - else %li.build .curve - .dropdown.inline.build-content{ type: 'button', data: { toggle: 'dropdown' } } + .dropdown.inline.build-content = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index b26de822450..8582e9a8772 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,7 +1,7 @@ - group_status = CommitStatus.where(id: subject).status %span.ci-status-icon = render_status_with_link('build', group_status) -%button.dropdown-menu-toggle +%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } %span.ci-status-text = name %span.badge= subject.size -- cgit v1.2.1 From 4e1efca14119a25c441d833268894a632eb269d5 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 6 Oct 2016 14:33:55 -0500 Subject: Fix node flex alignment --- app/views/projects/commit/_pipeline_status_group.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 8582e9a8772..6ada719e006 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,7 +1,7 @@ - group_status = CommitStatus.where(id: subject).status -%span.ci-status-icon - = render_status_with_link('build', group_status) %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } + %span.ci-status-icon + = render_status_with_link('build', group_status) %span.ci-status-text = name %span.badge= subject.size -- cgit v1.2.1 From a57b3fc80c50b951d3a54ce2108608d45826c5ef Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 6 Oct 2016 21:20:25 +0100 Subject: Corrected my correction for turbolinks -.-' Removed extra cell on generic pipeline --- app/assets/javascripts/pipeline.js.es6 | 3 +-- .../projects/generic_commit_statuses/_generic_commit_status.html.haml | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 1030447f74a..a29f6ecdd59 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -3,8 +3,7 @@ class Pipelines { constructor() { $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); - $(document).off('ready.addMarginToBuildColumns').on('ready.addMarginToBuildColumns', this.addMarginToBuildColumns); - $(document).off('page:load.addMarginToBuildColumns').on('page:load.addMarginToBuildColumns', this.addMarginToBuildColumns); + this.addMarginToBuildColumns(); } toggleGraph() { diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 331dc1fcc29..80fe6be49b0 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -62,5 +62,3 @@ %td.coverage - if generic_commit_status.try(:coverage) #{generic_commit_status.coverage}% - - %td -- cgit v1.2.1 From 274ac4efdb94efe4d3ea4d5fff193eb2f02db507 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 7 Oct 2016 12:23:00 +0100 Subject: JS review changes and fixed conflicts --- app/assets/javascripts/pipeline.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index a29f6ecdd59..6bf63ee6979 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -10,10 +10,10 @@ const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); const $btnText = $(this).find('.toggle-btn-text'); + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') } -- cgit v1.2.1 From 879a68a7c48c469c2646ecd89412cab9679d8860 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Fri, 7 Oct 2016 14:57:57 +0200 Subject: slight update to lines and curves positioning --- app/assets/stylesheets/pages/pipelines.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 0c3c1e404af..04cce24ce7d 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -321,6 +321,7 @@ border-top: 2px solid $border-color; width: 48px; height: 1px; + margin-top: -1px; } } } @@ -498,6 +499,7 @@ border-top: 2px solid $border-color; width: 48px; height: 1px; + margin-top: -1px; } } @@ -505,7 +507,7 @@ &:not(:first-child) { &::after, &::before { content: ''; - top: -47px; + top: -49px; position: absolute; border-bottom: 2px solid $border-color; width: 25px; @@ -531,7 +533,7 @@ &:nth-child(2) { &::after, &::before { height: 29px; - top: -7px; + top: -9px; } .curve { display: block; @@ -589,7 +591,7 @@ width: 21px; height: 25px; position: absolute; - top: -31.5px; + top: -32.5px; border-top: 2px solid $border-color; } -- cgit v1.2.1 From 7a28205629962427bfe5a48610ee4890b37f5ae8 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 7 Oct 2016 08:57:30 -0500 Subject: Remove negative margins --- app/assets/stylesheets/pages/pipelines.scss | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 04cce24ce7d..21224447628 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -316,12 +316,11 @@ &::before { content: ''; position: absolute; - top: 50%; + top: 49%; left: -48px; border-top: 2px solid $border-color; width: 48px; height: 1px; - margin-top: -1px; } } } @@ -494,12 +493,11 @@ &::after { content: ''; position: absolute; - top: 50%; + top: 49%; right: -48px; border-top: 2px solid $border-color; width: 48px; height: 1px; - margin-top: -1px; } } @@ -591,7 +589,7 @@ width: 21px; height: 25px; position: absolute; - top: -32.5px; + top: -31px; border-top: 2px solid $border-color; } -- cgit v1.2.1 From 0f7cc21e2479762975c4416148105e876c85d8cd Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 10 Oct 2016 17:09:48 +0100 Subject: Formatted all app/assets/javascripts to underscore naming convention --- app/assets/javascripts/LabelManager.js.es6 | 106 ------ app/assets/javascripts/commit/image-file.js | 177 --------- app/assets/javascripts/commit/image_file.js | 177 +++++++++ app/assets/javascripts/cycle-analytics.js.es6 | 93 ----- app/assets/javascripts/cycle_analytics.js.es6 | 93 +++++ .../javascripts/issues-bulk-assignment.js.es6 | 149 -------- .../javascripts/issues_bulk_assignment.js.es6 | 149 ++++++++ app/assets/javascripts/label_manager.js.es6 | 106 ++++++ app/assets/javascripts/network/branch-graph.js | 417 --------------------- app/assets/javascripts/network/branch_graph.js | 417 +++++++++++++++++++++ 10 files changed, 942 insertions(+), 942 deletions(-) delete mode 100644 app/assets/javascripts/LabelManager.js.es6 delete mode 100644 app/assets/javascripts/commit/image-file.js create mode 100644 app/assets/javascripts/commit/image_file.js delete mode 100644 app/assets/javascripts/cycle-analytics.js.es6 create mode 100644 app/assets/javascripts/cycle_analytics.js.es6 delete mode 100644 app/assets/javascripts/issues-bulk-assignment.js.es6 create mode 100644 app/assets/javascripts/issues_bulk_assignment.js.es6 create mode 100644 app/assets/javascripts/label_manager.js.es6 delete mode 100644 app/assets/javascripts/network/branch-graph.js create mode 100644 app/assets/javascripts/network/branch_graph.js diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/LabelManager.js.es6 deleted file mode 100644 index bc68e53504f..00000000000 --- a/app/assets/javascripts/LabelManager.js.es6 +++ /dev/null @@ -1,106 +0,0 @@ -((global) => { - - class LabelManager { - constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { - this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); - this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); - this.otherLabels = otherLabels || $('.js-other-labels'); - this.errorMessage = 'Unable to update label prioritization at this time'; - this.prioritizedLabels.sortable({ - items: 'li', - placeholder: 'list-placeholder', - axis: 'y', - update: this.onPrioritySortUpdate.bind(this) - }); - this.bindEvents(); - } - - bindEvents() { - return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); - } - - onTogglePriorityClick(e) { - e.preventDefault(); - const _this = e.data; - const $btn = $(e.currentTarget); - const $label = $(`#${$btn.data('domId')}`); - const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; - const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); - $tooltip.tooltip('destroy'); - return _this.toggleLabelPriority($label, action); - } - - toggleLabelPriority($label, action, persistState) { - if (persistState == null) { - persistState = true; - } - let xhr; - const _this = this; - const url = $label.find('.js-toggle-priority').data('url'); - let $target = this.prioritizedLabels; - let $from = this.otherLabels; - if (action === 'remove') { - $target = this.otherLabels; - $from = this.prioritizedLabels; - } - if ($from.find('li').length === 1) { - $from.find('.empty-message').removeClass('hidden'); - } - if (!$target.find('li').length) { - $target.find('.empty-message').addClass('hidden'); - } - $label.detach().appendTo($target); - // Return if we are not persisting state - if (!persistState) { - return; - } - if (action === 'remove') { - xhr = $.ajax({ - url, - type: 'DELETE' - }); - // Restore empty message - if (!$from.find('li').length) { - $from.find('.empty-message').removeClass('hidden'); - } - } else { - xhr = this.savePrioritySort($label, action); - } - return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); - } - - onPrioritySortUpdate() { - const xhr = this.savePrioritySort(); - return xhr.fail(function() { - return new Flash(this.errorMessage, 'alert'); - }); - } - - savePrioritySort() { - return $.post({ - url: this.prioritizedLabels.data('url'), - data: { - label_ids: this.getSortedLabelsIds() - } - }); - } - - rollbackLabelPosition($label, originalAction) { - const action = originalAction === 'remove' ? 'add' : 'remove'; - this.toggleLabelPriority($label, action, false); - return new Flash(this.errorMessage, 'alert'); - } - - getSortedLabelsIds() { - const sortedIds = []; - this.prioritizedLabels.find('li').each(function() { - sortedIds.push($(this).data('id')); - }); - return sortedIds; - } - } - - gl.LabelManager = LabelManager; - -})(window.gl || (window.gl = {})); - diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image-file.js deleted file mode 100644 index e893491b19b..00000000000 --- a/app/assets/javascripts/commit/image-file.js +++ /dev/null @@ -1,177 +0,0 @@ -(function() { - this.ImageFile = (function() { - var prepareFrames; - - // Width where images must fits in, for 2-up this gets divided by 2 - ImageFile.availWidth = 900; - - ImageFile.viewModes = ['two-up', 'swipe']; - - function ImageFile(file) { - this.file = file; - this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) { - // Determine if old and new file has same dimensions, if not show 'two-up' view - return function(deletedWidth, deletedHeight) { - return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) { - if (width === deletedWidth && height === deletedHeight) { - return _this.initViewModes(); - } else { - return _this.initView('two-up'); - } - }); - }; - })(this)); - } - - ImageFile.prototype.initViewModes = function() { - var viewMode; - viewMode = ImageFile.viewModes[0]; - $('.view-modes', this.file).removeClass('hide'); - $('.view-modes-menu', this.file).on('click', 'li', (function(_this) { - return function(event) { - if (!$(event.currentTarget).hasClass('active')) { - return _this.activateViewMode(event.currentTarget.className); - } - }; - })(this)); - return this.activateViewMode(viewMode); - }; - - ImageFile.prototype.activateViewMode = function(viewMode) { - $('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active'); - return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) { - return function() { - $(".view." + viewMode, _this.file).fadeIn(200); - return _this.initView(viewMode); - }; - })(this)); - }; - - ImageFile.prototype.initView = function(viewMode) { - return this.views[viewMode].call(this); - }; - - prepareFrames = function(view) { - var maxHeight, maxWidth; - maxWidth = 0; - maxHeight = 0; - $('.frame', view).each((function(_this) { - return function(index, frame) { - var height, width; - width = $(frame).width(); - height = $(frame).height(); - maxWidth = width > maxWidth ? width : maxWidth; - return maxHeight = height > maxHeight ? height : maxHeight; - }; - })(this)).css({ - width: maxWidth, - height: maxHeight - }); - return [maxWidth, maxHeight]; - }; - - ImageFile.prototype.views = { - 'two-up': function() { - return $('.two-up.view .wrap', this.file).each((function(_this) { - return function(index, wrap) { - $('img', wrap).each(function() { - var currentWidth; - currentWidth = $(this).width(); - if (currentWidth > ImageFile.availWidth / 2) { - return $(this).width(ImageFile.availWidth / 2); - } - }); - return _this.requestImageInfo($('img', wrap), function(width, height) { - $('.image-info .meta-width', wrap).text(width + "px"); - $('.image-info .meta-height', wrap).text(height + "px"); - return $('.image-info', wrap).removeClass('hide'); - }); - }; - })(this)); - }, - 'swipe': function() { - var maxHeight, maxWidth; - maxWidth = 0; - maxHeight = 0; - return $('.swipe.view', this.file).each((function(_this) { - return function(index, view) { - var ref; - ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; - $('.swipe-frame', view).css({ - width: maxWidth + 16, - height: maxHeight + 28 - }); - $('.swipe-wrap', view).css({ - width: maxWidth + 1, - height: maxHeight + 2 - }); - return $('.swipe-bar', view).css({ - left: 0 - }).draggable({ - axis: 'x', - containment: 'parent', - drag: function(event) { - return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left); - }, - stop: function(event) { - return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left); - } - }); - }; - })(this)); - }, - 'onion-skin': function() { - var dragTrackWidth, maxHeight, maxWidth; - maxWidth = 0; - maxHeight = 0; - dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width(); - return $('.onion-skin.view', this.file).each((function(_this) { - return function(index, view) { - var ref; - ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; - $('.onion-skin-frame', view).css({ - width: maxWidth + 16, - height: maxHeight + 28 - }); - $('.swipe-wrap', view).css({ - width: maxWidth + 1, - height: maxHeight + 2 - }); - return $('.dragger', view).css({ - left: dragTrackWidth - }).draggable({ - axis: 'x', - containment: 'parent', - drag: function(event) { - return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth); - }, - stop: function(event) { - return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth); - } - }); - }; - })(this)); - } - }; - - ImageFile.prototype.requestImageInfo = function(img, callback) { - var domImg; - domImg = img.get(0); - if (domImg) { - if (domImg.complete) { - return callback.call(this, domImg.naturalWidth, domImg.naturalHeight); - } else { - return img.on('load', (function(_this) { - return function() { - return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight); - }; - })(this)); - } - } - }; - - return ImageFile; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js new file mode 100644 index 00000000000..e893491b19b --- /dev/null +++ b/app/assets/javascripts/commit/image_file.js @@ -0,0 +1,177 @@ +(function() { + this.ImageFile = (function() { + var prepareFrames; + + // Width where images must fits in, for 2-up this gets divided by 2 + ImageFile.availWidth = 900; + + ImageFile.viewModes = ['two-up', 'swipe']; + + function ImageFile(file) { + this.file = file; + this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) { + // Determine if old and new file has same dimensions, if not show 'two-up' view + return function(deletedWidth, deletedHeight) { + return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) { + if (width === deletedWidth && height === deletedHeight) { + return _this.initViewModes(); + } else { + return _this.initView('two-up'); + } + }); + }; + })(this)); + } + + ImageFile.prototype.initViewModes = function() { + var viewMode; + viewMode = ImageFile.viewModes[0]; + $('.view-modes', this.file).removeClass('hide'); + $('.view-modes-menu', this.file).on('click', 'li', (function(_this) { + return function(event) { + if (!$(event.currentTarget).hasClass('active')) { + return _this.activateViewMode(event.currentTarget.className); + } + }; + })(this)); + return this.activateViewMode(viewMode); + }; + + ImageFile.prototype.activateViewMode = function(viewMode) { + $('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active'); + return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) { + return function() { + $(".view." + viewMode, _this.file).fadeIn(200); + return _this.initView(viewMode); + }; + })(this)); + }; + + ImageFile.prototype.initView = function(viewMode) { + return this.views[viewMode].call(this); + }; + + prepareFrames = function(view) { + var maxHeight, maxWidth; + maxWidth = 0; + maxHeight = 0; + $('.frame', view).each((function(_this) { + return function(index, frame) { + var height, width; + width = $(frame).width(); + height = $(frame).height(); + maxWidth = width > maxWidth ? width : maxWidth; + return maxHeight = height > maxHeight ? height : maxHeight; + }; + })(this)).css({ + width: maxWidth, + height: maxHeight + }); + return [maxWidth, maxHeight]; + }; + + ImageFile.prototype.views = { + 'two-up': function() { + return $('.two-up.view .wrap', this.file).each((function(_this) { + return function(index, wrap) { + $('img', wrap).each(function() { + var currentWidth; + currentWidth = $(this).width(); + if (currentWidth > ImageFile.availWidth / 2) { + return $(this).width(ImageFile.availWidth / 2); + } + }); + return _this.requestImageInfo($('img', wrap), function(width, height) { + $('.image-info .meta-width', wrap).text(width + "px"); + $('.image-info .meta-height', wrap).text(height + "px"); + return $('.image-info', wrap).removeClass('hide'); + }); + }; + })(this)); + }, + 'swipe': function() { + var maxHeight, maxWidth; + maxWidth = 0; + maxHeight = 0; + return $('.swipe.view', this.file).each((function(_this) { + return function(index, view) { + var ref; + ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; + $('.swipe-frame', view).css({ + width: maxWidth + 16, + height: maxHeight + 28 + }); + $('.swipe-wrap', view).css({ + width: maxWidth + 1, + height: maxHeight + 2 + }); + return $('.swipe-bar', view).css({ + left: 0 + }).draggable({ + axis: 'x', + containment: 'parent', + drag: function(event) { + return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left); + }, + stop: function(event) { + return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left); + } + }); + }; + })(this)); + }, + 'onion-skin': function() { + var dragTrackWidth, maxHeight, maxWidth; + maxWidth = 0; + maxHeight = 0; + dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width(); + return $('.onion-skin.view', this.file).each((function(_this) { + return function(index, view) { + var ref; + ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; + $('.onion-skin-frame', view).css({ + width: maxWidth + 16, + height: maxHeight + 28 + }); + $('.swipe-wrap', view).css({ + width: maxWidth + 1, + height: maxHeight + 2 + }); + return $('.dragger', view).css({ + left: dragTrackWidth + }).draggable({ + axis: 'x', + containment: 'parent', + drag: function(event) { + return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth); + }, + stop: function(event) { + return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth); + } + }); + }; + })(this)); + } + }; + + ImageFile.prototype.requestImageInfo = function(img, callback) { + var domImg; + domImg = img.get(0); + if (domImg) { + if (domImg.complete) { + return callback.call(this, domImg.naturalWidth, domImg.naturalHeight); + } else { + return img.on('load', (function(_this) { + return function() { + return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight); + }; + })(this)); + } + } + }; + + return ImageFile; + + })(); + +}).call(this); diff --git a/app/assets/javascripts/cycle-analytics.js.es6 b/app/assets/javascripts/cycle-analytics.js.es6 deleted file mode 100644 index cd9886ba58d..00000000000 --- a/app/assets/javascripts/cycle-analytics.js.es6 +++ /dev/null @@ -1,93 +0,0 @@ -((global) => { - - const COOKIE_NAME = 'cycle_analytics_help_dismissed'; - const store = gl.cycleAnalyticsStore = { - isLoading: true, - hasError: false, - isHelpDismissed: $.cookie(COOKIE_NAME), - analytics: {} - }; - - gl.CycleAnalytics = class CycleAnalytics { - constructor() { - const that = this; - - this.vue = new Vue({ - el: '#cycle-analytics', - name: 'CycleAnalytics', - created: this.fetchData(), - data: store, - methods: { - dismissLanding() { - that.dismissLanding(); - } - } - }); - } - - fetchData(options) { - store.isLoading = true; - options = options || { startDate: 30 }; - - $.ajax({ - url: $('#cycle-analytics').data('request-path'), - method: 'GET', - dataType: 'json', - contentType: 'application/json', - data: { start_date: options.startDate } - }).done((data) => { - this.decorateData(data); - this.initDropdown(); - }) - .error((data) => { - this.handleError(data); - }) - .always(() => { - store.isLoading = false; - }) - } - - decorateData(data) { - data.summary = data.summary || []; - data.stats = data.stats || []; - - data.summary.forEach((item) => { - item.value = item.value || '-'; - }); - - data.stats.forEach((item) => { - item.value = item.value || '- - -'; - }); - - store.analytics = data; - } - - handleError(data) { - store.hasError = true; - new Flash('There was an error while fetching cycle analytics data.', 'alert'); - } - - dismissLanding() { - store.isHelpDismissed = true; - $.cookie(COOKIE_NAME, true, { - path: gon.relative_url_root || '/' - }); - } - - initDropdown() { - const $dropdown = $('.js-ca-dropdown'); - const $label = $dropdown.find('.dropdown-label'); - - $dropdown.find('li a').off('click').on('click', (e) => { - e.preventDefault(); - const $target = $(e.currentTarget); - const value = $target.data('value'); - - $label.text($target.text().trim()); - this.fetchData({ startDate: value }); - }) - } - - } - -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/cycle_analytics.js.es6 b/app/assets/javascripts/cycle_analytics.js.es6 new file mode 100644 index 00000000000..cd9886ba58d --- /dev/null +++ b/app/assets/javascripts/cycle_analytics.js.es6 @@ -0,0 +1,93 @@ +((global) => { + + const COOKIE_NAME = 'cycle_analytics_help_dismissed'; + const store = gl.cycleAnalyticsStore = { + isLoading: true, + hasError: false, + isHelpDismissed: $.cookie(COOKIE_NAME), + analytics: {} + }; + + gl.CycleAnalytics = class CycleAnalytics { + constructor() { + const that = this; + + this.vue = new Vue({ + el: '#cycle-analytics', + name: 'CycleAnalytics', + created: this.fetchData(), + data: store, + methods: { + dismissLanding() { + that.dismissLanding(); + } + } + }); + } + + fetchData(options) { + store.isLoading = true; + options = options || { startDate: 30 }; + + $.ajax({ + url: $('#cycle-analytics').data('request-path'), + method: 'GET', + dataType: 'json', + contentType: 'application/json', + data: { start_date: options.startDate } + }).done((data) => { + this.decorateData(data); + this.initDropdown(); + }) + .error((data) => { + this.handleError(data); + }) + .always(() => { + store.isLoading = false; + }) + } + + decorateData(data) { + data.summary = data.summary || []; + data.stats = data.stats || []; + + data.summary.forEach((item) => { + item.value = item.value || '-'; + }); + + data.stats.forEach((item) => { + item.value = item.value || '- - -'; + }); + + store.analytics = data; + } + + handleError(data) { + store.hasError = true; + new Flash('There was an error while fetching cycle analytics data.', 'alert'); + } + + dismissLanding() { + store.isHelpDismissed = true; + $.cookie(COOKIE_NAME, true, { + path: gon.relative_url_root || '/' + }); + } + + initDropdown() { + const $dropdown = $('.js-ca-dropdown'); + const $label = $dropdown.find('.dropdown-label'); + + $dropdown.find('li a').off('click').on('click', (e) => { + e.preventDefault(); + const $target = $(e.currentTarget); + const value = $target.data('value'); + + $label.text($target.text().trim()); + this.fetchData({ startDate: value }); + }) + } + + } + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues-bulk-assignment.js.es6 deleted file mode 100644 index 0808f538f01..00000000000 --- a/app/assets/javascripts/issues-bulk-assignment.js.es6 +++ /dev/null @@ -1,149 +0,0 @@ -((global) => { - - class IssuableBulkActions { - constructor({ container, form, issues } = {}) { - this.container = container || $('.content'), - this.form = form || this.getElement('.bulk-update'); - this.issues = issues || this.getElement('.issues-list .issue'); - this.form.data('bulkActions', this); - this.willUpdateLabels = false; - this.bindEvents(); - // Fixes bulk-assign not working when navigating through pages - Issuable.initChecks(); - } - - getElement(selector) { - return this.container.find(selector); - } - - bindEvents() { - return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); - } - - onFormSubmit(e) { - e.preventDefault(); - return this.submit(); - } - - submit() { - const _this = this; - const xhr = $.ajax({ - url: this.form.attr('action'), - method: this.form.attr('method'), - dataType: 'JSON', - data: this.getFormDataAsObject() - }); - xhr.done(() => window.location.reload()); - xhr.fail(() => new Flash("Issue update failed")); - return xhr.always(this.onFormSubmitAlways.bind(this)); - } - - onFormSubmitAlways() { - return this.form.find('[type="submit"]').enable(); - } - - getSelectedIssues() { - return this.issues.has('.selected_issue:checked'); - } - - getLabelsFromSelection() { - const labels = []; - this.getSelectedIssues().map(function() { - const labelsData = $(this).data('labels'); - if (labelsData) { - return labelsData.map(function(labelId) { - if (labels.indexOf(labelId) === -1) { - return labels.push(labelId); - } - }); - } - }); - return labels; - } - - - /** - * Will return only labels that were marked previously and the user has unmarked - * @return {Array} Label IDs - */ - - getUnmarkedIndeterminedLabels() { - const result = []; - const labelsToKeep = []; - - this.getElement('.labels-filter .is-indeterminate') - .each((i, el) => labelsToKeep.push($(el).data('labelId'))); - - this.getLabelsFromSelection().forEach((id) => { - if (labelsToKeep.indexOf(id) === -1) { - result.push(id); - } - }); - - return result; - } - - - /** - * Simple form serialization, it will return just what we need - * Returns key/value pairs from form data - */ - - getFormDataAsObject() { - const formData = { - update: { - state_event: this.form.find('input[name="update[state_event]"]').val(), - assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), - milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), - issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), - subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), - add_label_ids: [], - remove_label_ids: [] - } - }; - if (this.willUpdateLabels) { - this.getLabelsToApply().map(function(id) { - return formData.update.add_label_ids.push(id); - }); - this.getLabelsToRemove().map(function(id) { - return formData.update.remove_label_ids.push(id); - }); - } - return formData; - } - - getLabelsToApply() { - const labelIds = []; - const $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); - $labels.each(function(k, label) { - if (label) { - return labelIds.push(parseInt($(label).val())); - } - }); - return labelIds; - } - - - /** - * Returns Label IDs that will be removed from issue selection - * @return {Array} Array of labels IDs - */ - - getLabelsToRemove() { - const result = []; - const indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); - const labelsToApply = this.getLabelsToApply(); - indeterminatedLabels.map(function(id) { - // We need to exclude label IDs that will be applied - // By not doing this will cause issues from selection to not add labels at all - if (labelsToApply.indexOf(id) === -1) { - return result.push(id); - } - }); - return result; - } - } - - global.IssuableBulkActions = IssuableBulkActions; - -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/issues_bulk_assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6 new file mode 100644 index 00000000000..0808f538f01 --- /dev/null +++ b/app/assets/javascripts/issues_bulk_assignment.js.es6 @@ -0,0 +1,149 @@ +((global) => { + + class IssuableBulkActions { + constructor({ container, form, issues } = {}) { + this.container = container || $('.content'), + this.form = form || this.getElement('.bulk-update'); + this.issues = issues || this.getElement('.issues-list .issue'); + this.form.data('bulkActions', this); + this.willUpdateLabels = false; + this.bindEvents(); + // Fixes bulk-assign not working when navigating through pages + Issuable.initChecks(); + } + + getElement(selector) { + return this.container.find(selector); + } + + bindEvents() { + return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); + } + + onFormSubmit(e) { + e.preventDefault(); + return this.submit(); + } + + submit() { + const _this = this; + const xhr = $.ajax({ + url: this.form.attr('action'), + method: this.form.attr('method'), + dataType: 'JSON', + data: this.getFormDataAsObject() + }); + xhr.done(() => window.location.reload()); + xhr.fail(() => new Flash("Issue update failed")); + return xhr.always(this.onFormSubmitAlways.bind(this)); + } + + onFormSubmitAlways() { + return this.form.find('[type="submit"]').enable(); + } + + getSelectedIssues() { + return this.issues.has('.selected_issue:checked'); + } + + getLabelsFromSelection() { + const labels = []; + this.getSelectedIssues().map(function() { + const labelsData = $(this).data('labels'); + if (labelsData) { + return labelsData.map(function(labelId) { + if (labels.indexOf(labelId) === -1) { + return labels.push(labelId); + } + }); + } + }); + return labels; + } + + + /** + * Will return only labels that were marked previously and the user has unmarked + * @return {Array} Label IDs + */ + + getUnmarkedIndeterminedLabels() { + const result = []; + const labelsToKeep = []; + + this.getElement('.labels-filter .is-indeterminate') + .each((i, el) => labelsToKeep.push($(el).data('labelId'))); + + this.getLabelsFromSelection().forEach((id) => { + if (labelsToKeep.indexOf(id) === -1) { + result.push(id); + } + }); + + return result; + } + + + /** + * Simple form serialization, it will return just what we need + * Returns key/value pairs from form data + */ + + getFormDataAsObject() { + const formData = { + update: { + state_event: this.form.find('input[name="update[state_event]"]').val(), + assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), + milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), + issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), + subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), + add_label_ids: [], + remove_label_ids: [] + } + }; + if (this.willUpdateLabels) { + this.getLabelsToApply().map(function(id) { + return formData.update.add_label_ids.push(id); + }); + this.getLabelsToRemove().map(function(id) { + return formData.update.remove_label_ids.push(id); + }); + } + return formData; + } + + getLabelsToApply() { + const labelIds = []; + const $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); + $labels.each(function(k, label) { + if (label) { + return labelIds.push(parseInt($(label).val())); + } + }); + return labelIds; + } + + + /** + * Returns Label IDs that will be removed from issue selection + * @return {Array} Array of labels IDs + */ + + getLabelsToRemove() { + const result = []; + const indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); + const labelsToApply = this.getLabelsToApply(); + indeterminatedLabels.map(function(id) { + // We need to exclude label IDs that will be applied + // By not doing this will cause issues from selection to not add labels at all + if (labelsToApply.indexOf(id) === -1) { + return result.push(id); + } + }); + return result; + } + } + + global.IssuableBulkActions = IssuableBulkActions; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6 new file mode 100644 index 00000000000..bc68e53504f --- /dev/null +++ b/app/assets/javascripts/label_manager.js.es6 @@ -0,0 +1,106 @@ +((global) => { + + class LabelManager { + constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { + this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); + this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); + this.otherLabels = otherLabels || $('.js-other-labels'); + this.errorMessage = 'Unable to update label prioritization at this time'; + this.prioritizedLabels.sortable({ + items: 'li', + placeholder: 'list-placeholder', + axis: 'y', + update: this.onPrioritySortUpdate.bind(this) + }); + this.bindEvents(); + } + + bindEvents() { + return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); + } + + onTogglePriorityClick(e) { + e.preventDefault(); + const _this = e.data; + const $btn = $(e.currentTarget); + const $label = $(`#${$btn.data('domId')}`); + const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; + const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); + $tooltip.tooltip('destroy'); + return _this.toggleLabelPriority($label, action); + } + + toggleLabelPriority($label, action, persistState) { + if (persistState == null) { + persistState = true; + } + let xhr; + const _this = this; + const url = $label.find('.js-toggle-priority').data('url'); + let $target = this.prioritizedLabels; + let $from = this.otherLabels; + if (action === 'remove') { + $target = this.otherLabels; + $from = this.prioritizedLabels; + } + if ($from.find('li').length === 1) { + $from.find('.empty-message').removeClass('hidden'); + } + if (!$target.find('li').length) { + $target.find('.empty-message').addClass('hidden'); + } + $label.detach().appendTo($target); + // Return if we are not persisting state + if (!persistState) { + return; + } + if (action === 'remove') { + xhr = $.ajax({ + url, + type: 'DELETE' + }); + // Restore empty message + if (!$from.find('li').length) { + $from.find('.empty-message').removeClass('hidden'); + } + } else { + xhr = this.savePrioritySort($label, action); + } + return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); + } + + onPrioritySortUpdate() { + const xhr = this.savePrioritySort(); + return xhr.fail(function() { + return new Flash(this.errorMessage, 'alert'); + }); + } + + savePrioritySort() { + return $.post({ + url: this.prioritizedLabels.data('url'), + data: { + label_ids: this.getSortedLabelsIds() + } + }); + } + + rollbackLabelPosition($label, originalAction) { + const action = originalAction === 'remove' ? 'add' : 'remove'; + this.toggleLabelPriority($label, action, false); + return new Flash(this.errorMessage, 'alert'); + } + + getSortedLabelsIds() { + const sortedIds = []; + this.prioritizedLabels.find('li').each(function() { + sortedIds.push($(this).data('id')); + }); + return sortedIds; + } + } + + gl.LabelManager = LabelManager; + +})(window.gl || (window.gl = {})); + diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch-graph.js deleted file mode 100644 index 91132af273a..00000000000 --- a/app/assets/javascripts/network/branch-graph.js +++ /dev/null @@ -1,417 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.BranchGraph = (function() { - function BranchGraph(element1, options1) { - this.element = element1; - this.options = options1; - this.scrollTop = bind(this.scrollTop, this); - this.scrollBottom = bind(this.scrollBottom, this); - this.scrollRight = bind(this.scrollRight, this); - this.scrollLeft = bind(this.scrollLeft, this); - this.scrollUp = bind(this.scrollUp, this); - this.scrollDown = bind(this.scrollDown, this); - this.preparedCommits = {}; - this.mtime = 0; - this.mspace = 0; - this.parents = {}; - this.colors = ["#000"]; - this.offsetX = 150; - this.offsetY = 20; - this.unitTime = 30; - this.unitSpace = 10; - this.prev_start = -1; - this.load(); - } - - BranchGraph.prototype.load = function() { - return $.ajax({ - url: this.options.url, - method: "get", - dataType: "json", - success: $.proxy(function(data) { - $(".loading", this.element).hide(); - this.prepareData(data.days, data.commits); - return this.buildGraph(); - }, this) - }); - }; - - BranchGraph.prototype.prepareData = function(days, commits) { - var c, ch, cw, j, len, ref; - this.days = days; - this.commits = commits; - this.collectParents(); - this.graphHeight = $(this.element).height(); - this.graphWidth = $(this.element).width(); - ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150); - cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300); - this.r = Raphael(this.element.get(0), cw, ch); - this.top = this.r.set(); - this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320); - ref = this.commits; - for (j = 0, len = ref.length; j < len; j++) { - c = ref[j]; - if (c.id in this.parents) { - c.isParent = true; - } - this.preparedCommits[c.id] = c; - this.markCommit(c); - } - return this.collectColors(); - }; - - BranchGraph.prototype.collectParents = function() { - var c, j, len, p, ref, results; - ref = this.commits; - results = []; - for (j = 0, len = ref.length; j < len; j++) { - c = ref[j]; - this.mtime = Math.max(this.mtime, c.time); - this.mspace = Math.max(this.mspace, c.space); - results.push((function() { - var l, len1, ref1, results1; - ref1 = c.parents; - results1 = []; - for (l = 0, len1 = ref1.length; l < len1; l++) { - p = ref1[l]; - this.parents[p[0]] = true; - results1.push(this.mspace = Math.max(this.mspace, p[1])); - } - return results1; - }).call(this)); - } - return results; - }; - - BranchGraph.prototype.collectColors = function() { - var k, results; - k = 0; - results = []; - while (k < this.mspace) { - this.colors.push(Raphael.getColor(.8)); - // Skipping a few colors in the spectrum to get more contrast between colors - Raphael.getColor(); - Raphael.getColor(); - results.push(k++); - } - return results; - }; - - BranchGraph.prototype.buildGraph = function() { - var cuday, cumonth, day, j, len, mm, r, ref; - r = this.r; - cuday = 0; - cumonth = ""; - r.rect(0, 0, 40, this.barHeight).attr({ - fill: "#222" - }); - r.rect(40, 0, 30, this.barHeight).attr({ - fill: "#444" - }); - ref = this.days; - for (mm = j = 0, len = ref.length; j < len; mm = ++j) { - day = ref[mm]; - if (cuday !== day[0] || cumonth !== day[1]) { - // Dates - r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({ - font: "12px Monaco, monospace", - fill: "#BBB" - }); - cuday = day[0]; - } - if (cumonth !== day[1]) { - // Months - r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({ - font: "12px Monaco, monospace", - fill: "#EEE" - }); - cumonth = day[1]; - } - } - this.renderPartialGraph(); - return this.bindEvents(); - }; - - BranchGraph.prototype.renderPartialGraph = function() { - var commit, end, i, isGraphEdge, start, x, y; - start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10; - if (start < 0) { - isGraphEdge = true; - start = 0; - } - end = start + 40; - if (this.commits.length < end) { - isGraphEdge = true; - end = this.commits.length; - } - if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) { - i = start; - this.prev_start = start; - while (i < end) { - commit = this.commits[i]; - i += 1; - if (commit.hasDrawn !== true) { - x = this.offsetX + this.unitSpace * (this.mspace - commit.space); - y = this.offsetY + this.unitTime * commit.time; - this.drawDot(x, y, commit); - this.drawLines(x, y, commit); - this.appendLabel(x, y, commit); - this.appendAnchor(x, y, commit); - commit.hasDrawn = true; - } - } - return this.top.toFront(); - } - }; - - BranchGraph.prototype.bindEvents = function() { - var element; - element = this.element; - return $(element).scroll((function(_this) { - return function(event) { - return _this.renderPartialGraph(); - }; - })(this)); - }; - - BranchGraph.prototype.scrollDown = function() { - this.element.scrollTop(this.element.scrollTop() + 50); - return this.renderPartialGraph(); - }; - - BranchGraph.prototype.scrollUp = function() { - this.element.scrollTop(this.element.scrollTop() - 50); - return this.renderPartialGraph(); - }; - - BranchGraph.prototype.scrollLeft = function() { - this.element.scrollLeft(this.element.scrollLeft() - 50); - return this.renderPartialGraph(); - }; - - BranchGraph.prototype.scrollRight = function() { - this.element.scrollLeft(this.element.scrollLeft() + 50); - return this.renderPartialGraph(); - }; - - BranchGraph.prototype.scrollBottom = function() { - return this.element.scrollTop(this.element.find('svg').height()); - }; - - BranchGraph.prototype.scrollTop = function() { - return this.element.scrollTop(0); - }; - - BranchGraph.prototype.appendLabel = function(x, y, commit) { - var label, r, rect, shortrefs, text, textbox, triangle; - if (!commit.refs) { - return; - } - r = this.r; - shortrefs = commit.refs; - // Truncate if longer than 15 chars - if (shortrefs.length > 17) { - shortrefs = shortrefs.substr(0, 15) + "…"; - } - text = r.text(x + 4, y, shortrefs).attr({ - "text-anchor": "start", - font: "10px Monaco, monospace", - fill: "#FFF", - title: commit.refs - }); - textbox = text.getBBox(); - // Create rectangle based on the size of the textbox - rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({ - fill: "#000", - "fill-opacity": .5, - stroke: "none" - }); - triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({ - fill: "#000", - "fill-opacity": .5, - stroke: "none" - }); - label = r.set(rect, text); - label.transform(["t", -rect.getBBox().width - 15, 0]); - // Set text to front - return text.toFront(); - }; - - BranchGraph.prototype.appendAnchor = function(x, y, commit) { - var anchor, options, r, top; - r = this.r; - top = this.top; - options = this.options; - anchor = r.circle(x, y, 10).attr({ - fill: "#000", - opacity: 0, - cursor: "pointer" - }).click(function() { - return window.open(options.commit_url.replace("%s", commit.id), "_blank"); - }).hover(function() { - this.tooltip = r.commitTooltip(x + 5, y, commit); - return top.push(this.tooltip.insertBefore(this)); - }, function() { - return this.tooltip && this.tooltip.remove() && delete this.tooltip; - }); - return top.push(anchor); - }; - - BranchGraph.prototype.drawDot = function(x, y, commit) { - var avatar_box_x, avatar_box_y, r; - r = this.r; - r.circle(x, y, 3).attr({ - fill: this.colors[commit.space], - stroke: "none" - }); - avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10; - avatar_box_y = y - 10; - r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({ - stroke: this.colors[commit.space], - "stroke-width": 2 - }); - r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20); - return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({ - "text-anchor": "start", - font: "14px Monaco, monospace" - }); - }; - - BranchGraph.prototype.drawLines = function(x, y, commit) { - var arrow, color, i, j, len, offset, parent, parentCommit, parentX1, parentX2, parentY, r, ref, results, route; - r = this.r; - ref = commit.parents; - results = []; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - parent = ref[i]; - parentCommit = this.preparedCommits[parent[0]]; - parentY = this.offsetY + this.unitTime * parentCommit.time; - parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space); - parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]); - // Set line color - if (parentCommit.space <= commit.space) { - color = this.colors[commit.space]; - } else { - color = this.colors[parentCommit.space]; - } - // Build line shape - if (parent[1] === commit.space) { - offset = [0, 5]; - arrow = "l-2,5,4,0,-2,-5,0,5"; - } else if (parent[1] < commit.space) { - offset = [3, 3]; - arrow = "l5,0,-2,4,-3,-4,4,2"; - } else { - offset = [-3, 3]; - arrow = "l-5,0,2,4,3,-4,-4,2"; - } - // Start point - route = ["M", x + offset[0], y + offset[1]]; - // Add arrow if not first parent - if (i > 0) { - route.push(arrow); - } - // Circumvent if overlap - if (commit.space !== parentCommit.space || commit.space !== parent[1]) { - route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5); - } - // End point - route.push("L", parentX1, parentY); - results.push(r.path(route).attr({ - stroke: color, - "stroke-width": 2 - })); - } - return results; - }; - - BranchGraph.prototype.markCommit = function(commit) { - var r, x, y; - if (commit.id === this.options.commit_id) { - r = this.r; - x = this.offsetX + this.unitSpace * (this.mspace - commit.space); - y = this.offsetY + this.unitTime * commit.time; - r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({ - fill: "#000", - "fill-opacity": .5, - stroke: "none" - }); - // Displayed in the center - return this.element.scrollTop(y - this.graphHeight / 2); - } - }; - - return BranchGraph; - - })(); - - Raphael.prototype.commitTooltip = function(x, y, commit) { - var boxHeight, boxWidth, icon, idText, messageText, nameText, rect, textSet, tooltip; - boxWidth = 300; - boxHeight = 200; - icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20); - nameText = this.text(x + 25, y + 10, commit.author.name); - idText = this.text(x, y + 35, commit.id); - messageText = this.text(x, y + 50, commit.message); - textSet = this.set(icon, nameText, idText, messageText).attr({ - "text-anchor": "start", - font: "12px Monaco, monospace" - }); - nameText.attr({ - font: "14px Arial", - "font-weight": "bold" - }); - idText.attr({ - fill: "#AAA" - }); - this.textWrap(messageText, boxWidth - 50); - rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ - fill: "#FFF", - stroke: "#000", - "stroke-linecap": "round", - "stroke-width": 2 - }); - tooltip = this.set(rect, textSet); - rect.attr({ - height: tooltip.getBBox().height + 10, - width: tooltip.getBBox().width + 10 - }); - tooltip.transform(["t", 20, 20]); - return tooltip; - }; - - Raphael.prototype.textWrap = function(t, width) { - var abc, b, content, h, j, len, letterWidth, s, word, words, x; - content = t.attr("text"); - abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - t.attr({ - text: abc - }); - letterWidth = t.getBBox().width / abc.length; - t.attr({ - text: content - }); - words = content.split(" "); - x = 0; - s = []; - for (j = 0, len = words.length; j < len; j++) { - word = words[j]; - if (x + (word.length * letterWidth) > width) { - s.push("\n"); - x = 0; - } - x += word.length * letterWidth; - s.push(word + " "); - } - t.attr({ - text: s.join("") - }); - b = t.getBBox(); - h = Math.abs(b.y2) - Math.abs(b.y) + 1; - return t.attr({ - y: b.y + h - }); - }; - -}).call(this); diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js new file mode 100644 index 00000000000..91132af273a --- /dev/null +++ b/app/assets/javascripts/network/branch_graph.js @@ -0,0 +1,417 @@ +(function() { + var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + this.BranchGraph = (function() { + function BranchGraph(element1, options1) { + this.element = element1; + this.options = options1; + this.scrollTop = bind(this.scrollTop, this); + this.scrollBottom = bind(this.scrollBottom, this); + this.scrollRight = bind(this.scrollRight, this); + this.scrollLeft = bind(this.scrollLeft, this); + this.scrollUp = bind(this.scrollUp, this); + this.scrollDown = bind(this.scrollDown, this); + this.preparedCommits = {}; + this.mtime = 0; + this.mspace = 0; + this.parents = {}; + this.colors = ["#000"]; + this.offsetX = 150; + this.offsetY = 20; + this.unitTime = 30; + this.unitSpace = 10; + this.prev_start = -1; + this.load(); + } + + BranchGraph.prototype.load = function() { + return $.ajax({ + url: this.options.url, + method: "get", + dataType: "json", + success: $.proxy(function(data) { + $(".loading", this.element).hide(); + this.prepareData(data.days, data.commits); + return this.buildGraph(); + }, this) + }); + }; + + BranchGraph.prototype.prepareData = function(days, commits) { + var c, ch, cw, j, len, ref; + this.days = days; + this.commits = commits; + this.collectParents(); + this.graphHeight = $(this.element).height(); + this.graphWidth = $(this.element).width(); + ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150); + cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300); + this.r = Raphael(this.element.get(0), cw, ch); + this.top = this.r.set(); + this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320); + ref = this.commits; + for (j = 0, len = ref.length; j < len; j++) { + c = ref[j]; + if (c.id in this.parents) { + c.isParent = true; + } + this.preparedCommits[c.id] = c; + this.markCommit(c); + } + return this.collectColors(); + }; + + BranchGraph.prototype.collectParents = function() { + var c, j, len, p, ref, results; + ref = this.commits; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + c = ref[j]; + this.mtime = Math.max(this.mtime, c.time); + this.mspace = Math.max(this.mspace, c.space); + results.push((function() { + var l, len1, ref1, results1; + ref1 = c.parents; + results1 = []; + for (l = 0, len1 = ref1.length; l < len1; l++) { + p = ref1[l]; + this.parents[p[0]] = true; + results1.push(this.mspace = Math.max(this.mspace, p[1])); + } + return results1; + }).call(this)); + } + return results; + }; + + BranchGraph.prototype.collectColors = function() { + var k, results; + k = 0; + results = []; + while (k < this.mspace) { + this.colors.push(Raphael.getColor(.8)); + // Skipping a few colors in the spectrum to get more contrast between colors + Raphael.getColor(); + Raphael.getColor(); + results.push(k++); + } + return results; + }; + + BranchGraph.prototype.buildGraph = function() { + var cuday, cumonth, day, j, len, mm, r, ref; + r = this.r; + cuday = 0; + cumonth = ""; + r.rect(0, 0, 40, this.barHeight).attr({ + fill: "#222" + }); + r.rect(40, 0, 30, this.barHeight).attr({ + fill: "#444" + }); + ref = this.days; + for (mm = j = 0, len = ref.length; j < len; mm = ++j) { + day = ref[mm]; + if (cuday !== day[0] || cumonth !== day[1]) { + // Dates + r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({ + font: "12px Monaco, monospace", + fill: "#BBB" + }); + cuday = day[0]; + } + if (cumonth !== day[1]) { + // Months + r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({ + font: "12px Monaco, monospace", + fill: "#EEE" + }); + cumonth = day[1]; + } + } + this.renderPartialGraph(); + return this.bindEvents(); + }; + + BranchGraph.prototype.renderPartialGraph = function() { + var commit, end, i, isGraphEdge, start, x, y; + start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10; + if (start < 0) { + isGraphEdge = true; + start = 0; + } + end = start + 40; + if (this.commits.length < end) { + isGraphEdge = true; + end = this.commits.length; + } + if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) { + i = start; + this.prev_start = start; + while (i < end) { + commit = this.commits[i]; + i += 1; + if (commit.hasDrawn !== true) { + x = this.offsetX + this.unitSpace * (this.mspace - commit.space); + y = this.offsetY + this.unitTime * commit.time; + this.drawDot(x, y, commit); + this.drawLines(x, y, commit); + this.appendLabel(x, y, commit); + this.appendAnchor(x, y, commit); + commit.hasDrawn = true; + } + } + return this.top.toFront(); + } + }; + + BranchGraph.prototype.bindEvents = function() { + var element; + element = this.element; + return $(element).scroll((function(_this) { + return function(event) { + return _this.renderPartialGraph(); + }; + })(this)); + }; + + BranchGraph.prototype.scrollDown = function() { + this.element.scrollTop(this.element.scrollTop() + 50); + return this.renderPartialGraph(); + }; + + BranchGraph.prototype.scrollUp = function() { + this.element.scrollTop(this.element.scrollTop() - 50); + return this.renderPartialGraph(); + }; + + BranchGraph.prototype.scrollLeft = function() { + this.element.scrollLeft(this.element.scrollLeft() - 50); + return this.renderPartialGraph(); + }; + + BranchGraph.prototype.scrollRight = function() { + this.element.scrollLeft(this.element.scrollLeft() + 50); + return this.renderPartialGraph(); + }; + + BranchGraph.prototype.scrollBottom = function() { + return this.element.scrollTop(this.element.find('svg').height()); + }; + + BranchGraph.prototype.scrollTop = function() { + return this.element.scrollTop(0); + }; + + BranchGraph.prototype.appendLabel = function(x, y, commit) { + var label, r, rect, shortrefs, text, textbox, triangle; + if (!commit.refs) { + return; + } + r = this.r; + shortrefs = commit.refs; + // Truncate if longer than 15 chars + if (shortrefs.length > 17) { + shortrefs = shortrefs.substr(0, 15) + "…"; + } + text = r.text(x + 4, y, shortrefs).attr({ + "text-anchor": "start", + font: "10px Monaco, monospace", + fill: "#FFF", + title: commit.refs + }); + textbox = text.getBBox(); + // Create rectangle based on the size of the textbox + rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({ + fill: "#000", + "fill-opacity": .5, + stroke: "none" + }); + triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({ + fill: "#000", + "fill-opacity": .5, + stroke: "none" + }); + label = r.set(rect, text); + label.transform(["t", -rect.getBBox().width - 15, 0]); + // Set text to front + return text.toFront(); + }; + + BranchGraph.prototype.appendAnchor = function(x, y, commit) { + var anchor, options, r, top; + r = this.r; + top = this.top; + options = this.options; + anchor = r.circle(x, y, 10).attr({ + fill: "#000", + opacity: 0, + cursor: "pointer" + }).click(function() { + return window.open(options.commit_url.replace("%s", commit.id), "_blank"); + }).hover(function() { + this.tooltip = r.commitTooltip(x + 5, y, commit); + return top.push(this.tooltip.insertBefore(this)); + }, function() { + return this.tooltip && this.tooltip.remove() && delete this.tooltip; + }); + return top.push(anchor); + }; + + BranchGraph.prototype.drawDot = function(x, y, commit) { + var avatar_box_x, avatar_box_y, r; + r = this.r; + r.circle(x, y, 3).attr({ + fill: this.colors[commit.space], + stroke: "none" + }); + avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10; + avatar_box_y = y - 10; + r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({ + stroke: this.colors[commit.space], + "stroke-width": 2 + }); + r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20); + return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({ + "text-anchor": "start", + font: "14px Monaco, monospace" + }); + }; + + BranchGraph.prototype.drawLines = function(x, y, commit) { + var arrow, color, i, j, len, offset, parent, parentCommit, parentX1, parentX2, parentY, r, ref, results, route; + r = this.r; + ref = commit.parents; + results = []; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + parent = ref[i]; + parentCommit = this.preparedCommits[parent[0]]; + parentY = this.offsetY + this.unitTime * parentCommit.time; + parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space); + parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]); + // Set line color + if (parentCommit.space <= commit.space) { + color = this.colors[commit.space]; + } else { + color = this.colors[parentCommit.space]; + } + // Build line shape + if (parent[1] === commit.space) { + offset = [0, 5]; + arrow = "l-2,5,4,0,-2,-5,0,5"; + } else if (parent[1] < commit.space) { + offset = [3, 3]; + arrow = "l5,0,-2,4,-3,-4,4,2"; + } else { + offset = [-3, 3]; + arrow = "l-5,0,2,4,3,-4,-4,2"; + } + // Start point + route = ["M", x + offset[0], y + offset[1]]; + // Add arrow if not first parent + if (i > 0) { + route.push(arrow); + } + // Circumvent if overlap + if (commit.space !== parentCommit.space || commit.space !== parent[1]) { + route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5); + } + // End point + route.push("L", parentX1, parentY); + results.push(r.path(route).attr({ + stroke: color, + "stroke-width": 2 + })); + } + return results; + }; + + BranchGraph.prototype.markCommit = function(commit) { + var r, x, y; + if (commit.id === this.options.commit_id) { + r = this.r; + x = this.offsetX + this.unitSpace * (this.mspace - commit.space); + y = this.offsetY + this.unitTime * commit.time; + r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({ + fill: "#000", + "fill-opacity": .5, + stroke: "none" + }); + // Displayed in the center + return this.element.scrollTop(y - this.graphHeight / 2); + } + }; + + return BranchGraph; + + })(); + + Raphael.prototype.commitTooltip = function(x, y, commit) { + var boxHeight, boxWidth, icon, idText, messageText, nameText, rect, textSet, tooltip; + boxWidth = 300; + boxHeight = 200; + icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20); + nameText = this.text(x + 25, y + 10, commit.author.name); + idText = this.text(x, y + 35, commit.id); + messageText = this.text(x, y + 50, commit.message); + textSet = this.set(icon, nameText, idText, messageText).attr({ + "text-anchor": "start", + font: "12px Monaco, monospace" + }); + nameText.attr({ + font: "14px Arial", + "font-weight": "bold" + }); + idText.attr({ + fill: "#AAA" + }); + this.textWrap(messageText, boxWidth - 50); + rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ + fill: "#FFF", + stroke: "#000", + "stroke-linecap": "round", + "stroke-width": 2 + }); + tooltip = this.set(rect, textSet); + rect.attr({ + height: tooltip.getBBox().height + 10, + width: tooltip.getBBox().width + 10 + }); + tooltip.transform(["t", 20, 20]); + return tooltip; + }; + + Raphael.prototype.textWrap = function(t, width) { + var abc, b, content, h, j, len, letterWidth, s, word, words, x; + content = t.attr("text"); + abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + t.attr({ + text: abc + }); + letterWidth = t.getBBox().width / abc.length; + t.attr({ + text: content + }); + words = content.split(" "); + x = 0; + s = []; + for (j = 0, len = words.length; j < len; j++) { + word = words[j]; + if (x + (word.length * letterWidth) > width) { + s.push("\n"); + x = 0; + } + x += word.length * letterWidth; + s.push(word + " "); + } + t.attr({ + text: s.join("") + }); + b = t.getBBox(); + h = Math.abs(b.y2) - Math.abs(b.y) + 1; + return t.attr({ + y: b.y + h + }); + }; + +}).call(this); -- cgit v1.2.1 From f60e0b92b8cba1d70c40094511a87fc0ed62977d Mon Sep 17 00:00:00 2001 From: TMate Software Support Date: Mon, 10 Oct 2016 18:14:29 +0000 Subject: Update migrating_from_svn.md document on migration from SVN to GitLab as suggested by @axil in MR 6549. --- doc/workflow/importing/migrating_from_svn.md | 52 ++++++++++++++++------------ 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index 76839315c53..fc27a38f735 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -8,21 +8,16 @@ between the two, for more information consult your favorite search engine. There are two approaches to SVN to Git migration: -#### [Git/SVN Mirror](#mirror) +1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which: + - Makes the GitLab repository to mirror the SVN project. + - Git and SVN repositories are kept in sync; you can use either one. + - Smoothens the migration process and allows to manage migration risks. - Make GitLab repository mirror SVN project. - - Git and SVN project are kept in sync; use either one or another. - - Smoothens migration process and allows to manage migration risks. +1. [Cut over migration](#cut-over-migration-with-svn2git) which: + - Translates and imports the existing data and history from SVN to Git. + - Is a fire and forget approach, good for smaller teams. -#### [Cut over migration](#cutover) - - Translate existing data and history from SVN to Git. - - Fire and forget approach, good for smaller teams. - -## Smooth migration with a Git/SVN mirror using SubGit +## Smooth migration with a Git/SVN mirror using SubGit #### Prerequisites @@ -37,20 +32,32 @@ at `/opt/subgit-VERSION/bin/subgit` #### Configuration In GitLab create new empty repository. In filesystem it will be located at -`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path. +`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path by default. +For convenice, assign this path to a variable: + +``` +GIT_REPOS_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git +``` + +SubGit will keep this repository will be kept in sync with a remote SVN project. +For convenience, assign remote SVN project URL to a variable: + +``` +SVN_PROJECT_URL=http://svn.company.com/repos/project +``` Run SubGit to set up a Git/SVN mirror. Make sure `subgit` command is ran -on behalf of the same user that runs GitLab. +on behalf of the same user that keeps ownership of GitLab Git repositories (`git` by default): ``` -subgit configure --layout auto SVN_PROJECT_URL GIT_REPOS_PATH +subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPOS_PATH ``` Adjust authors and branches mappings, if necessary: ``` -edit GIT_REPOS_PATH/subgit/authors.txt -edit GIT_REPOS_PATH/subgit/config +edit $GIT_REPOS_PATH/subgit/authors.txt +edit $GIT_REPOS_PATH/subgit/config ``` For more information regarding SubGit configuration options, refer to @@ -62,7 +69,7 @@ Run `subgit` to perform initial translation of existing SVN revisions into Git repository: ``` -subgit install GIT_REPOS_PATH +subgit install $GIT_REPOS_PATH ``` After initial translation is completed, GitLab Git repository and SVN project @@ -74,7 +81,7 @@ Would you prefer to perform one-time cut over migration with `subgit` use `import` command in place of `install`: ``` -subgit import GIT_REPOS_PATH +subgit import $GIT_REPOS_PATH ``` #### Licensing @@ -87,10 +94,9 @@ progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). #### Support -For any questions related to SVN to GitLab migration with SubGit contact us at [support@subgit.com](mailto:support@subgit.com). We support -all our users. +For any questions related to SVN to GitLab migration with SubGit you can contact SubGit team at [support@subgit.com](mailto:support@subgit.com). -## Cut over migration with svn2git +## Cut over migration with svn2git If you are currently using an SVN repository, you can migrate the repository to Git and GitLab. We recommend a hard cut over - run the migration command once -- cgit v1.2.1 From eecccf5e206ffdac6d29f757804165654324ea31 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 21:36:30 +0200 Subject: Change img dir name --- doc/gitlab-basics/add-file.md | 10 +++++----- doc/gitlab-basics/add-merge-request.md | 16 ++++++++-------- .../basicsimages/add_new_merge_request.png | Bin 9003 -> 0 bytes doc/gitlab-basics/basicsimages/add_sshkey.png | Bin 1394 -> 0 bytes doc/gitlab-basics/basicsimages/branch_info.png | Bin 7572 -> 0 bytes doc/gitlab-basics/basicsimages/branch_name.png | Bin 2137 -> 0 bytes doc/gitlab-basics/basicsimages/branches.png | Bin 3548 -> 0 bytes doc/gitlab-basics/basicsimages/button-create-mr.png | Bin 5927 -> 0 bytes doc/gitlab-basics/basicsimages/click-on-new-group.png | Bin 1957 -> 0 bytes doc/gitlab-basics/basicsimages/commit_changes.png | Bin 4941 -> 0 bytes doc/gitlab-basics/basicsimages/commit_message.png | Bin 5103 -> 0 bytes doc/gitlab-basics/basicsimages/commits.png | Bin 4112 -> 0 bytes doc/gitlab-basics/basicsimages/compare_branches.png | Bin 1520 -> 0 bytes doc/gitlab-basics/basicsimages/create_file.png | Bin 2451 -> 0 bytes doc/gitlab-basics/basicsimages/create_group.png | Bin 3184 -> 0 bytes doc/gitlab-basics/basicsimages/edit_file.png | Bin 2221 -> 0 bytes doc/gitlab-basics/basicsimages/file_located.png | Bin 3078 -> 0 bytes doc/gitlab-basics/basicsimages/file_name.png | Bin 2412 -> 0 bytes doc/gitlab-basics/basicsimages/find_file.png | Bin 8426 -> 0 bytes doc/gitlab-basics/basicsimages/find_group.png | Bin 5897 -> 0 bytes doc/gitlab-basics/basicsimages/fork.png | Bin 896 -> 0 bytes doc/gitlab-basics/basicsimages/group_info.png | Bin 15479 -> 0 bytes doc/gitlab-basics/basicsimages/groups.png | Bin 4752 -> 0 bytes doc/gitlab-basics/basicsimages/https.png | Bin 2822 -> 0 bytes doc/gitlab-basics/basicsimages/image_file.png | Bin 2796 -> 0 bytes doc/gitlab-basics/basicsimages/issue_title.png | Bin 8311 -> 0 bytes doc/gitlab-basics/basicsimages/issues.png | Bin 4153 -> 0 bytes doc/gitlab-basics/basicsimages/key.png | Bin 1177 -> 0 bytes doc/gitlab-basics/basicsimages/merge_requests.png | Bin 4213 -> 0 bytes doc/gitlab-basics/basicsimages/new_issue.png | Bin 2974 -> 0 bytes doc/gitlab-basics/basicsimages/new_merge_request.png | Bin 3162 -> 0 bytes doc/gitlab-basics/basicsimages/new_project.png | Bin 2234 -> 0 bytes doc/gitlab-basics/basicsimages/newbranch.png | Bin 1244 -> 0 bytes doc/gitlab-basics/basicsimages/paste_sshkey.png | Bin 7699 -> 0 bytes doc/gitlab-basics/basicsimages/profile_settings.png | Bin 1101 -> 0 bytes doc/gitlab-basics/basicsimages/project_info.png | Bin 21041 -> 0 bytes doc/gitlab-basics/basicsimages/public_file_link.png | Bin 3023 -> 0 bytes doc/gitlab-basics/basicsimages/select-group.png | Bin 6034 -> 0 bytes doc/gitlab-basics/basicsimages/select-group2.png | Bin 5040 -> 0 bytes doc/gitlab-basics/basicsimages/select_branch.png | Bin 11207 -> 0 bytes doc/gitlab-basics/basicsimages/select_project.png | Bin 16176 -> 0 bytes doc/gitlab-basics/basicsimages/settings.png | Bin 4149 -> 0 bytes doc/gitlab-basics/basicsimages/shh_keys.png | Bin 4782 -> 0 bytes doc/gitlab-basics/basicsimages/submit_new_issue.png | Bin 8644 -> 0 bytes doc/gitlab-basics/basicsimages/title_description_mr.png | Bin 11919 -> 0 bytes doc/gitlab-basics/basicsimages/white_space.png | Bin 2192 -> 0 bytes doc/gitlab-basics/command-line-commands.md | 4 ++-- doc/gitlab-basics/create-branch.md | 12 ++++++------ doc/gitlab-basics/create-group.md | 10 +++++----- doc/gitlab-basics/create-issue.md | 10 +++++----- doc/gitlab-basics/create-project.md | 4 ++-- doc/gitlab-basics/create-your-ssh-keys.md | 8 ++++---- doc/gitlab-basics/fork-project.md | 4 ++-- doc/gitlab-basics/img/add_new_merge_request.png | Bin 0 -> 9003 bytes doc/gitlab-basics/img/add_sshkey.png | Bin 0 -> 1394 bytes doc/gitlab-basics/img/branch_info.png | Bin 0 -> 7572 bytes doc/gitlab-basics/img/branch_name.png | Bin 0 -> 2137 bytes doc/gitlab-basics/img/branches.png | Bin 0 -> 3548 bytes doc/gitlab-basics/img/button-create-mr.png | Bin 0 -> 5927 bytes doc/gitlab-basics/img/click-on-new-group.png | Bin 0 -> 1957 bytes doc/gitlab-basics/img/commit_changes.png | Bin 0 -> 4941 bytes doc/gitlab-basics/img/commit_message.png | Bin 0 -> 5103 bytes doc/gitlab-basics/img/commits.png | Bin 0 -> 4112 bytes doc/gitlab-basics/img/compare_branches.png | Bin 0 -> 1520 bytes doc/gitlab-basics/img/create_file.png | Bin 0 -> 2451 bytes doc/gitlab-basics/img/create_group.png | Bin 0 -> 3184 bytes doc/gitlab-basics/img/edit_file.png | Bin 0 -> 2221 bytes doc/gitlab-basics/img/file_located.png | Bin 0 -> 3078 bytes doc/gitlab-basics/img/file_name.png | Bin 0 -> 2412 bytes doc/gitlab-basics/img/find_file.png | Bin 0 -> 8426 bytes doc/gitlab-basics/img/find_group.png | Bin 0 -> 5897 bytes doc/gitlab-basics/img/fork.png | Bin 0 -> 896 bytes doc/gitlab-basics/img/group_info.png | Bin 0 -> 15479 bytes doc/gitlab-basics/img/groups.png | Bin 0 -> 4752 bytes doc/gitlab-basics/img/https.png | Bin 0 -> 2822 bytes doc/gitlab-basics/img/image_file.png | Bin 0 -> 2796 bytes doc/gitlab-basics/img/issue_title.png | Bin 0 -> 8311 bytes doc/gitlab-basics/img/issues.png | Bin 0 -> 4153 bytes doc/gitlab-basics/img/key.png | Bin 0 -> 1177 bytes doc/gitlab-basics/img/merge_requests.png | Bin 0 -> 4213 bytes doc/gitlab-basics/img/new_issue.png | Bin 0 -> 2974 bytes doc/gitlab-basics/img/new_merge_request.png | Bin 0 -> 3162 bytes doc/gitlab-basics/img/new_project.png | Bin 0 -> 2234 bytes doc/gitlab-basics/img/newbranch.png | Bin 0 -> 1244 bytes doc/gitlab-basics/img/paste_sshkey.png | Bin 0 -> 7699 bytes doc/gitlab-basics/img/profile_settings.png | Bin 0 -> 1101 bytes doc/gitlab-basics/img/project_info.png | Bin 0 -> 21041 bytes doc/gitlab-basics/img/public_file_link.png | Bin 0 -> 3023 bytes doc/gitlab-basics/img/select-group.png | Bin 0 -> 6034 bytes doc/gitlab-basics/img/select-group2.png | Bin 0 -> 5040 bytes doc/gitlab-basics/img/select_branch.png | Bin 0 -> 11207 bytes doc/gitlab-basics/img/select_project.png | Bin 0 -> 16176 bytes doc/gitlab-basics/img/settings.png | Bin 0 -> 4149 bytes doc/gitlab-basics/img/shh_keys.png | Bin 0 -> 4782 bytes doc/gitlab-basics/img/submit_new_issue.png | Bin 0 -> 8644 bytes doc/gitlab-basics/img/title_description_mr.png | Bin 0 -> 11919 bytes doc/gitlab-basics/img/white_space.png | Bin 0 -> 2192 bytes 97 files changed, 39 insertions(+), 39 deletions(-) delete mode 100644 doc/gitlab-basics/basicsimages/add_new_merge_request.png delete mode 100644 doc/gitlab-basics/basicsimages/add_sshkey.png delete mode 100644 doc/gitlab-basics/basicsimages/branch_info.png delete mode 100644 doc/gitlab-basics/basicsimages/branch_name.png delete mode 100644 doc/gitlab-basics/basicsimages/branches.png delete mode 100644 doc/gitlab-basics/basicsimages/button-create-mr.png delete mode 100644 doc/gitlab-basics/basicsimages/click-on-new-group.png delete mode 100644 doc/gitlab-basics/basicsimages/commit_changes.png delete mode 100644 doc/gitlab-basics/basicsimages/commit_message.png delete mode 100644 doc/gitlab-basics/basicsimages/commits.png delete mode 100644 doc/gitlab-basics/basicsimages/compare_branches.png delete mode 100644 doc/gitlab-basics/basicsimages/create_file.png delete mode 100644 doc/gitlab-basics/basicsimages/create_group.png delete mode 100644 doc/gitlab-basics/basicsimages/edit_file.png delete mode 100644 doc/gitlab-basics/basicsimages/file_located.png delete mode 100644 doc/gitlab-basics/basicsimages/file_name.png delete mode 100644 doc/gitlab-basics/basicsimages/find_file.png delete mode 100644 doc/gitlab-basics/basicsimages/find_group.png delete mode 100644 doc/gitlab-basics/basicsimages/fork.png delete mode 100644 doc/gitlab-basics/basicsimages/group_info.png delete mode 100644 doc/gitlab-basics/basicsimages/groups.png delete mode 100644 doc/gitlab-basics/basicsimages/https.png delete mode 100644 doc/gitlab-basics/basicsimages/image_file.png delete mode 100644 doc/gitlab-basics/basicsimages/issue_title.png delete mode 100644 doc/gitlab-basics/basicsimages/issues.png delete mode 100644 doc/gitlab-basics/basicsimages/key.png delete mode 100644 doc/gitlab-basics/basicsimages/merge_requests.png delete mode 100644 doc/gitlab-basics/basicsimages/new_issue.png delete mode 100644 doc/gitlab-basics/basicsimages/new_merge_request.png delete mode 100644 doc/gitlab-basics/basicsimages/new_project.png delete mode 100644 doc/gitlab-basics/basicsimages/newbranch.png delete mode 100644 doc/gitlab-basics/basicsimages/paste_sshkey.png delete mode 100644 doc/gitlab-basics/basicsimages/profile_settings.png delete mode 100644 doc/gitlab-basics/basicsimages/project_info.png delete mode 100644 doc/gitlab-basics/basicsimages/public_file_link.png delete mode 100644 doc/gitlab-basics/basicsimages/select-group.png delete mode 100644 doc/gitlab-basics/basicsimages/select-group2.png delete mode 100644 doc/gitlab-basics/basicsimages/select_branch.png delete mode 100644 doc/gitlab-basics/basicsimages/select_project.png delete mode 100644 doc/gitlab-basics/basicsimages/settings.png delete mode 100644 doc/gitlab-basics/basicsimages/shh_keys.png delete mode 100644 doc/gitlab-basics/basicsimages/submit_new_issue.png delete mode 100644 doc/gitlab-basics/basicsimages/title_description_mr.png delete mode 100644 doc/gitlab-basics/basicsimages/white_space.png create mode 100644 doc/gitlab-basics/img/add_new_merge_request.png create mode 100644 doc/gitlab-basics/img/add_sshkey.png create mode 100644 doc/gitlab-basics/img/branch_info.png create mode 100644 doc/gitlab-basics/img/branch_name.png create mode 100644 doc/gitlab-basics/img/branches.png create mode 100644 doc/gitlab-basics/img/button-create-mr.png create mode 100644 doc/gitlab-basics/img/click-on-new-group.png create mode 100644 doc/gitlab-basics/img/commit_changes.png create mode 100644 doc/gitlab-basics/img/commit_message.png create mode 100644 doc/gitlab-basics/img/commits.png create mode 100644 doc/gitlab-basics/img/compare_branches.png create mode 100644 doc/gitlab-basics/img/create_file.png create mode 100644 doc/gitlab-basics/img/create_group.png create mode 100644 doc/gitlab-basics/img/edit_file.png create mode 100644 doc/gitlab-basics/img/file_located.png create mode 100644 doc/gitlab-basics/img/file_name.png create mode 100644 doc/gitlab-basics/img/find_file.png create mode 100644 doc/gitlab-basics/img/find_group.png create mode 100644 doc/gitlab-basics/img/fork.png create mode 100644 doc/gitlab-basics/img/group_info.png create mode 100644 doc/gitlab-basics/img/groups.png create mode 100644 doc/gitlab-basics/img/https.png create mode 100644 doc/gitlab-basics/img/image_file.png create mode 100644 doc/gitlab-basics/img/issue_title.png create mode 100644 doc/gitlab-basics/img/issues.png create mode 100644 doc/gitlab-basics/img/key.png create mode 100644 doc/gitlab-basics/img/merge_requests.png create mode 100644 doc/gitlab-basics/img/new_issue.png create mode 100644 doc/gitlab-basics/img/new_merge_request.png create mode 100644 doc/gitlab-basics/img/new_project.png create mode 100644 doc/gitlab-basics/img/newbranch.png create mode 100644 doc/gitlab-basics/img/paste_sshkey.png create mode 100644 doc/gitlab-basics/img/profile_settings.png create mode 100644 doc/gitlab-basics/img/project_info.png create mode 100644 doc/gitlab-basics/img/public_file_link.png create mode 100644 doc/gitlab-basics/img/select-group.png create mode 100644 doc/gitlab-basics/img/select-group2.png create mode 100644 doc/gitlab-basics/img/select_branch.png create mode 100644 doc/gitlab-basics/img/select_project.png create mode 100644 doc/gitlab-basics/img/settings.png create mode 100644 doc/gitlab-basics/img/shh_keys.png create mode 100644 doc/gitlab-basics/img/submit_new_issue.png create mode 100644 doc/gitlab-basics/img/title_description_mr.png create mode 100644 doc/gitlab-basics/img/white_space.png diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index ff10a98e8f5..86aff749ff1 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -6,22 +6,22 @@ To create a file in GitLab, sign in to GitLab. Select a project on the right side of your screen: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) It's a good idea to [create a branch](create-branch.md), but it's not necessary. Go to the directory where you'd like to add the file and click on the "+" sign next to the name of the project and directory: -![Create a file](basicsimages/create_file.png) +![Create a file](img/create_file.png) Name your file (you can't add spaces, so you can use hyphens or underscores). Don't forget to include the markup language you'd like to use : -![File name](basicsimages/file_name.png) +![File name](img/file_name.png) Add all the information that you'd like to include in your file: -![Add information](basicsimages/white_space.png) +![Add information](img/white_space.png) Add a commit message based on what you just added and then click on "commit changes": -![Commit changes](basicsimages/commit_changes.png) +![Commit changes](img/commit_changes.png) diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md index 236b4248ea2..082d1408aa2 100644 --- a/doc/gitlab-basics/add-merge-request.md +++ b/doc/gitlab-basics/add-merge-request.md @@ -6,31 +6,31 @@ To create a new Merge Request, sign in to GitLab. Go to the project where you'd like to merge your changes: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on "Merge Requests" on the left side of your screen: -![Merge requests](basicsimages/merge_requests.png) +![Merge requests](img/merge_requests.png) Click on "+ new Merge Request" on the right side of the screen: -![New Merge Request](basicsimages/new_merge_request.png) +![New Merge Request](img/new_merge_request.png) Select a source branch or branch: -![Select a branch](basicsimages/select_branch.png) +![Select a branch](img/select_branch.png) Click on the "compare branches" button: -![Compare branches](basicsimages/compare_branches.png) +![Compare branches](img/compare_branches.png) Add a title and a description to your Merge Request: -![Add a title and description](basicsimages/title_description_mr.png) +![Add a title and description](img/title_description_mr.png) Select a user to review your Merge Request and to accept or close it. You may also select milestones and labels (they are optional). Then click on the "submit new Merge Request" button: -![Add a new merge request](basicsimages/add_new_merge_request.png) +![Add a new merge request](img/add_new_merge_request.png) Your Merge Request will be ready to be approved and published. @@ -39,4 +39,4 @@ Your Merge Request will be ready to be approved and published. After you created a new branch, you'll immediately find a "create a Merge Request" button at the top of your screen. You may automatically create a Merge Request from your recently created branch when clicking on this button: -![Automatic MR button](basicsimages/button-create-mr.png) +![Automatic MR button](img/button-create-mr.png) diff --git a/doc/gitlab-basics/basicsimages/add_new_merge_request.png b/doc/gitlab-basics/basicsimages/add_new_merge_request.png deleted file mode 100644 index e60992c4c6a..00000000000 Binary files a/doc/gitlab-basics/basicsimages/add_new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/add_sshkey.png b/doc/gitlab-basics/basicsimages/add_sshkey.png deleted file mode 100644 index 89c86018629..00000000000 Binary files a/doc/gitlab-basics/basicsimages/add_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/branch_info.png b/doc/gitlab-basics/basicsimages/branch_info.png deleted file mode 100644 index 2264f3c5bf2..00000000000 Binary files a/doc/gitlab-basics/basicsimages/branch_info.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/branch_name.png b/doc/gitlab-basics/basicsimages/branch_name.png deleted file mode 100644 index 75fe8313611..00000000000 Binary files a/doc/gitlab-basics/basicsimages/branch_name.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/branches.png b/doc/gitlab-basics/basicsimages/branches.png deleted file mode 100644 index 8621bc05776..00000000000 Binary files a/doc/gitlab-basics/basicsimages/branches.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/button-create-mr.png b/doc/gitlab-basics/basicsimages/button-create-mr.png deleted file mode 100644 index b52ab148839..00000000000 Binary files a/doc/gitlab-basics/basicsimages/button-create-mr.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/click-on-new-group.png b/doc/gitlab-basics/basicsimages/click-on-new-group.png deleted file mode 100644 index 6450deec6fc..00000000000 Binary files a/doc/gitlab-basics/basicsimages/click-on-new-group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/commit_changes.png b/doc/gitlab-basics/basicsimages/commit_changes.png deleted file mode 100644 index a88809c5a3f..00000000000 Binary files a/doc/gitlab-basics/basicsimages/commit_changes.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/commit_message.png b/doc/gitlab-basics/basicsimages/commit_message.png deleted file mode 100644 index 4abe4517f98..00000000000 Binary files a/doc/gitlab-basics/basicsimages/commit_message.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/commits.png b/doc/gitlab-basics/basicsimages/commits.png deleted file mode 100644 index 2bfcaf75f01..00000000000 Binary files a/doc/gitlab-basics/basicsimages/commits.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/compare_branches.png b/doc/gitlab-basics/basicsimages/compare_branches.png deleted file mode 100644 index 8a18453dd05..00000000000 Binary files a/doc/gitlab-basics/basicsimages/compare_branches.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/create_file.png b/doc/gitlab-basics/basicsimages/create_file.png deleted file mode 100644 index 5ebe1b227dd..00000000000 Binary files a/doc/gitlab-basics/basicsimages/create_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/create_group.png b/doc/gitlab-basics/basicsimages/create_group.png deleted file mode 100644 index 7ecc3baa990..00000000000 Binary files a/doc/gitlab-basics/basicsimages/create_group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/edit_file.png b/doc/gitlab-basics/basicsimages/edit_file.png deleted file mode 100644 index 9d3e817d036..00000000000 Binary files a/doc/gitlab-basics/basicsimages/edit_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/file_located.png b/doc/gitlab-basics/basicsimages/file_located.png deleted file mode 100644 index e357cb5c6ab..00000000000 Binary files a/doc/gitlab-basics/basicsimages/file_located.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/file_name.png b/doc/gitlab-basics/basicsimages/file_name.png deleted file mode 100644 index 01639c77d0d..00000000000 Binary files a/doc/gitlab-basics/basicsimages/file_name.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/find_file.png b/doc/gitlab-basics/basicsimages/find_file.png deleted file mode 100644 index 6f26d26ae18..00000000000 Binary files a/doc/gitlab-basics/basicsimages/find_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/find_group.png b/doc/gitlab-basics/basicsimages/find_group.png deleted file mode 100644 index 1211510aae9..00000000000 Binary files a/doc/gitlab-basics/basicsimages/find_group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/fork.png b/doc/gitlab-basics/basicsimages/fork.png deleted file mode 100644 index 13ff8345616..00000000000 Binary files a/doc/gitlab-basics/basicsimages/fork.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/group_info.png b/doc/gitlab-basics/basicsimages/group_info.png deleted file mode 100644 index 2507d6c295b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/group_info.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/groups.png b/doc/gitlab-basics/basicsimages/groups.png deleted file mode 100644 index ef3dca60cc8..00000000000 Binary files a/doc/gitlab-basics/basicsimages/groups.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/https.png b/doc/gitlab-basics/basicsimages/https.png deleted file mode 100644 index e74dbc13f9a..00000000000 Binary files a/doc/gitlab-basics/basicsimages/https.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/image_file.png b/doc/gitlab-basics/basicsimages/image_file.png deleted file mode 100644 index 7f304b8e1f2..00000000000 Binary files a/doc/gitlab-basics/basicsimages/image_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/issue_title.png b/doc/gitlab-basics/basicsimages/issue_title.png deleted file mode 100644 index 60a6f7973be..00000000000 Binary files a/doc/gitlab-basics/basicsimages/issue_title.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/issues.png b/doc/gitlab-basics/basicsimages/issues.png deleted file mode 100644 index 14e9cdb64e1..00000000000 Binary files a/doc/gitlab-basics/basicsimages/issues.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/key.png b/doc/gitlab-basics/basicsimages/key.png deleted file mode 100644 index 04400173ce8..00000000000 Binary files a/doc/gitlab-basics/basicsimages/key.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/merge_requests.png b/doc/gitlab-basics/basicsimages/merge_requests.png deleted file mode 100644 index 570164df18b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/merge_requests.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/new_issue.png b/doc/gitlab-basics/basicsimages/new_issue.png deleted file mode 100644 index 94e7503dd8b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_issue.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/new_merge_request.png b/doc/gitlab-basics/basicsimages/new_merge_request.png deleted file mode 100644 index 842f5ebed74..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/new_project.png b/doc/gitlab-basics/basicsimages/new_project.png deleted file mode 100644 index 421e8bc247b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_project.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/newbranch.png b/doc/gitlab-basics/basicsimages/newbranch.png deleted file mode 100644 index d5fcf33c4ea..00000000000 Binary files a/doc/gitlab-basics/basicsimages/newbranch.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/paste_sshkey.png b/doc/gitlab-basics/basicsimages/paste_sshkey.png deleted file mode 100644 index 578ebee4440..00000000000 Binary files a/doc/gitlab-basics/basicsimages/paste_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/profile_settings.png b/doc/gitlab-basics/basicsimages/profile_settings.png deleted file mode 100644 index cb3f79f1879..00000000000 Binary files a/doc/gitlab-basics/basicsimages/profile_settings.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/project_info.png b/doc/gitlab-basics/basicsimages/project_info.png deleted file mode 100644 index e1adb8d48c2..00000000000 Binary files a/doc/gitlab-basics/basicsimages/project_info.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/public_file_link.png b/doc/gitlab-basics/basicsimages/public_file_link.png deleted file mode 100644 index f60df6807f4..00000000000 Binary files a/doc/gitlab-basics/basicsimages/public_file_link.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select-group.png b/doc/gitlab-basics/basicsimages/select-group.png deleted file mode 100644 index 33b978dd899..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select-group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select-group2.png b/doc/gitlab-basics/basicsimages/select-group2.png deleted file mode 100644 index aee22c638db..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select-group2.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select_branch.png b/doc/gitlab-basics/basicsimages/select_branch.png deleted file mode 100644 index f72a3ffb57f..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select_branch.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select_project.png b/doc/gitlab-basics/basicsimages/select_project.png deleted file mode 100644 index 3bb832ea8d0..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select_project.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/settings.png b/doc/gitlab-basics/basicsimages/settings.png deleted file mode 100644 index 78637013d9b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/settings.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/shh_keys.png b/doc/gitlab-basics/basicsimages/shh_keys.png deleted file mode 100644 index c87f11a9d3d..00000000000 Binary files a/doc/gitlab-basics/basicsimages/shh_keys.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/submit_new_issue.png b/doc/gitlab-basics/basicsimages/submit_new_issue.png deleted file mode 100644 index 78b854c8903..00000000000 Binary files a/doc/gitlab-basics/basicsimages/submit_new_issue.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/title_description_mr.png b/doc/gitlab-basics/basicsimages/title_description_mr.png deleted file mode 100644 index c31d61ec336..00000000000 Binary files a/doc/gitlab-basics/basicsimages/title_description_mr.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/white_space.png b/doc/gitlab-basics/basicsimages/white_space.png deleted file mode 100644 index eaa969bdcf4..00000000000 Binary files a/doc/gitlab-basics/basicsimages/white_space.png and /dev/null differ diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md index addd3b6b6eb..253edc8a7e8 100644 --- a/doc/gitlab-basics/command-line-commands.md +++ b/doc/gitlab-basics/command-line-commands.md @@ -6,11 +6,11 @@ In Git, when you copy a project you say you "clone" it. To work on a git project When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen. -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step). -![Copy the HTTPS or SSH](basicsimages/https.png) +![Copy the HTTPS or SSH](img/https.png) ## On the command line diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md index 7556b0f663e..8f078c7692f 100644 --- a/doc/gitlab-basics/create-branch.md +++ b/doc/gitlab-basics/create-branch.md @@ -8,19 +8,19 @@ To add changes to your GitLab project, you should create a branch. You can do it To create a new branch in GitLab, sign in and then select a project on the right side of your screen: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on "commits" on the menu on the left side of your screen: -![Commits](basicsimages/commits.png) +![Commits](img/commits.png) Click on the "branches" tab: -![Branches](basicsimages/branches.png) +![Branches](img/branches.png) Click on the "new branch" button on the right side of the screen: -![New branch](basicsimages/newbranch.png) +![New branch](img/newbranch.png) Fill out the information required: @@ -30,10 +30,10 @@ Fill out the information required: 1. Click on the button "create branch" -![Branch info](basicsimages/branch_info.png) +![Branch info](img/branch_info.png) ### Note: You will be able to find and select the name of your branch in the white box next to a project's name: -![Branch name](basicsimages/branch_name.png) +![Branch name](img/branch_name.png) diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index f80ae62e442..05497279959 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -12,11 +12,11 @@ Sign in to [GitLab.com](https://gitlab.com). When you are on your Dashboard, click on "Groups" on the left menu of your screen: -![Go to groups](basicsimages/select-group2.png) +![Go to groups](img/select-group2.png) Click on "New group" on the top right side of your screen: -![New group](basicsimages/click-on-new-group.png) +![New group](img/click-on-new-group.png) Fill out the information required: @@ -28,7 +28,7 @@ Fill out the information required: 1. Click on "create group" -![Group information](basicsimages/group_info.png) +![Group information](img/group_info.png) ## Add a project to a group @@ -36,8 +36,8 @@ There are 2 different ways to add a new project to a group: * Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md) -![New project](basicsimages/new_project.png) +![New project](img/new_project.png) * When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen -![Create a group](basicsimages/create_group.png) +![Create a group](img/create_group.png) diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md index da9a165b8f5..e11c86cafa0 100644 --- a/doc/gitlab-basics/create-issue.md +++ b/doc/gitlab-basics/create-issue.md @@ -6,22 +6,22 @@ To create an Issue, sign in to GitLab. Go to the project where you'd like to create the Issue: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on "Issues" on the left side of your screen: -![Issues](basicsimages/issues.png) +![Issues](img/issues.png) Click on the "+ new issue" button on the right side of your screen: -![New issue](basicsimages/new_issue.png) +![New issue](img/new_issue.png) Add a title and a description to your issue: -![Issue title and description](basicsimages/issue_title.png) +![Issue title and description](img/issue_title.png) You may assign the Issue to a user, add a milestone and add labels (they are all optional). Then click on "submit new issue": -![Submit new issue](basicsimages/submit_new_issue.png) +![Submit new issue](img/submit_new_issue.png) Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](../user/project/issues/automatic_issue_closing.md). diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index f737dffc024..c67e5908189 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -4,7 +4,7 @@ To create a new project, sign in to GitLab. Go to your Dashboard and click on "new project" on the right side of your screen. -![Create a project](basicsimages/new_project.png) +![Create a project](img/new_project.png) Fill out the required information: @@ -18,4 +18,4 @@ Fill out the required information: 1. Click on "create project" -!![Project information](basicsimages/project_info.png) +!![Project information](img/project_info.png) diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md index f31c353f2cf..50ac9c36988 100644 --- a/doc/gitlab-basics/create-your-ssh-keys.md +++ b/doc/gitlab-basics/create-your-ssh-keys.md @@ -12,15 +12,15 @@ After you confirm, go to GitLab and sign in to your account. On the left side menu, click on "profile settings" and then click on "SSH Keys": -![SSH Keys](basicsimages/shh_keys.png) +![SSH Keys](img/shh_keys.png) Then click on the green button "Add SSH Key": -![Add SSH Key](basicsimages/add_sshkey.png) +![Add SSH Key](img/add_sshkey.png) There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it: -![Paste SSH Key](basicsimages/paste_sshkey.png) +![Paste SSH Key](img/paste_sshkey.png) ## To generate an SSH Key on your command line @@ -28,6 +28,6 @@ Go to your [command line](start-using-git.md) and follow the [instructions](../s Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically. -![Paste SSH Key](basicsimages/key.png) +![Paste SSH Key](img/key.png) Now, you'll be able to use Git over SSH, instead of Git over HTTP. diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md index 5f8b81ea919..7232fc2c1ae 100644 --- a/doc/gitlab-basics/fork-project.md +++ b/doc/gitlab-basics/fork-project.md @@ -10,10 +10,10 @@ Sign in to GitLab. Select a project on the right side of your screen: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on the "fork" button on the right side of your screen: -![Fork](basicsimages/fork.png) +![Fork](img/fork.png) Click on the user or group to where you'd like to add the forked project. diff --git a/doc/gitlab-basics/img/add_new_merge_request.png b/doc/gitlab-basics/img/add_new_merge_request.png new file mode 100644 index 00000000000..e60992c4c6a Binary files /dev/null and b/doc/gitlab-basics/img/add_new_merge_request.png differ diff --git a/doc/gitlab-basics/img/add_sshkey.png b/doc/gitlab-basics/img/add_sshkey.png new file mode 100644 index 00000000000..89c86018629 Binary files /dev/null and b/doc/gitlab-basics/img/add_sshkey.png differ diff --git a/doc/gitlab-basics/img/branch_info.png b/doc/gitlab-basics/img/branch_info.png new file mode 100644 index 00000000000..2264f3c5bf2 Binary files /dev/null and b/doc/gitlab-basics/img/branch_info.png differ diff --git a/doc/gitlab-basics/img/branch_name.png b/doc/gitlab-basics/img/branch_name.png new file mode 100644 index 00000000000..75fe8313611 Binary files /dev/null and b/doc/gitlab-basics/img/branch_name.png differ diff --git a/doc/gitlab-basics/img/branches.png b/doc/gitlab-basics/img/branches.png new file mode 100644 index 00000000000..8621bc05776 Binary files /dev/null and b/doc/gitlab-basics/img/branches.png differ diff --git a/doc/gitlab-basics/img/button-create-mr.png b/doc/gitlab-basics/img/button-create-mr.png new file mode 100644 index 00000000000..b52ab148839 Binary files /dev/null and b/doc/gitlab-basics/img/button-create-mr.png differ diff --git a/doc/gitlab-basics/img/click-on-new-group.png b/doc/gitlab-basics/img/click-on-new-group.png new file mode 100644 index 00000000000..6450deec6fc Binary files /dev/null and b/doc/gitlab-basics/img/click-on-new-group.png differ diff --git a/doc/gitlab-basics/img/commit_changes.png b/doc/gitlab-basics/img/commit_changes.png new file mode 100644 index 00000000000..a88809c5a3f Binary files /dev/null and b/doc/gitlab-basics/img/commit_changes.png differ diff --git a/doc/gitlab-basics/img/commit_message.png b/doc/gitlab-basics/img/commit_message.png new file mode 100644 index 00000000000..4abe4517f98 Binary files /dev/null and b/doc/gitlab-basics/img/commit_message.png differ diff --git a/doc/gitlab-basics/img/commits.png b/doc/gitlab-basics/img/commits.png new file mode 100644 index 00000000000..2bfcaf75f01 Binary files /dev/null and b/doc/gitlab-basics/img/commits.png differ diff --git a/doc/gitlab-basics/img/compare_branches.png b/doc/gitlab-basics/img/compare_branches.png new file mode 100644 index 00000000000..8a18453dd05 Binary files /dev/null and b/doc/gitlab-basics/img/compare_branches.png differ diff --git a/doc/gitlab-basics/img/create_file.png b/doc/gitlab-basics/img/create_file.png new file mode 100644 index 00000000000..5ebe1b227dd Binary files /dev/null and b/doc/gitlab-basics/img/create_file.png differ diff --git a/doc/gitlab-basics/img/create_group.png b/doc/gitlab-basics/img/create_group.png new file mode 100644 index 00000000000..7ecc3baa990 Binary files /dev/null and b/doc/gitlab-basics/img/create_group.png differ diff --git a/doc/gitlab-basics/img/edit_file.png b/doc/gitlab-basics/img/edit_file.png new file mode 100644 index 00000000000..9d3e817d036 Binary files /dev/null and b/doc/gitlab-basics/img/edit_file.png differ diff --git a/doc/gitlab-basics/img/file_located.png b/doc/gitlab-basics/img/file_located.png new file mode 100644 index 00000000000..e357cb5c6ab Binary files /dev/null and b/doc/gitlab-basics/img/file_located.png differ diff --git a/doc/gitlab-basics/img/file_name.png b/doc/gitlab-basics/img/file_name.png new file mode 100644 index 00000000000..01639c77d0d Binary files /dev/null and b/doc/gitlab-basics/img/file_name.png differ diff --git a/doc/gitlab-basics/img/find_file.png b/doc/gitlab-basics/img/find_file.png new file mode 100644 index 00000000000..6f26d26ae18 Binary files /dev/null and b/doc/gitlab-basics/img/find_file.png differ diff --git a/doc/gitlab-basics/img/find_group.png b/doc/gitlab-basics/img/find_group.png new file mode 100644 index 00000000000..1211510aae9 Binary files /dev/null and b/doc/gitlab-basics/img/find_group.png differ diff --git a/doc/gitlab-basics/img/fork.png b/doc/gitlab-basics/img/fork.png new file mode 100644 index 00000000000..13ff8345616 Binary files /dev/null and b/doc/gitlab-basics/img/fork.png differ diff --git a/doc/gitlab-basics/img/group_info.png b/doc/gitlab-basics/img/group_info.png new file mode 100644 index 00000000000..2507d6c295b Binary files /dev/null and b/doc/gitlab-basics/img/group_info.png differ diff --git a/doc/gitlab-basics/img/groups.png b/doc/gitlab-basics/img/groups.png new file mode 100644 index 00000000000..ef3dca60cc8 Binary files /dev/null and b/doc/gitlab-basics/img/groups.png differ diff --git a/doc/gitlab-basics/img/https.png b/doc/gitlab-basics/img/https.png new file mode 100644 index 00000000000..e74dbc13f9a Binary files /dev/null and b/doc/gitlab-basics/img/https.png differ diff --git a/doc/gitlab-basics/img/image_file.png b/doc/gitlab-basics/img/image_file.png new file mode 100644 index 00000000000..7f304b8e1f2 Binary files /dev/null and b/doc/gitlab-basics/img/image_file.png differ diff --git a/doc/gitlab-basics/img/issue_title.png b/doc/gitlab-basics/img/issue_title.png new file mode 100644 index 00000000000..60a6f7973be Binary files /dev/null and b/doc/gitlab-basics/img/issue_title.png differ diff --git a/doc/gitlab-basics/img/issues.png b/doc/gitlab-basics/img/issues.png new file mode 100644 index 00000000000..14e9cdb64e1 Binary files /dev/null and b/doc/gitlab-basics/img/issues.png differ diff --git a/doc/gitlab-basics/img/key.png b/doc/gitlab-basics/img/key.png new file mode 100644 index 00000000000..04400173ce8 Binary files /dev/null and b/doc/gitlab-basics/img/key.png differ diff --git a/doc/gitlab-basics/img/merge_requests.png b/doc/gitlab-basics/img/merge_requests.png new file mode 100644 index 00000000000..570164df18b Binary files /dev/null and b/doc/gitlab-basics/img/merge_requests.png differ diff --git a/doc/gitlab-basics/img/new_issue.png b/doc/gitlab-basics/img/new_issue.png new file mode 100644 index 00000000000..94e7503dd8b Binary files /dev/null and b/doc/gitlab-basics/img/new_issue.png differ diff --git a/doc/gitlab-basics/img/new_merge_request.png b/doc/gitlab-basics/img/new_merge_request.png new file mode 100644 index 00000000000..842f5ebed74 Binary files /dev/null and b/doc/gitlab-basics/img/new_merge_request.png differ diff --git a/doc/gitlab-basics/img/new_project.png b/doc/gitlab-basics/img/new_project.png new file mode 100644 index 00000000000..421e8bc247b Binary files /dev/null and b/doc/gitlab-basics/img/new_project.png differ diff --git a/doc/gitlab-basics/img/newbranch.png b/doc/gitlab-basics/img/newbranch.png new file mode 100644 index 00000000000..d5fcf33c4ea Binary files /dev/null and b/doc/gitlab-basics/img/newbranch.png differ diff --git a/doc/gitlab-basics/img/paste_sshkey.png b/doc/gitlab-basics/img/paste_sshkey.png new file mode 100644 index 00000000000..578ebee4440 Binary files /dev/null and b/doc/gitlab-basics/img/paste_sshkey.png differ diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png new file mode 100644 index 00000000000..cb3f79f1879 Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings.png differ diff --git a/doc/gitlab-basics/img/project_info.png b/doc/gitlab-basics/img/project_info.png new file mode 100644 index 00000000000..e1adb8d48c2 Binary files /dev/null and b/doc/gitlab-basics/img/project_info.png differ diff --git a/doc/gitlab-basics/img/public_file_link.png b/doc/gitlab-basics/img/public_file_link.png new file mode 100644 index 00000000000..f60df6807f4 Binary files /dev/null and b/doc/gitlab-basics/img/public_file_link.png differ diff --git a/doc/gitlab-basics/img/select-group.png b/doc/gitlab-basics/img/select-group.png new file mode 100644 index 00000000000..33b978dd899 Binary files /dev/null and b/doc/gitlab-basics/img/select-group.png differ diff --git a/doc/gitlab-basics/img/select-group2.png b/doc/gitlab-basics/img/select-group2.png new file mode 100644 index 00000000000..aee22c638db Binary files /dev/null and b/doc/gitlab-basics/img/select-group2.png differ diff --git a/doc/gitlab-basics/img/select_branch.png b/doc/gitlab-basics/img/select_branch.png new file mode 100644 index 00000000000..f72a3ffb57f Binary files /dev/null and b/doc/gitlab-basics/img/select_branch.png differ diff --git a/doc/gitlab-basics/img/select_project.png b/doc/gitlab-basics/img/select_project.png new file mode 100644 index 00000000000..3bb832ea8d0 Binary files /dev/null and b/doc/gitlab-basics/img/select_project.png differ diff --git a/doc/gitlab-basics/img/settings.png b/doc/gitlab-basics/img/settings.png new file mode 100644 index 00000000000..78637013d9b Binary files /dev/null and b/doc/gitlab-basics/img/settings.png differ diff --git a/doc/gitlab-basics/img/shh_keys.png b/doc/gitlab-basics/img/shh_keys.png new file mode 100644 index 00000000000..c87f11a9d3d Binary files /dev/null and b/doc/gitlab-basics/img/shh_keys.png differ diff --git a/doc/gitlab-basics/img/submit_new_issue.png b/doc/gitlab-basics/img/submit_new_issue.png new file mode 100644 index 00000000000..78b854c8903 Binary files /dev/null and b/doc/gitlab-basics/img/submit_new_issue.png differ diff --git a/doc/gitlab-basics/img/title_description_mr.png b/doc/gitlab-basics/img/title_description_mr.png new file mode 100644 index 00000000000..c31d61ec336 Binary files /dev/null and b/doc/gitlab-basics/img/title_description_mr.png differ diff --git a/doc/gitlab-basics/img/white_space.png b/doc/gitlab-basics/img/white_space.png new file mode 100644 index 00000000000..eaa969bdcf4 Binary files /dev/null and b/doc/gitlab-basics/img/white_space.png differ -- cgit v1.2.1 From d4fab17d7c8c2b233248295755a6277fdee09c9f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 7 Oct 2016 22:48:23 -0700 Subject: Fix Error 500 when viewing old merge requests with bad diff data Customers running old versions of GitLab may have MergeRequestDiffs with the text ["--broken diff"] due to text generated by gitlab_git 1.0.3. To avoid the Error 500, verify that each element is a type that gitlab_git will accept before attempting to create a DiffCollection. Closes #20776 --- CHANGELOG | 1 + app/models/merge_request_diff.rb | 14 +++++++++++++- spec/models/merge_request_diff_spec.rb | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..dfb953fabd9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.13.0 (unreleased) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - Fix Error 500 when viewing old merge requests with bad diff data - Speed-up group milestones show page - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 36b8b70870b..3f7e96186a1 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 100 + # Valid types of serialized diffs allowed by Gitlab::Git::Diff + VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta] + belongs_to :merge_request state_machine :state, initial: :empty do @@ -170,6 +173,15 @@ class MergeRequestDiff < ActiveRecord::Base private + # Old GitLab implementations may have generated diffs as ["--broken-diff"]. + # Avoid an error 500 by ignoring bad elements. See: + # https://gitlab.com/gitlab-org/gitlab-ce/issues/20776 + def valid_raw_diff?(raw) + return false unless raw.respond_to?(:each) + + raw.any? { |element| VALID_CLASSES.include?(element.class) } + end + def dump_commits(commits) commits.map(&:to_hash) end @@ -200,7 +212,7 @@ class MergeRequestDiff < ActiveRecord::Base end def load_diffs(raw, options) - if raw.respond_to?(:each) + if valid_raw_diff?(raw) if paths = options[:paths] raw = raw.select do |diff| paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..96f1f60dbc0 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -44,6 +44,16 @@ describe MergeRequestDiff, models: true do end end + context 'when the raw diffs have invalid content' do + before { mr_diff.update_attributes(st_diffs: ["--broken-diff"]) } + + it 'returns an empty DiffCollection' do + expect(mr_diff.raw_diffs.to_a).to be_empty + expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.raw_diffs).to be_empty + end + end + context 'when the raw diffs exist' do it 'returns the diffs' do expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) -- cgit v1.2.1 From f8df2bc61f205da36b50961928509ca087e7b290 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 22:07:17 +0200 Subject: Refactor SSH key addition in GitLab basics --- doc/gitlab-basics/create-your-ssh-keys.md | 38 ++++++++++++--------- doc/gitlab-basics/img/add_sshkey.png | Bin 1394 -> 0 bytes doc/gitlab-basics/img/key.png | Bin 1177 -> 0 bytes doc/gitlab-basics/img/paste_sshkey.png | Bin 7699 -> 0 bytes doc/gitlab-basics/img/profile_settings.png | Bin 1101 -> 5975 bytes .../img/profile_settings_ssh_keys.png | Bin 0 -> 42977 bytes .../img/profile_settings_ssh_keys_paste_pub.png | Bin 0 -> 37486 bytes .../img/profile_settings_ssh_keys_single_key.png | Bin 0 -> 18498 bytes .../img/profile_settings_ssh_keys_title.png | Bin 0 -> 2362 bytes doc/gitlab-basics/img/shh_keys.png | Bin 4782 -> 0 bytes 10 files changed, 21 insertions(+), 17 deletions(-) delete mode 100644 doc/gitlab-basics/img/add_sshkey.png delete mode 100644 doc/gitlab-basics/img/key.png delete mode 100644 doc/gitlab-basics/img/paste_sshkey.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys_title.png delete mode 100644 doc/gitlab-basics/img/shh_keys.png diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md index 50ac9c36988..b6ebe374de3 100644 --- a/doc/gitlab-basics/create-your-ssh-keys.md +++ b/doc/gitlab-basics/create-your-ssh-keys.md @@ -1,33 +1,37 @@ # How to create your SSH Keys -You need to connect your computer to your GitLab account through SSH Keys. They are unique for every computer that you link your GitLab account with. +1. The first thing you need to do is go to your [command line](start-using-git.md) + and follow the [instructions](../ssh/README.md) to generate your SSH key pair. -## Generate your SSH Key +1. Once you do that, login to GitLab with your credentials. +1. On the upper right corner, click on your avatar and go to your **Profile settings**. -Create an account on GitLab. Sign up and check your email for your confirmation link. + ![Profile settings dropdown](img/profile_settings.png) -After you confirm, go to GitLab and sign in to your account. +1. Navigate to the **SSH keys** tab. -## Add your SSH Key + ![SSH Keys](img/profile_settings_ssh_keys.png) -On the left side menu, click on "profile settings" and then click on "SSH Keys": +3. Paste your **public** key that you generated in the first step in the 'Key' + box. -![SSH Keys](img/shh_keys.png) + ![Paste SSH public key](img/profile_settings_ssh_keys_paste_pub.png) -Then click on the green button "Add SSH Key": +1. Optionally, give it a descriptive title so that you can recognize it in the + event you add multiple keys. -![Add SSH Key](img/add_sshkey.png) + ![SSH key title](img/profile_settings_ssh_keys_title.png) -There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it: +1. Finally, click on **Add key** to add it to GitLab. You will be able to see + its fingerprint, its title and creation date. -![Paste SSH Key](img/paste_sshkey.png) + ![SSH key single page](img/profile_settings_ssh_keys_single_key.png) -## To generate an SSH Key on your command line -Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate it. +>**Note:** +Once you add a key, you cannot edit it, only remove it. In case the paste +didn't work, you will have to remove the offending key and re-add it. -Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically. +--- -![Paste SSH Key](img/key.png) - -Now, you'll be able to use Git over SSH, instead of Git over HTTP. +Congratulations! You are now ready to use Git over SSH, instead of Git over HTTP! diff --git a/doc/gitlab-basics/img/add_sshkey.png b/doc/gitlab-basics/img/add_sshkey.png deleted file mode 100644 index 89c86018629..00000000000 Binary files a/doc/gitlab-basics/img/add_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/img/key.png b/doc/gitlab-basics/img/key.png deleted file mode 100644 index 04400173ce8..00000000000 Binary files a/doc/gitlab-basics/img/key.png and /dev/null differ diff --git a/doc/gitlab-basics/img/paste_sshkey.png b/doc/gitlab-basics/img/paste_sshkey.png deleted file mode 100644 index 578ebee4440..00000000000 Binary files a/doc/gitlab-basics/img/paste_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png index cb3f79f1879..f0abd478849 100644 Binary files a/doc/gitlab-basics/img/profile_settings.png and b/doc/gitlab-basics/img/profile_settings.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys.png b/doc/gitlab-basics/img/profile_settings_ssh_keys.png new file mode 100644 index 00000000000..2c9a42fe10c Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png new file mode 100644 index 00000000000..cd7add6937f Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png new file mode 100644 index 00000000000..095beb02be8 Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png new file mode 100644 index 00000000000..4b998a7f948 Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png differ diff --git a/doc/gitlab-basics/img/shh_keys.png b/doc/gitlab-basics/img/shh_keys.png deleted file mode 100644 index c87f11a9d3d..00000000000 Binary files a/doc/gitlab-basics/img/shh_keys.png and /dev/null differ -- cgit v1.2.1 From 6de2990c998169e165c0bc9b0a7e0ab9099048e4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 22:18:06 +0200 Subject: Use new image for gitlab-basics/command-line-commands.md [ci skip] --- doc/gitlab-basics/README.md | 2 +- doc/gitlab-basics/command-line-commands.md | 26 ++++++++++++++++++++------ doc/gitlab-basics/img/https.png | Bin 2822 -> 0 bytes doc/gitlab-basics/img/project_clone_url.png | Bin 0 -> 40490 bytes 4 files changed, 21 insertions(+), 7 deletions(-) delete mode 100644 doc/gitlab-basics/img/https.png create mode 100644 doc/gitlab-basics/img/project_clone_url.png diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 3aa83975ace..38843f36b2e 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -2,9 +2,9 @@ Step-by-step guides on the basics of working with Git and GitLab. +- [Command Line basics](command-line-commands.md) - [Start using Git on the command line](start-using-git.md) - [Create and add your SSH Keys](create-your-ssh-keys.md) -- [Command Line basics](command-line-commands.md) - [Create a project](create-project.md) - [Create a group](create-group.md) - [Create a branch](create-branch.md) diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md index 253edc8a7e8..3b075ff5fc0 100644 --- a/doc/gitlab-basics/command-line-commands.md +++ b/doc/gitlab-basics/command-line-commands.md @@ -4,18 +4,21 @@ In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to GitLab. -When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen. +When you are on your Dashboard, click on the project that you'd like to clone. +To work in the project, you can copy a link to the Git repository through a SSH +or a HTTPS protocol. SSH is easier to use after it's been +[setup](create-your-ssh-keys.md). While you are at the **Project** tab, select +HTTPS or SSH from the dropdown menu and copy the link using the 'Copy to clipboard' +button (you'll have to paste it on your shell in the next step). -![Select a project](img/select_project.png) - -To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step). - -![Copy the HTTPS or SSH](img/https.png) +![Copy the HTTPS or SSH](img/project_clone_url.png) ## On the command line ### Clone your project + Go to your computer's shell and type the following command: + ``` git clone PASTE HTTPS OR SSH HERE ``` @@ -23,26 +26,31 @@ git clone PASTE HTTPS OR SSH HERE A clone of the project will be created in your computer. ### Go into a project, directory or file to work in it + ``` cd NAME-OF-PROJECT-OR-FILE ``` ### Go back one directory or file + ``` cd ../ ``` ### View what’s in the directory that you are in + ``` ls ``` ### Create a directory + ``` mkdir NAME-OF-YOUR-DIRECTORY ``` ### Create a README.md or file in directory + ``` touch README.md nano README.md @@ -53,27 +61,33 @@ nano README.md ``` ### Remove a file + ``` rm NAME-OF-FILE ``` ### Remove a directory and all of its contents + ``` rm -rf NAME-OF-DIRECTORY ``` ### View history in the command line + ``` history ``` ### Carry out commands for which the account you are using lacks authority + You will be asked for an administrator’s password. + ``` sudo ``` ### Tell where you are + ``` pwd ``` diff --git a/doc/gitlab-basics/img/https.png b/doc/gitlab-basics/img/https.png deleted file mode 100644 index e74dbc13f9a..00000000000 Binary files a/doc/gitlab-basics/img/https.png and /dev/null differ diff --git a/doc/gitlab-basics/img/project_clone_url.png b/doc/gitlab-basics/img/project_clone_url.png new file mode 100644 index 00000000000..eed430e1036 Binary files /dev/null and b/doc/gitlab-basics/img/project_clone_url.png differ -- cgit v1.2.1 From 594c320851afbf4c8dd1c78600a0195f12d6ce41 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 10 Oct 2016 21:44:29 +0100 Subject: Adds tests to verify if tabs are rendered --- spec/features/environments_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 3b38a7f5007..99246589eae 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -18,11 +18,26 @@ feature 'Environments', feature: true do before do visit namespace_project_environments_path(project.namespace, project) end + + context 'shows two tabs' do + scenario 'does show Available tab with link' do + expect(page).to have_link('Available') + end + + scenario 'does show Stopped tab with link' do + expect(page).to have_link('Stopped') + end + end context 'without environments' do scenario 'does show no environments' do expect(page).to have_content('You don\'t have any environments right now.') end + + scenario 'does show 0 as counter for environments in both tabs' do + expect(page.find('.js-avaibale-environments-count').text).to eq('0') + expect(page.find('.js-stopped-environments-count').text).to eq('0') + end end context 'with environments' do -- cgit v1.2.1 From 0a79e5cd44e1d83f0cd18ffb538da25e6b2deae3 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 16:21:08 -0500 Subject: Replace generic table with --- app/assets/stylesheets/pages/environments.scss | 4 ++-- app/assets/stylesheets/pages/merge_requests.scss | 4 ++-- app/assets/stylesheets/pages/pipelines.scss | 4 ++-- app/views/admin/runners/show.html.haml | 2 +- app/views/projects/builds/_table.html.haml | 2 +- app/views/projects/commit/_pipeline.html.haml | 2 +- app/views/projects/commit/_pipelines_list.haml | 2 +- app/views/projects/environments/index.html.haml | 2 +- app/views/projects/environments/show.html.haml | 2 +- app/views/projects/pipelines/index.html.haml | 2 +- spec/features/merge_requests/created_from_fork_spec.rb | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 3f19e920166..820cc0fc991 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -52,13 +52,13 @@ } } -.table.builds.environments { +.table.ci-table.environments { .icon-container { width: 20px; text-align: center; } - + .branch-commit { .commit-id { margin-right: 0; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 043f3f3afe1..9676c2d1026 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -264,7 +264,7 @@ line-height: 31px; } -.builds { +.ci-table { .table-holder { overflow-x: auto; } @@ -357,7 +357,7 @@ } .table-holder { - .builds { + .ci-table { th { background-color: $white-light; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a2779704eff..bad4eeaa2cf 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -20,7 +20,7 @@ margin: 4px; } - .table.builds { + .table.ci-table { min-width: 1200px; .branch-commit { @@ -44,7 +44,7 @@ overflow: auto; } -.table.builds { +.table.ci-table { min-width: 900px; &.pipeline { diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index a5e82e55cc1..10fea1996aa 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -71,7 +71,7 @@ .col-md-6 %h4 Recent builds served by this Runner - %table.table.builds.runner-builds + %table.table.ci-table.runner-builds %thead %tr %th Build diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index f3747ba2a21..9b91ae688ae 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -5,7 +5,7 @@ .nothing-here-block No builds to show - else .table-holder - %table.table.builds + %table.table.ci-table %thead %tr %th Status diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index da5b9832ba5..b5e5e8db9eb 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -55,7 +55,7 @@ \.gitlab-ci.yml not found in this commit .table-holder.pipeline-holder - %table.table.builds.pipeline + %table.table.ci-table.pipeline %thead %tr %th Status diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 998812793a2..640651e93f5 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -4,7 +4,7 @@ .nothing-here-block No pipelines to show - else .table-holder - %table.table.builds + %table.table.ci-table %tbody %th Status %th Pipeline diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index ab801409722..721ba156334 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -24,7 +24,7 @@ New environment - else .table-holder - %table.table.builds.environments + %table.table.ci-table.environments %tbody %th Environment %th Last Deployment diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 7a8d196cf4e..90c59223a35 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -24,7 +24,7 @@ = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success" - else .table-holder - %table.table.builds.environments + %table.table.ci-table.environments %thead %tr %th ID diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 2d1df095bfa..9eeef5f57b4 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -43,7 +43,7 @@ .nothing-here-block No pipelines to show - else .table-holder - %table.table.builds + %table.table.ci-table %thead %th.col-xs-1.col-sm-1 Status %th.col-xs-2.col-sm-4 Pipeline diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb index 4d5d4aa121a..a506624b30d 100644 --- a/spec/features/merge_requests/created_from_fork_spec.rb +++ b/spec/features/merge_requests/created_from_fork_spec.rb @@ -45,7 +45,7 @@ feature 'Merge request created from fork' do page.within('.merge-request-tabs') { click_link 'Builds' } wait_for_ajax - page.within('table.builds') do + page.within('table.ci-table') do expect(page).to have_content 'rspec' expect(page).to have_content 'spinach' end -- cgit v1.2.1 From 0012b74e01429b98d2c42f68f50dc6ce6ad2b70f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 16:31:24 -0500 Subject: Set height on build page rows --- app/assets/stylesheets/pages/pipelines.scss | 7 +++++++ app/views/projects/builds/_table.html.haml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index bad4eeaa2cf..149c29858fb 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -51,6 +51,13 @@ min-width: 650px; } + &.builds-page { + + tr { + height: 71px; + } + } + tr { th { padding: 16px 8px; diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index 9b91ae688ae..36294c89fa8 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -5,7 +5,7 @@ .nothing-here-block No builds to show - else .table-holder - %table.table.ci-table + %table.table.ci-table.builds-page %thead %tr %th Status -- cgit v1.2.1 From 64609ba84845969ef084aee3058ce2fbea582a3f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 16:43:40 -0500 Subject: Set height on MR builds rows --- app/assets/stylesheets/pages/merge_requests.scss | 6 ----- app/assets/stylesheets/pages/pipelines.scss | 30 +++++++++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 9676c2d1026..71cf17fdb41 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -264,12 +264,6 @@ line-height: 31px; } -.ci-table { - .table-holder { - overflow-x: auto; - } -} - .panel-new-merge-request { .panel-heading { padding: 5px 10px; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 149c29858fb..84af2a01997 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -574,19 +574,31 @@ } } -.pipelines.tab-pane { +.tab-pane { - .content-list.pipelines { - overflow: auto; - } + &.pipelines { - .stage { - max-width: 100px; - width: 100px; + .content-list.pipelines { + overflow: auto; + } + + .stage { + max-width: 100px; + width: 100px; + } + + .pipeline-actions { + min-width: initial; + } } - .pipeline-actions { - min-width: initial; + &.builds { + + .ci-table { + tr { + height: 71px; + } + } } } -- cgit v1.2.1 From ac93758a25e307573dcf79a76318f5b45aec5d07 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 10 Oct 2016 16:52:48 -0700 Subject: Bump mail_room to v0.8.1 to fix thread cleanup issue Closes #20273 --- CHANGELOG | 1 + Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..49804b10c0b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page + - Bump mail_room to v0.8.1 to fix thread cleanup issue - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Improve issue load time performance by avoiding ORDER BY in find_by call diff --git a/Gemfile.lock b/Gemfile.lock index b98c3acf948..ccab330993a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -399,7 +399,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.8.0) + mail_room (0.8.1) method_source (0.8.2) mime-types (2.99.3) mimemagic (0.3.0) -- cgit v1.2.1 From f19766a3c2223b05e1f6de1746a44e7173387988 Mon Sep 17 00:00:00 2001 From: Mitchell Hentges Date: Thu, 22 Sep 2016 11:15:00 -0700 Subject: Ensure that whitespace doesn't case adding members to fail --- CHANGELOG | 1 + app/assets/javascripts/users_select.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dc06303aecf..d94305a66d1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page + - Adding members no longer silently fails when there is extra whitespace - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Improve issue load time performance by avoiding ORDER BY in find_by call diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index bcabda3ceb2..d966277a8b2 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -261,10 +261,11 @@ } } if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) { + var trimmed = query.term.trim(); emailUser = { name: "Invite \"" + query.term + "\"", - username: query.term, - id: query.term + username: trimmed, + id: trimmed }; data.results.unshift(emailUser); } -- cgit v1.2.1 From ef696f592fd1eed038c789379837554237ff5974 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 11 Oct 2016 02:58:26 +0100 Subject: Add `robots.txt` to the list of reserved namespaces --- app/validators/namespace_validator.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb index 4dc3b2ab9a0..2821ecf0a88 100644 --- a/app/validators/namespace_validator.rb +++ b/app/validators/namespace_validator.rb @@ -24,6 +24,7 @@ class NamespaceValidator < ActiveModel::EachValidator projects public repository + robots.txt s search services -- cgit v1.2.1 From c003c894223f76635783b0c878006bff5800f2fe Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 06:41:40 +0200 Subject: Add new images for GitLab basics "create project" --- doc/gitlab-basics/create-project.md | 27 ++++++++++++--------- .../img/create_new_project_button.png | Bin 0 -> 10050 bytes doc/gitlab-basics/img/create_new_project_info.png | Bin 0 -> 49451 bytes doc/gitlab-basics/img/new_project.png | Bin 2234 -> 0 bytes doc/gitlab-basics/img/project_info.png | Bin 21041 -> 0 bytes 5 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 doc/gitlab-basics/img/create_new_project_button.png create mode 100644 doc/gitlab-basics/img/create_new_project_info.png delete mode 100644 doc/gitlab-basics/img/new_project.png delete mode 100644 doc/gitlab-basics/img/project_info.png diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index c67e5908189..3f45a631b3a 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -1,21 +1,24 @@ # How to create a project in GitLab -To create a new project, sign in to GitLab. +There are two ways to create a new project in GitLab. -Go to your Dashboard and click on "new project" on the right side of your screen. +1. While in your dashboard, you can create a new project using the **New project** + green button or you can use the cross icon in the upper right corner next to + your avatar which is always visible. -![Create a project](img/new_project.png) + ![Create a project](img/create_new_project_button.png) -Fill out the required information: +1. From there you can see several options. -1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores) + ![Project information](img/create_new_project_info.png) -1. Your project's description +1. Fill out the information: -1. Select a [visibility level](https://gitlab.com/help/public_access/public_access) + 1. "Project name" is the name of your project (you can't use spaces, but you + can use hyphens or underscores). + 1. The "Project description" is optional and will be shown in your project's + dashboard so others can briefly understand what your project is about. + 1. Select a [visibility level](../public_access/public_access.md). + 1. You can also [import your existing projects](../workflow/importing/README.md). -1. You can also [import your existing projects](http://docs.gitlab.com/ce/workflow/importing/README.html) - -1. Click on "create project" - -!![Project information](img/project_info.png) +1. Finally, click **Create project**. diff --git a/doc/gitlab-basics/img/create_new_project_button.png b/doc/gitlab-basics/img/create_new_project_button.png new file mode 100644 index 00000000000..e7c794d943f Binary files /dev/null and b/doc/gitlab-basics/img/create_new_project_button.png differ diff --git a/doc/gitlab-basics/img/create_new_project_info.png b/doc/gitlab-basics/img/create_new_project_info.png new file mode 100644 index 00000000000..16d56f0707f Binary files /dev/null and b/doc/gitlab-basics/img/create_new_project_info.png differ diff --git a/doc/gitlab-basics/img/new_project.png b/doc/gitlab-basics/img/new_project.png deleted file mode 100644 index 421e8bc247b..00000000000 Binary files a/doc/gitlab-basics/img/new_project.png and /dev/null differ diff --git a/doc/gitlab-basics/img/project_info.png b/doc/gitlab-basics/img/project_info.png deleted file mode 100644 index e1adb8d48c2..00000000000 Binary files a/doc/gitlab-basics/img/project_info.png and /dev/null differ -- cgit v1.2.1 From ec102fe8f1cce1a5b971807232550e3b1e0f9ae6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:11:55 +0200 Subject: New images for GitLab basics "Create group" docs --- doc/gitlab-basics/create-group.md | 51 +++++++++++---------- doc/gitlab-basics/img/click-on-new-group.png | Bin 1957 -> 0 bytes doc/gitlab-basics/img/create_group.png | Bin 3184 -> 0 bytes doc/gitlab-basics/img/create_new_group_info.png | Bin 0 -> 53103 bytes doc/gitlab-basics/img/create_new_group_sidebar.png | Bin 0 -> 5396 bytes .../img/create_new_project_from_group.png | Bin 0 -> 6545 bytes doc/gitlab-basics/img/group_info.png | Bin 15479 -> 0 bytes doc/gitlab-basics/img/select-group2.png | Bin 5040 -> 0 bytes doc/gitlab-basics/img/select_group_dropdown.png | Bin 0 -> 8038 bytes 9 files changed, 28 insertions(+), 23 deletions(-) delete mode 100644 doc/gitlab-basics/img/click-on-new-group.png delete mode 100644 doc/gitlab-basics/img/create_group.png create mode 100644 doc/gitlab-basics/img/create_new_group_info.png create mode 100644 doc/gitlab-basics/img/create_new_group_sidebar.png create mode 100644 doc/gitlab-basics/img/create_new_project_from_group.png delete mode 100644 doc/gitlab-basics/img/group_info.png delete mode 100644 doc/gitlab-basics/img/select-group2.png create mode 100644 doc/gitlab-basics/img/select_group_dropdown.png diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index 05497279959..64274ccd5eb 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -1,43 +1,48 @@ # How to create a group in GitLab -## Create a group - Your projects in GitLab can be organized in 2 different ways: -under your own namespace for single projects, such as ´your-name/project-1'; or under groups. -If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects. - -To create a group, follow the instructions below: +under your own namespace for single projects, such as `your-name/project-1` or +under groups. -Sign in to [GitLab.com](https://gitlab.com). +If you organize your projects under a group, it works like a folder. You can +manage your group members' permissions and access to the projects. -When you are on your Dashboard, click on "Groups" on the left menu of your screen: +--- -![Go to groups](img/select-group2.png) +To create a group: -Click on "New group" on the top right side of your screen: +1. Expand the left sidebar by clicking the three bars at the upper left corner + and then navigate to **Groups**. -![New group](img/click-on-new-group.png) + ![Go to groups](img/create_new_group_sidebar.png) -Fill out the information required: +1. Once in your groups dashboard, click on **New group**. -1. Add a group path or group name (you can't add spaces, so you can use hyphens or underscores) + ![Create new group information](img/create_new_group_info.png) -1. Add details or a group description +1. Fill out the needed information: -1. You can choose a group avatar if you'd like + 1. Set the "Group path" which will be the namespace under which your projects + will be hosted (path can contain only letters, digits, underscores, dashes + and dots; it cannot start with dashes or end in dot). + 1. Optionally, you can add a description so that others can briefly understand + what this group is about. + 1. Optionally, choose and avatar for your project. + 1. Choose the [visibility level](../public_access/public_access.md). -1. Click on "create group" +1. Finally, click the **Create group** button. -![Group information](img/group_info.png) - -## Add a project to a group +## Add a new project to a group There are 2 different ways to add a new project to a group: -* Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md) +- Select a group and then click on the **New project** button. + + ![New project](img/create_new_project_from_group.png) -![New project](img/new_project.png) + You can then continue on [creating a project](create-project.md). -* When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen +- While you are [creating a project](create-project.md), select a group namespace + you've already created from the dropdown menu. -![Create a group](img/create_group.png) + ![Select group](img/select_group_dropdown.png) diff --git a/doc/gitlab-basics/img/click-on-new-group.png b/doc/gitlab-basics/img/click-on-new-group.png deleted file mode 100644 index 6450deec6fc..00000000000 Binary files a/doc/gitlab-basics/img/click-on-new-group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/create_group.png b/doc/gitlab-basics/img/create_group.png deleted file mode 100644 index 7ecc3baa990..00000000000 Binary files a/doc/gitlab-basics/img/create_group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/create_new_group_info.png b/doc/gitlab-basics/img/create_new_group_info.png new file mode 100644 index 00000000000..c8eddfd1bbb Binary files /dev/null and b/doc/gitlab-basics/img/create_new_group_info.png differ diff --git a/doc/gitlab-basics/img/create_new_group_sidebar.png b/doc/gitlab-basics/img/create_new_group_sidebar.png new file mode 100644 index 00000000000..28017ee02e0 Binary files /dev/null and b/doc/gitlab-basics/img/create_new_group_sidebar.png differ diff --git a/doc/gitlab-basics/img/create_new_project_from_group.png b/doc/gitlab-basics/img/create_new_project_from_group.png new file mode 100644 index 00000000000..6d41d17f9ca Binary files /dev/null and b/doc/gitlab-basics/img/create_new_project_from_group.png differ diff --git a/doc/gitlab-basics/img/group_info.png b/doc/gitlab-basics/img/group_info.png deleted file mode 100644 index 2507d6c295b..00000000000 Binary files a/doc/gitlab-basics/img/group_info.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select-group2.png b/doc/gitlab-basics/img/select-group2.png deleted file mode 100644 index aee22c638db..00000000000 Binary files a/doc/gitlab-basics/img/select-group2.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select_group_dropdown.png b/doc/gitlab-basics/img/select_group_dropdown.png new file mode 100644 index 00000000000..7d8b89c2df9 Binary files /dev/null and b/doc/gitlab-basics/img/select_group_dropdown.png differ -- cgit v1.2.1 From 54642e5245df774eefc1a1bd82b9c033defb5bc1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:21:47 +0200 Subject: Reference the web editor docs in GitLab basics "Create a branch" --- doc/gitlab-basics/create-branch.md | 43 ++++----------------- .../img/web_editor_new_branch_from_issue.png | Bin 0 -> 4728 bytes doc/user/project/repository/web_editor.md | 4 +- 3 files changed, 10 insertions(+), 37 deletions(-) create mode 100644 doc/user/project/repository/img/web_editor_new_branch_from_issue.png diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md index 8f078c7692f..ad94f0dad29 100644 --- a/doc/gitlab-basics/create-branch.md +++ b/doc/gitlab-basics/create-branch.md @@ -2,38 +2,11 @@ A branch is an independent line of development. -New commits are recorded in the history for the current branch, which results in taking the source from someone’s repository (the place where the history of your work is stored) at certain point in time, and apply your own changes to it in the history of the project. - -To add changes to your GitLab project, you should create a branch. You can do it in your [shell](basic-git-commands.md) or in GitLab. - -To create a new branch in GitLab, sign in and then select a project on the right side of your screen: - -![Select a project](img/select_project.png) - -Click on "commits" on the menu on the left side of your screen: - -![Commits](img/commits.png) - -Click on the "branches" tab: - -![Branches](img/branches.png) - -Click on the "new branch" button on the right side of the screen: - -![New branch](img/newbranch.png) - -Fill out the information required: - -1. Add a name for your new branch (you can't add spaces, so you can use hyphens or underscores) - -1. On the "create from" space, add the the name of the branch you want to branch off from - -1. Click on the button "create branch" - -![Branch info](img/branch_info.png) - -### Note: - -You will be able to find and select the name of your branch in the white box next to a project's name: - -![Branch name](img/branch_name.png) +New commits are recorded in the history for the current branch, which results +in taking the source from someone’s repository (the place where the history of +your work is stored) at certain point in time, and apply your own changes to it +in the history of the project. + +To add changes to your GitLab project, you should create a branch. You can do +it in your [terminal](basic-git-commands.md) or by +[using the web interface](../user/project/repository/web_editor.md#create-a-new-branch). diff --git a/doc/user/project/repository/img/web_editor_new_branch_from_issue.png b/doc/user/project/repository/img/web_editor_new_branch_from_issue.png new file mode 100644 index 00000000000..b0a63ddf0ab Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_branch_from_issue.png differ diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md index 993c6bfb7e9..675e89e4247 100644 --- a/doc/user/project/repository/web_editor.md +++ b/doc/user/project/repository/web_editor.md @@ -97,11 +97,11 @@ There are multiple ways to create a branch from GitLab's web interface. In case your development workflow dictates to have an issue for every merge request, you can quickly create a branch right on the issue page which will be -tied with the issue itself. You can see a **New Branch** button after the issue +tied with the issue itself. You can see a **New branch** button after the issue description, unless there is already a branch with the same name or a referenced merge request. -![New Branch Button](img/new_branch_from_issue.png) +![New Branch Button](img/web_editor_new_branch_from_issue.png) Once you click it, a new branch will be created that diverges from the default branch of your project, by default `master`. The branch name will be based on -- cgit v1.2.1 From dd9e5ba16598eceb61a2d27a4519710b84d01397 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Tue, 11 Oct 2016 05:33:55 +0000 Subject: Fix step number and token param in URL example. Fix gitlab-rails command code formatting. --- doc/administration/troubleshooting/debug.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md index d127d7b85e5..d8dce4388e1 100644 --- a/doc/administration/troubleshooting/debug.md +++ b/doc/administration/troubleshooting/debug.md @@ -144,14 +144,14 @@ separate Rails process to debug the issue: 1. Obtain the private token for your user (Profile Settings -> Account). 1. Bring up the GitLab Rails console. For omnibus users, run: - ```` + ``` sudo gitlab-rails console ``` 1. At the Rails console, run: ```ruby - [1] pry(main)> app.get '/private_token?' + [1] pry(main)> app.get '/?private_token=' ``` For example: -- cgit v1.2.1 From 7d703a4dbbce44fda3cc0cfed7c48c27f496b6a0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:41:28 +0200 Subject: New images for GitLab basics "Fork project" docs --- doc/gitlab-basics/fork-project.md | 19 ++++++++++--------- doc/gitlab-basics/img/fork.png | Bin 896 -> 0 bytes doc/gitlab-basics/img/fork_choose_namespace.png | Bin 0 -> 39253 bytes doc/gitlab-basics/img/fork_new.png | Bin 0 -> 25540 bytes doc/gitlab-basics/img/select_project.png | Bin 16176 -> 0 bytes 5 files changed, 10 insertions(+), 9 deletions(-) delete mode 100644 doc/gitlab-basics/img/fork.png create mode 100644 doc/gitlab-basics/img/fork_choose_namespace.png create mode 100644 doc/gitlab-basics/img/fork_new.png delete mode 100644 doc/gitlab-basics/img/select_project.png diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md index 7232fc2c1ae..6c232fe6086 100644 --- a/doc/gitlab-basics/fork-project.md +++ b/doc/gitlab-basics/fork-project.md @@ -1,19 +1,20 @@ # How to fork a project -A fork is a copy of an original repository that you can put somewhere else -or where you can experiment and apply changes that you can later decide if +A fork is a copy of an original repository that you can put in another namespace +where you can experiment and apply changes that you can later decide if publishing or not, without affecting your original project. It takes just a few steps to fork a project in GitLab. -Sign in to GitLab. +1. Go to a project's dashboard under the **Project** tab and click on the + **Fork** button. -Select a project on the right side of your screen: + ![Click on Fork button](img/fork_new.png) -![Select a project](img/select_project.png) +1. You will be asked where to fork the repository. Click on the user or group + to where you'd like to add the forked project. -Click on the "fork" button on the right side of your screen: + ![Choose namespace](img/fork_choose_namespace.png) -![Fork](img/fork.png) - -Click on the user or group to where you'd like to add the forked project. +1. After a few moments, depending on the repository's size, the forking will + complete. diff --git a/doc/gitlab-basics/img/fork.png b/doc/gitlab-basics/img/fork.png deleted file mode 100644 index 13ff8345616..00000000000 Binary files a/doc/gitlab-basics/img/fork.png and /dev/null differ diff --git a/doc/gitlab-basics/img/fork_choose_namespace.png b/doc/gitlab-basics/img/fork_choose_namespace.png new file mode 100644 index 00000000000..82c9c3bd39e Binary files /dev/null and b/doc/gitlab-basics/img/fork_choose_namespace.png differ diff --git a/doc/gitlab-basics/img/fork_new.png b/doc/gitlab-basics/img/fork_new.png new file mode 100644 index 00000000000..41885223286 Binary files /dev/null and b/doc/gitlab-basics/img/fork_new.png differ diff --git a/doc/gitlab-basics/img/select_project.png b/doc/gitlab-basics/img/select_project.png deleted file mode 100644 index 3bb832ea8d0..00000000000 Binary files a/doc/gitlab-basics/img/select_project.png and /dev/null differ -- cgit v1.2.1 From 7374f876dcfd2c113d16367fa62ced23934130c9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:44:26 +0200 Subject: Reference the web editor docs in GitLab basics "Add a file" --- doc/gitlab-basics/add-file.md | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index 86aff749ff1..e9fbcbc23a9 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -1,27 +1,5 @@ # How to add a file -You can create a file in your [shell](command-line-commands.md) or in GitLab. - -To create a file in GitLab, sign in to GitLab. - -Select a project on the right side of your screen: - -![Select a project](img/select_project.png) - -It's a good idea to [create a branch](create-branch.md), but it's not necessary. - -Go to the directory where you'd like to add the file and click on the "+" sign next to the name of the project and directory: - -![Create a file](img/create_file.png) - -Name your file (you can't add spaces, so you can use hyphens or underscores). Don't forget to include the markup language you'd like to use : - -![File name](img/file_name.png) - -Add all the information that you'd like to include in your file: - -![Add information](img/white_space.png) - -Add a commit message based on what you just added and then click on "commit changes": - -![Commit changes](img/commit_changes.png) +You can create a file in your [terminal](command-line-commands.md) and push +to GitLab or you can use the +[web interface](../user/project/repository/web_editor.md#create-a-file). -- cgit v1.2.1 From 73fff8d9e33d31b0958b7ef889a6ad3ff6dbf4c5 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Tue, 11 Oct 2016 10:58:46 +0500 Subject: Build instead create in label_link model spec --- spec/models/label_link_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index 5e6f8ca1528..c18ed8574b1 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -1,8 +1,7 @@ require 'spec_helper' describe LabelLink, models: true do - let(:label) { create(:label_link) } - it { expect(label).to be_valid } + it { expect(build(:label_link)).to be_valid } it { is_expected.to belong_to(:label) } it { is_expected.to belong_to(:target) } -- cgit v1.2.1 From 85df1bf02dbcd180ad7db0c0e3c609671dac1a11 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Tue, 11 Oct 2016 11:02:53 +0500 Subject: Remove empty describe block on key spec model --- spec/models/key_spec.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index fd4a2beff58..7fc6ed1dd54 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -5,9 +5,6 @@ describe Key, models: true do it { is_expected.to belong_to(:user) } end - describe "Mass assignment" do - end - describe "Validation" do it { is_expected.to validate_presence_of(:title) } it { is_expected.to validate_presence_of(:key) } -- cgit v1.2.1 From 987c21f51ea67f1bd00fe50e61941920bc1feaa4 Mon Sep 17 00:00:00 2001 From: Manthan Mallikarjun Date: Mon, 4 Jul 2016 10:20:16 -0700 Subject: Add an example for testing a phoenix application with Gitlab CI. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + doc/ci/examples/README.md | 1 + doc/ci/examples/test-phoenix-application.md | 52 +++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 doc/ci/examples/test-phoenix-application.md diff --git a/CHANGELOG b/CHANGELOG index 06af4e4d6f4..d9c282257b4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.13.0 (unreleased) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup + - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 40f0165deef..08fbd9afa2f 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -11,6 +11,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md) - [Test a Clojure application](test-clojure-application.md) - [Test a Scala application](test-scala-application.md) +- [Test a Phoenix application](test-phoenix-application.md) - [Using `dpl` as deployment tool](deployment/README.md) - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md new file mode 100644 index 00000000000..78cab2c0aeb --- /dev/null +++ b/doc/ci/examples/test-phoenix-application.md @@ -0,0 +1,52 @@ +## Test a Phoenix application + +This example demonstrates the integration of Gitlab CI with Phoenix, elixir and +postgres. + +### Add `.gitlab-ci.yml` file to project + +The following `.gitlab-ci.yml` should be added in the root of your +repository to trigger CI: + +```yaml +image: elixir:1.3.1 + +services: + - postgres:9.5.3 + +variables: + MIX_ENV: "test" + +before_script: + # Setup phoenix dependencies + - apt-get update + - apt-get install -y postgresql-client + - mix local.hex --force + - mix deps.get --only test + - mix ecto.reset + +test: + script: + - mix test +``` + +The variables will set the Mix environment to test. The +before_script will install `psql`, and other phoenix dependencies and will also +run your migrations. + +Finally, the test script will run your tests. + +### Update the Config Settings + +In `config/test.exs`, update the database hostname: +``` +config :my_app, MyApp.Repo, + hostname: if(System.get_env("CI"), do: "postgres", else: "localhost"), +``` + +### Add the Migrations Folder + +If you do not have any migrations yet, you will need to create an empty +`.gitkeep` file in `priv/repo/migrations`. + +**Source**: https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 -- cgit v1.2.1 From 8a3f389df620570e2b51f088573b90644c53348e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 09:36:03 +0200 Subject: Improve a bit the example .gitlab-ci.yml for Phoenix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/ci/examples/test-phoenix-application.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md index 78cab2c0aeb..150698ca04b 100644 --- a/doc/ci/examples/test-phoenix-application.md +++ b/doc/ci/examples/test-phoenix-application.md @@ -1,7 +1,7 @@ ## Test a Phoenix application -This example demonstrates the integration of Gitlab CI with Phoenix, elixir and -postgres. +This example demonstrates the integration of Gitlab CI with Phoenix, Elixir and +Postgres. ### Add `.gitlab-ci.yml` file to project @@ -9,10 +9,10 @@ The following `.gitlab-ci.yml` should be added in the root of your repository to trigger CI: ```yaml -image: elixir:1.3.1 +image: elixir:1.3 services: - - postgres:9.5.3 + - postgres:9.6 variables: MIX_ENV: "test" @@ -30,16 +30,17 @@ test: - mix test ``` -The variables will set the Mix environment to test. The -before_script will install `psql`, and other phoenix dependencies and will also +The variables will set the Mix environment to "test". The +`before_script` will install `psql`, some Phoenix dependencies, and will also run your migrations. -Finally, the test script will run your tests. +Finally, the test `script` will run your tests. ### Update the Config Settings In `config/test.exs`, update the database hostname: -``` + +```elixir config :my_app, MyApp.Repo, hostname: if(System.get_env("CI"), do: "postgres", else: "localhost"), ``` @@ -49,4 +50,7 @@ config :my_app, MyApp.Repo, If you do not have any migrations yet, you will need to create an empty `.gitkeep` file in `priv/repo/migrations`. -**Source**: https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 +### Sources + +- https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 +- https://davejlong.com/ci-with-phoenix-and-gitlab/ -- cgit v1.2.1 From ebba49149395ba6fb0f14c3aa9c46a496b234dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 7 Oct 2016 18:35:36 +0200 Subject: Add a new gitlab:users:clear_all_authentication_tokens task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + doc/raketasks/user_management.md | 15 ++++++++++++++ lib/tasks/gitlab/users.rake | 11 +++++++++++ spec/tasks/gitlab/users_rake_spec.rb | 38 ++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 lib/tasks/gitlab/users.rake create mode 100644 spec/tasks/gitlab/users_rake_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 06af4e4d6f4..1a04e42c803 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -76,6 +76,7 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) + - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 - Reduce queries needed to find users using their SSH keys when pushing commits - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index 8a5e2d6e16b..044b104f5c2 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -70,3 +70,18 @@ sudo gitlab-rake gitlab:two_factor:disable_for_all_users # installation from source bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production ``` + +## Clear authentication tokens for all users. Important! Data loss! + +Clear authentication tokens for all users in the GitLab database. This +task is useful if your users' authentication tokens might have been exposed in +any way. All the existing tokens will become invalid, and new tokens are +automatically generated upon sign-in or user modification. + +``` +# omnibus-gitlab +sudo gitlab-rake gitlab:users:clear_all_authentication_tokens + +# installation from source +bundle exec rake gitlab:users:clear_all_authentication_tokens RAILS_ENV=production +``` diff --git a/lib/tasks/gitlab/users.rake b/lib/tasks/gitlab/users.rake new file mode 100644 index 00000000000..3a16ace60bd --- /dev/null +++ b/lib/tasks/gitlab/users.rake @@ -0,0 +1,11 @@ +namespace :gitlab do + namespace :users do + desc "GitLab | Clear the authentication token for all users" + task clear_all_authentication_tokens: :environment do |t, args| + # Do small batched updates because these updates will be slow and locking + User.select(:id).find_in_batches(batch_size: 100) do |batch| + User.where(id: batch.map(&:id)).update_all(authentication_token: nil) + end + end + end +end diff --git a/spec/tasks/gitlab/users_rake_spec.rb b/spec/tasks/gitlab/users_rake_spec.rb new file mode 100644 index 00000000000..e6ebef82b78 --- /dev/null +++ b/spec/tasks/gitlab/users_rake_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:users namespace rake task' do + let(:enable_registry) { true } + + before :all do + Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/users' + + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + def run_rake_task(task_name) + Rake::Task[task_name].reenable + Rake.application.invoke_task task_name + end + + describe 'clear_all_authentication_tokens' do + before do + # avoid writing task output to spec progress + allow($stdout).to receive :write + end + + context 'gitlab version' do + it 'clears the authentication token for all users' do + create_list(:user, 2) + + expect(User.pluck(:authentication_token)).to all(be_present) + + run_rake_task('gitlab:users:clear_all_authentication_tokens') + + expect(User.pluck(:authentication_token)).to all(be_nil) + end + end + end +end -- cgit v1.2.1 From 8d8282b42e765d20532d32b0598f42ea707f31f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 10:21:22 +0200 Subject: Add a safeguard in User#set_projects_limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/user.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 892ac28d5b3..f367f4616fb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -589,6 +589,11 @@ class User < ActiveRecord::Base end def set_projects_limit + # `User.select(:id)` raises + # `ActiveModel::MissingAttributeError: missing attribute: projects_limit` + # without this safeguard! + return unless self.has_attribute?(:projects_limit) + connection_default_value_defined = new_record? && !projects_limit_changed? return unless self.projects_limit.nil? || connection_default_value_defined -- cgit v1.2.1 From 6c1690fcc03406ad3230cb05ba8701289a25ba28 Mon Sep 17 00:00:00 2001 From: Artem Sidorenko Date: Tue, 13 Sep 2016 23:05:16 +0200 Subject: Allow empty merge requests --- CHANGELOG | 1 + app/assets/stylesheets/pages/merge_requests.scss | 12 ++++++ app/services/merge_requests/build_service.rb | 23 +++++----- .../projects/merge_requests/_new_compare.html.haml | 13 ------ .../projects/merge_requests/_new_submit.html.haml | 50 ++++++++++++---------- .../shared/icons/_illustration_no_commits.svg | 1 + spec/services/merge_requests/build_service_spec.rb | 20 ++++++++- 7 files changed, 71 insertions(+), 49 deletions(-) create mode 100644 app/views/shared/icons/_illustration_no_commits.svg diff --git a/CHANGELOG b/CHANGELOG index 72e146f4f3f..81c0bfbe3e7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -80,6 +80,7 @@ v 8.13.0 (unreleased) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..2158dc0cfe8 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -204,6 +204,18 @@ word-break: break-all; } +.commits-empty { + text-align: center; + + h4 { + padding-top: 20px; + padding-bottom: 10px; + } + svg { + width: 230px; + } +} + .mr-list { .merge-request { padding: 10px 15px; diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index e57791f6818..404f75616b5 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -4,7 +4,7 @@ module MergeRequests merge_request = MergeRequest.new(params) # Set MR attributes - merge_request.can_be_created = false + merge_request.can_be_created = true merge_request.compare_commits = [] merge_request.source_project = project unless merge_request.source_project @@ -22,6 +22,12 @@ module MergeRequests return build_failed(merge_request, message) end + if merge_request.source_project == merge_request.target_project && + merge_request.target_branch == merge_request.source_branch + + return build_failed(merge_request, 'You must select different branches') + end + compare = CompareService.new.execute( merge_request.source_project, merge_request.source_branch, @@ -29,17 +35,8 @@ module MergeRequests merge_request.target_branch, ) - commits = compare.commits - - # At this point we decide if merge request can be created - # If we have at least one commit to merge -> creation allowed - if commits.present? - merge_request.compare_commits = commits - merge_request.can_be_created = true - merge_request.compare = compare - else - merge_request.can_be_created = false - end + merge_request.compare_commits = compare.commits + merge_request.compare = compare set_title_and_description(merge_request) end @@ -89,6 +86,8 @@ module MergeRequests end end + merge_request.title = merge_request.wip_title if commits.empty? + merge_request end diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index de39964fca8..466ec1475d8 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -65,19 +65,6 @@ - if @merge_request.errors.any? = form_errors(@merge_request) - - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? - .light-well.append-bottom-default - .center - %h4 - There isn't anything to merge. - %p.slead - - if @merge_request.source_branch == @merge_request.target_branch - You'll need to use different branch names to get a valid comparison. - - else - %span.label-branch #{@merge_request.source_branch} - and - %span.label-branch #{@merge_request.target_branch} - are the same. = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" :javascript diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 88d8013a0d1..da6927879a4 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -18,29 +18,35 @@ = f.hidden_field :target_branch .mr-compare.merge-request - %ul.merge-request-tabs.nav-links.no-top.no-bottom - %li.commits-tab.active - = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do - Commits - %span.badge= @commits.size - - if @pipeline - %li.builds-tab - = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do - Builds - %span.badge= @statuses.size - %li.diffs-tab - = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do - Changes - %span.badge= @merge_request.diff_size + - if @commits.empty? + .commits-empty + %h4 + There are no commits yet. + = custom_icon ('illustration_no_commits') + - else + %ul.merge-request-tabs.nav-links.no-top.no-bottom + %li.commits-tab.active + = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do + Commits + %span.badge= @commits.size + - if @pipeline + %li.builds-tab + = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do + Builds + %span.badge= @statuses.size + %li.diffs-tab + = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do + Changes + %span.badge= @merge_request.diff_size - .tab-content - #commits.commits.tab-pane.active - = render "projects/merge_requests/show/commits" - #diffs.diffs.tab-pane - - # This tab is always loaded via AJAX - - if @pipeline - #builds.builds.tab-pane - = render "projects/merge_requests/show/builds" + .tab-content + #commits.commits.tab-pane.active + = render "projects/merge_requests/show/commits" + #diffs.diffs.tab-pane + - # This tab is always loaded via AJAX + - if @pipeline + #builds.builds.tab-pane + = render "projects/merge_requests/show/builds" .mr-loading-status = spinner diff --git a/app/views/shared/icons/_illustration_no_commits.svg b/app/views/shared/icons/_illustration_no_commits.svg new file mode 100644 index 00000000000..4f9d9add60d --- /dev/null +++ b/app/views/shared/icons/_illustration_no_commits.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 0d586e2216b..3a3f07ddcb9 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -52,12 +52,28 @@ describe MergeRequests::BuildService, services: true do end end - context 'no commits in the diff' do - let(:commits) { [] } + context 'same source and target branch' do + let(:source_branch) { 'master' } it 'forbids the merge request from being created' do expect(merge_request.can_be_created).to eq(false) end + + it 'adds an error message to the merge request' do + expect(merge_request.errors).to contain_exactly('You must select different branches') + end + end + + context 'no commits in the diff' do + let(:commits) { [] } + + it 'allows the merge request to be created' do + expect(merge_request.can_be_created).to eq(true) + end + + it 'adds a WIP prefix to the merge request title' do + expect(merge_request.title).to eq('WIP: Feature branch') + end end context 'one commit in the diff' do -- cgit v1.2.1 From 815bf9b5892e215dec31ab0a45a5cc75b091e2af Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 11:07:37 +0200 Subject: Add new images in GitLab basics "Create issue" docs --- doc/gitlab-basics/create-issue.md | 33 ++++++++++++++++------------- doc/gitlab-basics/img/issue_title.png | Bin 8311 -> 0 bytes doc/gitlab-basics/img/issues.png | Bin 4153 -> 0 bytes doc/gitlab-basics/img/new_issue.png | Bin 2974 -> 0 bytes doc/gitlab-basics/img/new_issue_button.png | Bin 0 -> 3070 bytes doc/gitlab-basics/img/new_issue_page.png | Bin 0 -> 53268 bytes doc/gitlab-basics/img/newbranch.png | Bin 1244 -> 0 bytes doc/gitlab-basics/img/project_navbar.png | Bin 0 -> 5745 bytes doc/gitlab-basics/img/submit_new_issue.png | Bin 8644 -> 0 bytes 9 files changed, 18 insertions(+), 15 deletions(-) delete mode 100644 doc/gitlab-basics/img/issue_title.png delete mode 100644 doc/gitlab-basics/img/issues.png delete mode 100644 doc/gitlab-basics/img/new_issue.png create mode 100644 doc/gitlab-basics/img/new_issue_button.png create mode 100644 doc/gitlab-basics/img/new_issue_page.png delete mode 100644 doc/gitlab-basics/img/newbranch.png create mode 100644 doc/gitlab-basics/img/project_navbar.png delete mode 100644 doc/gitlab-basics/img/submit_new_issue.png diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md index e11c86cafa0..13e5a738c89 100644 --- a/doc/gitlab-basics/create-issue.md +++ b/doc/gitlab-basics/create-issue.md @@ -1,27 +1,30 @@ # How to create an Issue in GitLab -The Issue Tracker is a good place to add things that need to be improved or solved in a project. +The issue tracker is a good place to add things that need to be improved or +solved in a project. -To create an Issue, sign in to GitLab. +--- -Go to the project where you'd like to create the Issue: +1. Go to the project where you'd like to create the issue and navigate to the + **Issues** tab on top. -![Select a project](img/select_project.png) + ![Issues](img/project_navbar.png) -Click on "Issues" on the left side of your screen: +1. Click on the **New issue** button on the right side of your screen. -![Issues](img/issues.png) + ![New issue](img/new_issue_button.png) -Click on the "+ new issue" button on the right side of your screen: +1. At the very minimum, add a title and a description to your issue. + You may assign it to a user, add a milestone or add labels (all optional). -![New issue](img/new_issue.png) + ![Issue title and description](img/new_issue_page.png) -Add a title and a description to your issue: +1. When ready, click on **Submit issue**. -![Issue title and description](img/issue_title.png) +--- -You may assign the Issue to a user, add a milestone and add labels (they are all optional). Then click on "submit new issue": - -![Submit new issue](img/submit_new_issue.png) - -Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](../user/project/issues/automatic_issue_closing.md). +Your Issue will now be added to the issue tracker of the project you opened it +at and will be ready to be reviewed. You can comment on it and mention the +people involved. You can also link issues to the merge requests where the issues +are solved. To do this, you can use an +[issue closing pattern](../user/project/issues/automatic_issue_closing.md). diff --git a/doc/gitlab-basics/img/issue_title.png b/doc/gitlab-basics/img/issue_title.png deleted file mode 100644 index 60a6f7973be..00000000000 Binary files a/doc/gitlab-basics/img/issue_title.png and /dev/null differ diff --git a/doc/gitlab-basics/img/issues.png b/doc/gitlab-basics/img/issues.png deleted file mode 100644 index 14e9cdb64e1..00000000000 Binary files a/doc/gitlab-basics/img/issues.png and /dev/null differ diff --git a/doc/gitlab-basics/img/new_issue.png b/doc/gitlab-basics/img/new_issue.png deleted file mode 100644 index 94e7503dd8b..00000000000 Binary files a/doc/gitlab-basics/img/new_issue.png and /dev/null differ diff --git a/doc/gitlab-basics/img/new_issue_button.png b/doc/gitlab-basics/img/new_issue_button.png new file mode 100644 index 00000000000..46b626bed65 Binary files /dev/null and b/doc/gitlab-basics/img/new_issue_button.png differ diff --git a/doc/gitlab-basics/img/new_issue_page.png b/doc/gitlab-basics/img/new_issue_page.png new file mode 100644 index 00000000000..843504130b7 Binary files /dev/null and b/doc/gitlab-basics/img/new_issue_page.png differ diff --git a/doc/gitlab-basics/img/newbranch.png b/doc/gitlab-basics/img/newbranch.png deleted file mode 100644 index d5fcf33c4ea..00000000000 Binary files a/doc/gitlab-basics/img/newbranch.png and /dev/null differ diff --git a/doc/gitlab-basics/img/project_navbar.png b/doc/gitlab-basics/img/project_navbar.png new file mode 100644 index 00000000000..97cf3cd9702 Binary files /dev/null and b/doc/gitlab-basics/img/project_navbar.png differ diff --git a/doc/gitlab-basics/img/submit_new_issue.png b/doc/gitlab-basics/img/submit_new_issue.png deleted file mode 100644 index 78b854c8903..00000000000 Binary files a/doc/gitlab-basics/img/submit_new_issue.png and /dev/null differ -- cgit v1.2.1 From 54fd5e830a867bd20d263bce8b37385dd7bd7b6e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 11:29:16 +0200 Subject: New images for GitLab basics "Create MR" docs --- doc/gitlab-basics/add-merge-request.md | 49 +++++++++------------ doc/gitlab-basics/img/button-create-mr.png | Bin 5927 -> 0 bytes doc/gitlab-basics/img/compare_branches.png | Bin 1520 -> 0 bytes doc/gitlab-basics/img/merge_request_new.png | Bin 0 -> 3596 bytes doc/gitlab-basics/img/merge_request_page.png | Bin 0 -> 91432 bytes .../img/merge_request_select_branch.png | Bin 0 -> 50707 bytes doc/gitlab-basics/img/new_merge_request.png | Bin 3162 -> 0 bytes doc/gitlab-basics/img/select_branch.png | Bin 11207 -> 0 bytes 8 files changed, 20 insertions(+), 29 deletions(-) delete mode 100644 doc/gitlab-basics/img/button-create-mr.png delete mode 100644 doc/gitlab-basics/img/compare_branches.png create mode 100644 doc/gitlab-basics/img/merge_request_new.png create mode 100644 doc/gitlab-basics/img/merge_request_page.png create mode 100644 doc/gitlab-basics/img/merge_request_select_branch.png delete mode 100644 doc/gitlab-basics/img/new_merge_request.png delete mode 100644 doc/gitlab-basics/img/select_branch.png diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md index 082d1408aa2..bf01fe51dc3 100644 --- a/doc/gitlab-basics/add-merge-request.md +++ b/doc/gitlab-basics/add-merge-request.md @@ -1,42 +1,33 @@ # How to create a merge request -Merge Requests are useful to integrate separate changes that you've made to a project, on different branches. +Merge requests are useful to integrate separate changes that you've made to a +project, on different branches. This is a brief guide on how to create a merge +request. For more information, check the +[merge requests documentation](../user/project/merge_requests.md). -To create a new Merge Request, sign in to GitLab. +--- -Go to the project where you'd like to merge your changes: +1. Before you start, you should have already [created a branch](create-branch.md) + and [pushed your changes](basic-git-commands.md) to GitLab. -![Select a project](img/select_project.png) +1. You can then go to the project where you'd like to merge your changes and + click on the **Merge requests** tab. -Click on "Merge Requests" on the left side of your screen: + ![Merge requests](img/project_navbar.png) -![Merge requests](img/merge_requests.png) +1. Click on **New merge request** on the right side of the screen. -Click on "+ new Merge Request" on the right side of the screen: + ![New Merge Request](img/merge_request_new.png) -![New Merge Request](img/new_merge_request.png) +1. Select a source branch and click on the **Compare branches and continue** button. -Select a source branch or branch: + ![Select a branch](img/merge_request_select_branch.png) -![Select a branch](img/select_branch.png) +1. At a minimum, add a title and a description to your merge request. Optionally, + select a user to review your merge request and to accept or close it. You may + also select a milestone and labels. -Click on the "compare branches" button: + ![New merge request page](img/merge_request_page.png) -![Compare branches](img/compare_branches.png) - -Add a title and a description to your Merge Request: - -![Add a title and description](img/title_description_mr.png) - -Select a user to review your Merge Request and to accept or close it. You may also select milestones and labels (they are optional). Then click on the "submit new Merge Request" button: - -![Add a new merge request](img/add_new_merge_request.png) - -Your Merge Request will be ready to be approved and published. - -### Note - -After you created a new branch, you'll immediately find a "create a Merge Request" button at the top of your screen. -You may automatically create a Merge Request from your recently created branch when clicking on this button: - -![Automatic MR button](img/button-create-mr.png) +1. When ready, click on the **Submit merge request** button. Your merge request + will be ready to be approved and published. diff --git a/doc/gitlab-basics/img/button-create-mr.png b/doc/gitlab-basics/img/button-create-mr.png deleted file mode 100644 index b52ab148839..00000000000 Binary files a/doc/gitlab-basics/img/button-create-mr.png and /dev/null differ diff --git a/doc/gitlab-basics/img/compare_branches.png b/doc/gitlab-basics/img/compare_branches.png deleted file mode 100644 index 8a18453dd05..00000000000 Binary files a/doc/gitlab-basics/img/compare_branches.png and /dev/null differ diff --git a/doc/gitlab-basics/img/merge_request_new.png b/doc/gitlab-basics/img/merge_request_new.png new file mode 100644 index 00000000000..0aba5743f01 Binary files /dev/null and b/doc/gitlab-basics/img/merge_request_new.png differ diff --git a/doc/gitlab-basics/img/merge_request_page.png b/doc/gitlab-basics/img/merge_request_page.png new file mode 100644 index 00000000000..68c3bbf9444 Binary files /dev/null and b/doc/gitlab-basics/img/merge_request_page.png differ diff --git a/doc/gitlab-basics/img/merge_request_select_branch.png b/doc/gitlab-basics/img/merge_request_select_branch.png new file mode 100644 index 00000000000..516436ff6cc Binary files /dev/null and b/doc/gitlab-basics/img/merge_request_select_branch.png differ diff --git a/doc/gitlab-basics/img/new_merge_request.png b/doc/gitlab-basics/img/new_merge_request.png deleted file mode 100644 index 842f5ebed74..00000000000 Binary files a/doc/gitlab-basics/img/new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select_branch.png b/doc/gitlab-basics/img/select_branch.png deleted file mode 100644 index f72a3ffb57f..00000000000 Binary files a/doc/gitlab-basics/img/select_branch.png and /dev/null differ -- cgit v1.2.1 From 777ca780275d7177dffa7d95e1e863192537da68 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 11:30:35 +0200 Subject: Rearrange GitLab basics READMEs [ci skip] --- doc/README.md | 2 +- doc/gitlab-basics/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/README.md b/doc/README.md index 9017b143260..99fbfc3438d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -7,7 +7,7 @@ - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry. -- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. +- [GitLab basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). - [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Markdown](user/markdown.md) GitLab's advanced formatting system. diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 38843f36b2e..746ccb89705 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -11,5 +11,5 @@ Step-by-step guides on the basics of working with Git and GitLab. - [Fork a project](fork-project.md) - [Add a file](add-file.md) - [Add an image](add-image.md) -- [Create a Merge Request](add-merge-request.md) -- [Create an Issue](create-issue.md) +- [Create an issue](create-issue.md) +- [Create a merge request](add-merge-request.md) -- cgit v1.2.1 From d87df157987e5ba2690fc1a7937e557821efb997 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Tue, 11 Oct 2016 11:34:20 +0200 Subject: changed the scss for the top line connectors to be exactly centered --- app/assets/stylesheets/pages/pipelines.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 21224447628..05f59279637 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -316,7 +316,7 @@ &::before { content: ''; position: absolute; - top: 49%; + top: 48%; left: -48px; border-top: 2px solid $border-color; width: 48px; @@ -493,7 +493,7 @@ &::after { content: ''; position: absolute; - top: 49%; + top: 48%; right: -48px; border-top: 2px solid $border-color; width: 48px; @@ -589,7 +589,7 @@ width: 21px; height: 25px; position: absolute; - top: -31px; + top: -32px; border-top: 2px solid $border-color; } -- cgit v1.2.1 From f7d2a97a84908c3cddf2d4fc550fbc2c7857af81 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 10 Oct 2016 17:28:09 +0100 Subject: Fixes padding in all clipboard icons that have .btn class --- app/helpers/button_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index a695aceea76..353e7fd33fd 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -22,7 +22,7 @@ module ButtonHelper class: "btn #{css_class}", data: data, type: :button, - title: "Copy to Clipboard" + title: 'Copy to Clipboard' end def http_clone_button(project, placement = 'right', append_link: true) -- cgit v1.2.1 From 4269e303515386f38a135fd07ad38955c027e424 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 10 Oct 2016 20:25:14 +0100 Subject: Adds entry to CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 56c3ee90541..6afcbf28013 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -88,6 +88,7 @@ v 8.13.0 (unreleased) - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) + - Fixes padding in all clipboard icons that have .btn class - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found -- cgit v1.2.1 From b0646c4f23aff0343c2fd455c7dc5de52ca6a362 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 11 Oct 2016 10:17:56 +0100 Subject: Adds btn-transparent class in correct place due to changes in master --- app/helpers/button_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 353e7fd33fd..85e1dc33ee8 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -15,7 +15,7 @@ module ButtonHelper # # See http://clipboardjs.com/#usage def clipboard_button(data = {}) - css_class = data[:class] || 'btn-clipboard' + css_class = data[:class] || 'btn-clipboard btn-transparent' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), -- cgit v1.2.1 From e98d4940fe8f5315f7467a13fcdd952ace73f9e8 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 13:14:08 +0200 Subject: Remove redundant images [ci skip] --- doc/gitlab-basics/README.md | 2 +- doc/gitlab-basics/img/add_new_merge_request.png | Bin 9003 -> 0 bytes doc/gitlab-basics/img/branch_info.png | Bin 7572 -> 0 bytes doc/gitlab-basics/img/branch_name.png | Bin 2137 -> 0 bytes doc/gitlab-basics/img/branches.png | Bin 3548 -> 0 bytes doc/gitlab-basics/img/commit_changes.png | Bin 4941 -> 0 bytes doc/gitlab-basics/img/commit_message.png | Bin 5103 -> 0 bytes doc/gitlab-basics/img/commits.png | Bin 4112 -> 0 bytes doc/gitlab-basics/img/create_file.png | Bin 2451 -> 0 bytes doc/gitlab-basics/img/edit_file.png | Bin 2221 -> 0 bytes doc/gitlab-basics/img/file_located.png | Bin 3078 -> 0 bytes doc/gitlab-basics/img/file_name.png | Bin 2412 -> 0 bytes doc/gitlab-basics/img/find_file.png | Bin 8426 -> 0 bytes doc/gitlab-basics/img/find_group.png | Bin 5897 -> 0 bytes doc/gitlab-basics/img/groups.png | Bin 4752 -> 0 bytes doc/gitlab-basics/img/image_file.png | Bin 2796 -> 0 bytes doc/gitlab-basics/img/merge_requests.png | Bin 4213 -> 0 bytes doc/gitlab-basics/img/select-group.png | Bin 6034 -> 0 bytes doc/gitlab-basics/img/settings.png | Bin 4149 -> 0 bytes doc/gitlab-basics/img/title_description_mr.png | Bin 11919 -> 0 bytes doc/gitlab-basics/img/white_space.png | Bin 2192 -> 0 bytes 21 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 doc/gitlab-basics/img/add_new_merge_request.png delete mode 100644 doc/gitlab-basics/img/branch_info.png delete mode 100644 doc/gitlab-basics/img/branch_name.png delete mode 100644 doc/gitlab-basics/img/branches.png delete mode 100644 doc/gitlab-basics/img/commit_changes.png delete mode 100644 doc/gitlab-basics/img/commit_message.png delete mode 100644 doc/gitlab-basics/img/commits.png delete mode 100644 doc/gitlab-basics/img/create_file.png delete mode 100644 doc/gitlab-basics/img/edit_file.png delete mode 100644 doc/gitlab-basics/img/file_located.png delete mode 100644 doc/gitlab-basics/img/file_name.png delete mode 100644 doc/gitlab-basics/img/find_file.png delete mode 100644 doc/gitlab-basics/img/find_group.png delete mode 100644 doc/gitlab-basics/img/groups.png delete mode 100644 doc/gitlab-basics/img/image_file.png delete mode 100644 doc/gitlab-basics/img/merge_requests.png delete mode 100644 doc/gitlab-basics/img/select-group.png delete mode 100644 doc/gitlab-basics/img/settings.png delete mode 100644 doc/gitlab-basics/img/title_description_mr.png delete mode 100644 doc/gitlab-basics/img/white_space.png diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 746ccb89705..d7e3aa35bdd 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -2,7 +2,7 @@ Step-by-step guides on the basics of working with Git and GitLab. -- [Command Line basics](command-line-commands.md) +- [Command line basics](command-line-commands.md) - [Start using Git on the command line](start-using-git.md) - [Create and add your SSH Keys](create-your-ssh-keys.md) - [Create a project](create-project.md) diff --git a/doc/gitlab-basics/img/add_new_merge_request.png b/doc/gitlab-basics/img/add_new_merge_request.png deleted file mode 100644 index e60992c4c6a..00000000000 Binary files a/doc/gitlab-basics/img/add_new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/img/branch_info.png b/doc/gitlab-basics/img/branch_info.png deleted file mode 100644 index 2264f3c5bf2..00000000000 Binary files a/doc/gitlab-basics/img/branch_info.png and /dev/null differ diff --git a/doc/gitlab-basics/img/branch_name.png b/doc/gitlab-basics/img/branch_name.png deleted file mode 100644 index 75fe8313611..00000000000 Binary files a/doc/gitlab-basics/img/branch_name.png and /dev/null differ diff --git a/doc/gitlab-basics/img/branches.png b/doc/gitlab-basics/img/branches.png deleted file mode 100644 index 8621bc05776..00000000000 Binary files a/doc/gitlab-basics/img/branches.png and /dev/null differ diff --git a/doc/gitlab-basics/img/commit_changes.png b/doc/gitlab-basics/img/commit_changes.png deleted file mode 100644 index a88809c5a3f..00000000000 Binary files a/doc/gitlab-basics/img/commit_changes.png and /dev/null differ diff --git a/doc/gitlab-basics/img/commit_message.png b/doc/gitlab-basics/img/commit_message.png deleted file mode 100644 index 4abe4517f98..00000000000 Binary files a/doc/gitlab-basics/img/commit_message.png and /dev/null differ diff --git a/doc/gitlab-basics/img/commits.png b/doc/gitlab-basics/img/commits.png deleted file mode 100644 index 2bfcaf75f01..00000000000 Binary files a/doc/gitlab-basics/img/commits.png and /dev/null differ diff --git a/doc/gitlab-basics/img/create_file.png b/doc/gitlab-basics/img/create_file.png deleted file mode 100644 index 5ebe1b227dd..00000000000 Binary files a/doc/gitlab-basics/img/create_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/edit_file.png b/doc/gitlab-basics/img/edit_file.png deleted file mode 100644 index 9d3e817d036..00000000000 Binary files a/doc/gitlab-basics/img/edit_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/file_located.png b/doc/gitlab-basics/img/file_located.png deleted file mode 100644 index e357cb5c6ab..00000000000 Binary files a/doc/gitlab-basics/img/file_located.png and /dev/null differ diff --git a/doc/gitlab-basics/img/file_name.png b/doc/gitlab-basics/img/file_name.png deleted file mode 100644 index 01639c77d0d..00000000000 Binary files a/doc/gitlab-basics/img/file_name.png and /dev/null differ diff --git a/doc/gitlab-basics/img/find_file.png b/doc/gitlab-basics/img/find_file.png deleted file mode 100644 index 6f26d26ae18..00000000000 Binary files a/doc/gitlab-basics/img/find_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/find_group.png b/doc/gitlab-basics/img/find_group.png deleted file mode 100644 index 1211510aae9..00000000000 Binary files a/doc/gitlab-basics/img/find_group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/groups.png b/doc/gitlab-basics/img/groups.png deleted file mode 100644 index ef3dca60cc8..00000000000 Binary files a/doc/gitlab-basics/img/groups.png and /dev/null differ diff --git a/doc/gitlab-basics/img/image_file.png b/doc/gitlab-basics/img/image_file.png deleted file mode 100644 index 7f304b8e1f2..00000000000 Binary files a/doc/gitlab-basics/img/image_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/merge_requests.png b/doc/gitlab-basics/img/merge_requests.png deleted file mode 100644 index 570164df18b..00000000000 Binary files a/doc/gitlab-basics/img/merge_requests.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select-group.png b/doc/gitlab-basics/img/select-group.png deleted file mode 100644 index 33b978dd899..00000000000 Binary files a/doc/gitlab-basics/img/select-group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/settings.png b/doc/gitlab-basics/img/settings.png deleted file mode 100644 index 78637013d9b..00000000000 Binary files a/doc/gitlab-basics/img/settings.png and /dev/null differ diff --git a/doc/gitlab-basics/img/title_description_mr.png b/doc/gitlab-basics/img/title_description_mr.png deleted file mode 100644 index c31d61ec336..00000000000 Binary files a/doc/gitlab-basics/img/title_description_mr.png and /dev/null differ diff --git a/doc/gitlab-basics/img/white_space.png b/doc/gitlab-basics/img/white_space.png deleted file mode 100644 index eaa969bdcf4..00000000000 Binary files a/doc/gitlab-basics/img/white_space.png and /dev/null differ -- cgit v1.2.1 From c64721f16331f2115d3fe87f3c04134f8cba0163 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 5 Oct 2016 17:40:13 +0100 Subject: Document the new CI_DEBUG_TRACE variable [ci skip] --- doc/ci/pipelines.md | 2 ++ doc/ci/variables/README.md | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index ca9b986a060..729c1dc8c0d 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -31,6 +31,8 @@ project. ## Seeing build status Clicking on a pipeline will show the builds that were run for that pipeline. +Clicking on an individual build will show you its build trace, and allow you to +cancel the build, retry it, or erase the build trace. ## Badges diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 22d67bd9964..a4c3a731a20 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -48,6 +48,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | @@ -105,6 +106,39 @@ Variables can be defined at a global level, but also at a job level. More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). +#### Debug tracing + +> **WARNING:** Enabling debug tracing can have severe security implications. The + output **will** contain the content of all your secure variables and any other + secrets! The output **will** be uploaded to the GitLab server and made visible + in build traces! + +By default, GitLab Runner hides most of the details of what it is doing when +processing a job. This behaviour keeps build traces short, and prevents secrets +from being leaked into the trace unless your script writes them to the screen. + +If a job isn't working as expected, this can make the problem difficult to +investigate; in these cases, you can enable debug tracing in `.gitlab-ci.yml`. +Available on GitLab Runner v1.7+, this feature enables the shell's execution +trace, resulting in a verbose build trace listing all commands that were run, +variables that were set, etc. + +Before enabling this, you should ensure builds are visible to +[team members only](../../../user/permissions.md#project-features). You should +also [erase](../pipelines.md#seeing-build-traces) all generated build traces +before making them visible again. + +To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: + +```yaml +job1: + variables: + CI_DEBUG_TRACE: "true" +``` + +The [example project](https://gitlab.com/gitlab-examples/ci-debug-trace) +demonstrates a working configuration, including build trace examples. + ### User-defined variables (Secure Variables) **This feature requires GitLab Runner 0.4.0 or higher** -- cgit v1.2.1 From 7d12683de51721e75b314b29272f4f024f7f0655 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 11 Oct 2016 11:03:11 +0100 Subject: Fixes broken tests --- spec/features/environments_spec.rb | 47 ++++++++++++++++++++-- .../merge_when_build_succeeds_spec.rb | 9 +++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 99246589eae..a8244ca89c6 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -46,6 +46,14 @@ feature 'Environments', feature: true do scenario 'does show environment name' do expect(page).to have_link(environment.name) end + + scenario 'does show number of opened environments in Availabe tab' do + expect(page.find('.js-avaibale-environments-count').text).to eq('1') + end + + scenario 'does show number of closed environments in Stopped tab' do + expect(page.find('.js-stopped-environments-count').text).to eq('0') + end context 'without deployments' do scenario 'does show no deployments' do @@ -76,6 +84,16 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end + + scenario 'does show close button' do + # TODO: Add test to verify if close button is visible + # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + end + + scenario 'does allow to close environment' do + # TODO: Add test to verify if close environment works + # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + end end end end @@ -137,6 +155,16 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end + + scenario 'does show close button' do + # TODO: Add test to verify if close button is visible + # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + end + + scenario 'does allow to close environment' do + # TODO: Add test to verify if close environment works + # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + end end end end @@ -194,9 +222,22 @@ feature 'Environments', feature: true do context 'when logged as master' do given(:role) { :master } - scenario 'does close environment' do - click_link 'Close' - expect(page).not_to have_link(environment.name) + scenario 'does not have a Close link' do + expect(page).not_to have_link('Close') + end + + context 'when environment is opened and can be closed' do + let(:project) { create(:project) } + let(:environment) { create(:environment, project: project) } + + let!(:deployment) do + create(:deployment, environment: environment, sha: project.commit('master').id) + end + + scenario 'does have a Close link' do + # TODO: Add missing validation. In order to have Close link + # this must be true: last_deployment.try(:close_action) + end end end diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 60bc07bd1a0..2c1a45af596 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -79,6 +79,15 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end end + context 'Has Environment' do + let(:environment) { create(:environment, project: project) } + + it 'does show link to close the environment' do + # TODO add test to verify if the button is visible when this condition + # is met: if environment.closeable? + end + end + def visit_merge_request(merge_request) visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) end -- cgit v1.2.1 From f361b1c624443f0e693d8b7c9bd81fb713c943cf Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 14:22:18 +0200 Subject: Refactor the SubGit/SVN documentation [ci skip] --- doc/workflow/importing/migrating_from_svn.md | 92 ++++++++++++++++------------ 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index fc27a38f735..423b095e69e 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -10,7 +10,7 @@ There are two approaches to SVN to Git migration: 1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which: - Makes the GitLab repository to mirror the SVN project. - - Git and SVN repositories are kept in sync; you can use either one. + - Git and SVN repositories are kept in sync; you can use either one. - Smoothens the migration process and allows to manage migration risks. 1. [Cut over migration](#cut-over-migration-with-svn2git) which: @@ -19,82 +19,94 @@ There are two approaches to SVN to Git migration: ## Smooth migration with a Git/SVN mirror using SubGit -#### Prerequisites +[SubGit](https://subgit.com) is a tool for a smooth, stress-free SVN to Git +migration. It creates a writable Git mirror of a local or remote Subversion +repository and that way you can use both Subversion and Git as long as you like. +It requires access to your GitLab server as it talks with the Git repositories +directly in a filesystem level. -Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions follow this -[instruction](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). +### SubGit prerequisites -Download SubGit tool from [https://subgit.com/download/](https://subgit.com/download/) +1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can + follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). +1. Download SubGit from https://subgit.com/download/. +1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit` + command will be available at `/opt/subgit-VERSION/bin/subgit`. -Unpack downloaded SubGit zip archive to `/opt` directory, subgit will be available -at `/opt/subgit-VERSION/bin/subgit` +### SubGit configuration -#### Configuration - -In GitLab create new empty repository. In filesystem it will be located at -`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path by default. -For convenice, assign this path to a variable: +The first step to mirror you SVN repository in GitLab is to create a new empty +project which will be used as a mirror. For Omnibus installations the path to +the repository will be located at +`/var/opt/gitlab/git-data/repositories/USER/REPO.git` by default. For +installations from source, the default repository directory will be +`/home/git/repositories/USER/REPO.git`. For convenience, assign this path to a +variable: ``` -GIT_REPOS_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git +GIT_REPO_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git ``` -SubGit will keep this repository will be kept in sync with a remote SVN project. -For convenience, assign remote SVN project URL to a variable: +SubGit will keep this repository in sync with a remote SVN project. For +convenience, assign your remote SVN project URL to a variable: ``` SVN_PROJECT_URL=http://svn.company.com/repos/project ``` -Run SubGit to set up a Git/SVN mirror. Make sure `subgit` command is ran -on behalf of the same user that keeps ownership of GitLab Git repositories (`git` by default): +Next you need to run SubGit to set up a Git/SVN mirror. Make sure the following +`subgit` command is ran on behalf of the same user that keeps ownership of +GitLab Git repositories (by default `git`): ``` -subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPOS_PATH +subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPO_PATH ``` -Adjust authors and branches mappings, if necessary: +Adjust authors and branches mappings, if necessary. Open with your favorite +text editor: ``` -edit $GIT_REPOS_PATH/subgit/authors.txt -edit $GIT_REPOS_PATH/subgit/config +edit $GIT_REPO_PATH/subgit/authors.txt +edit $GIT_REPO_PATH/subgit/config ``` -For more information regarding SubGit configuration options, refer to -[documentation](https://subgit.com/documentation.html) at SubGit web site. +For more information regarding the SubGit configuration options, refer to +[SubGit's documentation](https://subgit.com/documentation.html) website. -#### Initial translation +### Initial translation -Run `subgit` to perform initial translation of existing SVN revisions into -Git repository: +Now that SubGit has configured the Git/SVN repos, run `subgit` to perform the +initial translation of existing SVN revisions into the Git repository: ``` subgit install $GIT_REPOS_PATH ``` -After initial translation is completed, GitLab Git repository and SVN project -will be kept in sync by `subgit` - new Git commits will be translated to SVN -revisions and new SVN revisions will be translated to Git commits. Mirror works -transparently and does not require any special commands. +After the initial translation is completed, the Git repository and the SVN +project will be kept in sync by `subgit` - new Git commits will be translated to +SVN revisions and new SVN revisions will be translated to Git commits. Mirror +works transparently and does not require any special commands. -Would you prefer to perform one-time cut over migration with `subgit` use -`import` command in place of `install`: +If you would prefer to perform one-time cut over migration with `subgit`, use +the `import` command instead of `install`: ``` -subgit import $GIT_REPOS_PATH +subgit import $GIT_REPO_PATH ``` -#### Licensing +### SubGit licensing -Running SubGit in a mirror mode requires [registration](https://subgit.com/pricing.html). Registration is free for Open Source, -Academic and Startup projects. +Running SubGit in a mirror mode requires a +[registration](https://subgit.com/pricing.html). Registration is free for open +source, academic and startup projects. -We're currently working on deeper GitLab/SubGit intergation. You may track our +We're currently working on deeper GitLab/SubGit integration. You may track our progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). -#### Support +### SubGit support -For any questions related to SVN to GitLab migration with SubGit you can contact SubGit team at [support@subgit.com](mailto:support@subgit.com). +For any questions related to SVN to GitLab migration with SubGit, you can +contact the SubGit team directly at [support@subgit.com](mailto:support@subgit.com). ## Cut over migration with svn2git @@ -168,4 +180,4 @@ git push --tags origin ## Contribute to this guide We welcome all contributions that would expand this guide with instructions on -how to migrate from SVN and other version control systems. \ No newline at end of file +how to migrate from SVN and other version control systems. -- cgit v1.2.1 From 1022456bb15d18b05c14fe344950fb75c7c69f48 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 7 Oct 2016 16:49:48 +0100 Subject: Allow browsing branches that end with '.atom' We need to do two things to support this: 1. Simplify the regex capture in the routing for the CommitsController to not exclude the '.atom' suffix. That's a perfectly valid git branch name, so we shouldn't blow up if we get it. 2. Because Rails now can't automatically detect the request format, add some code to do so in `ExtractPath` when there is no path. This means that, given branches 'foo' and 'foo.atom', the Atom feed for the former is unroutable. To fix this: don't do that! Give the branches different names! --- CHANGELOG | 1 + config/routes/project.rb | 2 +- lib/extracts_path.rb | 33 +++++++- .../projects/commits_controller_spec.rb | 41 +++++++--- spec/lib/extracts_path_spec.rb | 87 +++++++++++++++++++++- spec/routing/project_routing_spec.rb | 2 +- 6 files changed, 152 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 392a356c79a..2f2396ad29f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment + - Allow browsing branches that end with '.atom' - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) - Cache rendered markdown in the database, rather than Redis diff --git a/config/routes/project.rb b/config/routes/project.rb index e8807ef06a7..4c1da5c4df5 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -159,7 +159,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: get( '/commits/*id', to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, + constraints: { id: /.+/, format: false }, as: :commits ) end diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index a4558d157c0..e4d996a3fb6 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -52,8 +52,7 @@ module ExtractsPath # Append a trailing slash if we only get a ref and no file path id += '/' unless id.ends_with?('/') - valid_refs = @project.repository.ref_names - valid_refs.select! { |v| id.start_with?("#{v}/") } + valid_refs = ref_names.select { |v| id.start_with?("#{v}/") } if valid_refs.length == 0 # No exact ref match, so just try our best @@ -74,6 +73,19 @@ module ExtractsPath pair end + # If we have an ID of 'foo.atom', and the controller provides Atom and HTML + # formats, then we have to check if the request was for the Atom version of + # the ID without the '.atom' suffix, or the HTML version of the ID including + # the suffix. We only check this if the version including the suffix doesn't + # match, so it is possible to create a branch which has an unroutable Atom + # feed. + def extract_ref_without_atom(id) + id_without_atom = id.sub(/\.atom$/, '') + valid_refs = ref_names.select { |v| "#{id_without_atom}/".start_with?("#{v}/") } + + valid_refs.max_by(&:length) + end + # Assigns common instance variables for views working with Git tree-ish objects # # Assignments are: @@ -86,6 +98,10 @@ module ExtractsPath # If the :id parameter appears to be requesting a specific response format, # that will be handled as well. # + # If there is no path and the ref doesn't exist in the repo, try to resolve + # the ref without an '.atom' suffix. If _that_ ref is found, set the request's + # format to Atom manually. + # # Automatically renders `not_found!` if a valid tree path could not be # resolved (e.g., when a user inserts an invalid path or ref). def assign_ref_vars @@ -103,6 +119,13 @@ module ExtractsPath @commit = @repo.commit(@options[:extended_sha1]) end + if @path.empty? && !@commit + @id = @ref = extract_ref_without_atom(@id) + @commit = @repo.commit(@ref) + + request.format = :atom if @commit + end + raise InvalidPathError unless @commit @hex_path = Digest::SHA1.hexdigest(@path) @@ -125,4 +148,10 @@ module ExtractsPath id += "/" + params[:path] unless params[:path].blank? id end + + def ref_names + return [] unless @project + + @ref_names ||= @project.repository.ref_names + end end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 2518a48e336..1ac7e03a2db 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -10,15 +10,38 @@ describe Projects::CommitsController do end describe "GET show" do - context "as atom feed" do - it "renders as atom" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: "master", - format: "atom") - expect(response).to be_success - expect(response.content_type).to eq('application/atom+xml') + context "when the ref name ends in .atom" do + render_views + + context "when the ref does not exist with the suffix" do + it "renders as atom" do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: "master.atom") + + expect(response).to be_success + expect(response.content_type).to eq('application/atom+xml') + end + end + + context "when the ref exists with the suffix" do + before do + commit = project.repository.commit('master') + + allow_any_instance_of(Repository).to receive(:commit).and_call_original + allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit) + + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: "master.atom") + end + + it "renders as HTML" do + expect(response).to be_success + expect(response.content_type).to eq('text/html') + end end end end diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index e10c1f5c547..0e85e302f29 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -6,6 +6,7 @@ describe ExtractsPath, lib: true do include Gitlab::Routing.url_helpers let(:project) { double('project') } + let(:request) { double('request') } before do @project = project @@ -15,9 +16,10 @@ describe ExtractsPath, lib: true do allow(project).to receive(:repository).and_return(repo) allow(project).to receive(:path_with_namespace). and_return('gitlab/gitlab-ci') + allow(request).to receive(:format=) end - describe '#assign_ref' do + describe '#assign_ref_vars' do let(:ref) { sample_commit[:id] } let(:params) { { path: sample_commit[:line_code_path], ref: ref } } @@ -61,6 +63,75 @@ describe ExtractsPath, lib: true do expect(@id).to eq(get_id) end end + + context 'ref only exists without .atom suffix' do + context 'with a path' do + let(:params) { { ref: 'v1.0.0.atom', path: 'README.md' } } + + it 'renders a 404' do + expect(self).to receive(:render_404) + + assign_ref_vars + end + end + + context 'without a path' do + let(:params) { { ref: 'v1.0.0.atom' } } + before { assign_ref_vars } + + it 'sets the un-suffixed version as @ref' do + expect(@ref).to eq('v1.0.0') + end + + it 'sets the request format to Atom' do + expect(request).to have_received(:format=).with(:atom) + end + end + end + + context 'ref exists with .atom suffix' do + context 'with a path' do + let(:params) { { ref: 'master.atom', path: 'README.md' } } + + before do + repository = @project.repository + allow(repository).to receive(:commit).and_call_original + allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master')) + + assign_ref_vars + end + + it 'sets the suffixed version as @ref' do + expect(@ref).to eq('master.atom') + end + + it 'does not change the request format' do + expect(request).not_to have_received(:format=) + end + end + + context 'without a path' do + let(:params) { { ref: 'master.atom' } } + + before do + repository = @project.repository + allow(repository).to receive(:commit).and_call_original + allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master')) + end + + it 'sets the suffixed version as @ref' do + assign_ref_vars + + expect(@ref).to eq('master.atom') + end + + it 'does not change the request format' do + expect(request).not_to receive(:format=) + + assign_ref_vars + end + end + end end describe '#extract_ref' do @@ -115,4 +186,18 @@ describe ExtractsPath, lib: true do end end end + + describe '#extract_ref_without_atom' do + it 'ignores any matching refs suffixed with atom' do + expect(extract_ref_without_atom('master.atom')).to eq('master') + end + + it 'returns the longest matching ref' do + expect(extract_ref_without_atom('release/app/v1.0.0.atom')).to eq('release/app/v1.0.0') + end + + it 'returns nil if there are no matching refs' do + expect(extract_ref_without_atom('foo.atom')).to eq(nil) + end + end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 77842057a10..2322430d212 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -337,7 +337,7 @@ describe Projects::CommitsController, 'routing' do end it 'to #show' do - expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'atom') + expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') end end -- cgit v1.2.1 From 75fa98bb93a416aaf16ff832b0bcc98abbbdaa19 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 11 Oct 2016 06:30:50 -0700 Subject: Remove duplicate CHANGELOG entry --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2095e4fc0fd..8c07ce32d08 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page - - Bump mail_room to v0.8.1 to fix thread cleanup issue - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Improve issue load time performance by avoiding ORDER BY in find_by call -- cgit v1.2.1 From ce8aedf030f0980b3712a3b0090b918fa1451a2d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 15:06:46 +0200 Subject: Add examples of fake tokens to be used in docs [ci skip] --- doc/development/doc_styleguide.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 39b801f761d..0b725cf200c 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -314,6 +314,29 @@ In this case: - different highlighting languages are used for each config in the code block - the [references](#references) guide is used for reconfigure/restart +## Fake tokens + +There may be times where a token is needed to demonstrate an API call using +cURL or a secret variable used in CI. It is strongly advised not to use real +tokens in documentation even if the probability of a token being exploited is +low. + +You can use the following fake tokens as examples. + +| **Token type** | **Token value** | +| --------------------- | --------------------------------- | +| Private user token | `9koXpg98eAheJpvBs5tK` | +| Personal access token | `n671WNGecHugsdEDPsyo` | +| Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` | +| Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` | +| Secret CI variable | `Li8j-mLUVA3eZYjPfd_H` | +| Specific Runner token | `yrnZW46BrtBFqM7xDzE7dddd` | +| Shared Runner token | `6Vk7ZsosqQyfreAxXTZr` | +| Trigger token | `be20d8dcc028677c931e04f3871a9b` | +| Webhook secret token | `6XhDroRcYPM5by_h-HLY` | +| Health check token | `Tu7BgjR9qeZTEyRzGG2P` | +| Request profile token | `7VgpS4Ax5utVD2esNstz` | + ## API Here is a list of must-have items. Use them in the exact order that appears -- cgit v1.2.1 From 8caf097a162cc43cea59162600bbb1fbc981f0bc Mon Sep 17 00:00:00 2001 From: henrik Date: Tue, 11 Oct 2016 15:41:10 +0200 Subject: Convert unicode emojis to images. --- CHANGELOG | 1 + app/models/merge_request_diff.rb | 14 +++++- doc/ci/pipelines.md | 2 + doc/ci/variables/README.md | 34 ++++++++++++++ lib/banzai/filter/emoji_filter.rb | 54 ++++++++++++++++++---- lib/gitlab/emoji.rb | 7 ++- spec/lib/banzai/filter/emoji_filter_spec.rb | 71 ++++++++++++++++++++++++++++- spec/models/merge_request_diff_spec.rb | 10 ++++ 8 files changed, 181 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 06dc2993b73..2095e4fc0fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - Fix Error 500 when viewing old merge requests with bad diff data - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 36b8b70870b..3f7e96186a1 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 100 + # Valid types of serialized diffs allowed by Gitlab::Git::Diff + VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta] + belongs_to :merge_request state_machine :state, initial: :empty do @@ -170,6 +173,15 @@ class MergeRequestDiff < ActiveRecord::Base private + # Old GitLab implementations may have generated diffs as ["--broken-diff"]. + # Avoid an error 500 by ignoring bad elements. See: + # https://gitlab.com/gitlab-org/gitlab-ce/issues/20776 + def valid_raw_diff?(raw) + return false unless raw.respond_to?(:each) + + raw.any? { |element| VALID_CLASSES.include?(element.class) } + end + def dump_commits(commits) commits.map(&:to_hash) end @@ -200,7 +212,7 @@ class MergeRequestDiff < ActiveRecord::Base end def load_diffs(raw, options) - if raw.respond_to?(:each) + if valid_raw_diff?(raw) if paths = options[:paths] raw = raw.select do |diff| paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index ca9b986a060..729c1dc8c0d 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -31,6 +31,8 @@ project. ## Seeing build status Clicking on a pipeline will show the builds that were run for that pipeline. +Clicking on an individual build will show you its build trace, and allow you to +cancel the build, retry it, or erase the build trace. ## Badges diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 22d67bd9964..a4c3a731a20 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -48,6 +48,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | @@ -105,6 +106,39 @@ Variables can be defined at a global level, but also at a job level. More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). +#### Debug tracing + +> **WARNING:** Enabling debug tracing can have severe security implications. The + output **will** contain the content of all your secure variables and any other + secrets! The output **will** be uploaded to the GitLab server and made visible + in build traces! + +By default, GitLab Runner hides most of the details of what it is doing when +processing a job. This behaviour keeps build traces short, and prevents secrets +from being leaked into the trace unless your script writes them to the screen. + +If a job isn't working as expected, this can make the problem difficult to +investigate; in these cases, you can enable debug tracing in `.gitlab-ci.yml`. +Available on GitLab Runner v1.7+, this feature enables the shell's execution +trace, resulting in a verbose build trace listing all commands that were run, +variables that were set, etc. + +Before enabling this, you should ensure builds are visible to +[team members only](../../../user/permissions.md#project-features). You should +also [erase](../pipelines.md#seeing-build-traces) all generated build traces +before making them visible again. + +To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: + +```yaml +job1: + variables: + CI_DEBUG_TRACE: "true" +``` + +The [example project](https://gitlab.com/gitlab-examples/ci-debug-trace) +demonstrates a working configuration, including build trace examples. + ### User-defined variables (Secure Variables) **This feature requires GitLab Runner 0.4.0 or higher** diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index 2492b5213ac..23ae6dfc79a 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -1,6 +1,6 @@ module Banzai module Filter - # HTML filter that replaces :emoji: with images. + # HTML filter that replaces :emoji: and unicode with images. # # Based on HTML::Pipeline::EmojiFilter # @@ -13,14 +13,14 @@ module Banzai def call search_text_nodes(doc).each do |node| content = node.to_html - next unless content.include?(':') next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) + if content.include?(':') || node.text.match(emoji_unicode_pattern) + html = emoji_name_image_filter(content) + html = emoji_unicode_image_filter(html) + next if html == content + node.replace(html) + end - html = emoji_image_filter(content) - - next if html == content - - node.replace(html) end doc @@ -31,18 +31,34 @@ module Banzai # text - String text to replace :emoji: in. # # Returns a String with :emoji: replaced with images. - def emoji_image_filter(text) + def emoji_name_image_filter(text) text.gsub(emoji_pattern) do |match| name = $1 ":#{name}:" end end + + # Replace unicode emojis with corresponding images if they exist. + # + # text - String text to replace unicode emojis in. + # + # Returns a String with unicode emojis replaced with images. + + def emoji_unicode_image_filter(text) + text.gsub(emoji_unicode_pattern) do |moji| + ":#{Gitlab::Emoji.emojis_by_moji[moji][" + end + end # Build a regexp that matches all valid :emoji: names. def self.emoji_pattern @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ end + # Build a regexp that matches all valid unicode emojis names. + def self.emoji_unicode_pattern + @emoji_unicode_pattern ||= /(#{Gitlab::Emoji.emojis_unicodes.map { |moji| Regexp.escape(moji) }.join('|')})/ + end private def emoji_url(name) @@ -60,6 +76,21 @@ module Banzai end end + def emoji_unicode_url(moji) + emoji_unicode_path = emoji_unicode_filename(moji) + + if context[:asset_host] + # Asset host is specified. + url_to_image(emoji_unicode_path) + elsif context[:asset_root] + # Gitlab url is specified + File.join(context[:asset_root], url_to_image(emoji_unicode_path)) + else + # All other cases + url_to_image(emoji_unicode_path) + end + end + def url_to_image(image) ActionController::Base.helpers.url_to_image(image) end @@ -71,6 +102,13 @@ module Banzai def emoji_filename(name) "#{Gitlab::Emoji.emoji_filename(name)}.png" end + def emoji_unicode_pattern + self.class.emoji_unicode_pattern + end + + def emoji_unicode_filename(name) + "#{Gitlab::Emoji.emoji_unicode_filename(name)}.png" + end end end end diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb index b63213ae208..803b0c2d057 100644 --- a/lib/gitlab/emoji.rb +++ b/lib/gitlab/emoji.rb @@ -9,7 +9,9 @@ module Gitlab def emojis_by_moji Gemojione.index.instance_variable_get(:@emoji_by_moji) end - + def emojis_unicodes + emojis_by_moji.keys.sort + end def emojis_names emojis.keys.sort end @@ -17,5 +19,8 @@ module Gitlab def emoji_filename(name) emojis[name]["unicode"] end + def emoji_unicode_filename(moji) + emojis_by_moji[moji]["unicode"] + end end end diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb index b5b38cf0c8c..475160bb5ec 100644 --- a/spec/lib/banzai/filter/emoji_filter_spec.rb +++ b/spec/lib/banzai/filter/emoji_filter_spec.rb @@ -12,11 +12,14 @@ describe Banzai::Filter::EmojiFilter, lib: true do ActionController::Base.asset_host = @original_asset_host end - it 'replaces supported emoji' do + it 'replaces supported name emoji' do doc = filter('

    :heart:

    ') expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' end - + it 'replaces supported unicode emoji' do + doc = filter('

    ❤️

    ') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' + end it 'ignores unsupported emoji' do exp = act = '

    :foo:

    ' doc = filter(act) @@ -28,46 +31,96 @@ describe Banzai::Filter::EmojiFilter, lib: true do expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' end + it 'correctly encodes unicode to the URL' do + doc = filter('

    👍

    ') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' + end + it 'matches at the start of a string' do doc = filter(':+1:') expect(doc.css('img').size).to eq 1 end + it 'unicode matches at the start of a string' do + doc = filter("'👍'") + expect(doc.css('img').size).to eq 1 + end + it 'matches at the end of a string' do doc = filter('This gets a :-1:') expect(doc.css('img').size).to eq 1 end + it 'unicode matches at the end of a string' do + doc = filter('This gets a 👍') + expect(doc.css('img').size).to eq 1 + end + it 'matches with adjacent text' do doc = filter('+1 (:+1:)') expect(doc.css('img').size).to eq 1 end + it 'unicode matches with adjacent text' do + doc = filter('+1 (👍)') + expect(doc.css('img').size).to eq 1 + end + it 'matches multiple emoji in a row' do doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:') expect(doc.css('img').size).to eq 3 end + it 'unicode matches multiple emoji in a row' do + doc = filter("'🙈🙉🙊'") + expect(doc.css('img').size).to eq 3 + end + + it 'mixed matches multiple emoji in a row' do + doc = filter("'🙈:see_no_evil:🙉:hear_no_evil:🙊:speak_no_evil:'") + expect(doc.css('img').size).to eq 6 + end + it 'has a title attribute' do doc = filter(':-1:') expect(doc.css('img').first.attr('title')).to eq ':-1:' end + it 'unicode has a title attribute' do + doc = filter("'👎'") + expect(doc.css('img').first.attr('title')).to eq ':thumbsdown:' + end + it 'has an alt attribute' do doc = filter(':-1:') expect(doc.css('img').first.attr('alt')).to eq ':-1:' end + it 'unicode has an alt attribute' do + doc = filter("'👎'") + expect(doc.css('img').first.attr('alt')).to eq ':thumbsdown:' + end + it 'has an align attribute' do doc = filter(':8ball:') expect(doc.css('img').first.attr('align')).to eq 'absmiddle' end + it 'unicode has an align attribute' do + doc = filter("'🎱'") + expect(doc.css('img').first.attr('align')).to eq 'absmiddle' + end + it 'has an emoji class' do doc = filter(':cat:') expect(doc.css('img').first.attr('class')).to eq 'emoji' end + it 'unicode has an emoji class' do + doc = filter("'🐱'") + expect(doc.css('img').first.attr('class')).to eq 'emoji' + end + it 'has height and width attributes' do doc = filter(':dog:') img = doc.css('img').first @@ -76,12 +129,26 @@ describe Banzai::Filter::EmojiFilter, lib: true do expect(img.attr('height')).to eq '20' end + it 'unicode has height and width attributes' do + doc = filter("'🐶'") + img = doc.css('img').first + + expect(img.attr('width')).to eq '20' + expect(img.attr('height')).to eq '20' + end + it 'keeps whitespace intact' do doc = filter('This deserves a :+1:, big time.') expect(doc.to_html).to match(/^This deserves a , big time\.\z/) end + it 'unicode keeps whitespace intact' do + doc = filter('This deserves a 🎱, big time.') + + expect(doc.to_html).to match(/^This deserves a , big time\.\z/) + end + it 'uses a custom asset_root context' do root = Gitlab.config.gitlab.url + 'gitlab/root' diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..96f1f60dbc0 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -44,6 +44,16 @@ describe MergeRequestDiff, models: true do end end + context 'when the raw diffs have invalid content' do + before { mr_diff.update_attributes(st_diffs: ["--broken-diff"]) } + + it 'returns an empty DiffCollection' do + expect(mr_diff.raw_diffs.to_a).to be_empty + expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.raw_diffs).to be_empty + end + end + context 'when the raw diffs exist' do it 'returns the diffs' do expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) -- cgit v1.2.1 From b4004488f76d7360acd2f38277d617447c76b888 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 4 Oct 2016 15:52:08 +0300 Subject: Make guests unable to view MRs --- CHANGELOG | 1 + app/models/event.rb | 8 ++- app/policies/project_policy.rb | 3 +- app/services/notification_service.rb | 6 ++- app/services/todo_service.rb | 6 +-- doc/user/permissions.md | 1 + .../projects/guest_navigation_menu_spec.rb | 28 +++++++++++ .../security/project/private_access_spec.rb | 2 +- spec/models/event_spec.rb | 11 +++++ spec/policies/project_policy_spec.rb | 5 +- .../services/merge_requests/update_service_spec.rb | 6 +++ spec/services/notification_service_spec.rb | 57 +++++++++++++++++++++- spec/services/todo_service_spec.rb | 22 ++++++++- 13 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 spec/features/projects/guest_navigation_menu_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 2095e4fc0fd..9653c4da17f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -93,6 +93,7 @@ v 8.13.0 (unreleased) - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found + - Make guests unable to view MRs on private projects v 8.12.5 (unreleased) - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread diff --git a/app/models/event.rb b/app/models/event.rb index 314d5ba438f..0764cb8cabd 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -68,8 +68,10 @@ class Event < ActiveRecord::Base true elsif issue? || issue_note? Ability.allowed?(user, :read_issue, note? ? note_target : target) + elsif merge_request? || merge_request_note? + Ability.allowed?(user, :read_merge_request, note? ? note_target : target) else - ((merge_request? || note?) && target.present?) || milestone? + milestone? end end @@ -280,6 +282,10 @@ class Event < ActiveRecord::Base note? && target && target.for_issue? end + def merge_request_note? + note? && target && target.for_merge_request? + end + def project_snippet_note? target.for_snippet? end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index a806cf83782..be4721d7a51 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -40,7 +40,6 @@ class ProjectPolicy < BasePolicy can! :read_milestone can! :read_project_snippet can! :read_project_member - can! :read_merge_request can! :read_note can! :create_project can! :create_issue @@ -63,6 +62,7 @@ class ProjectPolicy < BasePolicy can! :read_pipeline can! :read_environment can! :read_deployment + can! :read_merge_request end # Permissions given when an user is team member of a project @@ -117,6 +117,7 @@ class ProjectPolicy < BasePolicy can! :read_container_image can! :build_download_code can! :build_read_container_image + can! :read_merge_request end def owner_access! diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index de8049b8e2e..72712afc07e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -475,10 +475,12 @@ class NotificationService end def reject_users_without_access(recipients, target) - return recipients unless target.is_a?(Issue) + return recipients unless target.is_a?(Issuable) + + ability = :"read_#{target.to_ability_name}" recipients.select do |user| - user.can?(:read_issue, target) + user.can?(ability, target) end end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 776530ac0a5..f8e6b2ef094 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -273,12 +273,12 @@ class TodoService end def reject_users_without_access(users, project, target) - if target.is_a?(Note) && target.for_issue? + if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?) target = target.noteable end - if target.is_a?(Issue) - select_users(users, :read_issue, target) + if target.is_a?(Issuable) + select_users(users, :"read_#{target.to_ability_name}", target) else select_users(users, :read_project, project) end diff --git a/doc/user/permissions.md b/doc/user/permissions.md index c0dc80325b6..d6216a8dd50 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -32,6 +32,7 @@ The following table depicts the various user permission levels in a project. | See a commit status | | ✓ | ✓ | ✓ | ✓ | | See a container registry | | ✓ | ✓ | ✓ | ✓ | | See environments | | ✓ | ✓ | ✓ | ✓ | +| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | | Manage/Accept merge requests | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb new file mode 100644 index 00000000000..c22441f8929 --- /dev/null +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe "Guest navigation menu" do + let(:project) { create :empty_project, :private } + let(:guest) { create :user } + + before do + project.team << [guest, :guest] + + login_as(guest) + end + + it "shows allowed tabs only" do + visit namespace_project_path(project.namespace, project) + + within(".nav-links") do + expect(page).to have_content 'Project' + expect(page).to have_content 'Activity' + expect(page).to have_content 'Issues' + expect(page).to have_content 'Wiki' + + expect(page).not_to have_content 'Repository' + expect(page).not_to have_content 'Pipelines' + expect(page).not_to have_content 'Graphs' + expect(page).not_to have_content 'Merge Requests' + end + end +end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index ccb5c06dab0..79417c769a8 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -203,7 +203,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for developer } it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index af5002487cc..06cac929bbf 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -135,6 +135,17 @@ describe Event, models: true do it { expect(event.visible_to_user?(member)).to eq true } it { expect(event.visible_to_user?(guest)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true } + + context 'private project' do + let(:project) { create(:project, :private) } + + it { expect(event.visible_to_user?(non_member)).to eq false } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(guest)).to eq false } + it { expect(event.visible_to_user?(admin)).to eq true } + end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 43c8d884a47..658e3c13a73 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -12,7 +12,7 @@ describe ProjectPolicy, models: true do [ :read_project, :read_board, :read_list, :read_wiki, :read_issue, :read_label, :read_milestone, :read_project_snippet, :read_project_member, - :read_merge_request, :read_note, :create_project, :create_issue, :create_note, + :read_note, :create_project, :create_issue, :create_note, :upload_file ] end @@ -21,7 +21,8 @@ describe ProjectPolicy, models: true do [ :download_code, :fork_project, :create_project_snippet, :update_issue, :admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build, - :read_container_image, :read_pipeline, :read_environment, :read_deployment + :read_container_image, :read_pipeline, :read_environment, :read_deployment, + :read_merge_request ] end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 33db34c0f62..fd5f94047c2 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -17,6 +17,7 @@ describe MergeRequests::UpdateService, services: true do before do project.team << [user, :master] project.team << [user2, :developer] + project.team << [user3, :developer] end describe 'execute' do @@ -188,6 +189,11 @@ describe MergeRequests::UpdateService, services: true do let!(:non_subscriber) { create(:user) } let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } } + before do + project.team << [non_subscriber, :developer] + project.team << [subscriber, :developer] + end + it 'sends notifications for subscribers of newly added labels' do opts = { label_ids: [label.id] } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index d820646ebdf..699b9925b4e 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -331,7 +331,7 @@ describe NotificationService, services: true do describe '#new_note' do it "records sent notifications" do # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note - expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(4).times.and_call_original + expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(3).times.and_call_original notification.new_note(note) @@ -1169,6 +1169,61 @@ describe NotificationService, services: true do end end + context 'guest user in private project' do + let(:private_project) { create(:empty_project, :private) } + let(:guest) { create(:user) } + let(:developer) { create(:user) } + let(:assignee) { create(:user) } + let(:merge_request) { create(:merge_request, source_project: private_project, assignee: assignee) } + let(:merge_request1) { create(:merge_request, source_project: private_project, assignee: assignee, description: "cc @#{guest.username}") } + let(:note) { create(:note, noteable: merge_request, project: private_project) } + + before do + private_project.team << [assignee, :developer] + private_project.team << [developer, :developer] + private_project.team << [guest, :guest] + + ActionMailer::Base.deliveries.clear + end + + it 'filters out guests when new note is created' do + expect(SentNotification).to receive(:record).with(merge_request, any_args).exactly(1).times + + notification.new_note(note) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when new merge request is created' do + notification.new_merge_request(merge_request1, @u_disabled) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is closed' do + notification.close_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is reopened' do + notification.reopen_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is merged' do + notification.merge_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + end + def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index b41f6f14fbd..ed55791d24e 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -345,7 +345,7 @@ describe TodoService, services: true do service.new_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -357,7 +357,7 @@ describe TodoService, services: true do service.update_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -381,6 +381,7 @@ describe TodoService, services: true do should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) end it 'does not raise an error when description not change' do @@ -430,6 +431,11 @@ describe TodoService, services: true do should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED) end + + it 'does not create a todo for guests' do + service.reassigned_merge_request(mr_assigned, author) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end describe '#merge_merge_request' do @@ -441,6 +447,11 @@ describe TodoService, services: true do expect(first_todo.reload).to be_done expect(second_todo.reload).to be_done end + + it 'does not create todo for guests' do + service.merge_merge_request(mr_assigned, john_doe) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end describe '#new_award_emoji' do @@ -495,6 +506,13 @@ describe TodoService, services: true do should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request) end + + it 'does not create todo for guests' do + note_on_merge_request = create :note_on_merge_request, project: project, noteable: mr_assigned, note: mentions + service.new_note(note_on_merge_request, author) + + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end end -- cgit v1.2.1 From 1bbe1cea919d174b9bc1bdd39e6235ffadcccb50 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 15:55:32 +0200 Subject: Move health check docs under user/admin_area/monitoring [ci skip] --- doc/README.md | 2 +- doc/administration/monitoring/health_check.md | 66 --------------------- .../monitoring/img/health_check_token.png | Bin 6630 -> 0 bytes doc/monitoring/health_check.md | 2 +- doc/user/admin_area/monitoring/health_check.md | 66 +++++++++++++++++++++ .../monitoring/img/health_check_token.png | Bin 0 -> 6630 bytes 6 files changed, 68 insertions(+), 68 deletions(-) delete mode 100644 doc/administration/monitoring/health_check.md delete mode 100644 doc/administration/monitoring/img/health_check_token.png create mode 100644 doc/user/admin_area/monitoring/health_check.md create mode 100644 doc/user/admin_area/monitoring/img/health_check_token.png diff --git a/doc/README.md b/doc/README.md index 04f713a6c5f..effd92b43df 100644 --- a/doc/README.md +++ b/doc/README.md @@ -48,7 +48,7 @@ - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. - [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. -- [Monitoring uptime](administration/monitoring/health_check.md) Check the server status using the health check endpoint. +- [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint. - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. diff --git a/doc/administration/monitoring/health_check.md b/doc/administration/monitoring/health_check.md deleted file mode 100644 index eac57bc3de4..00000000000 --- a/doc/administration/monitoring/health_check.md +++ /dev/null @@ -1,66 +0,0 @@ -# Health Check - -> [Introduced][ce-3888] in GitLab 8.8. - -GitLab provides a health check endpoint for uptime monitoring on the `health_check` web -endpoint. The health check reports on the overall system status based on the status of -the database connection, the state of the database migrations, and the ability to write -and access the cache. This endpoint can be provided to uptime monitoring services like -[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. - -## Access Token - -An access token needs to be provided while accessing the health check endpoint. The current -accepted token can be found on the `admin/health_check` page of your GitLab instance. - -![access token](img/health_check_token.png) - -The access token can be passed as a URL parameter: - -``` -https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN -``` - -or as an HTTP header: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -## Using the Endpoint - -Once you have the access token, health information can be retrieved as plain text, JSON, -or XML using the `health_check` endpoint: - -- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` - -You can also ask for the status of specific services: - -- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` - -For example, the JSON output of the following health check: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -would be like: - -``` -{"healthy":true,"message":"success"} -``` - -## Status - -On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint -will return a valid successful HTTP status code, and a `success` message. Ideally your -uptime monitoring should look for the success message. - -[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 -[pingdom]: https://www.pingdom.com -[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html -[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/administration/monitoring/img/health_check_token.png b/doc/administration/monitoring/img/health_check_token.png deleted file mode 100644 index 2d7c82a65a8..00000000000 Binary files a/doc/administration/monitoring/img/health_check_token.png and /dev/null differ diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md index 23ae1b17258..6cf93c33ec2 100644 --- a/doc/monitoring/health_check.md +++ b/doc/monitoring/health_check.md @@ -1 +1 @@ -This document was moved to [administration/monitoring/health_check](../administration/monitoring/health_check.md). +This document was moved to [user/admin_area/monitoring/health_check](../user/admin_area/monitoring/health_check.md). diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md new file mode 100644 index 00000000000..eac57bc3de4 --- /dev/null +++ b/doc/user/admin_area/monitoring/health_check.md @@ -0,0 +1,66 @@ +# Health Check + +> [Introduced][ce-3888] in GitLab 8.8. + +GitLab provides a health check endpoint for uptime monitoring on the `health_check` web +endpoint. The health check reports on the overall system status based on the status of +the database connection, the state of the database migrations, and the ability to write +and access the cache. This endpoint can be provided to uptime monitoring services like +[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. + +## Access Token + +An access token needs to be provided while accessing the health check endpoint. The current +accepted token can be found on the `admin/health_check` page of your GitLab instance. + +![access token](img/health_check_token.png) + +The access token can be passed as a URL parameter: + +``` +https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN +``` + +or as an HTTP header: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +## Using the Endpoint + +Once you have the access token, health information can be retrieved as plain text, JSON, +or XML using the `health_check` endpoint: + +- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` + +You can also ask for the status of specific services: + +- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` + +For example, the JSON output of the following health check: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +would be like: + +``` +{"healthy":true,"message":"success"} +``` + +## Status + +On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint +will return a valid successful HTTP status code, and a `success` message. Ideally your +uptime monitoring should look for the success message. + +[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 +[pingdom]: https://www.pingdom.com +[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html +[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/user/admin_area/monitoring/img/health_check_token.png b/doc/user/admin_area/monitoring/img/health_check_token.png new file mode 100644 index 00000000000..2d7c82a65a8 Binary files /dev/null and b/doc/user/admin_area/monitoring/img/health_check_token.png differ -- cgit v1.2.1 From dd11c8f5c6984aed434c73197b9b592eee2646e1 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 11 Oct 2016 09:10:12 -0500 Subject: Fix overflow to show grouped builds arrow --- app/assets/stylesheets/pages/pipelines.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 05f59279637..28e850767b7 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -421,7 +421,6 @@ right: -197px; top: -9px; max-height: 245px; - overflow-y: scroll; a { color: $gl-text-color; -- cgit v1.2.1 From d8f33c0a51d9106ece6cd4bae469e40734e05f85 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 12:44:09 +0200 Subject: Move operations/ to new location [ci skip] --- doc/README.md | 2 +- doc/administration/operations.md | 6 + .../operations/cleaning_up_redis_sessions.md | 52 ++++++ .../operations/moving_repositories.md | 180 ++++++++++++++++++++ .../operations/sidekiq_memory_killer.md | 40 +++++ doc/administration/operations/unicorn.md | 86 ++++++++++ doc/operations/README.md | 6 +- doc/operations/cleaning_up_redis_sessions.md | 53 +----- doc/operations/moving_repositories.md | 181 +-------------------- doc/operations/sidekiq_memory_killer.md | 41 +---- doc/operations/unicorn.md | 87 +--------- 11 files changed, 370 insertions(+), 364 deletions(-) create mode 100644 doc/administration/operations.md create mode 100644 doc/administration/operations/cleaning_up_redis_sessions.md create mode 100644 doc/administration/operations/moving_repositories.md create mode 100644 doc/administration/operations/sidekiq_memory_killer.md create mode 100644 doc/administration/operations/unicorn.md diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..ed2d09bedec 100644 --- a/doc/README.md +++ b/doc/README.md @@ -34,7 +34,7 @@ - [Libravatar](customization/libravatar.md) Use Libravatar instead of Gravatar for user avatars. - [Log system](administration/logs.md) Log system. - [Environment Variables](administration/environment_variables.md) to configure GitLab. -- [Operations](operations/README.md) Keeping GitLab up and running. +- [Operations](administration/operations.md) Keeping GitLab up and running. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. - [Repository checks](administration/repository_checks.md) Periodic Git repository checks. - [Repository storages](administration/repository_storages.md) Manage the paths used to store repositories. diff --git a/doc/administration/operations.md b/doc/administration/operations.md new file mode 100644 index 00000000000..4b582d16b64 --- /dev/null +++ b/doc/administration/operations.md @@ -0,0 +1,6 @@ +# GitLab operations + +- [Sidekiq MemoryKiller](operations/sidekiq_memory_killer.md) +- [Cleaning up Redis sessions](operations/cleaning_up_redis_sessions.md) +- [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md) +- [Moving repositories to a new location](operations/moving_repositories.md) diff --git a/doc/administration/operations/cleaning_up_redis_sessions.md b/doc/administration/operations/cleaning_up_redis_sessions.md new file mode 100644 index 00000000000..93521e976d5 --- /dev/null +++ b/doc/administration/operations/cleaning_up_redis_sessions.md @@ -0,0 +1,52 @@ +# Cleaning up stale Redis sessions + +Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. +Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If +you have been running a large GitLab server (thousands of users) since before +GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis +database after you upgrade to GitLab 7.3. You can also perform a cleanup while +still running GitLab 7.2 or older, but in that case new stale sessions will +start building up again after you clean up. + +In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte +hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with +GitLab 7.3.0, the keys are +prefixed with 'session:gitlab:', so they would look like +'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to +remove the keys in the old format. + +First we define a shell function with the proper Redis connection details. + +``` +rcli() { + # This example works for Omnibus installations of GitLab 7.3 or newer. For an + # installation from source you will have to change the socket path and the + # path to redis-cli. + sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" +} + +# test the new shell function; the response should be PONG +rcli ping +``` + +Now we do a search to see if there are any session keys in the old format for +us to clean up. + +``` +# returns the number of old-format session keys in Redis +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l +``` + +If the number is larger than zero, you can proceed to expire the keys from +Redis. If the number is zero there is nothing to clean up. + +``` +# Tell Redis to expire each matched key after 600 seconds. +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli +# This will print '(integer) 1' for each key that gets expired. +``` + +Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis +background save interval) your Redis database will be compacted. If you are +still using GitLab 7.2, users who are not clicking around in GitLab during the +10 minute expiry window will be signed out of GitLab. diff --git a/doc/administration/operations/moving_repositories.md b/doc/administration/operations/moving_repositories.md new file mode 100644 index 00000000000..54adb99386a --- /dev/null +++ b/doc/administration/operations/moving_repositories.md @@ -0,0 +1,180 @@ +# Moving repositories managed by GitLab + +Sometimes you need to move all repositories managed by GitLab to +another filesystem or another server. In this document we will look +at some of the ways you can copy all your repositories from +`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`. + +We will look at three scenarios: the target directory is empty, the +target directory contains an outdated copy of the repositories, and +how to deal with thousands of repositories. + +**Each of the approaches we list can/will overwrite data in the +target directory `/mnt/gitlab/repositories`. Do not mix up the +source and the target.** + +## Target directory is empty: use a tar pipe + +If the target directory `/mnt/gitlab/repositories` is empty the +simplest thing to do is to use a tar pipe. This method has low +overhead and tar is almost always already installed on your system. +However, it is not possible to resume an interrupted tar pipe: if +that happens then all data must be copied again. + +``` +# As the git user +tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + tar -C /mnt/gitlab/repositories -xf - +``` + +If you want to see progress, replace `-xf` with `-xvf`. + +### Tar pipe to another server + +You can also use a tar pipe to copy data to another server. If your +'git' user has SSH access to the newserver as 'git@newserver', you +can pipe the data through SSH. + +``` +# As the git user +tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + ssh git@newserver tar -C /mnt/gitlab/repositories -xf - +``` + +If you want to compress the data before it goes over the network +(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`. + +## The target directory contains an outdated copy of the repositories: use rsync + +If the target directory already contains a partial / outdated copy +of the repositories it may be wasteful to copy all the data again +with tar. In this scenario it is better to use rsync. This utility +is either already installed on your system or easily installable +via apt, yum etc. + +``` +# As the 'git' user +rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + /mnt/gitlab/repositories +``` + +The `/.` in the command above is very important, without it you can +easily get the wrong directory structure in the target directory. +If you want to see progress, replace `-a` with `-av`. + +### Single rsync to another server + +If the 'git' user on your source system has SSH access to the target +server you can send the repositories over the network with rsync. + +``` +# As the 'git' user +rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + git@newserver:/mnt/gitlab/repositories +``` + +## Thousands of Git repositories: use one rsync per repository + +Every time you start an rsync job it has to inspect all files in +the source directory, all files in the target directory, and then +decide what files to copy or not. If the source or target directory +has many contents this startup phase of rsync can become a burden +for your GitLab server. In cases like this you can make rsync's +life easier by dividing its work in smaller pieces, and sync one +repository at a time. + +In addition to rsync we will use [GNU +Parallel](http://www.gnu.org/software/parallel/). This utility is +not included in GitLab so you need to install it yourself with apt +or yum. Also note that the GitLab scripts we used below were added +in GitLab 8.1. + +** This process does not clean up repositories at the target location that no +longer exist at the source. ** If you start using your GitLab instance with +`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos` +after switching to the new repository storage directory. + +### Parallel rsync for all repositories known to GitLab + +This will sync repositories with 10 rsync processes at a time. We keep +track of progress so that the transfer can be restarted if necessary. + +First we create a new directory, owned by 'git', to hold transfer +logs. We assume the directory is empty before we start the transfer +procedure, and that we are the only ones writing files in it. + +``` +# Omnibus +sudo mkdir /var/opt/gitlab/transfer-logs +sudo chown git:git /var/opt/gitlab/transfer-logs + +# Source +sudo -u git -H mkdir /home/git/transfer-logs +``` + +We seed the process with a list of the directories we want to copy. + +``` +# Omnibus +sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt' + +# Source +cd /home/git/gitlab +sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt' +``` + +Now we can start the transfer. The command below is idempotent, and +the number of jobs done by GNU Parallel should converge to zero. If it +does not some repositories listed in all-repos-1234.txt may have been +deleted/renamed before they could be copied. + +``` +# Omnibus +sudo -u git sh -c ' +cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\ + /usr/bin/env JOBS=10 \ + /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ + /var/opt/gitlab/transfer-logs/success-$(date +%s).log \ + /var/opt/gitlab/git-data/repositories \ + /mnt/gitlab/repositories +' + +# Source +cd /home/git/gitlab +sudo -u git -H sh -c ' +cat /home/git/transfer-logs/* | sort | uniq -u |\ + /usr/bin/env JOBS=10 \ + bin/parallel-rsync-repos \ + /home/git/transfer-logs/success-$(date +%s).log \ + /home/git/repositories \ + /mnt/gitlab/repositories +` +``` + +### Parallel rsync only for repositories with recent activity + +Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. +Then you might only want to sync repositories that were changed via GitLab +_after_ that time. You can use the 'SINCE' variable to tell 'rake +gitlab:list_repos' to only print repositories with recent activity. + +``` +# Omnibus +sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ + sudo -u git \ + /usr/bin/env JOBS=10 \ + /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ + success-$(date +%s).log \ + /var/opt/gitlab/git-data/repositories \ + /mnt/gitlab/repositories + +# Source +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ + sudo -u git -H \ + /usr/bin/env JOBS=10 \ + bin/parallel-rsync-repos \ + success-$(date +%s).log \ + /home/git/repositories \ + /mnt/gitlab/repositories +``` diff --git a/doc/administration/operations/sidekiq_memory_killer.md b/doc/administration/operations/sidekiq_memory_killer.md new file mode 100644 index 00000000000..b5e78348989 --- /dev/null +++ b/doc/administration/operations/sidekiq_memory_killer.md @@ -0,0 +1,40 @@ +# Sidekiq MemoryKiller + +The GitLab Rails application code suffers from memory leaks. For web requests +this problem is made manageable using +[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which +restarts Unicorn worker processes in between requests when needed. The Sidekiq +MemoryKiller applies the same approach to the Sidekiq processes used by GitLab +to process background jobs. + +Unlike unicorn-worker-killer, which is enabled by default for all GitLab +installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default +_only_ for Omnibus packages. The reason for this is that the MemoryKiller +relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab +installations from source do not all use Runit or an equivalent. + +With the default settings, the MemoryKiller will cause a Sidekiq restart no +more often than once every 15 minutes, with the restart causing about one +minute of delay for incoming background jobs. + +## Configuring the MemoryKiller + +The MemoryKiller is controlled using environment variables. + +- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is + greater than 0, then after each Sidekiq job, the MemoryKiller will check the + RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq + process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a + delayed shutdown is triggered. The default value for Omnibus packages is set + [in the omnibus-gitlab + repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). +- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When + a shutdown is triggered, the Sidekiq process will keep working normally for + another 15 minutes. +- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace + time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs. + Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells + Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must + restart Sidekiq. +- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of + the final signal sent to the Sidekiq process when we want it to shut down. diff --git a/doc/administration/operations/unicorn.md b/doc/administration/operations/unicorn.md new file mode 100644 index 00000000000..bad61151bda --- /dev/null +++ b/doc/administration/operations/unicorn.md @@ -0,0 +1,86 @@ +# Understanding Unicorn and unicorn-worker-killer + +## Unicorn + +GitLab uses [Unicorn](http://unicorn.bogomips.org/), a pre-forking Ruby web +server, to handle web requests (web browsers and Git HTTP clients). Unicorn is +a daemon written in Ruby and C that can load and run a Ruby on Rails +application; in our case the Rails application is GitLab Community Edition or +GitLab Enterprise Edition. + +Unicorn has a multi-process architecture to make better use of available CPU +cores (processes can run on different cores) and to have stronger fault +tolerance (most failures stay isolated in only one process and cannot take down +GitLab entirely). On startup, the Unicorn 'master' process loads a clean Ruby +environment with the GitLab application code, and then spawns 'workers' which +inherit this clean initial environment. The 'master' never handles any +requests, that is left to the workers. The operating system network stack +queues incoming requests and distributes them among the workers. + +In a perfect world, the master would spawn its pool of workers once, and then +the workers handle incoming web requests one after another until the end of +time. In reality, worker processes can crash or time out: if the master notices +that a worker takes too long to handle a request it will terminate the worker +process with SIGKILL ('kill -9'). No matter how the worker process ended, the +master process will replace it with a new 'clean' process again. Unicorn is +designed to be able to replace 'crashed' workers without dropping user +requests. + +This is what a Unicorn worker timeout looks like in `unicorn_stderr.log`. The +master process has PID 56227 below. + +``` +[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing +[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped # worker=10 +[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538 +[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready +``` + +### Tunables + +The main tunables for Unicorn are the number of worker processes and the +request timeout after which the Unicorn master terminates a worker process. +See the [omnibus-gitlab Unicorn settings +documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md) +if you want to adjust these settings. + +## unicorn-worker-killer + +GitLab has memory leaks. These memory leaks manifest themselves in long-running +processes, such as Unicorn workers. (The Unicorn master process is not known to +leak memory, probably because it does not handle user requests.) + +To make these memory leaks manageable, GitLab comes with the +[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This +gem [monkey-patches](https://en.wikipedia.org/wiki/Monkey_patch) the Unicorn +workers to do a memory self-check after every 16 requests. If the memory of the +Unicorn worker exceeds a pre-set limit then the worker process exits. The +Unicorn master then automatically replaces the worker process. + +This is a robust way to handle memory leaks: Unicorn is designed to handle +workers that 'crash' so no user requests will be dropped. The +unicorn-worker-killer gem is designed to only terminate a worker process _in +between requests_, so no user requests are affected. + +This is what a Unicorn worker memory restart looks like in unicorn_stderr.log. +You see that worker 4 (PID 125918) is inspecting itself and decides to exit. +The threshold memory value was 254802235 bytes, about 250MB. With GitLab this +threshold is a random value between 200 and 250 MB. The master process (PID +117565) then reaps the worker process and spawns a new 'worker 4' with PID +127549. + +``` +[2015-06-05T12:07:41.828374 #125918] WARN -- : #: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes) +[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1) +[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped # worker=4 +[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549 +[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready +``` + +One other thing that stands out in the log snippet above, taken from +GitLab.com, is that 'worker 4' was serving requests for only 23 seconds. This +is a normal value for our current GitLab.com setup and traffic. + +The high frequency of Unicorn memory restarts on some GitLab sites can be a +source of confusion for administrators. Usually they are a [red +herring](https://en.wikipedia.org/wiki/Red_herring). diff --git a/doc/operations/README.md b/doc/operations/README.md index 6a35dab7b6c..58f16aff7bd 100644 --- a/doc/operations/README.md +++ b/doc/operations/README.md @@ -1,5 +1 @@ -# GitLab operations - -- [Sidekiq MemoryKiller](sidekiq_memory_killer.md) -- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md) -- [Understanding Unicorn and unicorn-worker-killer](unicorn.md) +This document was moved to [administration/operations](../administration/operations.md). diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md index 93521e976d5..2a1d0a8c8eb 100644 --- a/doc/operations/cleaning_up_redis_sessions.md +++ b/doc/operations/cleaning_up_redis_sessions.md @@ -1,52 +1 @@ -# Cleaning up stale Redis sessions - -Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. -Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If -you have been running a large GitLab server (thousands of users) since before -GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis -database after you upgrade to GitLab 7.3. You can also perform a cleanup while -still running GitLab 7.2 or older, but in that case new stale sessions will -start building up again after you clean up. - -In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte -hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with -GitLab 7.3.0, the keys are -prefixed with 'session:gitlab:', so they would look like -'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to -remove the keys in the old format. - -First we define a shell function with the proper Redis connection details. - -``` -rcli() { - # This example works for Omnibus installations of GitLab 7.3 or newer. For an - # installation from source you will have to change the socket path and the - # path to redis-cli. - sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" -} - -# test the new shell function; the response should be PONG -rcli ping -``` - -Now we do a search to see if there are any session keys in the old format for -us to clean up. - -``` -# returns the number of old-format session keys in Redis -rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l -``` - -If the number is larger than zero, you can proceed to expire the keys from -Redis. If the number is zero there is nothing to clean up. - -``` -# Tell Redis to expire each matched key after 600 seconds. -rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli -# This will print '(integer) 1' for each key that gets expired. -``` - -Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis -background save interval) your Redis database will be compacted. If you are -still using GitLab 7.2, users who are not clicking around in GitLab during the -10 minute expiry window will be signed out of GitLab. +This document was moved to [administration/operations/cleaning_up_redis_sessions](../administration/operations/cleaning_up_redis_sessions.md). diff --git a/doc/operations/moving_repositories.md b/doc/operations/moving_repositories.md index 54adb99386a..c54bca324a5 100644 --- a/doc/operations/moving_repositories.md +++ b/doc/operations/moving_repositories.md @@ -1,180 +1 @@ -# Moving repositories managed by GitLab - -Sometimes you need to move all repositories managed by GitLab to -another filesystem or another server. In this document we will look -at some of the ways you can copy all your repositories from -`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`. - -We will look at three scenarios: the target directory is empty, the -target directory contains an outdated copy of the repositories, and -how to deal with thousands of repositories. - -**Each of the approaches we list can/will overwrite data in the -target directory `/mnt/gitlab/repositories`. Do not mix up the -source and the target.** - -## Target directory is empty: use a tar pipe - -If the target directory `/mnt/gitlab/repositories` is empty the -simplest thing to do is to use a tar pipe. This method has low -overhead and tar is almost always already installed on your system. -However, it is not possible to resume an interrupted tar pipe: if -that happens then all data must be copied again. - -``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - tar -C /mnt/gitlab/repositories -xf - -``` - -If you want to see progress, replace `-xf` with `-xvf`. - -### Tar pipe to another server - -You can also use a tar pipe to copy data to another server. If your -'git' user has SSH access to the newserver as 'git@newserver', you -can pipe the data through SSH. - -``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - ssh git@newserver tar -C /mnt/gitlab/repositories -xf - -``` - -If you want to compress the data before it goes over the network -(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`. - -## The target directory contains an outdated copy of the repositories: use rsync - -If the target directory already contains a partial / outdated copy -of the repositories it may be wasteful to copy all the data again -with tar. In this scenario it is better to use rsync. This utility -is either already installed on your system or easily installable -via apt, yum etc. - -``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - /mnt/gitlab/repositories -``` - -The `/.` in the command above is very important, without it you can -easily get the wrong directory structure in the target directory. -If you want to see progress, replace `-a` with `-av`. - -### Single rsync to another server - -If the 'git' user on your source system has SSH access to the target -server you can send the repositories over the network with rsync. - -``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - git@newserver:/mnt/gitlab/repositories -``` - -## Thousands of Git repositories: use one rsync per repository - -Every time you start an rsync job it has to inspect all files in -the source directory, all files in the target directory, and then -decide what files to copy or not. If the source or target directory -has many contents this startup phase of rsync can become a burden -for your GitLab server. In cases like this you can make rsync's -life easier by dividing its work in smaller pieces, and sync one -repository at a time. - -In addition to rsync we will use [GNU -Parallel](http://www.gnu.org/software/parallel/). This utility is -not included in GitLab so you need to install it yourself with apt -or yum. Also note that the GitLab scripts we used below were added -in GitLab 8.1. - -** This process does not clean up repositories at the target location that no -longer exist at the source. ** If you start using your GitLab instance with -`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos` -after switching to the new repository storage directory. - -### Parallel rsync for all repositories known to GitLab - -This will sync repositories with 10 rsync processes at a time. We keep -track of progress so that the transfer can be restarted if necessary. - -First we create a new directory, owned by 'git', to hold transfer -logs. We assume the directory is empty before we start the transfer -procedure, and that we are the only ones writing files in it. - -``` -# Omnibus -sudo mkdir /var/opt/gitlab/transfer-logs -sudo chown git:git /var/opt/gitlab/transfer-logs - -# Source -sudo -u git -H mkdir /home/git/transfer-logs -``` - -We seed the process with a list of the directories we want to copy. - -``` -# Omnibus -sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt' - -# Source -cd /home/git/gitlab -sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt' -``` - -Now we can start the transfer. The command below is idempotent, and -the number of jobs done by GNU Parallel should converge to zero. If it -does not some repositories listed in all-repos-1234.txt may have been -deleted/renamed before they could be copied. - -``` -# Omnibus -sudo -u git sh -c ' -cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\ - /usr/bin/env JOBS=10 \ - /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ - /var/opt/gitlab/transfer-logs/success-$(date +%s).log \ - /var/opt/gitlab/git-data/repositories \ - /mnt/gitlab/repositories -' - -# Source -cd /home/git/gitlab -sudo -u git -H sh -c ' -cat /home/git/transfer-logs/* | sort | uniq -u |\ - /usr/bin/env JOBS=10 \ - bin/parallel-rsync-repos \ - /home/git/transfer-logs/success-$(date +%s).log \ - /home/git/repositories \ - /mnt/gitlab/repositories -` -``` - -### Parallel rsync only for repositories with recent activity - -Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. -Then you might only want to sync repositories that were changed via GitLab -_after_ that time. You can use the 'SINCE' variable to tell 'rake -gitlab:list_repos' to only print repositories with recent activity. - -``` -# Omnibus -sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ - sudo -u git \ - /usr/bin/env JOBS=10 \ - /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ - success-$(date +%s).log \ - /var/opt/gitlab/git-data/repositories \ - /mnt/gitlab/repositories - -# Source -cd /home/git/gitlab -sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ - sudo -u git -H \ - /usr/bin/env JOBS=10 \ - bin/parallel-rsync-repos \ - success-$(date +%s).log \ - /home/git/repositories \ - /mnt/gitlab/repositories -``` +This document was moved to [administration/operations/moving_repositories](../administration/operations/moving_repositories.md). diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md index b5e78348989..cf7c3b2e2ed 100644 --- a/doc/operations/sidekiq_memory_killer.md +++ b/doc/operations/sidekiq_memory_killer.md @@ -1,40 +1 @@ -# Sidekiq MemoryKiller - -The GitLab Rails application code suffers from memory leaks. For web requests -this problem is made manageable using -[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which -restarts Unicorn worker processes in between requests when needed. The Sidekiq -MemoryKiller applies the same approach to the Sidekiq processes used by GitLab -to process background jobs. - -Unlike unicorn-worker-killer, which is enabled by default for all GitLab -installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default -_only_ for Omnibus packages. The reason for this is that the MemoryKiller -relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab -installations from source do not all use Runit or an equivalent. - -With the default settings, the MemoryKiller will cause a Sidekiq restart no -more often than once every 15 minutes, with the restart causing about one -minute of delay for incoming background jobs. - -## Configuring the MemoryKiller - -The MemoryKiller is controlled using environment variables. - -- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is - greater than 0, then after each Sidekiq job, the MemoryKiller will check the - RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq - process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a - delayed shutdown is triggered. The default value for Omnibus packages is set - [in the omnibus-gitlab - repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). -- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When - a shutdown is triggered, the Sidekiq process will keep working normally for - another 15 minutes. -- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace - time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs. - Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells - Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must - restart Sidekiq. -- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of - the final signal sent to the Sidekiq process when we want it to shut down. +This document was moved to [administration/operations/sidekiq_memory_killer](../administration/operations/sidekiq_memory_killer.md). diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md index bad61151bda..fbc9697b755 100644 --- a/doc/operations/unicorn.md +++ b/doc/operations/unicorn.md @@ -1,86 +1 @@ -# Understanding Unicorn and unicorn-worker-killer - -## Unicorn - -GitLab uses [Unicorn](http://unicorn.bogomips.org/), a pre-forking Ruby web -server, to handle web requests (web browsers and Git HTTP clients). Unicorn is -a daemon written in Ruby and C that can load and run a Ruby on Rails -application; in our case the Rails application is GitLab Community Edition or -GitLab Enterprise Edition. - -Unicorn has a multi-process architecture to make better use of available CPU -cores (processes can run on different cores) and to have stronger fault -tolerance (most failures stay isolated in only one process and cannot take down -GitLab entirely). On startup, the Unicorn 'master' process loads a clean Ruby -environment with the GitLab application code, and then spawns 'workers' which -inherit this clean initial environment. The 'master' never handles any -requests, that is left to the workers. The operating system network stack -queues incoming requests and distributes them among the workers. - -In a perfect world, the master would spawn its pool of workers once, and then -the workers handle incoming web requests one after another until the end of -time. In reality, worker processes can crash or time out: if the master notices -that a worker takes too long to handle a request it will terminate the worker -process with SIGKILL ('kill -9'). No matter how the worker process ended, the -master process will replace it with a new 'clean' process again. Unicorn is -designed to be able to replace 'crashed' workers without dropping user -requests. - -This is what a Unicorn worker timeout looks like in `unicorn_stderr.log`. The -master process has PID 56227 below. - -``` -[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing -[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped # worker=10 -[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538 -[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready -``` - -### Tunables - -The main tunables for Unicorn are the number of worker processes and the -request timeout after which the Unicorn master terminates a worker process. -See the [omnibus-gitlab Unicorn settings -documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md) -if you want to adjust these settings. - -## unicorn-worker-killer - -GitLab has memory leaks. These memory leaks manifest themselves in long-running -processes, such as Unicorn workers. (The Unicorn master process is not known to -leak memory, probably because it does not handle user requests.) - -To make these memory leaks manageable, GitLab comes with the -[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This -gem [monkey-patches](https://en.wikipedia.org/wiki/Monkey_patch) the Unicorn -workers to do a memory self-check after every 16 requests. If the memory of the -Unicorn worker exceeds a pre-set limit then the worker process exits. The -Unicorn master then automatically replaces the worker process. - -This is a robust way to handle memory leaks: Unicorn is designed to handle -workers that 'crash' so no user requests will be dropped. The -unicorn-worker-killer gem is designed to only terminate a worker process _in -between requests_, so no user requests are affected. - -This is what a Unicorn worker memory restart looks like in unicorn_stderr.log. -You see that worker 4 (PID 125918) is inspecting itself and decides to exit. -The threshold memory value was 254802235 bytes, about 250MB. With GitLab this -threshold is a random value between 200 and 250 MB. The master process (PID -117565) then reaps the worker process and spawns a new 'worker 4' with PID -127549. - -``` -[2015-06-05T12:07:41.828374 #125918] WARN -- : #: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes) -[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1) -[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped # worker=4 -[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549 -[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready -``` - -One other thing that stands out in the log snippet above, taken from -GitLab.com, is that 'worker 4' was serving requests for only 23 seconds. This -is a normal value for our current GitLab.com setup and traffic. - -The high frequency of Unicorn memory restarts on some GitLab sites can be a -source of confusion for administrators. Usually they are a [red -herring](https://en.wikipedia.org/wiki/Red_herring). +This document was moved to [administration/operations/unicorn](../administration/operations/unicorn.md). -- cgit v1.2.1 From fb5a4202062d07d2dbca544f4cfb475a65411716 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 11:42:37 -0300 Subject: Allow projects to have many boards --- app/models/project.rb | 3 +-- spec/models/project_spec.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 74d54e69648..795a456b094 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -65,8 +65,7 @@ class Project < ActiveRecord::Base belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' - - has_one :board, dependent: :destroy + has_many :boards, dependent: :destroy # Project services has_many :services diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dae546a0cdc..3748b1c7f5f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -24,7 +24,7 @@ describe Project, models: true do it { is_expected.to have_one(:slack_service).dependent(:destroy) } it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) } - it { is_expected.to have_one(:board).dependent(:destroy) } + it { is_expected.to have_many(:boards).dependent(:destroy) } it { is_expected.to have_one(:campfire_service).dependent(:destroy) } it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) } it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) } -- cgit v1.2.1 From 95a5cc9285a8583988ece697ebdb948730b5db55 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 15:58:28 -0300 Subject: Restrict the number of permitted boards per project to one --- app/models/project.rb | 7 ++++++- spec/models/project_spec.rb | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 795a456b094..30db7ed50b3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -16,6 +16,7 @@ class Project < ActiveRecord::Base extend Gitlab::ConfigHelper + NUMBER_OF_PERMITTED_BOARDS = 1 UNKNOWN_IMPORT_URL = 'http://unknown.git' cache_markdown_field :description, pipeline: :description @@ -65,7 +66,7 @@ class Project < ActiveRecord::Base belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' - has_many :boards, dependent: :destroy + has_many :boards, before_add: :validate_board_limit, dependent: :destroy # Project services has_many :services @@ -1338,4 +1339,8 @@ class Project < ActiveRecord::Base shared_projects.any? end + + def validate_board_limit(board) + raise StandardError, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 3748b1c7f5f..1b13f1be477 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -94,6 +94,15 @@ describe Project, models: true do end end end + + describe '#boards' do + it 'raises an error when attempting to add more than one board to the project' do + subject.boards.build + + expect { subject.boards.build }.to raise_error(StandardError, 'Number of permitted boards exceeded') + expect(subject.boards.size).to eq 1 + end + end end describe 'modules' do -- cgit v1.2.1 From e46a4aabd925e0182c31976b5d28c38b9a8a0872 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 16:20:21 -0300 Subject: Update Boards::CreateService to handle with the has_many association --- app/services/boards/create_service.rb | 15 ++++++++++----- spec/factories/boards.rb | 5 +++++ spec/factories/projects.rb | 8 -------- spec/services/boards/create_service_spec.rb | 22 ++++++++++------------ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb index 072a0749285..a9dbd76a44a 100644 --- a/app/services/boards/create_service.rb +++ b/app/services/boards/create_service.rb @@ -1,16 +1,21 @@ module Boards class CreateService < Boards::BaseService def execute - create_board! unless project.board.present? - project.board + if project.boards.empty? + create_board! + else + project.boards.first + end end private def create_board! - project.create_board - project.board.lists.create(list_type: :backlog) - project.board.lists.create(list_type: :done) + board = project.boards.create + board.lists.create(list_type: :backlog) + board.lists.create(list_type: :done) + + board end end end diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb index 35c4a0b6f08..ec46146d9b5 100644 --- a/spec/factories/boards.rb +++ b/spec/factories/boards.rb @@ -1,5 +1,10 @@ FactoryGirl.define do factory :board do project factory: :empty_project + + after(:create) do |board| + board.lists.create(list_type: :backlog) + board.lists.create(list_type: :done) + end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 331172445e4..719ef17f57e 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -124,12 +124,4 @@ FactoryGirl.define do ) end end - - factory :project_with_board, parent: :empty_project do - after(:create) do |project| - project.create_board - project.board.lists.create(list_type: :backlog) - project.board.lists.create(list_type: :done) - end - end end diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb index a1a4dd4c57c..fde807cc410 100644 --- a/spec/services/boards/create_service_spec.rb +++ b/spec/services/boards/create_service_spec.rb @@ -2,33 +2,31 @@ require 'spec_helper' describe Boards::CreateService, services: true do describe '#execute' do + let(:project) { create(:empty_project) } + subject(:service) { described_class.new(project, double) } context 'when project does not have a board' do - let(:project) { create(:empty_project, board: nil) } - it 'creates a new board' do expect { service.execute }.to change(Board, :count).by(1) end it 'creates default lists' do - service.execute + board = service.execute - expect(project.board.lists.size).to eq 2 - expect(project.board.lists.first).to be_backlog - expect(project.board.lists.last).to be_done + expect(board.lists.size).to eq 2 + expect(board.lists.first).to be_backlog + expect(board.lists.last).to be_done end end context 'when project has a board' do - let!(:project) { create(:project_with_board) } - - it 'does not create a new board' do - expect { service.execute }.not_to change(Board, :count) + before do + create(:board, project: project) end - it 'does not create board lists' do - expect { service.execute }.not_to change(project.board.lists, :count) + it 'does not create a new board' do + expect { service.execute }.not_to change(project.boards, :count) end end end -- cgit v1.2.1 From a2c485473a142a5aad2f03abefb4bfafed645995 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 13:54:46 -0300 Subject: Add Boards::Lists::ListService to list lists for a specific board --- app/services/boards/lists/list_service.rb | 9 +++++++++ spec/services/boards/lists/list_service_spec.rb | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 app/services/boards/lists/list_service.rb create mode 100644 spec/services/boards/lists/list_service_spec.rb diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb new file mode 100644 index 00000000000..ff739bc7d9c --- /dev/null +++ b/app/services/boards/lists/list_service.rb @@ -0,0 +1,9 @@ +module Boards + module Lists + class ListService < Boards::BaseService + def execute(board) + board.lists + end + end + end +end diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb new file mode 100644 index 00000000000..4464f80f796 --- /dev/null +++ b/spec/services/boards/lists/list_service_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Boards::Lists::ListService, services: true do + describe '#execute' do + it "returns board's lists" do + project = create(:empty_project) + board = create(:board, project: project) + label = create(:label, project: project) + backlog_list = create(:backlog_list, board: board) + list = create(:list, board: board, label: label) + done_list = create(:done_list, board: board) + + service = described_class.new(project, double) + + expect(service.execute(board)).to eq [backlog_list, list, done_list] + end + end +end -- cgit v1.2.1 From 9110746370c9759402af5087a612cbcfdf667c3c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:00:18 -0300 Subject: Update Boards::Lists::CreateService to create lists for a specific board --- app/services/boards/base_service.rb | 1 - app/services/boards/lists/create_service.rb | 10 +++++----- spec/services/boards/lists/create_service_spec.rb | 14 +++++++------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb index b2069ca825a..7eacacbaf7e 100644 --- a/app/services/boards/base_service.rb +++ b/app/services/boards/base_service.rb @@ -1,5 +1,4 @@ module Boards class BaseService < ::BaseService - delegate :board, to: :project end end diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index b1887820bd4..da6f59c0399 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -1,23 +1,23 @@ module Boards module Lists class CreateService < Boards::BaseService - def execute + def execute(board) List.transaction do label = project.labels.find(params[:label_id]) - position = next_position + position = next_position(board) - create_list(label, position) + create_list(board, label, position) end end private - def next_position + def next_position(board) max_position = board.lists.movable.maximum(:position) max_position.nil? ? 0 : max_position.succ end - def create_list(label, position) + def create_list(board, label, position) board.lists.create(label: label, list_type: :label, position: position) end end diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index bff9c1fd1fe..e7806add916 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Boards::Lists::CreateService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:label) { create(:label, project: project, name: 'in-progress') } @@ -11,7 +11,7 @@ describe Boards::Lists::CreateService, services: true do context 'when board lists is empty' do it 'creates a new list at beginning of the list' do - list = service.execute + list = service.execute(board) expect(list.position).to eq 0 end @@ -19,7 +19,7 @@ describe Boards::Lists::CreateService, services: true do context 'when board lists has backlog, and done lists' do it 'creates a new list at beginning of the list' do - list = service.execute + list = service.execute(board) expect(list.position).to eq 0 end @@ -30,7 +30,7 @@ describe Boards::Lists::CreateService, services: true do create(:list, board: board, position: 0) create(:list, board: board, position: 1) - list = service.execute + list = service.execute(board) expect(list.position).to eq 2 end @@ -40,7 +40,7 @@ describe Boards::Lists::CreateService, services: true do it 'creates a new list at end of the label lists' do list1 = create(:list, board: board, position: 0) - list2 = service.execute + list2 = service.execute(board) expect(list1.reload.position).to eq 0 expect(list2.reload.position).to eq 1 @@ -52,7 +52,7 @@ describe Boards::Lists::CreateService, services: true do label = create(:label, name: 'in-development') service = described_class.new(project, user, label_id: label.id) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound) end end end -- cgit v1.2.1 From af87cf7c6ee1778b283ed285cdd7edbaaffc5fa5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:06:34 -0300 Subject: Update Boards::Lists::GenerateService to generate for a specific board --- app/services/boards/lists/generate_service.rb | 8 ++++---- spec/services/boards/lists/generate_service_spec.rb | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 830e386c98b..686e4e4b336 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -1,11 +1,11 @@ module Boards module Lists class GenerateService < Boards::BaseService - def execute + def execute(board) return false unless board.lists.movable.empty? List.transaction do - label_params.each { |params| create_list(params) } + label_params.each { |params| create_list(board, params) } end true @@ -13,9 +13,9 @@ module Boards private - def create_list(params) + def create_list(board, params) label = find_or_create_label(params) - Lists::CreateService.new(project, current_user, label_id: label.id).execute + Lists::CreateService.new(project, current_user, label_id: label.id).execute(board) end def find_or_create_label(params) diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 4171e4d816c..8b2f5e81338 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -2,15 +2,15 @@ require 'spec_helper' describe Boards::Lists::GenerateService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } subject(:service) { described_class.new(project, user) } context 'when board lists is empty' do it 'creates the default lists' do - expect { service.execute }.to change(board.lists, :count).by(2) + expect { service.execute(board) }.to change(board.lists, :count).by(2) end end @@ -18,13 +18,13 @@ describe Boards::Lists::GenerateService, services: true do it 'does not creates the default lists' do create(:list, board: board) - expect { service.execute }.not_to change(board.lists, :count) + expect { service.execute(board) }.not_to change(board.lists, :count) end end context 'when project labels does not contains any list label' do it 'creates labels' do - expect { service.execute }.to change(project.labels, :count).by(2) + expect { service.execute(board) }.to change(project.labels, :count).by(2) end end @@ -32,7 +32,7 @@ describe Boards::Lists::GenerateService, services: true do it 'creates the missing labels' do create(:label, project: project, name: 'Doing') - expect { service.execute }.to change(project.labels, :count).by(1) + expect { service.execute(board) }.to change(project.labels, :count).by(1) end end end -- cgit v1.2.1 From 1a4b1e9735c8b3502d54f1e9ecbce0c3f235f5c7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:13:43 -0300 Subject: Update Boards::Lists::MoveService to move lists inside a specific board --- app/services/boards/lists/move_service.rb | 3 ++- spec/services/boards/lists/move_service_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb index 020ff69f4a7..7d0730e8332 100644 --- a/app/services/boards/lists/move_service.rb +++ b/app/services/boards/lists/move_service.rb @@ -2,6 +2,7 @@ module Boards module Lists class MoveService < Boards::BaseService def execute(list) + @board = list.board @old_position = list.position @new_position = params[:position] @@ -16,7 +17,7 @@ module Boards private - attr_reader :old_position, :new_position + attr_reader :board, :old_position, :new_position def valid_move? new_position.present? && new_position != old_position && diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb index 102ed67449d..63fa0bb8c5f 100644 --- a/spec/services/boards/lists/move_service_spec.rb +++ b/spec/services/boards/lists/move_service_spec.rb @@ -2,16 +2,16 @@ require 'spec_helper' describe Boards::Lists::MoveService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } - let!(:backlog) { project.board.backlog_list } + let!(:backlog) { create(:backlog_list, board: board) } let!(:planning) { create(:list, board: board, position: 0) } let!(:development) { create(:list, board: board, position: 1) } let!(:review) { create(:list, board: board, position: 2) } let!(:staging) { create(:list, board: board, position: 3) } - let!(:done) { project.board.done_list } + let!(:done) { create(:done_list, board: board) } context 'when list type is set to label' do it 'keeps position of lists when new position is nil' do -- cgit v1.2.1 From 1fa3f30811c3599a2f060803b201441046926b87 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:26:28 -0300 Subject: Update Boards::Issues::ListService to list issues for a board list --- app/services/boards/issues/list_service.rb | 4 +++ spec/services/boards/issues/list_service_spec.rb | 31 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 435a8c6e681..782dbf0db1a 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -10,6 +10,10 @@ module Boards private + def board + @board ||= project.boards.find(params[:board_id]) + end + def list @list ||= board.lists.find(params[:id]) end diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 5b9f454fd2d..cc96fd05189 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -4,7 +4,7 @@ describe Boards::Issues::ListService, services: true do describe '#execute' do let(:user) { create(:user) } let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } let(:development) { create(:label, project: project, name: 'Development') } @@ -13,10 +13,10 @@ describe Boards::Issues::ListService, services: true do let(:p2) { create(:label, title: 'P2', project: project, priority: 2) } let(:p3) { create(:label, title: 'P3', project: project, priority: 3) } - let!(:backlog) { project.board.backlog_list } + let!(:backlog) { create(:backlog_list, board: board) } let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { project.board.done_list } + let!(:done) { create(:done_list, board: board) } let!(:opened_issue1) { create(:labeled_issue, project: project, labels: [bug]) } let!(:opened_issue2) { create(:labeled_issue, project: project, labels: [p2]) } @@ -37,7 +37,7 @@ describe Boards::Issues::ListService, services: true do end it 'delegates search to IssuesFinder' do - params = { id: list1.id } + params = { board_id: board.id, id: list1.id } expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original @@ -46,7 +46,7 @@ describe Boards::Issues::ListService, services: true do context 'sets default order to priority' do it 'returns opened issues when listing issues from Backlog' do - params = { id: backlog.id } + params = { board_id: board.id, id: backlog.id } issues = described_class.new(project, user, params).execute @@ -54,7 +54,7 @@ describe Boards::Issues::ListService, services: true do end it 'returns closed issues when listing issues from Done' do - params = { id: done.id } + params = { board_id: board.id, id: done.id } issues = described_class.new(project, user, params).execute @@ -62,12 +62,29 @@ describe Boards::Issues::ListService, services: true do end it 'returns opened issues that have label list applied when listing issues from a label list' do - params = { id: list1.id } + params = { board_id: board.id, id: list1.id } issues = described_class.new(project, user, params).execute expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2] end end + + context 'with list that does not belongs to the board' do + it 'raises an error' do + list = create(:list) + service = described_class.new(project, user, board_id: board.id, id: list.id) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with invalid list id' do + it 'raises an error' do + service = described_class.new(project, user, board_id: board.id, id: nil) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end end end -- cgit v1.2.1 From 104c4f88cdc5aaa40334c83112f7c994ff7128c6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:32:53 -0300 Subject: Update Boards::Issues::MoveService to move issues on a specific board --- app/services/boards/issues/move_service.rb | 4 ++++ spec/services/boards/issues/move_service_spec.rb | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 84dc3f70e76..24f6f2c7025 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -10,6 +10,10 @@ module Boards private + def board + @board ||= project.boards.find(params[:board_id]) + end + def valid_move? moving_from_list.present? && moving_to_list.present? && moving_from_list != moving_to_list diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 180f1b08631..9cf5a17e128 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -3,17 +3,17 @@ require 'spec_helper' describe Boards::Issues::MoveService, services: true do describe '#execute' do let(:user) { create(:user) } - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } let(:development) { create(:label, project: project, name: 'Development') } let(:testing) { create(:label, project: project, name: 'Testing') } - let!(:backlog) { project.board.backlog_list } + let!(:backlog) { create(:backlog_list, board: board) } let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { project.board.done_list } + let!(:done) { create(:done_list, board: board) } before do project.team << [user, :developer] @@ -22,7 +22,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog' do it 'adds the label of the list it goes to' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { from_list_id: backlog.id, to_list_id: list1.id } + params = { board_id: board.id, from_list_id: backlog.id, to_list_id: list1.id } described_class.new(project, user, params).execute(issue) @@ -33,7 +33,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to backlog' do it 'removes all list-labels' do issue = create(:labeled_issue, project: project, labels: [bug, development, testing]) - params = { from_list_id: list1.id, to_list_id: backlog.id } + params = { board_id: board.id, from_list_id: list1.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) @@ -44,7 +44,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog to done' do it 'closes the issue' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { from_list_id: backlog.id, to_list_id: done.id } + params = { board_id: board.id, from_list_id: backlog.id, to_list_id: done.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -56,7 +56,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving an issue between lists' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { from_list_id: list1.id, to_list_id: list2.id } } + let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list2.id } } it 'delegates the label changes to Issues::UpdateService' do expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once @@ -73,7 +73,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to done' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing]) } - let(:params) { { from_list_id: list2.id, to_list_id: done.id } } + let(:params) { { board_id: board.id, from_list_id: list2.id, to_list_id: done.id } } it 'delegates the close proceedings to Issues::CloseService' do expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once @@ -92,7 +92,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done' do let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } - let(:params) { { from_list_id: done.id, to_list_id: list2.id } } + let(:params) { { board_id: board.id, from_list_id: done.id, to_list_id: list2.id } } it 'delegates the re-open proceedings to Issues::ReopenService' do expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once @@ -112,7 +112,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done to backlog' do it 'reopens the issue' do issue = create(:labeled_issue, :closed, project: project, labels: [bug]) - params = { from_list_id: done.id, to_list_id: backlog.id } + params = { board_id: board.id, from_list_id: done.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -124,7 +124,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to same list' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { from_list_id: list1.id, to_list_id: list1.id } } + let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list1.id } } it 'returns false' do expect(described_class.new(project, user, params).execute(issue)).to eq false -- cgit v1.2.1 From 8b15e328a60f050d4ff24c0c7203461ae2724a30 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:50:44 -0300 Subject: Removes all labels from project boards when moving and issue to done --- app/services/boards/issues/move_service.rb | 2 +- spec/services/boards/issues/move_service_spec.rb | 34 +++++++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 24f6f2c7025..9a16609a8b5 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -53,7 +53,7 @@ module Boards if moving_to_list.movable? moving_from_list.label_id else - board.lists.movable.pluck(:label_id) + project.boards.joins(:lists).merge(List.movable).pluck(:label_id) end Array(label_ids).compact diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 9cf5a17e128..c43b2aec490 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -4,16 +4,16 @@ describe Boards::Issues::MoveService, services: true do describe '#execute' do let(:user) { create(:user) } let(:project) { create(:empty_project) } - let(:board) { create(:board, project: project) } + let(:board1) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } let(:development) { create(:label, project: project, name: 'Development') } let(:testing) { create(:label, project: project, name: 'Testing') } - let!(:backlog) { create(:backlog_list, board: board) } - let!(:list1) { create(:list, board: board, label: development, position: 0) } - let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { create(:done_list, board: board) } + let!(:backlog) { create(:backlog_list, board: board1) } + let!(:list1) { create(:list, board: board1, label: development, position: 0) } + let!(:list2) { create(:list, board: board1, label: testing, position: 1) } + let!(:done) { create(:done_list, board: board1) } before do project.team << [user, :developer] @@ -22,7 +22,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog' do it 'adds the label of the list it goes to' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { board_id: board.id, from_list_id: backlog.id, to_list_id: list1.id } + params = { board_id: board1.id, from_list_id: backlog.id, to_list_id: list1.id } described_class.new(project, user, params).execute(issue) @@ -33,7 +33,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to backlog' do it 'removes all list-labels' do issue = create(:labeled_issue, project: project, labels: [bug, development, testing]) - params = { board_id: board.id, from_list_id: list1.id, to_list_id: backlog.id } + params = { board_id: board1.id, from_list_id: list1.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) @@ -44,7 +44,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog to done' do it 'closes the issue' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { board_id: board.id, from_list_id: backlog.id, to_list_id: done.id } + params = { board_id: board1.id, from_list_id: backlog.id, to_list_id: done.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -56,7 +56,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving an issue between lists' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list2.id } } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } it 'delegates the label changes to Issues::UpdateService' do expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once @@ -72,8 +72,12 @@ describe Boards::Issues::MoveService, services: true do end context 'when moving to done' do - let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing]) } - let(:params) { { board_id: board.id, from_list_id: list2.id, to_list_id: done.id } } + let(:board2) { create(:board, project: project) } + let(:regression) { create(:label, project: project, name: 'Regression') } + let!(:list3) { create(:list, board: board2, label: regression, position: 1) } + + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } + let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: done.id } } it 'delegates the close proceedings to Issues::CloseService' do expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once @@ -81,7 +85,7 @@ describe Boards::Issues::MoveService, services: true do described_class.new(project, user, params).execute(issue) end - it 'removes all list-labels and close the issue' do + it 'removes all list-labels from project boards and close the issue' do described_class.new(project, user, params).execute(issue) issue.reload @@ -92,7 +96,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done' do let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } - let(:params) { { board_id: board.id, from_list_id: done.id, to_list_id: list2.id } } + let(:params) { { board_id: board1.id, from_list_id: done.id, to_list_id: list2.id } } it 'delegates the re-open proceedings to Issues::ReopenService' do expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once @@ -112,7 +116,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done to backlog' do it 'reopens the issue' do issue = create(:labeled_issue, :closed, project: project, labels: [bug]) - params = { board_id: board.id, from_list_id: done.id, to_list_id: backlog.id } + params = { board_id: board1.id, from_list_id: done.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -124,7 +128,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to same list' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list1.id } } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } it 'returns false' do expect(described_class.new(project, user, params).execute(issue)).to eq false -- cgit v1.2.1 From b4b8e0ec9405c4b5d17b53552612397e847e734d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 16:01:06 -0300 Subject: Add Boards::ListService service to list project boards --- app/services/boards/list_service.rb | 14 ++++++++++++ spec/services/boards/list_service_spec.rb | 37 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 app/services/boards/list_service.rb create mode 100644 spec/services/boards/list_service_spec.rb diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb new file mode 100644 index 00000000000..cca4bdd82a2 --- /dev/null +++ b/app/services/boards/list_service.rb @@ -0,0 +1,14 @@ +module Boards + class ListService < Boards::BaseService + def execute + create_board! if project.boards.empty? + project.boards + end + + private + + def create_board! + Boards::CreateService.new(project, current_user).execute + end + end +end diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb new file mode 100644 index 00000000000..dff33e4bcbb --- /dev/null +++ b/spec/services/boards/list_service_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Boards::ListService, services: true do + describe '#execute' do + let(:project) { create(:empty_project) } + + subject(:service) { described_class.new(project, double) } + + context 'when project does not have a board' do + it 'creates a new project board' do + expect { service.execute }.to change(project.boards, :count).by(1) + end + + it 'delegates the project board creation to Boards::CreateService' do + expect_any_instance_of(Boards::CreateService).to receive(:execute).once + + service.execute + end + end + + context 'when project has a board' do + before do + create(:board, project: project) + end + + it 'does not create a new board' do + expect { service.execute }.not_to change(project.boards, :count) + end + end + + it 'returns project boards' do + board = create(:board, project: project) + + expect(service.execute).to match_array [board] + end + end +end -- cgit v1.2.1 From ecf4c10e9c395604583820ad01167db34d09d4aa Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 16:24:29 -0300 Subject: Add index action to Projects::BoardsController to return project boards --- app/controllers/projects/boards_controller.rb | 15 +++++-- app/views/projects/boards/index.html.haml | 0 config/routes/project.rb | 2 +- .../controllers/projects/boards_controller_spec.rb | 47 ++++++++++++++++++++++ spec/fixtures/api/schemas/board.json | 12 ++++++ spec/fixtures/api/schemas/boards.json | 4 ++ 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 app/views/projects/boards/index.html.haml create mode 100644 spec/fixtures/api/schemas/board.json create mode 100644 spec/fixtures/api/schemas/boards.json diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 0035633b774..56bc54fbd1c 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -1,9 +1,18 @@ class Projects::BoardsController < Projects::ApplicationController include IssuableCollections - - respond_to :html - before_action :authorize_read_board!, only: [:show] + before_action :authorize_read_board!, only: [:index, :show] + + def index + @boards = ::Boards::ListService.new(project, current_user).execute + + respond_to do |format| + format.html + format.json do + render json: @boards.as_json(only: [:id, :name]) + end + end + end def show ::Boards::CreateService.new(project, current_user).execute diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/config/routes/project.rb b/config/routes/project.rb index e8807ef06a7..3a7c4f86301 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -416,7 +416,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end end - resource :board, only: [:show] do + resources :boards, only: [:index, :show] do scope module: :boards do resources :issues, only: [:update] diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 6f6e608e1f3..d7698afd141 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -9,6 +9,53 @@ describe Projects::BoardsController do sign_in(user) end + describe 'GET index' do + it 'creates a new project board when project does not have one' do + expect { list_boards }.to change(project.boards, :count).by(1) + end + + context 'when format is HTML' do + it 'renders template' do + list_boards + + expect(response).to render_template :index + expect(response.content_type).to eq 'text/html' + end + end + + context 'when format is JSON' do + it 'returns a list of project boards' do + create_list(:board, 2, project: project) + + list_boards format: :json + + parsed_response = JSON.parse(response.body) + + expect(response).to match_response_schema('boards') + expect(parsed_response.length).to eq 2 + end + end + + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) + end + + it 'returns a not found 404 response' do + list_boards + + expect(response).to have_http_status(404) + end + end + + def list_boards(format: :html) + get :index, namespace_id: project.namespace.to_param, + project_id: project.to_param, + format: format + end + end + describe 'GET show' do it 'creates a new board when project does not have one' do expect { read_board }.to change(Board, :count).by(1) diff --git a/spec/fixtures/api/schemas/board.json b/spec/fixtures/api/schemas/board.json new file mode 100644 index 00000000000..6c6e2bee8cb --- /dev/null +++ b/spec/fixtures/api/schemas/board.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "required" : [ + "id", + "name" + ], + "properties" : { + "id": { "type": "integer" }, + "name": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/boards.json b/spec/fixtures/api/schemas/boards.json new file mode 100644 index 00000000000..117564ef77a --- /dev/null +++ b/spec/fixtures/api/schemas/boards.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "board.json" } +} -- cgit v1.2.1 From 723ed9cc3a76f7ce0e2d1b358a33d05fb05865c9 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 17:08:55 -0300 Subject: Update Projects::BoardsController#show to look up for a specific board --- app/controllers/projects/boards_controller.rb | 15 +++++++-- .../controllers/projects/boards_controller_spec.rb | 37 ++++++++++++++++------ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 56bc54fbd1c..bbb1b1bf1e1 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -9,13 +9,20 @@ class Projects::BoardsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @boards.as_json(only: [:id, :name]) + render json: serialize_as_json(@boards) end end end def show - ::Boards::CreateService.new(project, current_user).execute + @board = project.boards.find(params[:id]) + + respond_to do |format| + format.html + format.json do + render json: serialize_as_json(@board) + end + end end private @@ -23,4 +30,8 @@ class Projects::BoardsController < Projects::ApplicationController def authorize_read_board! return access_denied! unless can?(current_user, :read_board, project) end + + def serialize_as_json(resource) + resource.as_json(only: [:id, :name]) + end end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index d7698afd141..cc19035740e 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -57,15 +57,23 @@ describe Projects::BoardsController do end describe 'GET show' do - it 'creates a new board when project does not have one' do - expect { read_board }.to change(Board, :count).by(1) + let!(:board) { create(:board, project: project) } + + context 'when format is HTML' do + it 'renders template' do + read_board board: board + + expect(response).to render_template :show + expect(response.content_type).to eq 'text/html' + end end - it 'renders HTML template' do - read_board + context 'when format is JSON' do + it 'returns project board' do + read_board board: board, format: :json - expect(response).to render_template :show - expect(response.content_type).to eq 'text/html' + expect(response).to match_response_schema('board') + end end context 'with unauthorized user' do @@ -74,16 +82,27 @@ describe Projects::BoardsController do allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) end - it 'returns a successful 404 response' do - read_board + it 'returns a not found 404 response' do + read_board board: board + + expect(response).to have_http_status(404) + end + end + + context 'when board does not belong to project' do + it 'returns a not found 404 response' do + another_board = create(:board) + + read_board board: another_board expect(response).to have_http_status(404) end end - def read_board(format: :html) + def read_board(board:, format: :html) get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, + id: board.to_param, format: format end end -- cgit v1.2.1 From 5de4fba6e34ebf31dc6e737bf96dbd65d3daf958 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 19:24:42 -0300 Subject: Update Boards::Lists::DestroyService to remove list on a specic board --- app/services/boards/lists/destroy_service.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb index 25da3bfb56d..d75c5fd3dc6 100644 --- a/app/services/boards/lists/destroy_service.rb +++ b/app/services/boards/lists/destroy_service.rb @@ -4,6 +4,8 @@ module Boards def execute(list) return false unless list.destroyable? + @board = list.board + list.with_lock do decrement_higher_lists(list) remove_list(list) @@ -12,6 +14,8 @@ module Boards private + attr_reader :board + def decrement_higher_lists(list) board.lists.movable.where('position > ?', list.position) .update_all('position = position - 1') -- cgit v1.2.1 From e1f889df6463203e5bd899fca8e98de6b705cd43 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 19:25:29 -0300 Subject: Update endpoints to handle with board list changes --- .../projects/boards/lists_controller.rb | 18 ++++--- .../projects/boards/lists_controller_spec.rb | 57 ++++++++++++---------- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb index b995f586737..76ae41319c4 100644 --- a/app/controllers/projects/boards/lists_controller.rb +++ b/app/controllers/projects/boards/lists_controller.rb @@ -5,11 +5,11 @@ module Projects before_action :authorize_read_list!, only: [:index] def index - render json: serialize_as_json(project.board.lists) + render json: serialize_as_json(board.lists) end def create - list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute + list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute(board) if list.valid? render json: serialize_as_json(list) @@ -19,7 +19,7 @@ module Projects end def update - list = project.board.lists.movable.find(params[:id]) + list = board.lists.movable.find(params[:id]) service = ::Boards::Lists::MoveService.new(project, current_user, move_params) if service.execute(list) @@ -30,8 +30,8 @@ module Projects end def destroy - list = project.board.lists.destroyable.find(params[:id]) - service = ::Boards::Lists::DestroyService.new(project, current_user, params) + list = board.lists.destroyable.find(params[:id]) + service = ::Boards::Lists::DestroyService.new(project, current_user) if service.execute(list) head :ok @@ -43,8 +43,8 @@ module Projects def generate service = ::Boards::Lists::GenerateService.new(project, current_user) - if service.execute - render json: serialize_as_json(project.board.lists.movable) + if service.execute(board) + render json: serialize_as_json(board.lists.movable) else head :unprocessable_entity end @@ -60,6 +60,10 @@ module Projects return render_403 unless can?(current_user, :read_list, project) end + def board + @board ||= project.boards.find(params[:board_id]) + end + def list_params params.require(:list).permit(:label_id) end diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index 709006a3601..a53a5feef44 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::Boards::ListsController do describe 'GET index' do it 'returns a successful 200 response' do - read_board_list user: user + read_board_list user: user, board: board expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/json' @@ -22,7 +22,7 @@ describe Projects::Boards::ListsController do it 'returns a list of board lists' do create(:list, board: board) - read_board_list user: user + read_board_list user: user, board: board parsed_response = JSON.parse(response.body) @@ -37,17 +37,18 @@ describe Projects::Boards::ListsController do end it 'returns a forbidden 403 response' do - read_board_list user: user + read_board_list user: user, board: board expect(response).to have_http_status(403) end end - def read_board_list(user:) + def read_board_list(user:, board:) sign_in(user) get :index, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, format: :json end end @@ -57,13 +58,13 @@ describe Projects::Boards::ListsController do let(:label) { create(:label, project: project, name: 'Development') } it 'returns a successful 200 response' do - create_board_list user: user, label_id: label.id + create_board_list user: user, board: board, label_id: label.id expect(response).to have_http_status(200) end it 'returns the created list' do - create_board_list user: user, label_id: label.id + create_board_list user: user, board: board, label_id: label.id expect(response).to match_response_schema('list') end @@ -72,7 +73,7 @@ describe Projects::Boards::ListsController do context 'with invalid params' do context 'when label is nil' do it 'returns a not found 404 response' do - create_board_list user: user, label_id: nil + create_board_list user: user, board: board, label_id: nil expect(response).to have_http_status(404) end @@ -82,7 +83,7 @@ describe Projects::Boards::ListsController do it 'returns a not found 404 response' do label = create(:label, name: 'Development') - create_board_list user: user, label_id: label.id + create_board_list user: user, board: board, label_id: label.id expect(response).to have_http_status(404) end @@ -93,17 +94,18 @@ describe Projects::Boards::ListsController do it 'returns a forbidden 403 response' do label = create(:label, project: project, name: 'Development') - create_board_list user: guest, label_id: label.id + create_board_list user: guest, board: board, label_id: label.id expect(response).to have_http_status(403) end end - def create_board_list(user:, label_id:) + def create_board_list(user:, board:, label_id:) sign_in(user) post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, list: { label_id: label_id }, format: :json end @@ -115,13 +117,13 @@ describe Projects::Boards::ListsController do context 'with valid position' do it 'returns a successful 200 response' do - move user: user, list: planning, position: 1 + move user: user, board: board, list: planning, position: 1 expect(response).to have_http_status(200) end it 'moves the list to the desired position' do - move user: user, list: planning, position: 1 + move user: user, board: board, list: planning, position: 1 expect(planning.reload.position).to eq 1 end @@ -129,7 +131,7 @@ describe Projects::Boards::ListsController do context 'with invalid position' do it 'returns an unprocessable entity 422 response' do - move user: user, list: planning, position: 6 + move user: user, board: board, list: planning, position: 6 expect(response).to have_http_status(422) end @@ -137,7 +139,7 @@ describe Projects::Boards::ListsController do context 'with invalid list id' do it 'returns a not found 404 response' do - move user: user, list: 999, position: 1 + move user: user, board: board, list: 999, position: 1 expect(response).to have_http_status(404) end @@ -145,17 +147,18 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - move user: guest, list: planning, position: 6 + move user: guest, board: board, list: planning, position: 6 expect(response).to have_http_status(403) end end - def move(user:, list:, position:) + def move(user:, board:, list:, position:) sign_in(user) patch :update, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, id: list.to_param, list: { position: position }, format: :json @@ -167,19 +170,19 @@ describe Projects::Boards::ListsController do context 'with valid list id' do it 'returns a successful 200 response' do - remove_board_list user: user, list: planning + remove_board_list user: user, board: board, list: planning expect(response).to have_http_status(200) end it 'removes list from board' do - expect { remove_board_list user: user, list: planning }.to change(board.lists, :size).by(-1) + expect { remove_board_list user: user, board: board, list: planning }.to change(board.lists, :size).by(-1) end end context 'with invalid list id' do it 'returns a not found 404 response' do - remove_board_list user: user, list: 999 + remove_board_list user: user, board: board, list: 999 expect(response).to have_http_status(404) end @@ -187,17 +190,18 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - remove_board_list user: guest, list: planning + remove_board_list user: guest, board: board, list: planning expect(response).to have_http_status(403) end end - def remove_board_list(user:, list:) + def remove_board_list(user:, board:, list:) sign_in(user) delete :destroy, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, id: list.to_param, format: :json end @@ -206,13 +210,13 @@ describe Projects::Boards::ListsController do describe 'POST generate' do context 'when board lists is empty' do it 'returns a successful 200 response' do - generate_default_board_lists user: user + generate_default_lists user: user, board: board expect(response).to have_http_status(200) end it 'returns the defaults lists' do - generate_default_board_lists user: user + generate_default_lists user: user, board: board expect(response).to match_response_schema('lists') end @@ -222,7 +226,7 @@ describe Projects::Boards::ListsController do it 'returns an unprocessable entity 422 response' do create(:list, board: board) - generate_default_board_lists user: user + generate_default_lists user: user, board: board expect(response).to have_http_status(422) end @@ -230,17 +234,18 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - generate_default_board_lists user: guest + generate_default_lists user: guest, board: board expect(response).to have_http_status(403) end end - def generate_default_board_lists(user:) + def generate_default_lists(user:, board: board) sign_in(user) post :generate, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, format: :json end end -- cgit v1.2.1 From 67515098657704505935119503ef23e45f0fda04 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 23:44:44 -0300 Subject: Update endpoints to handle with board issues --- .../projects/boards/issues_controller.rb | 4 +- .../projects/boards/issues_controller_spec.rb | 53 ++++++++++++++++------ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 095af6c35eb..82c99e4e8d9 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -60,11 +60,11 @@ module Projects end def filter_params - params.merge(id: params[:list_id]) + params.merge(board_id: params[:board_id], id: params[:list_id]) end def move_params - params.permit(:id, :from_list_id, :to_list_id) + params.permit(:board_id, :id, :from_list_id, :to_list_id) end def issue_params diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 566658b508d..92e77291da9 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -1,15 +1,16 @@ require 'spec_helper' describe Projects::Boards::IssuesController do - let(:project) { create(:project_with_board) } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:guest) { create(:user) } let(:planning) { create(:label, project: project, name: 'Planning') } let(:development) { create(:label, project: project, name: 'Development') } - let!(:list1) { create(:list, board: project.board, label: planning, position: 0) } - let!(:list2) { create(:list, board: project.board, label: development, position: 1) } + let!(:list1) { create(:list, board: board, label: planning, position: 0) } + let!(:list2) { create(:list, board: board, label: development, position: 1) } before do project.team << [user, :master] @@ -24,7 +25,7 @@ describe Projects::Boards::IssuesController do create(:labeled_issue, project: project, labels: [development]) create(:labeled_issue, project: project, labels: [development], assignee: johndoe) - list_issues user: user, list_id: list2 + list_issues user: user, board: board, list: list2 parsed_response = JSON.parse(response.body) @@ -33,9 +34,17 @@ describe Projects::Boards::IssuesController do end end + context 'with invalid board id' do + it 'returns a not found 404 response' do + list_issues user: user, board: 999, list: list2 + + expect(response).to have_http_status(404) + end + end + context 'with invalid list id' do it 'returns a not found 404 response' do - list_issues user: user, list_id: 999 + list_issues user: user, board: board, list: 999 expect(response).to have_http_status(404) end @@ -47,19 +56,20 @@ describe Projects::Boards::IssuesController do allow(Ability).to receive(:allowed?).with(user, :read_issue, project).and_return(false) end - it 'returns a successful 403 response' do - list_issues user: user, list_id: list2 + it 'returns a forbidden 403 response' do + list_issues user: user, board: board, list: list2 expect(response).to have_http_status(403) end end - def list_issues(user:, list_id:) + def list_issues(user:, board:, list:) sign_in(user) get :index, namespace_id: project.namespace.to_param, project_id: project.to_param, - list_id: list_id.to_param + board_id: board.to_param, + list_id: list.to_param end end @@ -122,13 +132,13 @@ describe Projects::Boards::IssuesController do context 'with valid params' do it 'returns a successful 200 response' do - move user: user, issue: issue, from_list_id: list1.id, to_list_id: list2.id + move user: user, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(200) end it 'moves issue to the desired list' do - move user: user, issue: issue, from_list_id: list1.id, to_list_id: list2.id + move user: user, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(issue.reload.labels).to contain_exactly(development) end @@ -136,31 +146,44 @@ describe Projects::Boards::IssuesController do context 'with invalid params' do it 'returns a unprocessable entity 422 response for invalid lists' do - move user: user, issue: issue, from_list_id: nil, to_list_id: nil + move user: user, board: board, issue: issue, from_list_id: nil, to_list_id: nil expect(response).to have_http_status(422) end + it 'returns a not found 404 response for invalid board id' do + move user: user, board: 999, issue: issue, from_list_id: list1.id, to_list_id: list2.id + + expect(response).to have_http_status(404) + end + it 'returns a not found 404 response for invalid issue id' do - move user: user, issue: 999, from_list_id: list1.id, to_list_id: list2.id + move user: user, board: board, issue: 999, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(404) end end context 'with unauthorized user' do + let(:guest) { create(:user) } + + before do + project.team << [guest, :guest] + end + it 'returns a forbidden 403 response' do - move user: guest, issue: issue, from_list_id: list1.id, to_list_id: list2.id + move user: guest, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(403) end end - def move(user:, issue:, from_list_id:, to_list_id:) + def move(user:, board:, issue:, from_list_id:, to_list_id:) sign_in(user) patch :update, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, id: issue.to_param, from_list_id: from_list_id, to_list_id: to_list_id, -- cgit v1.2.1 From a88ed81fb97cdd04b3ca222a534f8319319d7091 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 23:45:54 -0300 Subject: Remove unused Projects::BoardListsController controller --- app/controllers/projects/board_lists_controller.rb | 65 ---------------------- 1 file changed, 65 deletions(-) delete mode 100644 app/controllers/projects/board_lists_controller.rb diff --git a/app/controllers/projects/board_lists_controller.rb b/app/controllers/projects/board_lists_controller.rb deleted file mode 100644 index 3cfb08d5822..00000000000 --- a/app/controllers/projects/board_lists_controller.rb +++ /dev/null @@ -1,65 +0,0 @@ -class Projects::BoardListsController < Projects::ApplicationController - respond_to :json - - before_action :authorize_admin_list! - - rescue_from ActiveRecord::RecordNotFound, with: :record_not_found - - def create - list = Boards::Lists::CreateService.new(project, current_user, list_params).execute - - if list.valid? - render json: list.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } }) - else - render json: list.errors, status: :unprocessable_entity - end - end - - def update - service = Boards::Lists::MoveService.new(project, current_user, move_params) - - if service.execute - head :ok - else - head :unprocessable_entity - end - end - - def destroy - service = Boards::Lists::DestroyService.new(project, current_user, params) - - if service.execute - head :ok - else - head :unprocessable_entity - end - end - - def generate - service = Boards::Lists::GenerateService.new(project, current_user) - - if service.execute - render json: project.board.lists.label.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } }) - else - head :unprocessable_entity - end - end - - private - - def authorize_admin_list! - return render_403 unless can?(current_user, :admin_list, project) - end - - def list_params - params.require(:list).permit(:label_id) - end - - def move_params - params.require(:list).permit(:position).merge(id: params[:id]) - end - - def record_not_found(exception) - render json: { error: exception.message }, status: :not_found - end -end -- cgit v1.2.1 From 38cece495715df191fcb0c4ec0c5c2e44da2debf Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 23:50:35 -0300 Subject: Fix rubocop offenses --- .../projects/boards/lists_controller_spec.rb | 2 +- spec/services/boards/issues/list_service_spec.rb | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index a53a5feef44..afede0191dc 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -240,7 +240,7 @@ describe Projects::Boards::ListsController do end end - def generate_default_lists(user:, board: board) + def generate_default_lists(user:, board:) sign_in(user) post :generate, namespace_id: project.namespace.to_param, diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index cc96fd05189..218a6d2bc2f 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -70,21 +70,21 @@ describe Boards::Issues::ListService, services: true do end end - context 'with list that does not belongs to the board' do - it 'raises an error' do - list = create(:list) - service = described_class.new(project, user, board_id: board.id, id: list.id) + context 'with list that does not belongs to the board' do + it 'raises an error' do + list = create(:list) + service = described_class.new(project, user, board_id: board.id, id: list.id) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) - end + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) end + end - context 'with invalid list id' do - it 'raises an error' do - service = described_class.new(project, user, board_id: board.id, id: nil) + context 'with invalid list id' do + it 'raises an error' do + service = described_class.new(project, user, board_id: board.id, id: nil) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) - end + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) end + end end end -- cgit v1.2.1 From 9dc4ebc8ea5cd223b17cbc1d772348bebc50cdeb Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 14:35:58 -0300 Subject: Fix links to issue boards --- app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/boards/show.html.haml | 2 +- app/views/projects/issues/_head.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index e44a2bfed9d..99a58bbb676 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -116,4 +116,4 @@ -# Shortcut to issue boards %li.hidden - = link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' + = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index edbbd3f3d2a..5b441b48caf 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,7 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project)}", + "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 509b01c548a..4825820c4d9 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -10,7 +10,7 @@ Issues = nav_link(controller: :boards) do - = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do + = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do %span Board -- cgit v1.2.1 From 81c253ded12c96bfa3f5cf27f0e23c61619ad914 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 14:37:41 -0300 Subject: Add Project#boards to import/export configuration file --- spec/lib/gitlab/import_export/all_models.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 006569254a6..5d5836e9bee 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -118,7 +118,7 @@ project: - creator - group - namespace -- board +- boards - last_event - services - campfire_service -- cgit v1.2.1 From 2c2a1dea67ef41a6e283c4816865645fba318a16 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 17:13:33 -0300 Subject: Refactoring service to create a new issue in a board list --- app/controllers/projects/boards/issues_controller.rb | 5 ++--- app/services/boards/issues/create_service.rb | 15 +++++++++++---- .../controllers/projects/boards/issues_controller_spec.rb | 13 +++++++------ spec/services/boards/issues/create_service_spec.rb | 12 ++++++------ 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 82c99e4e8d9..71eb56aed0b 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -16,9 +16,8 @@ module Projects end def create - list = project.board.lists.find(params[:list_id]) service = ::Boards::Issues::CreateService.new(project, current_user, issue_params) - issue = service.execute(list) + issue = service.execute if issue.valid? render json: serialize_as_json(issue) @@ -68,7 +67,7 @@ module Projects end def issue_params - params.require(:issue).permit(:title).merge(request: request) + params.require(:issue).permit(:title).merge(board_id: params[:board_id], list_id: params[:list_id], request: request) end def serialize_as_json(resource) diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb index 3701afd441f..a181e74043b 100644 --- a/app/services/boards/issues/create_service.rb +++ b/app/services/boards/issues/create_service.rb @@ -1,14 +1,21 @@ module Boards module Issues class CreateService < Boards::BaseService - def execute(list) - params.merge!(label_ids: [list.label_id]) - create_issue + def execute + create_issue(params.merge(label_ids: [list.label_id])) end private - def create_issue + def board + @board ||= project.boards.find(params.delete(:board_id)) + end + + def list + @list ||= board.lists.find(params.delete(:list_id)) + end + + def create_issue(params) ::Issues::CreateService.new(project, current_user, params).execute end end diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 92e77291da9..da59642f24d 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -76,13 +76,13 @@ describe Projects::Boards::IssuesController do describe 'POST create' do context 'with valid params' do it 'returns a successful 200 response' do - create_issue user: user, list: list1, title: 'New issue' + create_issue user: user, board: board, list: list1, title: 'New issue' expect(response).to have_http_status(200) end it 'returns the created issue' do - create_issue user: user, list: list1, title: 'New issue' + create_issue user: user, board: board, list: list1, title: 'New issue' expect(response).to match_response_schema('issue') end @@ -91,7 +91,7 @@ describe Projects::Boards::IssuesController do context 'with invalid params' do context 'when title is nil' do it 'returns an unprocessable entity 422 response' do - create_issue user: user, list: list1, title: nil + create_issue user: user, board: board, list: list1, title: nil expect(response).to have_http_status(422) end @@ -101,7 +101,7 @@ describe Projects::Boards::IssuesController do it 'returns a not found 404 response' do list = create(:list) - create_issue user: user, list: list, title: 'New issue' + create_issue user: user, board: board, list: list, title: 'New issue' expect(response).to have_http_status(404) end @@ -110,17 +110,18 @@ describe Projects::Boards::IssuesController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - create_issue user: guest, list: list1, title: 'New issue' + create_issue user: guest, board: board, list: list1, title: 'New issue' expect(response).to have_http_status(403) end end - def create_issue(user:, list:, title:) + def create_issue(user:, board:, list:, title:) sign_in(user) post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, list_id: list.to_param, issue: { title: title }, format: :json diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb index 33e10e79f6d..360ee398f77 100644 --- a/spec/services/boards/issues/create_service_spec.rb +++ b/spec/services/boards/issues/create_service_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' describe Boards::Issues::CreateService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:label) { create(:label, project: project, name: 'in-progress') } let!(:list) { create(:list, board: board, label: label, position: 0) } - subject(:service) { described_class.new(project, user, title: 'New issue') } + subject(:service) { described_class.new(project, user, board_id: board.id, list_id: list.id, title: 'New issue') } before do project.team << [user, :developer] @@ -17,15 +17,15 @@ describe Boards::Issues::CreateService, services: true do it 'delegates the create proceedings to Issues::CreateService' do expect_any_instance_of(Issues::CreateService).to receive(:execute).once - service.execute(list) + service.execute end it 'creates a new issue' do - expect { service.execute(list) }.to change(project.issues, :count).by(1) + expect { service.execute }.to change(project.issues, :count).by(1) end it 'adds the label of the list to the issue' do - issue = service.execute(list) + issue = service.execute expect(issue.labels).to eq [label] end -- cgit v1.2.1 From 1a2002d9084db71d5a0a2631985012b4544c4037 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 17:14:39 -0300 Subject: Update board specs to use board factory instead of project_with_board --- spec/controllers/projects/boards/lists_controller_spec.rb | 4 ++-- spec/features/boards/boards_spec.rb | 5 +++-- spec/features/boards/new_issue_spec.rb | 3 ++- spec/services/boards/issues/list_service_spec.rb | 2 +- spec/services/boards/lists/destroy_service_spec.rb | 12 ++++++------ spec/services/boards/lists/list_service_spec.rb | 4 +--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index afede0191dc..34d6119429d 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Projects::Boards::ListsController do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:guest) { create(:user) } diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 470e2bdbb9b..f79b6a77696 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -4,7 +4,8 @@ describe 'Issue Boards', feature: true, js: true do include WaitForAjax include WaitForVueResource - let(:project) { create(:project_with_board, :public) } + let(:project) { create(:empty_project, :public) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let!(:user2) { create(:user) } @@ -468,7 +469,7 @@ describe 'Issue Boards', feature: true, js: true do it 'removes filtered labels' do wait_for_vue_resource - + page.within '.labels-filter' do click_button('Label') wait_for_ajax diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index c046e6b8d79..1ceb60d5297 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -4,7 +4,8 @@ describe 'Issue Boards new issue', feature: true, js: true do include WaitForAjax include WaitForVueResource - let(:project) { create(:project_with_board, :public) } + let(:project) { create(:empty_project, :public) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } context 'authorized user' do diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 218a6d2bc2f..d9eb757f986 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Boards::Issues::ListService, services: true do describe '#execute' do let(:user) { create(:user) } - let(:project) { create(:project_with_board) } + let(:project) { create(:empty_project) } let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb index 474c4512471..628caf03476 100644 --- a/spec/services/boards/lists/destroy_service_spec.rb +++ b/spec/services/boards/lists/destroy_service_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Boards::Lists::DestroyService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } context 'when list type is label' do @@ -15,11 +15,11 @@ describe Boards::Lists::DestroyService, services: true do end it 'decrements position of higher lists' do - backlog = project.board.backlog_list + backlog = board.backlog_list development = create(:list, board: board, position: 0) review = create(:list, board: board, position: 1) staging = create(:list, board: board, position: 2) - done = project.board.done_list + done = board.done_list described_class.new(project, user).execute(development) @@ -31,14 +31,14 @@ describe Boards::Lists::DestroyService, services: true do end it 'does not remove list from board when list type is backlog' do - list = project.board.backlog_list + list = board.backlog_list service = described_class.new(project, user) expect { service.execute(list) }.not_to change(board.lists, :count) end it 'does not remove list from board when list type is done' do - list = project.board.done_list + list = board.done_list service = described_class.new(project, user) expect { service.execute(list) }.not_to change(board.lists, :count) diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb index 4464f80f796..334cee3f06d 100644 --- a/spec/services/boards/lists/list_service_spec.rb +++ b/spec/services/boards/lists/list_service_spec.rb @@ -6,13 +6,11 @@ describe Boards::Lists::ListService, services: true do project = create(:empty_project) board = create(:board, project: project) label = create(:label, project: project) - backlog_list = create(:backlog_list, board: board) list = create(:list, board: board, label: label) - done_list = create(:done_list, board: board) service = described_class.new(project, double) - expect(service.execute(board)).to eq [backlog_list, list, done_list] + expect(service.execute(board)).to eq [board.backlog_list, list, board.done_list] end end end -- cgit v1.2.1 From e171c1d80debd88d1aa3a3c74d1937b68a7f0f18 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 17:36:46 -0300 Subject: Update Issue Board API to handle with has_many association --- lib/api/boards.rb | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 4d5d144a02e..9b71d335128 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -7,13 +7,14 @@ module API # Get the project board get ':id/boards' do authorize!(:read_board, user_project) - present [user_project.board], with: Entities::Board + present user_project.boards, with: Entities::Board end segment ':id/boards/:board_id' do helpers do def project_board - board = user_project.board + board = user_project.boards.first + if params[:board_id].to_i == board.id board else @@ -55,8 +56,10 @@ module API authorize!(:admin_list, user_project) - list = ::Boards::Lists::CreateService.new(user_project, current_user, - { label_id: params[:label_id] }).execute + service = ::Boards::Lists::CreateService.new(user_project, current_user, + { label_id: params[:label_id] }) + + list = service.execute(project_board) if list.valid? present list, with: Entities::List @@ -78,10 +81,10 @@ module API authorize!(:admin_list, user_project) - moved = ::Boards::Lists::MoveService.new(user_project, current_user, - { position: params[:position].to_i }).execute(list) + service = ::Boards::Lists::MoveService.new(user_project, current_user, + { position: params[:position].to_i }) - if moved + if service.execute(list) present list, with: Entities::List else render_api_error!({ error: "List could not be moved!" }, 400) @@ -97,16 +100,16 @@ module API # Example Request: # DELETE /projects/:id/boards/:board_id/lists/:list_id delete "/lists/:list_id" do - list = board_lists.find_by(id: params[:list_id]) - authorize!(:admin_list, user_project) - if list - destroyed_list = ::Boards::Lists::DestroyService.new( - user_project, current_user).execute(list) - present destroyed_list, with: Entities::List + list = board_lists.find(params[:list_id]) + + service = ::Boards::Lists::DestroyService.new(user_project, current_user) + + if service.execute(list) + present list, with: Entities::List else - not_found!('List') + render_api_error!({ error: 'List could not be deleted!' }, 400) end end end -- cgit v1.2.1 From 478b3d64f8cc7a47c5d1fc0507036590417b8aa8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 7 Oct 2016 16:46:32 -0300 Subject: Remove Boards::BaseService --- app/services/boards/base_service.rb | 4 ---- app/services/boards/create_service.rb | 2 +- app/services/boards/issues/create_service.rb | 2 +- app/services/boards/issues/list_service.rb | 2 +- app/services/boards/issues/move_service.rb | 2 +- app/services/boards/list_service.rb | 2 +- app/services/boards/lists/create_service.rb | 2 +- app/services/boards/lists/destroy_service.rb | 2 +- app/services/boards/lists/generate_service.rb | 2 +- app/services/boards/lists/list_service.rb | 2 +- app/services/boards/lists/move_service.rb | 2 +- 11 files changed, 10 insertions(+), 14 deletions(-) delete mode 100644 app/services/boards/base_service.rb diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb deleted file mode 100644 index 7eacacbaf7e..00000000000 --- a/app/services/boards/base_service.rb +++ /dev/null @@ -1,4 +0,0 @@ -module Boards - class BaseService < ::BaseService - end -end diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb index a9dbd76a44a..9bdd7b6f0cf 100644 --- a/app/services/boards/create_service.rb +++ b/app/services/boards/create_service.rb @@ -1,5 +1,5 @@ module Boards - class CreateService < Boards::BaseService + class CreateService < BaseService def execute if project.boards.empty? create_board! diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb index a181e74043b..c0d7ff5b585 100644 --- a/app/services/boards/issues/create_service.rb +++ b/app/services/boards/issues/create_service.rb @@ -1,6 +1,6 @@ module Boards module Issues - class CreateService < Boards::BaseService + class CreateService < BaseService def execute create_issue(params.merge(label_ids: [list.label_id])) end diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 782dbf0db1a..fd4a462c7b2 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -1,6 +1,6 @@ module Boards module Issues - class ListService < Boards::BaseService + class ListService < BaseService def execute issues = IssuesFinder.new(current_user, filter_params).execute issues = without_board_labels(issues) unless list.movable? diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 9a16609a8b5..96554a92a02 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -1,6 +1,6 @@ module Boards module Issues - class MoveService < Boards::BaseService + class MoveService < BaseService def execute(issue) return false unless can?(current_user, :update_issue, issue) return false unless valid_move? diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb index cca4bdd82a2..84f1fc3a4e2 100644 --- a/app/services/boards/list_service.rb +++ b/app/services/boards/list_service.rb @@ -1,5 +1,5 @@ module Boards - class ListService < Boards::BaseService + class ListService < BaseService def execute create_board! if project.boards.empty? project.boards diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index da6f59c0399..abc7aeece39 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class CreateService < Boards::BaseService + class CreateService < BaseService def execute(board) List.transaction do label = project.labels.find(params[:label_id]) diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb index d75c5fd3dc6..f986e05944c 100644 --- a/app/services/boards/lists/destroy_service.rb +++ b/app/services/boards/lists/destroy_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class DestroyService < Boards::BaseService + class DestroyService < BaseService def execute(list) return false unless list.destroyable? diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 686e4e4b336..d8048f1c67e 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class GenerateService < Boards::BaseService + class GenerateService < BaseService def execute(board) return false unless board.lists.movable.empty? diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb index ff739bc7d9c..c579ed4c869 100644 --- a/app/services/boards/lists/list_service.rb +++ b/app/services/boards/lists/list_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class ListService < Boards::BaseService + class ListService < BaseService def execute(board) board.lists end diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb index 7d0730e8332..f2a68865f7b 100644 --- a/app/services/boards/lists/move_service.rb +++ b/app/services/boards/lists/move_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class MoveService < Boards::BaseService + class MoveService < BaseService def execute(list) @board = list.board @old_position = list.position -- cgit v1.2.1 From 7be133aa36c4160c31010dc74af002320e9070b8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 7 Oct 2016 17:06:27 -0300 Subject: Fix typo on Boards::Issues::ListService spec --- spec/services/boards/issues/list_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index d9eb757f986..7c206cf3ce7 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -70,7 +70,7 @@ describe Boards::Issues::ListService, services: true do end end - context 'with list that does not belongs to the board' do + context 'with list that does not belong to the board' do it 'raises an error' do list = create(:list) service = described_class.new(project, user, board_id: board.id, id: list.id) -- cgit v1.2.1 From c5ea58b63007b404a725f28eaae33f5f11d8d925 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 10 Oct 2016 14:36:09 +0100 Subject: Update endpoint path for the frontend --- app/views/projects/boards/index.html.haml | 19 +++++++++++++++++++ app/views/projects/boards/show.html.haml | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index e69de29bb2d..7a1f0ee4428 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -0,0 +1,19 @@ +- @no_container = true +- @content_class = "issue-boards-content" +- page_title "Boards" + +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('boards/boards_bundle.js') + = page_specific_javascript_tag('boards/test_utils/simulate_drag.js') if Rails.env.test? + += render "projects/issues/head" + += render 'shared/issuable/filter', type: :boards + +.boards-list#board-app{ "v-cloak" => true, + "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @boards.first)}", + "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", + "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } + .boards-app-loading.text-center{ "v-if" => "loading" } + = icon("spinner spin") + = render "projects/boards/components/board" diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 5b441b48caf..7bc476264b1 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,7 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", + "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @board)}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } -- cgit v1.2.1 From 2ad531f5e279bcd278600d1f95ff9d4e4988b034 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 11:22:30 -0300 Subject: Add Project::BoardLimitExcedeed error class --- app/models/project.rb | 4 +++- spec/models/project_spec.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 30db7ed50b3..758927edd5c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -16,6 +16,8 @@ class Project < ActiveRecord::Base extend Gitlab::ConfigHelper + class BoardLimitExceeded < StandardError; end + NUMBER_OF_PERMITTED_BOARDS = 1 UNKNOWN_IMPORT_URL = 'http://unknown.git' @@ -1341,6 +1343,6 @@ class Project < ActiveRecord::Base end def validate_board_limit(board) - raise StandardError, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS + raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1b13f1be477..308a00db9cd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -99,7 +99,7 @@ describe Project, models: true do it 'raises an error when attempting to add more than one board to the project' do subject.boards.build - expect { subject.boards.build }.to raise_error(StandardError, 'Number of permitted boards exceeded') + expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded') expect(subject.boards.size).to eq 1 end end -- cgit v1.2.1 From 4e9783e4ddffdb550c362e7bc93f31c904d638e3 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 11 Oct 2016 09:42:34 -0500 Subject: Allow scrolling within grouped pipelines --- app/assets/stylesheets/pages/pipelines.scss | 6 +++++- app/views/projects/commit/_pipeline_status_group.html.haml | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 28e850767b7..7843355f0ab 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -420,7 +420,11 @@ left: auto; right: -197px; top: -9px; - max-height: 245px; + + ul { + max-height: 245px; + overflow: auto; + } a { color: $gl-text-color; diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 6ada719e006..5d0d5ba0262 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -5,8 +5,9 @@ %span.ci-status-text = name %span.badge= subject.size -%ul.dropdown-menu.grouped-pipeline-dropdown - %li.arrow - - subject.each do |status| - %li - = render "projects/#{status.to_partial_path}_pipeline", subject: status +.dropdown-menu.grouped-pipeline-dropdown + .arrow + %ul + - subject.each do |status| + %li + = render "projects/#{status.to_partial_path}_pipeline", subject: status -- cgit v1.2.1 From e2c97567c1a80d1d4857efabead9021897406913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 16:47:02 +0200 Subject: Move some CHANGELOG entries to the 8.13.0 part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spotted while reviewing https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/789/diffs#diff-1. Signed-off-by: Rémy Coutable --- CHANGELOG | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ffbaefdb5f1..693f5179297 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,10 @@ v 8.13.0 (unreleased) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) + - Updating verbiage on git basics to be more intuitive + - Clarify documentation for Runners API (Gennady Trafimenkov) + - Change user & group landing page routing from /u/:username to /:username + - Prevent running GfmAutocomplete setup for each diff note !6569 - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) @@ -110,7 +114,6 @@ v 8.12.4 - Fix failed project deletion when feature visibility set to private. !6688 - Prevent claiming associated model IDs via import. - Set GitLab project exported file permissions to owner only - - Change user & group landing page routing from /u/:username to /:username v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves @@ -130,7 +133,6 @@ v 8.12.2 - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix resolve discussion buttons endpoint path - Refactor remnants of CoffeeScript destructured opts and super !6261 - - Prevent running GfmAutocomplete setup for each diff note !6569 v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST @@ -334,7 +336,6 @@ v 8.11.7 - Avoid conflict with admin labels when importing GitHub labels. !6158 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - Allow the Rails cookie to be used for API authentication. - - Updating verbiage on git basics to be more intuitive v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 @@ -495,7 +496,6 @@ v 8.11.0 - Add pipeline events hook - Bump gitlab_git to speedup DiffCollection iterations - Rewrite description of a blocked user in admin settings. (Elias Werberich) - - Clarify documentation for Runners API (Gennady Trafimenkov) - Make branches sortable without push permission !5462 (winniehell) - Check for Ci::Build artifacts at database level on pipeline partial - Convert image diff background image to CSS (ClemMakesApps) -- cgit v1.2.1 From 53f50edf4dac5133ff4d67a67ba5071e3eb7e78a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 23:29:20 -0300 Subject: Fix board relates specs --- app/controllers/projects/boards_controller.rb | 2 +- spec/features/boards/boards_spec.rb | 16 ++++++++-------- spec/features/boards/keyboard_shortcut_spec.rb | 4 +--- spec/features/boards/new_issue_spec.rb | 4 ++-- spec/fixtures/api/schemas/board.json | 3 +-- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index bbb1b1bf1e1..808affa4f98 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -32,6 +32,6 @@ class Projects::BoardsController < Projects::ApplicationController end def serialize_as_json(resource) - resource.as_json(only: [:id, :name]) + resource.as_json(only: [:id]) end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index f79b6a77696..0fb1608a0a3 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -18,7 +18,7 @@ describe 'Issue Boards', feature: true, js: true do context 'no lists' do before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource expect(page).to have_selector('.board', count: 3) end @@ -61,8 +61,8 @@ describe 'Issue Boards', feature: true, js: true do let!(:done) { create(:label, project: project, name: 'Done') } let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') } - let!(:list1) { create(:list, board: project.board, label: planning, position: 0) } - let!(:list2) { create(:list, board: project.board, label: development, position: 1) } + let!(:list1) { create(:list, board: board, label: planning, position: 0) } + let!(:list2) { create(:list, board: board, label: development, position: 1) } let!(:confidential_issue) { create(:issue, :confidential, project: project, author: user) } let!(:issue1) { create(:issue, project: project, assignee: user) } @@ -76,7 +76,7 @@ describe 'Issue Boards', feature: true, js: true do let!(:issue9) { create(:labeled_issue, project: project, labels: [testing, bug, accepting]) } before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource @@ -170,7 +170,7 @@ describe 'Issue Boards', feature: true, js: true do create(:issue, project: project) end - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource page.within(find('.board', match: :first)) do @@ -604,7 +604,7 @@ describe 'Issue Boards', feature: true, js: true do context 'keyboard shortcuts' do before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end @@ -617,7 +617,7 @@ describe 'Issue Boards', feature: true, js: true do context 'signed out user' do before do logout - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end @@ -633,7 +633,7 @@ describe 'Issue Boards', feature: true, js: true do project.team << [user_guest, :guest] logout login_as(user_guest) - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb index 7ef68e9eb8d..a5fc766401f 100644 --- a/spec/features/boards/keyboard_shortcut_spec.rb +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -6,9 +6,7 @@ describe 'Issue Boards shortcut', feature: true, js: true do let(:project) { create(:empty_project) } before do - project.create_board - project.board.lists.create(list_type: :backlog) - project.board.lists.create(list_type: :done) + create(:board, project: project) login_as :admin diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 1ceb60d5297..67d6da5f39a 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -14,7 +14,7 @@ describe 'Issue Boards new issue', feature: true, js: true do login_as(user) - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource expect(page).to have_selector('.board', count: 3) @@ -70,7 +70,7 @@ describe 'Issue Boards new issue', feature: true, js: true do context 'unauthorized user' do before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end diff --git a/spec/fixtures/api/schemas/board.json b/spec/fixtures/api/schemas/board.json index 6c6e2bee8cb..03aca4a3cc0 100644 --- a/spec/fixtures/api/schemas/board.json +++ b/spec/fixtures/api/schemas/board.json @@ -1,8 +1,7 @@ { "type": "object", "required" : [ - "id", - "name" + "id" ], "properties" : { "id": { "type": "integer" }, -- cgit v1.2.1 From dc49ee6247e041020e96c5d5be292e3f92926c4d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 11 Oct 2016 09:29:47 +0100 Subject: Updated to pass the board ID with the boards root to save conflicts with EE --- app/assets/javascripts/boards/boards_bundle.js.es6 | 3 ++- app/assets/javascripts/boards/services/board_service.js.es6 | 10 +++++----- app/views/projects/boards/index.html.haml | 3 ++- app/views/projects/boards/show.html.haml | 3 ++- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index 91c12570e09..d4f8f4b9420 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -28,12 +28,13 @@ $(() => { state: Store.state, loading: true, endpoint: $boardApp.dataset.endpoint, + boardId: $boardApp.dataset.boardId, disabled: $boardApp.dataset.disabled === 'true', issueLinkBase: $boardApp.dataset.issueLinkBase }, init: Store.create.bind(Store), created () { - gl.boardService = new BoardService(this.endpoint); + gl.boardService = new BoardService(this.endpoint, this.boardId); }, ready () { Store.disabled = this.disabled; diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6 index 2b825c3949f..b9c91cbf31e 100644 --- a/app/assets/javascripts/boards/services/board_service.js.es6 +++ b/app/assets/javascripts/boards/services/board_service.js.es6 @@ -1,15 +1,15 @@ class BoardService { - constructor (root) { + constructor (root, boardId) { Vue.http.options.root = root; - this.lists = Vue.resource(`${root}/lists{/id}`, {}, { + this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, { generate: { method: 'POST', - url: `${root}/lists/generate.json` + url: `${root}/${boardId}/lists/generate.json` } }); - this.issue = Vue.resource(`${root}/issues{/id}`, {}); - this.issues = Vue.resource(`${root}/lists{/id}/issues`, {}); + this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {}); + this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}); Vue.http.interceptors.push((request, next) => { request.headers['X-CSRF-Token'] = $.rails.csrfToken(); diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index 7a1f0ee4428..9252bbc08c1 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -11,7 +11,8 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @boards.first)}", + "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", + "data-board-id" => "#{@boards.first.id}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 7bc476264b1..d6e1ac5f3cf 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,8 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @board)}", + "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", + "data-board-id" => "#{@board.id}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } -- cgit v1.2.1 From 53b3c62e0f889d077ee5b19703c3e04384c85c03 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 11 Oct 2016 16:07:53 +0100 Subject: Fixed JS tests --- spec/javascripts/boards/boards_store_spec.js.es6 | 2 +- spec/javascripts/boards/issue_spec.js.es6 | 2 +- spec/javascripts/boards/list_spec.js.es6 | 2 +- spec/javascripts/boards/mock_data.js.es6 | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6 index 078e4b00023..15c305ce321 100644 --- a/spec/javascripts/boards/boards_store_spec.js.es6 +++ b/spec/javascripts/boards/boards_store_spec.js.es6 @@ -14,7 +14,7 @@ (() => { beforeEach(() => { - gl.boardService = new BoardService('/test/issue-boards/board'); + gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); $.cookie('issue_board_welcome_hidden', 'false'); diff --git a/spec/javascripts/boards/issue_spec.js.es6 b/spec/javascripts/boards/issue_spec.js.es6 index 3569d1b98bd..328c6f82ab5 100644 --- a/spec/javascripts/boards/issue_spec.js.es6 +++ b/spec/javascripts/boards/issue_spec.js.es6 @@ -16,7 +16,7 @@ describe('Issue model', () => { let issue; beforeEach(() => { - gl.boardService = new BoardService('/test/issue-boards/board'); + gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); issue = new ListIssue({ diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6 index 1688b996162..ec78d82e919 100644 --- a/spec/javascripts/boards/list_spec.js.es6 +++ b/spec/javascripts/boards/list_spec.js.es6 @@ -16,7 +16,7 @@ describe('List model', () => { let list; beforeEach(() => { - gl.boardService = new BoardService('/test/issue-boards/board'); + gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); list = new List(listObj); diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6 index f3797ed44d4..052455f2ca6 100644 --- a/spec/javascripts/boards/mock_data.js.es6 +++ b/spec/javascripts/boards/mock_data.js.es6 @@ -26,7 +26,7 @@ const listObjDuplicate = { const BoardsMockData = { 'GET': { - '/test/issue-boards/board/lists{/id}/issues': { + '/test/issue-boards/board/1/lists{/id}/issues': { issues: [{ title: 'Testing', iid: 1, @@ -37,13 +37,13 @@ const BoardsMockData = { } }, 'POST': { - '/test/issue-boards/board/lists{/id}': listObj + '/test/issue-boards/board/1/lists{/id}': listObj }, 'PUT': { - '/test/issue-boards/board/lists{/id}': {} + '/test/issue-boards/board/1/lists{/id}': {} }, 'DELETE': { - '/test/issue-boards/board/lists{/id}': {} + '/test/issue-boards/board/1/lists{/id}': {} } }; -- cgit v1.2.1 From 66af08df281214f59bb21e911949625024b41829 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 11 Oct 2016 16:10:12 +0100 Subject: Fixed default branch dropdown not converting to select2 Closes #23200 --- app/assets/javascripts/project_new.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index a787b11f2a9..3cf41505814 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -4,7 +4,9 @@ this.ProjectNew = (function() { function ProjectNew() { this.toggleSettings = bind(this.toggleSettings, this); - this.$selects = $('.features select'); + this.$selects = $('.features select').filter(function () { + return $(this).data('field'); + }); $('.project-edit-container').on('ajax:before', (function(_this) { return function() { -- cgit v1.2.1 From 8e70cf25641fc023e146ac1632f89203a55ce6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 17:25:57 +0200 Subject: Addresses Robert's feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/api/projects.md | 2 +- doc/api/users.md | 2 +- spec/requests/api/users_spec.rb | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 27436a052da..f96bf7f6d63 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -436,7 +436,7 @@ Parameters: ### Get project events Get the events for the specified project. -Sorted from newest to latest +Sorted from newest to oldest ``` GET /projects/:id/events diff --git a/doc/api/users.md b/doc/api/users.md index 15202010b7b..a52b2d51d78 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -630,7 +630,7 @@ Will return `200 OK` on success, `404 User Not Found` is user cannot be found or ### Get user contribution events -Get the contribution events for the specified user, sorted from newest to latest. +Get the contribution events for the specified user, sorted from newest to oldest. ``` GET /users/:id/events diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 56b2ec6271e..91e54af1209 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -898,7 +898,6 @@ describe API::API, api: true do describe 'GET /user/:id/events' do let(:user) { create(:user) } - let(:lambda_user) { create(:user) } let(:project) { create(:empty_project) } let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) } @@ -909,7 +908,9 @@ describe API::API, api: true do context "as a user than cannot see the event's project" do it 'returns no events' do - get api("/users/#{user.id}/events", lambda_user) + other_user = create(:user) + + get api("/users/#{user.id}/events", other_user) expect(response).to have_http_status(200) expect(json_response).to be_empty -- cgit v1.2.1 From c90483406ecc2717076391737ff57167b5a03393 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 3 Oct 2016 13:11:16 +0100 Subject: refactors tests because of gitlab-test repository changes --- CHANGELOG | 1 - app/models/repository.rb | 9 +++------ spec/controllers/projects/commit_controller_spec.rb | 16 ++++++++++------ spec/helpers/search_helper_spec.rb | 2 +- spec/lib/gitlab/data_builder/push_spec.rb | 8 ++++---- spec/mailers/notify_spec.rb | 2 +- spec/models/commit_spec.rb | 8 ++++---- spec/models/merge_request_diff_spec.rb | 6 +++--- spec/models/merge_request_spec.rb | 4 ++-- spec/models/repository_spec.rb | 15 ++++++++++++++- spec/requests/api/commits_spec.rb | 12 +++++++++--- spec/requests/api/repositories_spec.rb | 8 ++++---- spec/requests/git_http_spec.rb | 4 ++-- spec/services/merge_requests/refresh_service_spec.rb | 4 ++-- spec/services/system_note_service_spec.rb | 8 ++++---- spec/workers/emails_on_push_worker_spec.rb | 4 ++-- 16 files changed, 65 insertions(+), 46 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 841861293c8..0116d3ac0a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -119,7 +119,6 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 -v 8.12.0 (unreleased) - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable diff --git a/app/models/repository.rb b/app/models/repository.rb index 1bf6e58b9db..cf269061ebe 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1013,17 +1013,14 @@ class Repository branch_commit = commit(branch_name) root_ref_commit = commit(root_ref) - if branch_commit && !same_head?(branch_commit.id, root_ref_commit.id) - is_ancestor?(branch_commit.id, root_ref_commit.id) + if branch_commit + same_head = branch_commit.id == root_ref_commit.id + !same_head && is_ancestor?(branch_commit.id, root_ref_commit.id) else nil end end - def same_head?(first_commit_id, second_commit_id) - first_commit_id == second_commit_id - end - def merge_base(first_commit_id, second_commit_id) first_commit_id = commit(first_commit_id).try(:id) || first_commit_id second_commit_id = commit(second_commit_id).try(:id) || second_commit_id diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 7e440193d7b..646b097d74e 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -102,15 +102,16 @@ describe Projects::CommitController do describe "as patch" do include_examples "export as", :patch let(:format) { :patch } + let(:commit2) { project.commit('498214de67004b1da3d820901307bed2a68a8ef6') } it "is a git email patch" do - go(id: commit.id, format: format) + go(id: commit2.id, format: format) - expect(response.body).to start_with("From #{commit.id}") + expect(response.body).to start_with("From #{commit2.id}") end it "contains a git diff" do - go(id: commit.id, format: format) + go(id: commit2.id, format: format) expect(response.body).to match(/^diff --git/) end @@ -135,6 +136,8 @@ describe Projects::CommitController do describe "GET branches" do it "contains branch and tags information" do + commit = project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + get(:branches, namespace_id: project.namespace.to_param, project_id: project.to_param, @@ -254,16 +257,17 @@ describe Projects::CommitController do end let(:existing_path) { '.gitmodules' } + let(:commit2) { project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } context 'when the commit exists' do context 'when the user has access to the project' do context 'when the path exists in the diff' do it 'enables diff notes' do - diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) + diff_for_path(id: commit2.id, old_path: existing_path, new_path: existing_path) expect(assigns(:diff_notes_disabled)).to be_falsey expect(assigns(:comments_target)).to eq(noteable_type: 'Commit', - commit_id: commit.id) + commit_id: commit2.id) end it 'only renders the diffs for the path given' do @@ -272,7 +276,7 @@ describe Projects::CommitController do meth.call(diffs) end - diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) + diff_for_path(id: commit2.id, old_path: existing_path, new_path: existing_path) end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index c5b5aa8c445..64aa41020c9 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -19,7 +19,7 @@ describe SearchHelper do expect(subject.filename).to eq('CHANGELOG') expect(subject.basename).to eq('CHANGELOG') expect(subject.ref).to eq('master') - expect(subject.startline).to eq(186) + expect(subject.startline).to eq(188) expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") end diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb index b73434e8dd7..a379f798a16 100644 --- a/spec/lib/gitlab/data_builder/push_spec.rb +++ b/spec/lib/gitlab/data_builder/push_spec.rb @@ -8,13 +8,13 @@ describe Gitlab::DataBuilder::Push, lib: true do let(:data) { described_class.build_sample(project, user) } it { expect(data).to be_a(Hash) } - it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } - it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(data[:before]).to eq('1b12f15a11fc6e62177bef08f47bc7b5ce50b141') } + it { expect(data[:after]).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0') } it { expect(data[:ref]).to eq('refs/heads/master') } it { expect(data[:commits].size).to eq(3) } it { expect(data[:total_commits_count]).to eq(3) } - it { expect(data[:commits].first[:added]).to eq(['gitlab-grack']) } - it { expect(data[:commits].first[:modified]).to eq(['.gitmodules']) } + it { expect(data[:commits].first[:added]).to eq(['bar/branch-test.txt']) } + it { expect(data[:commits].first[:modified]).to eq([]) } it { expect(data[:commits].first[:removed]).to eq([]) } include_examples 'project hook data with deprecateds' diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 0e4130e8a3a..c8207e58e90 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -628,7 +628,7 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it 'has the correct subject' do - is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/ + is_expected.to have_subject /Re: #{project.name} | #{commit.title} \(#{commit.short_id}\)/ end it 'contains a link to the commit' do diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index d3e6a6648cc..51be3f36135 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -164,10 +164,10 @@ eos let(:data) { commit.hook_attrs(with_changed_files: true) } it { expect(data).to be_a(Hash) } - it { expect(data[:message]).to include('Add submodule from gitlab.com') } - it { expect(data[:timestamp]).to eq('2014-02-27T11:01:38+02:00') } - it { expect(data[:added]).to eq(["gitlab-grack"]) } - it { expect(data[:modified]).to eq([".gitmodules"]) } + it { expect(data[:message]).to include('adds bar folder and branch-test text file to check Repository merged_to_root_ref method') } + it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46+00:00') } + it { expect(data[:added]).to eq(["bar/branch-test.txt"]) } + it { expect(data[:modified]).to eq([]) } it { expect(data[:removed]).to eq([]) } end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..a27c2b28326 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -6,9 +6,9 @@ describe MergeRequestDiff, models: true do it { expect(subject).to be_valid } it { expect(subject).to be_persisted } - it { expect(subject.commits.count).to eq(5) } - it { expect(subject.diffs.count).to eq(8) } - it { expect(subject.head_commit_sha).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(subject.commits.count).to eq(29) } + it { expect(subject.diffs.count).to eq(20) } + it { expect(subject.head_commit_sha).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0') } it { expect(subject.base_commit_sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') } end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 38b6da50168..5884b4cff8c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -489,7 +489,7 @@ describe MergeRequest, models: true do subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) } it 'counts commits that are on target branch but not on source branch' do - expect(subject.diverged_commits_count).to eq(5) + expect(subject.diverged_commits_count).to eq(29) end end @@ -497,7 +497,7 @@ describe MergeRequest, models: true do subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_project, target_project: project) } it 'counts commits that are on target branch but not on source branch' do - expect(subject.diverged_commits_count).to eq(5) + expect(subject.diverged_commits_count).to eq(29) end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 6dd3f91be17..4df78713a82 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -114,17 +114,30 @@ describe Repository, models: true do end describe '#merged_to_root_ref?' do - context 'merged branch' do + context 'merged branch without ff' do subject { repository.merged_to_root_ref?('branch-merged') } it { is_expected.to be_truthy } end + # If the HEAD was ff then it will be false + context 'merged with ff' do + subject { repository.merged_to_root_ref?('improve/awesome') } + + it { is_expected.to be_truthy } + end + context 'not merged branch' do subject { repository.merged_to_root_ref?('not-merged-branch') } it { is_expected.to be_falsey } end + + context 'default branch' do + subject { repository.merged_to_root_ref?('master') } + + it { is_expected.to be_falsey } + end end describe '#can_be_merged?' do diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index aa610557056..66fa0c0c01f 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -53,7 +53,12 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user) - expect(json_response.size).to eq(commits.size - 1) + if commits.size >= 20 + expect(json_response.size).to eq(20) + else + expect(json_response.size).to eq(commits.size - 1) + end + expect(json_response.first["id"]).to eq(commits.second.id) expect(json_response.second["id"]).to eq(commits.third.id) end @@ -447,11 +452,12 @@ describe API::API, api: true do end it 'returns the inline comment' do - post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 7, line_type: 'new' + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path) - expect(json_response['line']).to eq(7) + expect(json_response['line']).to eq(1) expect(json_response['line_type']).to eq('new') end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 80a856a6e90..c4dc2d9006a 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -21,7 +21,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.first['name']).to eq('encoding') + expect(json_response.first['name']).to eq('bar') expect(json_response.first['type']).to eq('tree') expect(json_response.first['mode']).to eq('040000') end @@ -166,9 +166,9 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Array contributor = json_response.first - expect(contributor['email']).to eq('dmitriy.zaporozhets@gmail.com') - expect(contributor['name']).to eq('Dmitriy Zaporozhets') - expect(contributor['commits']).to eq(13) + expect(contributor['email']).to eq('tiagonbotelho@hotmail.com') + expect(contributor['name']).to eq('tiagonbotelho') + expect(contributor['commits']).to eq(1) expect(contributor['additions']).to eq(0) expect(contributor['deletions']).to eq(0) end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index c0c1e62e910..27f0fd22ae6 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -440,8 +440,8 @@ describe 'Git HTTP requests', lib: true do before do # Provide a dummy file in its place allow_any_instance_of(Repository).to receive(:blob_at).and_call_original - allow_any_instance_of(Repository).to receive(:blob_at).with('5937ac0a7beb003549fc5fd26fc247adbce4a52e', 'info/refs') do - Gitlab::Git::Blob.find(project.repository, 'master', '.gitignore') + allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do + Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt') end get "/#{project.path_with_namespace}/blob/master/info/refs" diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 59d3912018a..5b4e4908add 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -118,7 +118,7 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes).to be_empty } it { expect(@merge_request).to be_open } - it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') } + it { expect(@fork_merge_request.notes.last.note).to include('Added 28 commits') } it { expect(@fork_merge_request).to be_open } it { expect(@build_failed_todo).to be_pending } it { expect(@fork_build_failed_todo).to be_pending } @@ -169,7 +169,7 @@ describe MergeRequests::RefreshService, services: true do notes = @fork_merge_request.notes.reorder(:created_at).map(&:note) expect(notes[0]).to include('Restored source branch `master`') - expect(notes[1]).to include('Added 4 commits') + expect(notes[1]).to include('Added 28 commits') expect(@fork_merge_request).to be_open end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 304d4e62396..b4ba28dfe8e 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -54,7 +54,7 @@ describe SystemNoteService, services: true do it 'adds a message line for each commit' do new_commits.each_with_index do |commit, i| # Skip the header - expect(note_lines[i + 1]).to eq "* #{commit.short_id} - #{commit.title}" + expect(HTMLEntities.new.decode(note_lines[i + 1])).to eq "* #{commit.short_id} - #{commit.title}" end end end @@ -81,7 +81,7 @@ describe SystemNoteService, services: true do end it 'includes a commit count' do - expect(summary_line).to end_with " - 2 commits from branch `feature`" + expect(summary_line).to end_with " - 26 commits from branch `feature`" end end @@ -91,7 +91,7 @@ describe SystemNoteService, services: true do end it 'includes a commit count' do - expect(summary_line).to end_with " - 2 commits from branch `feature`" + expect(summary_line).to end_with " - 26 commits from branch `feature`" end end @@ -537,7 +537,7 @@ describe SystemNoteService, services: true do let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) } let(:jira_issue) { ExternalIssue.new("JIRA-1", project)} let(:jira_tracker) { project.jira_service } - let(:commit) { project.commit } + let(:commit) { project.repository.commits('master').find { |commit| commit.id == '5937ac0a7beb003549fc5fd26fc247adbce4a52e' } } context 'in JIRA issue tracker' do before do diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb index 7ca2c29da1c..036d037f3f9 100644 --- a/spec/workers/emails_on_push_worker_spec.rb +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -57,7 +57,7 @@ describe EmailsOnPushWorker do end it "sends a mail with the correct subject" do - expect(email.subject).to include('Change some files') + expect(email.subject).to include('adds bar folder and branch-test text file') end it "mentions force pushing in the body" do @@ -73,7 +73,7 @@ describe EmailsOnPushWorker do before { perform } it "sends a mail with the correct subject" do - expect(email.subject).to include('Change some files') + expect(email.subject).to include('adds bar folder and branch-test text file') end it "does not mention force pushing in the body" do -- cgit v1.2.1 From 8213fc6a744cb5c9fa4c7461dbf1a96f48898662 Mon Sep 17 00:00:00 2001 From: barthc Date: Mon, 10 Oct 2016 16:04:52 +0100 Subject: allow multiple labels commands --- CHANGELOG | 1 + app/services/slash_commands/interpret_service.rb | 21 +++++++++-- .../slash_commands/interpret_service_spec.rb | 43 ++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ba6c3f7c558..850cb16dff1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -91,6 +91,7 @@ v 8.13.0 (unreleased) - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Retouch environments list and deployments list + - Add multiple command support for all label related slash commands !6780 (barthc) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index 1725a30fae5..e4ae3dec8aa 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -122,7 +122,12 @@ module SlashCommands command :label do |labels_param| label_ids = find_label_ids(labels_param) - @updates[:add_label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:add_label_ids] ||= [] + @updates[:add_label_ids] += label_ids + + @updates[:add_label_ids].uniq! + end end desc 'Remove all or specific label(s)' @@ -136,7 +141,12 @@ module SlashCommands if labels_param.present? label_ids = find_label_ids(labels_param) - @updates[:remove_label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:remove_label_ids] ||= [] + @updates[:remove_label_ids] += label_ids + + @updates[:remove_label_ids].uniq! + end else @updates[:label_ids] = [] end @@ -152,7 +162,12 @@ module SlashCommands command :relabel do |labels_param| label_ids = find_label_ids(labels_param) - @updates[:label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:label_ids] ||= [] + @updates[:label_ids] += label_ids + + @updates[:label_ids].uniq! + end end desc 'Add a todo' diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb index ae4d286d250..b57e338b782 100644 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ b/spec/services/slash_commands/interpret_service_spec.rb @@ -86,6 +86,25 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'multiple label command' do + it 'fetches label ids and populates add_label_ids if content contains multiple /label' do + bug # populate the label + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id, bug.id]) + end + end + + shared_examples 'multiple label with same argument' do + it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id]) + end + end + shared_examples 'unlabel command' do it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do issuable.update(label_ids: [inprogress.id]) # populate the label @@ -95,6 +114,15 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'multiple unlabel command' do + it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do + issuable.update(label_ids: [inprogress.id, bug.id]) # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id]) + end + end + shared_examples 'unlabel command with no argument' do it 'populates label_ids: [] if content contains /unlabel with no arguments' do issuable.update(label_ids: [inprogress.id]) # populate the label @@ -285,6 +313,16 @@ describe SlashCommands::InterpretService, services: true do let(:issuable) { merge_request } end + it_behaves_like 'multiple label command' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{bug.title}) } + let(:issuable) { issue } + end + + it_behaves_like 'multiple label with same argument' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{inprogress.title}) } + let(:issuable) { issue } + end + it_behaves_like 'unlabel command' do let(:content) { %(/unlabel ~"#{inprogress.title}") } let(:issuable) { issue } @@ -295,6 +333,11 @@ describe SlashCommands::InterpretService, services: true do let(:issuable) { merge_request } end + it_behaves_like 'multiple unlabel command' do + let(:content) { %(/unlabel ~"#{inprogress.title}" \n/unlabel ~#{bug.title}) } + let(:issuable) { issue } + end + it_behaves_like 'unlabel command with no argument' do let(:content) { %(/unlabel) } let(:issuable) { issue } -- cgit v1.2.1 From 9db841127bef1509d32ae5f3ff2458923c84a19d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 11 Oct 2016 18:22:41 +0200 Subject: Remove pointless `.vagrant_enabled` file --- .vagrant_enabled | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .vagrant_enabled diff --git a/.vagrant_enabled b/.vagrant_enabled deleted file mode 100644 index e69de29bb2d..00000000000 -- cgit v1.2.1 From ea3bbbdef86ae30fcf76baaba11a3fceb6d2aa03 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 11 Oct 2016 13:01:57 -0500 Subject: FIx JS bug with select2 because of missing `data-field` attribute in select box. --- app/views/projects/edit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index d19422c8657..c8f84b96cb7 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -91,7 +91,7 @@ Git Large File Storage = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') .col-md-3 - = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control' + = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' } - if Gitlab.config.registry.enabled .form-group -- cgit v1.2.1 From b0acc0a308848529727e8bcf2a7c6e2bc0f76303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 20:29:42 +0200 Subject: Add 8.12.5, 8.11.9, and 8.10.12 CHANGELOG entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 48eaa328f23..37cbfeff01a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,6 @@ v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - - Improve issue load time performance by avoiding ORDER BY in find_by call - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) @@ -84,7 +83,6 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) - - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 - Reduce queries needed to find users using their SSH keys when pushing commits - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list @@ -102,8 +100,13 @@ v 8.13.0 (unreleased) - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects -v 8.12.5 (unreleased) - - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread +v 8.12.5 + - Switch from request to env in ::API::Helpers. !6615 + - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 + - Improve issue load time performance by avoiding ORDER BY in find_by call. !6724 + - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access v 8.12.4 - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) @@ -330,6 +333,10 @@ v 8.12.0 - Fix non-master branch readme display in tree view - Add UX improvements for merge request version diffs +v 8.11.9 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + v 8.11.8 - Respect the fork_project permission when forking projects - Set a restrictive CORS policy on the API for credentialed requests @@ -555,6 +562,10 @@ v 8.11.0 - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done +v 8.10.12 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + v 8.10.11 - Respect the fork_project permission when forking projects - Set a restrictive CORS policy on the API for credentialed requests -- cgit v1.2.1 From 670b2eb5c05a721f810a5b248612cadde0eaf2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 10:20:35 +0000 Subject: Merge branch 'api-fix-project-group-sharing' into 'security' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API: Share projects only with groups current_user can access Aims to address the issues here: https://gitlab.com/gitlab-org/gitlab-ce/issues/23004 * Projects can be shared with non-existent groups * Projects can be shared with groups that the current user does not have access to read Concerns: The new implementation of the API endpoint allows projects to be shared with a larger range of groups than can be done via the web UI. The form for sharing a project with a group uses the following API endpoint to index the available groups: https://gitlab.com/gitlab-org/gitlab-ce/blob/494269fc92f61098ee6bd635a0426129ce2c5456/lib/api/groups.rb#L17. The groups indexed in the web form will only be those groups that the user is currently a member of. The new implementation allows projects to be shared with any group that the authenticated user has access to view. This widens the range of groups to those that are public and internal. See merge request !2005 Signed-off-by: Rémy Coutable --- app/models/project_group_link.rb | 2 +- lib/api/projects.rb | 6 ++++++ spec/models/project_group_link_spec.rb | 2 +- spec/requests/api/projects_spec.rb | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index 7613cbdea93..db46def11eb 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -10,7 +10,7 @@ class ProjectGroupLink < ActiveRecord::Base belongs_to :group validates :project_id, presence: true - validates :group_id, presence: true + validates :group, presence: true validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" } validates :group_access, presence: true validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c24e8e8bd9b..da16e24d7ea 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -416,6 +416,12 @@ module API required_attributes! [:group_id, :group_access] attrs = attributes_for_keys [:group_id, :group_access, :expires_at] + group = Group.find_by_id(attrs[:group_id]) + + unless group && can?(current_user, :read_group, group) + not_found!('Group') + end + unless user_project.allowed_to_share_with_group? return render_api_error!("The project sharing with group is disabled", 400) end diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 2fa6715fcaf..c5ff1941378 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -11,7 +11,7 @@ describe ProjectGroupLink do it { should validate_presence_of(:project_id) } it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) } - it { should validate_presence_of(:group_id) } + it { should validate_presence_of(:group) } it { should validate_presence_of(:group_access) } end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5f19638b460..19a2c7a2700 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -819,6 +819,20 @@ describe API::API, api: true do expect(response.status).to eq 400 end + it 'returns a 404 error when user cannot read group' do + private_group = create(:group, :private) + + post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER + + expect(response.status).to eq 404 + end + + it 'returns a 404 error when group does not exist' do + post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER + + expect(response.status).to eq 404 + end + it "returns a 409 error when wrong params passed" do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 expect(response.status).to eq 409 -- cgit v1.2.1 From c7865786572a3a2e12ed67a3f8121c0be8c4ba38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 21:26:49 +0200 Subject: Make spec deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/requests/api/users_spec.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 91e54af1209..684c27f4353 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -926,20 +926,18 @@ describe API::API, api: true do it 'returns the "joined" event' do get api("/users/#{user.id}/events", user) - first_event = json_response.first + comment_event = json_response.find { |e| e['action_name'] == 'commented on' } - expect(first_event['action_name']).to eq('commented on') - expect(first_event['project_id'].to_i).to eq(project.id) - expect(first_event['author_username']).to eq(user.username) - expect(first_event['note']['id']).to eq(note.id) - expect(first_event['note']['body']).to eq('What an awesome day!') + expect(comment_event['project_id'].to_i).to eq(project.id) + expect(comment_event['author_username']).to eq(user.username) + expect(comment_event['note']['id']).to eq(note.id) + expect(comment_event['note']['body']).to eq('What an awesome day!') - last_event = json_response.last + joined_event = json_response.find { |e| e['action_name'] == 'joined' } - expect(last_event['action_name']).to eq('joined') - expect(last_event['project_id'].to_i).to eq(project.id) - expect(last_event['author_username']).to eq(user.username) - expect(last_event['author']['name']).to eq(user.name) + expect(joined_event['project_id'].to_i).to eq(project.id) + expect(joined_event['author_username']).to eq(user.username) + expect(joined_event['author']['name']).to eq(user.name) end end end -- cgit v1.2.1 From 32186b4ab89e751d42590d2cbfbcf0c6a131bdca Mon Sep 17 00:00:00 2001 From: Justin DiPierro Date: Thu, 6 Oct 2016 12:42:17 -0400 Subject: Added 'Download' button to snippet view --- CHANGELOG | 1 + app/controllers/snippets_controller.rb | 14 +++++++++++--- app/views/snippets/show.html.haml | 1 + config/routes/snippets.rb | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e2480b66ae4..ad34e7476ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -38,6 +38,7 @@ v 8.13.0 (unreleased) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - Remove redundant mixins (ClemMakesApps) + - Added 'Download' button to the Snippets page (Justin DiPierro) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index d198782138a..dee57e4a388 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,10 +1,10 @@ class SnippetsController < ApplicationController include ToggleAwardEmoji - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download] # Allow read snippet - before_action :authorize_read_snippet!, only: [:show, :raw] + before_action :authorize_read_snippet!, only: [:show, :raw, :download] # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] @@ -12,7 +12,7 @@ class SnippetsController < ApplicationController # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] - skip_before_action :authenticate_user!, only: [:index, :show, :raw] + skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download] layout 'snippets' respond_to :html @@ -75,6 +75,14 @@ class SnippetsController < ApplicationController ) end + def download + send_data( + @snippet.content, + type: 'text/plain; charset=utf-8', + filename: @snippet.sanitized_file_name + ) + end + protected def snippet diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index cd89155c616..27d7a6c5bb6 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -9,6 +9,7 @@ .file-actions = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" + = link_to 'Download', download_snippet_path(@snippet), class: "btn btn-sm" = render 'shared/snippets/blob' = render 'award_emoji/awards_block', awardable: @snippet, inline: true \ No newline at end of file diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb index 1949f215c66..3ca096f31ba 100644 --- a/config/routes/snippets.rb +++ b/config/routes/snippets.rb @@ -1,6 +1,7 @@ resources :snippets, concerns: :awardable do member do get 'raw' + get 'download' end end -- cgit v1.2.1 From ba99f0b45157e841550f87c1d4e6879e108d2d6c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 11 Oct 2016 15:17:56 -0700 Subject: Add CHANGELOG entry for 8.12.6 --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 37cbfeff01a..c8271f55cfb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -100,6 +100,9 @@ v 8.13.0 (unreleased) - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects +v 8.12.6 + - Update mailroom to 0.8.1 in Gemfile.lock !6814 + v 8.12.5 - Switch from request to env in ::API::Helpers. !6615 - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 -- cgit v1.2.1 From f0150ef4d9ac869be21079d21c856a1ede72a3bf Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 20 Sep 2016 15:44:07 -0500 Subject: Add disabled delete button to protected branches --- CHANGELOG | 1 + app/views/projects/branches/_branch.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..8b5d95649bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.13.0 (unreleased) - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created + - Add disabled delete button to protected branches (ClemMakesApps) - Add broadcast messages and alerts below sub-nav - Better empty state for Groups view - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 5217b8bf028..4480b2f22c3 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -30,8 +30,8 @@ = render 'projects/buttons/download', project: @project, ref: branch.name - - if can_remove_branch?(@project, branch.name) - = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do + - if can?(current_user, :push_code, @project) + = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: "btn btn-remove remove-row has-tooltip #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}", title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do = icon("trash-o") - if branch.name != @repository.root_ref -- cgit v1.2.1 From 9c92443ffe90ec58490fb66ebce98e8e19a07c9b Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 12 Oct 2016 10:46:30 +0500 Subject: Remove unused lets from deploy_key spec --- spec/models/deploy_key_spec.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 6a90598a629..93623e8e99b 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -1,9 +1,6 @@ require 'spec_helper' describe DeployKey, models: true do - let(:project) { create(:project) } - let(:deploy_key) { create(:deploy_key, projects: [project]) } - describe "Associations" do it { is_expected.to have_many(:deploy_keys_projects) } it { is_expected.to have_many(:projects) } -- cgit v1.2.1 From 31c41a8fe1ef67347e5bcfdb97ff15a079976945 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 12 Oct 2016 10:50:01 +0500 Subject: Use build instead create record in appearance_spec --- spec/models/appearance_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index c5658bd26e1..0b72a2f979b 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe Appearance, type: :model do - subject { create(:appearance) } + subject { build(:appearance) } it { is_expected.to be_valid } -- cgit v1.2.1 From b9b13ea8016077e186374e4ee45dfc8a59ea5a78 Mon Sep 17 00:00:00 2001 From: Thomas Balthazar Date: Mon, 8 Aug 2016 13:42:24 +0200 Subject: Create a new /templates API namespace The /licenses, /gitignores and /gitlab_ci_ymls endpoints are now also available under a new /templates namespace. Old endpoints will be deprecated when GitLab 9.0.0 is released. --- CHANGELOG | 1 + app/assets/javascripts/api.js | 7 +- doc/api/README.md | 4 +- doc/api/licenses.md | 147 ------- doc/api/templates/gitignores.md | 579 ++++++++++++++++++++++++++++ doc/api/templates/gitlab_ci_ymls.md | 120 ++++++ doc/api/templates/licenses.md | 147 +++++++ lib/api/api.rb | 1 - lib/api/license_templates.rb | 58 --- lib/api/templates.rb | 124 ++++-- spec/requests/api/license_templates_spec.rb | 136 ------- spec/requests/api/templates_spec.rb | 204 ++++++++-- 12 files changed, 1129 insertions(+), 399 deletions(-) delete mode 100644 doc/api/licenses.md create mode 100644 doc/api/templates/gitignores.md create mode 100644 doc/api/templates/gitlab_ci_ymls.md create mode 100644 doc/api/templates/licenses.md delete mode 100644 lib/api/license_templates.rb delete mode 100644 spec/requests/api/license_templates_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 201bab610ba..65d890185fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.13.0 (unreleased) - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Fix Error 500 when viewing old merge requests with bad diff data + - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 599331df3f5..56ec1489f89 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -6,11 +6,10 @@ groupProjectsPath: "/api/:version/groups/:id/projects.json", projectsPath: "/api/:version/projects.json?simple=true", labelsPath: "/:namespace_path/:project_path/labels", - licensePath: "/api/:version/licenses/:key", - gitignorePath: "/api/:version/gitignores/:key", - gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key", + licensePath: "/api/:version/templates/licenses/:key", + gitignorePath: "/api/:version/templates/gitignores/:key", + gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key", issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key", - group: function(group_id, callback) { var url = Api.buildUrl(Api.groupPath) .replace(':id', group_id); diff --git a/doc/api/README.md b/doc/api/README.md index 9e907689c80..00cb003ed8a 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -17,6 +17,8 @@ following locations: - [Commits](commits.md) - [Deployments](deployments.md) - [Deploy Keys](deploy_keys.md) +- [Gitignores templates](templates/gitignores.md) +- [GitLab CI Config templates](templates/gitlab_ci_ymls.md) - [Groups](groups.md) - [Group Access Requests](access_requests.md) - [Group Members](members.md) @@ -25,7 +27,7 @@ following locations: - [Labels](labels.md) - [Merge Requests](merge_requests.md) - [Milestones](milestones.md) -- [Open source license templates](licenses.md) +- [Open source license templates](templates/licenses.md) - [Namespaces](namespaces.md) - [Notes](notes.md) (comments) - [Notification settings](notification_settings.md) diff --git a/doc/api/licenses.md b/doc/api/licenses.md deleted file mode 100644 index ed26d1fb7fb..00000000000 --- a/doc/api/licenses.md +++ /dev/null @@ -1,147 +0,0 @@ -# Licenses - -## List license templates - -Get all license templates. - -``` -GET /licenses -``` - -| Attribute | Type | Required | Description | -| --------- | ------- | -------- | --------------------- | -| `popular` | boolean | no | If passed, returns only popular licenses | - -```bash -curl https://gitlab.example.com/api/v3/licenses?popular=1 -``` - -Example response: - -```json -[ - { - "key": "apache-2.0", - "name": "Apache License 2.0", - "nickname": null, - "featured": true, - "html_url": "http://choosealicense.com/licenses/apache-2.0/", - "source_url": "http://www.apache.org/licenses/LICENSE-2.0.html", - "description": "A permissive license that also provides an express grant of patent rights from contributors to users.", - "conditions": [ - "include-copyright", - "document-changes" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "patent-use", - "private-use" - ], - "limitations": [ - "trademark-use", - "no-liability" - ], - "content": " Apache License\n Version 2.0, January 2004\n [...]" - }, - { - "key": "gpl-3.0", - "name": "GNU General Public License v3.0", - "nickname": "GNU GPLv3", - "featured": true, - "html_url": "http://choosealicense.com/licenses/gpl-3.0/", - "source_url": "http://www.gnu.org/licenses/gpl-3.0.txt", - "description": "The GNU GPL is the most widely used free software license and has a strong copyleft requirement. When distributing derived works, the source code of the work must be made available under the same license.", - "conditions": [ - "include-copyright", - "document-changes", - "disclose-source", - "same-license" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "patent-use", - "private-use" - ], - "limitations": [ - "no-liability" - ], - "content": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n [...]" - }, - { - "key": "mit", - "name": "MIT License", - "nickname": null, - "featured": true, - "html_url": "http://choosealicense.com/licenses/mit/", - "source_url": "http://opensource.org/licenses/MIT", - "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", - "conditions": [ - "include-copyright" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "private-use" - ], - "limitations": [ - "no-liability" - ], - "content": "The MIT License (MIT)\n\nCopyright (c) [year] [fullname]\n [...]" - } -] -``` - -## Single license template - -Get a single license template. You can pass parameters to replace the license -placeholder. - -``` -GET /licenses/:key -``` - -| Attribute | Type | Required | Description | -| ---------- | ------ | -------- | ----------- | -| `key` | string | yes | The key of the license template | -| `project` | string | no | The copyrighted project name | -| `fullname` | string | no | The full-name of the copyright holder | - ->**Note:** -If you omit the `fullname` parameter but authenticate your request, the name of -the authenticated user will be used to replace the copyright holder placeholder. - -```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/licenses/mit?project=My+Cool+Project -``` - -Example response: - -```json -{ - "key": "mit", - "name": "MIT License", - "nickname": null, - "featured": true, - "html_url": "http://choosealicense.com/licenses/mit/", - "source_url": "http://opensource.org/licenses/MIT", - "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", - "conditions": [ - "include-copyright" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "private-use" - ], - "limitations": [ - "no-liability" - ], - "content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]" -} -``` diff --git a/doc/api/templates/gitignores.md b/doc/api/templates/gitignores.md new file mode 100644 index 00000000000..8235be92b12 --- /dev/null +++ b/doc/api/templates/gitignores.md @@ -0,0 +1,579 @@ +# Gitignores + +## List gitignore templates + +Get all gitignore templates. + +``` +GET /templates/gitignores +``` + +```bash +curl https://gitlab.example.com/api/v3/templates/gitignores +``` + +Example response: + +```json +[ + { + "name": "AppEngine" + }, + { + "name": "Laravel" + }, + { + "name": "Elisp" + }, + { + "name": "SketchUp" + }, + { + "name": "Ada" + }, + { + "name": "Ruby" + }, + { + "name": "Kohana" + }, + { + "name": "Nanoc" + }, + { + "name": "Erlang" + }, + { + "name": "OCaml" + }, + { + "name": "Lithium" + }, + { + "name": "Fortran" + }, + { + "name": "Scala" + }, + { + "name": "Node" + }, + { + "name": "Fancy" + }, + { + "name": "Perl" + }, + { + "name": "Zephir" + }, + { + "name": "WordPress" + }, + { + "name": "Symfony" + }, + { + "name": "FuelPHP" + }, + { + "name": "DM" + }, + { + "name": "Sdcc" + }, + { + "name": "Rust" + }, + { + "name": "C" + }, + { + "name": "Umbraco" + }, + { + "name": "Actionscript" + }, + { + "name": "Android" + }, + { + "name": "Grails" + }, + { + "name": "Composer" + }, + { + "name": "ExpressionEngine" + }, + { + "name": "Gcov" + }, + { + "name": "Qt" + }, + { + "name": "Phalcon" + }, + { + "name": "ArchLinuxPackages" + }, + { + "name": "TeX" + }, + { + "name": "SCons" + }, + { + "name": "Lilypond" + }, + { + "name": "CommonLisp" + }, + { + "name": "Rails" + }, + { + "name": "Mercury" + }, + { + "name": "Magento" + }, + { + "name": "ChefCookbook" + }, + { + "name": "GitBook" + }, + { + "name": "C++" + }, + { + "name": "Eagle" + }, + { + "name": "Go" + }, + { + "name": "OpenCart" + }, + { + "name": "Scheme" + }, + { + "name": "Typo3" + }, + { + "name": "SeamGen" + }, + { + "name": "Swift" + }, + { + "name": "Elm" + }, + { + "name": "Unity" + }, + { + "name": "Agda" + }, + { + "name": "CUDA" + }, + { + "name": "VVVV" + }, + { + "name": "Finale" + }, + { + "name": "LemonStand" + }, + { + "name": "Textpattern" + }, + { + "name": "Julia" + }, + { + "name": "Packer" + }, + { + "name": "Scrivener" + }, + { + "name": "Dart" + }, + { + "name": "Plone" + }, + { + "name": "Jekyll" + }, + { + "name": "Xojo" + }, + { + "name": "LabVIEW" + }, + { + "name": "Autotools" + }, + { + "name": "KiCad" + }, + { + "name": "Prestashop" + }, + { + "name": "ROS" + }, + { + "name": "Smalltalk" + }, + { + "name": "GWT" + }, + { + "name": "OracleForms" + }, + { + "name": "SugarCRM" + }, + { + "name": "Nim" + }, + { + "name": "SymphonyCMS" + }, + { + "name": "Maven" + }, + { + "name": "CFWheels" + }, + { + "name": "Python" + }, + { + "name": "ZendFramework" + }, + { + "name": "CakePHP" + }, + { + "name": "Concrete5" + }, + { + "name": "PlayFramework" + }, + { + "name": "Terraform" + }, + { + "name": "Elixir" + }, + { + "name": "CMake" + }, + { + "name": "Joomla" + }, + { + "name": "Coq" + }, + { + "name": "Delphi" + }, + { + "name": "Haskell" + }, + { + "name": "Yii" + }, + { + "name": "Java" + }, + { + "name": "UnrealEngine" + }, + { + "name": "AppceleratorTitanium" + }, + { + "name": "CraftCMS" + }, + { + "name": "ForceDotCom" + }, + { + "name": "ExtJs" + }, + { + "name": "MetaProgrammingSystem" + }, + { + "name": "D" + }, + { + "name": "Objective-C" + }, + { + "name": "RhodesRhomobile" + }, + { + "name": "R" + }, + { + "name": "EPiServer" + }, + { + "name": "Yeoman" + }, + { + "name": "VisualStudio" + }, + { + "name": "Processing" + }, + { + "name": "Leiningen" + }, + { + "name": "Stella" + }, + { + "name": "Opa" + }, + { + "name": "Drupal" + }, + { + "name": "TurboGears2" + }, + { + "name": "Idris" + }, + { + "name": "Jboss" + }, + { + "name": "CodeIgniter" + }, + { + "name": "Qooxdoo" + }, + { + "name": "Waf" + }, + { + "name": "Sass" + }, + { + "name": "Lua" + }, + { + "name": "Clojure" + }, + { + "name": "IGORPro" + }, + { + "name": "Gradle" + }, + { + "name": "Archives" + }, + { + "name": "SynopsysVCS" + }, + { + "name": "Ninja" + }, + { + "name": "Tags" + }, + { + "name": "OSX" + }, + { + "name": "Dreamweaver" + }, + { + "name": "CodeKit" + }, + { + "name": "NotepadPP" + }, + { + "name": "VisualStudioCode" + }, + { + "name": "Mercurial" + }, + { + "name": "BricxCC" + }, + { + "name": "DartEditor" + }, + { + "name": "Eclipse" + }, + { + "name": "Cloud9" + }, + { + "name": "TortoiseGit" + }, + { + "name": "NetBeans" + }, + { + "name": "GPG" + }, + { + "name": "Espresso" + }, + { + "name": "Redcar" + }, + { + "name": "Xcode" + }, + { + "name": "Matlab" + }, + { + "name": "LyX" + }, + { + "name": "SlickEdit" + }, + { + "name": "Dropbox" + }, + { + "name": "CVS" + }, + { + "name": "Calabash" + }, + { + "name": "JDeveloper" + }, + { + "name": "Vagrant" + }, + { + "name": "IPythonNotebook" + }, + { + "name": "TextMate" + }, + { + "name": "Ensime" + }, + { + "name": "WebMethods" + }, + { + "name": "VirtualEnv" + }, + { + "name": "Emacs" + }, + { + "name": "Momentics" + }, + { + "name": "JetBrains" + }, + { + "name": "SublimeText" + }, + { + "name": "Kate" + }, + { + "name": "ModelSim" + }, + { + "name": "Redis" + }, + { + "name": "KDevelop4" + }, + { + "name": "Bazaar" + }, + { + "name": "Linux" + }, + { + "name": "Windows" + }, + { + "name": "XilinxISE" + }, + { + "name": "Lazarus" + }, + { + "name": "EiffelStudio" + }, + { + "name": "Anjuta" + }, + { + "name": "Vim" + }, + { + "name": "Otto" + }, + { + "name": "MicrosoftOffice" + }, + { + "name": "LibreOffice" + }, + { + "name": "SBT" + }, + { + "name": "MonoDevelop" + }, + { + "name": "SVN" + }, + { + "name": "FlexBuilder" + } +] +``` + +## Single gitignore template + +Get a single gitignore template. + +``` +GET /templates/gitignores/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the gitignore template | + +```bash +curl https://gitlab.example.com/api/v3/templates/gitignores/Ruby +``` + +Example response: + +```json +{ + "name": "Ruby", + "content": "*.gem\n*.rbc\n/.config\n/coverage/\n/InstalledFiles\n/pkg/\n/spec/reports/\n/spec/examples.txt\n/test/tmp/\n/test/version_tmp/\n/tmp/\n\n# Used by dotenv library to load environment variables.\n# .env\n\n## Specific to RubyMotion:\n.dat*\n.repl_history\nbuild/\n*.bridgesupport\nbuild-iPhoneOS/\nbuild-iPhoneSimulator/\n\n## Specific to RubyMotion (use of CocoaPods):\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\n# vendor/Pods/\n\n## Documentation cache and generated files:\n/.yardoc/\n/_yardoc/\n/doc/\n/rdoc/\n\n## Environment normalization:\n/.bundle/\n/vendor/bundle\n/lib/bundler/man/\n\n# for a library or gem, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# Gemfile.lock\n# .ruby-version\n# .ruby-gemset\n\n# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:\n.rvmrc\n" +} +``` diff --git a/doc/api/templates/gitlab_ci_ymls.md b/doc/api/templates/gitlab_ci_ymls.md new file mode 100644 index 00000000000..e120016fbe6 --- /dev/null +++ b/doc/api/templates/gitlab_ci_ymls.md @@ -0,0 +1,120 @@ +# GitLab CI YMLs + +## List GitLab CI YML templates + +Get all GitLab CI YML templates. + +``` +GET /templates/gitlab_ci_ymls +``` + +```bash +curl https://gitlab.example.com/api/v3/templates/gitlab_ci_ymls +``` + +Example response: + +```json +[ + { + "name": "C++" + }, + { + "name": "Docker" + }, + { + "name": "Elixir" + }, + { + "name": "LaTeX" + }, + { + "name": "Grails" + }, + { + "name": "Rust" + }, + { + "name": "Nodejs" + }, + { + "name": "Ruby" + }, + { + "name": "Scala" + }, + { + "name": "Maven" + }, + { + "name": "Harp" + }, + { + "name": "Pelican" + }, + { + "name": "Hyde" + }, + { + "name": "Nanoc" + }, + { + "name": "Octopress" + }, + { + "name": "JBake" + }, + { + "name": "HTML" + }, + { + "name": "Hugo" + }, + { + "name": "Metalsmith" + }, + { + "name": "Hexo" + }, + { + "name": "Lektor" + }, + { + "name": "Doxygen" + }, + { + "name": "Brunch" + }, + { + "name": "Jekyll" + }, + { + "name": "Middleman" + } +] +``` + +## Single GitLab CI YML template + +Get a single GitLab CI YML template. + +``` +GET /templates/gitlab_ci_ymls/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the GitLab CI YML template | + +```bash +curl https://gitlab.example.com/api/v3/templates/gitlab_ci_ymls/Ruby +``` + +Example response: + +```json +{ + "name": "Ruby", + "content": "# This file is a template, and might need editing before it works on your project.\n# Official language image. Look for the different tagged releases at:\n# https://hub.docker.com/r/library/ruby/tags/\nimage: \"ruby:2.3\"\n\n# Pick zero or more services to be used on all builds.\n# Only needed when using a docker container to run your tests in.\n# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service\nservices:\n - mysql:latest\n - redis:latest\n - postgres:latest\n\nvariables:\n POSTGRES_DB: database_name\n\n# Cache gems in between builds\ncache:\n paths:\n - vendor/ruby\n\n# This is a basic example for a gem or script which doesn't use\n# services such as redis or postgres\nbefore_script:\n - ruby -v # Print out ruby version for debugging\n # Uncomment next line if your rails app needs a JS runtime:\n # - apt-get update -q && apt-get install nodejs -yqq\n - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image\n - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby\n\n# Optional - Delete if not using `rubocop`\nrubocop:\n script:\n - rubocop\n\nrspec:\n script:\n - rspec spec\n\nrails:\n variables:\n DATABASE_URL: \"postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB\"\n script:\n - bundle exec rake db:migrate\n - bundle exec rake db:seed\n - bundle exec rake test\n" +} +``` diff --git a/doc/api/templates/licenses.md b/doc/api/templates/licenses.md new file mode 100644 index 00000000000..ae7218cf1bd --- /dev/null +++ b/doc/api/templates/licenses.md @@ -0,0 +1,147 @@ +# Licenses + +## List license templates + +Get all license templates. + +``` +GET /templates/licenses +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `popular` | boolean | no | If passed, returns only popular licenses | + +```bash +curl https://gitlab.example.com/api/v3/templates/licenses?popular=1 +``` + +Example response: + +```json +[ + { + "key": "apache-2.0", + "name": "Apache License 2.0", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/apache-2.0/", + "source_url": "http://www.apache.org/licenses/LICENSE-2.0.html", + "description": "A permissive license that also provides an express grant of patent rights from contributors to users.", + "conditions": [ + "include-copyright", + "document-changes" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "patent-use", + "private-use" + ], + "limitations": [ + "trademark-use", + "no-liability" + ], + "content": " Apache License\n Version 2.0, January 2004\n [...]" + }, + { + "key": "gpl-3.0", + "name": "GNU General Public License v3.0", + "nickname": "GNU GPLv3", + "featured": true, + "html_url": "http://choosealicense.com/licenses/gpl-3.0/", + "source_url": "http://www.gnu.org/licenses/gpl-3.0.txt", + "description": "The GNU GPL is the most widely used free software license and has a strong copyleft requirement. When distributing derived works, the source code of the work must be made available under the same license.", + "conditions": [ + "include-copyright", + "document-changes", + "disclose-source", + "same-license" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "patent-use", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n [...]" + }, + { + "key": "mit", + "name": "MIT License", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/mit/", + "source_url": "http://opensource.org/licenses/MIT", + "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", + "conditions": [ + "include-copyright" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": "The MIT License (MIT)\n\nCopyright (c) [year] [fullname]\n [...]" + } +] +``` + +## Single license template + +Get a single license template. You can pass parameters to replace the license +placeholder. + +``` +GET /templates/licenses/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the license template | +| `project` | string | no | The copyrighted project name | +| `fullname` | string | no | The full-name of the copyright holder | + +>**Note:** +If you omit the `fullname` parameter but authenticate your request, the name of +the authenticated user will be used to replace the copyright holder placeholder. + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/templates/licenses/mit?project=My+Cool+Project +``` + +Example response: + +```json +{ + "key": "mit", + "name": "MIT License", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/mit/", + "source_url": "http://opensource.org/licenses/MIT", + "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", + "conditions": [ + "include-copyright" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]" +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 99722a0a65c..c64d444842d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -46,7 +46,6 @@ module API mount ::API::Boards mount ::API::Keys mount ::API::Labels - mount ::API::LicenseTemplates mount ::API::Lint mount ::API::Members mount ::API::MergeRequests diff --git a/lib/api/license_templates.rb b/lib/api/license_templates.rb deleted file mode 100644 index d0552299ed0..00000000000 --- a/lib/api/license_templates.rb +++ /dev/null @@ -1,58 +0,0 @@ -module API - # License Templates API - class LicenseTemplates < Grape::API - PROJECT_TEMPLATE_REGEX = - /[\<\{\[] - (project|description| - one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here - [\>\}\]]/xi.freeze - YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze - FULLNAME_TEMPLATE_REGEX = - /[\<\{\[] - (fullname|name\sof\s(author|copyright\sowner)) - [\>\}\]]/xi.freeze - - # Get the list of the available license templates - # - # Parameters: - # popular - Filter licenses to only the popular ones - # - # Example Request: - # GET /licenses - # GET /licenses?popular=1 - get 'licenses' do - options = { - featured: params[:popular].present? ? true : nil - } - present Licensee::License.all(options), with: Entities::RepoLicense - end - - # Get text for specific license - # - # Parameters: - # key (required) - The key of a license - # project - Copyrighted project name - # fullname - Full name of copyright holder - # - # Example Request: - # GET /licenses/mit - # - get 'licenses/:key', requirements: { key: /[\w\.-]+/ } do - required_attributes! [:key] - - not_found!('License') unless Licensee::License.find(params[:key]) - - # We create a fresh Licensee::License object since we'll modify its - # content in place below. - license = Licensee::License.new(params[:key]) - - license.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) - license.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present? - - fullname = params[:fullname].presence || current_user.try(:name) - license.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname - - present license, with: Entities::RepoLicense - end - end -end diff --git a/lib/api/templates.rb b/lib/api/templates.rb index b9e718147e1..8a53d9c0095 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -1,39 +1,115 @@ module API class Templates < Grape::API GLOBAL_TEMPLATE_TYPES = { - gitignores: Gitlab::Template::GitignoreTemplate, - gitlab_ci_ymls: Gitlab::Template::GitlabCiYmlTemplate + gitignores: { + klass: Gitlab::Template::GitignoreTemplate, + gitlab_version: 8.8 + }, + gitlab_ci_ymls: { + klass: Gitlab::Template::GitlabCiYmlTemplate, + gitlab_version: 8.9 + } }.freeze + PROJECT_TEMPLATE_REGEX = + /[\<\{\[] + (project|description| + one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here + [\>\}\]]/xi.freeze + YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze + FULLNAME_TEMPLATE_REGEX = + /[\<\{\[] + (fullname|name\sof\s(author|copyright\sowner)) + [\>\}\]]/xi.freeze + DEPRECATION_MESSAGE = ' This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze helpers do + def parsed_license_template + # We create a fresh Licensee::License object since we'll modify its + # content in place below. + template = Licensee::License.new(params[:name]) + + template.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) + template.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present? + + fullname = params[:fullname].presence || current_user.try(:name) + template.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname + template + end + def render_response(template_type, template) not_found!(template_type.to_s.singularize) unless template present template, with: Entities::Template end end - GLOBAL_TEMPLATE_TYPES.each do |template_type, klass| - # Get the list of the available template - # - # Example Request: - # GET /gitignores - # GET /gitlab_ci_ymls - get template_type.to_s do - present klass.all, with: Entities::TemplatesList - end - - # Get the text for a specific template present in local filesystem - # - # Parameters: - # name (required) - The name of a template - # - # Example Request: - # GET /gitignores/Elixir - # GET /gitlab_ci_ymls/Ruby - get "#{template_type}/:name" do - required_attributes! [:name] - new_template = klass.find(params[:name]) - render_response(template_type, new_template) + { "licenses" => :deprecated, "templates/licenses" => :ok }.each do |route, status| + desc 'Get the list of the available license template' do + detailed_desc = 'This feature was introduced in GitLab 8.7.' + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::RepoLicense + end + params do + optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses' + end + get route do + options = { + featured: declared(params).popular.present? ? true : nil + } + present Licensee::License.all(options), with: Entities::RepoLicense + end + end + + { "licenses/:name" => :deprecated, "templates/licenses/:name" => :ok }.each do |route, status| + desc 'Get the text for a specific license' do + detailed_desc = 'This feature was introduced in GitLab 8.7.' + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::RepoLicense + end + params do + requires :name, type: String, desc: 'The name of the template' + end + get route, requirements: { name: /[\w\.-]+/ } do + not_found!('License') unless Licensee::License.find(declared(params).name) + + template = parsed_license_template + + present template, with: Entities::RepoLicense + end + end + + GLOBAL_TEMPLATE_TYPES.each do |template_type, properties| + klass = properties[:klass] + gitlab_version = properties[:gitlab_version] + + { template_type => :deprecated, "templates/#{template_type}" => :ok }.each do |route, status| + desc 'Get the list of the available template' do + detailed_desc = "This feature was introduced in GitLab #{gitlab_version}." + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::TemplatesList + end + get route do + present klass.all, with: Entities::TemplatesList + end + end + + { "#{template_type}/:name" => :deprecated, "templates/#{template_type}/:name" => :ok }.each do |route, status| + desc 'Get the text for a specific template present in local filesystem' do + detailed_desc = "This feature was introduced in GitLab #{gitlab_version}." + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::Template + end + params do + requires :name, type: String, desc: 'The name of the template' + end + get route do + new_template = klass.find(declared(params).name) + + render_response(template_type, new_template) + end end end end diff --git a/spec/requests/api/license_templates_spec.rb b/spec/requests/api/license_templates_spec.rb deleted file mode 100644 index 9a1894d63a2..00000000000 --- a/spec/requests/api/license_templates_spec.rb +++ /dev/null @@ -1,136 +0,0 @@ -require 'spec_helper' - -describe API::API, api: true do - include ApiHelpers - - describe 'Entity' do - before { get api('/licenses/mit') } - - it { expect(json_response['key']).to eq('mit') } - it { expect(json_response['name']).to eq('MIT License') } - it { expect(json_response['nickname']).to be_nil } - it { expect(json_response['popular']).to be true } - it { expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') } - it { expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') } - it { expect(json_response['description']).to include('A permissive license that is short and to the point.') } - it { expect(json_response['conditions']).to eq(%w[include-copyright]) } - it { expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) } - it { expect(json_response['limitations']).to eq(%w[no-liability]) } - it { expect(json_response['content']).to include('The MIT License (MIT)') } - end - - describe 'GET /licenses' do - it 'returns a list of available license templates' do - get api('/licenses') - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(15) - expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') - end - - describe 'the popular parameter' do - context 'with popular=1' do - it 'returns a list of available popular license templates' do - get api('/licenses?popular=1') - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(3) - expect(json_response.map { |l| l['key'] }).to include('apache-2.0') - end - end - end - end - - describe 'GET /licenses/:key' do - context 'with :project and :fullname given' do - before do - get api("/licenses/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}") - end - - context 'for the mit license' do - let(:license_type) { 'mit' } - - it 'returns the license text' do - expect(json_response['content']).to include('The MIT License (MIT)') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton") - end - end - - context 'for the agpl-3.0 license' do - let(:license_type) { 'agpl-3.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the gpl-3.0 license' do - let(:license_type) { 'gpl-3.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the gpl-2.0 license' do - let(:license_type) { 'gpl-2.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the apache-2.0 license' do - let(:license_type) { 'apache-2.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('Apache License') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include("Copyright #{Time.now.year} Anton") - end - end - - context 'for an uknown license' do - let(:license_type) { 'muth-over9000' } - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - - context 'with no :fullname given' do - context 'with an authenticated user' do - let(:user) { create(:user) } - - it 'replaces the copyright owner placeholder with the name of the current user' do - get api('/licenses/mit', user) - - expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") - end - end - end - end -end diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb index 5bd5b861792..d32ba60fc4c 100644 --- a/spec/requests/api/templates_spec.rb +++ b/spec/requests/api/templates_spec.rb @@ -3,53 +3,201 @@ require 'spec_helper' describe API::Templates, api: true do include ApiHelpers - context 'global templates' do - describe 'the Template Entity' do - before { get api('/gitignores/Ruby') } + shared_examples_for 'the Template Entity' do |path| + before { get api(path) } - it { expect(json_response['name']).to eq('Ruby') } - it { expect(json_response['content']).to include('*.gem') } + it { expect(json_response['name']).to eq('Ruby') } + it { expect(json_response['content']).to include('*.gem') } + end + + shared_examples_for 'the TemplateList Entity' do |path| + before { get api(path) } + + it { expect(json_response.first['name']).not_to be_nil } + it { expect(json_response.first['content']).to be_nil } + end + + shared_examples_for 'requesting gitignores' do |path| + it 'returns a list of available gitignore templates' do + get api(path) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to be > 15 end + end - describe 'the TemplateList Entity' do - before { get api('/gitignores') } + shared_examples_for 'requesting gitlab-ci-ymls' do |path| + it 'returns a list of available gitlab_ci_ymls' do + get api(path) - it { expect(json_response.first['name']).not_to be_nil } - it { expect(json_response.first['content']).to be_nil } + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).not_to be_nil end + end + + shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path| + it 'adds a disclaimer on the top' do + get api(path) + + expect(response).to have_http_status(200) + expect(json_response['content']).to start_with("# This file is a template,") + end + end + + shared_examples_for 'the License Template Entity' do |path| + before { get api(path) } + + it 'returns a license template' do + expect(json_response['key']).to eq('mit') + expect(json_response['name']).to eq('MIT License') + expect(json_response['nickname']).to be_nil + expect(json_response['popular']).to be true + expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') + expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') + expect(json_response['description']).to include('A permissive license that is short and to the point.') + expect(json_response['conditions']).to eq(%w[include-copyright]) + expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) + expect(json_response['limitations']).to eq(%w[no-liability]) + expect(json_response['content']).to include('The MIT License (MIT)') + end + end - context 'requesting gitignores' do - describe 'GET /gitignores' do - it 'returns a list of available gitignore templates' do - get api('/gitignores') + shared_examples_for 'GET licenses' do |path| + it 'returns a list of available license templates' do + get api(path) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(15) + expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') + end + + describe 'the popular parameter' do + context 'with popular=1' do + it 'returns a list of available popular license templates' do + get api("#{path}?popular=1") + + expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.size).to be > 15 + expect(json_response.size).to eq(3) + expect(json_response.map { |l| l['key'] }).to include('apache-2.0') end end end + end - context 'requesting gitlab-ci-ymls' do - describe 'GET /gitlab_ci_ymls' do - it 'returns a list of available gitlab_ci_ymls' do - get api('/gitlab_ci_ymls') + shared_examples_for 'GET licenses/:name' do |path| + context 'with :project and :fullname given' do + before do + get api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}") + end - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).not_to be_nil + context 'for the mit license' do + let(:license_type) { 'mit' } + + it 'returns the license text' do + expect(json_response['content']).to include('The MIT License (MIT)') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton") + end + end + + context 'for the agpl-3.0 license' do + let(:license_type) { 'agpl-3.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") + end + end + + context 'for the gpl-3.0 license' do + let(:license_type) { 'gpl-3.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") + end + end + + context 'for the gpl-2.0 license' do + let(:license_type) { 'gpl-2.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") + end + end + + context 'for the apache-2.0 license' do + let(:license_type) { 'apache-2.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('Apache License') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include("Copyright #{Time.now.year} Anton") + end + end + + context 'for an uknown license' do + let(:license_type) { 'muth-over9000' } + + it 'returns a 404' do + expect(response).to have_http_status(404) end end end - describe 'GET /gitlab_ci_ymls/Ruby' do - it 'adds a disclaimer on the top' do - get api('/gitlab_ci_ymls/Ruby') + context 'with no :fullname given' do + context 'with an authenticated user' do + let(:user) { create(:user) } + + it 'replaces the copyright owner placeholder with the name of the current user' do + get api('/templates/licenses/mit', user) - expect(response).to have_http_status(200) - expect(json_response['name']).not_to be_nil - expect(json_response['content']).to start_with("# This file is a template,") + expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") + end end end end + + describe 'with /templates namespace' do + it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby' + it_behaves_like 'the TemplateList Entity', '/templates/gitignores' + it_behaves_like 'requesting gitignores', '/templates/gitignores' + it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls' + it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby' + it_behaves_like 'the License Template Entity', '/templates/licenses/mit' + it_behaves_like 'GET licenses', '/templates/licenses' + it_behaves_like 'GET licenses/:name', '/templates/licenses' + end + + describe 'without /templates namespace' do + it_behaves_like 'the Template Entity', '/gitignores/Ruby' + it_behaves_like 'the TemplateList Entity', '/gitignores' + it_behaves_like 'requesting gitignores', '/gitignores' + it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls' + it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby' + it_behaves_like 'the License Template Entity', '/licenses/mit' + it_behaves_like 'GET licenses', '/licenses' + it_behaves_like 'GET licenses/:name', '/licenses' + end end -- cgit v1.2.1 From df404f551a29258fa5b97c0e7abe144fd7a0fb31 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 12:07:58 +0200 Subject: Schedule async pipeline success worker after commit --- app/models/ci/pipeline.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d3a6e0a11f0..957f6755b2e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -3,6 +3,7 @@ module Ci extend Ci::Model include HasStatus include Importable + include AfterCommitQueue self.table_name = 'ci_commits' @@ -71,7 +72,7 @@ module Ci end after_transition [:created, :pending, :running] => :success do |pipeline| - PipelineSuccessWorker.perform_async(pipeline.id) + pipeline.run_after_commit { PipelineSuccessWorker.perform_async(id) } end after_transition do |pipeline, transition| -- cgit v1.2.1 From 9eedd6f9c7baa5693a24c33c29909bfc7dac1b21 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 12:23:52 +0200 Subject: Improve desc for pipeline integration spec in MWBS --- .../merge_when_build_succeeds_service_spec.rb | 36 ++++++++++++---------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 01cdc172136..b80cfd8f450 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -98,7 +98,26 @@ describe MergeRequests::MergeWhenBuildSucceedsService do service.trigger(unrelated_pipeline) end end + end + + describe "#cancel" do + before do + service.cancel(mr_merge_if_green_enabled) + end + + it "resets all the merge_when_build_succeeds params" do + expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey + expect(mr_merge_if_green_enabled.merge_params).to eq({}) + expect(mr_merge_if_green_enabled.merge_user).to be nil + end + + it 'Posts a system note' do + note = mr_merge_if_green_enabled.notes.last + expect(note.note).to include 'Canceled the automatic merge' + end + end + describe 'pipeline integration' do context 'when there are multiple stages in the pipeline' do let(:ref) { mr_merge_if_green_enabled.source_branch } let(:sha) { project.commit(ref).id } @@ -139,21 +158,4 @@ describe MergeRequests::MergeWhenBuildSucceedsService do end end end - - describe "#cancel" do - before do - service.cancel(mr_merge_if_green_enabled) - end - - it "resets all the merge_when_build_succeeds params" do - expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey - expect(mr_merge_if_green_enabled.merge_params).to eq({}) - expect(mr_merge_if_green_enabled.merge_user).to be nil - end - - it 'Posts a system note' do - note = mr_merge_if_green_enabled.notes.last - expect(note.note).to include 'Canceled the automatic merge' - end - end end -- cgit v1.2.1 From 2b37f040b612303b714ec8cee0b482427e7c2b3c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 11 Oct 2016 12:58:56 +0200 Subject: Ignore deployment for statistics in Cycle Analytics, except in staging and production stages Also, updated specs and docs. --- CHANGELOG | 1 + app/models/cycle_analytics.rb | 14 +++-- doc/user/project/cycle_analytics.md | 22 ++++---- spec/models/cycle_analytics/code_spec.rb | 88 +++++++++++++++++++++--------- spec/models/cycle_analytics/issue_spec.rb | 2 - spec/models/cycle_analytics/plan_spec.rb | 2 - spec/models/cycle_analytics/review_spec.rb | 4 +- spec/models/cycle_analytics/test_spec.rb | 5 -- 8 files changed, 83 insertions(+), 55 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ba6c3f7c558..576b755d06b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -75,6 +75,7 @@ v 8.13.0 (unreleased) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile + - Ignore deployment for statistics in Cycle Analytics, except in staging and production stages - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - Fix deploy status responsiveness error !6633 - Make searching for commits case insensitive diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb index be295487fd2..0f3fd995681 100644 --- a/app/models/cycle_analytics.rb +++ b/app/models/cycle_analytics.rb @@ -2,6 +2,8 @@ class CycleAnalytics include Gitlab::Database::Median include Gitlab::Database::DateTime + DEPLOYED_CHECK_METRICS = %i[production staging] + def initialize(project, from:) @project = project @from = from @@ -66,7 +68,7 @@ class CycleAnalytics # cycle analytics stage. interval_query = Arel::Nodes::As.new( cte_table, - subtract_datetimes(base_query, end_time_attrs, start_time_attrs, name.to_s)) + subtract_datetimes(base_query_for(name), end_time_attrs, start_time_attrs, name.to_s)) median_datetime(cte_table, interval_query, name) end @@ -75,7 +77,7 @@ class CycleAnalytics # closes the given issue) with issue and merge request metrics included. The metrics # are loaded with an inner join, so issues / merge requests without metrics are # automatically excluded. - def base_query + def base_query_for(name) arel_table = MergeRequestsClosingIssues.arel_table # Load issues @@ -91,7 +93,11 @@ class CycleAnalytics join(MergeRequest::Metrics.arel_table). on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id])) - # Limit to merge requests that have been deployed to production after `@from` - query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) + if DEPLOYED_CHECK_METRICS.include?(name) + # Limit to merge requests that have been deployed to production after `@from` + query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) + end + + query end end diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index c16058165d7..1892ccabb70 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -3,8 +3,8 @@ > [Introduced][ce-5986] in GitLab 8.12. > > **Note:** -This the first iteration of Cycle Analytics, you can follow the following issue -to track the changes that are coming to this feature: [#20975][ce-20975]. +There are more changes coming to Cycle Analytics, you can follow the following +issue to track the changes to this feature: [#20975][ce-20975]. Cycle Analytics measures the time it takes to go from an [idea to production] for each project you have. This is achieved by not only indicating the total time it @@ -48,13 +48,12 @@ You can see that there are seven stages in total: ## How the data is measured -Cycle Analytics records cycle time so only data on the issues that have been -deployed to production are measured. In case you just started a new project and -you have not pushed anything to production, then you will not be able to -properly see the Cycle Analytics of your project. +Cycle Analytics records cycle time and data based on the project issues with the +exception of the staging and production stages, where only data deployed to +production are measured. Specifically, if your CI is not set up and you have not defined a `production` -[environment], then you will not have any data. +[environment], then you will not have any data for those stages. Below you can see in more detail what the various stages of Cycle Analytics mean. @@ -76,9 +75,8 @@ Here's a little explanation of how this works behind the scenes: `` pair, the merge request has the [issue closing pattern] for the corresponding issue. All other issues and merge requests are **not** considered. -1. Then the pairs are filtered out. Any merge request - that has **not** been deployed to production in the last XX days (specified - by the UI - default is 90 days) prohibits these pairs from being considered. +1. Then the pairs are filtered out by last XX days (specified + by the UI - default is 90 days). So it prohibits these pairs from being considered. 1. For the remaining `` pairs, we check the information that we need for the stages, like issue creation date, merge request merge time, etc. @@ -86,8 +84,8 @@ Here's a little explanation of how this works behind the scenes: To sum up, anything that doesn't follow the [GitLab flow] won't be tracked at all. So, if a merge request doesn't close an issue or an issue is not labeled with a label present in the Issue Board or assigned a milestone or a project has no -`production` environment, the Cycle Analytics dashboard won't present any data -at all. +`production` environment (for staging and production stages), the Cycle Analytics +dashboard won't present any data at all. ## Example workflow diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index b9381e33914..7691d690db0 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -8,35 +8,69 @@ describe 'CycleAnalytics#code', feature: true do let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } - generate_cycle_analytics_spec( - phase: :code, - data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } }, - start_time_conditions: [["issue mentioned in a commit", - -> (context, data) do - context.create_commit_referencing_issue(data[:issue]) - end]], - end_time_conditions: [["merge request that closes issue is created", - -> (context, data) do - context.create_merge_request_closing_issue(data[:issue]) - end]], - post_fn: -> (context, data) do - context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master - end) - - context "when a regular merge request (that doesn't close the issue) is created" do - it "returns nil" do - 5.times do - issue = create(:issue, project: project) - - create_commit_referencing_issue(issue) - create_merge_request_closing_issue(issue, message: "Closes nothing") - - merge_merge_requests_closing_issue(issue) - deploy_master + context 'with deployment' do + generate_cycle_analytics_spec( + phase: :code, + data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } }, + start_time_conditions: [["issue mentioned in a commit", + -> (context, data) do + context.create_commit_referencing_issue(data[:issue]) + end]], + end_time_conditions: [["merge request that closes issue is created", + -> (context, data) do + context.create_merge_request_closing_issue(data[:issue]) + end]], + post_fn: -> (context, data) do + context.merge_merge_requests_closing_issue(data[:issue]) + context.deploy_master + end) + + context "when a regular merge request (that doesn't close the issue) is created" do + it "returns nil" do + 5.times do + issue = create(:issue, project: project) + + create_commit_referencing_issue(issue) + create_merge_request_closing_issue(issue, message: "Closes nothing") + + merge_merge_requests_closing_issue(issue) + deploy_master + end + + expect(subject.code).to be_nil end + end + end - expect(subject.code).to be_nil + context 'without deployment' do + generate_cycle_analytics_spec( + phase: :code, + data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } }, + start_time_conditions: [["issue mentioned in a commit", + -> (context, data) do + context.create_commit_referencing_issue(data[:issue]) + end]], + end_time_conditions: [["merge request that closes issue is created", + -> (context, data) do + context.create_merge_request_closing_issue(data[:issue]) + end]], + post_fn: -> (context, data) do + context.merge_merge_requests_closing_issue(data[:issue]) + end) + + context "when a regular merge request (that doesn't close the issue) is created" do + it "returns nil" do + 5.times do + issue = create(:issue, project: project) + + create_commit_referencing_issue(issue) + create_merge_request_closing_issue(issue, message: "Closes nothing") + + merge_merge_requests_closing_issue(issue) + end + + expect(subject.code).to be_nil + end end end end diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index e9cc71254ab..f649b44d367 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -28,7 +28,6 @@ describe 'CycleAnalytics#issue', models: true do if data[:issue].persisted? context.create_merge_request_closing_issue(data[:issue].reload) context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master end end) @@ -41,7 +40,6 @@ describe 'CycleAnalytics#issue', models: true do create_merge_request_closing_issue(issue) merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.issue).to be_nil diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index 5b8c96dc992..2cdefbeef21 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -31,7 +31,6 @@ describe 'CycleAnalytics#plan', feature: true do post_fn: -> (context, data) do context.create_merge_request_closing_issue(data[:issue], source_branch: data[:branch_name]) context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master end) context "when a regular label (instead of a list label) is added to the issue" do @@ -44,7 +43,6 @@ describe 'CycleAnalytics#plan', feature: true do create_merge_request_closing_issue(issue, source_branch: branch_name) merge_merge_requests_closing_issue(issue) - deploy_master expect(subject.issue).to be_nil end diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index b6e26d8f261..0ed080a42b1 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -19,14 +19,12 @@ describe 'CycleAnalytics#review', feature: true do -> (context, data) do context.merge_merge_requests_closing_issue(data[:issue]) end]], - post_fn: -> (context, data) { context.deploy_master }) + post_fn: nil) context "when a regular merge request (that doesn't close the issue) is created and merged" do it "returns nil" do 5.times do MergeRequests::MergeService.new(project, user).execute(create(:merge_request)) - - deploy_master end expect(subject.review).to be_nil diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index 89ace0b2742..c454e3e0701 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -20,7 +20,6 @@ describe 'CycleAnalytics#test', feature: true do end_time_conditions: [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]], post_fn: -> (context, data) do context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master end) context "when the pipeline is for a regular merge request (that doesn't close an issue)" do @@ -34,7 +33,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.succeed! merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.test).to be_nil @@ -49,7 +47,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.run! pipeline.succeed! - deploy_master end expect(subject.test).to be_nil @@ -67,7 +64,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.drop! merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.test).to be_nil @@ -85,7 +81,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.cancel! merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.test).to be_nil -- cgit v1.2.1 From f76f569bb87803b14c763427fc2875cca7dda892 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 12 Oct 2016 12:50:47 +0100 Subject: Moved data attribute values into helper method --- app/helpers/boards_helper.rb | 12 ++++++++++++ app/views/projects/boards/index.html.haml | 6 +----- app/views/projects/boards/show.html.haml | 6 +----- 3 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 app/helpers/boards_helper.rb diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb new file mode 100644 index 00000000000..b7247ffa8b2 --- /dev/null +++ b/app/helpers/boards_helper.rb @@ -0,0 +1,12 @@ +module BoardsHelper + def board_data + board = @board || @boards.first + + { + endpoint: namespace_project_boards_path(@project.namespace, @project), + board_id: board.id, + disabled: !can?(current_user, :admin_list, @project), + issue_link_base: namespace_project_issues_path(@project.namespace, @project) + } + end +end diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index 9252bbc08c1..885f8e34b55 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -10,11 +10,7 @@ = render 'shared/issuable/filter', type: :boards -.boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", - "data-board-id" => "#{@boards.first.id}", - "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", - "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } +.boards-list#board-app{ "v-cloak" => true, data: board_data } .boards-app-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") = render "projects/boards/components/board" diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index d6e1ac5f3cf..885f8e34b55 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -10,11 +10,7 @@ = render 'shared/issuable/filter', type: :boards -.boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", - "data-board-id" => "#{@board.id}", - "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", - "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } +.boards-list#board-app{ "v-cloak" => true, data: board_data } .boards-app-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") = render "projects/boards/components/board" -- cgit v1.2.1 From 42ba19f339a63a1e49a40fd4df9c75470ec71f25 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 12 Oct 2016 14:30:49 +0200 Subject: fixed newline --- spec/models/cycle_analytics/test_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index c454e3e0701..02ddfeed9c1 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -46,7 +46,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.run! pipeline.succeed! - end expect(subject.test).to be_nil -- cgit v1.2.1 From 1102659262749ca184af38aa9ad6fba8b562d51f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 11 Oct 2016 14:39:18 -0700 Subject: Add a bundle check step to ensure dependencies are correct This should help prevent merge issues in the future, which caused !6814 to be needed. --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb6f691058e..05687d22b68 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -263,6 +263,7 @@ bundler:audit: only: - master script: + - bundle check - "bundle exec bundle-audit check --update --ignore OSVDB-115941" migration paths: -- cgit v1.2.1 From 916210a0d4fb658a190a6da1dad82e8482741148 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 12 Oct 2016 05:49:10 -0700 Subject: Add a separate stage for bundle check --- .gitlab-ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05687d22b68..4130aa4b6e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -257,13 +257,18 @@ lint-doc: script: - scripts/lint-doc.sh +bundler:check: + stage: test + <<: *ruby-static-analysis + script: + - bundle check + bundler:audit: stage: test <<: *ruby-static-analysis only: - master script: - - bundle check - "bundle exec bundle-audit check --update --ignore OSVDB-115941" migration paths: -- cgit v1.2.1 From 254c8200ef185ad1c45db50c0141785bbb8238ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 12 Oct 2016 14:51:29 +0200 Subject: Use activerecord_sane_schema_dumper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- Gemfile | 2 + Gemfile.lock | 3 + db/schema.rb | 1174 +++++++++++++++++++++++++++++----------------------------- 3 files changed, 592 insertions(+), 587 deletions(-) diff --git a/Gemfile b/Gemfile index c5f1ce26daf..9d98a34a0d5 100644 --- a/Gemfile +++ b/Gemfile @@ -262,6 +262,8 @@ group :development do # thin instead webrick gem 'thin', '~> 1.7.0' + + gem 'activerecord_sane_schema_dumper', '0.2' end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 2feec4c4eb5..69804c8c533 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,6 +38,8 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 1.5.2, < 3) railties (>= 4.0, < 5.1) + activerecord_sane_schema_dumper (0.2) + rails (>= 4, < 5) activesupport (4.2.7.1) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) @@ -805,6 +807,7 @@ DEPENDENCIES RedCloth (~> 4.3.2) ace-rails-ap (~> 4.1.0) activerecord-session_store (~> 1.0.0) + activerecord_sane_schema_dumper (= 0.2) acts-as-taggable-on (~> 4.0) addressable (~> 2.3.8) after_commit_queue (~> 1.3.0) diff --git a/db/schema.rb b/db/schema.rb index c5ddf9eee32..a362fd8f228 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -18,94 +18,94 @@ ActiveRecord::Schema.define(version: 20161007133303) do enable_extension "pg_trgm" create_table "abuse_reports", force: :cascade do |t| - t.integer "reporter_id" - t.integer "user_id" - t.text "message" + t.integer "reporter_id" + t.integer "user_id" + t.text "message" t.datetime "created_at" t.datetime "updated_at" - t.text "message_html" + t.text "message_html" end create_table "appearances", force: :cascade do |t| - t.string "title" - t.text "description" - t.string "header_logo" - t.string "logo" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "description_html" + t.string "title" + t.text "description" + t.string "header_logo" + t.string "logo" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "description_html" end create_table "application_settings", force: :cascade do |t| - t.integer "default_projects_limit" - t.boolean "signup_enabled" - t.boolean "signin_enabled" - t.boolean "gravatar_enabled" - t.text "sign_in_text" + t.integer "default_projects_limit" + t.boolean "signup_enabled" + t.boolean "signin_enabled" + t.boolean "gravatar_enabled" + t.text "sign_in_text" t.datetime "created_at" t.datetime "updated_at" - t.string "home_page_url" - t.integer "default_branch_protection", default: 2 - t.text "restricted_visibility_levels" - t.boolean "version_check_enabled", default: true - t.integer "max_attachment_size", default: 10, null: false - t.integer "default_project_visibility" - t.integer "default_snippet_visibility" - t.text "domain_whitelist" - t.boolean "user_oauth_applications", default: true - t.string "after_sign_out_path" - t.integer "session_expire_delay", default: 10080, null: false - t.text "import_sources" - t.text "help_page_text" - t.string "admin_notification_email" - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false - t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 - t.boolean "recaptcha_enabled", default: false - t.string "recaptcha_site_key" - t.string "recaptcha_private_key" - t.integer "metrics_port", default: 8089 - t.boolean "akismet_enabled", default: false - t.string "akismet_api_key" - t.integer "metrics_sample_interval", default: 15 - t.boolean "sentry_enabled", default: false - t.string "sentry_dsn" - t.boolean "email_author_in_body", default: false - t.integer "default_group_visibility" - t.boolean "repository_checks_enabled", default: false - t.text "shared_runners_text" - t.integer "metrics_packet_size", default: 1 - t.text "disabled_oauth_sign_in_sources" - t.string "health_check_access_token" - t.boolean "send_user_confirmation_email", default: false - t.integer "container_registry_token_expire_delay", default: 5 - t.text "after_sign_up_text" - t.boolean "user_default_external", default: false, null: false - t.string "repository_storage", default: "default" - t.string "enabled_git_access_protocol" - t.boolean "domain_blacklist_enabled", default: false - t.text "domain_blacklist" - t.boolean "koding_enabled" - t.string "koding_url" - t.text "sign_in_text_html" - t.text "help_page_text_html" - t.text "shared_runners_text_html" - t.text "after_sign_up_text_html" + t.string "home_page_url" + t.integer "default_branch_protection", default: 2 + t.text "restricted_visibility_levels" + t.boolean "version_check_enabled", default: true + t.integer "max_attachment_size", default: 10, null: false + t.integer "default_project_visibility" + t.integer "default_snippet_visibility" + t.text "domain_whitelist" + t.boolean "user_oauth_applications", default: true + t.string "after_sign_out_path" + t.integer "session_expire_delay", default: 10080, null: false + t.text "import_sources" + t.text "help_page_text" + t.string "admin_notification_email" + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false + t.string "runners_registration_token" + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false + t.string "recaptcha_site_key" + t.string "recaptcha_private_key" + t.integer "metrics_port", default: 8089 + t.boolean "akismet_enabled", default: false + t.string "akismet_api_key" + t.integer "metrics_sample_interval", default: 15 + t.boolean "sentry_enabled", default: false + t.string "sentry_dsn" + t.boolean "email_author_in_body", default: false + t.integer "default_group_visibility" + t.boolean "repository_checks_enabled", default: false + t.text "shared_runners_text" + t.integer "metrics_packet_size", default: 1 + t.text "disabled_oauth_sign_in_sources" + t.string "health_check_access_token" + t.boolean "send_user_confirmation_email", default: false + t.integer "container_registry_token_expire_delay", default: 5 + t.text "after_sign_up_text" + t.boolean "user_default_external", default: false, null: false + t.string "repository_storage", default: "default" + t.string "enabled_git_access_protocol" + t.boolean "domain_blacklist_enabled", default: false + t.text "domain_blacklist" + t.boolean "koding_enabled" + t.string "koding_url" + t.text "sign_in_text_html" + t.text "help_page_text_html" + t.text "shared_runners_text_html" + t.text "after_sign_up_text_html" end create_table "audit_events", force: :cascade do |t| - t.integer "author_id", null: false - t.string "type", null: false - t.integer "entity_id", null: false - t.string "entity_type", null: false - t.text "details" + t.integer "author_id", null: false + t.string "type", null: false + t.integer "entity_id", null: false + t.string "entity_type", null: false + t.text "details" t.datetime "created_at" t.datetime "updated_at" end @@ -113,10 +113,10 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree create_table "award_emoji", force: :cascade do |t| - t.string "name" - t.integer "user_id" - t.integer "awardable_id" - t.string "awardable_type" + t.string "name" + t.integer "user_id" + t.integer "awardable_id" + t.string "awardable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -126,7 +126,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree create_table "boards", force: :cascade do |t| - t.integer "project_id", null: false + t.integer "project_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -134,61 +134,61 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "boards", ["project_id"], name: "index_boards_on_project_id", using: :btree create_table "broadcast_messages", force: :cascade do |t| - t.text "message", null: false + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" t.datetime "created_at" t.datetime "updated_at" - t.string "color" - t.string "font" - t.text "message_html" + t.string "color" + t.string "font" + t.text "message_html" end create_table "ci_application_settings", force: :cascade do |t| - t.boolean "all_broken_builds" - t.boolean "add_pusher" + t.boolean "all_broken_builds" + t.boolean "add_pusher" t.datetime "created_at" t.datetime "updated_at" end create_table "ci_builds", force: :cascade do |t| - t.integer "project_id" - t.string "status" + t.integer "project_id" + t.string "status" t.datetime "finished_at" - t.text "trace" + t.text "trace" t.datetime "created_at" t.datetime "updated_at" t.datetime "started_at" - t.integer "runner_id" - t.float "coverage" - t.integer "commit_id" - t.text "commands" - t.integer "job_id" - t.string "name" - t.boolean "deploy", default: false - t.text "options" - t.boolean "allow_failure", default: false, null: false - t.string "stage" - t.integer "trigger_request_id" - t.integer "stage_idx" - t.boolean "tag" - t.string "ref" - t.integer "user_id" - t.string "type" - t.string "target_url" - t.string "description" - t.text "artifacts_file" - t.integer "gl_project_id" - t.text "artifacts_metadata" - t.integer "erased_by_id" + t.integer "runner_id" + t.float "coverage" + t.integer "commit_id" + t.text "commands" + t.integer "job_id" + t.string "name" + t.boolean "deploy", default: false + t.text "options" + t.boolean "allow_failure", default: false, null: false + t.string "stage" + t.integer "trigger_request_id" + t.integer "stage_idx" + t.boolean "tag" + t.string "ref" + t.integer "user_id" + t.string "type" + t.string "target_url" + t.string "description" + t.text "artifacts_file" + t.integer "gl_project_id" + t.text "artifacts_metadata" + t.integer "erased_by_id" t.datetime "erased_at" t.datetime "artifacts_expire_at" - t.string "environment" - t.integer "artifacts_size", limit: 8 - t.string "when" - t.text "yaml_variables" + t.string "environment" + t.integer "artifacts_size", limit: 8 + t.string "when" + t.text "yaml_variables" t.datetime "queued_at" - t.string "token" + t.string "token" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree @@ -203,22 +203,22 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree create_table "ci_commits", force: :cascade do |t| - t.integer "project_id" - t.string "ref" - t.string "sha" - t.string "before_sha" - t.text "push_data" + t.integer "project_id" + t.string "ref" + t.string "sha" + t.string "before_sha" + t.text "push_data" t.datetime "created_at" t.datetime "updated_at" - t.boolean "tag", default: false - t.text "yaml_errors" + t.boolean "tag", default: false + t.text "yaml_errors" t.datetime "committed_at" - t.integer "gl_project_id" - t.string "status" + t.integer "gl_project_id" + t.string "status" t.datetime "started_at" t.datetime "finished_at" - t.integer "duration" - t.integer "user_id" + t.integer "duration" + t.integer "user_id" end add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree @@ -228,140 +228,140 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "ci_commits", ["user_id"], name: "index_ci_commits_on_user_id", using: :btree create_table "ci_events", force: :cascade do |t| - t.integer "project_id" - t.integer "user_id" - t.integer "is_admin" - t.text "description" + t.integer "project_id" + t.integer "user_id" + t.integer "is_admin" + t.text "description" t.datetime "created_at" t.datetime "updated_at" end create_table "ci_jobs", force: :cascade do |t| - t.integer "project_id", null: false - t.text "commands" - t.boolean "active", default: true, null: false + t.integer "project_id", null: false + t.text "commands" + t.boolean "active", default: true, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "build_branches", default: true, null: false - t.boolean "build_tags", default: false, null: false - t.string "job_type", default: "parallel" - t.string "refs" + t.string "name" + t.boolean "build_branches", default: true, null: false + t.boolean "build_tags", default: false, null: false + t.string "job_type", default: "parallel" + t.string "refs" t.datetime "deleted_at" end create_table "ci_projects", force: :cascade do |t| - t.string "name" - t.integer "timeout", default: 3600, null: false + t.string "name" + t.integer "timeout", default: 3600, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "token" - t.string "default_ref" - t.string "path" - t.boolean "always_build", default: false, null: false - t.integer "polling_interval" - t.boolean "public", default: false, null: false - t.string "ssh_url_to_repo" - t.integer "gitlab_id" - t.boolean "allow_git_fetch", default: true, null: false - t.string "email_recipients", default: "", null: false - t.boolean "email_add_pusher", default: true, null: false - t.boolean "email_only_broken_builds", default: true, null: false - t.string "skip_refs" - t.string "coverage_regex" - t.boolean "shared_runners_enabled", default: false - t.text "generated_yaml_config" + t.string "token" + t.string "default_ref" + t.string "path" + t.boolean "always_build", default: false, null: false + t.integer "polling_interval" + t.boolean "public", default: false, null: false + t.string "ssh_url_to_repo" + t.integer "gitlab_id" + t.boolean "allow_git_fetch", default: true, null: false + t.string "email_recipients", default: "", null: false + t.boolean "email_add_pusher", default: true, null: false + t.boolean "email_only_broken_builds", default: true, null: false + t.string "skip_refs" + t.string "coverage_regex" + t.boolean "shared_runners_enabled", default: false + t.text "generated_yaml_config" end create_table "ci_runner_projects", force: :cascade do |t| - t.integer "runner_id", null: false - t.integer "project_id" + t.integer "runner_id", null: false + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "gl_project_id" + t.integer "gl_project_id" end add_index "ci_runner_projects", ["gl_project_id"], name: "index_ci_runner_projects_on_gl_project_id", using: :btree add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree create_table "ci_runners", force: :cascade do |t| - t.string "token" + t.string "token" t.datetime "created_at" t.datetime "updated_at" - t.string "description" + t.string "description" t.datetime "contacted_at" - t.boolean "active", default: true, null: false - t.boolean "is_shared", default: false - t.string "name" - t.string "version" - t.string "revision" - t.string "platform" - t.string "architecture" - t.boolean "run_untagged", default: true, null: false - t.boolean "locked", default: false, null: false + t.boolean "active", default: true, null: false + t.boolean "is_shared", default: false + t.string "name" + t.string "version" + t.string "revision" + t.string "platform" + t.string "architecture" + t.boolean "run_untagged", default: true, null: false + t.boolean "locked", default: false, null: false end add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree create_table "ci_sessions", force: :cascade do |t| - t.string "session_id", null: false - t.text "data" + t.string "session_id", null: false + t.text "data" t.datetime "created_at" t.datetime "updated_at" end create_table "ci_taggings", force: :cascade do |t| - t.integer "tag_id" - t.integer "taggable_id" - t.string "taggable_type" - t.integer "tagger_id" - t.string "tagger_type" - t.string "context", limit: 128 + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context", limit: 128 t.datetime "created_at" end add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "ci_tags", force: :cascade do |t| - t.string "name" + t.string "name" t.integer "taggings_count", default: 0 end create_table "ci_trigger_requests", force: :cascade do |t| - t.integer "trigger_id", null: false - t.text "variables" + t.integer "trigger_id", null: false + t.text "variables" t.datetime "created_at" t.datetime "updated_at" - t.integer "commit_id" + t.integer "commit_id" end create_table "ci_triggers", force: :cascade do |t| - t.string "token" - t.integer "project_id" + t.string "token" + t.integer "project_id" t.datetime "deleted_at" t.datetime "created_at" t.datetime "updated_at" - t.integer "gl_project_id" + t.integer "gl_project_id" end add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree create_table "ci_variables", force: :cascade do |t| t.integer "project_id" - t.string "key" - t.text "value" - t.text "encrypted_value" - t.string "encrypted_value_salt" - t.string "encrypted_value_iv" + t.string "key" + t.text "value" + t.text "encrypted_value" + t.string "encrypted_value_salt" + t.string "encrypted_value_iv" t.integer "gl_project_id" end add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree create_table "deploy_keys_projects", force: :cascade do |t| - t.integer "deploy_key_id", null: false - t.integer "project_id", null: false + t.integer "deploy_key_id", null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -369,15 +369,15 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree create_table "deployments", force: :cascade do |t| - t.integer "iid", null: false - t.integer "project_id", null: false - t.integer "environment_id", null: false - t.string "ref", null: false - t.boolean "tag", null: false - t.string "sha", null: false - t.integer "user_id" - t.integer "deployable_id" - t.string "deployable_type" + t.integer "iid", null: false + t.integer "project_id", null: false + t.integer "environment_id", null: false + t.string "ref", null: false + t.boolean "tag", null: false + t.string "sha", null: false + t.integer "user_id" + t.integer "deployable_id" + t.string "deployable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -388,8 +388,8 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "deployments", ["project_id"], name: "index_deployments_on_project_id", using: :btree create_table "emails", force: :cascade do |t| - t.integer "user_id", null: false - t.string "email", null: false + t.integer "user_id", null: false + t.string "email", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -398,26 +398,26 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "environments", force: :cascade do |t| - t.integer "project_id" - t.string "name", null: false + t.integer "project_id" + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "external_url" - t.string "environment_type" + t.string "external_url" + t.string "environment_type" end add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree create_table "events", force: :cascade do |t| - t.string "target_type" - t.integer "target_id" - t.string "title" - t.text "data" - t.integer "project_id" + t.string "target_type" + t.integer "target_id" + t.string "title" + t.text "data" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "action" - t.integer "author_id" + t.integer "action" + t.integer "author_id" end add_index "events", ["action"], name: "index_events_on_action", using: :btree @@ -428,8 +428,8 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree create_table "forked_project_links", force: :cascade do |t| - t.integer "forked_to_project_id", null: false - t.integer "forked_from_project_id", null: false + t.integer "forked_to_project_id", null: false + t.integer "forked_from_project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -437,9 +437,9 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree create_table "identities", force: :cascade do |t| - t.string "extern_uid" - t.string "provider" - t.integer "user_id" + t.string "extern_uid" + t.string "provider" + t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" end @@ -447,37 +447,37 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree create_table "issue_metrics", force: :cascade do |t| - t.integer "issue_id", null: false + t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" t.datetime "first_associated_with_milestone_at" t.datetime "first_added_to_board_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "issue_metrics", ["issue_id"], name: "index_issue_metrics", using: :btree create_table "issues", force: :cascade do |t| - t.string "title" - t.integer "assignee_id" - t.integer "author_id" - t.integer "project_id" + t.string "title" + t.integer "assignee_id" + t.integer "author_id" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 - t.string "branch_name" - t.text "description" - t.integer "milestone_id" - t.string "state" - t.integer "iid" - t.integer "updated_by_id" - t.boolean "confidential", default: false + t.integer "position", default: 0 + t.string "branch_name" + t.text "description" + t.integer "milestone_id" + t.string "state" + t.integer "iid" + t.integer "updated_by_id" + t.boolean "confidential", default: false t.datetime "deleted_at" - t.date "due_date" - t.integer "moved_to_id" - t.integer "lock_version" - t.text "title_html" - t.text "description_html" + t.date "due_date" + t.integer "moved_to_id" + t.integer "lock_version" + t.text "title_html" + t.text "description_html" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -493,23 +493,23 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "keys", force: :cascade do |t| - t.integer "user_id" + t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" - t.text "key" - t.string "title" - t.string "type" - t.string "fingerprint" - t.boolean "public", default: false, null: false + t.text "key" + t.string "title" + t.string "type" + t.string "fingerprint" + t.boolean "public", default: false, null: false end add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree create_table "label_links", force: :cascade do |t| - t.integer "label_id" - t.integer "target_id" - t.string "target_type" + t.integer "label_id" + t.integer "target_id" + t.string "target_type" t.datetime "created_at" t.datetime "updated_at" end @@ -518,15 +518,15 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree create_table "labels", force: :cascade do |t| - t.string "title" - t.string "color" - t.integer "project_id" + t.string "title" + t.string "color" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "template", default: false - t.string "description" - t.integer "priority" - t.text "description_html" + t.boolean "template", default: false + t.string "description" + t.integer "priority" + t.text "description_html" end add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree @@ -534,18 +534,18 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "labels", ["title"], name: "index_labels_on_title", using: :btree create_table "lfs_objects", force: :cascade do |t| - t.string "oid", null: false - t.integer "size", limit: 8, null: false + t.string "oid", null: false + t.integer "size", limit: 8, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "file" + t.string "file" end add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree create_table "lfs_objects_projects", force: :cascade do |t| - t.integer "lfs_object_id", null: false - t.integer "project_id", null: false + t.integer "lfs_object_id", null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -553,12 +553,12 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree create_table "lists", force: :cascade do |t| - t.integer "board_id", null: false - t.integer "label_id" - t.integer "list_type", default: 1, null: false - t.integer "position" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "board_id", null: false + t.integer "label_id" + t.integer "list_type", default: 1, null: false + t.integer "position" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "lists", ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true, using: :btree @@ -566,20 +566,20 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "lists", ["label_id"], name: "index_lists_on_label_id", using: :btree create_table "members", force: :cascade do |t| - t.integer "access_level", null: false - t.integer "source_id", null: false - t.string "source_type", null: false - t.integer "user_id" - t.integer "notification_level", null: false - t.string "type" + t.integer "access_level", null: false + t.integer "source_id", null: false + t.string "source_type", null: false + t.integer "user_id" + t.integer "notification_level", null: false + t.string "type" t.datetime "created_at" t.datetime "updated_at" - t.integer "created_by_id" - t.string "invite_email" - t.string "invite_token" + t.integer "created_by_id" + t.string "invite_email" + t.string "invite_token" t.datetime "invite_accepted_at" t.datetime "requested_at" - t.date "expires_at" + t.date "expires_at" end add_index "members", ["access_level"], name: "index_members_on_access_level", using: :btree @@ -589,61 +589,61 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree create_table "merge_request_diffs", force: :cascade do |t| - t.string "state" - t.text "st_commits" - t.text "st_diffs" - t.integer "merge_request_id", null: false + t.string "state" + t.text "st_commits" + t.text "st_diffs" + t.integer "merge_request_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "base_commit_sha" - t.string "real_size" - t.string "head_commit_sha" - t.string "start_commit_sha" + t.string "base_commit_sha" + t.string "real_size" + t.string "head_commit_sha" + t.string "start_commit_sha" end add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", using: :btree create_table "merge_request_metrics", force: :cascade do |t| - t.integer "merge_request_id", null: false + t.integer "merge_request_id", null: false t.datetime "latest_build_started_at" t.datetime "latest_build_finished_at" t.datetime "first_deployed_to_production_at" t.datetime "merged_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree create_table "merge_requests", force: :cascade do |t| - t.string "target_branch", null: false - t.string "source_branch", null: false - t.integer "source_project_id", null: false - t.integer "author_id" - t.integer "assignee_id" - t.string "title" + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "source_project_id", null: false + t.integer "author_id" + t.integer "assignee_id" + t.string "title" t.datetime "created_at" t.datetime "updated_at" - t.integer "milestone_id" - t.string "state" - t.string "merge_status" - t.integer "target_project_id", null: false - t.integer "iid" - t.text "description" - t.integer "position", default: 0 + t.integer "milestone_id" + t.string "state" + t.string "merge_status" + t.integer "target_project_id", null: false + t.integer "iid" + t.text "description" + t.integer "position", default: 0 t.datetime "locked_at" - t.integer "updated_by_id" - t.text "merge_error" - t.text "merge_params" - t.boolean "merge_when_build_succeeds", default: false, null: false - t.integer "merge_user_id" - t.string "merge_commit_sha" + t.integer "updated_by_id" + t.text "merge_error" + t.text "merge_params" + t.boolean "merge_when_build_succeeds", default: false, null: false + t.integer "merge_user_id" + t.string "merge_commit_sha" t.datetime "deleted_at" - t.string "in_progress_merge_commit_sha" - t.integer "lock_version" - t.text "title_html" - t.text "description_html" + t.string "in_progress_merge_commit_sha" + t.integer "lock_version" + t.text "title_html" + t.text "description_html" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -660,26 +660,26 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "merge_requests_closing_issues", force: :cascade do |t| - t.integer "merge_request_id", null: false - t.integer "issue_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "merge_request_id", null: false + t.integer "issue_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "merge_requests_closing_issues", ["issue_id"], name: "index_merge_requests_closing_issues_on_issue_id", using: :btree add_index "merge_requests_closing_issues", ["merge_request_id"], name: "index_merge_requests_closing_issues_on_merge_request_id", using: :btree create_table "milestones", force: :cascade do |t| - t.string "title", null: false - t.integer "project_id", null: false - t.text "description" - t.date "due_date" + t.string "title", null: false + t.integer "project_id", null: false + t.text "description" + t.date "due_date" t.datetime "created_at" t.datetime "updated_at" - t.string "state" - t.integer "iid" - t.text "title_html" - t.text "description_html" + t.string "state" + t.integer "iid" + t.text "title_html" + t.text "description_html" end add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} @@ -690,20 +690,20 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "namespaces", force: :cascade do |t| - t.string "name", null: false - t.string "path", null: false - t.integer "owner_id" + t.string "name", null: false + t.string "path", null: false + t.integer "owner_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type" - t.string "description", default: "", null: false - t.string "avatar" - t.boolean "share_with_group_lock", default: false - t.integer "visibility_level", default: 20, null: false - t.boolean "request_access_enabled", default: true, null: false + t.string "type" + t.string "description", default: "", null: false + t.string "avatar" + t.boolean "share_with_group_lock", default: false + t.integer "visibility_level", default: 20, null: false + t.boolean "request_access_enabled", default: true, null: false t.datetime "deleted_at" - t.boolean "lfs_enabled" - t.text "description_html" + t.boolean "lfs_enabled" + t.text "description_html" end add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree @@ -716,27 +716,27 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree create_table "notes", force: :cascade do |t| - t.text "note" - t.string "noteable_type" - t.integer "author_id" + t.text "note" + t.string "noteable_type" + t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "project_id" - t.string "attachment" - t.string "line_code" - t.string "commit_id" - t.integer "noteable_id" - t.boolean "system", default: false, null: false - t.text "st_diff" - t.integer "updated_by_id" - t.string "type" - t.text "position" - t.text "original_position" + t.integer "project_id" + t.string "attachment" + t.string "line_code" + t.string "commit_id" + t.integer "noteable_id" + t.boolean "system", default: false, null: false + t.text "st_diff" + t.integer "updated_by_id" + t.string "type" + t.text "position" + t.text "original_position" t.datetime "resolved_at" - t.integer "resolved_by_id" - t.string "discussion_id" - t.string "original_discussion_id" - t.text "note_html" + t.integer "resolved_by_id" + t.string "discussion_id" + t.string "original_discussion_id" + t.text "note_html" end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -752,13 +752,13 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree create_table "notification_settings", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "source_id" - t.string "source_type" - t.integer "level", default: 0, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "events" + t.integer "user_id", null: false + t.integer "source_id" + t.string "source_type" + t.integer "level", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "events" end add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree @@ -766,27 +766,27 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "notification_settings", ["user_id"], name: "index_notification_settings_on_user_id", using: :btree create_table "oauth_access_grants", force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false t.datetime "revoked_at" - t.string "scopes" + t.string "scopes" end add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree create_table "oauth_access_tokens", force: :cascade do |t| - t.integer "resource_owner_id" - t.integer "application_id" - t.string "token", null: false - t.string "refresh_token" - t.integer "expires_in" + t.integer "resource_owner_id" + t.integer "application_id" + t.string "token", null: false + t.string "refresh_token" + t.integer "expires_in" t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes" + t.datetime "created_at", null: false + t.string "scopes" end add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree @@ -794,40 +794,40 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree create_table "oauth_applications", force: :cascade do |t| - t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false + t.string "name", null: false + t.string "uid", null: false + t.string "secret", null: false + t.text "redirect_uri", null: false + t.string "scopes", default: "", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "owner_id" - t.string "owner_type" + t.integer "owner_id" + t.string "owner_type" end add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree create_table "personal_access_tokens", force: :cascade do |t| - t.integer "user_id", null: false - t.string "token", null: false - t.string "name", null: false - t.boolean "revoked", default: false + t.integer "user_id", null: false + t.string "token", null: false + t.string "name", null: false + t.boolean "revoked", default: false t.datetime "expires_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree create_table "project_features", force: :cascade do |t| - t.integer "project_id" - t.integer "merge_requests_access_level" - t.integer "issues_access_level" - t.integer "wiki_access_level" - t.integer "snippets_access_level" - t.integer "builds_access_level" + t.integer "project_id" + t.integer "merge_requests_access_level" + t.integer "issues_access_level" + t.integer "wiki_access_level" + t.integer "snippets_access_level" + t.integer "builds_access_level" t.datetime "created_at" t.datetime "updated_at" end @@ -835,60 +835,60 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree create_table "project_group_links", force: :cascade do |t| - t.integer "project_id", null: false - t.integer "group_id", null: false + t.integer "project_id", null: false + t.integer "group_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "group_access", default: 30, null: false - t.date "expires_at" + t.integer "group_access", default: 30, null: false + t.date "expires_at" end create_table "project_import_data", force: :cascade do |t| t.integer "project_id" - t.text "data" - t.text "encrypted_credentials" - t.string "encrypted_credentials_iv" - t.string "encrypted_credentials_salt" + t.text "data" + t.text "encrypted_credentials" + t.string "encrypted_credentials_iv" + t.string "encrypted_credentials_salt" end create_table "projects", force: :cascade do |t| - t.string "name" - t.string "path" - t.text "description" + t.string "name" + t.string "path" + t.text "description" t.datetime "created_at" t.datetime "updated_at" - t.integer "creator_id" - t.integer "namespace_id" + t.integer "creator_id" + t.integer "namespace_id" t.datetime "last_activity_at" - t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false - t.string "avatar" - t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.string "import_type" - t.string "import_source" - t.integer "commit_count", default: 0 - t.text "import_error" - t.integer "ci_id" - t.boolean "shared_runners_enabled", default: true, null: false - t.string "runners_token" - t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false - t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false - t.boolean "last_repository_check_failed" + t.string "import_url" + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false + t.string "avatar" + t.string "import_status" + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.string "import_type" + t.string "import_source" + t.integer "commit_count", default: 0 + t.text "import_error" + t.integer "ci_id" + t.boolean "shared_runners_enabled", default: true, null: false + t.string "runners_token" + t.string "build_coverage_regex" + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false + t.boolean "pending_delete", default: false + t.boolean "public_builds", default: true, null: false + t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" - t.boolean "container_registry_enabled" - t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false - t.boolean "has_external_issue_tracker" - t.string "repository_storage", default: "default", null: false - t.boolean "request_access_enabled", default: true, null: false - t.boolean "has_external_wiki" - t.boolean "lfs_enabled" - t.text "description_html" + t.boolean "container_registry_enabled" + t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false + t.boolean "has_external_issue_tracker" + t.string "repository_storage", default: "default", null: false + t.boolean "request_access_enabled", default: true, null: false + t.boolean "has_external_wiki" + t.boolean "lfs_enabled" + t.text "description_html" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree @@ -907,26 +907,26 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree create_table "protected_branch_merge_access_levels", force: :cascade do |t| - t.integer "protected_branch_id", null: false - t.integer "access_level", default: 40, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 40, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "protected_branch_merge_access_levels", ["protected_branch_id"], name: "index_protected_branch_merge_access", using: :btree create_table "protected_branch_push_access_levels", force: :cascade do |t| - t.integer "protected_branch_id", null: false - t.integer "access_level", default: 40, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 40, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "protected_branch_push_access_levels", ["protected_branch_id"], name: "index_protected_branch_push_access", using: :btree create_table "protected_branches", force: :cascade do |t| - t.integer "project_id", null: false - t.string "name", null: false + t.integer "project_id", null: false + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -934,12 +934,12 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree create_table "releases", force: :cascade do |t| - t.string "tag" - t.text "description" - t.integer "project_id" + t.string "tag" + t.text "description" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.text "description_html" + t.text "description_html" end add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree @@ -948,54 +948,54 @@ ActiveRecord::Schema.define(version: 20161007133303) do create_table "sent_notifications", force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" - t.string "noteable_type" + t.string "noteable_type" t.integer "recipient_id" - t.string "commit_id" - t.string "reply_key", null: false - t.string "line_code" - t.string "note_type" - t.text "position" + t.string "commit_id" + t.string "reply_key", null: false + t.string "line_code" + t.string "note_type" + t.text "position" end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree create_table "services", force: :cascade do |t| - t.string "type" - t.string "title" - t.integer "project_id" + t.string "type" + t.string "title" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false - t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false - t.boolean "wiki_page_events", default: true - t.boolean "pipeline_events", default: false, null: false - t.boolean "confidential_issues_events", default: true, null: false + t.boolean "active", default: false, null: false + t.text "properties" + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false + t.boolean "wiki_page_events", default: true + t.boolean "pipeline_events", default: false, null: false + t.boolean "confidential_issues_events", default: true, null: false end add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree add_index "services", ["template"], name: "index_services_on_template", using: :btree create_table "snippets", force: :cascade do |t| - t.string "title" - t.text "content" - t.integer "author_id", null: false - t.integer "project_id" + t.string "title" + t.text "content" + t.integer "author_id", null: false + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "file_name" - t.string "type" - t.integer "visibility_level", default: 0, null: false - t.text "title_html" - t.text "content_html" + t.string "file_name" + t.string "type" + t.integer "visibility_level", default: 0, null: false + t.text "title_html" + t.text "content_html" end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -1006,23 +1006,23 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree create_table "spam_logs", force: :cascade do |t| - t.integer "user_id" - t.string "source_ip" - t.string "user_agent" - t.boolean "via_api" - t.string "noteable_type" - t.string "title" - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "submitted_as_ham", default: false, null: false + t.integer "user_id" + t.string "source_ip" + t.string "user_agent" + t.boolean "via_api" + t.string "noteable_type" + t.string "title" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "submitted_as_ham", default: false, null: false end create_table "subscriptions", force: :cascade do |t| - t.integer "user_id" - t.integer "subscribable_id" - t.string "subscribable_type" - t.boolean "subscribed" + t.integer "user_id" + t.integer "subscribable_id" + t.string "subscribable_type" + t.boolean "subscribed" t.datetime "created_at" t.datetime "updated_at" end @@ -1030,12 +1030,12 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree create_table "taggings", force: :cascade do |t| - t.integer "tag_id" - t.integer "taggable_id" - t.string "taggable_type" - t.integer "tagger_id" - t.string "tagger_type" - t.string "context" + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context" t.datetime "created_at" end @@ -1043,24 +1043,24 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "tags", force: :cascade do |t| - t.string "name" + t.string "name" t.integer "taggings_count", default: 0 end add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "todos", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "project_id", null: false - t.integer "target_id" - t.string "target_type", null: false - t.integer "author_id" - t.integer "action", null: false - t.string "state", null: false + t.integer "user_id", null: false + t.integer "project_id", null: false + t.integer "target_id" + t.string "target_type", null: false + t.integer "author_id" + t.integer "action", null: false + t.string "state", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "note_id" - t.string "commit_id" + t.integer "note_id" + t.string "commit_id" end add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree @@ -1077,88 +1077,88 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree create_table "u2f_registrations", force: :cascade do |t| - t.text "certificate" - t.string "key_handle" - t.string "public_key" - t.integer "counter" - t.integer "user_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "name" + t.text "certificate" + t.string "key_handle" + t.string "public_key" + t.integer "counter" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" end add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree create_table "user_agent_details", force: :cascade do |t| - t.string "user_agent", null: false - t.string "ip_address", null: false - t.integer "subject_id", null: false - t.string "subject_type", null: false - t.boolean "submitted", default: false, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "user_agent", null: false + t.string "ip_address", null: false + t.integer "subject_id", null: false + t.string "subject_type", null: false + t.boolean "submitted", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false - t.string "authentication_token" - t.integer "theme_id", default: 1, null: false - t.string "bio" - t.integer "failed_attempts", default: 0 + t.string "name" + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false + t.string "authentication_token" + t.integer "theme_id", default: 1, null: false + t.string "bio" + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false - t.string "state" - t.integer "color_scheme_id", default: 1, null: false + t.string "username" + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false + t.string "state" + t.integer "color_scheme_id", default: 1, null: false t.datetime "password_expires_at" - t.integer "created_by_id" + t.integer "created_by_id" t.datetime "last_credential_check_at" - t.string "avatar" - t.string "confirmation_token" + t.string "avatar" + t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false - t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false - t.string "location" - t.string "encrypted_otp_secret" - t.string "encrypted_otp_secret_iv" - t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false - t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 - t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false - t.string "unlock_token" + t.string "unconfirmed_email" + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false + t.string "notification_email" + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "location" + t.string "encrypted_otp_secret" + t.string "encrypted_otp_secret_iv" + t.string "encrypted_otp_secret_salt" + t.boolean "otp_required_for_login", default: false, null: false + t.text "otp_backup_codes" + t.string "public_email", default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 + t.integer "consumed_timestep" + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false + t.string "unlock_token" t.datetime "otp_grace_period_started_at" - t.boolean "ldap_email", default: false, null: false - t.boolean "external", default: false - t.string "organization" + t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false + t.string "organization" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree @@ -1176,8 +1176,8 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} create_table "users_star_projects", force: :cascade do |t| - t.integer "project_id", null: false - t.integer "user_id", null: false + t.integer "project_id", null: false + t.integer "user_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -1187,23 +1187,23 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree create_table "web_hooks", force: :cascade do |t| - t.string "url", limit: 2000 - t.integer "project_id" + t.string "url", limit: 2000 + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", default: "ProjectHook" - t.integer "service_id" - t.boolean "push_events", default: true, null: false - t.boolean "issues_events", default: false, null: false - t.boolean "merge_requests_events", default: false, null: false - t.boolean "tag_push_events", default: false - t.boolean "note_events", default: false, null: false - t.boolean "enable_ssl_verification", default: true - t.boolean "build_events", default: false, null: false - t.boolean "wiki_page_events", default: false, null: false - t.string "token" - t.boolean "pipeline_events", default: false, null: false - t.boolean "confidential_issues_events", default: false, null: false + t.string "type", default: "ProjectHook" + t.integer "service_id" + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false + t.boolean "tag_push_events", default: false + t.boolean "note_events", default: false, null: false + t.boolean "enable_ssl_verification", default: true + t.boolean "build_events", default: false, null: false + t.boolean "wiki_page_events", default: false, null: false + t.string "token" + t.boolean "pipeline_events", default: false, null: false + t.boolean "confidential_issues_events", default: false, null: false end add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree -- cgit v1.2.1 From 2e3bc85421ddfc814e81f75e3adb8a8c1d53093e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 15:21:11 +0200 Subject: Do not return from proc-closure in pipeline transition --- app/models/commit_status.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index aac5f7a5937..7b554be4f9a 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -86,7 +86,7 @@ class CommitStatus < ActiveRecord::Base end after_transition do |commit_status, transition| - return if transition.loopback? + next if transition.loopback? commit_status.run_after_commit do pipeline.try do |pipeline| -- cgit v1.2.1 From c143003bfbd5cda725451c38ff1eca8ba469409b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 13:53:18 +0300 Subject: Bump gitlab_git to 10.6.8 Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index c5f1ce26daf..59ff78c8fae 100644 --- a/Gemfile +++ b/Gemfile @@ -51,7 +51,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.6.7' +gem 'gitlab_git', '~> 10.6.8' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 2feec4c4eb5..edccf870821 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -280,7 +280,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.6.7) + gitlab_git (10.6.8) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -863,7 +863,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.6.7) + gitlab_git (~> 10.6.8) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) gollum-rugged_adapter (~> 0.4.2) -- cgit v1.2.1 From ac4db38094f4a68a81b0a7570c5835f663c01cfd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 14:04:50 +0300 Subject: Use straight diff approach when compare merge request versions Signed-off-by: Dmitriy Zaporozhets --- app/models/compare.rb | 5 ++++- app/models/merge_request_diff.rb | 7 +++++-- app/services/compare_service.rb | 7 ++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/models/compare.rb b/app/models/compare.rb index 4856510f526..4b568a1d11c 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -11,9 +11,10 @@ class Compare end end - def initialize(compare, project) + def initialize(compare, project, straight = false) @compare = compare @project = project + @straight = straight end def commits @@ -36,6 +37,8 @@ class Compare alias_method :commit, :head_commit def base_commit + return start_commit if @straight + return @base_commit if defined?(@base_commit) @base_commit = if start_commit && head_commit diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 3f7e96186a1..0ea9e892be2 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -167,8 +167,11 @@ class MergeRequestDiff < ActiveRecord::Base self == merge_request.merge_request_diff end - def compare_with(sha) - CompareService.new.execute(project, head_commit_sha, project, sha) + def compare_with(sha, straight = true) + # When compare merge request versions we want diff A..B instead of A...B + # so we handle cases when user squash and rebase commits in one of versions. + # For this reason we set straight to true by default. + CompareService.new.execute(project, head_commit_sha, project, sha, straight) end private diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 6d6075628af..6df3b958b8a 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch) + def execute(source_project, source_branch, target_project, target_branch, straight = false) source_commit = source_project.commit(source_branch) return unless source_commit @@ -23,9 +23,10 @@ class CompareService raw_compare = Gitlab::Git::Compare.new( target_project.repository.raw_repository, target_branch, - source_sha + source_sha, + straight ) - Compare.new(raw_compare, target_project) + Compare.new(raw_compare, target_project, straight) end end -- cgit v1.2.1 From 1bb64ec99476451d67463e3508b449aeea7e694f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 17:21:19 +0300 Subject: Add spec for compare service Signed-off-by: Dmitriy Zaporozhets --- spec/services/compare_service_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 spec/services/compare_service_spec.rb diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb new file mode 100644 index 00000000000..d809f291b44 --- /dev/null +++ b/spec/services/compare_service_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe CompareService, services: true do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { described_class.new } + + describe '#execute' do + context 'compare with base, like feature...fix' do + subject { service.execute(project, 'feature', project, 'fix', false) } + + it { expect(subject.diffs.size).to eq(1) } + end + + context 'straight compare, like feature..fix' do + subject { service.execute(project, 'feature', project, 'fix', true) } + + it { expect(subject.diffs.size).to eq(3) } + end + end +end -- cgit v1.2.1 From afe28b7be3a3a5a9db6e5a32465c0e3a8e9cb83d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 17:23:36 +0300 Subject: Add mr version improvement to changelog Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..51870619fb4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -127,6 +127,9 @@ v 8.12.4 - Fix failed project deletion when feature visibility set to private. !6688 - Prevent claiming associated model IDs via import. - Set GitLab project exported file permissions to owner only + - Improve the way merge request versions are compared with each other + +v 8.12.4 (unreleased) v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -- cgit v1.2.1 From cdcc11d48fe6d04f6a1cfebc39bcea71d35b3e4a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 17:46:47 +0300 Subject: Improve tests for merge request diff model Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 2 +- spec/models/merge_request_diff_spec.rb | 40 +++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 0ea9e892be2..6ad2201573d 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -169,7 +169,7 @@ class MergeRequestDiff < ActiveRecord::Base def compare_with(sha, straight = true) # When compare merge request versions we want diff A..B instead of A...B - # so we handle cases when user squash and rebase commits in one of versions. + # so we handle cases when user does squash and rebase of the commits between versions. # For this reason we set straight to true by default. CompareService.new.execute(project, head_commit_sha, project, sha, straight) end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index f27de0948ee..69b8afd22a3 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -74,27 +74,37 @@ describe MergeRequestDiff, models: true do end end end + end - describe '#commits_sha' do - shared_examples 'returning all commits SHA' do - it 'returns all commits SHA' do - commits_sha = subject.commits_sha + describe '#commits_sha' do + shared_examples 'returning all commits SHA' do + it 'returns all commits SHA' do + commits_sha = subject.commits_sha - expect(commits_sha).to eq(subject.commits.map(&:sha)) - end + expect(commits_sha).to eq(subject.commits.map(&:sha)) end + end - context 'when commits were loaded' do - before do - subject.commits - end - - it_behaves_like 'returning all commits SHA' + context 'when commits were loaded' do + before do + subject.commits end - context 'when commits were not loaded' do - it_behaves_like 'returning all commits SHA' - end + it_behaves_like 'returning all commits SHA' + end + + context 'when commits were not loaded' do + it_behaves_like 'returning all commits SHA' + end + end + + describe '#compare_with' do + subject { create(:merge_request).merge_request_diff } + + it 'delegates compare to the service' do + expect(CompareService).to receive(:new).and_call_original + + subject.compare_with('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') end end end -- cgit v1.2.1 From 7421c08ace8a129b37c4942eb21a0b5cf1a73a53 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 18:02:06 +0300 Subject: Better tests for MergeRequestDiff#compare_with method Signed-off-by: Dmitriy Zaporozhets --- spec/models/merge_request_diff_spec.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 69b8afd22a3..e5007424041 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -99,12 +99,18 @@ describe MergeRequestDiff, models: true do end describe '#compare_with' do - subject { create(:merge_request).merge_request_diff } + subject { create(:merge_request, source_branch: 'fix').merge_request_diff } it 'delegates compare to the service' do expect(CompareService).to receive(:new).and_call_original - subject.compare_with('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') + subject.compare_with(nil) + end + + it 'uses git diff A..B approach by default' do + diffs = subject.compare_with('0b4bc9a49b562e85de7cc9e834518ea6828729b9').diffs + + expect(diffs.size).to eq(3) end end end -- cgit v1.2.1 From b48c4b2662e7db9d68052392fb34dd2b27d12cf5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 13:27:59 +0300 Subject: Refactor straight compare diff code Signed-off-by: Dmitriy Zaporozhets --- app/models/compare.rb | 22 ++++++++++++++++------ app/models/merge_request_diff.rb | 4 ++-- app/services/compare_service.rb | 4 ++-- spec/services/compare_service_spec.rb | 4 ++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/models/compare.rb b/app/models/compare.rb index 4b568a1d11c..3a8bbcb1acd 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -11,7 +11,7 @@ class Compare end end - def initialize(compare, project, straight = false) + def initialize(compare, project, straight: false) @compare = compare @project = project @straight = straight @@ -37,8 +37,6 @@ class Compare alias_method :commit, :head_commit def base_commit - return start_commit if @straight - return @base_commit if defined?(@base_commit) @base_commit = if start_commit && head_commit @@ -48,6 +46,18 @@ class Compare end end + def start_commit_sha + start_commit.try(:sha) + end + + def base_commit_sha + base_commit.try(:sha) + end + + def head_commit_sha + commit.try(:sha) + end + def raw_diffs(*args) @compare.diffs(*args) end @@ -61,9 +71,9 @@ class Compare def diff_refs Gitlab::Diff::DiffRefs.new( - base_sha: base_commit.try(:sha), - start_sha: start_commit.try(:sha), - head_sha: commit.try(:sha) + base_sha: @straight ? start_commit_sha : base_commit_sha, + start_sha: start_commit_sha, + head_sha: head_commit_sha ) end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 6ad2201573d..b8a10b7968e 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -167,11 +167,11 @@ class MergeRequestDiff < ActiveRecord::Base self == merge_request.merge_request_diff end - def compare_with(sha, straight = true) + def compare_with(sha, straight: true) # When compare merge request versions we want diff A..B instead of A...B # so we handle cases when user does squash and rebase of the commits between versions. # For this reason we set straight to true by default. - CompareService.new.execute(project, head_commit_sha, project, sha, straight) + CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight) end private diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 6df3b958b8a..5e8fafca98c 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch, straight = false) + def execute(source_project, source_branch, target_project, target_branch, straight: false) source_commit = source_project.commit(source_branch) return unless source_commit @@ -27,6 +27,6 @@ class CompareService straight ) - Compare.new(raw_compare, target_project, straight) + Compare.new(raw_compare, target_project, straight: straight) end end diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb index d809f291b44..3760f19aaa2 100644 --- a/spec/services/compare_service_spec.rb +++ b/spec/services/compare_service_spec.rb @@ -7,13 +7,13 @@ describe CompareService, services: true do describe '#execute' do context 'compare with base, like feature...fix' do - subject { service.execute(project, 'feature', project, 'fix', false) } + subject { service.execute(project, 'feature', project, 'fix', straight: false) } it { expect(subject.diffs.size).to eq(1) } end context 'straight compare, like feature..fix' do - subject { service.execute(project, 'feature', project, 'fix', true) } + subject { service.execute(project, 'feature', project, 'fix', straight: true) } it { expect(subject.diffs.size).to eq(3) } end -- cgit v1.2.1 From c65c800f8e466ffcfb58613fc775c4afa11f91e6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 15:09:34 +0300 Subject: Inform user when comparing 2 version with different base Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 11 ++++++----- app/views/projects/merge_requests/show/_versions.html.haml | 7 +++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 1006b3e62e8..c712d6e36b1 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -401,8 +401,13 @@ padding: 16px; } + .content-block { + padding: $gl-padding-top $gl-padding; + } + .comments-disabled-notif { - padding: 10px 16px; + border-top: 1px solid $border-color; + .btn { margin-left: 5px; } @@ -413,10 +418,6 @@ margin: 0 7px; } - .comments-disabled-notif { - border-top: 1px solid $border-color; - } - .dropdown-title { color: $gl-text-color; } diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 988ac0feae1..c282e3868cc 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -64,6 +64,13 @@ #{@merge_request.target_branch} (base) .monospace #{short_sha(@merge_request_diff.base_commit_sha)} + - if @start_version && @start_version.base_commit_sha != @merge_request_diff.base_commit_sha + .content-block + Selected versions have different base commits. + Changes will include + = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do + new commits + from #{@merge_request.target_branch} - unless @merge_request_diff.latest? && !@start_sha .comments-disabled-notif.content-block = icon('info-circle') -- cgit v1.2.1 From d0fb1835158467ce58c59f489384b13ad0f0ff7c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 15:45:14 +0300 Subject: Fix CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 51870619fb4..445566db95f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -129,8 +129,6 @@ v 8.12.4 - Set GitLab project exported file permissions to owner only - Improve the way merge request versions are compared with each other -v 8.12.4 (unreleased) - v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -- cgit v1.2.1 From 45418734311561bb72aa5ab7a89d81bbd5705f7a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Oct 2016 14:51:56 +0200 Subject: Added documentation chapter for Git attributes As discussed in https://gitlab.com/gitlab-org/gitlab_git/issues/28 we'll need to clearly document the need for .gitattributes files being encoded using UTF-8. [ci skip] --- CHANGELOG | 1 + doc/README.md | 1 + doc/user/project/git_attributes.md | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 doc/user/project/git_attributes.md diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..894f4bc9af0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Clarify documentation for Runners API (Gennady Trafimenkov) - Change user & group landing page routing from /u/:username to /:username - Prevent running GfmAutocomplete setup for each diff note !6569 + - Added documentation for .gitattributes files - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) diff --git a/doc/README.md b/doc/README.md index 42ee44f83dc..7e3d9b00900 100644 --- a/doc/README.md +++ b/doc/README.md @@ -20,6 +20,7 @@ - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. - [University](university/README.md) Learn Git and GitLab through videos and courses. +- [Git Attributes](user/project/git_attributes.md) Managing Git attributes using a `.gitattributes` file. ## Administrator documentation diff --git a/doc/user/project/git_attributes.md b/doc/user/project/git_attributes.md new file mode 100644 index 00000000000..21ef94e61f7 --- /dev/null +++ b/doc/user/project/git_attributes.md @@ -0,0 +1,22 @@ +# Git Attributes + +GitLab supports defining custom [Git attributes][gitattributes] such as what +files to treat as binary, and what language to use for syntax highlighting +diffs. + +To define these attributes, create a file called `.gitattributes` in the root +directory of your repository and push it to the default branch of your project. + +## Encoding Requirements + +The `.gitattributes` file _must_ be encoded in UTF-8 and _must not_ contain a +Byte Order Mark. If a different encoding is used, the file's contents will be +ignored. + +## Syntax Highlighting + +The `.gitattributes` file can be used to define which language to use when +syntax highlighting files and diffs. See ["Syntax +Highlighting"](highlighting.md) for more information. + +[gitattributes]: https://git-scm.com/docs/gitattributes -- cgit v1.2.1 From 3dd7ad5064afa49aa74a7d40ba02fff7e67846d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 17:42:32 +0300 Subject: Improve mr compare message when base is different Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 3 +-- app/helpers/merge_requests_helper.rb | 4 ++++ app/views/projects/merge_requests/show/_versions.html.haml | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index c712d6e36b1..7cf69c56d15 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -402,12 +402,11 @@ } .content-block { + border-top: 1px solid $border-color; padding: $gl-padding-top $gl-padding; } .comments-disabled-notif { - border-top: 1px solid $border-color; - .btn { margin-left: 5px; } diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index b0a76765d97..249cb44e9d5 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -123,4 +123,8 @@ module MergeRequestsHelper def version_index(merge_request_diff) @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff) end + + def different_base?(version1, version2) + version1 && version2 && version1.base_commit_sha != version2.base_commit_sha + end end diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index c282e3868cc..eab48b78cb3 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -64,13 +64,16 @@ #{@merge_request.target_branch} (base) .monospace #{short_sha(@merge_request_diff.base_commit_sha)} - - if @start_version && @start_version.base_commit_sha != @merge_request_diff.base_commit_sha + - if different_base?(@start_version, @merge_request_diff) .content-block + = icon('info-circle') Selected versions have different base commits. Changes will include = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do new commits - from #{@merge_request.target_branch} + from + %code #{@merge_request.target_branch} + - unless @merge_request_diff.latest? && !@start_sha .comments-disabled-notif.content-block = icon('info-circle') -- cgit v1.2.1 From dd3934f7a2fdcafd6607d893da15c9a195065d54 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Oct 2016 16:44:47 +0200 Subject: Update to gitlab-shell 3.6.6 --- CHANGELOG | 2 +- GITLAB_SHELL_VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 894f4bc9af0..edbd7454548 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,7 @@ v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - - Use gitlab-shell v3.6.2 (GIT TRACE logging) + - Use gitlab-shell v3.6.6 - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 0f44168a4d5..4f2c1d15f6d 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.6.4 +3.6.6 -- cgit v1.2.1 From 8aa381c9c408baef295e721f4f11964de8db1aa0 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Oct 2016 14:45:26 +0000 Subject: Revert "Merge branch 'tests-use-tmpfs' into 'master'" This reverts merge request !6730 --- .gitlab-ci.yml | 2 -- spec/spec_helper.rb | 5 ----- 2 files changed, 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb6f691058e..8645488335e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,8 +19,6 @@ variables: before_script: - source ./scripts/prepare_build.sh - cp config/gitlab.yml.example config/gitlab.yml - - mkdir -p tmp/tests - - mount -t tmpfs tmpfs tmp/tests || echo "tmpfs mount failed, falling back to disc" - bundle --version - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - retry gem install knapsack diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f313bd4f249..b19f5824236 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -50,11 +50,6 @@ RSpec.configure do |config| example.run Rails.cache = caching_store end - - config.after(:each) do - FileUtils.rm_rf("tmp/tests/repositories") - FileUtils.mkdir_p("tmp/tests/repositories") - end end FactoryGirl::SyntaxRunner.class_eval do -- cgit v1.2.1 From b998479c81512b7c9a2cff28e7aabff3c4be0424 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 12 Oct 2016 13:32:48 +0200 Subject: API: Version information --- CHANGELOG | 1 + doc/api/README.md | 1 + doc/api/version.md | 23 +++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/version.rb | 12 ++++++++++++ spec/requests/api/version_spec.rb | 27 +++++++++++++++++++++++++++ 6 files changed, 65 insertions(+) create mode 100644 doc/api/version.md create mode 100644 lib/api/version.rb create mode 100644 spec/requests/api/version_spec.rb diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..401d0d66de3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.13.0 (unreleased) - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Simplify Mentionable concern instance methods + - API: Ability to retrieve version information (Robert Schilling) - Fix permission for setting an issue's due date - API: Multi-file commit !6096 (mahcsig) - Revert "Label list shows all issues (opened or closed) with that label" diff --git a/doc/api/README.md b/doc/api/README.md index 9e907689c80..75b098f2eeb 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -46,6 +46,7 @@ following locations: - [Todos](todos.md) - [Users](users.md) - [Validate CI configuration](ci/lint.md) +- [Version](version.md) ### Internal CI API diff --git a/doc/api/version.md b/doc/api/version.md new file mode 100644 index 00000000000..287d17cf97f --- /dev/null +++ b/doc/api/version.md @@ -0,0 +1,23 @@ +# Version API + +>**Note:** This feature was introduced in GitLab 8.13 + +Retrieve version information for this GitLab instance. Responds `200 OK` for +authenticated users. + +``` +GET /version +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/version +``` + +Example response: + +```json +{ + "version": "8.13.0-pre", + "revision": "4e963fe" +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 99722a0a65c..eb4e1fc504f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -73,6 +73,7 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + mount ::API::Version route :any, '*path' do error!('404 Not Found', 404) diff --git a/lib/api/version.rb b/lib/api/version.rb new file mode 100644 index 00000000000..9ba576bd828 --- /dev/null +++ b/lib/api/version.rb @@ -0,0 +1,12 @@ +module API + class Version < Grape::API + before { authenticate! } + + desc 'Get the version information of the GitLab instance.' do + detail 'This feature was introduced in GitLab 8.13.' + end + get '/version' do + { version: Gitlab::VERSION, revision: Gitlab::REVISION } + end + end +end diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb new file mode 100644 index 00000000000..54b69a0cae7 --- /dev/null +++ b/spec/requests/api/version_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + describe 'GET /version' do + context 'when unauthenticated' do + it 'returns authentication error' do + get api('/version') + + expect(response).to have_http_status(401) + end + end + + context 'when authenticated' do + let(:user) { create(:user) } + + it 'returns the version information' do + get api('/version', user) + + expect(response).to have_http_status(200) + expect(json_response['version']).to eq(Gitlab::VERSION) + expect(json_response['revision']).to eq(Gitlab::REVISION) + end + end + end +end -- cgit v1.2.1 From 2c5a95cbeb3ac4a19ad177d03d2703235e1f1c3c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 18:49:41 +0300 Subject: Rename users routing from /u/:username to /users/:username for consistency with other routes Signed-off-by: Dmitriy Zaporozhets --- config/routes/user.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/routes/user.rb b/config/routes/user.rb index 54bbcb18f6a..ae15b9d02a3 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -1,8 +1,5 @@ require 'constraints/user_url_constrainer' -get '/u/:username', to: redirect('/%{username}'), - constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Wed, 12 Oct 2016 17:43:20 +0100 Subject: Updated JS to work with issue index & show --- app/assets/javascripts/dispatcher.js | 1 + app/assets/javascripts/labels_select.js | 4 ++-- app/assets/javascripts/milestone_select.js | 2 +- app/assets/javascripts/users_select.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index adff73af79c..f3ef13ce20e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -21,6 +21,7 @@ shortcut_handler = null; switch (page) { case 'projects:boards:show': + case 'projects:boards:index': shortcut_handler = new ShortcutsNavigation(); break; case 'projects:merge_requests:index': diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index e356872624a..f1e719937c7 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -292,7 +292,7 @@ return; } - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { return; } if ($dropdown.hasClass('js-multiselect')) { @@ -334,7 +334,7 @@ page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = page === 'projects:merge_requests:index'; - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { if (label.isAny) { gl.issueBoards.BoardsStore.state.filters['label_name'] = []; } diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 26cc6eb0e96..cee42633c79 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -110,7 +110,7 @@ e.preventDefault(); return; } - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); e.preventDefault(); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index bcabda3ceb2..4c071f334fd 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -160,7 +160,7 @@ selectedId = user.id; return; } - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { selectedId = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.updateFiltersUrl(); -- cgit v1.2.1 From f32bc1f52662664048876b27e181a1cacda02580 Mon Sep 17 00:00:00 2001 From: Georg G Date: Wed, 12 Oct 2016 18:48:53 +0200 Subject: Add spec for Projects::GraphsController#languages --- .../controllers/projects/graphs_controller_spec.rb | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 spec/controllers/projects/graphs_controller_spec.rb diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb new file mode 100644 index 00000000000..3f4535b7a24 --- /dev/null +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' +require 'ostruct' + +describe Projects::GraphsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + sign_in(user) + project.team << [user, :master] + end + + describe 'GET #languages' do + let(:linguist_repository) do + OpenStruct.new(languages: { + 'Ruby' => 1000, + 'CoffeeScript' => 350, + 'PowerShell' => 15 + }) + end + + let(:expected_values) do + ps_color = "##{Digest::SHA256.hexdigest('PowerShell')[0...6]}" + [ + # colors from Linguist: + { value: 73.26, label: "Ruby", color: "#701516", highlight: "#701516" }, + { value: 25.64, label: "CoffeeScript", color: "#244776", highlight: "#244776" }, + # colors from SHA256 fallback: + { value: 1.1, label: "PowerShell", color: ps_color, highlight: ps_color } + ] + end + + before do + allow(Linguist::Repository).to receive(:new).and_return(linguist_repository) + end + + it 'sets the correct colour according to language' do + get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master') + + expect(assigns(:languages)).to eq(expected_values) + end + end +end -- cgit v1.2.1 From da64f9e544bc5f05818c3f66b278e6e30584a8b4 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 12 Oct 2016 18:54:23 +0200 Subject: Update health_check gem to `~> 2.2.0` Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/22278 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 9d98a34a0d5..901e60ef9e2 100644 --- a/Gemfile +++ b/Gemfile @@ -343,7 +343,7 @@ gem 'oauth2', '~> 1.2.0' gem 'paranoia', '~> 2.0' # Health check -gem 'health_check', '~> 2.1.0' +gem 'health_check', '~> 2.2.0' # System information gem 'vmstat', '~> 2.2' diff --git a/Gemfile.lock b/Gemfile.lock index 69804c8c533..924e3a6781a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -336,7 +336,7 @@ GEM thor tilt hashie (3.4.4) - health_check (2.1.0) + health_check (2.2.1) rails (>= 4.0) hipchat (1.5.2) httparty @@ -875,7 +875,7 @@ DEPENDENCIES grape-entity (~> 0.4.2) haml_lint (~> 0.18.2) hamlit (~> 2.6.1) - health_check (~> 2.1.0) + health_check (~> 2.2.0) hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) httparty (~> 0.13.3) -- cgit v1.2.1 From abfb4f6e32ac13929678039e324307ee3ef2af43 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 12 Oct 2016 19:00:38 +0200 Subject: Document Capybara errors from es6 in es5 file. --- doc/development/frontend.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index f879cd57e25..56c8516508e 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -223,3 +223,14 @@ For our currently-supported browsers, see our [requirements][requirements]. [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting [scss-style-guide]: scss_styleguide.md [requirements]: ../install/requirements.md#supported-web-browsers + +## Common Errors + +### Rspec (Capybara/Poltergeist) chokes on general JavaScript errors + +If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being thrown in tests, but +can't reproduce them manually, you may have included `ES6`-style JavaScript in files that don't +have the `.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file you're +working in (`git mv .js> `). + + -- cgit v1.2.1 From f1e5ca3a31c9c5c1c36aec8eaeaca89273855500 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 13 Oct 2016 01:12:00 +0800 Subject: Use any? instead of count > 0, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16832243 --- app/models/project_services/builds_email_service.rb | 2 +- app/models/project_services/pipelines_email_service.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb index fa66e5864b8..201b94b065b 100644 --- a/app/models/project_services/builds_email_service.rb +++ b/app/models/project_services/builds_email_service.rb @@ -43,7 +43,7 @@ class BuildsEmailService < Service end def can_test? - project.builds.count > 0 + project.builds.any? end def disabled_title diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index 701b879a73e..ec3c1bc85ee 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -39,7 +39,7 @@ class PipelinesEmailService < Service end def can_test? - project.pipelines.count > 0 + project.pipelines.any? end def disabled_title -- cgit v1.2.1 From c33e2f0ae923a5f84218a47ef111a1bc87cbb27f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 13 Oct 2016 01:23:05 +0800 Subject: New hash syntax, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16832366 --- app/views/notify/pipeline_failed_email.html.haml | 138 +++++++++++----------- app/views/notify/pipeline_success_email.html.haml | 114 +++++++++--------- 2 files changed, 126 insertions(+), 126 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 220684ccddf..89cbcba67e5 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -1,9 +1,9 @@ -%html{:lang => "en"} +%html{lang: "en"} %head - %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ - %meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/ - %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/ + %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/ + %meta{content: "width=device-width, initial-scale=1", name: "viewport"}/ + %meta{content: "IE=edge", "http-equiv": "X-UA-Compatible"}/ %title= message.subject :css /* CLIENT-SPECIFIC STYLES */ @@ -41,139 +41,139 @@ padding-right: 10px !important; } } - %body{:style => "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} - %table#body{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;"} + %body{style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} + %table#body{border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;"} %tbody %tr.line - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;"}   + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;"}   %tr.header - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} - %img{:alt => "GitLab", :height => "50", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), :width => "55"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} + %img{alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55"}/ %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} - %table.wrapper{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} + %table.wrapper{border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} - %table.content{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;border-collapse:separate;border-spacing:0;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} + %table.content{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;"} %tbody %tr.alert - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;"} - %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;margin:0 auto;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;"} + %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} - %img{:alt => "x", :height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), :style => "display:block;", :width => "13"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} + %img{alt: "x", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} Your pipeline has failed. %tr.spacer - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   %tr.section - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} - %table.info{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} + %table.info{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} - if @project.group - %a.muted{:href => group_url(@project.group), :style => "color:#333333;text-decoration:none;"} + %a.muted{href: group_url(@project.group), style: "color:#333333;text-decoration:none;"} = @project.group.name - else - %a.muted{:href => user_url(@project.namespace.owner), :style => "color:#333333;text-decoration:none;"} + %a.muted{href: user_url(@project.namespace.owner), style: "color:#333333;text-decoration:none;"} = @project.namespace.owner.name \/ - %a.muted{:href => namespace_project_url(@project.namespace, @project), :style => "color:#333333;text-decoration:none;"} + %a.muted{href: namespace_project_url(@project.namespace, @project), style: "color:#333333;text-decoration:none;"} = @project.name %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img{:height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), :style => "display:block;", :width => "13"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a.muted{:href => namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), :style => "color:#333333;text-decoration:none;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %a.muted{href: namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), style: "color:#333333;text-decoration:none;"} = @pipeline.ref %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img{:height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), :style => "display:block;", :width => "13"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a{:href => namespace_project_commit_url(@project.namespace, @project, @pipeline.sha), :style => "color:#3084bb;text-decoration:none;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %a{href: namespace_project_commit_url(@project.namespace, @project, @pipeline.sha), style: "color:#3084bb;text-decoration:none;"} = @pipeline.short_sha - if @merge_request in - %a{:href => namespace_project_merge_request_url(@project.namespace, @project, @merge_request), :style => "color:#3084bb;text-decoration:none;"} + %a{href: namespace_project_merge_request_url(@project.namespace, @project, @merge_request), style: "color:#3084bb;text-decoration:none;"} = @merge_request.to_reference - .commit{:style => "color:#5c5c5c;font-weight:300;"} + .commit{style: "color:#5c5c5c;font-weight:300;"} = @pipeline.git_commit_message.truncate(50) %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Author - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Author + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} %tbody %tr - commit = @pipeline.commit - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img.avatar{:height => "24", :src => avatar_icon(commit.author || commit.author_email, 24), :style => "display:block;border-radius:12px;margin:-2px 0;", :width => "24"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img.avatar{height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - if commit.author.nil? %span = commit.author_name - else - %a.muted{:href => user_url(commit.author), :style => "color:#333333;text-decoration:none;"} + %a.muted{href: user_url(commit.author), style: "color:#333333;text-decoration:none;"} = commit.author.name %tr.spacer - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   - failed = @pipeline.statuses.latest.failed %tr.pre-section - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;"} Pipeline - %a{:href => namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id), :style => "color:#3084bb;text-decoration:none;"} + %a{href: namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id), style: "color:#3084bb;text-decoration:none;"} = "\##{@pipeline.id}" had = failed.size failed = "#{'build'.pluralize(failed.size)}." %tr.warning - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;"} Logs may contain sensitive data. Please consider before forwarding this email. %tr.section - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;"} - %table.builds{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;border-collapse:collapse;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;"} + %table.builds{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:collapse;"} %tbody - failed.each do |build| %tr.build-state - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} - %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} + %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;"} - %img{:alt => "x", :height => "10", :src => image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), :style => "display:block;", :width => "10"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;"} + %img{alt: "x", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;"} = build.stage - %td{:align => "right", :style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} - %a{:href => namespace_project_build_url(@project.namespace, @project, build.id), :style => "color:#3084bb;text-decoration:none;"} + %td{align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} + %a{href: namespace_project_build_url(@project.namespace, @project, build.id), style: "color:#3084bb;text-decoration:none;"} = build.name %tr.build-log - %td{:colspan => "2", :style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"} - %pre{:style => "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;"} + %td{colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"} + %pre{style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;"} = build.trace_html(last_lines: 10).html_safe %tr.footer - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} - %img{:alt => "GitLab", :height => "33", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), :style => "display:block;margin:0 auto 1em;", :width => "90"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} + %img{alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90"}/ %div - %a{:href => profile_notifications_url, :style => "color:#3084bb;text-decoration:none;"} Manage all notifications + %a{href: profile_notifications_url, style: "color:#3084bb;text-decoration:none;"} Manage all notifications · - %a{:href => help_url, :style => "color:#3084bb;text-decoration:none;"} Help + %a{href: help_url, style: "color:#3084bb;text-decoration:none;"} Help %div You're receiving this email because of your account on = succeed "." do - %a{:href => root_url, :style => "color:#3084bb;text-decoration:none;"}= Gitlab.config.gitlab.host + %a{href: root_url, style: "color:#3084bb;text-decoration:none;"}= Gitlab.config.gitlab.host diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 7ff2d31d1b7..4e96a95147a 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -1,9 +1,9 @@ -%html{:lang => "en"} +%html{lang: "en"} %head - %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ - %meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/ - %meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/ + %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/ + %meta{content: "width=device-width, initial-scale=1", name: "viewport"}/ + %meta{content: "IE=edge", "http-equiv": "X-UA-Compatible"}/ %title= message.subject :css /* CLIENT-SPECIFIC STYLES */ @@ -41,116 +41,116 @@ padding-right: 10px !important; } } - %body{:style => "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} - %table#body{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;"} + %body{style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} + %table#body{border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;"} %tbody %tr.line - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;"}   + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;"}   %tr.header - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} - %img{:alt => "GitLab", :height => "50", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), :width => "55"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} + %img{alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55"}/ %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} - %table.wrapper{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} + %table.wrapper{border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} - %table.content{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;border-collapse:separate;border-spacing:0;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} + %table.content{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;"} %tbody %tr.success - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;"} - %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;margin:0 auto;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;"} + %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} - %img{:alt => "✓", :height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), :style => "display:block;", :width => "13"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} + %img{alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} Your pipeline has passed. %tr.spacer - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   %tr.section - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} - %table.info{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "width:100%;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} + %table.info{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} - if @project.group - %a.muted{:href => group_url(@project.group), :style => "color:#333333;text-decoration:none;"} + %a.muted{href: group_url(@project.group), style: "color:#333333;text-decoration:none;"} = @project.group.name - else - %a.muted{:href => user_url(@project.namespace.owner), :style => "color:#333333;text-decoration:none;"} + %a.muted{href: user_url(@project.namespace.owner), style: "color:#333333;text-decoration:none;"} = @project.namespace.owner.name \/ - %a.muted{:href => namespace_project_url(@project.namespace, @project), :style => "color:#333333;text-decoration:none;"} + %a.muted{href: namespace_project_url(@project.namespace, @project), style: "color:#333333;text-decoration:none;"} = @project.name %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img{:height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), :style => "display:block;", :width => "13"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a.muted{:href => namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), :style => "color:#333333;text-decoration:none;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %a.muted{href: namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), style: "color:#333333;text-decoration:none;"} = @pipeline.ref %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} %tbody %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img{:height => "13", :src => image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), :style => "display:block;", :width => "13"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a{:href => namespace_project_commit_url(@project.namespace, @project, @pipeline.sha), :style => "color:#3084bb;text-decoration:none;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %a{href: namespace_project_commit_url(@project.namespace, @project, @pipeline.sha), style: "color:#3084bb;text-decoration:none;"} = @pipeline.short_sha - if @merge_request in - %a{:href => namespace_project_merge_request_url(@project.namespace, @project, @merge_request), :style => "color:#3084bb;text-decoration:none;"} + %a{href: namespace_project_merge_request_url(@project.namespace, @project, @merge_request), style: "color:#3084bb;text-decoration:none;"} = @merge_request.to_reference - .commit{:style => "color:#5c5c5c;font-weight:300;"} + .commit{style: "color:#5c5c5c;font-weight:300;"} = @pipeline.git_commit_message.truncate(50) %tr - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Author - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{:border => "0", :cellpadding => "0", :cellspacing => "0", :style => "border-collapse:collapse;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Author + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} + %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} %tbody %tr - commit = @pipeline.commit - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img.avatar{:height => "24", :src => avatar_icon(commit.author || commit.author_email, 24), :style => "display:block;border-radius:12px;margin:-2px 0;", :width => "24"}/ - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} + %img.avatar{height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - if commit.author.nil? %span = commit.author_name - else - %a.muted{:href => user_url(commit.author), :style => "color:#333333;text-decoration:none;"} + %a.muted{href: user_url(commit.author), style: "color:#333333;text-decoration:none;"} = commit.author.name %tr.spacer - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   %tr.success-message - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"} + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"} - build_count = @pipeline.statuses.latest.size - stage_count = @pipeline.stages.size Pipeline - %a{:href => namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id), :style => "color:#3084bb;text-decoration:none;"} + %a{href: namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id), style: "color:#3084bb;text-decoration:none;"} = "\##{@pipeline.id}" successfully completed = "#{build_count} #{'build'.pluralize(build_count)}" in = "#{stage_count} #{'stage'.pluralize(stage_count)}." %tr.footer - %td{:style => "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} - %img{:alt => "GitLab", :height => "33", :src => image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), :style => "display:block;margin:0 auto 1em;", :width => "90"}/ + %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} + %img{alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90"}/ %div - %a{:href => profile_notifications_url, :style => "color:#3084bb;text-decoration:none;"} Manage all notifications + %a{href: profile_notifications_url, style: "color:#3084bb;text-decoration:none;"} Manage all notifications · - %a{:href => help_url, :style => "color:#3084bb;text-decoration:none;"} Help + %a{href: help_url, style: "color:#3084bb;text-decoration:none;"} Help %div You're receiving this email because of your account on = succeed "." do - %a{:href => root_url, :style => "color:#3084bb;text-decoration:none;"}= Gitlab.config.gitlab.host + %a{href: root_url, style: "color:#3084bb;text-decoration:none;"}= Gitlab.config.gitlab.host -- cgit v1.2.1 From 16e6431c83af0a3c862a0e306f1810774033a6bb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 13 Oct 2016 01:43:23 +0800 Subject: Use helpers from GitlabRoutingHelper, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16832930 https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16832936 https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16833033 --- app/views/notify/pipeline_failed_email.html.haml | 4 ++-- app/views/notify/pipeline_failed_email.text.erb | 4 ++-- app/views/notify/pipeline_success_email.html.haml | 4 ++-- app/views/notify/pipeline_success_email.text.erb | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 89cbcba67e5..e76411755e7 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -83,7 +83,7 @@ %a.muted{href: user_url(@project.namespace.owner), style: "color:#333333;text-decoration:none;"} = @project.namespace.owner.name \/ - %a.muted{href: namespace_project_url(@project.namespace, @project), style: "color:#333333;text-decoration:none;"} + %a.muted{href: project_url(@project), style: "color:#333333;text-decoration:none;"} = @project.name %tr %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch @@ -109,7 +109,7 @@ = @pipeline.short_sha - if @merge_request in - %a{href: namespace_project_merge_request_url(@project.namespace, @project, @merge_request), style: "color:#3084bb;text-decoration:none;"} + %a{href: merge_request_url(@merge_request), style: "color:#3084bb;text-decoration:none;"} = @merge_request.to_reference .commit{style: "color:#5c5c5c;font-weight:300;"} = @pipeline.git_commit_message.truncate(50) diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb index 56379411be4..b632d94facc 100644 --- a/app/views/notify/pipeline_failed_email.text.erb +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -1,9 +1,9 @@ Your pipeline has failed. -Project: <%= @project.name %> ( <%= namespace_project_url(@project.namespace, @project) %> ) +Project: <%= @project.name %> ( <%= project_url(@project) %> ) Branch: <%= @pipeline.ref %> ( <%= namespace_project_commits_url(@project.namespace, @project, @pipeline.ref) %> ) <% if @merge_request -%> -Merge Request: <%= @merge_request.to_reference %> ( <%= namespace_project_merge_request_url(@project.namespace, @project, @merge_request) %> ) +Merge Request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge_request) %> ) <% end -%> Commit: <%= @pipeline.short_sha %> ( <%= namespace_project_commit_url(@project.namespace, @project, @pipeline.sha) %> ) diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 4e96a95147a..4696726b75e 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -83,7 +83,7 @@ %a.muted{href: user_url(@project.namespace.owner), style: "color:#333333;text-decoration:none;"} = @project.namespace.owner.name \/ - %a.muted{href: namespace_project_url(@project.namespace, @project), style: "color:#333333;text-decoration:none;"} + %a.muted{href: project_url(@project), style: "color:#333333;text-decoration:none;"} = @project.name %tr %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch @@ -109,7 +109,7 @@ = @pipeline.short_sha - if @merge_request in - %a{href: namespace_project_merge_request_url(@project.namespace, @project, @merge_request), style: "color:#3084bb;text-decoration:none;"} + %a{href: merge_request_url(@merge_request), style: "color:#3084bb;text-decoration:none;"} = @merge_request.to_reference .commit{style: "color:#5c5c5c;font-weight:300;"} = @pipeline.git_commit_message.truncate(50) diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index 29b290d9704..15f22905bc6 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -1,9 +1,9 @@ Your pipeline has passed. -Project: <%= @project.name %> ( <%= namespace_project_url(@project.namespace, @project) %> ) +Project: <%= @project.name %> ( <%= project_url(@project) %> ) Branch: <%= @pipeline.ref %> ( <%= namespace_project_commits_url(@project.namespace, @project, @pipeline.ref) %> ) <% if @merge_request -%> -Merge Request: <%= @merge_request.to_reference %> ( <%= namespace_project_merge_request_url(@project.namespace, @project, @merge_request) %> ) +Merge Request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge_request) %> ) <% end -%> Commit: <%= @pipeline.short_sha %> ( <%= namespace_project_commit_url(@project.namespace, @project, @pipeline.sha) %> ) -- cgit v1.2.1 From 2461e10912b484c6942cd26f8fffd5c5557befa7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 13:44:33 +0200 Subject: Execute pipeline hooks asynchronously --- app/models/ci/pipeline.rb | 7 ++++++- app/workers/pipeline_hooks_worker.rb | 9 +++++++++ spec/workers/pipeline_hooks_worker_spec.rb | 23 +++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 app/workers/pipeline_hooks_worker.rb create mode 100644 spec/workers/pipeline_hooks_worker_spec.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2cf9892edc5..13a58a7a308 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -3,6 +3,7 @@ module Ci extend Ci::Model include HasStatus include Importable + include AfterCommitQueue self.table_name = 'ci_commits' @@ -71,7 +72,11 @@ module Ci end after_transition do |pipeline, transition| - pipeline.execute_hooks unless transition.loopback? + next if transition.loopback? + + pipeline.run_after_commit do + PipelineHooksWorker.perform_async(id) + end end end diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb new file mode 100644 index 00000000000..ab5e9f6daad --- /dev/null +++ b/app/workers/pipeline_hooks_worker.rb @@ -0,0 +1,9 @@ +class PipelineHooksWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id) + .try(:execute_hooks) + end +end diff --git a/spec/workers/pipeline_hooks_worker_spec.rb b/spec/workers/pipeline_hooks_worker_spec.rb new file mode 100644 index 00000000000..035e329839f --- /dev/null +++ b/spec/workers/pipeline_hooks_worker_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe PipelineHooksWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'executes hooks for the pipeline' do + expect_any_instance_of(Ci::Pipeline) + .to receive(:execute_hooks) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 5c36905a75e621669ced3c9bf850294855c563bb Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 12 Oct 2016 17:42:56 -0500 Subject: de-duplicate project namespace markup --- app/views/notify/pipeline_failed_email.html.haml | 10 ++++------ app/views/notify/pipeline_success_email.html.haml | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index e76411755e7..7c95ea699a2 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -76,12 +76,10 @@ %tr %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} - - if @project.group - %a.muted{href: group_url(@project.group), style: "color:#333333;text-decoration:none;"} - = @project.group.name - - else - %a.muted{href: user_url(@project.namespace.owner), style: "color:#333333;text-decoration:none;"} - = @project.namespace.owner.name + - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name + - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) + %a.muted{href: namespace_url, style: "color:#333333;text-decoration:none;"} + = namespace_name \/ %a.muted{href: project_url(@project), style: "color:#333333;text-decoration:none;"} = @project.name diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 4696726b75e..23b4f645d78 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -76,12 +76,10 @@ %tr %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} - - if @project.group - %a.muted{href: group_url(@project.group), style: "color:#333333;text-decoration:none;"} - = @project.group.name - - else - %a.muted{href: user_url(@project.namespace.owner), style: "color:#333333;text-decoration:none;"} - = @project.namespace.owner.name + - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name + - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) + %a.muted{href: namespace_url, style: "color:#333333;text-decoration:none;"} + = namespace_name \/ %a.muted{href: project_url(@project), style: "color:#333333;text-decoration:none;"} = @project.name -- cgit v1.2.1 From ce4dad45557888066733bb20470bf56b87d4c8b7 Mon Sep 17 00:00:00 2001 From: Georg G Date: Thu, 13 Oct 2016 07:01:34 +0200 Subject: Use test double and matchers --- spec/controllers/projects/graphs_controller_spec.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index 3f4535b7a24..dd743356145 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' -require 'ostruct' describe Projects::GraphsController do let(:project) { create(:project) } @@ -12,7 +11,7 @@ describe Projects::GraphsController do describe 'GET #languages' do let(:linguist_repository) do - OpenStruct.new(languages: { + double(languages: { 'Ruby' => 1000, 'CoffeeScript' => 350, 'PowerShell' => 15 @@ -23,10 +22,10 @@ describe Projects::GraphsController do ps_color = "##{Digest::SHA256.hexdigest('PowerShell')[0...6]}" [ # colors from Linguist: - { value: 73.26, label: "Ruby", color: "#701516", highlight: "#701516" }, - { value: 25.64, label: "CoffeeScript", color: "#244776", highlight: "#244776" }, + { label: "Ruby", color: "#701516", highlight: "#701516" }, + { label: "CoffeeScript", color: "#244776", highlight: "#244776" }, # colors from SHA256 fallback: - { value: 1.1, label: "PowerShell", color: ps_color, highlight: ps_color } + { label: "PowerShell", color: ps_color, highlight: ps_color } ] end @@ -37,7 +36,9 @@ describe Projects::GraphsController do it 'sets the correct colour according to language' do get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master') - expect(assigns(:languages)).to eq(expected_values) + expected_values.each do |val| + expect(assigns(:languages)).to include(include(val)) + end end end end -- cgit v1.2.1 From 36aea928a407376787a961bbc03ca3946a55902b Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 13 Oct 2016 00:18:00 -0500 Subject: invert if statement order --- app/views/notify/pipeline_failed_email.html.haml | 8 ++++---- app/views/notify/pipeline_failed_email.text.erb | 6 +++--- app/views/notify/pipeline_success_email.html.haml | 8 ++++---- app/views/notify/pipeline_success_email.text.erb | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 7c95ea699a2..1f7b354f4bc 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -121,12 +121,12 @@ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} %img.avatar{height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24"}/ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - - if commit.author.nil? - %span - = commit.author_name - - else + - if commit.author %a.muted{href: user_url(commit.author), style: "color:#333333;text-decoration:none;"} = commit.author.name + - else + %span + = commit.author_name %tr.spacer %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb index b632d94facc..1b249c0cb7c 100644 --- a/app/views/notify/pipeline_failed_email.text.erb +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -9,10 +9,10 @@ Merge Request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge Commit: <%= @pipeline.short_sha %> ( <%= namespace_project_commit_url(@project.namespace, @project, @pipeline.sha) %> ) Commit Message: <%= @pipeline.git_commit_message.truncate(50) %> <% commit = @pipeline.commit -%> -<% if commit.author.nil? -%> -Commit Author: <%= commit.author_name %> -<% else -%> +<% if commit.author -%> Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> ) +<% else -%> +Commit Author: <%= commit.author_name %> <% end -%> <% failed = @pipeline.statuses.latest.failed -%> diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 23b4f645d78..81b32524fb2 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -121,12 +121,12 @@ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} %img.avatar{height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24"}/ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - - if commit.author.nil? - %span - = commit.author_name - - else + - if commit.author %a.muted{href: user_url(commit.author), style: "color:#333333;text-decoration:none;"} = commit.author.name + - else + %span + = commit.author_name %tr.spacer %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"}   diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index 15f22905bc6..7e7eec6d09c 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -9,10 +9,10 @@ Merge Request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge Commit: <%= @pipeline.short_sha %> ( <%= namespace_project_commit_url(@project.namespace, @project, @pipeline.sha) %> ) Commit Message: <%= @pipeline.git_commit_message.truncate(50) %> <% commit = @pipeline.commit -%> -<% if commit.author.nil? -%> -Commit Author: <%= commit.author_name %> -<% else -%> +<% if commit.author -%> Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> ) +<% else -%> +Commit Author: <%= commit.author_name %> <% end -%> <% build_count = @pipeline.statuses.latest.size -%> -- cgit v1.2.1 From 48015deda12cdff6b814785c97316e5b77c4a03e Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 12 Oct 2016 11:16:29 +0500 Subject: Union examples in event spec fo speed up --- spec/models/event_spec.rb | 110 +++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 06cac929bbf..733b79079ed 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -27,17 +27,17 @@ describe Event, models: true do end describe "Push event" do - before do - project = create(:project) - @user = project.owner - @event = create_event(project, @user) + let(:project) { create(:project) } + let(:user) { project.owner } + let(:event) { create_event(project, user) } + + it do + expect(event.push?).to be_truthy + expect(event.visible_to_user?).to be_truthy + expect(event.tag?).to be_falsey + expect(event.branch_name).to eq("master") + expect(event.author).to eq(user) end - - it { expect(@event.push?).to be_truthy } - it { expect(@event.visible_to_user?).to be_truthy } - it { expect(@event.tag?).to be_falsey } - it { expect(@event.branch_name).to eq("master") } - it { expect(@event.author).to eq(@user) } end describe '#note?' do @@ -59,8 +59,8 @@ describe Event, models: true do describe '#visible_to_user?' do let(:project) { create(:empty_project, :public) } let(:non_member) { create(:user) } - let(:member) { create(:user) } - let(:guest) { create(:user) } + let(:member) { create(:user) } + let(:guest) { create(:user) } let(:author) { create(:author) } let(:assignee) { create(:user) } let(:admin) { create(:admin) } @@ -79,23 +79,27 @@ describe Event, models: true do context 'for non confidential issues' do let(:target) { issue } - it { expect(event.visible_to_user?(non_member)).to eq true } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq true + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end end context 'for confidential issues' do let(:target) { confidential_issue } - it { expect(event.visible_to_user?(non_member)).to eq false } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq false + expect(event.visible_to_user?(admin)).to eq true + end end end @@ -103,23 +107,27 @@ describe Event, models: true do context 'on non confidential issues' do let(:target) { note_on_issue } - it { expect(event.visible_to_user?(non_member)).to eq true } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq true + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end end context 'on confidential issues' do let(:target) { note_on_confidential_issue } - it { expect(event.visible_to_user?(non_member)).to eq false } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq false + expect(event.visible_to_user?(admin)).to eq true + end end end @@ -129,22 +137,26 @@ describe Event, models: true do let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } let(:target) { note_on_merge_request } - it { expect(event.visible_to_user?(non_member)).to eq true } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq true + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end context 'private project' do let(:project) { create(:project, :private) } - it { expect(event.visible_to_user?(non_member)).to eq false } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq false + expect(event.visible_to_user?(admin)).to eq true + end end end end @@ -214,6 +226,6 @@ describe Event, models: true do action: Event::PUSHED, data: data, author_id: user.id - }.merge(attrs)) + }.merge!(attrs)) end end -- cgit v1.2.1 From 9521736ebc062ba6f693da389f895061ac7a8b3a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 13 Oct 2016 09:19:30 +0200 Subject: updated var name based on feedback --- app/models/cycle_analytics.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb index 0f3fd995681..8ed4a56b19b 100644 --- a/app/models/cycle_analytics.rb +++ b/app/models/cycle_analytics.rb @@ -2,7 +2,7 @@ class CycleAnalytics include Gitlab::Database::Median include Gitlab::Database::DateTime - DEPLOYED_CHECK_METRICS = %i[production staging] + DEPLOYMENT_METRIC_STAGES = %i[production staging] def initialize(project, from:) @project = project @@ -93,7 +93,7 @@ class CycleAnalytics join(MergeRequest::Metrics.arel_table). on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id])) - if DEPLOYED_CHECK_METRICS.include?(name) + if DEPLOYMENT_METRIC_STAGES.include?(name) # Limit to merge requests that have been deployed to production after `@from` query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) end -- cgit v1.2.1 From 6c88a9dfbbee692e872d6f237cb4ca325ebc4a26 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 13 Oct 2016 09:30:18 +0200 Subject: Add more info on the new CI permissions model [ci skip] --- doc/user/project/new_ci_build_permissions_model.md | 54 ++++++++++++++-------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 5253825d507..19385a99d64 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -87,20 +87,6 @@ your Runners in the most possible secure way, by avoiding the following: By using an insecure GitLab Runner configuration, you allow the rogue developers to steal the tokens of other builds. -## Debugging problems - -With the new permission model in place, there may be times that your build will -fail. This is most likely because your project tries to access other project's -sources, and you don't have the appropriate permissions. In the build log look -for information about 403 or forbidden access messages - -As an Administrator, you can verify that the user is a member of the group or -project they're trying to have access to, and you can impersonate the user to -retry the failing build in order to verify that everything is correct. - -You need to make sure that your installation has HTTPS cloning enabled. -HTTPS support is required by GitLab CI to clone all sources. - ## Build triggers [Build triggers][triggers] do not support the new permission model. @@ -152,17 +138,46 @@ with GitLab 8.12. ## Making use of the new CI build permissions model -With the new build permission model, there is now an easy way to access all +With the new build permissions model, there is now an easy way to access all dependent source code in a project. That way, we can: 1. Access a project's Git submodules 1. Access private container images 1. Access project's and submodule LFS objects -Let's see how that works with Git submodules and private Docker images hosted on +Below you can see the prerequisites needed to make use of the new permissions +model and how that works with Git submodules and private Docker images hosted on the container registry. -## Git submodules +### Prerequisites to use the new permissions model + +With the new permissions model in place, there may be times that your build will +fail. This is most likely because your project tries to access other project's +sources, and you don't have the appropriate permissions. In the build log look +for information about 403 or forbidden access messages. + +In short here's what you need to do should you encounter any issues. + +As an administrator: + +- **500 errors**: You will need to update [GitLab Workhorse][workhorse] to at + least 0.8.2. This is done automatically for Omnibus installations, you need to + check manually for installations from source. +- **500 errors**: Check if you have another web proxy sitting in front of NGINX (HAProxy, + Apache, etc.). It might be a good idea to let GitLab use the internal NGINX + web server and not disable it completely. See [this comment][comment] for an + example. +- **403 errors**: You need to make sure that your installation has [HTTP(S) + cloning enabled][https]. HTTP(S) support is now a **requirement** by GitLab CI + to clone all sources. + +As a user: + +- Make sure you are a member of the group or project you're trying to have + access to. As an Administrator, you can verify that by impersonating the user + and retry the failing build in order to verify that everything is correct. + +### Git submodules > It often happens that while working on one project, you need to use another @@ -286,7 +301,10 @@ test: - docker run $CI_REGISTRY/group/other-project:latest ``` -[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules [build permissions]: ../permissions.md#builds-permissions +[comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302 [ext]: ../permissions.md#external-users +[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules +[https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols [triggers]: ../../ci/triggers/README.md +[workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse -- cgit v1.2.1 From 3d6f18cec57fc6c7776bd05558e0d03dd3b141e1 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 12 Oct 2016 20:38:33 +0200 Subject: GrapeDSL for variables --- lib/api/variables.rb | 89 +++++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/lib/api/variables.rb b/lib/api/variables.rb index f6495071a11..b9fb3c21dbb 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -4,27 +4,29 @@ module API before { authenticate! } before { authorize! :admin_build, user_project } + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects do - # Get project variables - # - # Parameters: - # id (required) - The ID of a project - # page (optional) - The page number for pagination - # per_page (optional) - The value of items per page to show - # Example Request: - # GET /projects/:id/variables + desc 'Get project variables' do + success Entities::Variable + end + params do + optional :page, type: Integer, desc: 'The page number for pagination' + optional :per_page, type: Integer, desc: 'The value of items per page to show' + end get ':id/variables' do variables = user_project.variables present paginate(variables), with: Entities::Variable end - # Get specific variable of a project - # - # Parameters: - # id (required) - The ID of a project - # key (required) - The `key` of variable - # Example Request: - # GET /projects/:id/variables/:key + desc 'Get a specific variable from a project' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end get ':id/variables/:key' do key = params[:key] variable = user_project.variables.find_by(key: key.to_s) @@ -34,18 +36,15 @@ module API present variable, with: Entities::Variable end - # Create a new variable in project - # - # Parameters: - # id (required) - The ID of a project - # key (required) - The key of variable - # value (required) - The value of variable - # Example Request: - # POST /projects/:id/variables + desc 'Create a new variable in a project' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + requires :value, type: String, desc: 'The value of the variable' + end post ':id/variables' do - required_attributes! [:key, :value] - - variable = user_project.variables.create(key: params[:key], value: params[:value]) + variable = user_project.variables.create(declared(params, include_parent_namespaces: false).to_h) if variable.valid? present variable, with: Entities::Variable @@ -54,41 +53,37 @@ module API end end - # Update existing variable of a project - # - # Parameters: - # id (required) - The ID of a project - # key (optional) - The `key` of variable - # value (optional) - New value for `value` field of variable - # Example Request: - # PUT /projects/:id/variables/:key + desc 'Update an existing variable from a project' do + success Entities::Variable + end + params do + optional :key, type: String, desc: 'The key of the variable' + optional :value, type: String, desc: 'The value of the variable' + end put ':id/variables/:key' do - variable = user_project.variables.find_by(key: params[:key].to_s) + variable = user_project.variables.find_by(key: params[:key]) return not_found!('Variable') unless variable - attrs = attributes_for_keys [:value] - if variable.update(attrs) + if variable.update(value: params[:value]) present variable, with: Entities::Variable else render_validation_error!(variable) end end - # Delete existing variable of a project - # - # Parameters: - # id (required) - The ID of a project - # key (required) - The ID of a variable - # Example Request: - # DELETE /projects/:id/variables/:key + desc 'Delete an existing variable from a project' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end delete ':id/variables/:key' do - variable = user_project.variables.find_by(key: params[:key].to_s) + variable = user_project.variables.find_by(key: params[:key]) return not_found!('Variable') unless variable - variable.destroy - present variable, with: Entities::Variable + present variable.destroy, with: Entities::Variable end end end -- cgit v1.2.1 From cfd0d66c8308c0f259e39322193c8ddb34ec28f9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 10:04:58 +0200 Subject: Reassign secret token when regenerating one --- lib/gitlab/backend/shell.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index d0060fbaca1..9cec71a3222 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -47,8 +47,8 @@ module Gitlab unless File.size?(secret_file) # Generate a new token of 16 random hexadecimal characters and store it in secret_file. - token = SecureRandom.hex(16) - File.write(secret_file, token) + @secret_token = SecureRandom.hex(16) + File.write(secret_file, @secret_token) end link_path = File.join(shell_path, '.gitlab_shell_secret') -- cgit v1.2.1 From 3939d4dfbfda5f7bb5133e12573c80f600ce451f Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 13 Oct 2016 09:58:06 +0100 Subject: Fix secret names in HA docs --- doc/administration/high_availability/gitlab.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md index 8a881ce8863..137fed35a73 100644 --- a/doc/administration/high_availability/gitlab.md +++ b/doc/administration/high_availability/gitlab.md @@ -101,9 +101,9 @@ need some additional configuration. ```ruby gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860' - gitlab_rails['secret_token'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa' - gitlab_ci['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d' - gitlab_ci['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964' + gitlab_rails['otp_key_base'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa' + gitlab_rails['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d' + gitlab_rails['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964' ``` 1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations -- cgit v1.2.1 From 96ee975805dd9bbb20bd26edf2dcc7bfdc858c78 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Oct 2016 10:25:38 +0100 Subject: Moved how we remove event listeners --- app/assets/javascripts/members.js.es6 | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 5dc6990bdb6..a0cd20f21e8 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -3,20 +3,13 @@ class Members { constructor() { - this.removeListeners(); this.addListeners(); } - removeListeners() { - $('.project_member, .group_member').off('ajax:success'); - $('.js-member-update-control').off('change'); - $('.js-edit-member-form').off('ajax:success'); - } - addListeners() { - $('.project_member, .group_member').on('ajax:success', this.removeRow); - $('.js-member-update-control').on('change', this.formSubmit); - $('.js-edit-member-form').on('ajax:success', this.formSuccess); + $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow); + $('.js-member-update-control').off('change').on('change', this.formSubmit); + $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess); } removeRow(e) { -- cgit v1.2.1 From 3c476ee62512a0642ca3e5e6b228f9abdd867a34 Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Tue, 11 Oct 2016 16:52:49 +0100 Subject: Maintain "force_remove_source_branch" options on Merge Request unless specified --- CHANGELOG | 1 + app/services/merge_requests/update_service.rb | 5 ++++- spec/services/merge_requests/update_service_spec.rb | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5e026b4ec94..279501518c1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.13.0 (unreleased) - Update Gitlab Shell to fix some problems with moving projects between storages - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references + - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified - Simplify Mentionable concern instance methods - API: Ability to retrieve version information (Robert Schilling) - Fix permission for setting an issue's due date diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 9dbec49d163..a37cc3fdf21 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -15,7 +15,10 @@ module MergeRequests params.except!(:target_branch, :force_remove_source_branch) end - merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) + if params[:force_remove_source_branch].present? + merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) + end + handle_wip_event(merge_request) update(merge_request) end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index fd5f94047c2..2433a7dad06 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -105,6 +105,18 @@ describe MergeRequests::UpdateService, services: true do expect(note).not_to be_nil expect(note.note).to eq 'Target branch changed from `master` to `target`' end + + context 'when not including source branch removal options' do + before do + opts.delete(:force_remove_source_branch) + end + + it 'maintains the original options' do + update_merge_request(opts) + + expect(@merge_request.merge_params["force_remove_source_branch"]).to eq("1") + end + end end context 'todos' do -- cgit v1.2.1 From 9ec7aeac2362151e15e59531f347f2d7924437f8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Oct 2016 11:18:56 +0100 Subject: Tweaked position of badge in panel headings Various UI tweaks --- app/assets/stylesheets/framework/panels.scss | 5 +++++ app/assets/stylesheets/framework/selects.scss | 2 +- app/assets/stylesheets/pages/members.scss | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index c6f30e144fd..5ba0486177f 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -13,6 +13,11 @@ .dropdown-menu-toggle { line-height: 20px; } + + .badge { + margin-top: -2px; + margin-left: 5px; + } } .panel-body { diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index b309e2ad9f4..58f9db0fb21 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -94,7 +94,7 @@ } .select2-search-choice { - margin: 8px 0 0 8px; + margin: 5px 0 0 8px; box-shadow: none; border-color: $input-border; color: $gl-text-color; diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index ff4fd751f26..756efa9c7fa 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -9,6 +9,10 @@ float: left; width: 50%; } + + strong { + font-weight: 600; + } } .controls { -- cgit v1.2.1 From fafc5a1777484ba6b1b12c5e88f3fc783fb4d839 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 12:45:16 +0200 Subject: Perform CI build hooks asynchronously using worker --- app/models/ci/build.rb | 5 +++-- app/workers/build_hooks_worker.rb | 9 +++++++++ spec/workers/build_hooks_worker_spec.rb | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 app/workers/build_hooks_worker.rb create mode 100644 spec/workers/build_hooks_worker_spec.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5dbf66173de..b0d13c1bf06 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1,6 +1,7 @@ module Ci class Build < CommitStatus include TokenAuthenticatable + include AfterCommitQueue belongs_to :runner, class_name: 'Ci::Runner' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' @@ -75,12 +76,12 @@ module Ci state_machine :status do after_transition pending: :running do |build| - build.execute_hooks + build.run_after_commit { BuildHooksWorker.perform_async(id) } end after_transition any => [:success, :failed, :canceled] do |build| build.update_coverage - build.execute_hooks + build.run_after_commit { BuildHooksWorker.perform_async(id) } end after_transition any => [:success] do |build| diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb new file mode 100644 index 00000000000..e22ececb3fd --- /dev/null +++ b/app/workers/build_hooks_worker.rb @@ -0,0 +1,9 @@ +class BuildHooksWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(build_id) + Ci::Build.find_by(id: build_id) + .try(:execute_hooks) + end +end diff --git a/spec/workers/build_hooks_worker_spec.rb b/spec/workers/build_hooks_worker_spec.rb new file mode 100644 index 00000000000..97654a93f5c --- /dev/null +++ b/spec/workers/build_hooks_worker_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe BuildHooksWorker do + describe '#perform' do + context 'when build exists' do + let!(:build) { create(:ci_build) } + + it 'calls build hooks' do + expect_any_instance_of(Ci::Build) + .to receive(:execute_hooks) + + described_class.new.perform(build.id) + end + end + + context 'when build does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 2273b5d6d6f64a14d41bc8d25287d615502d2622 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 13 Oct 2016 12:52:51 +0200 Subject: Sort API mounts --- lib/api/api.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/api/api.rb b/lib/api/api.rb index 99722a0a65c..a6f32a9fc04 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -31,11 +31,12 @@ module API # Keep in alphabetical order mount ::API::AccessRequests mount ::API::AwardEmoji + mount ::API::Boards mount ::API::Branches mount ::API::BroadcastMessages mount ::API::Builds - mount ::API::CommitStatuses mount ::API::Commits + mount ::API::CommitStatuses mount ::API::DeployKeys mount ::API::Deployments mount ::API::Environments @@ -43,22 +44,21 @@ module API mount ::API::Groups mount ::API::Internal mount ::API::Issues - mount ::API::Boards mount ::API::Keys mount ::API::Labels mount ::API::LicenseTemplates mount ::API::Lint mount ::API::Members - mount ::API::MergeRequests mount ::API::MergeRequestDiffs + mount ::API::MergeRequests mount ::API::Milestones mount ::API::Namespaces mount ::API::Notes mount ::API::NotificationSettings mount ::API::Pipelines mount ::API::ProjectHooks - mount ::API::ProjectSnippets mount ::API::Projects + mount ::API::ProjectSnippets mount ::API::Repositories mount ::API::Runners mount ::API::Services -- cgit v1.2.1 From 204fdcb1abb9c76b2d4bd6260c6e5ce91529aeb8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 12:58:25 +0200 Subject: Add build success worker that runs asynchronously --- app/models/ci/build.rb | 21 +++++++++------------ app/workers/build_success_worker.rb | 27 +++++++++++++++++++++++++++ spec/workers/build_success_worker_spec.rb | 25 +++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 app/workers/build_success_worker.rb create mode 100644 spec/workers/build_success_worker_spec.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index b0d13c1bf06..ff70f3deb52 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -76,25 +76,22 @@ module Ci state_machine :status do after_transition pending: :running do |build| - build.run_after_commit { BuildHooksWorker.perform_async(id) } + build.run_after_commit do + BuildHooksWorker.perform_async(id) + end end after_transition any => [:success, :failed, :canceled] do |build| build.update_coverage - build.run_after_commit { BuildHooksWorker.perform_async(id) } + + build.run_after_commit do + BuildHooksWorker.perform_async(id) + end end after_transition any => [:success] do |build| - if build.environment.present? - service = CreateDeploymentService.new( - build.project, build.user, - environment: build.environment, - sha: build.sha, - ref: build.ref, - tag: build.tag, - options: build.options.to_h[:environment], - variables: build.variables) - service.execute(build) + build.run_after_commit do + BuildSuccessWorker.perform_async(id) end end end diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb new file mode 100644 index 00000000000..d6db7642d82 --- /dev/null +++ b/app/workers/build_success_worker.rb @@ -0,0 +1,27 @@ +class BuildSuccessWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(build_id) + Ci::Build.find_by(id: build_id).try do |build| + perform_deloyment(build) + end + end + + private + + def perform_deloyment(build) + return if build.environment.blank? + + service = CreateDeploymentService.new( + build.project, build.user, + environment: build.environment, + sha: build.sha, + ref: build.ref, + tag: build.tag, + options: build.options.to_h[:environment], + variables: build.variables) + + service.execute(build) + end +end diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb new file mode 100644 index 00000000000..c42bcd45d50 --- /dev/null +++ b/spec/workers/build_success_worker_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe BuildSuccessWorker do + describe '#perform' do + context 'when build exists' do + context 'when build belogs to the environment' do + let!(:build) { create(:ci_build, environment: 'production') } + + it 'executes deployment service' do + expect_any_instance_of(CreateDeploymentService) + .to receive(:execute) + + described_class.new.perform(build.id) + end + end + end + + context 'when build does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 85324ff8d36165709d5c98569ca0745b32ba9095 Mon Sep 17 00:00:00 2001 From: Georg G Date: Thu, 13 Oct 2016 13:08:19 +0200 Subject: Fix indentation and change inner matcher --- spec/controllers/projects/graphs_controller_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index dd743356145..74e6603b0cb 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -12,10 +12,10 @@ describe Projects::GraphsController do describe 'GET #languages' do let(:linguist_repository) do double(languages: { - 'Ruby' => 1000, - 'CoffeeScript' => 350, - 'PowerShell' => 15 - }) + 'Ruby' => 1000, + 'CoffeeScript' => 350, + 'PowerShell' => 15 + }) end let(:expected_values) do @@ -37,7 +37,7 @@ describe Projects::GraphsController do get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master') expected_values.each do |val| - expect(assigns(:languages)).to include(include(val)) + expect(assigns(:languages)).to include(a_hash_including(val)) end end end -- cgit v1.2.1 From bc4d732af126277b3e85aedce795158bd389fbea Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Oct 2016 14:11:26 +0300 Subject: Update users routing spec Signed-off-by: Dmitriy Zaporozhets --- spec/features/users_spec.rb | 12 ++++++++++++ spec/routing/routing_spec.rb | 12 ++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 6498b7317b4..c78c84dc5d0 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -49,6 +49,18 @@ feature 'Users', feature: true do expect(current_path).to eq user_path(user) expect(page).to have_text(user.name) end + + scenario '/u/user1/groups redirects to user groups page' do + visit '/u/user1/groups' + + expect(current_path).to eq user_groups_path(user) + end + + scenario '/u/user1/projects redirects to user projects page' do + visit '/u/user1/projects' + + expect(current_path).to eq user_projects_path(user) + end end def errors_on_page(page) diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 0ee1c811dfb..f848d96c729 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -15,27 +15,27 @@ describe UsersController, "routing" do end it "to #groups" do - expect(get("/u/User/groups")).to route_to('users#groups', username: 'User') + expect(get("/users/User/groups")).to route_to('users#groups', username: 'User') end it "to #projects" do - expect(get("/u/User/projects")).to route_to('users#projects', username: 'User') + expect(get("/users/User/projects")).to route_to('users#projects', username: 'User') end it "to #contributed" do - expect(get("/u/User/contributed")).to route_to('users#contributed', username: 'User') + expect(get("/users/User/contributed")).to route_to('users#contributed', username: 'User') end it "to #snippets" do - expect(get("/u/User/snippets")).to route_to('users#snippets', username: 'User') + expect(get("/users/User/snippets")).to route_to('users#snippets', username: 'User') end it "to #calendar" do - expect(get("/u/User/calendar")).to route_to('users#calendar', username: 'User') + expect(get("/users/User/calendar")).to route_to('users#calendar', username: 'User') end it "to #calendar_activities" do - expect(get("/u/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User') + expect(get("/users/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User') end end -- cgit v1.2.1 From ae95118a4ff3242a210d93e57370f6ef400db886 Mon Sep 17 00:00:00 2001 From: Johan H Date: Tue, 11 Oct 2016 16:27:09 +0200 Subject: Convert UTF-8 Emoji to Gitlab emoji --- CHANGELOG | 1 + lib/banzai/filter/emoji_filter.rb | 39 ++++++++++++++++------------- lib/gitlab/emoji.rb | 7 ++++-- spec/lib/banzai/filter/emoji_filter_spec.rb | 16 ++++++++++++ 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9e193132ac2..16b05d1d972 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.13.0 (unreleased) - Simplify Mentionable concern instance methods - Fix permission for setting an issue's due date - API: Multi-file commit !6096 (mahcsig) + - Unicode emoji are now converted to images - Revert "Label list shows all issues (opened or closed) with that label" - Expose expires_at field when sharing project on API - Fix VueJS template tags being rendered in code comments diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index 23ae6dfc79a..a8c1ca0c60a 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -14,15 +14,16 @@ module Banzai search_text_nodes(doc).each do |node| content = node.to_html next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) - if content.include?(':') || node.text.match(emoji_unicode_pattern) - html = emoji_name_image_filter(content) - html = emoji_unicode_image_filter(html) - next if html == content - node.replace(html) - end - end + next unless content.include?(':') || node.text.match(emoji_unicode_pattern) + + html = emoji_name_image_filter(content) + html = emoji_unicode_image_filter(html) + next if html == content + + node.replace(html) + end doc end @@ -34,31 +35,35 @@ module Banzai def emoji_name_image_filter(text) text.gsub(emoji_pattern) do |match| name = $1 - ":#{name}:" + emoji_image_tag(name, emoji_url(name)) end end - - # Replace unicode emojis with corresponding images if they exist. + # Replace unicode emoji with corresponding images if they exist. # - # text - String text to replace unicode emojis in. + # text - String text to replace unicode emoji in. # - # Returns a String with unicode emojis replaced with images. - + # Returns a String with unicode emoji replaced with images. def emoji_unicode_image_filter(text) text.gsub(emoji_unicode_pattern) do |moji| - ":#{Gitlab::Emoji.emojis_by_moji[moji][" + emoji_image_tag(Gitlab::Emoji.emojis_by_moji[moji]['name'], emoji_unicode_url(moji)) end end + + def emoji_image_tag(emoji_name, emoji_url) + ":#{emoji_name}:" + end + # Build a regexp that matches all valid :emoji: names. def self.emoji_pattern @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ end + # Build a regexp that matches all valid unicode emojis names. def self.emoji_unicode_pattern @emoji_unicode_pattern ||= /(#{Gitlab::Emoji.emojis_unicodes.map { |moji| Regexp.escape(moji) }.join('|')})/ - end + private def emoji_url(name) @@ -80,13 +85,10 @@ module Banzai emoji_unicode_path = emoji_unicode_filename(moji) if context[:asset_host] - # Asset host is specified. url_to_image(emoji_unicode_path) elsif context[:asset_root] - # Gitlab url is specified File.join(context[:asset_root], url_to_image(emoji_unicode_path)) else - # All other cases url_to_image(emoji_unicode_path) end end @@ -102,6 +104,7 @@ module Banzai def emoji_filename(name) "#{Gitlab::Emoji.emoji_filename(name)}.png" end + def emoji_unicode_pattern self.class.emoji_unicode_pattern end diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb index 803b0c2d057..bbbca8acc40 100644 --- a/lib/gitlab/emoji.rb +++ b/lib/gitlab/emoji.rb @@ -9,16 +9,19 @@ module Gitlab def emojis_by_moji Gemojione.index.instance_variable_get(:@emoji_by_moji) end + def emojis_unicodes - emojis_by_moji.keys.sort + emojis_by_moji.keys end + def emojis_names - emojis.keys.sort + emojis.keys end def emoji_filename(name) emojis[name]["unicode"] end + def emoji_unicode_filename(moji) emojis_by_moji[moji]["unicode"] end diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb index 475160bb5ec..c8e62f528df 100644 --- a/spec/lib/banzai/filter/emoji_filter_spec.rb +++ b/spec/lib/banzai/filter/emoji_filter_spec.rb @@ -16,10 +16,12 @@ describe Banzai::Filter::EmojiFilter, lib: true do doc = filter('

    :heart:

    ') expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' end + it 'replaces supported unicode emoji' do doc = filter('

    ❤️

    ') expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' end + it 'ignores unsupported emoji' do exp = act = '

    :foo:

    ' doc = filter(act) @@ -162,4 +164,18 @@ describe Banzai::Filter::EmojiFilter, lib: true do doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?') expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') end + + it 'uses a custom asset_root context' do + root = Gitlab.config.gitlab.url + 'gitlab/root' + + doc = filter("'🎱'", asset_root: root) + expect(doc.css('img').first.attr('src')).to start_with(root) + end + + it 'uses a custom asset_host context' do + ActionController::Base.asset_host = 'https://cdn.example.com' + + doc = filter("'🎱'", asset_host: 'https://this-is-ignored-i-guess?') + expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') + end end -- cgit v1.2.1 From 03a8ed971154cb218b82e8be53943612de94999f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 13:23:23 +0200 Subject: Use trasaction to process build deployment --- app/services/create_deployment_service.rb | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 799ad3e1bd0..c6dc2148c11 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -2,25 +2,29 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable = nil) - environment = find_or_create_environment + ActiveRecord::Base.transaction do + @deployable = deployable + @environment = prepare_environment - deployment = project.deployments.create( - environment: environment, + deploy.tap do |deployment| + deployment.update_merge_request_metrics! + end + end + end + + private + + def deploy + project.deployments.create( + environment: @environment, ref: params[:ref], tag: params[:tag], sha: params[:sha], user: current_user, - deployable: deployable - ) - - deployment.update_merge_request_metrics! - - deployment + deployable: @deployable) end - private - - def find_or_create_environment + def prepare_environment project.environments.find_or_create_by(name: expanded_name) do |environment| environment.external_url = expanded_url end -- cgit v1.2.1 From cb7872c3a0c789f9e906492098bb7d643f135e52 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Oct 2016 14:24:09 +0300 Subject: Remove /u/ prefix from user pages in documentation Signed-off-by: Dmitriy Zaporozhets --- doc/api/award_emoji.md | 18 +++++++++--------- doc/api/builds.md | 8 ++++---- doc/api/commits.md | 8 ++++---- doc/api/deployments.md | 12 ++++++------ doc/api/issues.md | 38 +++++++++++++++++++------------------- doc/api/keys.md | 2 +- doc/api/merge_requests.md | 18 +++++++++--------- doc/api/notes.md | 6 +++--- doc/api/pipelines.md | 10 +++++----- doc/api/projects.md | 10 +++++----- doc/api/todos.md | 18 +++++++++--------- doc/api/users.md | 24 ++++++++++++------------ doc/development/performance.md | 2 +- 13 files changed, 87 insertions(+), 87 deletions(-) diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md index c464e3f3f71..06111f4ab67 100644 --- a/doc/api/award_emoji.md +++ b/doc/api/award_emoji.md @@ -43,7 +43,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-15T10:09:34.206Z", "updated_at": "2016-06-15T10:09:34.206Z", @@ -59,7 +59,7 @@ Example Response: "id": 26, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/user4" + "web_url": "http://gitlab.example.com/user4" }, "created_at": "2016-06-15T10:09:34.177Z", "updated_at": "2016-06-15T10:09:34.177Z", @@ -103,7 +103,7 @@ Example Response: "id": 26, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/user4" + "web_url": "http://gitlab.example.com/user4" }, "created_at": "2016-06-15T10:09:34.177Z", "updated_at": "2016-06-15T10:09:34.177Z", @@ -146,7 +146,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-17T17:47:29.266Z", "updated_at": "2016-06-17T17:47:29.266Z", @@ -190,7 +190,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-17T17:47:29.266Z", "updated_at": "2016-06-17T17:47:29.266Z", @@ -238,7 +238,7 @@ Example Response: "id": 26, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/user4" + "web_url": "http://gitlab.example.com/user4" }, "created_at": "2016-06-15T10:09:34.197Z", "updated_at": "2016-06-15T10:09:34.197Z", @@ -279,7 +279,7 @@ Example Response: "id": 26, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/user4" + "web_url": "http://gitlab.example.com/user4" }, "created_at": "2016-06-15T10:09:34.197Z", "updated_at": "2016-06-15T10:09:34.197Z", @@ -319,7 +319,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-17T19:59:55.888Z", "updated_at": "2016-06-17T19:59:55.888Z", @@ -362,7 +362,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-17T19:59:55.888Z", "updated_at": "2016-06-17T19:59:55.888Z", diff --git a/doc/api/builds.md b/doc/api/builds.md index e8a9e4743d3..e40f198696d 100644 --- a/doc/api/builds.md +++ b/doc/api/builds.md @@ -64,7 +64,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://gitlab.dev/u/root", + "web_url": "http://gitlab.dev/root", "website_url": "" } }, @@ -108,7 +108,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://gitlab.dev/u/root", + "web_url": "http://gitlab.dev/root", "website_url": "" } } @@ -212,7 +212,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://gitlab.dev/u/root", + "web_url": "http://gitlab.dev/root", "website_url": "" } } @@ -279,7 +279,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://gitlab.dev/u/root", + "web_url": "http://gitlab.dev/root", "website_url": "" } } diff --git a/doc/api/commits.md b/doc/api/commits.md index 3e20beefb8a..6e0882a94de 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -288,7 +288,7 @@ Example response: ```json { "author" : { - "web_url" : "https://gitlab.example.com/u/thedude", + "web_url" : "https://gitlab.example.com/thedude", "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png", "username" : "thedude", "state" : "active", @@ -343,7 +343,7 @@ Example response: "author" : { "username" : "thedude", "state" : "active", - "web_url" : "https://gitlab.example.com/u/thedude", + "web_url" : "https://gitlab.example.com/thedude", "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png", "id" : 28, "name" : "Jeff Lebowski" @@ -370,7 +370,7 @@ Example response: "id" : 28, "name" : "Jeff Lebowski", "username" : "thedude", - "web_url" : "https://gitlab.example.com/u/thedude", + "web_url" : "https://gitlab.example.com/thedude", "state" : "active", "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png" }, @@ -408,7 +408,7 @@ Example response: ```json { "author" : { - "web_url" : "https://gitlab.example.com/u/thedude", + "web_url" : "https://gitlab.example.com/thedude", "name" : "Jeff Lebowski", "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png", "username" : "thedude", diff --git a/doc/api/deployments.md b/doc/api/deployments.md index 417962de82d..3d95c4cde60 100644 --- a/doc/api/deployments.md +++ b/doc/api/deployments.md @@ -56,7 +56,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://localhost:3000/u/root", + "web_url": "http://localhost:3000/root", "website_url": "" } }, @@ -75,7 +75,7 @@ Example of response "name": "Administrator", "state": "active", "username": "root", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" } }, { @@ -114,7 +114,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://localhost:3000/u/root", + "web_url": "http://localhost:3000/root", "website_url": "" } }, @@ -133,7 +133,7 @@ Example of response "name": "Administrator", "state": "active", "username": "root", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" } } ] @@ -169,7 +169,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "environment": { "id": 9, @@ -193,7 +193,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root", + "web_url": "http://localhost:3000/root", "created_at": "2016-08-11T07:09:20.351Z", "is_admin": true, "bio": null, diff --git a/doc/api/issues.md b/doc/api/issues.md index eed0d2fce51..134263d27b4 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -46,7 +46,7 @@ Example response: "author" : { "state" : "active", "id" : 18, - "web_url" : "https://gitlab.example.com/u/eileen.lowe", + "web_url" : "https://gitlab.example.com/eileen.lowe", "name" : "Alexandra Bashirian", "avatar_url" : null, "username" : "eileen.lowe" @@ -67,7 +67,7 @@ Example response: "state" : "active", "id" : 1, "name" : "Administrator", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root" }, @@ -134,7 +134,7 @@ Example response: }, "author" : { "state" : "active", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root", "id" : 1, @@ -145,7 +145,7 @@ Example response: "iid" : 1, "assignee" : { "avatar_url" : null, - "web_url" : "https://gitlab.example.com/u/lennie", + "web_url" : "https://gitlab.example.com/lennie", "state" : "active", "username" : "lennie", "id" : 9, @@ -215,7 +215,7 @@ Example response: }, "author" : { "state" : "active", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root", "id" : 1, @@ -226,7 +226,7 @@ Example response: "iid" : 1, "assignee" : { "avatar_url" : null, - "web_url" : "https://gitlab.example.com/u/lennie", + "web_url" : "https://gitlab.example.com/lennie", "state" : "active", "username" : "lennie", "id" : 9, @@ -281,7 +281,7 @@ Example response: }, "author" : { "state" : "active", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root", "id" : 1, @@ -292,7 +292,7 @@ Example response: "iid" : 1, "assignee" : { "avatar_url" : null, - "web_url" : "https://gitlab.example.com/u/lennie", + "web_url" : "https://gitlab.example.com/lennie", "state" : "active", "username" : "lennie", "id" : 9, @@ -357,7 +357,7 @@ Example response: "name" : "Alexandra Bashirian", "avatar_url" : null, "state" : "active", - "web_url" : "https://gitlab.example.com/u/eileen.lowe", + "web_url" : "https://gitlab.example.com/eileen.lowe", "id" : 18, "username" : "eileen.lowe" }, @@ -414,7 +414,7 @@ Example response: "username" : "eileen.lowe", "id" : 18, "state" : "active", - "web_url" : "https://gitlab.example.com/u/eileen.lowe" + "web_url" : "https://gitlab.example.com/eileen.lowe" }, "state" : "closed", "title" : "Issues with auth", @@ -500,7 +500,7 @@ Example response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/axel.block" + "web_url": "https://gitlab.example.com/axel.block" }, "author": { "name": "Kris Steuber", @@ -508,7 +508,7 @@ Example response: "id": 10, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/solon.cremin" + "web_url": "https://gitlab.example.com/solon.cremin" }, "due_date": null, "web_url": "http://example.com/example/example/issues/11", @@ -557,7 +557,7 @@ Example response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/axel.block" + "web_url": "https://gitlab.example.com/axel.block" }, "author": { "name": "Kris Steuber", @@ -565,7 +565,7 @@ Example response: "id": 10, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/solon.cremin" + "web_url": "https://gitlab.example.com/solon.cremin" }, "due_date": null, "web_url": "http://example.com/example/example/issues/11", @@ -614,7 +614,7 @@ Example response: "id": 21, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/3e6f06a86cf27fa8b56f3f74f7615987?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/keyon" + "web_url": "https://gitlab.example.com/keyon" }, "author": { "name": "Vivian Hermann", @@ -622,7 +622,7 @@ Example response: "id": 11, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/orville" + "web_url": "https://gitlab.example.com/orville" }, "subscribed": false, "due_date": null, @@ -669,7 +669,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "action_name": "marked", "target_type": "Issue", @@ -700,7 +700,7 @@ Example response: "id": 14, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/francisca" + "web_url": "https://gitlab.example.com/francisca" }, "author": { "name": "Maxie Medhurst", @@ -708,7 +708,7 @@ Example response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "subscribed": true, "user_notes_count": 7, diff --git a/doc/api/keys.md b/doc/api/keys.md index faa6f212b43..b68f08a007d 100644 --- a/doc/api/keys.md +++ b/doc/api/keys.md @@ -24,7 +24,7 @@ Parameters: "id": 25, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/cfa35b8cd2ec278026357769582fa563?s=40\u0026d=identicon", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2015-09-03T07:24:01.670Z", "is_admin": false, "bio": null, diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 494040a1ce8..f4167403c2c 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -621,7 +621,7 @@ Example response when the GitLab issue tracker is used: "author" : { "state" : "active", "id" : 18, - "web_url" : "https://gitlab.example.com/u/eileen.lowe", + "web_url" : "https://gitlab.example.com/eileen.lowe", "name" : "Alexandra Bashirian", "avatar_url" : null, "username" : "eileen.lowe" @@ -642,7 +642,7 @@ Example response when the GitLab issue tracker is used: "state" : "active", "id" : 1, "name" : "Administrator", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root" }, @@ -711,7 +711,7 @@ Example response: "id": 19, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/leila" + "web_url": "https://gitlab.example.com/leila" }, "assignee": { "name": "Celine Wehner", @@ -719,7 +719,7 @@ Example response: "id": 16, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/carli" + "web_url": "https://gitlab.example.com/carli" }, "source_project_id": 5, "target_project_id": 5, @@ -787,7 +787,7 @@ Example response: "id": 19, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/leila" + "web_url": "https://gitlab.example.com/leila" }, "assignee": { "name": "Celine Wehner", @@ -795,7 +795,7 @@ Example response: "id": 16, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/carli" + "web_url": "https://gitlab.example.com/carli" }, "source_project_id": 5, "target_project_id": 5, @@ -858,7 +858,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "action_name": "marked", "target_type": "MergeRequest", @@ -881,7 +881,7 @@ Example response: "id": 14, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/francisca" + "web_url": "https://gitlab.example.com/francisca" }, "assignee": { "name": "Dr. Gabrielle Strosin", @@ -889,7 +889,7 @@ Example response: "id": 4, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/733005fcd7e6df12d2d8580171ccb966?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/barrett.krajcik" + "web_url": "https://gitlab.example.com/barrett.krajcik" }, "source_project_id": 3, "target_project_id": 3, diff --git a/doc/api/notes.md b/doc/api/notes.md index 572844b8b3f..58d40eecf3e 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -143,7 +143,7 @@ Example Response: "state": "active", "created_at": "2013-09-30T13:46:01Z", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/pipin" + "web_url": "https://gitlab.example.com/pipin" }, "created_at": "2016-04-05T22:10:44.164Z", "system": false, @@ -268,7 +268,7 @@ Example Response: "state": "active", "created_at": "2013-09-30T13:46:01Z", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/pipin" + "web_url": "https://gitlab.example.com/pipin" }, "created_at": "2016-04-06T16:51:53.239Z", "system": false, @@ -398,7 +398,7 @@ Example Response: "state": "active", "created_at": "2013-09-30T13:46:01Z", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/pipin" + "web_url": "https://gitlab.example.com/pipin" }, "created_at": "2016-04-05T22:11:59.923Z", "system": false, diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index 847408a7f61..a29b3eb6f44 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -34,7 +34,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-16T10:23:19.007Z", "updated_at": "2016-08-16T10:23:19.216Z", @@ -57,7 +57,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-16T10:23:21.184Z", "updated_at": "2016-08-16T10:23:21.314Z", @@ -103,7 +103,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-11T11:28:34.085Z", "updated_at": "2016-08-11T11:32:35.169Z", @@ -148,7 +148,7 @@ Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-11T11:28:34.085Z", "updated_at": "2016-08-11T11:32:35.169Z", @@ -193,7 +193,7 @@ Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-11T11:28:34.085Z", "updated_at": "2016-08-11T11:32:35.169Z", diff --git a/doc/api/projects.md b/doc/api/projects.md index f96bf7f6d63..b7791b4748a 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -465,7 +465,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" }, @@ -482,7 +482,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "john", "data": { @@ -528,7 +528,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" }, @@ -552,7 +552,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2015-12-04T10:33:56.698Z", "system": false, @@ -567,7 +567,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" } diff --git a/doc/api/todos.md b/doc/api/todos.md index 0cd644dfd2f..a5e81801024 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -44,7 +44,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "action_name": "marked", "target_type": "MergeRequest", @@ -67,7 +67,7 @@ Example Response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "assignee": { "name": "Administrator", @@ -75,7 +75,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "source_project_id": 2, "target_project_id": 2, @@ -117,7 +117,7 @@ Example Response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "action_name": "assigned", "target_type": "MergeRequest", @@ -140,7 +140,7 @@ Example Response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "assignee": { "name": "Administrator", @@ -148,7 +148,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "source_project_id": 2, "target_project_id": 2, @@ -215,7 +215,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "action_name": "marked", "target_type": "MergeRequest", @@ -238,7 +238,7 @@ Example Response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "assignee": { "name": "Administrator", @@ -246,7 +246,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "source_project_id": 2, "target_project_id": 2, diff --git a/doc/api/users.md b/doc/api/users.md index a52b2d51d78..2b12770d5a5 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -20,7 +20,7 @@ GET /users "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", - "web_url": "http://localhost:3000/u/john_smith" + "web_url": "http://localhost:3000/john_smith" }, { "id": 2, @@ -28,7 +28,7 @@ GET /users "name": "Jack Smith", "state": "blocked", "avatar_url": "http://gravatar.com/../e32131cd8.jpeg", - "web_url": "http://localhost:3000/u/jack_smith" + "web_url": "http://localhost:3000/jack_smith" } ] ``` @@ -48,7 +48,7 @@ GET /users "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2012-05-23T08:00:58Z", "is_admin": false, "bio": null, @@ -81,7 +81,7 @@ GET /users "name": "Jack Smith", "state": "blocked", "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg", - "web_url": "http://localhost:3000/u/jack_smith", + "web_url": "http://localhost:3000/jack_smith", "created_at": "2012-05-23T08:01:01Z", "is_admin": false, "bio": null, @@ -141,7 +141,7 @@ Parameters: "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2012-05-23T08:00:58Z", "is_admin": false, "bio": null, @@ -172,7 +172,7 @@ Parameters: "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2012-05-23T08:00:58Z", "is_admin": false, "bio": null, @@ -293,7 +293,7 @@ GET /user "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2012-05-23T08:00:58Z", "is_admin": false, "bio": null, @@ -665,7 +665,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" }, @@ -682,7 +682,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "john", "data": { @@ -728,7 +728,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" }, @@ -752,7 +752,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2015-12-04T10:33:56.698Z", "system": false, @@ -767,7 +767,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" } diff --git a/doc/development/performance.md b/doc/development/performance.md index 7ff603e2c4a..a00c0fe07b5 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -253,5 +253,5 @@ impact on runtime performance, and as such, using a constant instead of referencing an object directly may even slow code down. [#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607 -[yorickpeterse]: https://gitlab.com/u/yorickpeterse +[yorickpeterse]: https://gitlab.com/yorickpeterse [anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern -- cgit v1.2.1 From 41de407837dec94925af40c9c556ec9303f4de4e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Oct 2016 14:25:53 +0300 Subject: Add user routing rename to CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..2cbf5bc3277 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -102,6 +102,7 @@ v 8.13.0 (unreleased) - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects + - Change user pages routing from /u/:username/PATH to /users/:username/PATH v 8.12.6 - Update mailroom to 0.8.1 in Gemfile.lock !6814 -- cgit v1.2.1 From 0eb5b0a08af7ebe68d515a68e2a94b3d0213393f Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Mon, 3 Oct 2016 05:44:58 +0200 Subject: Use forked github-markup gem to enable python3 support with omnibus --- Gemfile | 2 +- Gemfile.lock | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 230561f90d3..7323776d24c 100644 --- a/Gemfile +++ b/Gemfile @@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' -gem 'github-markup', '~> 1.4' +gem 'github-markup', '~> 1.4', git: 'https://github.com/brodock/github-markup.git', branch: 'python3-fix' gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~>3.6' diff --git a/Gemfile.lock b/Gemfile.lock index 067908af3fb..dde92fff044 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: https://github.com/brodock/github-markup.git + revision: 62ff5993081920ad75016f2b87019dd14e3af137 + branch: python3-fix + specs: + github-markup (1.4.0) + GEM remote: https://rubygems.org/ specs: @@ -272,7 +279,6 @@ GEM escape_utils (~> 1.1.0) mime-types (>= 1.19) rugged (>= 0.23.0b) - github-markup (1.4.0) gitlab-flowdock-git-hook (1.0.1) flowdock (~> 0.7) gitlab-grit (>= 2.4.1) @@ -864,7 +870,7 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.0) github-linguist (~> 4.7.0) - github-markup (~> 1.4) + github-markup (~> 1.4)! gitlab-flowdock-git-hook (~> 1.0.1) gitlab_git (~> 10.6.8) gitlab_omniauth-ldap (~> 1.2.1) -- cgit v1.2.1 From 2549db7aaed7cad60d5ddf129a70da25b09aded2 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Thu, 13 Oct 2016 04:44:23 +0200 Subject: Updated gitlab-markup forked gem to download from rubygems --- CHANGELOG | 3 +++ Gemfile | 2 +- Gemfile.lock | 11 +++-------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e13e0eef87a..b2a406b0def 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -109,6 +109,9 @@ v 8.13.0 (unreleased) - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects +v 8.12.7 + - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 + v 8.12.6 - Update mailroom to 0.8.1 in Gemfile.lock !6814 diff --git a/Gemfile b/Gemfile index 7323776d24c..1204a546983 100644 --- a/Gemfile +++ b/Gemfile @@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' -gem 'github-markup', '~> 1.4', git: 'https://github.com/brodock/github-markup.git', branch: 'python3-fix' +gem 'gitlab-markup', '~> 1.5.0' gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~>3.6' diff --git a/Gemfile.lock b/Gemfile.lock index dde92fff044..25a176da8fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,3 @@ -GIT - remote: https://github.com/brodock/github-markup.git - revision: 62ff5993081920ad75016f2b87019dd14e3af137 - branch: python3-fix - specs: - github-markup (1.4.0) - GEM remote: https://rubygems.org/ specs: @@ -279,6 +272,7 @@ GEM escape_utils (~> 1.1.0) mime-types (>= 1.19) rugged (>= 0.23.0b) + github-markup (1.4.0) gitlab-flowdock-git-hook (1.0.1) flowdock (~> 0.7) gitlab-grit (>= 2.4.1) @@ -288,6 +282,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) + gitlab-markup (1.5.0) gitlab_git (10.6.8) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) @@ -870,8 +865,8 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.0) github-linguist (~> 4.7.0) - github-markup (~> 1.4)! gitlab-flowdock-git-hook (~> 1.0.1) + gitlab-markup (~> 1.5.0) gitlab_git (~> 10.6.8) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) -- cgit v1.2.1 From 1290df050bc338776749f8f1965b44aae6baf726 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 13 Oct 2016 14:00:56 +0200 Subject: Add job to trigger a docs.gitlab.com build --- .gitlab-ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 441f77740a8..62e06683124 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -299,6 +299,14 @@ coverage: - coverage/index.html - coverage/assets/ +# Trigger docs build +trigger_docs: + stage: post-test + script: + - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v3/projects/38069/trigger/builds" + only: + - master + # Notify slack in the end notify:slack: -- cgit v1.2.1 From 8efa041a730515e57f127850414b3557c7af60b4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 14:55:44 +0200 Subject: Do not process build success if project was removed --- app/workers/build_success_worker.rb | 6 ++++-- spec/workers/build_success_worker_spec.rb | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index d6db7642d82..5afb15f52b4 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -4,13 +4,15 @@ class BuildSuccessWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - perform_deloyment(build) + return unless build.project + + create_deloyment(build) end end private - def perform_deloyment(build) + def create_deloyment(build) return if build.environment.blank? service = CreateDeploymentService.new( diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb index c42bcd45d50..dba70883130 100644 --- a/spec/workers/build_success_worker_spec.rb +++ b/spec/workers/build_success_worker_spec.rb @@ -13,6 +13,17 @@ describe BuildSuccessWorker do described_class.new.perform(build.id) end end + + context 'when build is not associated with project' do + let!(:build) { create(:ci_build, project: nil) } + + it 'does not create deployment' do + expect_any_instance_of(CreateDeploymentService) + .not_to receive(:execute) + + described_class.new.perform(build.id) + end + end end context 'when build does not exist' do -- cgit v1.2.1 From 2709f679d07a5fc08f5cc31a416e00e61a04d2c1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 13 Oct 2016 15:02:05 +0200 Subject: Loosen requirement on request_store version This gem follows semantic versioning and will not introduce breaking changes in a minor version. See https://github.com/steveklabnik/request_store#semantic-versioning Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/2868 --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 230561f90d3..2601c33a5b4 100644 --- a/Gemfile +++ b/Gemfile @@ -225,7 +225,7 @@ gem 'gon', '~> 6.1.0' gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 4.1.0' gem 'jquery-ui-rails', '~> 5.0.0' -gem 'request_store', '~> 1.3.0' +gem 'request_store', '~> 1.3' gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' gem 'net-ssh', '~> 3.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index 067908af3fb..77370e8bdaf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -937,7 +937,7 @@ DEPENDENCIES redis (~> 3.2) redis-namespace (~> 1.5.2) redis-rails (~> 4.0.0) - request_store (~> 1.3.0) + request_store (~> 1.3) rerun (~> 0.11.0) responders (~> 2.0) rouge (~> 2.0) -- cgit v1.2.1 From 5fd635d18b77f56f6acd264ccaa1a357e2fa1cdd Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 15:22:55 +0200 Subject: Update code coverage for CI build asynchronously --- app/workers/build_coverage_worker.rb | 9 +++++++++ spec/workers/build_coverage_worker_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 app/workers/build_coverage_worker.rb create mode 100644 spec/workers/build_coverage_worker_spec.rb diff --git a/app/workers/build_coverage_worker.rb b/app/workers/build_coverage_worker.rb new file mode 100644 index 00000000000..0680645a8db --- /dev/null +++ b/app/workers/build_coverage_worker.rb @@ -0,0 +1,9 @@ +class BuildCoverageWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(build_id) + Ci::Build.find_by(id: build_id) + .try(:update_coverage) + end +end diff --git a/spec/workers/build_coverage_worker_spec.rb b/spec/workers/build_coverage_worker_spec.rb new file mode 100644 index 00000000000..ba20488f663 --- /dev/null +++ b/spec/workers/build_coverage_worker_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe BuildCoverageWorker do + describe '#perform' do + context 'when build exists' do + let!(:build) { create(:ci_build) } + + it 'updates code coverage' do + expect_any_instance_of(Ci::Build) + .to receive(:update_coverage) + + described_class.new.perform(build.id) + end + end + + context 'when build does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 4e9342599b472a28928a95fd43efc4df851f8e33 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 15:24:33 +0200 Subject: Fix typo in build success worker for deployment --- app/workers/build_success_worker.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index 5afb15f52b4..a9dc34166a1 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -6,13 +6,13 @@ class BuildSuccessWorker Ci::Build.find_by(id: build_id).try do |build| return unless build.project - create_deloyment(build) + create_deployment(build) end end private - def create_deloyment(build) + def create_deployment(build) return if build.environment.blank? service = CreateDeploymentService.new( -- cgit v1.2.1 From 17237e14466dd75e703b93daf56e511e13bdd28b Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Thu, 13 Oct 2016 14:43:05 +0000 Subject: Update README.md --- docker/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/README.md b/docker/README.md index ee1f32adc26..f9e12c5733b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -2,6 +2,6 @@ * The official GitLab Community Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ce/). * The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ee/). -* The complete usage guide can be found in [Using GitLab Docker images](http://doc.gitlab.com/omnibus/docker/) +* The complete usage guide can be found in [Using GitLab Docker images](https://docs.gitlab.com/omnibus/docker/) * The Dockerfile used for building public images is in [Omnibus Repository](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker) -* Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#build-docker-image) +* Check the guide for [creating Omnibus-based Docker Image](https://docs.gitlab.com/omnibus/build/README.html#build-docker-image) -- cgit v1.2.1 From ae851edc3c76fd7b81de2e3d48ec8a531f3609b3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 10 Oct 2016 03:19:55 +0100 Subject: Added no-template functionality Added tests --- .../templates/issuable_template_selector.js.es6 | 8 ++++- app/views/shared/issuable/_form.html.haml | 2 ++ spec/features/projects/issuable_templates_spec.rb | 40 ++++++++++++++++++---- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index 2ecf3b18975..bd4e3c3d00d 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -16,7 +16,13 @@ if (initialQuery.name) this.requestFile(initialQuery); $('.reset-template', this.dropdown.parent()).on('click', () => { - if (this.currentTemplate) this.setInputValueToTemplateContent(false); + this.setInputValueToTemplateContent(); + }); + + $('.no-template', this.dropdown.parent()).on('click', () => { + this.currentTemplate = ''; + this.setInputValueToTemplateContent(); + $('.dropdown-toggle-text', this.dropdown).text('Choose a template'); }); } diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c3f4e10c954..16834c3db29 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -23,6 +23,8 @@ data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do %ul.dropdown-footer-list %li + %a.no-template + No template %a.reset-template Reset template %div{ class: issuable_template_names.any? ? 'col-sm-7 col-lg-8' : 'col-sm-10' } diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index cd79c4f512d..d886909ce85 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -15,6 +15,7 @@ feature 'issuable templates', feature: true, js: true do let(:template_content) { 'this is a test "bug" template' } let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) } let(:issue) { create(:issue, author: user, assignee: user, project: project) } + let(:description_addition) { ' appending to description' } background do project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false) @@ -26,7 +27,26 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects "bug" template' do select_template 'bug' wait_for_ajax - preview_template(template_content) + preview_template + save_changes + end + + scenario 'user selects "bug" template and then "no template"' do + select_template 'bug' + wait_for_ajax + select_option 'No template' + wait_for_ajax + preview_template('') + save_changes('') + end + + scenario 'user selects "bug" template, edits description and then selects "reset template"' do + select_template 'bug' + wait_for_ajax + find_field('issue_description').send_keys(description_addition) + preview_template(template_content + description_addition) + select_option 'Reset template' + preview_template save_changes end @@ -37,7 +57,7 @@ feature 'issuable templates', feature: true, js: true do wait_for_ajax end_height = page.evaluate_script('$(".markdown-area").outerHeight()') - + expect(end_height).not_to eq(start_height) end end @@ -75,7 +95,7 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects "feature-proposal" template' do select_template 'feature-proposal' wait_for_ajax - preview_template(template_content) + preview_template save_changes end end @@ -102,25 +122,31 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects template' do select_template 'feature-proposal' wait_for_ajax - preview_template(template_content) + preview_template save_changes end end end end - def preview_template(expected_content) + def preview_template(expected_content = template_content) click_link 'Preview' expect(page).to have_content expected_content + click_link 'Write' end - def save_changes + def save_changes(expected_content = template_content) click_button "Save changes" - expect(page).to have_content template_content + expect(page).to have_content expected_content end def select_template(name) first('.js-issuable-selector').click first('.js-issuable-selector-wrap .dropdown-content a', text: name).click end + + def select_option(name) + first('.js-issuable-selector').click + first('.js-issuable-selector-wrap .dropdown-footer-list a', text: name).click + end end -- cgit v1.2.1 From 776cea4c00d883cafc2bc5381f3b61b146a93976 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 13 Oct 2016 14:19:52 +0100 Subject: Handle case where deployment ref no longer exists Keep-around refs for deployments were only introduced in 8.10, so any deployment created in 8.9 could have a SHA pointing to a commit that no longer exists in the repository. We can't do anything useful with those deployments, so make `#includes_commit?` always return false for those. --- app/models/deployment.rb | 9 ++++++++- spec/models/deployment_spec.rb | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 82b27b78229..f63cc179b9e 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -40,7 +40,14 @@ class Deployment < ActiveRecord::Base def includes_commit?(commit) return false unless commit - project.repository.is_ancestor?(commit.id, sha) + # Before 8.10, deployments didn't have keep-around refs. Any deployment + # created before then could have a `sha` referring to a commit that no + # longer exists in the repository, so just ignore those. + begin + project.repository.is_ancestor?(commit.id, sha) + rescue Rugged::OdbError + false + end end def update_merge_request_metrics! diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index bfff639ad78..01a4a53a264 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -38,5 +38,14 @@ describe Deployment, models: true do expect(deployment.includes_commit?(commit)).to be true end end + + context 'when the SHA for the deployment does not exist in the repo' do + it 'returns false' do + deployment.update(sha: Gitlab::Git::BLANK_SHA) + commit = project.commit + + expect(deployment.includes_commit?(commit)).to be false + end + end end end -- cgit v1.2.1 From 0f3960cffaf80eccc2b85e4f5d2d8558dfc633f6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 29 Sep 2016 14:28:33 -0500 Subject: Replace unique keyframes mixin with specific keyframe animation names --- CHANGELOG | 1 + app/assets/stylesheets/framework/logo.scss | 78 +++++++++++++--------------- app/assets/stylesheets/framework/mixins.scss | 7 +++ 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1cb9b8acf51..cb17a856d66 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.13.0 (unreleased) - AbstractReferenceFilter caches project_refs on RequestStore when active - Speed-up group milestones show page - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) + - Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps) - Add more tests for calendar contribution (ClemMakesApps) - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Fix permission for setting an issue's due date diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss index 3ee3fb4cee5..c214eabcad7 100644 --- a/app/assets/stylesheets/framework/logo.scss +++ b/app/assets/stylesheets/framework/logo.scss @@ -1,15 +1,3 @@ -@mixin unique-keyframes { - $animation-name: unique-id(); - @include webkit-prefix(animation-name, $animation-name); - - @-webkit-keyframes #{$animation-name} { - @content; - } - @keyframes #{$animation-name} { - @content; - } -} - @mixin tanuki-logo-colors($path-color) { fill: $path-color; transition: all 0.8s; @@ -20,28 +8,6 @@ } } -@mixin tanuki-second-highlight-animations($tanuki-color) { - @include unique-keyframes { - 10%, 80% { - fill: #{$tanuki-color} - } - 20%, 90% { - fill: lighten($tanuki-color, 25%); - } - } -} - -@mixin tanuki-forth-highlight-animations($tanuki-color) { - @include unique-keyframes { - 30%, 60% { - fill: #{$tanuki-color}; - } - 40%, 70% { - fill: lighten($tanuki-color, 25%); - } - } -} - .tanuki-logo { .tanuki-left-ear, @@ -67,7 +33,7 @@ } .tanuki-left-cheek { - @include unique-keyframes { + @include include-keyframes(animate-tanuki-left-cheek) { 0%, 10%, 100% { fill: lighten($tanuki-yellow, 25%); } @@ -78,15 +44,29 @@ } .tanuki-left-eye { - @include tanuki-second-highlight-animations($tanuki-orange); + @include include-keyframes(animate-tanuki-left-eye) { + 10%, 80% { + fill: $tanuki-orange; + } + 20%, 90% { + fill: lighten($tanuki-orange, 25%); + } + } } .tanuki-left-ear { - @include tanuki-second-highlight-animations($tanuki-red); + @include include-keyframes(animate-tanuki-left-ear) { + 10%, 80% { + fill: $tanuki-red; + } + 20%, 90% { + fill: lighten($tanuki-red, 25%); + } + } } .tanuki-nose { - @include unique-keyframes { + @include include-keyframes(animate-tanuki-nose) { 20%, 70% { fill: $tanuki-red; } @@ -97,15 +77,29 @@ } .tanuki-right-eye { - @include tanuki-forth-highlight-animations($tanuki-orange); + @include include-keyframes(animate-tanuki-right-eye) { + 30%, 60% { + fill: $tanuki-orange; + } + 40%, 70% { + fill: lighten($tanuki-orange, 25%); + } + } } .tanuki-right-ear { - @include tanuki-forth-highlight-animations($tanuki-red); + @include include-keyframes(animate-tanuki-right-ear) { + 30%, 60% { + fill: $tanuki-red; + } + 40%, 70% { + fill: lighten($tanuki-red, 25%); + } + } } .tanuki-right-cheek { - @include unique-keyframes { + @include include-keyframes(animate-tanuki-right-cheek) { 40% { fill: $tanuki-yellow; } @@ -115,4 +109,4 @@ } } } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1ec08cdef23..5d9f341aa5e 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -95,3 +95,10 @@ @content; } } + +@mixin include-keyframes($animation-name) { + @include webkit-prefix(animation-name, $animation-name); + @include keyframes($animation-name) { + @content; + } +} -- cgit v1.2.1 From bba47886264d0ca7650d2b4b202d69984650b0ae Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 10 Oct 2016 09:40:14 +0200 Subject: Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService --- CHANGELOG | 1 + app/models/project.rb | 5 --- app/services/git_push_service.rb | 15 ++------- app/workers/update_merge_requests_worker.rb | 16 +++++++++ spec/models/project_spec.rb | 21 ------------ spec/services/git_push_service_spec.rb | 4 +-- .../merge_requests/refresh_service_spec.rb | 3 +- spec/workers/post_receive_spec.rb | 8 ++++- spec/workers/update_merge_requests_worker_spec.rb | 38 ++++++++++++++++++++++ 9 files changed, 68 insertions(+), 43 deletions(-) create mode 100644 app/workers/update_merge_requests_worker.rb create mode 100644 spec/workers/update_merge_requests_worker_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 4b1ac9d2c3c..472601fa336 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.13.0 (unreleased) - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) + - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment diff --git a/app/models/project.rb b/app/models/project.rb index 74d54e69648..8c488f4aeef 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -827,11 +827,6 @@ class Project < ActiveRecord::Base end end - def update_merge_requests(oldrev, newrev, ref, user) - MergeRequests::RefreshService.new(self, user). - execute(oldrev, newrev, ref) - end - def valid_repo? repository.exists? rescue diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index c499427605a..e8415862de5 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -63,13 +63,12 @@ class GitPushService < BaseService protected def update_merge_requests - @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user) + UpdateMergeRequestsWorker.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref]) EventCreateService.new.push(@project, current_user, build_push_data) - SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks) - Ci::CreatePipelineService.new(project, current_user, build_push_data).execute + Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute ProjectCacheWorker.perform_async(@project.id) end @@ -148,16 +147,6 @@ class GitPushService < BaseService push_commits) end - def build_push_data_system_hook - @push_data_system ||= Gitlab::DataBuilder::Push.build( - @project, - current_user, - params[:oldrev], - params[:newrev], - params[:ref], - []) - end - def push_to_existing_branch? # Return if this is not a push to a branch (e.g. new commits) Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev]) diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb new file mode 100644 index 00000000000..03f0528cdae --- /dev/null +++ b/app/workers/update_merge_requests_worker.rb @@ -0,0 +1,16 @@ +class UpdateMergeRequestsWorker + include Sidekiq::Worker + + def perform(project_id, user_id, oldrev, newrev, ref) + project = Project.find_by(id: project_id) + return unless project + + user = User.find_by(id: user_id) + return unless user + + MergeRequests::RefreshService.new(project, user).execute(oldrev, newrev, ref) + + push_data = Gitlab::DataBuilder::Push.build(project, user, oldrev, newrev, ref, []) + SystemHooksService.new.execute_hooks(push_data, :push_hooks) + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dae546a0cdc..7435713d4c0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -219,7 +219,6 @@ describe Project, models: true do describe 'Respond to' do it { is_expected.to respond_to(:url_to_repo) } it { is_expected.to respond_to(:repo_exists?) } - it { is_expected.to respond_to(:update_merge_requests) } it { is_expected.to respond_to(:execute_hooks) } it { is_expected.to respond_to(:owner) } it { is_expected.to respond_to(:path_with_namespace) } @@ -380,26 +379,6 @@ describe Project, models: true do end end - describe '#update_merge_requests' do - let(:project) { create(:project) } - let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:key) { create(:key, user_id: project.owner.id) } - let(:prev_commit_id) { merge_request.commits.last.id } - let(:commit_id) { merge_request.commits.first.id } - - it 'closes merge request if last commit from source branch was pushed to target branch' do - project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.target_branch}", key.user) - merge_request.reload - expect(merge_request.merged?).to be_truthy - end - - it 'updates merge request commits with new one if pushed to source branch' do - project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user) - merge_request.reload - expect(merge_request.diff_head_sha).to eq(commit_id) - end - end - describe '.find_with_namespace' do context 'with namespace' do before do diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 8e3e12114f2..dd2a9e9903a 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -184,8 +184,8 @@ describe GitPushService, services: true do context "Updates merge requests" do it "when pushing a new branch for the first time" do - expect(project).to receive(:update_merge_requests). - with(@blankrev, 'newrev', 'refs/heads/master', user) + expect(UpdateMergeRequestsWorker).to receive(:perform_async). + with(project.id, user.id, @blankrev, 'newrev', 'refs/heads/master') execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 5b4e4908add..e515bc9f89c 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -62,7 +62,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes).not_to be_empty } it { expect(@merge_request).to be_open } - it { expect(@merge_request.merge_when_build_succeeds).to be_falsey} + it { expect(@merge_request.merge_when_build_succeeds).to be_falsey } + it { expect(@merge_request.diff_head_sha).to eq(@newrev) } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } it { expect(@build_failed_todo).to be_done } diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index ffeaafe654a..984acdade36 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -92,7 +92,13 @@ describe PostReceive do allow(Project).to receive(:find_with_namespace).and_return(project) expect(project).to receive(:execute_hooks).twice expect(project).to receive(:execute_services).twice - expect(project).to receive(:update_merge_requests) + + PostReceive.new.perform(pwd(project), key_id, base64_changes) + end + + it "enqueues a UpdateMergeRequestsWorker job" do + allow(Project).to receive(:find_with_namespace).and_return(project) + expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args) PostReceive.new.perform(pwd(project), key_id, base64_changes) end diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb new file mode 100644 index 00000000000..c78a69eda67 --- /dev/null +++ b/spec/workers/update_merge_requests_worker_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe UpdateMergeRequestsWorker do + include RepoHelpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + + subject { described_class.new } + + describe '#perform' do + let(:oldrev) { "123456" } + let(:newrev) { "789012" } + let(:ref) { "refs/heads/test" } + + def perform + subject.perform(project.id, user.id, oldrev, newrev, ref) + end + + it 'executes MergeRequests::RefreshService with expected values' do + expect(MergeRequests::RefreshService).to receive(:new).with(project, user).and_call_original + expect_any_instance_of(MergeRequests::RefreshService).to receive(:execute).with(oldrev, newrev, ref) + + perform + end + + it 'executes SystemHooksService with expected values' do + push_data = double('push_data') + expect(Gitlab::DataBuilder::Push).to receive(:build).with(project, user, oldrev, newrev, ref, []).and_return(push_data) + + system_hook_service = double('system_hook_service') + expect(SystemHooksService).to receive(:new).and_return(system_hook_service) + expect(system_hook_service).to receive(:execute_hooks).with(push_data, :push_hooks) + + perform + end + end +end -- cgit v1.2.1 From 069f2d347585a0f79ab8e3ddfb194ebbc86176c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 29 Sep 2016 19:31:14 +0200 Subject: Draft a quick CE->EE merge check rake task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 7 +++ lib/tasks/ce_to_ee_merge_check.rake | 6 +++ lib/tasks/gitlab/dev.rake | 96 +++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 lib/tasks/ce_to_ee_merge_check.rake create mode 100644 lib/tasks/gitlab/dev.rake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d19f55aca3..7e2d705a888 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -210,6 +210,13 @@ rake brakeman: *exec rake flay: *exec license_finder: *exec rake downtime_check: *exec +rake ce_to_ee_merge_check: + <<: *exec + only: + - branches + except: + - tags + - master rake db:migrate:reset: stage: test diff --git a/lib/tasks/ce_to_ee_merge_check.rake b/lib/tasks/ce_to_ee_merge_check.rake new file mode 100644 index 00000000000..bb87f85cd9c --- /dev/null +++ b/lib/tasks/ce_to_ee_merge_check.rake @@ -0,0 +1,6 @@ +desc 'Checks if the branch would apply cleanly to EE' +task ce_to_ee_merge_check: :environment do + return if defined?(Gitlab::License) + + Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke +end diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake new file mode 100644 index 00000000000..bf17ba499bc --- /dev/null +++ b/lib/tasks/gitlab/dev.rake @@ -0,0 +1,96 @@ +namespace :gitlab do + namespace :dev do + desc 'Checks if the branch would apply cleanly to EE' + task ce_to_ee_merge_check: :environment do + ce_repo = ENV['CI_BUILD_REPO'] + ce_branch = ENV['CI_BUILD_REF_NAME'] + + ee_repo = 'https://gitlab.com/gitlab-org/gitlab-ee.git' + ee_branch = "#{ce_branch}-ee" + ee_dir = 'gitlab-ee-merge-check' + + puts "\n=> Cloning #{ee_repo} into #{ee_dir}\n" + `git clone #{ee_repo} #{ee_dir} --depth 1` + Dir.chdir(ee_dir) do + puts "\n => Fetching #{ce_repo}/#{ce_branch}\n" + `git fetch #{ce_repo} #{ce_branch} --depth 1` + + # Try to merge the current tested branch to EE/master... + puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" + `git merge --ff-only FETCH_HEAD` + + exit 0 if $?.success? + + # Try to merge a possible -ee branch to EE/master... + puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" + `git merge --ff-only #{ee_branch}` + + # The -ee doesn't exist + if $?.exitstatus == 1 + puts <<-MSG.strip_heredoc + \n================================================================= + The #{ce_branch} branch cannot be merged without conflicts to the + current EE/master, and no #{ee_branch} branch was detected in + the EE repository. + + Please create a #{ee_branch} branch that includes changes + #{ce_branch} but also specific changes than can be applied cleanly + to EE/master. + + You can create this branch as follow: + + 1. In the EE repo: + $ git fetch origin + $ git fetch #{ce_repo} #{ce_branch} + $ git checkout -b #{ee_branch} FETCH_HEAD + $ git rebase origin/master + 2. At this point you will likely have conflicts, solve them, and + continue/finish the rebase. + 3. You can squash all the original #{ce_branch} commits into a + single "Port of #{ce_branch} to EE". + 4. Push your branch to #{ee_repo}: + $ git push origin #{ee_branch} + =================================================================\n + MSG + + exit 1 + end + + # The -ee cannot be merged cleanly to EE/master... + unless $?.success? + puts <<-MSG.strip_heredoc + \n================================================================= + The #{ce_branch} branch cannot be merged without conflicts to + EE/master, and even though the #{ee_branch} branch exists in the EE + repository, it cannot be merged without conflicts to EE/master. + + Please update the #{ee_branch}, push it again to #{ee_repo}, and + retry this job. + =================================================================\n + MSG + + exit 2 + end + + puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" + `git merge --ff-only FETCH_HEAD` + exit 0 if $?.success? + + # The -ee can be merged cleanly to EE/master, but still + # cannot be merged cleanly to EE/master... + puts <<-MSG.strip_heredoc + \n================================================================= + The #{ce_branch} branch cannot be merged without conflicts to EE, and + even though the #{ee_branch} branch exists in the EE repository and + applies cleanly to EE/master, it doesn't prevent conflicts when + merging #{ce_branch} into EE. + + We may be in a complex situation here. + =================================================================\n + MSG + + exit 3 + end + end + end +end -- cgit v1.2.1 From 2650d5f895c6f7e56f7ba76e5fa448d38fd15d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 7 Oct 2016 17:21:51 +0200 Subject: Improve the branch existence and merge checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also add a safeguard for non-CI env. Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 2 +- lib/tasks/ce_to_ee_merge_check.rake | 2 -- lib/tasks/gitlab/dev.rake | 35 +++++++++++++++++++++++------------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7e2d705a888..8708eae1b2a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -216,7 +216,7 @@ rake ce_to_ee_merge_check: - branches except: - tags - - master + allow_failure: yes rake db:migrate:reset: stage: test diff --git a/lib/tasks/ce_to_ee_merge_check.rake b/lib/tasks/ce_to_ee_merge_check.rake index bb87f85cd9c..424e7883060 100644 --- a/lib/tasks/ce_to_ee_merge_check.rake +++ b/lib/tasks/ce_to_ee_merge_check.rake @@ -1,6 +1,4 @@ desc 'Checks if the branch would apply cleanly to EE' task ce_to_ee_merge_check: :environment do - return if defined?(Gitlab::License) - Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke end diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake index bf17ba499bc..47bdb2d32d2 100644 --- a/lib/tasks/gitlab/dev.rake +++ b/lib/tasks/gitlab/dev.rake @@ -2,6 +2,9 @@ namespace :gitlab do namespace :dev do desc 'Checks if the branch would apply cleanly to EE' task ce_to_ee_merge_check: :environment do + return if defined?(Gitlab::License) + return unless ENV['CI'] + ce_repo = ENV['CI_BUILD_REPO'] ce_branch = ENV['CI_BUILD_REF_NAME'] @@ -17,27 +20,28 @@ namespace :gitlab do # Try to merge the current tested branch to EE/master... puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" - `git merge --ff-only FETCH_HEAD` + `git merge FETCH_HEAD` exit 0 if $?.success? - # Try to merge a possible -ee branch to EE/master... - puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" - `git merge --ff-only #{ee_branch}` + # Check if the -ee branch exists... + puts "\n => Check if #{ee_repo}/#{ee_branch} exists\n" + `git rev-parse --verify #{ee_branch}` # The -ee doesn't exist - if $?.exitstatus == 1 + unless $?.success? + puts puts <<-MSG.strip_heredoc - \n================================================================= + ================================================================= The #{ce_branch} branch cannot be merged without conflicts to the current EE/master, and no #{ee_branch} branch was detected in the EE repository. - Please create a #{ee_branch} branch that includes changes + Please create a #{ee_branch} branch that includes changes from #{ce_branch} but also specific changes than can be applied cleanly to EE/master. - You can create this branch as follow: + You can create this branch as follows: 1. In the EE repo: $ git fetch origin @@ -45,7 +49,8 @@ namespace :gitlab do $ git checkout -b #{ee_branch} FETCH_HEAD $ git rebase origin/master 2. At this point you will likely have conflicts, solve them, and - continue/finish the rebase. + continue/finish the rebase. Note: You can squash the CE commits + before rebasing. 3. You can squash all the original #{ce_branch} commits into a single "Port of #{ce_branch} to EE". 4. Push your branch to #{ee_repo}: @@ -56,10 +61,15 @@ namespace :gitlab do exit 1 end + # Try to merge the -ee branch to EE/master... + puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" + `git merge #{ee_branch} master` + # The -ee cannot be merged cleanly to EE/master... unless $?.success? + puts puts <<-MSG.strip_heredoc - \n================================================================= + ================================================================= The #{ce_branch} branch cannot be merged without conflicts to EE/master, and even though the #{ee_branch} branch exists in the EE repository, it cannot be merged without conflicts to EE/master. @@ -73,13 +83,14 @@ namespace :gitlab do end puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" - `git merge --ff-only FETCH_HEAD` + `git merge FETCH_HEAD` exit 0 if $?.success? # The -ee can be merged cleanly to EE/master, but still # cannot be merged cleanly to EE/master... + puts puts <<-MSG.strip_heredoc - \n================================================================= + ================================================================= The #{ce_branch} branch cannot be merged without conflicts to EE, and even though the #{ee_branch} branch exists in the EE repository and applies cleanly to EE/master, it doesn't prevent conflicts when -- cgit v1.2.1 From e836b904b77e857ad2e1a0bc65129380d5f0e6dc Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 13 Oct 2016 17:52:21 +0200 Subject: Grapify system hooks API --- lib/api/system_hooks.rb | 60 ++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 22b8f90dc5c..2e76b91051f 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -7,38 +7,36 @@ module API end resource :hooks do - # Get the list of system hooks - # - # Example Request: - # GET /hooks + desc 'Get the list of system hooks' do + success Entities::Hook + end get do - @hooks = SystemHook.all - present @hooks, with: Entities::Hook + hooks = SystemHook.all + present hooks, with: Entities::Hook end - # Create new system hook - # - # Parameters: - # url (required) - url for system hook - # Example Request - # POST /hooks + desc 'Create a new system hook' do + success Entities::Hook + end + params do + requires :url, type: String, desc: 'The URL for the system hook' + end post do - attrs = attributes_for_keys [:url] - required_attributes! [:url] - @hook = SystemHook.new attrs - if @hook.save - present @hook, with: Entities::Hook + hook = SystemHook.new declared(params).to_h + + if hook.save + present hook, with: Entities::Hook else not_found! end end - # Test a hook - # - # Example Request - # GET /hooks/:id + desc 'Test a hook' + params do + requires :id, type: Integer, desc: 'The ID of the system hook' + end get ":id" do - @hook = SystemHook.find(params[:id]) + hook = SystemHook.find(params[:id]) data = { event_name: "project_create", name: "Ruby", @@ -47,20 +45,20 @@ module API owner_name: "Someone", owner_email: "example@gitlabhq.com" } - @hook.execute(data, 'system_hooks') + hook.execute(data, 'system_hooks') data end - # Delete a hook. This is an idempotent function. - # - # Parameters: - # id (required) - ID of the hook - # Example Request: - # DELETE /hooks/:id + desc 'Delete a hook' do + success Entities::Hook + end + params do + requires :id, type: Integer, desc: 'The ID of the system hook' + end delete ":id" do begin - @hook = SystemHook.find(params[:id]) - @hook.destroy + hook = SystemHook.find(params[:id]) + present hook.destroy, with: Entities::Hook rescue # SystemHook raises an Error if no hook with id found end -- cgit v1.2.1 From 36309374df8feeea99c4eef5b0fd1e27d41ff6c9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 13 Oct 2016 19:16:45 +0200 Subject: Do not run before_script, artifacts, cache in trigger_docs job --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d19f55aca3..6b54d07d1d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -302,6 +302,9 @@ coverage: # Trigger docs build trigger_docs: stage: post-test + before_script: [] + cache: {} + artifacts: {} script: - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v3/projects/38069/trigger/builds" only: -- cgit v1.2.1 From 8751491b8db471dc661daa19bc82a9dbd58e4aae Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 13 Oct 2016 13:58:31 -0500 Subject: Improve tabbing usability for sign in page --- CHANGELOG | 1 + app/assets/stylesheets/pages/login.scss | 10 ++++++++++ app/views/devise/sessions/_new_base.html.haml | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7f96a9cba4e..a6091d3f4f1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.13.0 (unreleased) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page + - Improve tabbing usability for sign in page (ClemMakesApps) - Adding members no longer silently fails when there is extra whitespace - Update runner version only when updating contacted_at - Add link from system note to compare with previous version diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index a5ca509163d..a08b033dff9 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -48,6 +48,16 @@ margin: 0 0 10px; } + .new_user { + position: relative; + padding-bottom: 35px; + } + + .sign-in { + position: absolute; + bottom: 0; + } + .login-footer { margin-top: 10px; diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 9f5520603cd..781fd1b32a6 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,6 +1,8 @@ = form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off" = f.password_field :password, class: "form-control bottom", placeholder: "Password" + .sign-in + = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "user_remember_me"} @@ -8,5 +10,3 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) - %div - = f.submit "Sign in", class: "btn btn-save" -- cgit v1.2.1 From 5c5259335f8bcc4de117c1e36648a269911281fb Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 11:39:37 +0100 Subject: Add instrumentation to conflict classes --- config/initializers/metrics.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index be22085b0df..3b8771543e4 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -67,6 +67,7 @@ if Gitlab::Metrics.enabled? ['app', 'finders'] => ['app', 'finders'], ['app', 'mailers', 'emails'] => ['app', 'mailers'], ['app', 'services', '**'] => ['app', 'services'], + ['lib', 'gitlab', 'conflicts'] => ['lib'], ['lib', 'gitlab', 'diff'] => ['lib'], ['lib', 'gitlab', 'email', 'message'] => ['lib'], ['lib', 'gitlab', 'checks'] => ['lib'] -- cgit v1.2.1 From 3f71c43e88c56bb5310c8814cd9f95cafb4f53ef Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 13:59:10 +0100 Subject: Allow setting content for resolutions When reading conflicts: 1. Add a `type` field. `text` works as before, and has `sections`; `text-editor` is a file with ambiguous conflict markers that can only be resolved in an editor. 2. Add a `content_path` field pointing to a JSON representation of the file's content for a single file. 3. Hitting `content_path` returns a similar datastructure to the `file`, but without the `content_path` and `sections` fields, and with a `content` field containing the full contents of the file (with conflict markers). When writing conflicts: 1. Instead of `sections` being at the top level, they are now in a `files` array. This matches the read format better. 2. The `files` array contains file hashes, each of which must contain: a. `new_path` b. `old_path` c. EITHER `sections` (which works as before) or `content` (with the full content of the resolved file). --- app/controllers/application_controller.rb | 7 +- .../projects/merge_requests_controller.rb | 20 ++- app/models/merge_request.rb | 2 +- app/services/merge_requests/resolve_service.rb | 24 ++- config/routes/project.rb | 1 + lib/gitlab/conflict/file.rb | 51 ++++++- lib/gitlab/conflict/file_collection.rb | 4 + lib/gitlab/conflict/parser.rb | 15 +- lib/gitlab/conflict/resolution_error.rb | 6 + .../projects/merge_requests_controller_spec.rb | 168 +++++++++++++++++++-- .../merge_requests/resolve_service_spec.rb | 139 +++++++++++++++-- 11 files changed, 395 insertions(+), 42 deletions(-) create mode 100644 lib/gitlab/conflict/resolution_error.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b3455e04c29..bf37421771f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -114,7 +114,12 @@ class ApplicationController < ActionController::Base end def render_404 - render file: Rails.root.join("public", "404"), layout: false, status: "404" + respond_to do |format| + format.json { head :not_found } + format.any do + render file: Rails.root.join("public", "404"), layout: false, status: "404" + end + end end def no_cache_headers diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 869d96b86f4..2c7a0062f2b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -9,15 +9,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ - :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, + :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines, :merge, :merge_check, :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] - before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] + before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check] before_action :define_commit_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs] - before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] + before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines] before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :apply_diff_view_cookie!, only: [:new_diffs] before_action :build_merge_request, only: [:new, :new_diffs] @@ -33,7 +33,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :authenticate_user!, only: [:assign_related_issues] - before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts] + before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :conflict_for_path, :resolve_conflicts] def index @merge_requests = merge_requests_collection @@ -170,6 +170,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + def conflict_for_path + return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui? + + file = @merge_request.conflicts.file_for_path(params[:old_path], params[:new_path]) + + return render_404 unless file + + render json: file, full_content: true + end + def resolve_conflicts return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui? @@ -184,7 +194,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) } - rescue Gitlab::Conflict::File::MissingResolution => e + rescue Gitlab::Conflict::ResolutionError => e render status: :bad_request, json: { message: e.message } end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a743bf313ae..1fb0371fe06 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -868,7 +868,7 @@ class MergeRequest < ActiveRecord::Base # files. conflicts.files.each(&:lines) @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0 - rescue Rugged::OdbError, Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing + rescue Rugged::OdbError, Gitlab::Conflict::Parser::UnresolvableError, Gitlab::Conflict::FileCollection::ConflictSideMissing @conflicts_can_be_resolved_in_ui = false end end diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb index 19caa038c44..d22a1d3e0ad 100644 --- a/app/services/merge_requests/resolve_service.rb +++ b/app/services/merge_requests/resolve_service.rb @@ -1,5 +1,8 @@ module MergeRequests class ResolveService < MergeRequests::BaseService + class MissingFiles < Gitlab::Conflict::ResolutionError + end + attr_accessor :conflicts, :rugged, :merge_index, :merge_request def execute(merge_request) @@ -10,8 +13,16 @@ module MergeRequests fetch_their_commit! - conflicts.files.each do |file| - write_resolved_file_to_index(file, params[:sections]) + params[:files].each do |file_params| + conflict_file = merge_request.conflicts.file_for_path(file_params[:old_path], file_params[:new_path]) + + write_resolved_file_to_index(conflict_file, file_params) + end + + unless merge_index.conflicts.empty? + missing_files = merge_index.conflicts.map { |file| file[:ours][:path] } + + raise MissingFiles, "Missing resolutions for the following files: #{missing_files.join(', ')}" end commit_params = { @@ -23,8 +34,13 @@ module MergeRequests project.repository.resolve_conflicts(current_user, merge_request.source_branch, commit_params) end - def write_resolved_file_to_index(file, resolutions) - new_file = file.resolve_lines(resolutions).map(&:text).join("\n") + def write_resolved_file_to_index(file, params) + new_file = if params[:sections] + file.resolve_lines(params[:sections]).map(&:text).join("\n") + elsif params[:content] + file.resolve_content(params[:content]) + end + our_path = file.our_path merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode) diff --git a/config/routes/project.rb b/config/routes/project.rb index f9d58f5d5b2..cbce9cd47a0 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -267,6 +267,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: get :commits get :diffs get :conflicts + get :conflict_for_path get :builds get :pipelines get :merge_check diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index dff9e29c6a5..26a9f170298 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -4,12 +4,12 @@ module Gitlab include Gitlab::Routing.url_helpers include IconsHelper - class MissingResolution < StandardError + class MissingResolution < ResolutionError end CONTEXT_LINES = 3 - attr_reader :merge_file_result, :their_path, :our_path, :our_mode, :merge_request, :repository + attr_reader :merge_file_result, :their_path, :our_path, :our_mode, :merge_request, :repository, :type def initialize(merge_file_result, conflict, merge_request:) @merge_file_result = merge_file_result @@ -21,12 +21,24 @@ module Gitlab @match_line_headers = {} end + def content + merge_file_result[:data] + end + # Array of Gitlab::Diff::Line objects def lines - @lines ||= Gitlab::Conflict::Parser.new.parse(merge_file_result[:data], + return @lines if defined?(@lines) + + begin + @type = 'text' + @lines = Gitlab::Conflict::Parser.new.parse(content, our_path: our_path, their_path: their_path, parent_file: self) + rescue Gitlab::Conflict::Parser::ParserError + @type = 'text-editor' + @lines = nil + end end def resolve_lines(resolution) @@ -53,6 +65,14 @@ module Gitlab end.compact end + def resolve_content(resolution) + if resolution == content + raise MissingResolution, "Resolved content has no changes for file #{our_path}" + end + + resolution + end + def highlight_lines! their_file = lines.reject { |line| line.type == 'new' }.map(&:text).join("\n") our_file = lines.reject { |line| line.type == 'old' }.map(&:text).join("\n") @@ -170,21 +190,36 @@ module Gitlab match_line.text = "@@ -#{match_line.old_pos},#{line.old_pos} +#{match_line.new_pos},#{line.new_pos} @@#{header}" end - def as_json(opts = nil) - { + def as_json(opts = {}) + json_hash = { old_path: their_path, new_path: our_path, blob_icon: file_type_icon_class('file', our_mode, our_path), blob_path: namespace_project_blob_path(merge_request.project.namespace, merge_request.project, - ::File.join(merge_request.diff_refs.head_sha, our_path)), - sections: sections + ::File.join(merge_request.diff_refs.head_sha, our_path)) } + + if opts[:full_content] + json_hash.merge(content: content) + else + json_hash.merge!(sections: sections) if type == 'text' + + json_hash.merge(type: type, content_path: content_path) + end + end + + def content_path + conflict_for_path_namespace_project_merge_request_path(merge_request.project.namespace, + merge_request.project, + merge_request, + old_path: their_path, + new_path: our_path) end # Don't try to print merge_request or repository. def inspect - instance_variables = [:merge_file_result, :their_path, :our_path, :our_mode].map do |instance_variable| + instance_variables = [:merge_file_result, :their_path, :our_path, :our_mode, :type].map do |instance_variable| value = instance_variable_get("@#{instance_variable}") "#{instance_variable}=\"#{value}\"" diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index bbd0427a2c8..fa5bd4649d4 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -30,6 +30,10 @@ module Gitlab end end + def file_for_path(old_path, new_path) + files.find { |file| file.their_path == old_path && file.our_path == new_path } + end + def as_json(opts = nil) { target_branch: merge_request.target_branch, diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index 98e842cded3..ddd657903fb 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -1,19 +1,24 @@ module Gitlab module Conflict class Parser - class ParserError < StandardError + class UnresolvableError < StandardError end - class UnexpectedDelimiter < ParserError + class UnmergeableFile < UnresolvableError end - class MissingEndDelimiter < ParserError + class UnsupportedEncoding < UnresolvableError + end + + # Recoverable errors - the conflict can be resolved in an editor, but not with + # sections. + class ParserError < StandardError end - class UnmergeableFile < ParserError + class UnexpectedDelimiter < ParserError end - class UnsupportedEncoding < ParserError + class MissingEndDelimiter < ParserError end def parse(text, our_path:, their_path:, parent_file: nil) diff --git a/lib/gitlab/conflict/resolution_error.rb b/lib/gitlab/conflict/resolution_error.rb new file mode 100644 index 00000000000..a0f2006bc24 --- /dev/null +++ b/lib/gitlab/conflict/resolution_error.rb @@ -0,0 +1,6 @@ +module Gitlab + module Conflict + class ResolutionError < StandardError + end + end +end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 84298f8bef4..1311b4aa264 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -570,7 +570,7 @@ describe Projects::MergeRequestsController do context 'when the conflicts cannot be resolved in the UI' do before do allow_any_instance_of(Gitlab::Conflict::Parser). - to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnexpectedDelimiter) + to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) get :conflicts, namespace_id: merge_request_with_conflicts.project.namespace.to_param, @@ -655,6 +655,61 @@ describe Projects::MergeRequestsController do id: merge_request.iid expect(merge_request.reload.title).to eq(merge_request.wipless_title) + end + + describe 'GET conflict_for_path' do + let(:json_response) { JSON.parse(response.body) } + + def conflict_for_path(path) + get :conflict_for_path, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project.to_param, + id: merge_request_with_conflicts.iid, + old_path: path, + new_path: path, + format: 'json' + end + + context 'when the conflicts cannot be resolved in the UI' do + before do + allow_any_instance_of(Gitlab::Conflict::Parser). + to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) + + conflict_for_path('files/ruby/regex.rb') + end + + + it 'returns a 404 status code' do + expect(response).to have_http_status(:not_found) + end + end + + context 'when the file does not exist cannot be resolved in the UI' do + before { conflict_for_path('files/ruby/regexp.rb') } + + it 'returns a 404 status code' do + expect(response).to have_http_status(:not_found) + end + end + + context 'with an existing file' do + let(:path) { 'files/ruby/regex.rb' } + + before { conflict_for_path(path) } + + it 'returns a 200 status code' do + expect(response).to have_http_status(:ok) + end + + it 'returns the file in JSON format' do + content = merge_request_with_conflicts.conflicts.file_for_path(path, path).content + + expect(json_response).to include('old_path' => path, + 'new_path' => path, + 'blob_icon' => 'file-text-o', + 'blob_path' => a_string_ending_with(path), + 'content' => content) + end end end @@ -662,22 +717,37 @@ describe Projects::MergeRequestsController do let(:json_response) { JSON.parse(response.body) } let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } - def resolve_conflicts(sections) + def resolve_conflicts(files) post :resolve_conflicts, namespace_id: merge_request_with_conflicts.project.namespace.to_param, project_id: merge_request_with_conflicts.project.to_param, id: merge_request_with_conflicts.iid, format: 'json', - sections: sections, + files: files, commit_message: 'Commit message' end context 'with valid params' do before do - resolve_conflicts('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin') + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'sections' => { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) end it 'creates a new commit on the branch' do @@ -692,7 +762,23 @@ describe Projects::MergeRequestsController do context 'when sections are missing' do before do - resolve_conflicts('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head') + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'sections' => { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head' + } + } + ] + + resolve_conflicts(resolved_files) end it 'returns a 400 error' do @@ -700,7 +786,71 @@ describe Projects::MergeRequestsController do end it 'has a message with the name of the first missing section' do - expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9') + expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + + context 'when files are missing' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the name of the missing file' do + expect(json_response['message']).to include('files/ruby/popen.rb') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + + context 'when a file has identical content to the conflict' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'content' => merge_request_with_conflicts.conflicts.file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb').content + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the path of the problem file' do + expect(json_response['message']).to include('files/ruby/popen.rb') end it 'does not create a new commit' do diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb index d71932458fa..e667e93bea4 100644 --- a/spec/services/merge_requests/resolve_service_spec.rb +++ b/spec/services/merge_requests/resolve_service_spec.rb @@ -24,15 +24,26 @@ describe MergeRequests::ResolveService do end describe '#execute' do - context 'with valid params' do + context 'with section params' do let(:params) do { - sections: { - '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' - }, + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + sections: { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + old_path: 'files/ruby/regex.rb', + new_path: 'files/ruby/regex.rb', + sections: { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ], commit_message: 'This is a commit message!' } end @@ -74,8 +85,96 @@ describe MergeRequests::ResolveService do end end - context 'when a resolution is missing' do - let(:invalid_params) { { sections: { '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' } } } + context 'with content and sections params' do + let(:popen_content) { "class Popen\nend" } + + let(:params) do + { + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + content: popen_content + }, { + old_path: 'files/ruby/regex.rb', + new_path: 'files/ruby/regex.rb', + sections: { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ], + commit_message: 'This is a commit message!' + } + end + + before do + MergeRequests::ResolveService.new(project, user, params).execute(merge_request) + end + + it 'creates a commit with the message' do + expect(merge_request.source_branch_head.message).to eq(params[:commit_message]) + end + + it 'creates a commit with the correct parents' do + expect(merge_request.source_branch_head.parents.map(&:id)). + to eq(['1450cd639e0bc6721eb02800169e464f212cde06', + '75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b']) + end + + it 'sets the content to the content given' do + blob = merge_request.source_project.repository.blob_at(merge_request.source_branch_head.sha, + 'files/ruby/popen.rb') + + expect(blob.data).to eq(popen_content) + end + end + + context 'when a resolution section is missing' do + let(:invalid_params) do + { + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + content: '' + }, { + old_path: 'files/ruby/regex.rb', + new_path: 'files/ruby/regex.rb', + sections: { '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head' } + } + ], + commit_message: 'This is a commit message!' + } + end + + let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) } + + it 'raises a MissingResolution error' do + expect { service.execute(merge_request) }. + to raise_error(Gitlab::Conflict::File::MissingResolution) + end + end + + context 'when the content of a file is unchanged' do + let(:invalid_params) do + { + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + content: '' + }, { + old_path: 'files/ruby/regex.rb', + new_path: 'files/ruby/regex.rb', + content: merge_request.conflicts.file_for_path('files/ruby/regex.rb', 'files/ruby/regex.rb').content + } + ], + commit_message: 'This is a commit message!' + } + end + let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) } it 'raises a MissingResolution error' do @@ -83,5 +182,27 @@ describe MergeRequests::ResolveService do to raise_error(Gitlab::Conflict::File::MissingResolution) end end + + context 'when a file is missing' do + let(:invalid_params) do + { + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + content: '' + } + ], + commit_message: 'This is a commit message!' + } + end + + let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) } + + it 'raises a MissingFiles error' do + expect { service.execute(merge_request) }. + to raise_error(MergeRequests::ResolveService::MissingFiles) + end + end end end -- cgit v1.2.1 From 9727366b5a0a39a125925e2a7a78ed47e29b02f7 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 16:19:16 +0100 Subject: Make RuboCop happy --- spec/controllers/projects/merge_requests_controller_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 1311b4aa264..b4f637c93e2 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -678,7 +678,6 @@ describe Projects::MergeRequestsController do conflict_for_path('files/ruby/regex.rb') end - it 'returns a 404 status code' do expect(response).to have_http_status(:not_found) end -- cgit v1.2.1 From 241cca011f9a35c03e8f5fae1381a4af8a8f26bb Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 16:20:29 +0100 Subject: Fix specs --- app/controllers/application_controller.rb | 4 ++-- spec/support/test_env.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bf37421771f..81c0fa26d18 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -115,10 +115,10 @@ class ApplicationController < ActionController::Base def render_404 respond_to do |format| - format.json { head :not_found } - format.any do + format.html do render file: Rails.root.join("public", "404"), layout: false, status: "404" end + format.any { head :not_found } end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index d56274d0979..243d671c521 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -26,10 +26,10 @@ module TestEnv 'expand-collapse-lines' => '238e82d', 'video' => '8879059', 'crlf-diff' => '5938907', - 'conflict-start' => '75284c7', + 'conflict-start' => '824be60', 'conflict-resolvable' => '1450cd6', 'conflict-binary-file' => '259a6fb', - 'conflict-contains-conflict-markers' => '5e0964c', + 'conflict-contains-conflict-markers' => '78a3086', 'conflict-missing-side' => 'eb227b3', 'conflict-non-utf8' => 'd0a293c', 'conflict-too-large' => '39fa04f', -- cgit v1.2.1 From 082be0917cf15d410b1db3ca49b32049a56c117e Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 16:50:53 +0100 Subject: Fix MR model spec --- spec/models/merge_request_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 5884b4cff8c..91a423b670c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1155,12 +1155,6 @@ describe MergeRequest, models: true do expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey end - it 'returns a falsey value when the conflicts contain a file with ambiguous conflict markers' do - merge_request = create_merge_request('conflict-contains-conflict-markers') - - expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey - end - it 'returns a falsey value when the conflicts contain a file edited in one branch and deleted in another' do merge_request = create_merge_request('conflict-missing-side') @@ -1172,6 +1166,12 @@ describe MergeRequest, models: true do expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy end + + it 'returns a truthy value when the conflicts have to be resolved in an editor' do + merge_request = create_merge_request('conflict-contains-conflict-markers') + + expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy + end end describe "#forked_source_project_missing?" do -- cgit v1.2.1 From 98dd958df3b4dd1b58b3a3387ea9d52dc78af46b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 17:44:35 +0100 Subject: Fix resolve service specs --- spec/services/merge_requests/resolve_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb index e667e93bea4..388abb6a0df 100644 --- a/spec/services/merge_requests/resolve_service_spec.rb +++ b/spec/services/merge_requests/resolve_service_spec.rb @@ -60,7 +60,7 @@ describe MergeRequests::ResolveService do it 'creates a commit with the correct parents' do expect(merge_request.source_branch_head.parents.map(&:id)). to eq(['1450cd639e0bc6721eb02800169e464f212cde06', - '75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b']) + '824be604a34828eb682305f0d963056cfac87b2d']) end end @@ -120,7 +120,7 @@ describe MergeRequests::ResolveService do it 'creates a commit with the correct parents' do expect(merge_request.source_branch_head.parents.map(&:id)). to eq(['1450cd639e0bc6721eb02800169e464f212cde06', - '75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b']) + '824be604a34828eb682305f0d963056cfac87b2d']) end it 'sets the content to the content given' do -- cgit v1.2.1 From 7529bbae944823041e8690c011c4cfb39534074b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 2 Sep 2016 16:16:54 +0100 Subject: Add JSON Schema --- .../projects/merge_requests_controller_spec.rb | 4 + spec/fixtures/api/schemas/conflicts.json | 137 +++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 spec/fixtures/api/schemas/conflicts.json diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index b4f637c93e2..06b37aa4997 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -597,6 +597,10 @@ describe Projects::MergeRequestsController do format: 'json' end + it 'matches the schema' do + expect(response).to match_response_schema('conflicts') + end + it 'includes meta info about the MR' do expect(json_response['commit_message']).to include('Merge branch') expect(json_response['commit_sha']).to match(/\h{40}/) diff --git a/spec/fixtures/api/schemas/conflicts.json b/spec/fixtures/api/schemas/conflicts.json new file mode 100644 index 00000000000..a947783d505 --- /dev/null +++ b/spec/fixtures/api/schemas/conflicts.json @@ -0,0 +1,137 @@ +{ + "type": "object", + "required": [ + "commit_message", + "commit_sha", + "source_branch", + "target_branch", + "files" + ], + "properties": { + "commit_message": {"type": "string"}, + "commit_sha": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, + "source_branch": {"type": "string"}, + "target_branch": {"type": "string"}, + "files": { + "type": "array", + "items": { + "oneOf": [ + { "$ref": "#/definitions/conflict-text-with-sections" }, + { "$ref": "#/definitions/conflict-text-for-editor" } + ] + } + } + }, + "definitions": { + "conflict-base": { + "type": "object", + "required": [ + "old_path", + "new_path", + "blob_icon", + "blob_path" + ], + "properties": { + "old_path": {"type": "string"}, + "new_path": {"type": "string"}, + "blob_icon": {"type": "string"}, + "blob_path": {"type": "string"} + } + }, + "conflict-text-for-editor": { + "allOf": [ + {"$ref": "#/definitions/conflict-base"}, + { + "type": "object", + "required": [ + "type", + "content_path" + ], + "properties": { + "type": {"type": {"enum": ["text-editor"]}}, + "content_path": {"type": "string"} + } + } + ] + }, + "conflict-text-with-sections": { + "allOf": [ + {"$ref": "#/definitions/conflict-base"}, + { + "type": "object", + "required": [ + "type", + "content_path", + "sections" + ], + "properties": { + "type": {"type": {"enum": ["text"]}}, + "content_path": {"type": "string"}, + "sections": { + "type": "array", + "items": { + "oneOf": [ + { "$ref": "#/definitions/section-context" }, + { "$ref": "#/definitions/section-conflict" } + ] + } + } + } + } + ] + }, + "section-base": { + "type": "object", + "required": [ + "conflict", + "lines" + ], + "properties": { + "conflict": {"type": "boolean"}, + "lines": { + "type": "array", + "items": { + "type": "object", + "required": [ + "old_line", + "new_line", + "text", + "rich_text" + ], + "properties": { + "type": {"type": "string"}, + "old_line": {"type": "string"}, + "new_line": {"type": "string"}, + "text": {"type": "string"}, + "rich_text": {"type": "string"} + } + } + } + } + }, + "section-context": { + "allOf": [ + {"$ref": "#/definitions/section-base"}, + { + "type": "object", + "properties": { + "conflict": {"enum": [false]} + } + } + ] + }, + "section-conflict": { + "allOf": [ + {"$ref": "#/definitions/section-base"}, + { + "type": "object", + "required": ["id"], + "properties": { + "conflict": {"enum": [true]}, + "id": {"type": "string"} + } + } + ] + } + } +} -- cgit v1.2.1 From 4743d19463e7aef965665e43238af73820d18d7f Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 7 Sep 2016 12:28:13 +0100 Subject: Simplify conflict file JSON creation --- lib/gitlab/conflict/file.rb | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 26a9f170298..661e43d3fa9 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -9,7 +9,7 @@ module Gitlab CONTEXT_LINES = 3 - attr_reader :merge_file_result, :their_path, :our_path, :our_mode, :merge_request, :repository, :type + attr_reader :merge_file_result, :their_path, :our_path, :our_mode, :merge_request, :repository def initialize(merge_file_result, conflict, merge_request:) @merge_file_result = merge_file_result @@ -25,6 +25,12 @@ module Gitlab merge_file_result[:data] end + def type + lines unless @type + + @type.inquiry + end + # Array of Gitlab::Diff::Line objects def lines return @lines if defined?(@lines) @@ -200,12 +206,14 @@ module Gitlab ::File.join(merge_request.diff_refs.head_sha, our_path)) } - if opts[:full_content] - json_hash.merge(content: content) - else - json_hash.merge!(sections: sections) if type == 'text' - - json_hash.merge(type: type, content_path: content_path) + json_hash.tap do |json_hash| + if opts[:full_content] + json_hash[:content] = content + else + json_hash[:sections] = sections if type.text? + json_hash[:type] = type + json_hash[:content_path] = content_path + end end end -- cgit v1.2.1 From 6af52d7d23cf9dbfcd58a2d3031ed19887f7a558 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 20 Sep 2016 09:16:35 +0300 Subject: We now support resolving conflicts with ambiguous markers --- spec/features/merge_requests/conflicts_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 759edf8ec80..1e8aeacea05 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -42,7 +42,6 @@ feature 'Merge request conflict resolution', js: true, feature: true do UNRESOLVABLE_CONFLICTS = { 'conflict-too-large' => 'when the conflicts contain a large file', 'conflict-binary-file' => 'when the conflicts contain a binary file', - 'conflict-contains-conflict-markers' => 'when the conflicts contain a file with ambiguous conflict markers', 'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another', 'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file', } -- cgit v1.2.1 From 26f658decd3943bbc66c567ea91e7b859b32e0e6 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 8 Sep 2016 11:44:04 -0500 Subject: Implement editor to manually resolve merge conflicts --- .../merge_conflict_data_provider.js.es6 | 165 ++++++++++++++++----- .../javascripts/merge_conflict_resolver.js.es6 | 60 ++++++-- .../components/diff_file_editor.js.es6 | 63 ++++++++ app/assets/stylesheets/pages/merge_conflicts.scss | 26 ++++ .../projects/merge_requests/conflicts.html.haml | 3 + .../conflicts/_diff_file_editor.html.haml | 9 ++ .../conflicts/_file_actions.html.haml | 12 ++ .../conflicts/_inline_view.html.haml | 10 +- .../conflicts/_parallel_view.html.haml | 10 +- .../conflicts/_submit_form.html.haml | 9 +- .../components/_diff_file_editor.html.haml | 6 + 11 files changed, 300 insertions(+), 73 deletions(-) create mode 100644 app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 create mode 100644 app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/_file_actions.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index 13ee794ba38..bfed78ff99c 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -2,7 +2,9 @@ const HEAD_HEADER_TEXT = 'HEAD//our changes'; const ORIGIN_HEADER_TEXT = 'origin//their changes'; const HEAD_BUTTON_TITLE = 'Use ours'; const ORIGIN_BUTTON_TITLE = 'Use theirs'; - +const INTERACTIVE_RESOLVE_MODE = 'interactive'; +const EDIT_RESOLVE_MODE = 'edit'; +const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE; class MergeConflictDataProvider { @@ -18,8 +20,7 @@ class MergeConflictDataProvider { diffViewType : diffViewType, fixedLayout : fixedLayout, isSubmitting : false, - conflictsData : {}, - resolutionData : {} + conflictsData : {} } } @@ -35,9 +36,9 @@ class MergeConflictDataProvider { data.shortCommitSha = data.commit_sha.slice(0, 7); data.commitMessage = data.commit_message; + this.decorateFiles(data); this.setParallelLines(data); this.setInlineLines(data); - this.updateResolutionsData(data); } vueInstance.conflictsData = data; @@ -47,16 +48,12 @@ class MergeConflictDataProvider { vueInstance.conflictsData.conflictsText = conflictsText; } - - updateResolutionsData(data) { - const vi = this.vueInstance; - - data.files.forEach( (file) => { - file.sections.forEach( (section) => { - if (section.conflict) { - vi.$set(`resolutionData['${section.id}']`, false); - } - }); + decorateFiles(data) { + data.files.forEach((file) => { + file.content = ''; + file.resolutionData = {}; + file.promptDiscardConfirmation = false; + file.resolveMode = DEFAULT_RESOLVE_MODE; }); } @@ -165,11 +162,14 @@ class MergeConflictDataProvider { } - handleSelected(sectionId, selection) { + handleSelected(file, sectionId, selection) { const vi = this.vueInstance; + let files = vi.conflictsData.files; + + vi.$set(`conflictsData.files[${files.indexOf(file)}].resolutionData['${sectionId}']`, selection); + - vi.resolutionData[sectionId] = selection; - vi.conflictsData.files.forEach( (file) => { + files.forEach( (file) => { file.inlineLines.forEach( (line) => { if (line.id === sectionId && (line.hasConflict || line.isHeader)) { this.markLine(line, selection); @@ -208,6 +208,48 @@ class MergeConflictDataProvider { .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout); } + setFileResolveMode(file, mode) { + const vi = this.vueInstance; + + // Restore Interactive mode when switching to Edit mode + if (mode === EDIT_RESOLVE_MODE) { + file.resolutionData = {}; + + this.restoreFileLinesState(file); + } + + file.resolveMode = mode; + } + + + restoreFileLinesState(file) { + file.inlineLines.forEach((line) => { + if (line.hasConflict || line.isHeader) { + line.isSelected = false; + line.isUnselected = false; + } + }); + + file.parallelLines.forEach((lines) => { + const left = lines[0]; + const right = lines[1]; + const isLeftMatch = left.hasConflict || left.isHeader; + const isRightMatch = right.hasConflict || right.isHeader; + + if (isLeftMatch || isRightMatch) { + left.isSelected = false; + left.isUnselected = false; + right.isSelected = false; + right.isUnselected = false; + } + }); + } + + + setPromptConfirmationState(file, state) { + file.promptDiscardConfirmation = state; + } + markLine(line, selection) { if (selection === 'head' && line.isHead) { @@ -226,31 +268,54 @@ class MergeConflictDataProvider { getConflictsCount() { - return Object.keys(this.vueInstance.resolutionData).length; - } - - - getResolvedCount() { - let count = 0; - const data = this.vueInstance.resolutionData; + const files = this.vueInstance.conflictsData.files; + let count = 0; - for (const id in data) { - const resolution = data[id]; - if (resolution) { - count++; - } - } + files.forEach((file) => { + file.sections.forEach((section) => { + if (section.conflict) { + count++; + } + }); + }); return count; } isReadyToCommit() { - const { conflictsData, isSubmitting } = this.vueInstance - const allResolved = this.getConflictsCount() === this.getResolvedCount(); - const hasCommitMessage = $.trim(conflictsData.commitMessage).length; + const vi = this.vueInstance; + const files = this.vueInstance.conflictsData.files; + const hasCommitMessage = $.trim(this.vueInstance.conflictsData.commitMessage).length; + let unresolved = 0; + + for (let i = 0, l = files.length; i < l; i++) { + let file = files[i]; + + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + let numberConflicts = 0; + let resolvedConflicts = Object.keys(file.resolutionData).length + + for (let j = 0, k = file.sections.length; j < k; j++) { + if (file.sections[j].conflict) { + numberConflicts++; + } + } + + if (resolvedConflicts !== numberConflicts) { + unresolved++; + } + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + // Unlikely to happen since switching to Edit mode saves content automatically. + // Checking anyway in case the save strategy changes in the future + if (!file.content) { + unresolved++; + continue; + } + } + } - return !isSubmitting && hasCommitMessage && allResolved; + return !vi.isSubmitting && hasCommitMessage && !unresolved; } @@ -332,10 +397,33 @@ class MergeConflictDataProvider { getCommitData() { - return { - commit_message: this.vueInstance.conflictsData.commitMessage, - sections: this.vueInstance.resolutionData - } + let conflictsData = this.vueInstance.conflictsData; + let commitData = {}; + + commitData = { + commitMessage: conflictsData.commitMessage, + files: [] + }; + + conflictsData.files.forEach((file) => { + let addFile; + + addFile = { + old_path: file.old_path, + new_path: file.new_path + }; + + // Submit only one data for type of editing + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + addFile.sections = file.resolutionData; + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + addFile.content = file.content; + } + + commitData.files.push(addFile); + }); + + return commitData; } @@ -343,5 +431,4 @@ class MergeConflictDataProvider { const { old_path, new_path } = file; return old_path === new_path ? new_path : `${old_path} → ${new_path}`; } - } diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6 index 7e756433bf5..c317a6521b5 100644 --- a/app/assets/javascripts/merge_conflict_resolver.js.es6 +++ b/app/assets/javascripts/merge_conflict_resolver.js.es6 @@ -1,13 +1,16 @@ //= require vue +//= require ./merge_conflicts/components/diff_file_editor + +const INTERACTIVE_RESOLVE_MODE = 'interactive'; +const EDIT_RESOLVE_MODE = 'edit'; class MergeConflictResolver { constructor() { - this.dataProvider = new MergeConflictDataProvider() - this.initVue() + this.dataProvider = new MergeConflictDataProvider(); + this.initVue(); } - initVue() { const that = this; this.vue = new Vue({ @@ -17,15 +20,28 @@ class MergeConflictResolver { created : this.fetchData(), computed : this.setComputedProperties(), methods : { - handleSelected(sectionId, selection) { - that.dataProvider.handleSelected(sectionId, selection); + handleSelected(file, sectionId, selection) { + that.dataProvider.handleSelected(file, sectionId, selection); }, handleViewTypeChange(newType) { that.dataProvider.updateViewType(newType); }, commit() { that.commit(); - } + }, + onClickResolveModeButton(file, mode) { + that.toggleResolveMode(file, mode); + }, + acceptDiscardConfirmation(file) { + that.dataProvider.setPromptConfirmationState(file, false); + that.dataProvider.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE); + }, + cancelDiscardConfirmation(file) { + that.dataProvider.setPromptConfirmationState(file, false); + }, + }, + components: { + 'diff-file-editor': window.gl.diffFileEditor } }) } @@ -36,7 +52,6 @@ class MergeConflictResolver { return { conflictsCount() { return dp.getConflictsCount() }, - resolvedCount() { return dp.getResolvedCount() }, readyToCommit() { return dp.isReadyToCommit() }, commitButtonText() { return dp.getCommitButtonText() } } @@ -69,14 +84,29 @@ class MergeConflictResolver { commit() { this.vue.isSubmitting = true; - $.post($('#conflicts').data('resolveConflictsPath'), this.dataProvider.getCommitData()) - .done((data) => { - window.location.href = data.redirect_to; - }) - .error(() => { - this.vue.isSubmitting = false; - new Flash('Something went wrong!'); - }); + $.ajax({ + url: $('#conflicts').data('resolveConflictsPath'), + data: JSON.stringify(this.dataProvider.getCommitData()), + contentType: "application/json", + dataType: 'json', + method: 'POST' + }) + .done((data) => { + window.location.href = data.redirect_to; + }) + .error(() => { + this.vue.isSubmitting = false; + new Flash('Something went wrong!'); + }); } + + toggleResolveMode(file, mode) { + if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) { + this.dataProvider.setPromptConfirmationState(file, true); + return; + } + + this.dataProvider.setFileResolveMode(file, mode); + } } diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 new file mode 100644 index 00000000000..570d9ff877c --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -0,0 +1,63 @@ +((global) => { + global.diffFileEditor = Vue.extend({ + props: ['file', 'loadFile'], + template: '#diff-file-editor', + data() { + return { + originalState: '', + saved: false, + loading: false, + fileLoaded: false + } + }, + computed: { + classObject() { + return { + 'load-file': this.loadFile, + 'saved': this.saved, + 'is-loading': this.loading + }; + } + }, + watch: { + loadFile(val) { + const self = this; + + if (!val || this.fileLoaded || this.loading) { + return + } + + this.loading = true; + + $.get(this.file.content_path) + .done((file) => { + $(self.$el).find('textarea').val(file.content); + + self.originalState = file.content; + self.fileLoaded = true; + self.saveDiffResolution(); + }) + .fail(() => { + console.log('error'); + }) + .always(() => { + self.loading = false; + }); + } + }, + methods: { + saveDiffResolution() { + this.saved = true; + + // This probably be better placed in the data provider + this.file.content = this.$el.querySelector('textarea').value; + this.file.resolveEditChanged = this.file.content !== this.originalState; + this.file.promptDiscardConfirmation = false; + }, + onInput() { + this.saveDiffResolution(); + } + } + }); + +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 5ec660799e3..577a97e8c0e 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -235,4 +235,30 @@ $colors: ( .btn-success .fa-spinner { color: #fff; } + + .editor-wrap { + &.is-loading { + .editor { + display: none; + } + + .loading-text { + display: block; + } + } + + &.saved { + .editor { + border-top: solid 1px green; + } + } + + .editor { + border-top: solid 1px yellow; + } + + .loading-text { + display: none; + } + } } diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index a524936f73c..e3add1f799f 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -27,3 +27,6 @@ = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/submit_form" + +-# Components += render partial: 'projects/merge_requests/conflicts/components/diff_file_editor' diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml new file mode 100644 index 00000000000..d0c518e5249 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml @@ -0,0 +1,9 @@ +- if_condition = local_assigns.fetch(:if_condition, '') + +.diff-editor-wrap{ "v-show" => if_condition } + .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } + %p + Are you sure to discard your changes? + %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes + %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel + %diff-file-editor{":file" => "file", ":load-file" => if_condition } diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml new file mode 100644 index 00000000000..124dfde7b22 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml @@ -0,0 +1,12 @@ +.file-actions + .btn-group + %button.btn{ ":class" => "{ 'active': file.resolveMode == 'interactive' }", + '@click' => "onClickResolveModeButton(file, 'interactive')", + type: 'button' } + Interactive mode + %button.btn{ ':class' => "{ 'active': file.resolveMode == 'edit' }", + '@click' => "onClickResolveModeButton(file, 'edit')", + type: 'button' } + Edit inline + %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} + View file @{{conflictsData.shortCommitSha}} diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml index 19c7da4b5e3..7120b6ff48d 100644 --- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml @@ -3,12 +3,9 @@ .file-title %i.fa.fa-fw{":class" => "file.iconClass"} %strong {{file.filePath}} - .file-actions - %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} - View file @{{conflictsData.shortCommitSha}} - + = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight + .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } %table %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} %template{"v-if" => "!line.isHeader"} @@ -24,5 +21,6 @@ %td.diff-line-num.header{":class" => class_bindings} %td.line_content.header{":class" => class_bindings} %strong {{{line.richText}}} - %button.btn{"@click" => "handleSelected(line.id, line.section)"} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } {{line.buttonTitle}} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && !isParallel" } diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml index 2e6f67c2eaf..18c830ddb17 100644 --- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml @@ -3,12 +3,9 @@ .file-title %i.fa.fa-fw{":class" => "file.iconClass"} %strong {{file.filePath}} - .file-actions - %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} - View file @{{conflictsData.shortCommitSha}} - + = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight + .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } %table %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} %template{"v-for" => "line in section"} @@ -17,7 +14,7 @@ %td.diff-line-num.header{":class" => class_bindings} %td.line_content.header{":class" => class_bindings} %strong {{line.richText}} - %button.btn{"@click" => "handleSelected(line.id, line.section)"} + %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} {{line.buttonTitle}} %template{"v-if" => "!line.isHeader"} @@ -25,3 +22,4 @@ {{line.lineNumber}} %td.line_content.parallel{":class" => class_bindings} {{{line.richText}}} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && isParallel" } diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index 78bd4133ea2..380f8722186 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -1,15 +1,10 @@ -.content-block.oneline-block.files-changed - %strong.resolved-count {{resolvedCount}} - of - %strong.total-count {{conflictsCount}} - conflicts have been resolved - +.content-block .commit-message-container.form-group .max-width-marker %textarea.form-control.js-commit-message{"v-model" => "conflictsData.commitMessage"} {{{conflictsData.commitMessage}}} - %button{type: "button", class: "btn btn-success js-submit-button", ":disabled" => "!readyToCommit", "@click" => "commit()"} + %button{type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } %span {{commitButtonText}} = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml new file mode 100644 index 00000000000..0556341fd64 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -0,0 +1,6 @@ +%template{ id: "diff-file-editor" } + %div + .editor-wrap{ ":class" => "classObject" } + %p.loading-text Loading... + .editor + %textarea{ "@input" => "onInput", cols: '80', rows: '20' } -- cgit v1.2.1 From a3eb39a1068df93d732585272c5eaff380801430 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 16 Sep 2016 19:41:15 -0500 Subject: Replace textarea with Ace editor --- .../components/diff_file_editor.js.es6 | 27 ++++++++++++++++------ app/assets/stylesheets/pages/merge_conflicts.scss | 7 ++++++ .../projects/merge_requests/conflicts.html.haml | 2 ++ .../components/_diff_file_editor.html.haml | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 570d9ff877c..abdf73febb4 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -4,7 +4,7 @@ template: '#diff-file-editor', data() { return { - originalState: '', + originalContent: '', saved: false, loading: false, fileLoaded: false @@ -23,6 +23,8 @@ loadFile(val) { const self = this; + this.resetEditorContent(); + if (!val || this.fileLoaded || this.loading) { return } @@ -31,10 +33,19 @@ $.get(this.file.content_path) .done((file) => { - $(self.$el).find('textarea').val(file.content); - self.originalState = file.content; + let content = self.$el.querySelector('pre'); + let fileContent = document.createTextNode(file.content); + + content.textContent = fileContent.textContent; + + self.originalContent = file.content; self.fileLoaded = true; + self.editor = ace.edit(content); + self.editor.$blockScrolling = Infinity; // Turn off annoying warning + self.editor.on('change', () => { + self.saveDiffResolution(); + }); self.saveDiffResolution(); }) .fail(() => { @@ -50,12 +61,14 @@ this.saved = true; // This probably be better placed in the data provider - this.file.content = this.$el.querySelector('textarea').value; - this.file.resolveEditChanged = this.file.content !== this.originalState; + this.file.content = this.editor.getValue(); + this.file.resolveEditChanged = this.file.content !== this.originalContent; this.file.promptDiscardConfirmation = false; }, - onInput() { - this.saveDiffResolution(); + resetEditorContent() { + if (this.fileLoaded) { + this.editor.setValue(this.originalContent, -1); + } } } }); diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 577a97e8c0e..2a8c693b2a8 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -255,6 +255,13 @@ $colors: ( .editor { border-top: solid 1px yellow; + + pre { + height: 350px; + border: none; + border-radius: 0; + margin-bottom: 0; + } } .loading-text { diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index e3add1f799f..ff641b90b86 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -6,6 +6,8 @@ 'unselected': line.isUnselected }" - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/ace.js') = render "projects/merge_requests/show/mr_title" .merge-request-details.issuable-details diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 0556341fd64..940558e02e0 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -3,4 +3,4 @@ .editor-wrap{ ":class" => "classObject" } %p.loading-text Loading... .editor - %textarea{ "@input" => "onInput", cols: '80', rows: '20' } + %pre{ "style" => "height: 350px" } -- cgit v1.2.1 From 17830296a6143c4d76155449ff4da4377e263657 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Sep 2016 10:10:17 -0500 Subject: Styles for discard alert --- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/merge_conflicts.scss | 16 +++++++++++++--- .../merge_requests/conflicts/_diff_file_editor.html.haml | 7 ++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 4c34ed3ebf7..7690d65de8e 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -56,6 +56,7 @@ $border-gray-light: #dcdcdc; $border-gray-normal: #d7d7d7; $border-gray-dark: #c6cacf; +$border-green-extra-light: #9adb84; $border-green-light: #2faa60; $border-green-normal: #2ca05b; $border-green-dark: #279654; diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 2a8c693b2a8..ea848b2c2c0 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -249,13 +249,11 @@ $colors: ( &.saved { .editor { - border-top: solid 1px green; + border-top: solid 2px $border-green-extra-light; } } .editor { - border-top: solid 1px yellow; - pre { height: 350px; border: none; @@ -268,4 +266,16 @@ $colors: ( display: none; } } + + .discard-changes-alert { + background-color: $background-color; + text-align: right; + padding: $gl-padding-top $gl-padding; + color: $gl-text-color; + + .discard-actions { + display: inline-block; + margin-left: 10px; + } + } } diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml index d0c518e5249..a335470de75 100644 --- a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml @@ -2,8 +2,9 @@ .diff-editor-wrap{ "v-show" => if_condition } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } - %p + .discard-changes-alert Are you sure to discard your changes? - %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes - %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel + .discard-actions + %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes + %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel %diff-file-editor{":file" => "file", ":load-file" => if_condition } -- cgit v1.2.1 From 02bfb0ff1bf53abf44c50d8310ad0856d26860cf Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Sep 2016 10:51:09 -0500 Subject: Style for resolve conflicts form --- app/assets/stylesheets/pages/merge_conflicts.scss | 4 ++++ .../conflicts/_submit_form.html.haml | 27 ++++++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index ea848b2c2c0..c851bd52b1a 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -278,4 +278,8 @@ $colors: ( margin-left: 10px; } } + + .resolve-conflicts-form { + padding-top: $gl-padding; + } } diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index 380f8722186..fbb15c307c4 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -1,10 +1,17 @@ -.content-block - .commit-message-container.form-group - .max-width-marker - %textarea.form-control.js-commit-message{"v-model" => "conflictsData.commitMessage"} - {{{conflictsData.commitMessage}}} - - %button{type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } - %span {{commitButtonText}} - - = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" +.form-horizontal.resolve-conflicts-form + .form-group + %label.col-sm-2.control-label{ "for" => "commit-message" } + Commit message + .col-sm-10 + .commit-message-container + .max-width-marker + %textarea.form-control.js-commit-message#commit-message{ "v-model" => "conflictsData.commitMessage", "rows" => "5" } + {{{conflictsData.commitMessage}}} + .form-group + .col-sm-offset-2.col-sm-10 + .row + .col-xs-6 + %button{ type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } + %span {{commitButtonText}} + .col-xs-6.text-right + = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" -- cgit v1.2.1 From 0402a18367a7a08d3f40f8b63b961a0e1abb345a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Sep 2016 11:04:52 -0500 Subject: Replace loading text with spinner --- app/assets/stylesheets/pages/merge_conflicts.scss | 4 ++-- .../merge_requests/conflicts/components/_diff_file_editor.html.haml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index c851bd52b1a..d447ca53c16 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -242,7 +242,7 @@ $colors: ( display: none; } - .loading-text { + .loading { display: block; } } @@ -262,7 +262,7 @@ $colors: ( } } - .loading-text { + .loading { display: none; } } diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 940558e02e0..9965bdf9028 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -1,6 +1,7 @@ %template{ id: "diff-file-editor" } %div .editor-wrap{ ":class" => "classObject" } - %p.loading-text Loading... + .loading + %i.fa.fa-spinner.fa-spin .editor %pre{ "style" => "height: 350px" } -- cgit v1.2.1 From 12ca16080b86b2b0cee8037800952347c2dcd8c4 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 20 Sep 2016 12:37:53 -0500 Subject: check if files is set before counting --- app/assets/javascripts/merge_conflict_data_provider.js.es6 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index bfed78ff99c..6d1d3f36b33 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -268,6 +268,10 @@ class MergeConflictDataProvider { getConflictsCount() { + if (!this.vueInstance.conflictsData.files) { + return 0; + } + const files = this.vueInstance.conflictsData.files; let count = 0; -- cgit v1.2.1 From 4178ddee18918c5186ba75aaf9b303138fb97b30 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sun, 25 Sep 2016 15:45:58 -0500 Subject: Add tests to check if files are resolved with Edit Inline mode --- spec/features/merge_requests/conflicts_spec.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 1e8aeacea05..d2057aa2fe7 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -36,6 +36,32 @@ feature 'Merge request conflict resolution', js: true, feature: true do retry end end + + context 'when in inline mode' do + it 'resolves files manually' do + within find('.files-wrapper .diff-file.inline-view', text: 'files/ruby/popen.rb') do + click_button 'Edit inline' + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("One morning");'); + end + + within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do + click_button 'Edit inline' + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[1]).setValue("Gregor Samsa woke from troubled dreams");'); + end + + click_button 'Commit conflict resolution' + wait_for_ajax + expect(page).to have_content('All merge conflicts were resolved') + + click_on 'Changes' + wait_for_ajax + + expect(page).to have_content('One morning') + expect(page).to have_content('Gregor Samsa woke from troubled dreams') + end + end end end -- cgit v1.2.1 From e84f959ae47e35eaebdc6c0adaf1e089326601ce Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 26 Sep 2016 15:49:04 +0100 Subject: Fix editor spec --- spec/features/merge_requests/conflicts_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index d2057aa2fe7..721360b207e 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -54,6 +54,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do click_button 'Commit conflict resolution' wait_for_ajax expect(page).to have_content('All merge conflicts were resolved') + merge_request.reload_diff click_on 'Changes' wait_for_ajax -- cgit v1.2.1 From a8ac9089afb664e569b34c61dc6782d20d1019d1 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 28 Sep 2016 05:12:13 -0500 Subject: Refactor JS code - Use a store base object to manage application state. - Add a service to handle ajax requests. - Load code only when needed --- app/assets/javascripts/dispatcher.js | 3 - .../components/diff_file_editor.js.es6 | 5 +- .../merge_conflicts/merge_conflict_service.js.es6 | 30 ++ .../merge_conflicts/merge_conflict_store.js.es6 | 419 +++++++++++++++++++++ .../merge_conflicts/merge_conflicts_bundle.js.es6 | 84 +++++ .../projects/merge_requests/conflicts.html.haml | 3 +- .../conflicts/_commit_stats.html.haml | 6 +- .../conflicts/_submit_form.html.haml | 1 - config/application.rb | 1 + spec/features/merge_requests/conflicts_spec.rb | 4 +- 10 files changed, 545 insertions(+), 11 deletions(-) create mode 100644 app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f3ef13ce20e..2c277766d2d 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -97,9 +97,6 @@ new ZenMode(); new MergedButtons(); break; - case "projects:merge_requests:conflicts": - window.mcui = new MergeConflictResolver() - break; case 'projects:merge_requests:index': shortcut_handler = new ShortcutsNavigation(); Issuable.init(); diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index abdf73febb4..49d30f6e9e8 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -1,5 +1,8 @@ ((global) => { - global.diffFileEditor = Vue.extend({ + + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.diffFileEditor = Vue.extend({ props: ['file', 'loadFile'], template: '#diff-file-editor', data() { diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 new file mode 100644 index 00000000000..da2fb8b1323 --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 @@ -0,0 +1,30 @@ +((global) => { + global.mergeConflicts = global.mergeConflicts || {}; + + class mergeConflictsService { + constructor(options) { + this.conflictsPath = options.conflictsPath; + this.resolveConflictsPath = options.resolveConflictsPath; + } + + fetchConflictsData() { + return $.ajax({ + dataType: 'json', + url: this.conflictsPath + }); + } + + submitResolveConflicts(data) { + return $.ajax({ + url: this.resolveConflictsPath, + data: JSON.stringify(data), + contentType: 'application/json', + dataType: 'json', + method: 'POST' + }); + } + }; + + global.mergeConflicts.mergeConflictsService = mergeConflictsService; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 new file mode 100644 index 00000000000..d35e6d8aed6 --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -0,0 +1,419 @@ +((global) => { + global.mergeConflicts = global.mergeConflicts || {}; + + const diffViewType = $.cookie('diff_view'); + const HEAD_HEADER_TEXT = 'HEAD//our changes'; + const ORIGIN_HEADER_TEXT = 'origin//their changes'; + const HEAD_BUTTON_TITLE = 'Use ours'; + const ORIGIN_BUTTON_TITLE = 'Use theirs'; + const INTERACTIVE_RESOLVE_MODE = 'interactive'; + const EDIT_RESOLVE_MODE = 'edit'; + const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE; + const VIEW_TYPES = { + INLINE: 'inline', + PARALLEL: 'parallel' + }; + + global.mergeConflicts.mergeConflictsStore = { + state: { + isLoading: true, + hasError: false, + isSubmitting: false, + isParallel: diffViewType === VIEW_TYPES.PARALLEL, + diffViewType: diffViewType, + conflictsData: {} + }, + + setConflictsData(data) { + this.decorateFiles(data.files); + this.setInlineLines(data.files); + this.setParallelLines(data.files); + + this.state.conflictsData = { + files: data.files, + commitMessage: data.commit_message, + sourceBranch: data.source_branch, + targetBranch: data.target_branch, + commitMessage: data.commit_message, + shortCommitSha: data.commit_sha.slice(0, 7), + }; + }, + + decorateFiles(files) { + files.forEach((file) => { + file.content = ''; + file.resolutionData = {}; + file.promptDiscardConfirmation = false; + file.resolveMode = DEFAULT_RESOLVE_MODE; + }); + }, + + setInlineLines(files) { + files.forEach((file) => { + file.iconClass = `fa-${file.blob_icon}`; + file.blobPath = file.blob_path; + file.filePath = this.getFilePath(file); + file.inlineLines = []; + + file.sections.forEach((section) => { + let currentLineType = 'new'; + const { conflict, lines, id } = section; + + if (conflict) { + file.inlineLines.push(this.getHeadHeaderLine(id)); + } + + lines.forEach((line) => { + const { type } = line; + + if ((type === 'new' || type === 'old') && currentLineType !== type) { + currentLineType = type; + file.inlineLines.push({ lineType: 'emptyLine', richText: '' }); + } + + this.decorateLineForInlineView(line, id, conflict); + file.inlineLines.push(line); + }) + + if (conflict) { + file.inlineLines.push(this.getOriginHeaderLine(id)); + } + }); + }); + }, + + setParallelLines(files) { + files.forEach((file) => { + file.filePath = this.getFilePath(file); + file.iconClass = `fa-${file.blob_icon}`; + file.blobPath = file.blob_path; + file.parallelLines = []; + const linesObj = { left: [], right: [] }; + + file.sections.forEach((section) => { + const { conflict, lines, id } = section; + + if (conflict) { + linesObj.left.push(this.getOriginHeaderLine(id)); + linesObj.right.push(this.getHeadHeaderLine(id)); + } + + lines.forEach((line) => { + const { type } = line; + + if (conflict) { + if (type === 'old') { + linesObj.left.push(this.getLineForParallelView(line, id, 'conflict')); + } + else if (type === 'new') { + linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true)); + } + } + else { + const lineType = type || 'context'; + + linesObj.left.push (this.getLineForParallelView(line, id, lineType)); + linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); + } + }); + + this.checkLineLengths(linesObj); + }); + + for (let i = 0, len = linesObj.left.length; i < len; i++) { + file.parallelLines.push([ + linesObj.right[i], + linesObj.left[i] + ]); + } + + return file; + }); + }, + + setLoadingState(state) { + this.state.isLoading = state; + }, + + setErrorState(state) { + this.state.hasError = state; + }, + + setFailedRequest(message) { + console.log('setFailedRequest'); + this.state.hasError = true; + this.state.conflictsData.errorMessage = message; + }, + + getConflictsCount() { + if (!this.state.conflictsData.files) { + return 0; + } + + const files = this.state.conflictsData.files; + let count = 0; + + files.forEach((file) => { + file.sections.forEach((section) => { + if (section.conflict) { + count++; + } + }); + }); + + return count; + }, + + getConflictsCountText() { + const count = this.getConflictsCount(); + const text = count ? 'conflicts' : 'conflict'; + + return `${count} ${text}`; + }, + + setViewType(viewType) { + this.state.diffView = viewType; + this.state.isParallel = viewType === VIEW_TYPES.PARALLEL; + + $.cookie('diff_view', viewType, { + path: gon.relative_url_root || '/' + }); + }, + + getHeadHeaderLine(id) { + return { + id: id, + richText: HEAD_HEADER_TEXT, + buttonTitle: HEAD_BUTTON_TITLE, + type: 'new', + section: 'head', + isHeader: true, + isHead: true, + isSelected: false, + isUnselected: false + } + }, + + decorateLineForInlineView(line, id, conflict) { + const { type } = line; + line.id = id; + line.hasConflict = conflict; + line.isHead = type === 'new'; + line.isOrigin = type === 'old'; + line.hasMatch = type === 'match'; + line.richText = line.rich_text; + line.isSelected = false; + line.isUnselected = false; + }, + + getLineForParallelView(line, id, lineType, isHead) { + const { old_line, new_line, rich_text } = line; + const hasConflict = lineType === 'conflict'; + + return { + id, + lineType, + hasConflict, + isHead: hasConflict && isHead, + isOrigin: hasConflict && !isHead, + hasMatch: lineType === 'match', + lineNumber: isHead ? new_line : old_line, + section: isHead ? 'head' : 'origin', + richText: rich_text, + isSelected: false, + isUnselected: false + } + }, + + getOriginHeaderLine(id) { + return { + id: id, + richText: ORIGIN_HEADER_TEXT, + buttonTitle: ORIGIN_BUTTON_TITLE, + type: 'old', + section: 'origin', + isHeader: true, + isOrigin: true, + isSelected: false, + isUnselected: false + } + }, + + getFilePath(file) { + const { old_path, new_path } = file; + return old_path === new_path ? new_path : `${old_path} → ${new_path}`; + }, + + checkLineLengths(linesObj) { + let { left, right } = linesObj; + + if (left.length !== right.length) { + if (left.length > right.length) { + const diff = left.length - right.length; + for (let i = 0; i < diff; i++) { + right.push({ lineType: 'emptyLine', richText: '' }); + } + } + else { + const diff = right.length - left.length; + for (let i = 0; i < diff; i++) { + left.push({ lineType: 'emptyLine', richText: '' }); + } + } + } + }, + + setPromptConfirmationState(file, state) { + file.promptDiscardConfirmation = state; + }, + + setFileResolveMode(file, mode) { + // Restore Interactive mode when switching to Edit mode + if (mode === EDIT_RESOLVE_MODE) { + file.resolutionData = {}; + + this.restoreFileLinesState(file); + } + + file.resolveMode = mode; + }, + + restoreFileLinesState(file) { + file.inlineLines.forEach((line) => { + if (line.hasConflict || line.isHeader) { + line.isSelected = false; + line.isUnselected = false; + } + }); + + file.parallelLines.forEach((lines) => { + const left = lines[0]; + const right = lines[1]; + const isLeftMatch = left.hasConflict || left.isHeader; + const isRightMatch = right.hasConflict || right.isHeader; + + if (isLeftMatch || isRightMatch) { + left.isSelected = false; + left.isUnselected = false; + right.isSelected = false; + right.isUnselected = false; + } + }); + }, + + isReadyToCommit() { + const files = this.state.conflictsData.files; + const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length; + let unresolved = 0; + + for (let i = 0, l = files.length; i < l; i++) { + let file = files[i]; + + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + let numberConflicts = 0; + let resolvedConflicts = Object.keys(file.resolutionData).length + + for (let j = 0, k = file.sections.length; j < k; j++) { + if (file.sections[j].conflict) { + numberConflicts++; + } + } + + if (resolvedConflicts !== numberConflicts) { + unresolved++; + } + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + // Unlikely to happen since switching to Edit mode saves content automatically. + // Checking anyway in case the save strategy changes in the future + if (!file.content) { + unresolved++; + continue; + } + } + } + + return !this.state.isSubmitting && hasCommitMessage && !unresolved; + }, + + getCommitButtonText() { + const initial = 'Commit conflict resolution'; + const inProgress = 'Committing...'; + + return this.state ? this.state.isSubmitting ? inProgress : initial : initial; + }, + + getCommitData() { + let commitData = {}; + + commitData = { + commit_message: this.state.conflictsData.commitMessage, + files: [] + }; + + this.state.conflictsData.files.forEach((file) => { + let addFile; + + addFile = { + old_path: file.old_path, + new_path: file.new_path + }; + + // Submit only one data for type of editing + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + addFile.sections = file.resolutionData; + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + addFile.content = file.content; + } + + commitData.files.push(addFile); + }); + + return commitData; + }, + + handleSelected(file, sectionId, selection) { + Vue.set(file.resolutionData, sectionId, selection); + + this.state.conflictsData.files.forEach((file) => { + file.inlineLines.forEach((line) => { + if (line.id === sectionId && (line.hasConflict || line.isHeader)) { + this.markLine(line, selection); + } + }); + + file.parallelLines.forEach((lines) => { + const left = lines[0]; + const right = lines[1]; + const hasSameId = right.id === sectionId || left.id === sectionId; + const isLeftMatch = left.hasConflict || left.isHeader; + const isRightMatch = right.hasConflict || right.isHeader; + + if (hasSameId && (isLeftMatch || isRightMatch)) { + this.markLine(left, selection); + this.markLine(right, selection); + } + }) + }); + }, + + markLine(line, selection) { + if (selection === 'head' && line.isHead) { + line.isSelected = true; + line.isUnselected = false; + } + else if (selection === 'origin' && line.isOrigin) { + line.isSelected = true; + line.isUnselected = false; + } + else { + line.isSelected = false; + line.isUnselected = true; + } + }, + + setSubmitState(state) { + this.state.isSubmitting = state; + } + }; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 new file mode 100644 index 00000000000..b5123e22f7a --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -0,0 +1,84 @@ +//= require vue +//= require ./merge_conflict_store +//= require ./merge_conflict_service +//= require ./components/diff_file_editor + +$(() => { + const INTERACTIVE_RESOLVE_MODE = 'interactive'; + const $conflicts = $(document.getElementById('conflicts')); + const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore; + const mergeConflictsService = new gl.mergeConflicts.mergeConflictsService({ + conflictsPath: $conflicts.data('conflictsPath'), + resolveConflictsPath: $conflicts.data('resolveConflictsPath') + }); + + gl.MergeConflictsResolverApp = new Vue({ + el: '#conflicts', + data: mergeConflictsStore.state, + components: { + 'diff-file-editor': gl.mergeConflicts.diffFileEditor + }, + computed: { + conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, + readyToCommit() { return mergeConflictsStore.isReadyToCommit() }, + commitButtonText() { return mergeConflictsStore.getCommitButtonText() } + }, + created() { + mergeConflictsService + .fetchConflictsData() + .done((data) => { + if (data.type === 'error') { + mergeConflictsStore.setFailedRequest(data.message); + } else { + mergeConflictsStore.setConflictsData(data); + } + }) + .error(() => { + mergeConflictsStore.setFailedRequest(); + }) + .always(() => { + mergeConflictsStore.setLoadingState(false); + + this.$nextTick(() => { + $conflicts.find('.js-syntax-highlight').syntaxHighlight(); + }); + }); + }, + methods: { + handleSelected(file, sectionId, selection) { + mergeConflictsStore.handleSelected(file, sectionId, selection); + }, + handleViewTypeChange(viewType) { + mergeConflictsStore.setViewType(viewType); + }, + onClickResolveModeButton(file, mode) { + if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) { + mergeConflictsStore.setPromptConfirmationState(file, true); + return; + } + + mergeConflictsStore.setFileResolveMode(file, mode); + }, + acceptDiscardConfirmation(file) { + mergeConflictsStore.setPromptConfirmationState(file, false); + mergeConflictsStore.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE); + }, + cancelDiscardConfirmation(file) { + mergeConflictsStore.setPromptConfirmationState(file, false); + }, + commit() { + mergeConflictsStore.setSubmitState(true); + + mergeConflictsService + .submitResolveConflicts(mergeConflictsStore.getCommitData()) + .done((data) => { + window.location.href = data.redirect_to; + }) + .error(() => { + mergeConflictsStore.setSubmitState(false); + new Flash('Failed to save merge conflicts resolutions. Please try again!'); + }); + } + } + }) +}); diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index ff641b90b86..997f40c0588 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -7,6 +7,7 @@ - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" - content_for :page_specific_javascripts do + = page_specific_javascript_tag('merge_conflicts/merge_conflicts_bundle.js') = page_specific_javascript_tag('lib/ace.js') = render "projects/merge_requests/show/mr_title" @@ -26,8 +27,8 @@ = render partial: "projects/merge_requests/conflicts/commit_stats" .files-wrapper{"v-if" => "!isLoading && !hasError"} - = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings } + = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/submit_form" -# Components diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index 457c467fba9..a3831d5a34e 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -13,8 +13,8 @@ .js-toggle-container .commit-stat-summary Showing - %strong.cred {{conflictsCount}} {{conflictsData.conflictsText}} + %strong.cred {{conflictsCountText}} between - %strong {{conflictsData.source_branch}} + %strong {{conflictsData.sourceBranch}} and - %strong {{conflictsData.target_branch}} + %strong {{conflictsData.targetBranch}} diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index fbb15c307c4..6ffaa9ad4d2 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -6,7 +6,6 @@ .commit-message-container .max-width-marker %textarea.form-control.js-commit-message#commit-message{ "v-model" => "conflictsData.commitMessage", "rows" => "5" } - {{{conflictsData.commitMessage}}} .form-group .col-sm-offset-2.col-sm-10 .row diff --git a/config/application.rb b/config/application.rb index 962ffe0708d..8a9c539cb43 100644 --- a/config/application.rb +++ b/config/application.rb @@ -89,6 +89,7 @@ module Gitlab config.assets.precompile << "profile/profile_bundle.js" config.assets.precompile << "diff_notes/diff_notes_bundle.js" config.assets.precompile << "boards/boards_bundle.js" + config.assets.precompile << "merge_conflicts/merge_conflicts_bundle.js" config.assets.precompile << "boards/test_utils/simulate_drag.js" config.assets.precompile << "blob_edit/blob_edit_bundle.js" config.assets.precompile << "snippet/snippet_bundle.js" diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 721360b207e..f2ff000486b 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -42,13 +42,13 @@ feature 'Merge request conflict resolution', js: true, feature: true do within find('.files-wrapper .diff-file.inline-view', text: 'files/ruby/popen.rb') do click_button 'Edit inline' wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("One morning");'); + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("One morning");') end within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do click_button 'Edit inline' wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[1]).setValue("Gregor Samsa woke from troubled dreams");'); + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') end click_button 'Commit conflict resolution' -- cgit v1.2.1 From 197ac5ebbfd8ca7fbcb79776fd25ca0e213376c4 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 29 Sep 2016 20:26:01 -0500 Subject: Ability to resolve conflicts for files with `text-editor` as conflict type --- .../components/diff_file_editor.js.es6 | 43 ++-- .../merge_conflicts/merge_conflict_store.js.es6 | 240 +++++++++++---------- .../conflicts/_diff_file_editor.html.haml | 6 +- .../conflicts/_file_actions.html.haml | 2 +- .../conflicts/_inline_view.html.haml | 41 ++-- .../conflicts/_parallel_view.html.haml | 2 +- spec/features/merge_requests/conflicts_spec.rb | 38 +++- 7 files changed, 213 insertions(+), 159 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 49d30f6e9e8..46aad9fe33c 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -7,10 +7,10 @@ template: '#diff-file-editor', data() { return { - originalContent: '', saved: false, loading: false, - fileLoaded: false + fileLoaded: false, + originalContent: '', } }, computed: { @@ -23,43 +23,48 @@ } }, watch: { - loadFile(val) { - const self = this; - + ['file.showEditor'](val) { this.resetEditorContent(); if (!val || this.fileLoaded || this.loading) { - return + return; } + this.loadEditor(); + } + }, + ready() { + if (this.file.loadEditor) { + this.loadEditor(); + } + }, + methods: { + loadEditor() { this.loading = true; $.get(this.file.content_path) .done((file) => { - - let content = self.$el.querySelector('pre'); + let content = this.$el.querySelector('pre'); let fileContent = document.createTextNode(file.content); content.textContent = fileContent.textContent; - self.originalContent = file.content; - self.fileLoaded = true; - self.editor = ace.edit(content); - self.editor.$blockScrolling = Infinity; // Turn off annoying warning - self.editor.on('change', () => { - self.saveDiffResolution(); + this.originalContent = file.content; + this.fileLoaded = true; + this.editor = ace.edit(content); + this.editor.$blockScrolling = Infinity; // Turn off annoying warning + this.editor.on('change', () => { + this.saveDiffResolution(); }); - self.saveDiffResolution(); + this.saveDiffResolution(); }) .fail(() => { console.log('error'); }) .always(() => { - self.loading = false; + this.loading = false; }); - } - }, - methods: { + }, saveDiffResolution() { this.saved = true; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index d35e6d8aed6..e1f862797f5 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -13,6 +13,10 @@ INLINE: 'inline', PARALLEL: 'parallel' }; + const CONFLICT_TYPES = { + TEXT: 'text', + TEXT_EDITOR: 'text-editor' + }; global.mergeConflicts.mergeConflictsStore = { state: { @@ -26,8 +30,6 @@ setConflictsData(data) { this.decorateFiles(data.files); - this.setInlineLines(data.files); - this.setParallelLines(data.files); this.state.conflictsData = { files: data.files, @@ -45,90 +47,90 @@ file.resolutionData = {}; file.promptDiscardConfirmation = false; file.resolveMode = DEFAULT_RESOLVE_MODE; - }); - }, - - setInlineLines(files) { - files.forEach((file) => { + file.filePath = this.getFilePath(file); file.iconClass = `fa-${file.blob_icon}`; file.blobPath = file.blob_path; - file.filePath = this.getFilePath(file); - file.inlineLines = []; - file.sections.forEach((section) => { - let currentLineType = 'new'; - const { conflict, lines, id } = section; + if (file.type === CONFLICT_TYPES.TEXT) { + file.showEditor = false; + file.loadEditor = false; - if (conflict) { - file.inlineLines.push(this.getHeadHeaderLine(id)); - } + this.setInlineLine(file); + this.setParallelLine(file); + } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) { + file.showEditor = true; + file.loadEditor = true; + } + }); + }, - lines.forEach((line) => { - const { type } = line; + setInlineLine(file) { + file.inlineLines = []; - if ((type === 'new' || type === 'old') && currentLineType !== type) { - currentLineType = type; - file.inlineLines.push({ lineType: 'emptyLine', richText: '' }); - } + file.sections.forEach((section) => { + let currentLineType = 'new'; + const { conflict, lines, id } = section; + + if (conflict) { + file.inlineLines.push(this.getHeadHeaderLine(id)); + } - this.decorateLineForInlineView(line, id, conflict); - file.inlineLines.push(line); - }) + lines.forEach((line) => { + const { type } = line; - if (conflict) { - file.inlineLines.push(this.getOriginHeaderLine(id)); + if ((type === 'new' || type === 'old') && currentLineType !== type) { + currentLineType = type; + file.inlineLines.push({ lineType: 'emptyLine', richText: '' }); } - }); + + this.decorateLineForInlineView(line, id, conflict); + file.inlineLines.push(line); + }) + + if (conflict) { + file.inlineLines.push(this.getOriginHeaderLine(id)); + } }); }, - setParallelLines(files) { - files.forEach((file) => { - file.filePath = this.getFilePath(file); - file.iconClass = `fa-${file.blob_icon}`; - file.blobPath = file.blob_path; - file.parallelLines = []; - const linesObj = { left: [], right: [] }; - - file.sections.forEach((section) => { - const { conflict, lines, id } = section; + setParallelLine(file) { + file.parallelLines = []; + const linesObj = { left: [], right: [] }; - if (conflict) { - linesObj.left.push(this.getOriginHeaderLine(id)); - linesObj.right.push(this.getHeadHeaderLine(id)); - } + file.sections.forEach((section) => { + const { conflict, lines, id } = section; - lines.forEach((line) => { - const { type } = line; + if (conflict) { + linesObj.left.push(this.getOriginHeaderLine(id)); + linesObj.right.push(this.getHeadHeaderLine(id)); + } - if (conflict) { - if (type === 'old') { - linesObj.left.push(this.getLineForParallelView(line, id, 'conflict')); - } - else if (type === 'new') { - linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true)); - } - } - else { - const lineType = type || 'context'; + lines.forEach((line) => { + const { type } = line; - linesObj.left.push (this.getLineForParallelView(line, id, lineType)); - linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); + if (conflict) { + if (type === 'old') { + linesObj.left.push(this.getLineForParallelView(line, id, 'conflict')); + } else if (type === 'new') { + linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true)); } - }); + } else { + const lineType = type || 'context'; - this.checkLineLengths(linesObj); + linesObj.left.push (this.getLineForParallelView(line, id, lineType)); + linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); + } }); - for (let i = 0, len = linesObj.left.length; i < len; i++) { - file.parallelLines.push([ - linesObj.right[i], - linesObj.left[i] - ]); - } - - return file; + this.checkLineLengths(linesObj); }); + + for (let i = 0, len = linesObj.left.length; i < len; i++) { + file.parallelLines.push([ + linesObj.right[i], + linesObj.left[i] + ]); + } }, setLoadingState(state) { @@ -140,13 +142,12 @@ }, setFailedRequest(message) { - console.log('setFailedRequest'); this.state.hasError = true; this.state.conflictsData.errorMessage = message; }, getConflictsCount() { - if (!this.state.conflictsData.files) { + if (!this.state.conflictsData.files.length) { return 0; } @@ -154,11 +155,15 @@ let count = 0; files.forEach((file) => { - file.sections.forEach((section) => { - if (section.conflict) { - count++; - } - }); + if (file.type === CONFLICT_TYPES.TEXT) { + file.sections.forEach((section) => { + if (section.conflict) { + count++; + } + }); + } else { + count++; + } }); return count; @@ -172,7 +177,7 @@ }, setViewType(viewType) { - this.state.diffView = viewType; + this.state.diffView = viewType; this.state.isParallel = viewType === VIEW_TYPES.PARALLEL; $.cookie('diff_view', viewType, { @@ -253,8 +258,7 @@ for (let i = 0; i < diff; i++) { right.push({ lineType: 'emptyLine', richText: '' }); } - } - else { + } else { const diff = right.length - left.length; for (let i = 0; i < diff; i++) { left.push({ lineType: 'emptyLine', richText: '' }); @@ -268,8 +272,12 @@ }, setFileResolveMode(file, mode) { - // Restore Interactive mode when switching to Edit mode - if (mode === EDIT_RESOLVE_MODE) { + if (mode === INTERACTIVE_RESOLVE_MODE) { + file.showEditor = false; + } else if (mode === EDIT_RESOLVE_MODE) { + // Restore Interactive mode when switching to Edit mode + file.showEditor = true; + file.loadEditor = true; file.resolutionData = {}; this.restoreFileLinesState(file); @@ -287,9 +295,9 @@ }); file.parallelLines.forEach((lines) => { - const left = lines[0]; - const right = lines[1]; - const isLeftMatch = left.hasConflict || left.isHeader; + const left = lines[0]; + const right = lines[1]; + const isLeftMatch = left.hasConflict || left.isHeader; const isRightMatch = right.hasConflict || right.isHeader; if (isLeftMatch || isRightMatch) { @@ -313,14 +321,17 @@ let numberConflicts = 0; let resolvedConflicts = Object.keys(file.resolutionData).length - for (let j = 0, k = file.sections.length; j < k; j++) { - if (file.sections[j].conflict) { - numberConflicts++; + // We only check if + if (file.type === CONFLICT_TYPES.TEXT) { + for (let j = 0, k = file.sections.length; j < k; j++) { + if (file.sections[j].conflict) { + numberConflicts++; + } } - } - if (resolvedConflicts !== numberConflicts) { - unresolved++; + if (resolvedConflicts !== numberConflicts) { + unresolved++; + } } } else if (file.resolveMode === EDIT_RESOLVE_MODE) { // Unlikely to happen since switching to Edit mode saves content automatically. @@ -358,10 +369,15 @@ new_path: file.new_path }; - // Submit only one data for type of editing - if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { - addFile.sections = file.resolutionData; - } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + if (file.type === CONFLICT_TYPES.TEXT) { + + // Submit only one data for type of editing + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + addFile.sections = file.resolutionData; + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + addFile.content = file.content; + } + } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) { addFile.content = file.content; } @@ -374,39 +390,35 @@ handleSelected(file, sectionId, selection) { Vue.set(file.resolutionData, sectionId, selection); - this.state.conflictsData.files.forEach((file) => { - file.inlineLines.forEach((line) => { - if (line.id === sectionId && (line.hasConflict || line.isHeader)) { - this.markLine(line, selection); - } - }); + file.inlineLines.forEach((line) => { + if (line.id === sectionId && (line.hasConflict || line.isHeader)) { + this.markLine(line, selection); + } + }); - file.parallelLines.forEach((lines) => { - const left = lines[0]; - const right = lines[1]; - const hasSameId = right.id === sectionId || left.id === sectionId; - const isLeftMatch = left.hasConflict || left.isHeader; - const isRightMatch = right.hasConflict || right.isHeader; + file.parallelLines.forEach((lines) => { + const left = lines[0]; + const right = lines[1]; + const hasSameId = right.id === sectionId || left.id === sectionId; + const isLeftMatch = left.hasConflict || left.isHeader; + const isRightMatch = right.hasConflict || right.isHeader; - if (hasSameId && (isLeftMatch || isRightMatch)) { - this.markLine(left, selection); - this.markLine(right, selection); - } - }) + if (hasSameId && (isLeftMatch || isRightMatch)) { + this.markLine(left, selection); + this.markLine(right, selection); + } }); }, markLine(line, selection) { if (selection === 'head' && line.isHead) { - line.isSelected = true; + line.isSelected = true; line.isUnselected = false; - } - else if (selection === 'origin' && line.isOrigin) { - line.isSelected = true; + } else if (selection === 'origin' && line.isOrigin) { + line.isSelected = true; line.isUnselected = false; - } - else { - line.isSelected = false; + } else { + line.isSelected = false; line.isUnselected = true; } }, diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml index a335470de75..497db7e3f9b 100644 --- a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml @@ -1,10 +1,8 @@ -- if_condition = local_assigns.fetch(:if_condition, '') - -.diff-editor-wrap{ "v-show" => if_condition } +.diff-editor-wrap{ "v-show" => "file.showEditor" } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } .discard-changes-alert Are you sure to discard your changes? .discard-actions %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel - %diff-file-editor{":file" => "file", ":load-file" => if_condition } + %diff-file-editor{":file" => "file"} diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml index 124dfde7b22..05af57acf03 100644 --- a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml +++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml @@ -1,5 +1,5 @@ .file-actions - .btn-group + .btn-group{"v-if" => "file.type === 'text'"} %button.btn{ ":class" => "{ 'active': file.resolveMode == 'interactive' }", '@click' => "onClickResolveModeButton(file, 'interactive')", type: 'button' } diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml index 7120b6ff48d..60ac21d26c3 100644 --- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml @@ -4,23 +4,26 @@ %i.fa.fa-fw{":class" => "file.iconClass"} %strong {{file.filePath}} = render partial: 'projects/merge_requests/conflicts/file_actions' - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } - %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.new_line{":class" => class_bindings} - %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => class_bindings} - %a {{line.old_line}} - %td.line_content{":class" => class_bindings} - {{{line.richText}}} + %template{"v-if" => "file.type === 'text'"} + .diff-content.diff-wrap-lines + .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } + %table + %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} + %template{"v-if" => "!line.isHeader"} + %td.diff-line-num.new_line{":class" => class_bindings} + %a {{line.new_line}} + %td.diff-line-num.old_line{":class" => class_bindings} + %a {{line.old_line}} + %td.line_content{":class" => class_bindings} + {{{line.richText}}} - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{{line.richText}}} - %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } - {{line.buttonTitle}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && !isParallel" } + %template{"v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => class_bindings} + %td.diff-line-num.header{":class" => class_bindings} + %td.line_content.header{":class" => class_bindings} + %strong {{{line.richText}}} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } + {{line.buttonTitle}} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor' + %template{"v-else" => true} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor' diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml index 18c830ddb17..7ed1485fc01 100644 --- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml @@ -22,4 +22,4 @@ {{line.lineNumber}} %td.line_content.parallel{":class" => class_bindings} {{{line.richText}}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && isParallel" } + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index f2ff000486b..0e5507a0210 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -37,7 +37,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do end end - context 'when in inline mode' do + context 'when in inline mode' do it 'resolves files manually' do within find('.files-wrapper .diff-file.inline-view', text: 'files/ruby/popen.rb') do click_button 'Edit inline' @@ -66,6 +66,42 @@ feature 'Merge request conflict resolution', js: true, feature: true do end end + context 'when a merge request can be resolved in the UI' do + let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') } + + before do + project.team << [user, :developer] + login_as(user) + + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + context 'a conflict contain markers' do + before { click_link('conflicts', href: /\/conflicts\Z/) } + + it 'resolves files manually' do + within find('.files-wrapper .diff-file.inline-view', text: 'files/markdown/ruby-style-guide.md') do + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') + end + + click_button 'Commit conflict resolution' + wait_for_ajax + + expect(page).to have_content('All merge conflicts were resolved') + + merge_request.reload_diff + + click_on 'Changes' + wait_for_ajax + find('.nothing-here-block', visible: true).click + wait_for_ajax + + expect(page).to have_content('Gregor Samsa woke from troubled dreams') + end + end + end + UNRESOLVABLE_CONFLICTS = { 'conflict-too-large' => 'when the conflicts contain a large file', 'conflict-binary-file' => 'when the conflicts contain a binary file', -- cgit v1.2.1 From cebad0fb60c90ff64cf16b022d262381aff5bf4f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 30 Sep 2016 16:23:38 -0500 Subject: Do not show Diff view switcher if all files are can be only resolved with an editor --- .../javascripts/merge_conflicts/merge_conflict_store.js.es6 | 12 ++++++++++++ .../merge_conflicts/merge_conflicts_bundle.js.es6 | 3 ++- .../merge_requests/conflicts/_commit_stats.html.haml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index e1f862797f5..e31cac49d44 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -425,6 +425,18 @@ setSubmitState(state) { this.state.isSubmitting = state; + }, + + fileTextTypePresent() { + let found = false; + + this.state.conflictsData.files.find((f) => { + if (f.type === CONFLICT_TYPES.TEXT) { + return found = true; + } + }); + + return found; } }; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index b5123e22f7a..449b4d2209a 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -21,7 +21,8 @@ $(() => { computed: { conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, readyToCommit() { return mergeConflictsStore.isReadyToCommit() }, - commitButtonText() { return mergeConflictsStore.getCommitButtonText() } + commitButtonText() { return mergeConflictsStore.getCommitButtonText() }, + showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent() } }, created() { mergeConflictsService diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index a3831d5a34e..7222b8ae4d3 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -1,5 +1,5 @@ .content-block.oneline-block.files-changed{"v-if" => "!isLoading && !hasError"} - .inline-parallel-buttons + .inline-parallel-buttons{"v-if" => "showDiffViewTypeSwitcher"} .btn-group %a.btn{ | ":class" => "{'active': !isParallel}", | -- cgit v1.2.1 From f947972dede2f8d68215555e4b4fd6e90a6bc437 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 30 Sep 2016 17:42:36 -0500 Subject: Improve diff view switching and components --- .../projects/merge_requests/conflicts.html.haml | 17 +++++++++++-- .../conflicts/_inline_view.html.haml | 29 ---------------------- .../conflicts/_parallel_view.html.haml | 25 ------------------- .../_resolve_mode_interactive_inline.html.haml | 17 +++++++++++++ .../_resolve_mode_interactive_parallel.html.haml | 17 +++++++++++++ spec/features/merge_requests/conflicts_spec.rb | 10 ++++---- 6 files changed, 54 insertions(+), 61 deletions(-) delete mode 100644 app/views/projects/merge_requests/conflicts/_inline_view.html.haml delete mode 100644 app/views/projects/merge_requests/conflicts/_parallel_view.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index 997f40c0588..5ff7befe0ca 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -27,9 +27,22 @@ = render partial: "projects/merge_requests/conflicts/commit_stats" .files-wrapper{"v-if" => "!isLoading && !hasError"} - = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings } - = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } + .files + .diff-file.file-holder.conflict{"v-for" => "file in conflictsData.files"} + .file-title + %i.fa.fa-fw{":class" => "file.iconClass"} + %strong {{file.filePath}} + = render partial: 'projects/merge_requests/conflicts/file_actions' + .diff-content.diff-wrap-lines + %div{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_inline", locals: { class_bindings: class_bindings } + %div{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_parallel", locals: { class_bindings: class_bindings } + %div{"v-show" => " file.resolveMode === 'edit' || file.type === 'text-editor'"} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } + = render partial: "projects/merge_requests/conflicts/submit_form" -# Components = render partial: 'projects/merge_requests/conflicts/components/diff_file_editor' + \ No newline at end of file diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml deleted file mode 100644 index 60ac21d26c3..00000000000 --- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml +++ /dev/null @@ -1,29 +0,0 @@ -.files{"v-show" => "!isParallel"} - .diff-file.file-holder.conflict.inline-view{"v-for" => "file in conflictsData.files"} - .file-title - %i.fa.fa-fw{":class" => "file.iconClass"} - %strong {{file.filePath}} - = render partial: 'projects/merge_requests/conflicts/file_actions' - %template{"v-if" => "file.type === 'text'"} - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } - %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.new_line{":class" => class_bindings} - %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => class_bindings} - %a {{line.old_line}} - %td.line_content{":class" => class_bindings} - {{{line.richText}}} - - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{{line.richText}}} - %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } - {{line.buttonTitle}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor' - %template{"v-else" => true} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor' diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml deleted file mode 100644 index 7ed1485fc01..00000000000 --- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml +++ /dev/null @@ -1,25 +0,0 @@ -.files{"v-show" => "isParallel"} - .diff-file.file-holder.conflict.parallel-view{"v-for" => "file in conflictsData.files"} - .file-title - %i.fa.fa-fw{":class" => "file.iconClass"} - %strong {{file.filePath}} - = render partial: 'projects/merge_requests/conflicts/file_actions' - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } - %table - %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} - %template{"v-for" => "line in section"} - - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{line.richText}} - %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} - {{line.buttonTitle}} - - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.old_line{":class" => class_bindings} - {{line.lineNumber}} - %td.line_content.parallel{":class" => class_bindings} - {{{line.richText}}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml new file mode 100644 index 00000000000..b1d7a90eb74 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml @@ -0,0 +1,17 @@ +.diff-wrap-lines.code.file-content.js-syntax-highlight + %table + %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} + %template{"v-if" => "!line.isHeader"} + %td.diff-line-num.new_line{":class" => class_bindings} + %a {{line.new_line}} + %td.diff-line-num.old_line{":class" => class_bindings} + %a {{line.old_line}} + %td.line_content{":class" => class_bindings} + {{{line.richText}}} + %template{"v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => class_bindings} + %td.diff-line-num.header{":class" => class_bindings} + %td.line_content.header{":class" => class_bindings} + %strong {{{line.richText}}} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } + {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml new file mode 100644 index 00000000000..f5af347f59b --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml @@ -0,0 +1,17 @@ +.diff-wrap-lines.code.file-content.js-syntax-highlight + %table + %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} + %template{"v-for" => "line in section"} + + %template{"v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => class_bindings} + %td.line_content.header{":class" => class_bindings} + %strong {{line.richText}} + %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} + {{line.buttonTitle}} + + %template{"v-if" => "!line.isHeader"} + %td.diff-line-num.old_line{":class" => class_bindings} + {{line.lineNumber}} + %td.line_content.parallel{":class" => class_bindings} + {{{line.richText}}} diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 0e5507a0210..4cecc22aa6c 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -39,16 +39,16 @@ feature 'Merge request conflict resolution', js: true, feature: true do context 'when in inline mode' do it 'resolves files manually' do - within find('.files-wrapper .diff-file.inline-view', text: 'files/ruby/popen.rb') do + within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do click_button 'Edit inline' wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("One morning");') + execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");') end within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do click_button 'Edit inline' wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') + execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') end click_button 'Commit conflict resolution' @@ -80,9 +80,9 @@ feature 'Merge request conflict resolution', js: true, feature: true do before { click_link('conflicts', href: /\/conflicts\Z/) } it 'resolves files manually' do - within find('.files-wrapper .diff-file.inline-view', text: 'files/markdown/ruby-style-guide.md') do + within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') + execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') end click_button 'Commit conflict resolution' -- cgit v1.2.1 From 4f76ff0ecd0d3b278f9722042b66d1767078ddd0 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 4 Oct 2016 03:24:44 -0500 Subject: Use .some instead of .find for phantomjs compatibility --- .../javascripts/merge_conflicts/merge_conflict_store.js.es6 | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index e31cac49d44..bdd6753f66a 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -428,15 +428,7 @@ }, fileTextTypePresent() { - let found = false; - - this.state.conflictsData.files.find((f) => { - if (f.type === CONFLICT_TYPES.TEXT) { - return found = true; - } - }); - - return found; + return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT); } }; -- cgit v1.2.1 From c4142cf9c0c0b217034c60a0a973d2e96b17a428 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 07:57:22 -0500 Subject: Improve components for PhantomJs compatibility --- .../components/diff_file_editor.js.es6 | 14 ++++++++++--- .../components/inline_conflict_lines.js.es6 | 12 +++++++++++ .../components/parallel_conflict_line.js.es6 | 14 +++++++++++++ .../components/parallel_conflict_lines.js.es6 | 15 ++++++++++++++ .../merge_conflicts/merge_conflict_store.js.es6 | 6 +++--- .../merge_conflicts/merge_conflicts_bundle.js.es6 | 12 +++++++---- .../mixins/line_conflict_actions.js.es6 | 12 +++++++++++ .../mixins/line_conflict_utils.js.es6 | 18 ++++++++++++++++ .../projects/merge_requests/conflicts.html.haml | 24 ++++++++-------------- .../conflicts/_commit_stats.html.haml | 8 ++------ .../conflicts/_diff_file_editor.html.haml | 8 -------- .../_resolve_mode_interactive_inline.html.haml | 17 --------------- .../_resolve_mode_interactive_parallel.html.haml | 17 --------------- .../components/_diff_file_editor.html.haml | 10 +++++++-- .../components/_inline_conflict_lines.html.haml | 15 ++++++++++++++ .../components/_parallel_conflict_line.html.haml | 10 +++++++++ .../components/_parallel_conflict_lines.html.haml | 4 ++++ 17 files changed, 140 insertions(+), 76 deletions(-) create mode 100644 app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 delete mode 100644 app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml delete mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml delete mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 46aad9fe33c..417095a8db7 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -3,8 +3,11 @@ global.mergeConflicts = global.mergeConflicts || {}; global.mergeConflicts.diffFileEditor = Vue.extend({ - props: ['file', 'loadFile'], - template: '#diff-file-editor', + props: { + file: Object, + onCancelDiscardConfirmation: Function, + onAcceptDiscardConfirmation: Function + }, data() { return { saved: false, @@ -16,7 +19,6 @@ computed: { classObject() { return { - 'load-file': this.loadFile, 'saved': this.saved, 'is-loading': this.loading }; @@ -77,6 +79,12 @@ if (this.fileLoaded) { this.editor.setValue(this.originalContent, -1); } + }, + cancelDiscardConfirmation(file) { + this.onCancelDiscardConfirmation(file); + }, + acceptDiscardConfirmation(file) { + this.onAcceptDiscardConfirmation(file); } } }); diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 new file mode 100644 index 00000000000..b4be1c8988d --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 @@ -0,0 +1,12 @@ +((global) => { + + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.inlineConflictLines = Vue.extend({ + props: { + file: Object + }, + mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions], + }); + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 new file mode 100644 index 00000000000..8b0a8ab2073 --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 @@ -0,0 +1,14 @@ +((global) => { + + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.parallelConflictLine = Vue.extend({ + props: { + file: Object, + line: Object + }, + mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions], + template: '#parallel-conflict-line' + }); + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 new file mode 100644 index 00000000000..eb4cc6a9dac --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 @@ -0,0 +1,15 @@ +((global) => { + + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.parallelConflictLines = Vue.extend({ + props: { + file: Object + }, + mixins: [global.mergeConflicts.utils], + components: { + 'parallel-conflict-line': gl.mergeConflicts.parallelConflictLine + } + }); + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index bdd6753f66a..83bcc3f51aa 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -196,7 +196,7 @@ isHead: true, isSelected: false, isUnselected: false - } + }; }, decorateLineForInlineView(line, id, conflict) { @@ -227,7 +227,7 @@ richText: rich_text, isSelected: false, isUnselected: false - } + }; }, getOriginHeaderLine(id) { @@ -241,7 +241,7 @@ isOrigin: true, isSelected: false, isUnselected: false - } + }; }, getFilePath(file) { diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index 449b4d2209a..12688365f19 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -1,7 +1,12 @@ //= require vue //= require ./merge_conflict_store //= require ./merge_conflict_service +//= require ./mixins/line_conflict_utils +//= require ./mixins/line_conflict_actions //= require ./components/diff_file_editor +//= require ./components/inline_conflict_lines +//= require ./components/parallel_conflict_line +//= require ./components/parallel_conflict_lines $(() => { const INTERACTIVE_RESOLVE_MODE = 'interactive'; @@ -16,7 +21,9 @@ $(() => { el: '#conflicts', data: mergeConflictsStore.state, components: { - 'diff-file-editor': gl.mergeConflicts.diffFileEditor + 'diff-file-editor': gl.mergeConflicts.diffFileEditor, + 'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines, + 'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines }, computed: { conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, @@ -46,9 +53,6 @@ $(() => { }); }, methods: { - handleSelected(file, sectionId, selection) { - mergeConflictsStore.handleSelected(file, sectionId, selection); - }, handleViewTypeChange(viewType) { mergeConflictsStore.setViewType(viewType); }, diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 new file mode 100644 index 00000000000..114a2c5b305 --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 @@ -0,0 +1,12 @@ +((global) => { + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.actions = { + methods: { + handleSelected(file, sectionId, selection) { + gl.mergeConflicts.mergeConflictsStore.handleSelected(file, sectionId, selection); + } + } + }; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 new file mode 100644 index 00000000000..b846a90ab2a --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 @@ -0,0 +1,18 @@ +((global) => { + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.utils = { + methods: { + lineCssClass(line) { + return { + 'head': line.isHead, + 'origin': line.isOrigin, + 'match': line.hasMatch, + 'selected': line.isSelected, + 'unselected': line.isUnselected + }; + } + } + }; + +})(window.gl || (window.gl = {})); diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index 5ff7befe0ca..d9f74d2cbfb 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -1,10 +1,3 @@ -- class_bindings = "{ | - 'head': line.isHead, | - 'origin': line.isOrigin, | - 'match': line.hasMatch, | - 'selected': line.isSelected, | - 'unselected': line.isUnselected }" - - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" - content_for :page_specific_javascripts do = page_specific_javascript_tag('merge_conflicts/merge_conflicts_bundle.js') @@ -34,15 +27,14 @@ %strong {{file.filePath}} = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - %div{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } - = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_inline", locals: { class_bindings: class_bindings } - %div{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } - = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_parallel", locals: { class_bindings: class_bindings } - %div{"v-show" => " file.resolveMode === 'edit' || file.type === 'text-editor'"} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } + .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines" + .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/components/parallel_conflict_lines" + %div{"v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'"} + = render partial: "projects/merge_requests/conflicts/components/diff_file_editor" = render partial: "projects/merge_requests/conflicts/submit_form" --# Components -= render partial: 'projects/merge_requests/conflicts/components/diff_file_editor' - \ No newline at end of file +-# Components += render partial: 'projects/merge_requests/conflicts/components/parallel_conflict_line' diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index 7222b8ae4d3..5ab3cd96163 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -1,13 +1,9 @@ .content-block.oneline-block.files-changed{"v-if" => "!isLoading && !hasError"} .inline-parallel-buttons{"v-if" => "showDiffViewTypeSwitcher"} .btn-group - %a.btn{ | - ":class" => "{'active': !isParallel}", | - "@click" => "handleViewTypeChange('inline')"} + %button.btn{":class" => "{'active': !isParallel}", "@click" => "handleViewTypeChange('inline')"} Inline - %a.btn{ | - ":class" => "{'active': isParallel}", | - "@click" => "handleViewTypeChange('parallel')"} + %button.btn{":class" => "{'active': isParallel}", "@click" => "handleViewTypeChange('parallel')"} Side-by-side .js-toggle-container diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml deleted file mode 100644 index 497db7e3f9b..00000000000 --- a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.diff-editor-wrap{ "v-show" => "file.showEditor" } - .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } - .discard-changes-alert - Are you sure to discard your changes? - .discard-actions - %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes - %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel - %diff-file-editor{":file" => "file"} diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml deleted file mode 100644 index b1d7a90eb74..00000000000 --- a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -.diff-wrap-lines.code.file-content.js-syntax-highlight - %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.new_line{":class" => class_bindings} - %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => class_bindings} - %a {{line.old_line}} - %td.line_content{":class" => class_bindings} - {{{line.richText}}} - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{{line.richText}}} - %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } - {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml deleted file mode 100644 index f5af347f59b..00000000000 --- a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -.diff-wrap-lines.code.file-content.js-syntax-highlight - %table - %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} - %template{"v-for" => "line in section"} - - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{line.richText}} - %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} - {{line.buttonTitle}} - - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.old_line{":class" => class_bindings} - {{line.lineNumber}} - %td.line_content.parallel{":class" => class_bindings} - {{{line.richText}}} diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 9965bdf9028..943ae6ee129 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -1,5 +1,11 @@ -%template{ id: "diff-file-editor" } - %div +%diff-file-editor{"inline-template" => "true", ":file" => "file", ":on-cancel-discard-confirmation" => "cancelDiscardConfirmation", ":on-accept-discard-confirmation" => "acceptDiscardConfirmation"} + .diff-editor-wrap{ "v-show" => "file.showEditor" } + .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } + .discard-changes-alert + Are you sure to discard your changes? + .discard-actions + %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes + %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel .editor-wrap{ ":class" => "classObject" } .loading %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml new file mode 100644 index 00000000000..f094df7fcaa --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml @@ -0,0 +1,15 @@ +%inline-conflict-lines{ "inline-template" => "true", ":file" => "file"} + %table + %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} + %td.diff-line-num.new_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + %a {{line.new_line}} + %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + %a {{line.old_line}} + %td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + {{{line.richText}}} + %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %strong {{{line.richText}}} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } + {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml new file mode 100644 index 00000000000..5690bf7419c --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml @@ -0,0 +1,10 @@ +%script{"id" => 'parallel-conflict-line', "type" => "text/x-template"} + %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %strong {{line.richText}} + %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} + {{line.buttonTitle}} + %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + {{line.lineNumber}} + %td.line_content.parallel{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + {{{line.richText}}} diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml new file mode 100644 index 00000000000..a8ecdf59393 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml @@ -0,0 +1,4 @@ +%parallel-conflict-lines{"inline-template" => "true", ":file" => "file"} + %table + %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} + %td{"is"=>"parallel-conflict-line", "v-for" => "line in section", ":line" => "line", ":file" => "file"} -- cgit v1.2.1 From 54bfe70795e289b86485b2a57d72b6711e4994bd Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 07:57:57 -0500 Subject: Add more tests to check conflicts resolution --- .../merge_conflict_data_provider.js.es6 | 2 +- .../merge_conflicts/merge_conflict_store.js.es6 | 4 +- .../projects/merge_requests_controller_spec.rb | 1 + spec/features/merge_requests/conflicts_spec.rb | 145 ++++++++++++++------- 4 files changed, 101 insertions(+), 51 deletions(-) diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index 6d1d3f36b33..5877d2f1896 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -290,7 +290,7 @@ class MergeConflictDataProvider { isReadyToCommit() { const vi = this.vueInstance; const files = this.vueInstance.conflictsData.files; - const hasCommitMessage = $.trim(this.vueInstance.conflictsData.commitMessage).length; + const hasCommitMessage = this.vueInstance.conflictsData.commitMessage.trim(); let unresolved = 0; for (let i = 0, l = files.length; i < l; i++) { diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index 83bcc3f51aa..5c5c65f29d4 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -321,7 +321,8 @@ let numberConflicts = 0; let resolvedConflicts = Object.keys(file.resolutionData).length - // We only check if + // We only check for conflicts type 'text' + // since conflicts `text_editor` can´t be resolved in interactive mode if (file.type === CONFLICT_TYPES.TEXT) { for (let j = 0, k = file.sections.length; j < k; j++) { if (file.sections[j].conflict) { @@ -334,6 +335,7 @@ } } } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + // Unlikely to happen since switching to Edit mode saves content automatically. // Checking anyway in case the save strategy changes in the future if (!file.content) { diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 06b37aa4997..31f43bdc89a 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -659,6 +659,7 @@ describe Projects::MergeRequestsController do id: merge_request.iid expect(merge_request.reload.title).to eq(merge_request.wipless_title) + end end describe 'GET conflict_for_path' do diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 4cecc22aa6c..d258ff52bbb 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -12,74 +12,121 @@ feature 'Merge request conflict resolution', js: true, feature: true do end end - context 'when a merge request can be resolved in the UI' do - let(:merge_request) { create_merge_request('conflict-resolvable') } + shared_examples "conflicts are resolved in Interactive mode" do + it 'conflicts are resolved in Interactive mode' do + within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do + click_button 'Use ours' + end - before do - project.team << [user, :developer] - login_as(user) + within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do + all('button', text: 'Use ours').each do |button| + button.click + end + end - visit namespace_project_merge_request_path(project.namespace, project, merge_request) - end + click_button 'Commit conflict resolution' + wait_for_ajax - it 'shows a link to the conflict resolution page' do - expect(page).to have_link('conflicts', href: /\/conflicts\Z/) - end + expect(page).to have_content('All merge conflicts were resolved') + merge_request.reload_diff - context 'visiting the conflicts resolution page' do - before { click_link('conflicts', href: /\/conflicts\Z/) } + click_on 'Changes' + wait_for_ajax - it 'shows the conflicts' do - begin - expect(find('#conflicts')).to have_content('popen.rb') - rescue Capybara::Poltergeist::JavascriptError - retry - end + within find('.diff-file', text: 'files/ruby/popen.rb') do + expect(page).to have_selector('.line_content.new', text: "vars = { 'PWD' => path }") + expect(page).to have_selector('.line_content.new', text: "options = { chdir: path }") end - context 'when in inline mode' do - it 'resolves files manually' do - within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do - click_button 'Edit inline' - wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");') - end - - within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do - click_button 'Edit inline' - wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') - end - - click_button 'Commit conflict resolution' - wait_for_ajax - expect(page).to have_content('All merge conflicts were resolved') - merge_request.reload_diff + within find('.diff-file', text: 'files/ruby/regex.rb') do + expect(page).to have_selector('.line_content.new', text: "def username_regexp") + expect(page).to have_selector('.line_content.new', text: "def project_name_regexp") + expect(page).to have_selector('.line_content.new', text: "def path_regexp") + expect(page).to have_selector('.line_content.new', text: "def archive_formats_regexp") + expect(page).to have_selector('.line_content.new', text: "def git_reference_regexp") + expect(page).to have_selector('.line_content.new', text: "def default_regexp") + end + end + end - click_on 'Changes' - wait_for_ajax + shared_examples "conflicts are resolved in Edit inline mode" do + it 'conflicts are resolved in Edit inline mode' do + expect(find('#conflicts')).to have_content('popen.rb') - expect(page).to have_content('One morning') - expect(page).to have_content('Gregor Samsa woke from troubled dreams') - end + within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do + click_button 'Edit inline' + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");') end + + within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do + click_button 'Edit inline' + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') + end + + click_button 'Commit conflict resolution' + wait_for_ajax + expect(page).to have_content('All merge conflicts were resolved') + merge_request.reload_diff + + click_on 'Changes' + wait_for_ajax + + expect(page).to have_content('One morning') + expect(page).to have_content('Gregor Samsa woke from troubled dreams') end end - context 'when a merge request can be resolved in the UI' do - let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') } - + context 'can be resolved in the UI' do before do project.team << [user, :developer] login_as(user) + end - visit namespace_project_merge_request_path(project.namespace, project, merge_request) + context 'the conflicts are resolvable' do + let(:merge_request) { create_merge_request('conflict-resolvable') } + + before { visit namespace_project_merge_request_path(project.namespace, project, merge_request) } + + it 'shows a link to the conflict resolution page' do + expect(page).to have_link('conflicts', href: /\/conflicts\Z/) + end + + context 'in Inline view mode' do + before { click_link('conflicts', href: /\/conflicts\Z/) } + + include_examples "conflicts are resolved in Interactive mode" + include_examples "conflicts are resolved in Edit inline mode" + end + + context 'in Parallel view mode' do + before do + click_link('conflicts', href: /\/conflicts\Z/) + click_button 'Side-by-side' + end + + include_examples "conflicts are resolved in Interactive mode" + include_examples "conflicts are resolved in Edit inline mode" + end end - context 'a conflict contain markers' do - before { click_link('conflicts', href: /\/conflicts\Z/) } + context 'the conflict contain markers' do + let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') } + + before do + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + click_link('conflicts', href: /\/conflicts\Z/) + end + + it 'conflicts can not be resolved in Interactive mode' do + within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do + expect(page).not_to have_content 'Interactive mode' + expect(page).not_to have_content 'Edit inline' + end + end - it 'resolves files manually' do + it 'conflicts are resolved in Edit inline mode' do within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do wait_for_ajax execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') @@ -94,7 +141,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do click_on 'Changes' wait_for_ajax - find('.nothing-here-block', visible: true).click + find('.click-to-expand').click wait_for_ajax expect(page).to have_content('Gregor Samsa woke from troubled dreams') -- cgit v1.2.1 From cc874d9167b7ba5531aeade09f69ecdc1057a0ee Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 18:05:16 -0500 Subject: Fix column limit on mysql --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8708eae1b2a..2c2edd8e9e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,7 @@ before_script: - bundle --version - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - retry gem install knapsack - - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate' + - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql' stages: - prepare -- cgit v1.2.1 From 8fd7a5942286d69eb34406e06ba5263c3944012e Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 20:49:16 -0500 Subject: Remove unused files --- .../merge_conflict_data_provider.js.es6 | 438 --------------------- .../javascripts/merge_conflict_resolver.js.es6 | 112 ------ 2 files changed, 550 deletions(-) delete mode 100644 app/assets/javascripts/merge_conflict_data_provider.js.es6 delete mode 100644 app/assets/javascripts/merge_conflict_resolver.js.es6 diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 deleted file mode 100644 index 5877d2f1896..00000000000 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ /dev/null @@ -1,438 +0,0 @@ -const HEAD_HEADER_TEXT = 'HEAD//our changes'; -const ORIGIN_HEADER_TEXT = 'origin//their changes'; -const HEAD_BUTTON_TITLE = 'Use ours'; -const ORIGIN_BUTTON_TITLE = 'Use theirs'; -const INTERACTIVE_RESOLVE_MODE = 'interactive'; -const EDIT_RESOLVE_MODE = 'edit'; -const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE; - -class MergeConflictDataProvider { - - getInitialData() { - // TODO: remove reliance on jQuery and DOM state introspection - const diffViewType = $.cookie('diff_view'); - const fixedLayout = $('.content-wrapper .container-fluid').hasClass('container-limited'); - - return { - isLoading : true, - hasError : false, - isParallel : diffViewType === 'parallel', - diffViewType : diffViewType, - fixedLayout : fixedLayout, - isSubmitting : false, - conflictsData : {} - } - } - - - decorateData(vueInstance, data) { - this.vueInstance = vueInstance; - - if (data.type === 'error') { - vueInstance.hasError = true; - data.errorMessage = data.message; - } - else { - data.shortCommitSha = data.commit_sha.slice(0, 7); - data.commitMessage = data.commit_message; - - this.decorateFiles(data); - this.setParallelLines(data); - this.setInlineLines(data); - } - - vueInstance.conflictsData = data; - vueInstance.isSubmitting = false; - - const conflictsText = this.getConflictsCount() > 1 ? 'conflicts' : 'conflict'; - vueInstance.conflictsData.conflictsText = conflictsText; - } - - decorateFiles(data) { - data.files.forEach((file) => { - file.content = ''; - file.resolutionData = {}; - file.promptDiscardConfirmation = false; - file.resolveMode = DEFAULT_RESOLVE_MODE; - }); - } - - - setParallelLines(data) { - data.files.forEach( (file) => { - file.filePath = this.getFilePath(file); - file.iconClass = `fa-${file.blob_icon}`; - file.blobPath = file.blob_path; - file.parallelLines = []; - const linesObj = { left: [], right: [] }; - - file.sections.forEach( (section) => { - const { conflict, lines, id } = section; - - if (conflict) { - linesObj.left.push(this.getOriginHeaderLine(id)); - linesObj.right.push(this.getHeadHeaderLine(id)); - } - - lines.forEach( (line) => { - const { type } = line; - - if (conflict) { - if (type === 'old') { - linesObj.left.push(this.getLineForParallelView(line, id, 'conflict')); - } - else if (type === 'new') { - linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true)); - } - } - else { - const lineType = type || 'context'; - - linesObj.left.push (this.getLineForParallelView(line, id, lineType)); - linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); - } - }); - - this.checkLineLengths(linesObj); - }); - - for (let i = 0, len = linesObj.left.length; i < len; i++) { - file.parallelLines.push([ - linesObj.right[i], - linesObj.left[i] - ]); - } - - }); - } - - - checkLineLengths(linesObj) { - let { left, right } = linesObj; - - if (left.length !== right.length) { - if (left.length > right.length) { - const diff = left.length - right.length; - for (let i = 0; i < diff; i++) { - right.push({ lineType: 'emptyLine', richText: '' }); - } - } - else { - const diff = right.length - left.length; - for (let i = 0; i < diff; i++) { - left.push({ lineType: 'emptyLine', richText: '' }); - } - } - } - } - - - setInlineLines(data) { - data.files.forEach( (file) => { - file.iconClass = `fa-${file.blob_icon}`; - file.blobPath = file.blob_path; - file.filePath = this.getFilePath(file); - file.inlineLines = [] - - file.sections.forEach( (section) => { - let currentLineType = 'new'; - const { conflict, lines, id } = section; - - if (conflict) { - file.inlineLines.push(this.getHeadHeaderLine(id)); - } - - lines.forEach( (line) => { - const { type } = line; - - if ((type === 'new' || type === 'old') && currentLineType !== type) { - currentLineType = type; - file.inlineLines.push({ lineType: 'emptyLine', richText: '' }); - } - - this.decorateLineForInlineView(line, id, conflict); - file.inlineLines.push(line); - }) - - if (conflict) { - file.inlineLines.push(this.getOriginHeaderLine(id)); - } - }); - }); - } - - - handleSelected(file, sectionId, selection) { - const vi = this.vueInstance; - let files = vi.conflictsData.files; - - vi.$set(`conflictsData.files[${files.indexOf(file)}].resolutionData['${sectionId}']`, selection); - - - files.forEach( (file) => { - file.inlineLines.forEach( (line) => { - if (line.id === sectionId && (line.hasConflict || line.isHeader)) { - this.markLine(line, selection); - } - }); - - file.parallelLines.forEach( (lines) => { - const left = lines[0]; - const right = lines[1]; - const hasSameId = right.id === sectionId || left.id === sectionId; - const isLeftMatch = left.hasConflict || left.isHeader; - const isRightMatch = right.hasConflict || right.isHeader; - - if (hasSameId && (isLeftMatch || isRightMatch)) { - this.markLine(left, selection); - this.markLine(right, selection); - } - }) - }); - } - - - updateViewType(newType) { - const vi = this.vueInstance; - - if (newType === vi.diffViewType || !(newType === 'parallel' || newType === 'inline')) { - return; - } - - vi.diffViewType = newType; - vi.isParallel = newType === 'parallel'; - $.cookie('diff_view', newType, { - path: (gon && gon.relative_url_root) || '/' - }); - $('.content-wrapper .container-fluid') - .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout); - } - - setFileResolveMode(file, mode) { - const vi = this.vueInstance; - - // Restore Interactive mode when switching to Edit mode - if (mode === EDIT_RESOLVE_MODE) { - file.resolutionData = {}; - - this.restoreFileLinesState(file); - } - - file.resolveMode = mode; - } - - - restoreFileLinesState(file) { - file.inlineLines.forEach((line) => { - if (line.hasConflict || line.isHeader) { - line.isSelected = false; - line.isUnselected = false; - } - }); - - file.parallelLines.forEach((lines) => { - const left = lines[0]; - const right = lines[1]; - const isLeftMatch = left.hasConflict || left.isHeader; - const isRightMatch = right.hasConflict || right.isHeader; - - if (isLeftMatch || isRightMatch) { - left.isSelected = false; - left.isUnselected = false; - right.isSelected = false; - right.isUnselected = false; - } - }); - } - - - setPromptConfirmationState(file, state) { - file.promptDiscardConfirmation = state; - } - - - markLine(line, selection) { - if (selection === 'head' && line.isHead) { - line.isSelected = true; - line.isUnselected = false; - } - else if (selection === 'origin' && line.isOrigin) { - line.isSelected = true; - line.isUnselected = false; - } - else { - line.isSelected = false; - line.isUnselected = true; - } - } - - - getConflictsCount() { - if (!this.vueInstance.conflictsData.files) { - return 0; - } - - const files = this.vueInstance.conflictsData.files; - let count = 0; - - files.forEach((file) => { - file.sections.forEach((section) => { - if (section.conflict) { - count++; - } - }); - }); - - return count; - } - - - isReadyToCommit() { - const vi = this.vueInstance; - const files = this.vueInstance.conflictsData.files; - const hasCommitMessage = this.vueInstance.conflictsData.commitMessage.trim(); - let unresolved = 0; - - for (let i = 0, l = files.length; i < l; i++) { - let file = files[i]; - - if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { - let numberConflicts = 0; - let resolvedConflicts = Object.keys(file.resolutionData).length - - for (let j = 0, k = file.sections.length; j < k; j++) { - if (file.sections[j].conflict) { - numberConflicts++; - } - } - - if (resolvedConflicts !== numberConflicts) { - unresolved++; - } - } else if (file.resolveMode === EDIT_RESOLVE_MODE) { - // Unlikely to happen since switching to Edit mode saves content automatically. - // Checking anyway in case the save strategy changes in the future - if (!file.content) { - unresolved++; - continue; - } - } - } - - return !vi.isSubmitting && hasCommitMessage && !unresolved; - } - - - getCommitButtonText() { - const initial = 'Commit conflict resolution'; - const inProgress = 'Committing...'; - const vue = this.vueInstance; - - return vue ? vue.isSubmitting ? inProgress : initial : initial; - } - - - decorateLineForInlineView(line, id, conflict) { - const { type } = line; - line.id = id; - line.hasConflict = conflict; - line.isHead = type === 'new'; - line.isOrigin = type === 'old'; - line.hasMatch = type === 'match'; - line.richText = line.rich_text; - line.isSelected = false; - line.isUnselected = false; - } - - getLineForParallelView(line, id, lineType, isHead) { - const { old_line, new_line, rich_text } = line; - const hasConflict = lineType === 'conflict'; - - return { - id, - lineType, - hasConflict, - isHead : hasConflict && isHead, - isOrigin : hasConflict && !isHead, - hasMatch : lineType === 'match', - lineNumber : isHead ? new_line : old_line, - section : isHead ? 'head' : 'origin', - richText : rich_text, - isSelected : false, - isUnselected : false - } - } - - - getHeadHeaderLine(id) { - return { - id : id, - richText : HEAD_HEADER_TEXT, - buttonTitle : HEAD_BUTTON_TITLE, - type : 'new', - section : 'head', - isHeader : true, - isHead : true, - isSelected : false, - isUnselected: false - } - } - - - getOriginHeaderLine(id) { - return { - id : id, - richText : ORIGIN_HEADER_TEXT, - buttonTitle : ORIGIN_BUTTON_TITLE, - type : 'old', - section : 'origin', - isHeader : true, - isOrigin : true, - isSelected : false, - isUnselected: false - } - } - - - handleFailedRequest(vueInstance, data) { - vueInstance.hasError = true; - vueInstance.conflictsData.errorMessage = 'Something went wrong!'; - } - - - getCommitData() { - let conflictsData = this.vueInstance.conflictsData; - let commitData = {}; - - commitData = { - commitMessage: conflictsData.commitMessage, - files: [] - }; - - conflictsData.files.forEach((file) => { - let addFile; - - addFile = { - old_path: file.old_path, - new_path: file.new_path - }; - - // Submit only one data for type of editing - if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { - addFile.sections = file.resolutionData; - } else if (file.resolveMode === EDIT_RESOLVE_MODE) { - addFile.content = file.content; - } - - commitData.files.push(addFile); - }); - - return commitData; - } - - - getFilePath(file) { - const { old_path, new_path } = file; - return old_path === new_path ? new_path : `${old_path} → ${new_path}`; - } -} diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6 deleted file mode 100644 index c317a6521b5..00000000000 --- a/app/assets/javascripts/merge_conflict_resolver.js.es6 +++ /dev/null @@ -1,112 +0,0 @@ -//= require vue -//= require ./merge_conflicts/components/diff_file_editor - -const INTERACTIVE_RESOLVE_MODE = 'interactive'; -const EDIT_RESOLVE_MODE = 'edit'; - -class MergeConflictResolver { - - constructor() { - this.dataProvider = new MergeConflictDataProvider(); - this.initVue(); - } - - initVue() { - const that = this; - this.vue = new Vue({ - el : '#conflicts', - name : 'MergeConflictResolver', - data : this.dataProvider.getInitialData(), - created : this.fetchData(), - computed : this.setComputedProperties(), - methods : { - handleSelected(file, sectionId, selection) { - that.dataProvider.handleSelected(file, sectionId, selection); - }, - handleViewTypeChange(newType) { - that.dataProvider.updateViewType(newType); - }, - commit() { - that.commit(); - }, - onClickResolveModeButton(file, mode) { - that.toggleResolveMode(file, mode); - }, - acceptDiscardConfirmation(file) { - that.dataProvider.setPromptConfirmationState(file, false); - that.dataProvider.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE); - }, - cancelDiscardConfirmation(file) { - that.dataProvider.setPromptConfirmationState(file, false); - }, - }, - components: { - 'diff-file-editor': window.gl.diffFileEditor - } - }) - } - - - setComputedProperties() { - const dp = this.dataProvider; - - return { - conflictsCount() { return dp.getConflictsCount() }, - readyToCommit() { return dp.isReadyToCommit() }, - commitButtonText() { return dp.getCommitButtonText() } - } - } - - - fetchData() { - const dp = this.dataProvider; - - $.get($('#conflicts').data('conflictsPath')) - .done((data) => { - dp.decorateData(this.vue, data); - }) - .error((data) => { - dp.handleFailedRequest(this.vue, data); - }) - .always(() => { - this.vue.isLoading = false; - - this.vue.$nextTick(() => { - $('#conflicts .js-syntax-highlight').syntaxHighlight(); - }); - - $('.content-wrapper .container-fluid') - .toggleClass('container-limited', !this.vue.isParallel && this.vue.fixedLayout); - }) - } - - - commit() { - this.vue.isSubmitting = true; - - $.ajax({ - url: $('#conflicts').data('resolveConflictsPath'), - data: JSON.stringify(this.dataProvider.getCommitData()), - contentType: "application/json", - dataType: 'json', - method: 'POST' - }) - .done((data) => { - window.location.href = data.redirect_to; - }) - .error(() => { - this.vue.isSubmitting = false; - new Flash('Something went wrong!'); - }); - } - - - toggleResolveMode(file, mode) { - if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) { - this.dataProvider.setPromptConfirmationState(file, true); - return; - } - - this.dataProvider.setFileResolveMode(file, mode); - } -} -- cgit v1.2.1 From 6141816c233d87888d1450d3bc48a28fc6044ec1 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 20:49:43 -0500 Subject: Use plain JS to get elements and data and values --- .../javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index 12688365f19..7fd3749b3e2 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -10,14 +10,14 @@ $(() => { const INTERACTIVE_RESOLVE_MODE = 'interactive'; - const $conflicts = $(document.getElementById('conflicts')); + const conflictsEl = document.querySelector('#conflicts'); const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore; const mergeConflictsService = new gl.mergeConflicts.mergeConflictsService({ - conflictsPath: $conflicts.data('conflictsPath'), - resolveConflictsPath: $conflicts.data('resolveConflictsPath') + conflictsPath: conflictsEl.dataset.conflictsPath, + resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath }); - gl.MergeConflictsResolverApp = new Vue({ + gl.MergeConflictsResolverApp = new Vue({ el: '#conflicts', data: mergeConflictsStore.state, components: { @@ -48,7 +48,7 @@ $(() => { mergeConflictsStore.setLoadingState(false); this.$nextTick(() => { - $conflicts.find('.js-syntax-highlight').syntaxHighlight(); + $(conflictsEl.querySelectorAll('.js-syntax-highlight')).syntaxHighlight(); }); }); }, -- cgit v1.2.1 From edd30976bd8069a02e0674ee36d1455c06ea751f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 12 Oct 2016 18:53:29 -0500 Subject: Update CHANGELOG --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 99bbd99726d..f0b250f365c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,8 @@ v 8.13.0 (unreleased) - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.6 + - Ability to resolve merge request conflicts with editor !6374 + - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup -- cgit v1.2.1 From cdd0dd507d1d5a226f1f438c4bfa602a115caac3 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 12 Oct 2016 22:52:44 -0500 Subject: Fix discard message --- .../merge_requests/conflicts/components/_diff_file_editor.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 943ae6ee129..3c927d362c2 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -2,7 +2,7 @@ .diff-editor-wrap{ "v-show" => "file.showEditor" } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } .discard-changes-alert - Are you sure to discard your changes? + Are you sure you want to discard your changes? .discard-actions %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel -- cgit v1.2.1 From 3764fd4b4166bdf869cc04e4e82558d6b80b4ca5 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 13 Oct 2016 10:34:15 +0100 Subject: Add blob_ace_mode to conflict content response --- lib/gitlab/conflict/file.rb | 5 +++++ spec/controllers/projects/merge_requests_controller_spec.rb | 1 + spec/lib/gitlab/conflict/file_spec.rb | 11 +++++++++++ 3 files changed, 17 insertions(+) diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 661e43d3fa9..c843315782d 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -25,6 +25,10 @@ module Gitlab merge_file_result[:data] end + def our_blob + @our_blob ||= repository.blob_at(merge_request.diff_refs.head_sha, our_path) + end + def type lines unless @type @@ -209,6 +213,7 @@ module Gitlab json_hash.tap do |json_hash| if opts[:full_content] json_hash[:content] = content + json_hash[:blob_ace_mode] = our_blob && our_blob.language.try(:ace_mode) else json_hash[:sections] = sections if type.text? json_hash[:type] = type diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 31f43bdc89a..3fe90375b92 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -712,6 +712,7 @@ describe Projects::MergeRequestsController do 'new_path' => path, 'blob_icon' => 'file-text-o', 'blob_path' => a_string_ending_with(path), + 'blob_ace_mode' => 'ruby', 'content' => content) end end diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb index 60020487061..648d342ecf8 100644 --- a/spec/lib/gitlab/conflict/file_spec.rb +++ b/spec/lib/gitlab/conflict/file_spec.rb @@ -257,5 +257,16 @@ FILE it 'includes the blob icon for the file' do expect(conflict_file.as_json[:blob_icon]).to eq('file-text-o') end + + context 'with the full_content option passed' do + it 'includes the full content of the conflict' do + expect(conflict_file.as_json(full_content: true)).to have_key(:content) + end + + it 'includes the detected language of the conflict file' do + expect(conflict_file.as_json(full_content: true)[:blob_ace_mode]). + to eq('ruby') + end + end end end -- cgit v1.2.1 From f5de2ce3fd74af214556f677f54efa3f4da13ed3 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 13 Oct 2016 12:32:35 -0500 Subject: Add syntax highlighting to files --- .../javascripts/merge_conflicts/components/diff_file_editor.js.es6 | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 417095a8db7..3379414343f 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -55,6 +55,7 @@ this.fileLoaded = true; this.editor = ace.edit(content); this.editor.$blockScrolling = Infinity; // Turn off annoying warning + this.editor.getSession().setMode(`ace/mode/${file.blob_ace_mode}`); this.editor.on('change', () => { this.saveDiffResolution(); }); -- cgit v1.2.1 From 26e327ea93140f6400b965b845958ff50461718c Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 13 Oct 2016 14:17:28 -0500 Subject: Update CHANGELOG --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f0b250f365c..e9f84503b2b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,7 +9,6 @@ v 8.13.0 (unreleased) - Add link from system note to compare with previous version - Use gitlab-shell v3.6.6 - Ability to resolve merge request conflicts with editor !6374 - - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup -- cgit v1.2.1 From 29a6f0d28ba4ca6c44b63d0ece2eff4416fb73e0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 14 Oct 2016 03:33:49 +0800 Subject: Introduce more GitLab routing helpers, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16852207 --- app/helpers/gitlab_routing_helper.rb | 32 +++++++++++++++++++++++ app/views/notify/pipeline_failed_email.html.haml | 8 +++--- app/views/notify/pipeline_failed_email.text.erb | 8 +++--- app/views/notify/pipeline_success_email.html.haml | 6 ++--- app/views/notify/pipeline_success_email.text.erb | 6 ++--- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 670a7ca36f4..f9c3d3f270f 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -94,6 +94,38 @@ module GitlabRoutingHelper namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args) end + def pipeline_url(pipeline, *args) + namespace_project_pipeline_url( + pipeline.project.namespace, + pipeline.project, + pipeline.id, + *args) + end + + def pipeline_build_url(pipeline, build, *args) + namespace_project_build_url( + pipeline.project.namespace, + pipeline.project, + build.id, + *args) + end + + def commits_url(entity, *args) + namespace_project_commits_url( + entity.project.namespace, + entity.project, + entity.ref, + *args) + end + + def commit_url(entity, *args) + namespace_project_commit_url( + entity.project.namespace, + entity.project, + entity.sha, + *args) + end + def project_snippet_url(entity, *args) namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args) end diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 1f7b354f4bc..ec02c2e2d90 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -92,7 +92,7 @@ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13"}/ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a.muted{href: namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), style: "color:#333333;text-decoration:none;"} + %a.muted{href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;"} = @pipeline.ref %tr %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit @@ -103,7 +103,7 @@ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13"}/ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a{href: namespace_project_commit_url(@project.namespace, @project, @pipeline.sha), style: "color:#3084bb;text-decoration:none;"} + %a{href: commit_url(@pipeline), style: "color:#3084bb;text-decoration:none;"} = @pipeline.short_sha - if @merge_request in @@ -134,7 +134,7 @@ %tr.pre-section %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;"} Pipeline - %a{href: namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id), style: "color:#3084bb;text-decoration:none;"} + %a{href: pipeline_url(@pipeline), style: "color:#3084bb;text-decoration:none;"} = "\##{@pipeline.id}" had = failed.size @@ -158,7 +158,7 @@ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;"} = build.stage %td{align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} - %a{href: namespace_project_build_url(@project.namespace, @project, build.id), style: "color:#3084bb;text-decoration:none;"} + %a{href: pipeline_build_url(@pipeline, build), style: "color:#3084bb;text-decoration:none;"} = build.name %tr.build-log %td{colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"} diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb index 1b249c0cb7c..8f8084b58e1 100644 --- a/app/views/notify/pipeline_failed_email.text.erb +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -1,12 +1,12 @@ Your pipeline has failed. Project: <%= @project.name %> ( <%= project_url(@project) %> ) -Branch: <%= @pipeline.ref %> ( <%= namespace_project_commits_url(@project.namespace, @project, @pipeline.ref) %> ) +Branch: <%= @pipeline.ref %> ( <%= commits_url(@pipeline) %> ) <% if @merge_request -%> Merge Request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge_request) %> ) <% end -%> -Commit: <%= @pipeline.short_sha %> ( <%= namespace_project_commit_url(@project.namespace, @project, @pipeline.sha) %> ) +Commit: <%= @pipeline.short_sha %> ( <%= commit_url(@pipeline) %> ) Commit Message: <%= @pipeline.git_commit_message.truncate(50) %> <% commit = @pipeline.commit -%> <% if commit.author -%> @@ -16,10 +16,10 @@ Commit Author: <%= commit.author_name %> <% end -%> <% failed = @pipeline.statuses.latest.failed -%> -Pipeline #<%= @pipeline.id %> ( <%= namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id) %> ) had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>. +Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) had <%= failed.size %> failed <%= 'build'.pluralize(failed.size) %>. <% failed.each do |build| -%> -Build #<%= build.id %> ( <%= namespace_project_build_url(@project.namespace, @project, build.id) %> ) +Build #<%= build.id %> ( <%= pipeline_build_url(@pipeline, build) %> ) Stage: <%= build.stage %> Name: <%= build.name %> Trace: <%= build.trace_with_state(last_lines: 10)[:text] %> diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 81b32524fb2..0fdf118c9bc 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -92,7 +92,7 @@ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13"}/ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a.muted{href: namespace_project_commits_url(@project.namespace, @project, @pipeline.ref), style: "color:#333333;text-decoration:none;"} + %a.muted{href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;"} = @pipeline.ref %tr %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit @@ -103,7 +103,7 @@ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13"}/ %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a{href: namespace_project_commit_url(@project.namespace, @project, @pipeline.sha), style: "color:#3084bb;text-decoration:none;"} + %a{href: commit_url(@pipeline), style: "color:#3084bb;text-decoration:none;"} = @pipeline.short_sha - if @merge_request in @@ -135,7 +135,7 @@ - build_count = @pipeline.statuses.latest.size - stage_count = @pipeline.stages.size Pipeline - %a{href: namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id), style: "color:#3084bb;text-decoration:none;"} + %a{href: pipeline_url(@pipeline), style: "color:#3084bb;text-decoration:none;"} = "\##{@pipeline.id}" successfully completed = "#{build_count} #{'build'.pluralize(build_count)}" diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index 7e7eec6d09c..afca0a1b808 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -1,12 +1,12 @@ Your pipeline has passed. Project: <%= @project.name %> ( <%= project_url(@project) %> ) -Branch: <%= @pipeline.ref %> ( <%= namespace_project_commits_url(@project.namespace, @project, @pipeline.ref) %> ) +Branch: <%= @pipeline.ref %> ( <%= commits_url(@pipeline.ref) %> ) <% if @merge_request -%> Merge Request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge_request) %> ) <% end -%> -Commit: <%= @pipeline.short_sha %> ( <%= namespace_project_commit_url(@project.namespace, @project, @pipeline.sha) %> ) +Commit: <%= @pipeline.short_sha %> ( <%= commit_url(@pipeline) %> ) Commit Message: <%= @pipeline.git_commit_message.truncate(50) %> <% commit = @pipeline.commit -%> <% if commit.author -%> @@ -17,7 +17,7 @@ Commit Author: <%= commit.author_name %> <% build_count = @pipeline.statuses.latest.size -%> <% stage_count = @pipeline.stages.size -%> -Pipeline #<%= @pipeline.id %> ( <%= namespace_project_pipeline_url(@project.namespace, @project, @pipeline.id) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. +Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>. Manage all notifications: <%= profile_notifications_url %> -- cgit v1.2.1 From 18f731386ae2820c31c8a82c501e8ca7afc7ea7b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 14 Oct 2016 03:40:50 +0800 Subject: Boo. Midnight programming is bad. Just pass pipeline --- app/views/notify/pipeline_success_email.text.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index afca0a1b808..ae22d474f2c 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -1,7 +1,7 @@ Your pipeline has passed. Project: <%= @project.name %> ( <%= project_url(@project) %> ) -Branch: <%= @pipeline.ref %> ( <%= commits_url(@pipeline.ref) %> ) +Branch: <%= @pipeline.ref %> ( <%= commits_url(@pipeline) %> ) <% if @merge_request -%> Merge Request: <%= @merge_request.to_reference %> ( <%= merge_request_url(@merge_request) %> ) <% end -%> -- cgit v1.2.1 From d00f17c0108c73b521b533bad7d2d7706cb25a5c Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Thu, 13 Oct 2016 21:56:28 +0000 Subject: fix grafana_configuration.md move link --- doc/monitoring/performance/grafana_configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 93320b40174..0d4be02ff5f 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -1 +1 @@ -This document was moved to [administration/monitoring/performance/grafana_configuration](../administration/monitoring/performance/grafana_configuration.md). +This document was moved to [administration/monitoring/performance/grafana_configuration](../../administration/monitoring/performance/grafana_configuration.md). -- cgit v1.2.1 From 6c7d3a0ee4c4c2b4e0a38fe92cd364c888eda907 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Fri, 14 Oct 2016 09:40:40 +1100 Subject: Remove '/u' prefix form username from Account page --- CHANGELOG | 1 + app/views/profiles/accounts/show.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 99bbd99726d..f933c298b01 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Remove '/u' prefix form username from Account page (blackst0ne) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index c80f22457b4..8ee643f3bcc 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -86,11 +86,11 @@ = f.label :username, "Path", class: "label-light" .input-group .input-group-addon - = "#{root_url}u/" + = "#{root_url}" = f.text_field :username, required: true, class: 'form-control' .help-block Current path: - = "#{root_url}u/#{current_user.username}" + = "#{root_url}#{current_user.username}" .prepend-top-default = f.button class: "btn btn-warning", type: "submit" do = icon "spinner spin", class: "hidden loading-username" -- cgit v1.2.1 From 86d59657fc8ba0f96b8f957b1298a34144e060f9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 11:55:26 +0100 Subject: Increased performance of GL dropdown renderItem - Fixes an issue where `renderItem` is called several times even when not required - Increased performance when rendering dropdown items Closes #21110 --- app/assets/javascripts/gl_dropdown.js | 63 +++++++++++++++++------------------ 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index e034ca68645..c0cb70cb427 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -37,12 +37,9 @@ e.preventDefault() } }) - .on('keyup', function(e) { + .on('input', function(e) { var keyCode; keyCode = e.which; - if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) { - return; - } if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.addClass(HAS_VALUE_CLASS); } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { @@ -500,14 +497,17 @@ // Render the full menu GitLabDropdown.prototype.renderMenu = function(html) { - var menu_html; - menu_html = ""; if (this.options.renderMenu) { - menu_html = this.options.renderMenu(html); + return this.options.renderMenu(html); } else { - menu_html = $('
      ').append(html); + var ul = document.createElement('ul'); + + for (var i = 0; i < html.length; i++) { + ul.appendChild(html[i]); + } + + return ul; } - return menu_html; }; // Append the menu into the dropdown @@ -521,7 +521,7 @@ }; GitLabDropdown.prototype.renderItem = function(data, group, index) { - var cssClass, field, fieldName, groupAttrs, html, selected, text, url, value; + var field, fieldName, html, selected, text, url, value; if (group == null) { group = false; } @@ -529,18 +529,16 @@ // Render the row index = false; } - html = ""; - // Divider - if (data === "divider") { - return "
    • "; - } - // Separator is a full-width divider - if (data === "separator") { - return "
    • "; + html = document.createElement('li'); + if (data === 'divider' || data === 'separator') { + html.className = data; + return html; } // Header if (data.header != null) { - return _.template('')({ header: data.header }); + html.className = 'dropdown-header'; + html.innerHTML = data.header; + return html; } if (this.options.renderRow) { // Call the render function @@ -567,24 +565,25 @@ } else { text = data.text != null ? data.text : ''; } - cssClass = ""; - if (selected) { - cssClass = "is-active"; - } if (this.highlight) { text = this.highlightTextMatches(text, this.filterInput.val()); } + // Create the list item & the link + var link = document.createElement('a'); + + link.href = url; + link.innerHTML = text; + + if (selected) { + link.className = 'is-active'; + } + if (group) { - groupAttrs = 'data-group=' + group + ' data-index=' + index; - } else { - groupAttrs = ''; + link.dataset.group = group; + link.dataset.index = index; } - html = _.template('
    • class="<%- cssClass %>"><%= text %>
    • ')({ - url: url, - groupAttrs: groupAttrs, - cssClass: cssClass, - text: text - }); + + html.appendChild(link); } return html; }; -- cgit v1.2.1 From ef5a76f68a89b9bee9070800ceb19753d0c602fe Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 17:31:08 +0100 Subject: Fixed rendering of HTML strings --- app/assets/javascripts/gl_dropdown.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index c0cb70cb427..06b31b9b24b 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -503,7 +503,13 @@ var ul = document.createElement('ul'); for (var i = 0; i < html.length; i++) { - ul.appendChild(html[i]); + var el = html[i]; + + if (typeof el === 'string') { + ul.innerHTML += el; + } else { + ul.appendChild(el); + } } return ul; -- cgit v1.2.1 From ba4e6376e1a6284286d2af46a9f5f1afa6382fd0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 09:29:37 +0100 Subject: Fixed appending jQuery elements --- app/assets/javascripts/gl_dropdown.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 06b31b9b24b..e94e7cd8be8 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -505,6 +505,10 @@ for (var i = 0; i < html.length; i++) { var el = html[i]; + if (el instanceof jQuery) { + el = el.get(0); + } + if (typeof el === 'string') { ul.innerHTML += el; } else { -- cgit v1.2.1 From e8901b20e9c07ae2654e267e5e88c0aa3476835d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 11:25:59 +0100 Subject: Changed trigger keyup to input --- app/assets/javascripts/gl_dropdown.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index e94e7cd8be8..b7a70fab74e 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -25,7 +25,7 @@ return function(e) { e.preventDefault(); e.stopPropagation(); - return _this.input.val('').trigger('keyup').focus(); + return _this.input.val('').trigger('input').focus(); }; })(this)); // Key events @@ -38,8 +38,6 @@ } }) .on('input', function(e) { - var keyCode; - keyCode = e.which; if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.addClass(HAS_VALUE_CLASS); } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { @@ -252,7 +250,7 @@ _this.fullData = data; _this.parseData(_this.fullData); if (_this.options.filterable && _this.filter && _this.filter.input) { - return _this.filter.input.trigger('keyup'); + return _this.filter.input.trigger('input'); } }; // Remote data @@ -484,7 +482,7 @@ // Triggering 'keyup' will re-render the dropdown which is not always required // specially if we want to keep the state of the dropdown needed for bulk-assignment if (!this.options.persistWhenHide) { - $input.trigger("keyup"); + $input.trigger("input"); } if (this.dropdown.find(".dropdown-toggle-page").length) { $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS); -- cgit v1.2.1 From 88996deba5a1d8b29f03856b295b33caa7290981 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 12:32:30 +0100 Subject: Fixed keycode undefined --- app/assets/javascripts/gl_dropdown.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index b7a70fab74e..fe798509420 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -37,7 +37,7 @@ e.preventDefault() } }) - .on('input', function(e) { + .on('input', function() { if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.addClass(HAS_VALUE_CLASS); } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { @@ -50,10 +50,6 @@ if (this.options.remote) { clearTimeout(timeout); return timeout = setTimeout(function() { - var blurField = this.shouldBlur(keyCode); - if (blurField && this.filterInputBlur) { - this.input.blur(); - } return this.options.query(this.input.val(), function(data) { return this.options.callback(data); }.bind(this)); -- cgit v1.2.1 From 8ebc0919ea7a0647b5b5a0951cd3ce2070d78bb2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 14 Oct 2016 08:26:40 +0200 Subject: Link to review apps example from docs [ci skip] --- doc/ci/examples/README.md | 1 + doc/ci/yaml/README.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 08fbd9afa2f..ffc310ec8c7 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -13,6 +13,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test a Scala application](test-scala-application.md) - [Test a Phoenix application](test-phoenix-application.md) - [Using `dpl` as deployment tool](deployment/README.md) +- [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/) - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index cdf5ecc7a84..59399861a97 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -594,6 +594,8 @@ create the `review-apps/branch-name` environment. This environment should be accessible under `https://branch-name.review.example.com/`. +You can see a simple example at https://gitlab.com/gitlab-examples/review-apps-nginx/. + ### artifacts >**Notes:** -- cgit v1.2.1 From ab2a7a806a698d6638bb8b3f2ecc694f265fe65e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 13 Oct 2016 23:38:50 -0700 Subject: Add missing routes to make group edits work Closes #23306 --- config/routes/group.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/routes/group.rb b/config/routes/group.rb index 47a8a0a53d4..8bee2bb1eb4 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -3,6 +3,10 @@ require 'constraints/group_url_constrainer' constraints(GroupUrlConstrainer.new) do scope(path: ':id', as: :group, controller: :groups) do get '/', action: :show + post '/', action: :create + patch '/', action: :update + put '/', action: :update + delete '/', action: :destroy end end -- cgit v1.2.1 From 17e23b4c9c141bdbdcde4efec0bbcfdd544da8bd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Oct 2016 00:06:44 -0700 Subject: Add specs for group edit and deletion --- spec/features/groups_spec.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index c54ec2563ad..3f908294cbb 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -47,6 +47,32 @@ feature 'Group', feature: true do end end + describe 'Group Edit' do + let(:group) { create(:group) } + let(:path) { edit_group_path(group) } + + it 'saves new settings' do + expect(group.request_access_enabled).to be_truthy + visit path + + find('#group_request_access_enabled').set(false) + + click_button 'Save group' + + expect(page).to have_content 'successfully updated' + group.reload + expect(group.request_access_enabled).to be_falsey + end + + it 'removes group' do + visit path + + click_link 'Remove Group' + + expect(page).to have_content "scheduled for deletion" + end + end + describe 'description' do let(:group) { create(:group) } let(:path) { group_path(group) } -- cgit v1.2.1 From b927473c45e42e99adbbe69b71653f1b2981df01 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 14 Oct 2016 09:16:55 +0200 Subject: Grapify todos API --- lib/api/todos.rb | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 19df13d8aac..832b04a3bb1 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -8,18 +8,19 @@ module API 'issues' => ->(id) { find_project_issue(id) } } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do ISSUABLE_TYPES.each do |type, finder| type_id_str = "#{type.singularize}_id".to_sym - # Create a todo on an issuable - # - # Parameters: - # id (required) - The ID of a project - # issuable_id (required) - The ID of an issuable - # Example Request: - # POST /projects/:id/issues/:issuable_id/todo - # POST /projects/:id/merge_requests/:issuable_id/todo + desc 'Create a todo on an issuable' do + success Entities::Todo + end + params do + requires type_id_str, type: Integer, desc: 'The ID of an issuable' + end post ":id/#{type}/:#{type_id_str}/todo" do issuable = instance_exec(params[type_id_str], &finder) todo = TodoService.new.mark_todo(issuable, current_user).first @@ -40,25 +41,21 @@ module API end end - # Get a todo list - # - # Example Request: - # GET /todos - # + desc 'Get a todo list' do + success Entities::Todo + end get do todos = find_todos present paginate(todos), with: Entities::Todo, current_user: current_user end - # Mark a todo as done - # - # Parameters: - # id: (required) - The ID of the todo being marked as done - # - # Example Request: - # DELETE /todos/:id - # + desc 'Mark a todo as done' do + success Entities::Todo + end + params do + requires :id, type: Integer, desc: 'The ID of the todo being marked as done' + end delete ':id' do todo = current_user.todos.find(params[:id]) TodoService.new.mark_todos_as_done([todo], current_user) @@ -66,11 +63,7 @@ module API present todo.reload, with: Entities::Todo, current_user: current_user end - # Mark all todos as done - # - # Example Request: - # DELETE /todos - # + desc 'Mark all todos as done' delete do todos = find_todos TodoService.new.mark_todos_as_done(todos, current_user) -- cgit v1.2.1 From c850651b9174a40376dc5b712ce696406adedeab Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 14 Oct 2016 10:07:36 +0200 Subject: Add link to update docs for source installations --- doc/user/project/new_ci_build_permissions_model.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 19385a99d64..8827b501901 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -162,7 +162,7 @@ As an administrator: - **500 errors**: You will need to update [GitLab Workhorse][workhorse] to at least 0.8.2. This is done automatically for Omnibus installations, you need to - check manually for installations from source. + [check manually][update-docs] for installations from source. - **500 errors**: Check if you have another web proxy sitting in front of NGINX (HAProxy, Apache, etc.). It might be a good idea to let GitLab use the internal NGINX web server and not disable it completely. See [this comment][comment] for an @@ -307,4 +307,5 @@ test: [git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules [https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols [triggers]: ../../ci/triggers/README.md +[update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update [workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse -- cgit v1.2.1 From 3726dc4bb73647123ebef5c97f401f5537d3ae16 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 10:19:16 +0200 Subject: Check if project exists before creating deployment --- app/services/create_deployment_service.rb | 6 ++++++ app/workers/build_success_worker.rb | 2 -- spec/services/create_deployment_service_spec.rb | 17 ++++++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index c6dc2148c11..ff9a8310a8c 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -2,6 +2,8 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable = nil) + return unless executable? + ActiveRecord::Base.transaction do @deployable = deployable @environment = prepare_environment @@ -14,6 +16,10 @@ class CreateDeploymentService < BaseService private + def executable? + project && name.present? + end + def deploy project.deployments.create( environment: @environment, diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index a9dc34166a1..500d357ce31 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -4,8 +4,6 @@ class BuildSuccessWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - return unless build.project - create_deployment(build) end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 343b4385bf2..5fe56e7725f 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -84,11 +84,22 @@ describe CreateDeploymentService, services: true do expect(subject).to be_persisted end end + + context 'when project was removed' do + let(:project) { nil } + + it 'does not create deployment or environment' do + expect { subject }.not_to raise_error + + expect(Environment.count).to be_zero + expect(Deployment.count).to be_zero + end + end end describe 'processing of builds' do let(:environment) { nil } - + shared_examples 'does not create environment and deployment' do it 'does not create a new environment' do expect { subject }.not_to change { Environment.count } @@ -133,12 +144,12 @@ describe CreateDeploymentService, services: true do context 'without environment specified' do let(:build) { create(:ci_build, project: project) } - + it_behaves_like 'does not create environment and deployment' do subject { build.success } end end - + context 'when environment is specified' do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production', options: options) } -- cgit v1.2.1 From e8c9ccc2d8f85e22bb4969cb544f9de25f4c08f5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 14 Oct 2016 10:25:11 +0200 Subject: Refactor merge requests revisions - A system note now appears on every push - Replace dashes with underscores in images [ci skip] --- .../merge_requests/img/versions-compare.png | Bin 68722 -> 0 bytes .../merge_requests/img/versions-dropdown.png | Bin 60587 -> 0 bytes .../merge_requests/img/versions_compare.png | Bin 0 -> 68722 bytes .../merge_requests/img/versions_dropdown.png | Bin 0 -> 60587 bytes .../merge_requests/img/versions_system_note.png | Bin 0 -> 18731 bytes doc/user/project/merge_requests/versions.md | 30 ++++++++++++++------- 6 files changed, 20 insertions(+), 10 deletions(-) delete mode 100644 doc/user/project/merge_requests/img/versions-compare.png delete mode 100644 doc/user/project/merge_requests/img/versions-dropdown.png create mode 100644 doc/user/project/merge_requests/img/versions_compare.png create mode 100644 doc/user/project/merge_requests/img/versions_dropdown.png create mode 100644 doc/user/project/merge_requests/img/versions_system_note.png diff --git a/doc/user/project/merge_requests/img/versions-compare.png b/doc/user/project/merge_requests/img/versions-compare.png deleted file mode 100644 index 890cae7768c..00000000000 Binary files a/doc/user/project/merge_requests/img/versions-compare.png and /dev/null differ diff --git a/doc/user/project/merge_requests/img/versions-dropdown.png b/doc/user/project/merge_requests/img/versions-dropdown.png deleted file mode 100644 index 9bab9304e14..00000000000 Binary files a/doc/user/project/merge_requests/img/versions-dropdown.png and /dev/null differ diff --git a/doc/user/project/merge_requests/img/versions_compare.png b/doc/user/project/merge_requests/img/versions_compare.png new file mode 100644 index 00000000000..890cae7768c Binary files /dev/null and b/doc/user/project/merge_requests/img/versions_compare.png differ diff --git a/doc/user/project/merge_requests/img/versions_dropdown.png b/doc/user/project/merge_requests/img/versions_dropdown.png new file mode 100644 index 00000000000..9bab9304e14 Binary files /dev/null and b/doc/user/project/merge_requests/img/versions_dropdown.png differ diff --git a/doc/user/project/merge_requests/img/versions_system_note.png b/doc/user/project/merge_requests/img/versions_system_note.png new file mode 100644 index 00000000000..7c9d7715745 Binary files /dev/null and b/doc/user/project/merge_requests/img/versions_system_note.png differ diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md index 2805fdf635c..77eab7ba5e3 100644 --- a/doc/user/project/merge_requests/versions.md +++ b/doc/user/project/merge_requests/versions.md @@ -7,26 +7,36 @@ of merge request diff is created. When you visit a merge request that contains more than one pushes, you can select and compare the versions of those merge request diffs. -![Merge Request Versions](img/versions.png) +![Merge request versions](img/versions.png) + +--- By default, the latest version of changes is shown. However, you can select an older one from version dropdown. -![Merge Request Versions](img/versions-dropdown.png) +![Merge request versions dropdown](img/versions_dropdown.png) + +--- -You can also compare the merge request version with older one to see what is +You can also compare the merge request version with an older one to see what has changed since then. -![Merge Request Versions](img/versions-compare.png) +![Merge request versions compare](img/versions_compare.png) + +--- + +Every time you push new changes to the branch, a link to compare the last +changes appears as a system note. -Please note that comments are disabled while viewing outdated merge versions -or comparing to versions other than base. +![Merge request versions system note](img/versions_system_note.png) --- ->**Note:** -Merge request versions are based on push not on commit. So, if you pushed 5 -commits in a single push, it will be a single option in the dropdown. If you -pushed 5 times, that will count for 5 options. +>**Notes:** +- Comments are disabled while viewing outdated merge versions or comparing to + versions other than base. +- Merge request versions are based on push not on commit. So, if you pushed 5 + commits in a single push, it will be a single option in the dropdown. If you + pushed 5 times, that will count for 5 options. [ce-5467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5467 -- cgit v1.2.1 From ebddb5f329778ebf96d78c31e2ac93707a455864 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Oct 2016 09:34:53 +0100 Subject: Fixed undefined keycode build error --- app/assets/javascripts/gl_dropdown.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index fe798509420..53762f2965c 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -43,9 +43,6 @@ } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.removeClass(HAS_VALUE_CLASS); } - if (keyCode === 13 && !options.elIsInput) { - return false; - } // Only filter asynchronously only if option remote is set if (this.options.remote) { clearTimeout(timeout); -- cgit v1.2.1 From 04147f8773992b50c372160ed73ce8e7674d77d9 Mon Sep 17 00:00:00 2001 From: Sean Packham Date: Fri, 14 Oct 2016 09:56:47 +0100 Subject: Fixed missing links --- doc/university/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/university/README.md b/doc/university/README.md index 8b3538d5616..d5bb7299ecf 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -67,18 +67,18 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project #### 1.7 Community and Support -1. [Getting Help](/getting-help/) +1. [Getting Help](https://about.gitlab.com/getting-help/) - Proposing Features and Reporting and Tracking bugs for GitLab - The GitLab IRC channel, Gitter Chat Room, Community Forum and Mailing List - Getting Technical Support - Being part of our Great Community and Contributing to GitLab 1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/) 1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/) -1. [GitLab Training Workshops](/training) +1. [GitLab Training Workshops](https://about.gitlab.com/getting-help/training) #### 1.8 GitLab Training Material -1. [Git and GitLab Terminology](/glossary/) +1. [Git and GitLab Terminology](glossary/README.md) 1. [Git and GitLab Workshop - Slides](https://docs.google.com/presentation/d/1JzTYD8ij9slejV2-TO-NzjCvlvj6mVn9BORePXNJoMI/edit?usp=drive_web) 1. [Git and GitLab Revision](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/university/training/end-user) @@ -209,7 +209,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project *Some content can only be accessed by GitLab team members* -1. [Support Path](/support/) +1. [Support Path](support/README.md) 1. [Sales Path (redirect to sales handbook)](https://about.gitlab.com/handbook/sales-onboarding/) 1. [GitLab architecture for noobs](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/development/architecture.md) 1. [Client Assessment of GitLab versus GitHub](https://docs.google.com/a/gitlab.com/spreadsheets/d/18cRF9Y5I6I7Z_ab6qhBEW55YpEMyU4PitZYjomVHM-M/edit?usp=sharing) -- cgit v1.2.1 From 75e12ca9880e900c8d0b29c973bdef40c6757c6f Mon Sep 17 00:00:00 2001 From: Sean Packham Date: Fri, 14 Oct 2016 10:00:02 +0100 Subject: Fixed missing links --- doc/university/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/university/README.md b/doc/university/README.md index d5bb7299ecf..e71e49c33c8 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -74,7 +74,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project - Being part of our Great Community and Contributing to GitLab 1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/) 1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/) -1. [GitLab Training Workshops](https://about.gitlab.com/getting-help/training) +1. [GitLab Training Workshops](https://about.gitlab.com/training) #### 1.8 GitLab Training Material -- cgit v1.2.1 From 6a4f71008390752e6b5574a9e9bdf277732d852d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 4 Oct 2016 16:03:13 +0200 Subject: Show what time ago a MR was deployed --- CHANGELOG | 1 + app/assets/javascripts/merge_request_widget.js | 193 ----------------- app/assets/javascripts/merge_request_widget.js.es6 | 240 +++++++++++++++++++++ app/assets/stylesheets/pages/merge_requests.scss | 4 + .../projects/merge_requests_controller.rb | 31 ++- app/models/environment.rb | 8 + app/models/merge_request.rb | 9 + app/models/repository.rb | 8 + .../merge_requests/widget/_heading.html.haml | 16 +- .../projects/merge_requests/widget/_show.html.haml | 2 +- .../merge_requests/widget_deployments_spec.rb | 26 +++ spec/javascripts/merge_request_widget_spec.js | 27 ++- spec/models/environment_spec.rb | 17 ++ spec/models/repository_spec.rb | 23 ++ .../merge_requests/_heading.html.haml_spec.rb | 28 --- 15 files changed, 391 insertions(+), 242 deletions(-) delete mode 100644 app/assets/javascripts/merge_request_widget.js create mode 100644 app/assets/javascripts/merge_request_widget.js.es6 create mode 100644 spec/features/merge_requests/widget_deployments_spec.rb delete mode 100644 spec/views/projects/merge_requests/_heading.html.haml_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 99bbd99726d..348ac41a534 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -50,6 +50,7 @@ v 8.13.0 (unreleased) - Add new issue button to each list on Issues Board - Added soft wrap button to repository file/blob editor - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) + - Show the time ago a merge request was deployed to an environment - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js deleted file mode 100644 index 7bbcdf59838..00000000000 --- a/app/assets/javascripts/merge_request_widget.js +++ /dev/null @@ -1,193 +0,0 @@ -(function() { - var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; - - this.MergeRequestWidget = (function() { - function MergeRequestWidget(opts) { - // Initialize MergeRequestWidget behavior - // - // check_enable - Boolean, whether to check automerge status - // merge_check_url - String, URL to use to check automerge status - // ci_status_url - String, URL to use to check CI status - // - this.opts = opts; - $('#modal_merge_info').modal({ - show: false - }); - this.firstCICheck = true; - this.readyForCICheck = false; - this.cancel = false; - clearInterval(this.fetchBuildStatusInterval); - this.clearEventListeners(); - this.addEventListeners(); - this.getCIStatus(false); - this.pollCIStatus(); - notifyPermissions(); - } - - MergeRequestWidget.prototype.clearEventListeners = function() { - return $(document).off('page:change.merge_request'); - }; - - MergeRequestWidget.prototype.cancelPolling = function() { - return this.cancel = true; - }; - - MergeRequestWidget.prototype.addEventListeners = function() { - var allowedPages; - allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; - return $(document).on('page:change.merge_request', (function(_this) { - return function() { - var page; - page = $('body').data('page').split(':').last(); - if (allowedPages.indexOf(page) < 0) { - clearInterval(_this.fetchBuildStatusInterval); - _this.cancelPolling(); - return _this.clearEventListeners(); - } - }; - })(this)); - }; - - MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) { - if (deleteSourceBranch == null) { - deleteSourceBranch = false; - } - return $.ajax({ - type: 'GET', - url: $('.merge-request').data('url'), - success: (function(_this) { - return function(data) { - var callback, urlSuffix; - if (data.state === "merged") { - urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : ''; - return window.location.href = window.location.pathname + urlSuffix; - } else if (data.merge_error) { - return $('.mr-widget-body').html("

      " + data.merge_error + "

      "); - } else { - callback = function() { - return merge_request_widget.mergeInProgress(deleteSourceBranch); - }; - return setTimeout(callback, 2000); - } - }; - })(this), - dataType: 'json' - }); - }; - - MergeRequestWidget.prototype.getMergeStatus = function() { - return $.get(this.opts.merge_check_url, function(data) { - return $('.mr-state-widget').replaceWith(data); - }); - }; - - MergeRequestWidget.prototype.ciLabelForStatus = function(status) { - switch (status) { - case 'success': - return 'passed'; - case 'success_with_warnings': - return 'passed with warnings'; - default: - return status; - } - }; - - MergeRequestWidget.prototype.pollCIStatus = function() { - return this.fetchBuildStatusInterval = setInterval(((function(_this) { - return function() { - if (!_this.readyForCICheck) { - return; - } - _this.getCIStatus(true); - return _this.readyForCICheck = false; - }; - })(this)), 10000); - }; - - MergeRequestWidget.prototype.getCIStatus = function(showNotification) { - var _this; - _this = this; - $('.ci-widget-fetching').show(); - return $.getJSON(this.opts.ci_status_url, (function(_this) { - return function(data) { - var message, status, title; - if (_this.cancel) { - return; - } - _this.readyForCICheck = true; - if (data.status === '') { - return; - } - if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { - _this.opts.ci_status = data.status; - _this.showCIStatus(data.status); - if (data.coverage) { - _this.showCICoverage(data.coverage); - } - // The first check should only update the UI, a notification - // should only be displayed on status changes - if (showNotification && !_this.firstCICheck) { - status = _this.ciLabelForStatus(data.status); - if (status === "preparing") { - title = _this.opts.ci_title.preparing; - status = status.charAt(0).toUpperCase() + status.slice(1); - message = _this.opts.ci_message.preparing.replace('{{status}}', status); - } else { - title = _this.opts.ci_title.normal; - message = _this.opts.ci_message.normal.replace('{{status}}', status); - } - title = title.replace('{{status}}', status); - message = message.replace('{{sha}}', data.sha); - message = message.replace('{{title}}', data.title); - notify(title, message, _this.opts.gitlab_icon, function() { - this.close(); - return Turbolinks.visit(_this.opts.builds_path); - }); - } - return _this.firstCICheck = false; - } - }; - })(this)); - }; - - MergeRequestWidget.prototype.showCIStatus = function(state) { - var allowed_states; - if (state == null) { - return; - } - $('.ci_widget').hide(); - allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]; - if (indexOf.call(allowed_states, state) >= 0) { - $('.ci_widget.ci-' + state).show(); - switch (state) { - case "failed": - case "canceled": - case "not_found": - return this.setMergeButtonClass('btn-danger'); - case "running": - return this.setMergeButtonClass('btn-warning'); - case "success": - case "success_with_warnings": - return this.setMergeButtonClass('btn-create'); - } - } else { - $('.ci_widget.ci-error').show(); - return this.setMergeButtonClass('btn-danger'); - } - }; - - MergeRequestWidget.prototype.showCICoverage = function(coverage) { - var text; - text = 'Coverage ' + coverage + '%'; - return $('.ci_widget:visible .ci-coverage').text(text); - }; - - MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) { - return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-warning btn-create').addClass(css_class); - }; - - return MergeRequestWidget; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 new file mode 100644 index 00000000000..7c727cf214f --- /dev/null +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -0,0 +1,240 @@ + ((global) => { + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + const DEPLOYMENT_TEMPLATE = `
      +
      + <%= ci_success_icon %> + + Deployed to + + <%- name %> + + + <%- deployed_at %> + + + + View on <%- external_url_formatted %> + + +
      +
      `; + + global.MergeRequestWidget = (function() { + function MergeRequestWidget(opts) { + // Initialize MergeRequestWidget behavior + // + // check_enable - Boolean, whether to check automerge status + // merge_check_url - String, URL to use to check automerge status + // ci_status_url - String, URL to use to check CI status + // + this.opts = opts; + this.$widgetBody = $('.mr-widget-body'); + $('#modal_merge_info').modal({ + show: false + }); + this.firstCICheck = true; + this.readyForCICheck = false; + this.cancel = false; + clearInterval(this.fetchBuildStatusInterval); + this.clearEventListeners(); + this.addEventListeners(); + this.getCIStatus(false); + this.retrieveSuccessIcon(); + this.pollCIStatus(); + notifyPermissions(); + } + + MergeRequestWidget.prototype.clearEventListeners = function() { + return $(document).off('page:change.merge_request'); + }; + + MergeRequestWidget.prototype.cancelPolling = function() { + return this.cancel = true; + }; + + MergeRequestWidget.prototype.addEventListeners = function() { + var allowedPages; + allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; + return $(document).on('page:change.merge_request', (function(_this) { + return function() { + var page; + page = $('body').data('page').split(':').last(); + if (allowedPages.indexOf(page) < 0) { + clearInterval(_this.fetchBuildStatusInterval); + _this.cancelPolling(); + return _this.clearEventListeners(); + } + }; + })(this)); + }; + + MergeRequestWidget.prototype.retrieveSuccessIcon = function() { + const $ciSuccessIcon = $('.js-success-icon'); + this.$ciSuccessIcon = $ciSuccessIcon.html(); + $ciSuccessIcon.remove(); + } + + MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) { + if (deleteSourceBranch == null) { + deleteSourceBranch = false; + } + return $.ajax({ + type: 'GET', + url: $('.merge-request').data('url'), + success: (function(_this) { + return function(data) { + var callback, urlSuffix; + if (data.state === "merged") { + urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : ''; + return window.location.href = window.location.pathname + urlSuffix; + } else if (data.merge_error) { + return this.$widgetBody.html("

      " + data.merge_error + "

      "); + } else { + callback = function() { + return merge_request_widget.mergeInProgress(deleteSourceBranch); + }; + return setTimeout(callback, 2000); + } + }; + })(this), + dataType: 'json' + }); + }; + + MergeRequestWidget.prototype.getMergeStatus = function() { + return $.get(this.opts.merge_check_url, function(data) { + return $('.mr-state-widget').replaceWith(data); + }); + }; + + MergeRequestWidget.prototype.ciLabelForStatus = function(status) { + switch (status) { + case 'success': + return 'passed'; + case 'success_with_warnings': + return 'passed with warnings'; + default: + return status; + } + }; + + MergeRequestWidget.prototype.pollCIStatus = function() { + return this.fetchBuildStatusInterval = setInterval(((function(_this) { + return function() { + if (!_this.readyForCICheck) { + return; + } + _this.getCIStatus(true); + return _this.readyForCICheck = false; + }; + })(this)), 10000); + }; + + MergeRequestWidget.prototype.getCIStatus = function(showNotification) { + var _this; + _this = this; + $('.ci-widget-fetching').show(); + return $.getJSON(this.opts.ci_status_url, (function(_this) { + return function(data) { + var message, status, title; + if (_this.cancel) { + return; + } + _this.readyForCICheck = true; + if (data.status === '') { + return; + } + if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); + if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { + _this.opts.ci_status = data.status; + _this.showCIStatus(data.status); + if (data.coverage) { + _this.showCICoverage(data.coverage); + } + // The first check should only update the UI, a notification + // should only be displayed on status changes + if (showNotification && !_this.firstCICheck) { + status = _this.ciLabelForStatus(data.status); + if (status === "preparing") { + title = _this.opts.ci_title.preparing; + status = status.charAt(0).toUpperCase() + status.slice(1); + message = _this.opts.ci_message.preparing.replace('{{status}}', status); + } else { + title = _this.opts.ci_title.normal; + message = _this.opts.ci_message.normal.replace('{{status}}', status); + } + title = title.replace('{{status}}', status); + message = message.replace('{{sha}}', data.sha); + message = message.replace('{{title}}', data.title); + notify(title, message, _this.opts.gitlab_icon, function() { + this.close(); + return Turbolinks.visit(_this.opts.builds_path); + }); + } + return _this.firstCICheck = false; + } + }; + })(this)); + }; + + MergeRequestWidget.prototype.renderEnvironments = function(environments) { + for (let i = 0; i < environments.length; i++) { + const environment = environments[i]; + if ($(`.mr-state-widget #${ environment.id }`).length) return; + const $template = $(DEPLOYMENT_TEMPLATE); + if (!environment.external_url) $('.js-environment-link', $template).remove(); + if (environment.deployed_at) { + environment.deployed_at = $.timeago(environment.deployed_at) + '.'; + } else { + $('.js-environment-timeago', $template).remove(); + environment.name += '.'; + } + environment.ci_success_icon = this.$ciSuccessIcon; + const templateString = _.unescape($template[0].outerHTML); + const template = _.template(templateString)(environment) + this.$widgetBody.before(template); + } + }; + + MergeRequestWidget.prototype.showCIStatus = function(state) { + var allowed_states; + if (state == null) { + return; + } + $('.ci_widget').hide(); + allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]; + if (indexOf.call(allowed_states, state) >= 0) { + $('.ci_widget.ci-' + state).show(); + switch (state) { + case "failed": + case "canceled": + case "not_found": + return this.setMergeButtonClass('btn-danger'); + case "running": + return this.setMergeButtonClass('btn-warning'); + case "success": + case "success_with_warnings": + return this.setMergeButtonClass('btn-create'); + } + } else { + $('.ci_widget.ci-error').show(); + return this.setMergeButtonClass('btn-danger'); + } + }; + + MergeRequestWidget.prototype.showCICoverage = function(coverage) { + var text; + text = 'Coverage ' + coverage + '%'; + return $('.ci_widget:visible .ci-coverage').text(text); + }; + + MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) { + return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-warning btn-create').addClass(css_class); + }; + + return MergeRequestWidget; + + })(); + + })(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7cf69c56d15..96d5547154d 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -121,6 +121,10 @@ color: #5c5d5e; } + .js-deployment-link { + display: inline-block; + } + .mr-widget-body { h4 { font-weight: 600; diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 869d96b86f4..28225fbb762 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -393,11 +393,40 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + environments = @merge_request.environments + deployments = @merge_request.deployments + + if environments.present? + environments = environments.select { |e| can?(current_user, :read_environment, e) }.map do |environment| + project = environment.project + deployment = deployments.find { |d| d.environment == environment } + + environment = { + name: environment.name, + id: environment.id, + url: namespace_project_environment_path(project.namespace, project, environment), + external_url: environment.external_url, + deployed_at: deployment ? deployment.created_at : nil + } + + if environment[:external_url] + environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') + end + + if environment[:deployed_at] + environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) + end + + environment + end + end + response = { title: merge_request.title, sha: merge_request.diff_head_commit.short_id, status: status, - coverage: coverage + coverage: coverage, + environments: environments } render json: response diff --git a/app/models/environment.rb b/app/models/environment.rb index f0f3ee23223..1c7d06906f3 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -48,6 +48,14 @@ class Environment < ActiveRecord::Base self.name == "production" end + def deployment_id_for(commit) + ref = project.repository.ref_name_for_sha(ref_path, commit.sha) + + return nil unless ref + + ref.split('/').last.to_i + end + def ref_path "refs/environments/#{Shellwords.shellescape(name)}" end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a743bf313ae..ec8c09f83db 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -685,6 +685,15 @@ class MergeRequest < ActiveRecord::Base !pipeline || pipeline.success? end + def deployments + deployment_ids = + environments.map do |environment| + environment.deployment_id_for(diff_head_commit) + end.compact + + Deployments.find(deployment_ids) + end + def environments return [] unless diff_head_commit diff --git a/app/models/repository.rb b/app/models/repository.rb index 608c99eed46..37833cf004f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -719,6 +719,14 @@ class Repository end end + def ref_name_for_sha(environment_ref_path, sha) + args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{environment_ref_path} --contains #{sha}) + + # Not found -> ["", 0] + # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0] + Gitlab::Popen.popen(args, path_to_repo).first.split.last + end + def refs_contains_sha(ref_type, sha) args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha}) names = Gitlab::Popen.popen(args, path_to_repo).first diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 5b7f83c344f..cda8f0b7de6 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -44,17 +44,5 @@ = icon("times-circle") Could not connect to the CI server. Please check your settings and try again. -- @merge_request.environments.sort_by(&:name).each do |environment| - - if can?(current_user, :read_environment, environment) - .mr-widget-heading - .ci_widget.ci-success - = ci_icon_for_status("success") - %span - Deployed to - = succeed '.' do - = link_to environment.name, environment_path(environment), class: 'environment' - - external_url = environment.external_url - - if external_url - = link_to external_url, target: '_blank' do - %span.hidden-xs View on #{external_url.gsub(/\A.*?:\/\//, '')} - = icon('external-link', right: true) + .js-success-icon.hidden + = ci_icon_for_status('success') diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index ea618263a4a..856ec1e0bee 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -33,4 +33,4 @@ merge_request_widget.clearEventListeners(); } - merge_request_widget = new MergeRequestWidget(opts); + merge_request_widget = new window.gl.MergeRequestWidget(opts); diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb new file mode 100644 index 00000000000..8e23ec50d4a --- /dev/null +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +feature 'Widget Deployments Header', feature: true, js: true do + include WaitForAjax + + describe 'when deployed to an environment' do + let(:project) { merge_request.target_project } + let(:merge_request) { create(:merge_request, :merged) } + let(:environment) { create(:environment, project: project) } + let!(:deployment) do + create(:deployment, environment: environment, sha: project.commit('master').id) + end + + before do + login_as :admin + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'displays that the environment is deployed' do + wait_for_ajax + + expect(page).to have_content("Deployed to #{environment.name}") + expect(find('.ci_widget > span > span')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium)) + end + end +end diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 17b32914ec3..75ef10939de 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -1,5 +1,5 @@ - /*= require merge_request_widget */ +/*= require lib/utils/jquery.timeago.js */ (function() { describe('MergeRequestWidget', function() { @@ -20,7 +20,7 @@ gitlab_icon: "gitlab_logo.png", builds_path: "http://sampledomain.local/sampleBuildsPath" }; - this["class"] = new MergeRequestWidget(this.opts); + this["class"] = new window.gl.MergeRequestWidget(this.opts); return this.ciStatusData = { "title": "Sample MR title", "sha": "12a34bc5", @@ -30,7 +30,7 @@ }); return describe('getCIStatus', function() { beforeEach(function() { - return spyOn(jQuery, 'getJSON').and.callFake((function(_this) { + spyOn(jQuery, 'getJSON').and.callFake((function(_this) { return function(req, cb) { return cb(_this.ciStatusData); }; @@ -61,13 +61,30 @@ this["class"].getCIStatus(false); return expect(spy).not.toHaveBeenCalled(); }); - return it('should not display a notification on the first check after the widget has been created', function() { + it('should not display a notification on the first check after the widget has been created', function() { var spy; spy = spyOn(window, 'notify'); - this["class"] = new MergeRequestWidget(this.opts); + this["class"] = new window.gl.MergeRequestWidget(this.opts); this["class"].getCIStatus(true); return expect(spy).not.toHaveBeenCalled(); }); + it('should call renderEnvironments when the environments property is set', function() { + this.ciStatusData.environments = [{ + created_at: '2016-09-12T13:38:30.636Z', + environment_id: 1, + environment_name: 'env1', + external_url: 'https://test-url.com', + external_url_formatted: 'test-url.com' + }]; + var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); + this['class'].getCIStatus(false); + expect(spy).toHaveBeenCalledWith(this.ciStatusData.environments); + }); + it('should not call renderEnvironments when the environments property is not set', function() { + var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); + this['class'].getCIStatus(false); + expect(spy).not.toHaveBeenCalled(); + }); }); }); diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 6b1867a44e1..fb9629ac47a 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -64,6 +64,23 @@ describe Environment, models: true do end end + describe '#deployment_id_for' do + let(:project) { create(:project) } + let!(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } + let!(:deployment1) { create(:deployment, environment: environment, ref: commit.id) } + let(:head_commit) { project.commit } + let(:commit) { project.commit.parent } + + it 'returns deployment id for the environment' do + expect(environment.deployment_id_for(commit)).to eq deployment1.id + end + + it 'return nil when no deployment is found' do + expect(environment.deployment_id_for(head_commit)).to eq nil + end + end + describe '#environment_type' do subject { environment.environment_type } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4b80efbe12b..f977cf73673 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -7,15 +7,18 @@ describe Repository, models: true do let(:project) { create(:project) } let(:repository) { project.repository } let(:user) { create(:user) } + let(:commit_options) do author = repository.user_to_committer(user) { message: 'Test message', committer: author, author: author } end + let(:merge_commit) do merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) merge_commit_id = repository.merge(user, merge_request, commit_options) repository.commit(merge_commit_id) end + let(:author_email) { FFaker::Internet.email } # I have to remove periods from the end of the name @@ -90,6 +93,26 @@ describe Repository, models: true do end end + describe '#ref_name_for_sha' do + context 'ref found' do + it 'returns the ref' do + allow_any_instance_of(Gitlab::Popen).to receive(:popen). + and_return(["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]) + + expect(repository.ref_name_for_sha('bla', '0' * 40)).to eq 'refs/environments/production/77' + end + end + + context 'ref not found' do + it 'returns nil' do + allow_any_instance_of(Gitlab::Popen).to receive(:popen). + and_return(["", 0]) + + expect(repository.ref_name_for_sha('bla', '0' * 40)).to eq nil + end + end + end + describe '#last_commit_for_path' do subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } diff --git a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb deleted file mode 100644 index 86980f59cd8..00000000000 --- a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper' - -describe 'projects/merge_requests/widget/_heading' do - include Devise::Test::ControllerHelpers - - context 'when released to an environment' do - let(:project) { merge_request.target_project } - let(:merge_request) { create(:merge_request, :merged) } - let(:environment) { create(:environment, project: project) } - let!(:deployment) do - create(:deployment, environment: environment, sha: project.commit('master').id) - end - - before do - assign(:merge_request, merge_request) - assign(:project, project) - - allow(view).to receive(:can?).and_return(true) - - render - end - - it 'displays that the environment is deployed' do - expect(rendered).to match("Deployed to") - expect(rendered).to match("#{environment.name}") - end - end -end -- cgit v1.2.1 From 8a1064d76252e16b64b6e4f03d819b6dc20e1d6d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 4 Oct 2016 18:50:25 +0200 Subject: Fix indenting error in HAML --- app/models/merge_request.rb | 2 +- app/views/projects/merge_requests/widget/_heading.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ec8c09f83db..0e427378bac 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -691,7 +691,7 @@ class MergeRequest < ActiveRecord::Base environment.deployment_id_for(diff_head_commit) end.compact - Deployments.find(deployment_ids) + Deployment.find(deployment_ids) end def environments diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index cda8f0b7de6..a82c846baa7 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -44,5 +44,5 @@ = icon("times-circle") Could not connect to the CI server. Please check your settings and try again. - .js-success-icon.hidden - = ci_icon_for_status('success') +.js-success-icon.hidden + = ci_icon_for_status('success') -- cgit v1.2.1 From fa58068b2894b900d4b2519825411e0710557fc6 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 5 Oct 2016 13:52:15 +0200 Subject: Refactor ci_status on MergeRequestController --- app/assets/stylesheets/pages/merge_requests.scss | 4 +- .../projects/merge_requests_controller.rb | 45 ++++++++++------------ app/models/environment.rb | 5 ++- app/models/merge_request.rb | 22 ++++------- app/models/repository.rb | 4 +- 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 96d5547154d..6a0fae8a3f9 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -122,8 +122,8 @@ } .js-deployment-link { - display: inline-block; - } + display: inline-block; + } .mr-widget-body { h4 { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 28225fbb762..00043c5a4c0 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -393,33 +393,28 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - environments = @merge_request.environments - deployments = @merge_request.deployments - - if environments.present? - environments = environments.select { |e| can?(current_user, :read_environment, e) }.map do |environment| - project = environment.project - deployment = deployments.find { |d| d.environment == environment } - - environment = { - name: environment.name, - id: environment.id, - url: namespace_project_environment_path(project.namespace, project, environment), - external_url: environment.external_url, - deployed_at: deployment ? deployment.created_at : nil - } - - if environment[:external_url] - environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') - end - - if environment[:deployed_at] - environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) - end + environments = @merge_request.environments.map do |environment| + next unless can?(current_user, :read_environment, environment) + + deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + environment = { + name: environment.name, + id: environment.id, + url: namespace_project_environment_path(@project.namespace, @project, environment), + external_url: environment.external_url, + deployed_at: deployment ? deployment.created_at : nil + } + + if environment[:external_url] + environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') + end - environment + if environment[:deployed_at] + environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) end - end + + environment + end.compact response = { title: merge_request.title, diff --git a/app/models/environment.rb b/app/models/environment.rb index 1c7d06906f3..c6cae81ce6a 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -48,12 +48,13 @@ class Environment < ActiveRecord::Base self.name == "production" end - def deployment_id_for(commit) + def first_deployment_for(commit) ref = project.repository.ref_name_for_sha(ref_path, commit.sha) return nil unless ref - ref.split('/').last.to_i + deployment_id = ref.split('/').last.to_i + deployments.find(deployment_id) end def ref_path diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 0e427378bac..5ccfe11a2a2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -685,24 +685,18 @@ class MergeRequest < ActiveRecord::Base !pipeline || pipeline.success? end - def deployments - deployment_ids = - environments.map do |environment| - environment.deployment_id_for(diff_head_commit) - end.compact - - Deployment.find(deployment_ids) - end - def environments return [] unless diff_head_commit - environments = source_project.environments_for( - source_branch, diff_head_commit) - environments += target_project.environments_for( - target_branch, diff_head_commit, with_tags: true) + @environments ||= + begin + environments = source_project.environments_for( + source_branch, diff_head_commit) + environments += target_project.environments_for( + target_branch, diff_head_commit, with_tags: true) - environments.uniq + environments.uniq + end end def state_human_name diff --git a/app/models/repository.rb b/app/models/repository.rb index 37833cf004f..72e473871fa 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -719,8 +719,8 @@ class Repository end end - def ref_name_for_sha(environment_ref_path, sha) - args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{environment_ref_path} --contains #{sha}) + def ref_name_for_sha(ref_path, sha) + args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha}) # Not found -> ["", 0] # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0] -- cgit v1.2.1 From 58368fbc53bfe7c2a9b425626819eae576afff09 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 7 Oct 2016 23:10:06 +0100 Subject: Moved ci_status environments logic to new action ci_envrionments_status and set up frontend polling --- app/assets/javascripts/merge_request_widget.js.es6 | 53 +++++++++++++------ .../projects/merge_requests_controller.rb | 38 +++++++------- .../projects/merge_requests/widget/_show.html.haml | 1 + config/routes.rb | 5 -- config/routes/project.rb | 1 + spec/javascripts/merge_request_widget_spec.js | 60 +++++++++++++--------- 6 files changed, 96 insertions(+), 62 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 7c727cf214f..8354f7ab8a4 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -35,13 +35,17 @@ }); this.firstCICheck = true; this.readyForCICheck = false; + this.readyForCIEnvironmentCheck = false; this.cancel = false; clearInterval(this.fetchBuildStatusInterval); + clearInterval(this.fetchBuildEnvironmentStatusInterval); this.clearEventListeners(); this.addEventListeners(); this.getCIStatus(false); + this.getCIEnvironmentsStatus(); this.retrieveSuccessIcon(); this.pollCIStatus(); + this.pollCIEnvironmentsStatus(); notifyPermissions(); } @@ -62,6 +66,7 @@ page = $('body').data('page').split(':').last(); if (allowedPages.indexOf(page) < 0) { clearInterval(_this.fetchBuildStatusInterval); + clearInterval(_this.fetchBuildEnvironmentStatusInterval); _this.cancelPolling(); return _this.clearEventListeners(); } @@ -178,23 +183,39 @@ })(this)); }; + MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() { + this.fetchBuildEnvironmentStatusInterval = setInterval(() => { + if (!this.readyForCIEnvironmentCheck) return; + this.getCIEnvironmentsStatus(); + this.readyForCIEnvironmentCheck = false; + }, 300000); + }; + + MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() { + $.getJSON(this.opts.ci_environments_status_url, (environments) => { + if (this.cancel) return; + this.readyForCIEnvironmentCheck = true; + if (environments && environments.length) this.renderEnvironments(environments); + }); + }; + MergeRequestWidget.prototype.renderEnvironments = function(environments) { - for (let i = 0; i < environments.length; i++) { - const environment = environments[i]; - if ($(`.mr-state-widget #${ environment.id }`).length) return; - const $template = $(DEPLOYMENT_TEMPLATE); - if (!environment.external_url) $('.js-environment-link', $template).remove(); - if (environment.deployed_at) { - environment.deployed_at = $.timeago(environment.deployed_at) + '.'; - } else { - $('.js-environment-timeago', $template).remove(); - environment.name += '.'; - } - environment.ci_success_icon = this.$ciSuccessIcon; - const templateString = _.unescape($template[0].outerHTML); - const template = _.template(templateString)(environment) - this.$widgetBody.before(template); - } + for (let i = 0; i < environments.length; i++) { + const environment = environments[i]; + if ($(`.mr-state-widget #${ environment.id }`).length) return; + const $template = $(DEPLOYMENT_TEMPLATE); + if (!environment.external_url) $('.js-environment-link', $template).remove(); + if (environment.deployed_at) { + environment.deployed_at = $.timeago(environment.deployed_at) + '.'; + } else { + $('.js-environment-timeago', $template).remove(); + environment.name += '.'; + } + environment.ci_success_icon = this.$ciSuccessIcon; + const templateString = _.unescape($template[0].outerHTML); + const template = _.template(templateString)(environment) + this.$widgetBody.before(template); + } }; MergeRequestWidget.prototype.showCIStatus = function(state) { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 00043c5a4c0..e17d560138f 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, - :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues + :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] @@ -393,11 +393,23 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - environments = @merge_request.environments.map do |environment| + response = { + title: merge_request.title, + sha: merge_request.diff_head_commit.short_id, + status: status, + coverage: coverage + } + + render json: response + end + + def ci_environments_status + render json: @merge_request.environments.map do |environment| next unless can?(current_user, :read_environment, environment) deployment = environment.first_deployment_for(@merge_request.diff_head_commit) - environment = { + + environment_data = { name: environment.name, id: environment.id, url: namespace_project_environment_path(@project.namespace, @project, environment), @@ -405,26 +417,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController deployed_at: deployment ? deployment.created_at : nil } - if environment[:external_url] - environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') + if environment_data[:external_url] + environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') end - if environment[:deployed_at] - environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) + if environment_data[:deployed_at] + environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) end - environment + environment_data end.compact - - response = { - title: merge_request.title, - sha: merge_request.diff_head_commit.short_id, - status: status, - coverage: coverage, - environments: environments - } - - render json: response end protected diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 856ec1e0bee..608fdf1c5f5 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -12,6 +12,7 @@ merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", check_enable: #{@merge_request.unchecked? ? "true" : "false"}, ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", + ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}", ci_message: { diff --git a/config/routes.rb b/config/routes.rb index 83c3a42c19f..68dc84d9c9e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,9 +83,4 @@ Rails.application.routes.draw do draw :group draw :user draw :project - - # Get all keys of user - get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - - root to: "root#index" end diff --git a/config/routes/project.rb b/config/routes/project.rb index f9d58f5d5b2..200922b74db 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -273,6 +273,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: post :merge post :cancel_merge_when_build_succeeds get :ci_status + get :ci_environments_status post :toggle_subscription post :remove_wip get :diff_for_path diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 75ef10939de..7b20572742c 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -8,6 +8,7 @@ window.notify = function() {}; this.opts = { ci_status_url: "http://sampledomain.local/ci/getstatus", + ci_environments_status_url: "http://sampledomain.local/ci/getenvironmentsstatus", ci_status: "", ci_message: { normal: "Build {{status}} for \"{{title}}\"", @@ -21,15 +22,45 @@ builds_path: "http://sampledomain.local/sampleBuildsPath" }; this["class"] = new window.gl.MergeRequestWidget(this.opts); - return this.ciStatusData = { - "title": "Sample MR title", - "sha": "12a34bc5", - "status": "success", - "coverage": 98 - }; }); + + describe('getCIEnvironmentsStatus', function() { + beforeEach(function() { + this.ciEnvironmentsStatusData = { + created_at: '2016-09-12T13:38:30.636Z', + environment_id: 1, + environment_name: 'env1', + external_url: 'https://test-url.com', + external_url_formatted: 'test-url.com' + }; + + spyOn(jQuery, 'getJSON').and.callFake((req, cb) => { + cb(this.ciEnvironmentsStatusData); + }); + }); + + it('should call renderEnvironments when the environments property is set', function() { + const spy = spyOn(this.class, 'renderEnvironments').and.stub(); + this.class.getCIEnvironmentsStatus(); + expect(spy).toHaveBeenCalledWith(this.ciEnvironmentsStatusData); + }); + + it('should not call renderEnvironments when the environments property is not set', function() { + const spy = spyOn(this.class, 'renderEnvironments').and.stub(); + this.class.getCIEnvironmentsStatus(); + expect(spy).not.toHaveBeenCalled(); + }); + }); + return describe('getCIStatus', function() { beforeEach(function() { + this.ciStatusData = { + "title": "Sample MR title", + "sha": "12a34bc5", + "status": "success", + "coverage": 98 + }; + spyOn(jQuery, 'getJSON').and.callFake((function(_this) { return function(req, cb) { return cb(_this.ciStatusData); @@ -68,23 +99,6 @@ this["class"].getCIStatus(true); return expect(spy).not.toHaveBeenCalled(); }); - it('should call renderEnvironments when the environments property is set', function() { - this.ciStatusData.environments = [{ - created_at: '2016-09-12T13:38:30.636Z', - environment_id: 1, - environment_name: 'env1', - external_url: 'https://test-url.com', - external_url_formatted: 'test-url.com' - }]; - var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); - this['class'].getCIStatus(false); - expect(spy).toHaveBeenCalledWith(this.ciStatusData.environments); - }); - it('should not call renderEnvironments when the environments property is not set', function() { - var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); - this['class'].getCIStatus(false); - expect(spy).not.toHaveBeenCalled(); - }); }); }); -- cgit v1.2.1 From 88b03bb542a8480d61c260a9dc3769ab791995e5 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 10 Oct 2016 20:38:48 +0200 Subject: Rename method in test --- .../projects/merge_requests_controller.rb | 41 ++++++++++++---------- config/routes.rb | 5 +++ spec/models/environment_spec.rb | 6 ++-- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e17d560138f..5f7f46cf566 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -404,29 +404,34 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_environments_status - render json: @merge_request.environments.map do |environment| - next unless can?(current_user, :read_environment, environment) - - deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + environments = + begin + @merge_request.environments.map do |environment| + next unless can?(current_user, :read_environment, environment) + + deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + + environment_data = { + name: environment.name, + id: environment.id, + url: namespace_project_environment_path(@project.namespace, @project, environment), + external_url: environment.external_url, + deployed_at: deployment ? deployment.created_at : nil + } - environment_data = { - name: environment.name, - id: environment.id, - url: namespace_project_environment_path(@project.namespace, @project, environment), - external_url: environment.external_url, - deployed_at: deployment ? deployment.created_at : nil - } + if environment_data[:external_url] + environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') + end - if environment_data[:external_url] - environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') - end + if environment_data[:deployed_at] + environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) + end - if environment_data[:deployed_at] - environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) + environment_data + end.compact end - environment_data - end.compact + render json: environments end protected diff --git a/config/routes.rb b/config/routes.rb index 68dc84d9c9e..83c3a42c19f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,4 +83,9 @@ Rails.application.routes.draw do draw :group draw :user draw :project + + # Get all keys of user + get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } + + root to: "root#index" end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index fb9629ac47a..e172ee8e590 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -64,7 +64,7 @@ describe Environment, models: true do end end - describe '#deployment_id_for' do + describe '#first_deployment_for' do let(:project) { create(:project) } let!(:environment) { create(:environment, project: project) } let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } @@ -73,11 +73,11 @@ describe Environment, models: true do let(:commit) { project.commit.parent } it 'returns deployment id for the environment' do - expect(environment.deployment_id_for(commit)).to eq deployment1.id + expect(environment.first_deployment_for(commit)).to eq deployment1 end it 'return nil when no deployment is found' do - expect(environment.deployment_id_for(head_commit)).to eq nil + expect(environment.first_deployment_for(head_commit)).to eq nil end end -- cgit v1.2.1 From 4b40027b50a2be4bad76b0a4a6a5e92c0de14255 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 11 Oct 2016 18:48:43 +0100 Subject: Fixed conflict and corrected teaspoon test --- spec/javascripts/merge_request_widget_spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 7b20572742c..c9175e2b704 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -26,13 +26,13 @@ describe('getCIEnvironmentsStatus', function() { beforeEach(function() { - this.ciEnvironmentsStatusData = { + this.ciEnvironmentsStatusData = [{ created_at: '2016-09-12T13:38:30.636Z', environment_id: 1, environment_name: 'env1', external_url: 'https://test-url.com', external_url_formatted: 'test-url.com' - }; + }]; spyOn(jQuery, 'getJSON').and.callFake((req, cb) => { cb(this.ciEnvironmentsStatusData); @@ -46,6 +46,7 @@ }); it('should not call renderEnvironments when the environments property is not set', function() { + this.ciEnvironmentsStatusData = null; const spy = spyOn(this.class, 'renderEnvironments').and.stub(); this.class.getCIEnvironmentsStatus(); expect(spy).not.toHaveBeenCalled(); -- cgit v1.2.1 From 35e2315a66e34f290230720cfa74240fd5532970 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 12 Oct 2016 15:21:01 +0200 Subject: Minor style improvement --- .../projects/merge_requests_controller.rb | 20 ++++++-------------- app/models/deployment.rb | 4 ++++ app/models/environment.rb | 8 +++++++- db/schema.rb | 12 +++++++++++- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5f7f46cf566..5c802798028 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -404,30 +404,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_environments_status - environments = + environments = begin @merge_request.environments.map do |environment| next unless can?(current_user, :read_environment, environment) deployment = environment.first_deployment_for(@merge_request.diff_head_commit) - environment_data = { - name: environment.name, + { id: environment.id, + name: environment.name, url: namespace_project_environment_path(@project.namespace, @project, environment), external_url: environment.external_url, - deployed_at: deployment ? deployment.created_at : nil + external_url_formatted: environment.formatted_external_url, + deployed_at: deployment.try(:created_at), + deployed_at_formatted: deployment.try(:formatted_deployment_time) } - - if environment_data[:external_url] - environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') - end - - if environment_data[:deployed_at] - environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) - end - - environment_data end.compact end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index f63cc179b9e..3d9902d496e 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -84,6 +84,10 @@ class Deployment < ActiveRecord::Base take end + def formatted_deployment_time + created_at.to_time.in_time_zone.to_s(:medium) + end + private def ref_path diff --git a/app/models/environment.rb b/app/models/environment.rb index c6cae81ce6a..d970bc0a005 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -53,11 +53,17 @@ class Environment < ActiveRecord::Base return nil unless ref - deployment_id = ref.split('/').last.to_i + deployment_id = ref.split('/').last deployments.find(deployment_id) end def ref_path "refs/environments/#{Shellwords.shellescape(name)}" end + + def formatted_external_url + return nil unless external_url + + external_url.gsub(/\A.*?:\/\//, '') + end end diff --git a/db/schema.rb b/db/schema.rb index a362fd8f228..0bb03dfe4bc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -446,6 +446,17 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree + create_table "integrations", force: :cascade do |t| + t.integer "project_id" + t.string "name" + t.string "external_token" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "integrations", ["external_token"], name: "index_integrations_on_external_token", unique: true, using: :btree + add_index "integrations", ["project_id"], name: "index_integrations_on_project_id", using: :btree + create_table "issue_metrics", force: :cascade do |t| t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" @@ -613,7 +624,6 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.datetime "updated_at", null: false end - add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree create_table "merge_requests", force: :cascade do |t| -- cgit v1.2.1 From 6e509ae3b60eb0fbe4bddb7dbb42beaf47817f3b Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 13 Oct 2016 13:00:06 +0100 Subject: Added safety check for formatted values --- app/assets/javascripts/merge_request_widget.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 8354f7ab8a4..fcadc4bc515 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -204,8 +204,8 @@ const environment = environments[i]; if ($(`.mr-state-widget #${ environment.id }`).length) return; const $template = $(DEPLOYMENT_TEMPLATE); - if (!environment.external_url) $('.js-environment-link', $template).remove(); - if (environment.deployed_at) { + if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); + if (environment.deployed_at && environment.deployed_at_formatted) { environment.deployed_at = $.timeago(environment.deployed_at) + '.'; } else { $('.js-environment-timeago', $template).remove(); -- cgit v1.2.1 From 78c69d8d66ae254bae5736e927772e280f2c6b93 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 14 Oct 2016 17:08:48 +0800 Subject: Space between subject and {, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16956860 --- spec/services/ci/send_pipeline_notification_service_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/services/ci/send_pipeline_notification_service_spec.rb b/spec/services/ci/send_pipeline_notification_service_spec.rb index 6a32357b72c..288302cc94f 100644 --- a/spec/services/ci/send_pipeline_notification_service_spec.rb +++ b/spec/services/ci/send_pipeline_notification_service_spec.rb @@ -11,6 +11,7 @@ describe Ci::SendPipelineNotificationService, services: true do let(:project) { create(:project) } let(:user) { create(:user) } + subject{ described_class.new(pipeline) } describe '#execute' do -- cgit v1.2.1 From 5b9971fa9b967eb5591242155d6f5fce2c292b7d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 14 Oct 2016 17:41:56 +0800 Subject: Use the same style in the same file, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16957939 --- app/helpers/gitlab_routing_helper.rb | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f9c3d3f270f..1e081335865 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -95,35 +95,19 @@ module GitlabRoutingHelper end def pipeline_url(pipeline, *args) - namespace_project_pipeline_url( - pipeline.project.namespace, - pipeline.project, - pipeline.id, - *args) + namespace_project_pipeline_url(pipeline.project.namespace, pipeline.project, pipeline.id, *args) end def pipeline_build_url(pipeline, build, *args) - namespace_project_build_url( - pipeline.project.namespace, - pipeline.project, - build.id, - *args) + namespace_project_build_url(pipeline.project.namespace, pipeline.project,build.id, *args) end def commits_url(entity, *args) - namespace_project_commits_url( - entity.project.namespace, - entity.project, - entity.ref, - *args) + namespace_project_commits_url(entity.project.namespace, entity.project, entity.ref, *args) end def commit_url(entity, *args) - namespace_project_commit_url( - entity.project.namespace, - entity.project, - entity.sha, - *args) + namespace_project_commit_url(entity.project.namespace, entity.project, entity.sha, *args) end def project_snippet_url(entity, *args) -- cgit v1.2.1 From b8003aa012843fac1745c94788b993c36570fbd9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 11:47:54 +0200 Subject: Calculate build coverage asynchronously --- app/models/ci/build.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index ff70f3deb52..23119c5ed61 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -82,9 +82,8 @@ module Ci end after_transition any => [:success, :failed, :canceled] do |build| - build.update_coverage - build.run_after_commit do + BuildCoverageWorker.perform_async(id) BuildHooksWorker.perform_async(id) end end -- cgit v1.2.1 From d1a7b35b167b747a4794110dc1354200fd6dc592 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Oct 2016 10:49:23 +0100 Subject: Updated issuable dropdown titles Closes #23337 --- app/views/shared/issuable/_form.html.haml | 6 +++--- app/views/shared/issuable/_label_dropdown.html.haml | 3 ++- app/views/shared/issuable/_milestone_dropdown.html.haml | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c3f4e10c954..a75653d842f 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -85,20 +85,20 @@ .issuable-form-select-holder - if issuable.assignee_id = f.hidden_field :assignee_id - = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", + = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input" + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - has_labels = issuable.project.labels.any? = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' } + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label" - if has_due_date .col-lg-6 .form-group diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 6d307611640..22b5a6aa11b 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -8,6 +8,7 @@ - classes = local_assigns.fetch(:classes, []) - selected = local_assigns.fetch(:selected, nil) - selected_toggle = local_assigns.fetch(:selected_toggle, nil) +- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label") - dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"} - dropdown_data.merge!(data_options) - classes << 'js-extra-options' if extra_options @@ -23,7 +24,7 @@ = multi_label_name(selected, "Labels") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable - = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } + = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create } - if show_create && project && can?(current_user, :admin_label, project) = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index ab3cc33d18f..b85e2c11ff0 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -2,9 +2,10 @@ - extra_class = extra_class || '' - show_menu_above = show_menu_above || false - selected_text = selected.try(:title) +- dropdown_title = dropdown_title || "Filter by milestone" - if selected.present? = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) -= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", += dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list -- cgit v1.2.1 From 270434f7372dcb4d412f3f62068f69d8eac212e8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Oct 2016 08:34:35 +0100 Subject: Loads GFM once for per page #22827 --- CHANGELOG | 1 + app/views/layouts/application.html.haml | 1 + app/views/projects/_zen.html.haml | 4 +--- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 99bbd99726d..7c8989b2072 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -116,6 +116,7 @@ v 8.13.0 (unreleased) v 8.12.7 - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 + - Fix GFM autocomplete setup being called several times v 8.12.6 - Update mailroom to 0.8.1 in Gemfile.lock !6814 diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 15a94ac23c5..6c2285fa2b6 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -11,3 +11,4 @@ = render 'layouts/page', sidebar: sidebar, nav: nav = yield :scripts_body + = render "layouts/init_auto_complete" if @gfm_form diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index cb97181b9e1..0c8241053e7 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -1,3 +1,4 @@ +- @gfm_form = true - supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false) .zen-backdrop - classes << ' js-gfm-input js-autosize markdown-area' @@ -7,6 +8,3 @@ = text_area_tag attr, nil, class: classes, placeholder: placeholder %a.zen-control.zen-control-leave.js-zen-leave{ href: "#" } = icon('compress') - -- content_for :scripts_body do - = render "layouts/init_auto_complete" if current_user && (@target_project || @project) -- cgit v1.2.1 From 8710cdf57d4160685071a44dc225cb02865634e2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Oct 2016 11:15:44 +0100 Subject: Use local assigns to get the dropdown title --- app/views/shared/issuable/_milestone_dropdown.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index b85e2c11ff0..f27a9002ec2 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -2,7 +2,7 @@ - extra_class = extra_class || '' - show_menu_above = show_menu_above || false - selected_text = selected.try(:title) -- dropdown_title = dropdown_title || "Filter by milestone" +- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone") - if selected.present? = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", -- cgit v1.2.1 From eb4a42e0cef5e89b1d01cfa2a401908ffd09874c Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Fri, 14 Oct 2016 21:26:43 +1100 Subject: Remove changelog item and interpolation --- CHANGELOG | 1 - app/views/profiles/accounts/show.html.haml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f933c298b01..99bbd99726d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - - Remove '/u' prefix form username from Account page (blackst0ne) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 8ee643f3bcc..e2e974ba072 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -86,7 +86,7 @@ = f.label :username, "Path", class: "label-light" .input-group .input-group-addon - = "#{root_url}" + = root_url = f.text_field :username, required: true, class: 'form-control' .help-block Current path: -- cgit v1.2.1 From cf15af31353b141028717456aa6967c9c11697af Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 13 Oct 2016 14:23:18 +0200 Subject: Add test, fix merge error --- .../projects/merge_requests_controller.rb | 3 ++- db/schema.rb | 12 +-------- .../projects/merge_requests_controller_spec.rb | 30 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5c802798028..9207c954335 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -409,12 +409,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.environments.map do |environment| next unless can?(current_user, :read_environment, environment) + project = environment.project deployment = environment.first_deployment_for(@merge_request.diff_head_commit) { id: environment.id, name: environment.name, - url: namespace_project_environment_path(@project.namespace, @project, environment), + url: namespace_project_environment_path(project.namespace, project, environment), external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, deployed_at: deployment.try(:created_at), diff --git a/db/schema.rb b/db/schema.rb index 0bb03dfe4bc..a362fd8f228 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -446,17 +446,6 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree - create_table "integrations", force: :cascade do |t| - t.integer "project_id" - t.string "name" - t.string "external_token" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - add_index "integrations", ["external_token"], name: "index_integrations_on_external_token", unique: true, using: :btree - add_index "integrations", ["project_id"], name: "index_integrations_on_project_id", using: :btree - create_table "issue_metrics", force: :cascade do |t| t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" @@ -624,6 +613,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.datetime "updated_at", null: false end + add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree create_table "merge_requests", force: :cascade do |t| diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 84298f8bef4..d509f0f2b96 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -756,4 +756,34 @@ describe Projects::MergeRequestsController do post_assign_issues end end + + describe 'GET ci_environments_status' do + context 'when the environment is from a forked project' do + let!(:forked) { create(:project) } + let!(:environment) { create(:environment, project: forked) } + let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } + let(:json_response) { JSON.parse(response.body) } + let(:admin) { create(:admin) } + + let(:merge_request) do + create(:forked_project_link, forked_to_project: forked, + forked_from_project: project) + + create(:merge_request, source_project: forked, target_project: project) + end + + before do + forked.team << [user, :master] + + get :ci_environments_status, + namespace_id: merge_request.project.namespace.to_param, + project_id: merge_request.project.to_param, + id: merge_request.iid, format: 'json' + end + + it 'links to the environment on that project' do + expect(json_response.first['url']).to match /#{forked.path_with_namespace}/ + end + end + end end -- cgit v1.2.1 From 4c46c9a9738cfa90dd450e70ccf85e470be1d789 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 14 Oct 2016 09:38:20 +0200 Subject: Grapify boards API --- lib/api/boards.rb | 76 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 9b71d335128..b14dd4f6e83 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -3,19 +3,28 @@ module API class Boards < Grape::API before { authenticate! } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do - # Get the project board + desc 'Get all project boards' do + detail 'This feature was introduced in 8.13' + success Entities::Board + end get ':id/boards' do authorize!(:read_board, user_project) present user_project.boards, with: Entities::Board end + params do + requires :board_id, type: Integer, desc: 'The ID of a board' + end segment ':id/boards/:board_id' do helpers do def project_board board = user_project.boards.first - if params[:board_id].to_i == board.id + if params[:board_id] == board.id board else not_found!('Board') @@ -27,29 +36,35 @@ module API end end - # Get the lists of a project board - # Does not include `backlog` and `done` lists + desc 'Get the lists of a project board' do + detail 'Does not include `backlog` and `done` lists. This feature was introduced in 8.13' + success Entities::List + end get '/lists' do authorize!(:read_board, user_project) present board_lists, with: Entities::List end - # Get a list of a project board + desc 'Get a list of a project board' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :list_id, type: Integer, desc: 'The ID of a list' + end get '/lists/:list_id' do authorize!(:read_board, user_project) present board_lists.find(params[:list_id]), with: Entities::List end - # Create a new board list - # - # Parameters: - # id (required) - The ID of a project - # label_id (required) - The ID of an existing label - # Example Request: - # POST /projects/:id/boards/:board_id/lists + desc 'Create a new board list' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :label_id, type: Integer, desc: 'The ID of an existing label' + end post '/lists' do - required_attributes! [:label_id] - unless user_project.labels.exists?(params[:label_id]) render_api_error!({ error: "Label not found!" }, 400) end @@ -68,21 +83,21 @@ module API end end - # Moves a board list to a new position - # - # Parameters: - # id (required) - The ID of a project - # board_id (required) - The ID of a board - # position (required) - The position of the list - # Example Request: - # PUT /projects/:id/boards/:board_id/lists/:list_id + desc 'Moves a board list to a new position' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :list_id, type: Integer, desc: 'The ID of a list' + requires :position, type: Integer, desc: 'The position of the list' + end put '/lists/:list_id' do list = project_board.lists.movable.find(params[:list_id]) authorize!(:admin_list, user_project) service = ::Boards::Lists::MoveService.new(user_project, current_user, - { position: params[:position].to_i }) + { position: params[:position] }) if service.execute(list) present list, with: Entities::List @@ -91,14 +106,13 @@ module API end end - # Delete a board list - # - # Parameters: - # id (required) - The ID of a project - # board_id (required) - The ID of a board - # list_id (required) - The ID of a board list - # Example Request: - # DELETE /projects/:id/boards/:board_id/lists/:list_id + desc 'Delete a board list' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :list_id, type: Integer, desc: 'The ID of a board list' + end delete "/lists/:list_id" do authorize!(:admin_list, user_project) -- cgit v1.2.1 From b5f9d4c4bc48b252d3175432a3bb6fb1ca394af9 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 14 Oct 2016 19:13:11 +0800 Subject: Introduce Pipeline#merge_requests_with_active_first, Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16956802 --- app/mailers/emails/pipelines.rb | 5 +---- app/models/ci/pipeline.rb | 12 ++++++++---- app/models/merge_request.rb | 4 ++++ spec/models/ci/pipeline_spec.rb | 31 +++++++++++++++++++++++++++++++ spec/models/merge_request_spec.rb | 10 +++++++++- 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 903d123705a..92f1431ce66 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -13,10 +13,7 @@ module Emails def pipeline_mail(pipeline, to, status) @project = pipeline.project @pipeline = pipeline - @merge_request = @project.merge_requests.opened. - find_by(source_project: @project, - source_branch: @pipeline.ref, - target_branch: @project.default_branch) + @merge_request = pipeline.merge_requests_with_active_first.first add_headers mail(to: to, subject: pipeline_subject(status), skip_premailer: true) do |format| diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2cf9892edc5..bf2d861b86f 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -293,10 +293,14 @@ module Ci # the merge request's latest commit. def merge_requests @merge_requests ||= - begin - project.merge_requests.where(source_branch: self.ref). - select { |merge_request| merge_request.pipeline.try(:id) == self.id } - end + project.merge_requests.where(source_branch: ref). + select { |merge_request| merge_request.pipeline.try(:id) == id } + end + + def merge_requests_with_active_first + merge_requests.sort_by do |merge_request| + [merge_request.state_priority, -merge_request.updated_at.to_i] + end end private diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a743bf313ae..852317bbed1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -169,6 +169,10 @@ class MergeRequest < ActiveRecord::Base work_in_progress?(title) ? title : "WIP: #{title}" end + def state_priority + %w[opened reopened closed merged locked].index(state) + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 550a890797e..2a0b00dc545 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -523,4 +523,35 @@ describe Ci::Pipeline, models: true do expect(pipeline.merge_requests).to be_empty end end + + describe '#merge_requests_with_active_first' do + let(:project) { create(:project) } + + let(:pipeline) do + create(:ci_empty_pipeline, + status: 'created', + project: project, + ref: 'master', + sha: project.repository.commit('master').sha) + end + + let!(:merge_requests) do + [create_merge_request(:merged, Time.at(0)), + create_merge_request(:merged, Time.at(9)), + create_merge_request(:opened, Time.at(0))] + end + + it 'returns opened/recent merge requests first, then closed ones' do + expect(pipeline.merge_requests_with_active_first). + to eq(merge_requests.reverse) + end + + def create_merge_request(state, updated_at) + create(:merge_request, + source_project: project, + source_branch: pipeline.ref, + state: state, + updated_at: updated_at) + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 38b6da50168..8cf53fd1dee 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -58,6 +58,14 @@ describe MergeRequest, models: true do it { is_expected.to respond_to(:merge_when_build_succeeds) } end + describe '#state_priority' do + it 'returns the priority of state' do + %w[opened reopened closed merged locked].each.with_index do |state, idx| + expect(MergeRequest.new(state: state).state_priority).to eq(idx) + end + end + end + describe '.in_projects' do it 'returns the merge requests for a set of projects' do expect(described_class.in_projects(Project.all)).to eq([subject]) @@ -334,7 +342,7 @@ describe MergeRequest, models: true do wip_title = "WIP: #{subject.title}" expect(subject.wip_title).to eq wip_title - end + end it "does not add the WIP: prefix multiple times" do wip_title = "WIP: #{subject.title}" -- cgit v1.2.1 From d7324f0ed5181f0eb765871a35eaee587bf25fa9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 14 Oct 2016 14:51:59 +0300 Subject: Move edit group scenario to rspec and refactor groups_spec Signed-off-by: Dmitriy Zaporozhets --- config/routes/group.rb | 1 - features/groups.feature | 5 --- features/steps/groups.rb | 12 ------- spec/features/groups_spec.rb | 78 ++++++++++++++++++++++++-------------------- 4 files changed, 42 insertions(+), 54 deletions(-) diff --git a/config/routes/group.rb b/config/routes/group.rb index 8bee2bb1eb4..33143f0dfa2 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -3,7 +3,6 @@ require 'constraints/group_url_constrainer' constraints(GroupUrlConstrainer.new) do scope(path: ':id', as: :group, controller: :groups) do get '/', action: :show - post '/', action: :create patch '/', action: :update put '/', action: :update delete '/', action: :destroy diff --git a/features/groups.feature b/features/groups.feature index 49e939807b5..4044bd9be79 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -39,11 +39,6 @@ Feature: Groups When I visit group "Owned" merge requests page Then I should not see merge requests from the archived project - Scenario: I should see edit group "Owned" page - When I visit group "Owned" settings page - And I change group "Owned" name to "new-name" - Then I should see new group "Owned" name - Scenario: I edit group "Owned" avatar When I visit group "Owned" settings page And I change group "Owned" avatar diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 4fa7d7c6567..0e81e99120b 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -73,18 +73,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps author: current_user end - step 'I change group "Owned" name to "new-name"' do - fill_in 'group_name', with: 'new-name' - fill_in 'group_path', with: 'new-name' - click_button "Save group" - end - - step 'I should see new group "Owned" name' do - page.within ".navbar-gitlab" do - expect(page).to have_content "new-name" - end - end - step 'I change group "Owned" avatar' do attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')) click_button "Save group" diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 3f908294cbb..13bfe90302c 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -11,93 +11,99 @@ feature 'Group', feature: true do end end - describe 'creating a group with space in group path' do - it 'renders new group form with validation errors' do - visit new_group_path - fill_in 'Group path', with: 'space group' + describe 'create a group' do + before { visit new_group_path } - click_button 'Create group' + describe 'with space in group path' do + it 'renders new group form with validation errors' do + fill_in 'Group path', with: 'space group' + click_button 'Create group' - expect(current_path).to eq(groups_path) - expect(page).to have_namespace_error_message + expect(current_path).to eq(groups_path) + expect(page).to have_namespace_error_message + end end - end - - describe 'creating a group with .atom at end of group path' do - it 'renders new group form with validation errors' do - visit new_group_path - fill_in 'Group path', with: 'atom_group.atom' - click_button 'Create group' + describe 'with .atom at end of group path' do + it 'renders new group form with validation errors' do + fill_in 'Group path', with: 'atom_group.atom' + click_button 'Create group' - expect(current_path).to eq(groups_path) - expect(page).to have_namespace_error_message + expect(current_path).to eq(groups_path) + expect(page).to have_namespace_error_message + end end - end - - describe 'creating a group with .git at end of group path' do - it 'renders new group form with validation errors' do - visit new_group_path - fill_in 'Group path', with: 'git_group.git' - click_button 'Create group' + describe 'with .git at end of group path' do + it 'renders new group form with validation errors' do + fill_in 'Group path', with: 'git_group.git' + click_button 'Create group' - expect(current_path).to eq(groups_path) - expect(page).to have_namespace_error_message + expect(current_path).to eq(groups_path) + expect(page).to have_namespace_error_message + end end end - describe 'Group Edit' do + describe 'group edit' do let(:group) { create(:group) } let(:path) { edit_group_path(group) } + let(:new_name) { 'new-name' } - it 'saves new settings' do - expect(group.request_access_enabled).to be_truthy - visit path - - find('#group_request_access_enabled').set(false) + before { visit path } + it 'saves new settings' do + fill_in 'group_name', with: new_name click_button 'Save group' expect(page).to have_content 'successfully updated' - group.reload - expect(group.request_access_enabled).to be_falsey + expect(find('#group_name').value).to eq(new_name) + + page.within ".navbar-gitlab" do + expect(page).to have_content new_name + end end it 'removes group' do - visit path - click_link 'Remove Group' expect(page).to have_content "scheduled for deletion" end end - describe 'description' do + describe 'group page with markdown description' do let(:group) { create(:group) } let(:path) { group_path(group) } it 'parses Markdown' do group.update_attribute(:description, 'This is **my** group') + visit path + expect(page).to have_css('.description > p > strong') end it 'passes through html-pipeline' do group.update_attribute(:description, 'This group is the :poop:') + visit path + expect(page).to have_css('.description > p > img') end it 'sanitizes unwanted tags' do group.update_attribute(:description, '# Group Description') + visit path + expect(page).not_to have_css('.description h1') end it 'permits `rel` attribute on links' do group.update_attribute(:description, 'https://google.com/') + visit path + expect(page).to have_css('.description a[rel]') end end -- cgit v1.2.1 From 7d97a22e860b92a58f862214a6d13f9e5e29c64a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 14 Oct 2016 19:54:51 +0800 Subject: Just show the first merge request we found, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16963209 --- app/mailers/emails/pipelines.rb | 2 +- app/models/ci/pipeline.rb | 6 ------ app/models/merge_request.rb | 4 ---- spec/models/ci/pipeline_spec.rb | 31 ------------------------------- spec/models/merge_request_spec.rb | 8 -------- 5 files changed, 1 insertion(+), 50 deletions(-) diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 92f1431ce66..601c8b5cd62 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -13,7 +13,7 @@ module Emails def pipeline_mail(pipeline, to, status) @project = pipeline.project @pipeline = pipeline - @merge_request = pipeline.merge_requests_with_active_first.first + @merge_request = pipeline.merge_requests.first add_headers mail(to: to, subject: pipeline_subject(status), skip_premailer: true) do |format| diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 042b1c04054..957f6755b2e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -302,12 +302,6 @@ module Ci .select { |merge_request| merge_request.pipeline.try(:id) == self.id } end - def merge_requests_with_active_first - merge_requests.sort_by do |merge_request| - [merge_request.state_priority, -merge_request.updated_at.to_i] - end - end - private def pipeline_data diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 3efb7aead77..5ccfe11a2a2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -169,10 +169,6 @@ class MergeRequest < ActiveRecord::Base work_in_progress?(title) ? title : "WIP: #{title}" end - def state_priority - %w[opened reopened closed merged locked].index(state) - end - def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 2a0b00dc545..550a890797e 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -523,35 +523,4 @@ describe Ci::Pipeline, models: true do expect(pipeline.merge_requests).to be_empty end end - - describe '#merge_requests_with_active_first' do - let(:project) { create(:project) } - - let(:pipeline) do - create(:ci_empty_pipeline, - status: 'created', - project: project, - ref: 'master', - sha: project.repository.commit('master').sha) - end - - let!(:merge_requests) do - [create_merge_request(:merged, Time.at(0)), - create_merge_request(:merged, Time.at(9)), - create_merge_request(:opened, Time.at(0))] - end - - it 'returns opened/recent merge requests first, then closed ones' do - expect(pipeline.merge_requests_with_active_first). - to eq(merge_requests.reverse) - end - - def create_merge_request(state, updated_at) - create(:merge_request, - source_project: project, - source_branch: pipeline.ref, - state: state, - updated_at: updated_at) - end - end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index a570e42c6ac..2167a988f99 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -58,14 +58,6 @@ describe MergeRequest, models: true do it { is_expected.to respond_to(:merge_when_build_succeeds) } end - describe '#state_priority' do - it 'returns the priority of state' do - %w[opened reopened closed merged locked].each.with_index do |state, idx| - expect(MergeRequest.new(state: state).state_priority).to eq(idx) - end - end - end - describe '.in_projects' do it 'returns the merge requests for a set of projects' do expect(described_class.in_projects(Project.all)).to eq([subject]) -- cgit v1.2.1 From 43973223af6755c8213e639e616bc788d7c0493c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 14 Oct 2016 20:11:56 +0800 Subject: Add back the space which Sublime can't easily tell: Failed build: https://gitlab.com/gitlab-org/gitlab-ce/builds/5116496 Reason why I can't see this: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6019#note_16958250 --- app/helpers/gitlab_routing_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 1e081335865..bccf64d1aac 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -99,7 +99,7 @@ module GitlabRoutingHelper end def pipeline_build_url(pipeline, build, *args) - namespace_project_build_url(pipeline.project.namespace, pipeline.project,build.id, *args) + namespace_project_build_url(pipeline.project.namespace, pipeline.project, build.id, *args) end def commits_url(entity, *args) -- cgit v1.2.1 From 04593581037bca7a7c3b00c719404e610c158cc1 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 13 Oct 2016 19:32:10 +0200 Subject: API: Fix Sytem hooks delete behavior --- doc/api/system_hooks.md | 7 ++----- lib/api/system_hooks.rb | 10 ++++------ spec/requests/api/system_hooks_spec.rb | 7 ++++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md index 1802fae14fe..073e99b7147 100644 --- a/doc/api/system_hooks.md +++ b/doc/api/system_hooks.md @@ -98,11 +98,8 @@ Example response: ## Delete system hook -Deletes a system hook. This is an idempotent API function and returns `200 OK` -even if the hook is not available. - -If the hook is deleted, a JSON object is returned. An error is raised if the -hook is not found. +Deletes a system hook. It returns `200 OK` if the hooks is deleted and +`404 Not Found` if the hook is not found. --- diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 2e76b91051f..794e34874f4 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -56,12 +56,10 @@ module API requires :id, type: Integer, desc: 'The ID of the system hook' end delete ":id" do - begin - hook = SystemHook.find(params[:id]) - present hook.destroy, with: Entities::Hook - rescue - # SystemHook raises an Error if no hook with id found - end + hook = SystemHook.find_by(id: params[:id]) + not_found!('System hook') unless hook + + present hook.destroy, with: Entities::Hook end end end diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 1ce2658569e..f8a1aed5441 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -73,9 +73,10 @@ describe API::API, api: true do end.to change { SystemHook.count }.by(-1) end - it "returns success if hook id not found" do - delete api("/hooks/12345", admin) - expect(response).to have_http_status(200) + it 'returns 404 if the system hook does not exist' do + delete api('/hooks/12345', admin) + + expect(response).to have_http_status(404) end end end -- cgit v1.2.1 From fd7fb993667265967633a1ac51f0ed17d852ae64 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 5 Oct 2016 18:24:39 -0600 Subject: Remove spinach retry. --- .gitlab-ci.yml | 2 +- Gemfile | 1 - Gemfile.lock | 3 --- features/support/db_cleaner.rb | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3b864f16cf..30471286026 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,7 +99,7 @@ update-knapsack: - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - - knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' + - knapsack spinach artifacts: expire_in: 31d paths: diff --git a/Gemfile b/Gemfile index 5f754c1b66f..563851c25f5 100644 --- a/Gemfile +++ b/Gemfile @@ -278,7 +278,6 @@ group :development, :test do gem 'rspec-rails', '~> 3.5.0' gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' - gem 'spinach-rerun-reporter', '~> 0.0.2' # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) gem 'minitest', '~> 5.7.0' diff --git a/Gemfile.lock b/Gemfile.lock index a9892d1c130..60d434a1b69 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -700,8 +700,6 @@ GEM capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) - spinach-rerun-reporter (0.0.2) - spinach (~> 0.8) spring (1.7.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) @@ -964,7 +962,6 @@ DEPENDENCIES simplecov (= 0.12.0) slack-notifier (~> 1.2.0) spinach-rails (~> 0.2.1) - spinach-rerun-reporter (~> 0.0.2) spring (~> 1.7.0) spring-commands-rspec (~> 1.0.4) spring-commands-spinach (~> 1.1.0) diff --git a/features/support/db_cleaner.rb b/features/support/db_cleaner.rb index 1ab308cfa55..8294bb1445f 100644 --- a/features/support/db_cleaner.rb +++ b/features/support/db_cleaner.rb @@ -1,6 +1,6 @@ require 'database_cleaner' -DatabaseCleaner.strategy = :truncation +DatabaseCleaner[:active_record].strategy = :truncation Spinach.hooks.before_scenario do DatabaseCleaner.start -- cgit v1.2.1 From 06528eebb2255a77904d751e9f3934210cd5945b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 13:09:18 +0200 Subject: Re-run specs if failed --- .gitlab-ci.yml | 2 +- Gemfile | 1 + Gemfile.lock | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 30471286026..48015d5ec2a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,7 +99,7 @@ update-knapsack: - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - - knapsack spinach + - if ! knapsack spinach "-r rerun"; then bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt); fi artifacts: expire_in: 31d paths: diff --git a/Gemfile b/Gemfile index 563851c25f5..5f754c1b66f 100644 --- a/Gemfile +++ b/Gemfile @@ -278,6 +278,7 @@ group :development, :test do gem 'rspec-rails', '~> 3.5.0' gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' + gem 'spinach-rerun-reporter', '~> 0.0.2' # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) gem 'minitest', '~> 5.7.0' diff --git a/Gemfile.lock b/Gemfile.lock index 60d434a1b69..a9892d1c130 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -700,6 +700,8 @@ GEM capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) + spinach-rerun-reporter (0.0.2) + spinach (~> 0.8) spring (1.7.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) @@ -962,6 +964,7 @@ DEPENDENCIES simplecov (= 0.12.0) slack-notifier (~> 1.2.0) spinach-rails (~> 0.2.1) + spinach-rerun-reporter (~> 0.0.2) spring (~> 1.7.0) spring-commands-rspec (~> 1.0.4) spring-commands-spinach (~> 1.1.0) -- cgit v1.2.1 From caa141eaa719f52b126065ff42c68fdc36b52437 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 14:12:30 +0200 Subject: Fix retries --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 48015d5ec2a..ace0de5cad3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,7 +99,7 @@ update-knapsack: - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - - if ! knapsack spinach "-r rerun"; then bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt); fi + - knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' artifacts: expire_in: 31d paths: -- cgit v1.2.1 From eaa22eb447b8dcd690f084221db26043274f343b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 14:32:47 +0200 Subject: Try to fix re-run --- features/support/rerun.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/support/rerun.rb b/features/support/rerun.rb index 8b176c5be89..60b78f9d050 100644 --- a/features/support/rerun.rb +++ b/features/support/rerun.rb @@ -1,5 +1,7 @@ # The spinach-rerun-reporter doesn't define the on_undefined_step # See it here: https://github.com/javierav/spinach-rerun-reporter/blob/master/lib/spinach/reporter/rerun.rb +require 'spinach-rerun-reporter' + module Spinach class Reporter class Rerun -- cgit v1.2.1 From 5904793ad8ba88d3dfc9c973bcffd1d426db5a33 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 12:53:51 +0200 Subject: Add build finished worker that creates a workflow --- app/models/ci/build.rb | 3 +-- app/workers/build_finished_worker.rb | 12 ++++++++++++ spec/workers/build_finished_worker_spec.rb | 30 ++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 app/workers/build_finished_worker.rb create mode 100644 spec/workers/build_finished_worker_spec.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 23119c5ed61..87475119b23 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -83,8 +83,7 @@ module Ci after_transition any => [:success, :failed, :canceled] do |build| build.run_after_commit do - BuildCoverageWorker.perform_async(id) - BuildHooksWorker.perform_async(id) + BuildFinishedWorker.perform_async(id) end end diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb new file mode 100644 index 00000000000..f1267ab92fc --- /dev/null +++ b/app/workers/build_finished_worker.rb @@ -0,0 +1,12 @@ +class BuildFinishedWorker + include Sidekiq::Worker + + def perform(build_id) + Ci::Build.find_by(id: build_id).try do |build| + build.with_lock do + BuildCoverageWorker.new.perform(build.id) + BuildHooksWorker.new.perform(build.id) + end + end + end +end diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb new file mode 100644 index 00000000000..2868167c7d4 --- /dev/null +++ b/spec/workers/build_finished_worker_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe BuildFinishedWorker do + describe '#perform' do + context 'when build exists' do + let(:build) { create(:ci_build) } + + it 'calculates coverage and calls hooks' do + expect(BuildCoverageWorker) + .to receive(:new).ordered.and_call_original + expect(BuildHooksWorker) + .to receive(:new).ordered.and_call_original + + expect_any_instance_of(BuildCoverageWorker) + .to receive(:perform) + expect_any_instance_of(BuildHooksWorker) + .to receive(:perform) + + described_class.new.perform(build.id) + end + end + + context 'when build does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From f24966174701024ece1e728315f5e847b684ee65 Mon Sep 17 00:00:00 2001 From: Sean Packham Date: Fri, 14 Oct 2016 13:53:35 +0100 Subject: Added book club to university --- doc/university/README.md | 1 + doc/university/bookclub/booklist.md | 113 ++++++++++++++++++++++++++++++++++++ doc/university/bookclub/index.md | 19 ++++++ 3 files changed, 133 insertions(+) create mode 100644 doc/university/bookclub/booklist.md create mode 100644 doc/university/bookclub/index.md diff --git a/doc/university/README.md b/doc/university/README.md index e71e49c33c8..f5a0dab39fe 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -64,6 +64,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project 1. [Making GitLab Great for Everyone - Video](https://www.youtube.com/watch?v=GGC40y4vMx0) - Response to "Dear GitHub" letter 1. [Using Innersourcing to Improve Collaboration](https://about.gitlab.com/2014/09/05/innersourcing-using-the-open-source-workflow-to-improve-collaboration-within-an-organization/) 1. [The Software Development Market and GitLab - Video](https://www.youtube.com/watch?v=sXlhgPK1NTY&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=6) - [Slides](https://docs.google.com/presentation/d/1vCU-NbZWz8NTNK8Vu3y4zGMAHb5DpC8PE5mHtw1PWfI/edit) +1. [The GitLab Book Club](bookclub/index.md) #### 1.7 Community and Support diff --git a/doc/university/bookclub/booklist.md b/doc/university/bookclub/booklist.md new file mode 100644 index 00000000000..c4229832e9f --- /dev/null +++ b/doc/university/bookclub/booklist.md @@ -0,0 +1,113 @@ +# Books + +List of books and resources, that may be worth reading. + +## Papers + +1. **The Humble Programmer** + + Edsger W. Dijkstra, 1972 ([paper](http://dl.acm.org/citation.cfm?id=361591)) + +## Programming + +1. **Design Patterns: Elements of Reusable Object-Oriented Software** + + Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, 1994 ([amazon](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)) + +1. **Clean Code: A Handbook of Agile Software Craftsmanship** + + Robert C. "Uncle Bob" Martin, 2008 ([amazon](http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)) + +1. **Code Complete: A Practical Handbook of Software Construction**, 2nd Edition + + Steve McConnell, 2004 ([amazon](http://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670)) + +1. **The Pragmatic Programmer: From Journeyman to Master** + + Andrew Hunt, David Thomas, 1999 ([amazon](http://www.amazon.com/Pragmatic-Programmer-Journeyman-Master/dp/020161622X)) + +1. **Working Effectively with Legacy Code** + + Michael Feathers, 2004 ([amazon](http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052)) + +1. **Eloquent Ruby** + + Russ Olsen, 2011 ([amazon](http://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104)) + +1. **Domain-Driven Design: Tackling Complexity in the Heart of Software** + + Eric Evans, 2003 ([amazon](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215)) + +1. **How to Solve It: A New Aspect of Mathematical Method** + + Polya G. 1957 ([amazon](http://www.amazon.com/How-Solve-Mathematical-Princeton-Science/dp/069116407X)) + +1. **Software Creativity 2.0** + + Robert L. Glass, 2006 ([amazon](http://www.amazon.com/Software-Creativity-2-0-Robert-Glass/dp/0977213315)) + +1. **Object-Oriented Software Construction** + + Bertrand Meyer, 1997 ([amazon](http://www.amazon.com/Object-Oriented-Software-Construction-Book-CD-ROM/dp/0136291554)) + +1. **Refactoring: Improving the Design of Existing Code** + + Martin Fowler, Kent Beck, 1999 ([amazon](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672)) + +1. **Test Driven Development: By Example** + + Kent Beck, 2002 ([amazon](http://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530)) + +1. **Algorithms in C++: Fundamentals, Data Structure, Sorting, Searching** + + Robert Sedgewick, 1990 ([amazon](http://www.amazon.com/Algorithms-Parts-1-4-Fundamentals-Structure/dp/0201350882)) + +1. **Effective C++** + + Scott Mayers, 1996 ([amazon](http://www.amazon.com/Effective-Specific-Improve-Programs-Designs/dp/0321334876)) + +1. **Extreme Programming Explained: Embrace Change** + + Kent Beck, 1999 ([amazon](http://www.amazon.com/Extreme-Programming-Explained-Embrace-Change/dp/0321278658)) + +1. **The Art of Computer Programming** + + Donald E. Knuth, 1997 ([amazon](http://www.amazon.com/Computer-Programming-Volumes-1-4A-Boxed/dp/0321751043)) + +1. **Writing Efficient Programs** + + Jon Louis Bentley, 1982 ([amazon](http://www.amazon.com/Writing-Efficient-Programs-Prentice-Hall-Software/dp/013970244X)) + +1. **The Mythical Man-Month: Essays on Software Engineering** + + Frederick Phillips Brooks, 1975 ([amazon](http://www.amazon.com/Mythical-Man-Month-Essays-Software-Engineering/dp/0201006502)) + +1. **Peopleware: Productive Projects and Teams** 3rd Edition + + Tom DeMarco, Tim Lister, 2013 ([amazon](http://www.amazon.com/Peopleware-Productive-Projects-Teams-3rd/dp/0321934113)) + +1. **Principles Of Software Engineering Management** + + Tom Gilb, 1988 ([amazon](http://www.amazon.com/Principles-Software-Engineering-Management-Gilb/dp/0201192462)) + +## Other + +1. **Thinking, Fast and Slow** + + Daniel Kahneman, 2013 ([amazon](http://www.amazon.com/Thinking-Fast-Slow-Daniel-Kahneman/dp/0374533555)) + +1. **The Social Animal** 11th Edition + + Elliot Aronson, 2011 ([amazon](http://www.amazon.com/Social-Animal-Elliot-Aronson/dp/1429233419)) + +1. **Influence: Science and Practice** 5th Edition + + Robert B. Cialdini, 2008 ([amazon](http://www.amazon.com/Influence-Practice-Robert-B-Cialdini/dp/0205609996)) + +1. **Getting to Yes: Negotiating Agreement Without Giving In** + + Roger Fisher, William L. Ury, Bruce Patton, 2011 ([amazon](http://www.amazon.com/Getting-Yes-Negotiating-Agreement-Without/dp/0143118757)) + +1. **How to Win Friends & Influence People** + + Dale Carnegie, 1981 ([amazon](http://www.amazon.com/How-Win-Friends-Influence-People/dp/0671027034)) diff --git a/doc/university/bookclub/index.md b/doc/university/bookclub/index.md new file mode 100644 index 00000000000..022a61f4429 --- /dev/null +++ b/doc/university/bookclub/index.md @@ -0,0 +1,19 @@ +# The GitLab Book Club + +The Book Club is a casual meet-up to read and discuss books we like. +We'll find a time that suits most, if not all. + +See the [book list](booklist.md) for additional recommendations. + +## Currently reading : Books about remote work + +1. **Remote: Office not required** + + David Heinemeier Hansson and Jason Fried, 2013 + ([amazon](http://www.amazon.co.uk/Remote-Required-David-Heinemeier-Hansson/dp/0091954673)) + +1. **The Year Without Pants** + + Scott Berkun, 2013 ([ScottBerkun.com](http://scottberkun.com/yearwithoutpants/)) + +Any other books you'd like to suggest? Edit this page and add them to the queue. -- cgit v1.2.1 From aef739605987dc70424b83c56ba393462520af52 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 30 Sep 2016 11:38:56 +0200 Subject: Cache gems in CI on tags --- .gitlab-ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62e06683124..7d19f55aca3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -339,3 +339,16 @@ pages: - public only: - master + +# Insurance in case a gem needed by one of our releases gets yanked from +# rubygems.org in the future. +cache gems: + only: + - tags + variables: + SETUP_DB: "false" + script: + - bundle package --all --all-platforms + artifacts: + paths: + - vendor/cache -- cgit v1.2.1 From c736ffdfb043df34a89273639297cfc86d2d5f88 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 14 Oct 2016 16:04:23 +0300 Subject: Validate user id for users select autcomplete Single user autcomplete should be used only for existing users with digital ID provided. Now js code puts any input into generating user URL which can lead to 500 error because routing like this does not exists: GET "/autocomplete/users/whatever@example.com.json". Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/users_select.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 6aa0e1cd2b6..3020b7cc239 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -325,6 +325,10 @@ }; UsersSelect.prototype.user = function(user_id, callback) { + if(!/^\d+$/.test(user_id)) { + return false; + } + var url; url = this.buildUrl(this.userPath); url = url.replace(':id', user_id); -- cgit v1.2.1 From 9ec3c32f98f9548b4aa756c8fba74159b12492a7 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 14 Oct 2016 13:39:20 +0100 Subject: Improved specifity of toggleable file headers --- .gitlab-ci.yml | 13 ------------- app/assets/stylesheets/framework/files.scss | 9 --------- app/assets/stylesheets/pages/diff.scss | 13 +++++++++++++ app/assets/stylesheets/pages/notes.scss | 8 ++++++++ 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d19f55aca3..62e06683124 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -339,16 +339,3 @@ pages: - public only: - master - -# Insurance in case a gem needed by one of our releases gets yanked from -# rubygems.org in the future. -cache gems: - only: - - tags - variables: - SETUP_DB: "false" - script: - - bundle package --all --all-platforms - artifacts: - paths: - - vendor/cache diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 81520500594..76a3c083697 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -26,15 +26,6 @@ padding: 10px $gl-padding; word-wrap: break-word; border-radius: 3px 3px 0 0; - cursor: pointer; - - &:hover { - background-color: $dark-background-color; - } - - .diff-toggle-caret { - padding-right: 6px; - } &.file-title-clear { padding-left: 0; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index b8ef76cc74e..84094b0cb1b 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -33,6 +33,19 @@ font-size: smaller; } } + + .file-title { + cursor: pointer; + + &:hover { + background-color: $dark-background-color; + } + + .diff-toggle-caret { + padding-right: 6px; + } + } + .diff-content { overflow: auto; overflow-y: hidden; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index d399f84a2ff..efeea96373f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -147,6 +147,14 @@ ul.notes { // Diff code in discussion view .discussion-body .diff-file { + .file-title { + cursor: default; + + &:hover { + background-color: $gray-light; + } + } + .diff-header > span { margin-right: 10px; } -- cgit v1.2.1 From 5bcf56401e502e9bb6094d37e76ea736d68cddfc Mon Sep 17 00:00:00 2001 From: De Wet Blomerus Date: Fri, 14 Oct 2016 15:16:18 +0200 Subject: remove a period that can be annoying --- .gitlab/merge_request_templates/Documentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md index d2a1eb56423..9b541aadad1 100644 --- a/.gitlab/merge_request_templates/Documentation.md +++ b/.gitlab/merge_request_templates/Documentation.md @@ -1,4 +1,4 @@ -See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html. +See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html ## What does this MR do? -- cgit v1.2.1 From 513f061b5b521a1a428a05ebc467784f672ae07b Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 14 Oct 2016 14:47:13 +0100 Subject: Use gitlab-workhorse 0.8.5 --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- doc/update/8.12-to-8.13.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index b60d71966ae..7ada0d303f3 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.8.4 +0.8.5 diff --git a/doc/install/installation.md b/doc/install/installation.md index 1fa8678223a..c9acc9cdfb0 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of 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.8.4 + sudo -u git -H git checkout v0.8.5 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index 00d63c1b3c6..8940d14559b 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -84,7 +84,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.8.4 +sudo -u git -H git checkout v0.8.5 sudo -u git -H make ``` -- cgit v1.2.1 From c5bf8e362ce10e52428bda873eb14b41e15a1bd0 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 14 Oct 2016 14:34:31 +0200 Subject: Use module_function in Banzai::Renderer Using `extend self` prevents GitLab Performance Monitoring from being able to track class methods. Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/23347 --- CHANGELOG | 1 + lib/banzai/renderer.rb | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4f2f74a7595..d2f1501bd2a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - Updating verbiage on git basics to be more intuitive - Clarify documentation for Runners API (Gennady Trafimenkov) + - The instrumentation for Banzai::Renderer has been restored - Change user & group landing page routing from /u/:username to /:username - Prevent running GfmAutocomplete setup for each diff note !6569 - Added documentation for .gitattributes files diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index 6924a293da8..ce048a36fa0 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -1,6 +1,6 @@ module Banzai module Renderer - extend self + module_function # Convert a Markdown String into an HTML-safe String of HTML # @@ -141,8 +141,6 @@ module Banzai end.html_safe end - private - def cacheless_render(text, context = {}) Gitlab::Metrics.measure(:banzai_cacheless_render) do result = render_result(text, context) -- cgit v1.2.1 From 3f8fa3576509e2c90fbf9c18450e3daede32deac Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 14 Oct 2016 15:53:06 +0100 Subject: Document restrictions on cache and artifact paths See https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/issues/1792 --- doc/ci/yaml/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 59399861a97..84ea59ab687 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -159,7 +159,8 @@ Variables can be also defined on [job level](#job-variables). > Introduced in GitLab Runner v0.7.0. `cache` is used to specify a list of files and directories which should be -cached between builds. +cached between builds. You can only use paths that are within the project +workspace. **By default the caching is enabled per-job and per-branch.** @@ -606,8 +607,8 @@ You can see a simple example at https://gitlab.com/gitlab-examples/review-apps-n > - Build artifacts are only collected for successful builds by default. `artifacts` is used to specify a list of files and directories which should be -attached to the build after success. To pass artifacts between different builds, -see [dependencies](#dependencies). +attached to the build after success. You can only use paths that are within the +project workspace. To pass artifacts between different builds, see [dependencies](#dependencies). Below are some examples. -- cgit v1.2.1 From 308769f82b19a142f443fb101a01a60c2b19757f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 17:48:28 +0200 Subject: Remove unecessary lock --- app/workers/build_finished_worker.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb index f1267ab92fc..e7286b77ac5 100644 --- a/app/workers/build_finished_worker.rb +++ b/app/workers/build_finished_worker.rb @@ -3,10 +3,8 @@ class BuildFinishedWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - build.with_lock do - BuildCoverageWorker.new.perform(build.id) - BuildHooksWorker.new.perform(build.id) - end + BuildCoverageWorker.new.perform(build.id) + BuildHooksWorker.new.perform(build.id) end end end -- cgit v1.2.1 From b9f351e8798529f09316c0d8ab77a01c6a18f7bf Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 14 Oct 2016 17:12:50 +0000 Subject: Rename pipeline.js.es6 to pipelines.js.es6 --- app/assets/javascripts/pipeline.js.es6 | 40 --------------------------------- app/assets/javascripts/pipelines.js.es6 | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 40 deletions(-) delete mode 100644 app/assets/javascripts/pipeline.js.es6 create mode 100644 app/assets/javascripts/pipelines.js.es6 diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 deleted file mode 100644 index 6bf63ee6979..00000000000 --- a/app/assets/javascripts/pipeline.js.es6 +++ /dev/null @@ -1,40 +0,0 @@ -((global) => { - - class Pipelines { - constructor() { - $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); - this.addMarginToBuildColumns(); - } - - toggleGraph() { - const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); - const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); - const $btnText = $(this).find('.toggle-btn-text'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - - $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - - - graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') - } - - addMarginToBuildColumns() { - const $secondChildBuildNode = $('.build:nth-child(2)'); - if ($secondChildBuildNode.length) { - const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); - const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); - const $previousColumn = $multiBuildColumn.prev('.stage-column'); - $multiBuildColumn.addClass('left-margin'); - $firstChildBuildNode.addClass('left-connector'); - $previousColumn.each(function() { - $this = $(this); - if ($('.build', $this).length === 1) $this.addClass('no-margin'); - }); - } - $('.pipeline-graph').removeClass('hidden'); - } - } - - global.Pipelines = Pipelines; - -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 new file mode 100644 index 00000000000..6bf63ee6979 --- /dev/null +++ b/app/assets/javascripts/pipelines.js.es6 @@ -0,0 +1,40 @@ +((global) => { + + class Pipelines { + constructor() { + $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); + this.addMarginToBuildColumns(); + } + + toggleGraph() { + const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); + const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); + const $btnText = $(this).find('.toggle-btn-text'); + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); + + $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); + + + graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + } + + addMarginToBuildColumns() { + const $secondChildBuildNode = $('.build:nth-child(2)'); + if ($secondChildBuildNode.length) { + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); + const $previousColumn = $multiBuildColumn.prev('.stage-column'); + $multiBuildColumn.addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); + $previousColumn.each(function() { + $this = $(this); + if ($('.build', $this).length === 1) $this.addClass('no-margin'); + }); + } + $('.pipeline-graph').removeClass('hidden'); + } + } + + global.Pipelines = Pipelines; + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From c180221a5b0e58c42632412d2084efc3bd4cda99 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Fri, 14 Oct 2016 19:49:36 +0200 Subject: Add docs for request profiling Closes #23239 --- CHANGELOG | 1 + doc/README.md | 1 + .../performance/img/request_profile_result.png | Bin 0 -> 9720 bytes .../performance/img/request_profiling_token.png | Bin 0 -> 30076 bytes .../monitoring/performance/request_profiling.md | 16 ++++++++++++++++ doc/development/performance.md | 3 ++- 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 doc/administration/monitoring/performance/img/request_profile_result.png create mode 100644 doc/administration/monitoring/performance/img/request_profiling_token.png create mode 100644 doc/administration/monitoring/performance/request_profiling.md diff --git a/CHANGELOG b/CHANGELOG index d2f1501bd2a..9def97ca342 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -115,6 +115,7 @@ v 8.13.0 (unreleased) - Fixes padding in all clipboard icons that have .btn class - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found + - Add docs for request profiling - Make guests unable to view MRs on private projects v 8.12.7 diff --git a/doc/README.md b/doc/README.md index 7e3d9b00900..c30bf328003 100644 --- a/doc/README.md +++ b/doc/README.md @@ -49,6 +49,7 @@ - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. - [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. +- [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests. - [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint. - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. diff --git a/doc/administration/monitoring/performance/img/request_profile_result.png b/doc/administration/monitoring/performance/img/request_profile_result.png new file mode 100644 index 00000000000..73e2fdcab67 Binary files /dev/null and b/doc/administration/monitoring/performance/img/request_profile_result.png differ diff --git a/doc/administration/monitoring/performance/img/request_profiling_token.png b/doc/administration/monitoring/performance/img/request_profiling_token.png new file mode 100644 index 00000000000..04d87567816 Binary files /dev/null and b/doc/administration/monitoring/performance/img/request_profiling_token.png differ diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md new file mode 100644 index 00000000000..c358dfbead2 --- /dev/null +++ b/doc/administration/monitoring/performance/request_profiling.md @@ -0,0 +1,16 @@ +# Request Profiling + +## Procedure +1. Grab the profiling token from `Monitoring > Requests Profiles` admin page +(highlighted in a blue in the image below). +![Profile token](img/request_profiling_token.png) +1. Pass the header `X-Profile-Token: ` to the request you want to profile. You can use any of these tools + * [ModHeader](https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj) Chrome extension + * [Modify Headers](https://addons.mozilla.org/en-US/firefox/addon/modify-headers/) Firefox extension + * `curl --header 'X-Profile-Token: ' https://gitlab.example.com/group/project` +1. Once request is finished (which will take a little longer than usual), you can +view the profiling output from `Monitoring > Requests Profiles` admin page. +![Profiling output](img/request_profile_result.png) + +## Cleaning up +Profiling output will be cleared out every day via a Sidekiq worker. diff --git a/doc/development/performance.md b/doc/development/performance.md index 7ff603e2c4a..65d34829025 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -34,10 +34,11 @@ graphs/dashboards. ## Tooling -GitLab provides two built-in tools to aid the process of improving performance: +GitLab provides built-in tools to aid the process of improving performance: * [Sherlock](profiling.md#sherlock) * [GitLab Performance Monitoring](../monitoring/performance/monitoring.md) +* [Request Profiling](../administration/monitoring/performance/request_profiling.md) GitLab employees can use GitLab.com's performance monitoring systems located at , this requires you to log in using your -- cgit v1.2.1 From e88aa25f42c394785d4c176046f10c815250c8b1 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 14 Oct 2016 15:39:08 -0500 Subject: Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint --- .scss-lint.yml | 4 +- CHANGELOG | 1 + app/assets/stylesheets/behaviors.scss | 1 + app/assets/stylesheets/framework/blocks.scss | 2 + app/assets/stylesheets/framework/buttons.scss | 6 +- app/assets/stylesheets/framework/callout.scss | 5 + app/assets/stylesheets/framework/common.scss | 39 +-- app/assets/stylesheets/framework/dropdowns.scss | 2 + app/assets/stylesheets/framework/files.scss | 9 + app/assets/stylesheets/framework/forms.scss | 4 +- app/assets/stylesheets/framework/gitlab-theme.scss | 2 +- app/assets/stylesheets/framework/header.scss | 1 + app/assets/stylesheets/framework/lists.scss | 1 + app/assets/stylesheets/framework/logo.scss | 7 + app/assets/stylesheets/framework/mixins.scss | 1 + app/assets/stylesheets/framework/nav.scss | 1 + app/assets/stylesheets/framework/selects.scss | 4 + app/assets/stylesheets/framework/timeline.scss | 1 + app/assets/stylesheets/framework/tw_bootstrap.scss | 10 + app/assets/stylesheets/framework/typography.scss | 1 + app/assets/stylesheets/highlight/dark.scss | 128 ++++----- app/assets/stylesheets/highlight/monokai.scss | 118 ++++----- .../stylesheets/highlight/solarized_dark.scss | 136 +++++----- .../stylesheets/highlight/solarized_light.scss | 136 +++++----- app/assets/stylesheets/highlight/white.scss | 2 +- .../stylesheets/mailers/repository_push_email.scss | 2 +- app/assets/stylesheets/notify.scss | 10 +- app/assets/stylesheets/pages/admin.scss | 12 +- app/assets/stylesheets/pages/builds.scss | 2 + app/assets/stylesheets/pages/commit.scss | 6 + app/assets/stylesheets/pages/commits.scss | 1 + app/assets/stylesheets/pages/dashboard.scss | 2 + app/assets/stylesheets/pages/diff.scss | 24 ++ app/assets/stylesheets/pages/editor.scss | 5 + app/assets/stylesheets/pages/events.scss | 8 +- app/assets/stylesheets/pages/groups.scss | 1 + app/assets/stylesheets/pages/help.scss | 8 +- app/assets/stylesheets/pages/issuable.scss | 8 +- app/assets/stylesheets/pages/issues.scss | 1 + app/assets/stylesheets/pages/labels.scss | 1 + app/assets/stylesheets/pages/lint.scss | 1 + app/assets/stylesheets/pages/login.scss | 2 +- app/assets/stylesheets/pages/merge_conflicts.scss | 2 + app/assets/stylesheets/pages/merge_requests.scss | 4 + app/assets/stylesheets/pages/milestone.scss | 1 + app/assets/stylesheets/pages/note_form.scss | 2 + app/assets/stylesheets/pages/notes.scss | 2 + app/assets/stylesheets/pages/pipelines.scss | 4 +- app/assets/stylesheets/pages/profile.scss | 1 + app/assets/stylesheets/pages/projects.scss | 18 ++ app/assets/stylesheets/pages/runners.scss | 1 + app/assets/stylesheets/pages/status.scss | 2 + app/assets/stylesheets/pages/tree.scss | 1 + app/assets/stylesheets/pages/xterm.scss | 290 ++++++++++++++++++++- 54 files changed, 746 insertions(+), 298 deletions(-) diff --git a/.scss-lint.yml b/.scss-lint.yml index 71df6be6a15..5093702519b 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -61,7 +61,7 @@ linters: # Separate rule, function, and mixin declarations with empty lines. EmptyLineBetweenBlocks: - enabled: false + enabled: true # Reports when you have an empty rule set. EmptyRule: @@ -219,7 +219,7 @@ linters: # Property values, @extend, @include, and @import directives, and variable # declarations should always end with a semicolon. TrailingSemicolon: - enabled: false + enabled: true # Reports lines containing trailing whitespace. TrailingWhitespace: diff --git a/CHANGELOG b/CHANGELOG index 3d6347c5395..194ee18b74c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.13.0 (unreleased) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page - Improve tabbing usability for sign in page (ClemMakesApps) + - Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint - Adding members no longer silently fails when there is extra whitespace - Update runner version only when updating contacted_at - Add link from system note to compare with previous version diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index 897bc49e7df..e3ca7f6373a 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -5,6 +5,7 @@ display: none; &.hide { display: block; } } + &.open .content { display: block; &.hide { display: none; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 8002e56724b..df2e2ea8d2c 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -19,6 +19,7 @@ &.diff-collapsed { padding: 5px; + .click-to-expand { cursor: pointer; } @@ -203,6 +204,7 @@ } } } + &.user-cover-block { padding: 24px 0 0; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index a7c8d782e9b..7c0ed72dbc5 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -25,7 +25,7 @@ &:focus { background-color: $hover-background; color: $hover-text; - border-color: $hover-border;; + border-color: $hover-border; } } @@ -240,6 +240,7 @@ width: 100%; margin: 0; margin-bottom: 15px; + &.btn { padding: 6px 0; } @@ -321,6 +322,7 @@ .btn-build { margin-left: 10px; + i { color: $gl-icon-color; } @@ -328,6 +330,7 @@ .clone-dropdown-btn a { color: $dropdown-link-color; + &:hover { text-decoration: none; } @@ -337,6 +340,7 @@ background-color: $background-color !important; border: 1px solid lightgrey; cursor: default; + &:active { -moz-box-shadow: inset 0 0 0 white; -webkit-box-shadow: inset 0 0 0 white; diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index da7bab74a32..f3b6ad88ad6 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -13,10 +13,12 @@ color: $text-color; background: $background-color; } + .bs-callout h4 { margin-top: 0; margin-bottom: 5px; } + .bs-callout p:last-child { margin-bottom: 0; } @@ -27,16 +29,19 @@ border-color: #eed3d7; color: #b94a48; } + .bs-callout-warning { background-color: #faf8f0; border-color: #faebcc; color: #8a6d3b; } + .bs-callout-info { background-color: #f4f8fa; border-color: #bce8f1; color: #34789a; } + .bs-callout-success { background-color: #dff0d8; border-color: #5ca64d; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 5957dce89bc..81e4e264560 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -1,31 +1,31 @@ /** COLORS **/ .cgray { color: $gl-gray; } -.clgray { color: #bbb } +.clgray { color: #bbb; } .cred { color: $gl-text-red; } .cgreen { color: $gl-text-green; } -.cdark { color: #444 } +.cdark { color: #444; } /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } .prepend-top-5 { margin-top: 5px; } -.prepend-top-10 { margin-top: 10px } +.prepend-top-10 { margin-top: 10px; } .prepend-top-default { margin-top: $gl-padding !important; } -.prepend-top-20 { margin-top: 20px } -.prepend-left-5 { margin-left: 5px } -.prepend-left-10 { margin-left: 10px } +.prepend-top-20 { margin-top: 20px; } +.prepend-left-5 { margin-left: 5px; } +.prepend-left-10 { margin-left: 10px; } .prepend-left-default { margin-left: $gl-padding; } -.prepend-left-20 { margin-left: 20px } -.append-right-5 { margin-right: 5px } -.append-right-10 { margin-right: 10px } +.prepend-left-20 { margin-left: 20px; } +.append-right-5 { margin-right: 5px; } +.append-right-10 { margin-right: 10px; } .append-right-default { margin-right: $gl-padding; } -.append-right-20 { margin-right: 20px } -.append-bottom-0 { margin-bottom: 0 } -.append-bottom-10 { margin-bottom: 10px } -.append-bottom-15 { margin-bottom: 15px } -.append-bottom-20 { margin-bottom: 20px } +.append-right-20 { margin-right: 20px; } +.append-bottom-0 { margin-bottom: 0; } +.append-bottom-10 { margin-bottom: 10px; } +.append-bottom-15 { margin-bottom: 15px; } +.append-bottom-20 { margin-bottom: 20px; } .append-bottom-default { margin-bottom: $gl-padding; } -.inline { display: inline-block } -.center { text-align: center } +.inline { display: inline-block; } +.center { text-align: center; } .underlined-link { text-decoration: underline; } .hint { font-style: italic; color: #999; } @@ -97,6 +97,7 @@ span.update-author { color: #999; font-weight: normal; font-style: italic; + strong { font-weight: bold; font-style: normal; @@ -128,7 +129,7 @@ p.time { // Fix issue with notes & lists creating a bunch of bottom borders. li.note { - img { max-width: 100% } + img { max-width: 100%; } .note-title { li { border-bottom: none !important; @@ -172,6 +173,7 @@ li.note { @extend .col-md-6; text-align: left; margin-top: 40px; + pre { background: white; border: none; @@ -197,6 +199,7 @@ li.note { background: #c67; color: #fff; font-weight: bold; + a { color: #fff; text-decoration: underline; @@ -227,6 +230,7 @@ li.note { &.milestone-closed { background: $gray-light; } + .progress { margin-bottom: 0; margin-top: 4px; @@ -286,6 +290,7 @@ table { .footer-links { margin-bottom: 20px; + a { margin-right: 15px; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index baa95711329..a839371a6f2 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -12,6 +12,7 @@ .dropdown-menu, .dropdown-menu-nav { display: block; + @media (max-width: $screen-xs-max) { width: 100%; } @@ -48,6 +49,7 @@ margin-top: -6px; color: $dropdown-toggle-icon-color; font-size: 10px; + &.fa-spinner { font-size: 16px; margin-top: -8px; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 76a3c083697..13c1bbf0359 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -57,6 +57,7 @@ margin-top: -3px; } } + .file-content { background: #fff; @@ -96,22 +97,27 @@ border: none; margin: 0; } + tr { border-bottom: 1px solid #eee; } + td { &:first-child { border-left: none; } + &:last-child { border-right: none; } } + td.blame-commit { padding: 0 10px; min-width: 400px; background: $gray-light; } + td.line-numbers { float: none; border-left: 1px solid #ddd; @@ -121,6 +127,7 @@ margin-right: 0; } } + td.lines { padding: 0; } @@ -137,8 +144,10 @@ border-left: 1px solid $border-color; margin-bottom: 0; background: white; + li { color: #888; + p { margin: 0; color: #333; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 3d01179f074..311e3fa1a35 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -9,7 +9,7 @@ input { input[type='text'].danger { background: #f2dede!important; border-color: #d66; - text-shadow: 0 1px 1px #fff + text-shadow: 0 1px 1px #fff; } .datetime-controls { @@ -117,9 +117,11 @@ label { display: table-cell; width: 200px !important; } + .input-group-addon { background-color: #f7f8fa; } + .input-group-addon:not(:first-child):not(:last-child) { border-left: 0; border-right: 0; diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 3673b81f183..fe834f4e2f6 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -62,7 +62,7 @@ } i { - color: $white-light + color: $white-light; } path, diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 9823abdde1f..3a4fdd0da22 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -168,6 +168,7 @@ header { a { color: $gl-text-color; + &:hover { text-decoration: underline; } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 9114425cfdd..4b2627c1b87 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -60,6 +60,7 @@ padding-top: 1px; margin: 0; color: $gray-dark; + img { position: relative; top: 3px; diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss index c214eabcad7..a90e45bb5f4 100644 --- a/app/assets/stylesheets/framework/logo.scss +++ b/app/assets/stylesheets/framework/logo.scss @@ -37,6 +37,7 @@ 0%, 10%, 100% { fill: lighten($tanuki-yellow, 25%); } + 90% { fill: $tanuki-yellow; } @@ -48,6 +49,7 @@ 10%, 80% { fill: $tanuki-orange; } + 20%, 90% { fill: lighten($tanuki-orange, 25%); } @@ -59,6 +61,7 @@ 10%, 80% { fill: $tanuki-red; } + 20%, 90% { fill: lighten($tanuki-red, 25%); } @@ -70,6 +73,7 @@ 20%, 70% { fill: $tanuki-red; } + 30%, 80% { fill: lighten($tanuki-red, 25%); } @@ -81,6 +85,7 @@ 30%, 60% { fill: $tanuki-orange; } + 40%, 70% { fill: lighten($tanuki-orange, 25%); } @@ -92,6 +97,7 @@ 30%, 60% { fill: $tanuki-red; } + 40%, 70% { fill: lighten($tanuki-red, 25%); } @@ -103,6 +109,7 @@ 40% { fill: $tanuki-yellow; } + 60% { fill: lighten($tanuki-yellow, 25%); } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 7fabf27a558..f84ca36d10f 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -34,6 +34,7 @@ &.active { background: $gray-light; + a { font-weight: 600; } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index ea43f4afc37..899db045b74 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -210,6 +210,7 @@ @media (max-width: $screen-xs-max) { padding-bottom: 0; width: 100%; + .btn, form, .dropdown, .dropdown-menu-toggle, .form-control { margin: 0 0 10px; display: block; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index bf9208f83f3..e0708c65695 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -137,6 +137,7 @@ .select2-results { max-height: 350px; + .select2-highlighted { background: $gl-primary; } @@ -212,9 +213,11 @@ .group-image { float: left; } + .group-name { font-weight: bold; } + .group-path { color: #999; } @@ -239,6 +242,7 @@ color: #aaa; font-weight: normal; } + .namespace-path { margin-left: 10px; font-weight: bolder; diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index 0b0bd80c326..eb63a9f214b 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -48,6 +48,7 @@ &:before { background: none; } + .timeline-entry .timeline-entry-inner { .timeline-icon { display: none; diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index e3154657c54..f4106641269 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -48,31 +48,40 @@ .clearfix { @include clearfix(); } + .center-block { @include center-block(); } + .pull-right { float: right !important; } + .pull-left { float: left !important; } + .hide { display: none; } + .show { display: block !important; } + .invisible { visibility: hidden; } + .text-hide { @include text-hide(); } + .hidden { display: none !important; visibility: hidden !important; } + .affix { position: fixed; } @@ -146,6 +155,7 @@ padding: 6px 15px; font-size: 13px; font-weight: normal; + a { color: #777; } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index d099a884f54..8df0067fac1 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -106,6 +106,7 @@ @extend .table-bordered; margin: 12px 0; color: #5c5d5e; + th { background: #f8fafc; } diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 16ffbe57a99..a3acee299e3 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -55,68 +55,68 @@ color: #000 !important; } - .hll { background-color: #373b41 } - .c { color: #969896 } /* Comment */ - .err { color: #c66 } /* Error */ - .k { color: #b294bb } /* Keyword */ - .l { color: #de935f } /* Literal */ - .n { color: #c5c8c6 } /* Name */ - .o { color: #8abeb7 } /* Operator */ - .p { color: #c5c8c6 } /* Punctuation */ - .cm { color: #969896 } /* Comment.Multiline */ - .cp { color: #969896 } /* Comment.Preproc */ - .c1 { color: #969896 } /* Comment.Single */ - .cs { color: #969896 } /* Comment.Special */ - .gd { color: #c66 } /* Generic.Deleted */ - .ge { font-style: italic } /* Generic.Emph */ - .gh { color: #c5c8c6; font-weight: bold } /* Generic.Heading */ - .gi { color: #b5bd68 } /* Generic.Inserted */ - .gp { color: #969896; font-weight: bold } /* Generic.Prompt */ - .gs { font-weight: bold } /* Generic.Strong */ - .gu { color: #8abeb7; font-weight: bold } /* Generic.Subheading */ - .kc { color: #b294bb } /* Keyword.Constant */ - .kd { color: #b294bb } /* Keyword.Declaration */ - .kn { color: #8abeb7 } /* Keyword.Namespace */ - .kp { color: #b294bb } /* Keyword.Pseudo */ - .kr { color: #b294bb } /* Keyword.Reserved */ - .kt { color: #f0c674 } /* Keyword.Type */ - .ld { color: #b5bd68 } /* Literal.Date */ - .m { color: #de935f } /* Literal.Number */ - .s { color: #b5bd68 } /* Literal.String */ - .na { color: #81a2be } /* Name.Attribute */ - .nb { color: #c5c8c6 } /* Name.Builtin */ - .nc { color: #f0c674 } /* Name.Class */ - .no { color: #c66 } /* Name.Constant */ - .nd { color: #8abeb7 } /* Name.Decorator */ - .ni { color: #c5c8c6 } /* Name.Entity */ - .ne { color: #c66 } /* Name.Exception */ - .nf { color: #81a2be } /* Name.Function */ - .nl { color: #c5c8c6 } /* Name.Label */ - .nn { color: #f0c674 } /* Name.Namespace */ - .nx { color: #81a2be } /* Name.Other */ - .py { color: #c5c8c6 } /* Name.Property */ - .nt { color: #8abeb7 } /* Name.Tag */ - .nv { color: #c66 } /* Name.Variable */ - .ow { color: #8abeb7 } /* Operator.Word */ - .w { color: #c5c8c6 } /* Text.Whitespace */ - .mf { color: #de935f } /* Literal.Number.Float */ - .mh { color: #de935f } /* Literal.Number.Hex */ - .mi { color: #de935f } /* Literal.Number.Integer */ - .mo { color: #de935f } /* Literal.Number.Oct */ - .sb { color: #b5bd68 } /* Literal.String.Backtick */ - .sc { color: #c5c8c6 } /* Literal.String.Char */ - .sd { color: #969896 } /* Literal.String.Doc */ - .s2 { color: #b5bd68 } /* Literal.String.Double */ - .se { color: #de935f } /* Literal.String.Escape */ - .sh { color: #b5bd68 } /* Literal.String.Heredoc */ - .si { color: #de935f } /* Literal.String.Interpol */ - .sx { color: #b5bd68 } /* Literal.String.Other */ - .sr { color: #b5bd68 } /* Literal.String.Regex */ - .s1 { color: #b5bd68 } /* Literal.String.Single */ - .ss { color: #b5bd68 } /* Literal.String.Symbol */ - .bp { color: #c5c8c6 } /* Name.Builtin.Pseudo */ - .vc { color: #c66 } /* Name.Variable.Class */ - .vg { color: #c66 } /* Name.Variable.Global */ - .vi { color: #c66 } /* Name.Variable.Instance */ - .il { color: #de935f } /* Literal.Number.Integer.Long */ + .hll { background-color: #373b41; } + .c { color: #969896; } /* Comment */ + .err { color: #c66; } /* Error */ + .k { color: #b294bb; } /* Keyword */ + .l { color: #de935f; } /* Literal */ + .n { color: #c5c8c6; } /* Name */ + .o { color: #8abeb7; } /* Operator */ + .p { color: #c5c8c6; } /* Punctuation */ + .cm { color: #969896; } /* Comment.Multiline */ + .cp { color: #969896; } /* Comment.Preproc */ + .c1 { color: #969896; } /* Comment.Single */ + .cs { color: #969896; } /* Comment.Special */ + .gd { color: #c66; } /* Generic.Deleted */ + .ge { font-style: italic; } /* Generic.Emph */ + .gh { color: #c5c8c6; font-weight: bold; } /* Generic.Heading */ + .gi { color: #b5bd68; } /* Generic.Inserted */ + .gp { color: #969896; font-weight: bold; } /* Generic.Prompt */ + .gs { font-weight: bold; } /* Generic.Strong */ + .gu { color: #8abeb7; font-weight: bold; } /* Generic.Subheading */ + .kc { color: #b294bb; } /* Keyword.Constant */ + .kd { color: #b294bb; } /* Keyword.Declaration */ + .kn { color: #8abeb7; } /* Keyword.Namespace */ + .kp { color: #b294bb; } /* Keyword.Pseudo */ + .kr { color: #b294bb; } /* Keyword.Reserved */ + .kt { color: #f0c674; } /* Keyword.Type */ + .ld { color: #b5bd68; } /* Literal.Date */ + .m { color: #de935f; } /* Literal.Number */ + .s { color: #b5bd68; } /* Literal.String */ + .na { color: #81a2be; } /* Name.Attribute */ + .nb { color: #c5c8c6; } /* Name.Builtin */ + .nc { color: #f0c674; } /* Name.Class */ + .no { color: #c66; } /* Name.Constant */ + .nd { color: #8abeb7; } /* Name.Decorator */ + .ni { color: #c5c8c6; } /* Name.Entity */ + .ne { color: #c66; } /* Name.Exception */ + .nf { color: #81a2be; } /* Name.Function */ + .nl { color: #c5c8c6; } /* Name.Label */ + .nn { color: #f0c674; } /* Name.Namespace */ + .nx { color: #81a2be; } /* Name.Other */ + .py { color: #c5c8c6; } /* Name.Property */ + .nt { color: #8abeb7; } /* Name.Tag */ + .nv { color: #c66; } /* Name.Variable */ + .ow { color: #8abeb7; } /* Operator.Word */ + .w { color: #c5c8c6; } /* Text.Whitespace */ + .mf { color: #de935f; } /* Literal.Number.Float */ + .mh { color: #de935f; } /* Literal.Number.Hex */ + .mi { color: #de935f; } /* Literal.Number.Integer */ + .mo { color: #de935f; } /* Literal.Number.Oct */ + .sb { color: #b5bd68; } /* Literal.String.Backtick */ + .sc { color: #c5c8c6; } /* Literal.String.Char */ + .sd { color: #969896; } /* Literal.String.Doc */ + .s2 { color: #b5bd68; } /* Literal.String.Double */ + .se { color: #de935f; } /* Literal.String.Escape */ + .sh { color: #b5bd68; } /* Literal.String.Heredoc */ + .si { color: #de935f; } /* Literal.String.Interpol */ + .sx { color: #b5bd68; } /* Literal.String.Other */ + .sr { color: #b5bd68; } /* Literal.String.Regex */ + .s1 { color: #b5bd68; } /* Literal.String.Single */ + .ss { color: #b5bd68; } /* Literal.String.Symbol */ + .bp { color: #c5c8c6; } /* Name.Builtin.Pseudo */ + .vc { color: #c66; } /* Name.Variable.Class */ + .vg { color: #c66; } /* Name.Variable.Global */ + .vi { color: #c66; } /* Name.Variable.Instance */ + .il { color: #de935f; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 7de920e074b..e9228c94db9 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -55,65 +55,65 @@ color: #000 !important; } - .hll { background-color: #49483e } - .c { color: #75715e } /* Comment */ - .err { color: #960050; background-color: #1e0010 } /* Error */ - .k { color: #66d9ef } /* Keyword */ - .l { color: #ae81ff } /* Literal */ - .n { color: #f8f8f2 } /* Name */ - .o { color: #f92672 } /* Operator */ - .p { color: #f8f8f2 } /* Punctuation */ - .cm { color: #75715e } /* Comment.Multiline */ - .cp { color: #75715e } /* Comment.Preproc */ - .c1 { color: #75715e } /* Comment.Single */ - .cs { color: #75715e } /* Comment.Special */ - .ge { font-style: italic } /* Generic.Emph */ - .gs { font-weight: bold } /* Generic.Strong */ - .kc { color: #66d9ef } /* Keyword.Constant */ - .kd { color: #66d9ef } /* Keyword.Declaration */ - .kn { color: #f92672 } /* Keyword.Namespace */ - .kp { color: #66d9ef } /* Keyword.Pseudo */ - .kr { color: #66d9ef } /* Keyword.Reserved */ - .kt { color: #66d9ef } /* Keyword.Type */ - .ld { color: #e6db74 } /* Literal.Date */ - .m { color: #ae81ff } /* Literal.Number */ - .s { color: #e6db74 } /* Literal.String */ - .na { color: #a6e22e } /* Name.Attribute */ - .nb { color: #f8f8f2 } /* Name.Builtin */ - .nc { color: #a6e22e } /* Name.Class */ - .no { color: #66d9ef } /* Name.Constant */ - .nd { color: #a6e22e } /* Name.Decorator */ - .ni { color: #f8f8f2 } /* Name.Entity */ - .ne { color: #a6e22e } /* Name.Exception */ - .nf { color: #a6e22e } /* Name.Function */ - .nl { color: #f8f8f2 } /* Name.Label */ - .nn { color: #f8f8f2 } /* Name.Namespace */ - .nx { color: #a6e22e } /* Name.Other */ - .py { color: #f8f8f2 } /* Name.Property */ - .nt { color: #f92672 } /* Name.Tag */ - .nv { color: #f8f8f2 } /* Name.Variable */ - .ow { color: #f92672 } /* Operator.Word */ - .w { color: #f8f8f2 } /* Text.Whitespace */ - .mf { color: #ae81ff } /* Literal.Number.Float */ - .mh { color: #ae81ff } /* Literal.Number.Hex */ - .mi { color: #ae81ff } /* Literal.Number.Integer */ - .mo { color: #ae81ff } /* Literal.Number.Oct */ - .sb { color: #e6db74 } /* Literal.String.Backtick */ - .sc { color: #e6db74 } /* Literal.String.Char */ - .sd { color: #e6db74 } /* Literal.String.Doc */ - .s2 { color: #e6db74 } /* Literal.String.Double */ - .se { color: #ae81ff } /* Literal.String.Escape */ - .sh { color: #e6db74 } /* Literal.String.Heredoc */ - .si { color: #e6db74 } /* Literal.String.Interpol */ - .sx { color: #e6db74 } /* Literal.String.Other */ - .sr { color: #e6db74 } /* Literal.String.Regex */ - .s1 { color: #e6db74 } /* Literal.String.Single */ - .ss { color: #e6db74 } /* Literal.String.Symbol */ - .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ - .vc { color: #f8f8f2 } /* Name.Variable.Class */ - .vg { color: #f8f8f2 } /* Name.Variable.Global */ - .vi { color: #f8f8f2 } /* Name.Variable.Instance */ - .il { color: #ae81ff } /* Literal.Number.Integer.Long */ + .hll { background-color: #49483e; } + .c { color: #75715e; } /* Comment */ + .err { color: #960050; background-color: #1e0010; } /* Error */ + .k { color: #66d9ef; } /* Keyword */ + .l { color: #ae81ff; } /* Literal */ + .n { color: #f8f8f2; } /* Name */ + .o { color: #f92672; } /* Operator */ + .p { color: #f8f8f2; } /* Punctuation */ + .cm { color: #75715e; } /* Comment.Multiline */ + .cp { color: #75715e; } /* Comment.Preproc */ + .c1 { color: #75715e; } /* Comment.Single */ + .cs { color: #75715e; } /* Comment.Special */ + .ge { font-style: italic; } /* Generic.Emph */ + .gs { font-weight: bold; } /* Generic.Strong */ + .kc { color: #66d9ef; } /* Keyword.Constant */ + .kd { color: #66d9ef; } /* Keyword.Declaration */ + .kn { color: #f92672; } /* Keyword.Namespace */ + .kp { color: #66d9ef; } /* Keyword.Pseudo */ + .kr { color: #66d9ef; } /* Keyword.Reserved */ + .kt { color: #66d9ef; } /* Keyword.Type */ + .ld { color: #e6db74; } /* Literal.Date */ + .m { color: #ae81ff; } /* Literal.Number */ + .s { color: #e6db74; } /* Literal.String */ + .na { color: #a6e22e; } /* Name.Attribute */ + .nb { color: #f8f8f2; } /* Name.Builtin */ + .nc { color: #a6e22e; } /* Name.Class */ + .no { color: #66d9ef; } /* Name.Constant */ + .nd { color: #a6e22e; } /* Name.Decorator */ + .ni { color: #f8f8f2; } /* Name.Entity */ + .ne { color: #a6e22e; } /* Name.Exception */ + .nf { color: #a6e22e; } /* Name.Function */ + .nl { color: #f8f8f2; } /* Name.Label */ + .nn { color: #f8f8f2; } /* Name.Namespace */ + .nx { color: #a6e22e; } /* Name.Other */ + .py { color: #f8f8f2; } /* Name.Property */ + .nt { color: #f92672; } /* Name.Tag */ + .nv { color: #f8f8f2; } /* Name.Variable */ + .ow { color: #f92672; } /* Operator.Word */ + .w { color: #f8f8f2; } /* Text.Whitespace */ + .mf { color: #ae81ff; } /* Literal.Number.Float */ + .mh { color: #ae81ff; } /* Literal.Number.Hex */ + .mi { color: #ae81ff; } /* Literal.Number.Integer */ + .mo { color: #ae81ff; } /* Literal.Number.Oct */ + .sb { color: #e6db74; } /* Literal.String.Backtick */ + .sc { color: #e6db74; } /* Literal.String.Char */ + .sd { color: #e6db74; } /* Literal.String.Doc */ + .s2 { color: #e6db74; } /* Literal.String.Double */ + .se { color: #ae81ff; } /* Literal.String.Escape */ + .sh { color: #e6db74; } /* Literal.String.Heredoc */ + .si { color: #e6db74; } /* Literal.String.Interpol */ + .sx { color: #e6db74; } /* Literal.String.Other */ + .sr { color: #e6db74; } /* Literal.String.Regex */ + .s1 { color: #e6db74; } /* Literal.String.Single */ + .ss { color: #e6db74; } /* Literal.String.Symbol */ + .bp { color: #f8f8f2; } /* Name.Builtin.Pseudo */ + .vc { color: #f8f8f2; } /* Name.Variable.Class */ + .vg { color: #f8f8f2; } /* Name.Variable.Global */ + .vi { color: #f8f8f2; } /* Name.Variable.Instance */ + .il { color: #ae81ff; } /* Literal.Number.Integer.Long */ .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index b11499c71ee..c3c7773b9e2 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -72,72 +72,72 @@ green #859900 operators, other keywords */ - .c { color: #586e75 } /* Comment */ - .err { color: #93a1a1 } /* Error */ - .g { color: #93a1a1 } /* Generic */ - .k { color: #859900 } /* Keyword */ - .l { color: #93a1a1 } /* Literal */ - .n { color: #93a1a1 } /* Name */ - .o { color: #859900 } /* Operator */ - .x { color: #cb4b16 } /* Other */ - .p { color: #93a1a1 } /* Punctuation */ - .cm { color: #586e75 } /* Comment.Multiline */ - .cp { color: #859900 } /* Comment.Preproc */ - .c1 { color: #586e75 } /* Comment.Single */ - .cs { color: #859900 } /* Comment.Special */ - .gd { color: #2aa198 } /* Generic.Deleted */ - .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ - .gr { color: #dc322f } /* Generic.Error */ - .gh { color: #cb4b16 } /* Generic.Heading */ - .gi { color: #859900 } /* Generic.Inserted */ - .go { color: #93a1a1 } /* Generic.Output */ - .gp { color: #93a1a1 } /* Generic.Prompt */ - .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ - .gu { color: #cb4b16 } /* Generic.Subheading */ - .gt { color: #93a1a1 } /* Generic.Traceback */ - .kc { color: #cb4b16 } /* Keyword.Constant */ - .kd { color: #268bd2 } /* Keyword.Declaration */ - .kn { color: #859900 } /* Keyword.Namespace */ - .kp { color: #859900 } /* Keyword.Pseudo */ - .kr { color: #268bd2 } /* Keyword.Reserved */ - .kt { color: #dc322f } /* Keyword.Type */ - .ld { color: #93a1a1 } /* Literal.Date */ - .m { color: #2aa198 } /* Literal.Number */ - .s { color: #2aa198 } /* Literal.String */ - .na { color: #93a1a1 } /* Name.Attribute */ - .nb { color: #b58900 } /* Name.Builtin */ - .nc { color: #268bd2 } /* Name.Class */ - .no { color: #cb4b16 } /* Name.Constant */ - .nd { color: #268bd2 } /* Name.Decorator */ - .ni { color: #cb4b16 } /* Name.Entity */ - .ne { color: #cb4b16 } /* Name.Exception */ - .nf { color: #268bd2 } /* Name.Function */ - .nl { color: #93a1a1 } /* Name.Label */ - .nn { color: #93a1a1 } /* Name.Namespace */ - .nx { color: #93a1a1 } /* Name.Other */ - .py { color: #93a1a1 } /* Name.Property */ - .nt { color: #268bd2 } /* Name.Tag */ - .nv { color: #268bd2 } /* Name.Variable */ - .ow { color: #859900 } /* Operator.Word */ - .w { color: #93a1a1 } /* Text.Whitespace */ - .mf { color: #2aa198 } /* Literal.Number.Float */ - .mh { color: #2aa198 } /* Literal.Number.Hex */ - .mi { color: #2aa198 } /* Literal.Number.Integer */ - .mo { color: #2aa198 } /* Literal.Number.Oct */ - .sb { color: #586e75 } /* Literal.String.Backtick */ - .sc { color: #2aa198 } /* Literal.String.Char */ - .sd { color: #93a1a1 } /* Literal.String.Doc */ - .s2 { color: #2aa198 } /* Literal.String.Double */ - .se { color: #cb4b16 } /* Literal.String.Escape */ - .sh { color: #93a1a1 } /* Literal.String.Heredoc */ - .si { color: #2aa198 } /* Literal.String.Interpol */ - .sx { color: #2aa198 } /* Literal.String.Other */ - .sr { color: #dc322f } /* Literal.String.Regex */ - .s1 { color: #2aa198 } /* Literal.String.Single */ - .ss { color: #2aa198 } /* Literal.String.Symbol */ - .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2 } /* Name.Variable.Class */ - .vg { color: #268bd2 } /* Name.Variable.Global */ - .vi { color: #268bd2 } /* Name.Variable.Instance */ - .il { color: #2aa198 } /* Literal.Number.Integer.Long */ + .c { color: #586e75; } /* Comment */ + .err { color: #93a1a1; } /* Error */ + .g { color: #93a1a1; } /* Generic */ + .k { color: #859900; } /* Keyword */ + .l { color: #93a1a1; } /* Literal */ + .n { color: #93a1a1; } /* Name */ + .o { color: #859900; } /* Operator */ + .x { color: #cb4b16; } /* Other */ + .p { color: #93a1a1; } /* Punctuation */ + .cm { color: #586e75; } /* Comment.Multiline */ + .cp { color: #859900; } /* Comment.Preproc */ + .c1 { color: #586e75; } /* Comment.Single */ + .cs { color: #859900; } /* Comment.Special */ + .gd { color: #2aa198; } /* Generic.Deleted */ + .ge { color: #93a1a1; font-style: italic; } /* Generic.Emph */ + .gr { color: #dc322f; } /* Generic.Error */ + .gh { color: #cb4b16; } /* Generic.Heading */ + .gi { color: #859900; } /* Generic.Inserted */ + .go { color: #93a1a1; } /* Generic.Output */ + .gp { color: #93a1a1; } /* Generic.Prompt */ + .gs { color: #93a1a1; font-weight: bold; } /* Generic.Strong */ + .gu { color: #cb4b16; } /* Generic.Subheading */ + .gt { color: #93a1a1; } /* Generic.Traceback */ + .kc { color: #cb4b16; } /* Keyword.Constant */ + .kd { color: #268bd2; } /* Keyword.Declaration */ + .kn { color: #859900; } /* Keyword.Namespace */ + .kp { color: #859900; } /* Keyword.Pseudo */ + .kr { color: #268bd2; } /* Keyword.Reserved */ + .kt { color: #dc322f; } /* Keyword.Type */ + .ld { color: #93a1a1; } /* Literal.Date */ + .m { color: #2aa198; } /* Literal.Number */ + .s { color: #2aa198; } /* Literal.String */ + .na { color: #93a1a1; } /* Name.Attribute */ + .nb { color: #b58900; } /* Name.Builtin */ + .nc { color: #268bd2; } /* Name.Class */ + .no { color: #cb4b16; } /* Name.Constant */ + .nd { color: #268bd2; } /* Name.Decorator */ + .ni { color: #cb4b16; } /* Name.Entity */ + .ne { color: #cb4b16; } /* Name.Exception */ + .nf { color: #268bd2; } /* Name.Function */ + .nl { color: #93a1a1; } /* Name.Label */ + .nn { color: #93a1a1; } /* Name.Namespace */ + .nx { color: #93a1a1; } /* Name.Other */ + .py { color: #93a1a1; } /* Name.Property */ + .nt { color: #268bd2; } /* Name.Tag */ + .nv { color: #268bd2; } /* Name.Variable */ + .ow { color: #859900; } /* Operator.Word */ + .w { color: #93a1a1; } /* Text.Whitespace */ + .mf { color: #2aa198; } /* Literal.Number.Float */ + .mh { color: #2aa198; } /* Literal.Number.Hex */ + .mi { color: #2aa198; } /* Literal.Number.Integer */ + .mo { color: #2aa198; } /* Literal.Number.Oct */ + .sb { color: #586e75; } /* Literal.String.Backtick */ + .sc { color: #2aa198; } /* Literal.String.Char */ + .sd { color: #93a1a1; } /* Literal.String.Doc */ + .s2 { color: #2aa198; } /* Literal.String.Double */ + .se { color: #cb4b16; } /* Literal.String.Escape */ + .sh { color: #93a1a1; } /* Literal.String.Heredoc */ + .si { color: #2aa198; } /* Literal.String.Interpol */ + .sx { color: #2aa198; } /* Literal.String.Other */ + .sr { color: #dc322f; } /* Literal.String.Regex */ + .s1 { color: #2aa198; } /* Literal.String.Single */ + .ss { color: #2aa198; } /* Literal.String.Symbol */ + .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ + .vc { color: #268bd2; } /* Name.Variable.Class */ + .vg { color: #268bd2; } /* Name.Variable.Global */ + .vi { color: #268bd2; } /* Name.Variable.Instance */ + .il { color: #2aa198; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 657bb5e3cd9..5956a28cafe 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -78,72 +78,72 @@ green #859900 operators, other keywords */ - .c { color: #93a1a1 } /* Comment */ - .err { color: #586e75 } /* Error */ - .g { color: #586e75 } /* Generic */ - .k { color: #859900 } /* Keyword */ - .l { color: #586e75 } /* Literal */ - .n { color: #586e75 } /* Name */ - .o { color: #859900 } /* Operator */ - .x { color: #cb4b16 } /* Other */ - .p { color: #586e75 } /* Punctuation */ - .cm { color: #93a1a1 } /* Comment.Multiline */ - .cp { color: #859900 } /* Comment.Preproc */ - .c1 { color: #93a1a1 } /* Comment.Single */ - .cs { color: #859900 } /* Comment.Special */ - .gd { color: #2aa198 } /* Generic.Deleted */ - .ge { color: #586e75; font-style: italic } /* Generic.Emph */ - .gr { color: #dc322f } /* Generic.Error */ - .gh { color: #cb4b16 } /* Generic.Heading */ - .gi { color: #859900 } /* Generic.Inserted */ - .go { color: #586e75 } /* Generic.Output */ - .gp { color: #586e75 } /* Generic.Prompt */ - .gs { color: #586e75; font-weight: bold } /* Generic.Strong */ - .gu { color: #cb4b16 } /* Generic.Subheading */ - .gt { color: #586e75 } /* Generic.Traceback */ - .kc { color: #cb4b16 } /* Keyword.Constant */ - .kd { color: #268bd2 } /* Keyword.Declaration */ - .kn { color: #859900 } /* Keyword.Namespace */ - .kp { color: #859900 } /* Keyword.Pseudo */ - .kr { color: #268bd2 } /* Keyword.Reserved */ - .kt { color: #dc322f } /* Keyword.Type */ - .ld { color: #586e75 } /* Literal.Date */ - .m { color: #2aa198 } /* Literal.Number */ - .s { color: #2aa198 } /* Literal.String */ - .na { color: #586e75 } /* Name.Attribute */ - .nb { color: #b58900 } /* Name.Builtin */ - .nc { color: #268bd2 } /* Name.Class */ - .no { color: #cb4b16 } /* Name.Constant */ - .nd { color: #268bd2 } /* Name.Decorator */ - .ni { color: #cb4b16 } /* Name.Entity */ - .ne { color: #cb4b16 } /* Name.Exception */ - .nf { color: #268bd2 } /* Name.Function */ - .nl { color: #586e75 } /* Name.Label */ - .nn { color: #586e75 } /* Name.Namespace */ - .nx { color: #586e75 } /* Name.Other */ - .py { color: #586e75 } /* Name.Property */ - .nt { color: #268bd2 } /* Name.Tag */ - .nv { color: #268bd2 } /* Name.Variable */ - .ow { color: #859900 } /* Operator.Word */ - .w { color: #586e75 } /* Text.Whitespace */ - .mf { color: #2aa198 } /* Literal.Number.Float */ - .mh { color: #2aa198 } /* Literal.Number.Hex */ - .mi { color: #2aa198 } /* Literal.Number.Integer */ - .mo { color: #2aa198 } /* Literal.Number.Oct */ - .sb { color: #93a1a1 } /* Literal.String.Backtick */ - .sc { color: #2aa198 } /* Literal.String.Char */ - .sd { color: #586e75 } /* Literal.String.Doc */ - .s2 { color: #2aa198 } /* Literal.String.Double */ - .se { color: #cb4b16 } /* Literal.String.Escape */ - .sh { color: #586e75 } /* Literal.String.Heredoc */ - .si { color: #2aa198 } /* Literal.String.Interpol */ - .sx { color: #2aa198 } /* Literal.String.Other */ - .sr { color: #dc322f } /* Literal.String.Regex */ - .s1 { color: #2aa198 } /* Literal.String.Single */ - .ss { color: #2aa198 } /* Literal.String.Symbol */ - .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2 } /* Name.Variable.Class */ - .vg { color: #268bd2 } /* Name.Variable.Global */ - .vi { color: #268bd2 } /* Name.Variable.Instance */ - .il { color: #2aa198 } /* Literal.Number.Integer.Long */ + .c { color: #93a1a1; } /* Comment */ + .err { color: #586e75; } /* Error */ + .g { color: #586e75; } /* Generic */ + .k { color: #859900; } /* Keyword */ + .l { color: #586e75; } /* Literal */ + .n { color: #586e75; } /* Name */ + .o { color: #859900; } /* Operator */ + .x { color: #cb4b16; } /* Other */ + .p { color: #586e75; } /* Punctuation */ + .cm { color: #93a1a1; } /* Comment.Multiline */ + .cp { color: #859900; } /* Comment.Preproc */ + .c1 { color: #93a1a1; } /* Comment.Single */ + .cs { color: #859900; } /* Comment.Special */ + .gd { color: #2aa198; } /* Generic.Deleted */ + .ge { color: #586e75; font-style: italic; } /* Generic.Emph */ + .gr { color: #dc322f; } /* Generic.Error */ + .gh { color: #cb4b16; } /* Generic.Heading */ + .gi { color: #859900; } /* Generic.Inserted */ + .go { color: #586e75; } /* Generic.Output */ + .gp { color: #586e75; } /* Generic.Prompt */ + .gs { color: #586e75; font-weight: bold; } /* Generic.Strong */ + .gu { color: #cb4b16; } /* Generic.Subheading */ + .gt { color: #586e75; } /* Generic.Traceback */ + .kc { color: #cb4b16; } /* Keyword.Constant */ + .kd { color: #268bd2; } /* Keyword.Declaration */ + .kn { color: #859900; } /* Keyword.Namespace */ + .kp { color: #859900; } /* Keyword.Pseudo */ + .kr { color: #268bd2; } /* Keyword.Reserved */ + .kt { color: #dc322f; } /* Keyword.Type */ + .ld { color: #586e75; } /* Literal.Date */ + .m { color: #2aa198; } /* Literal.Number */ + .s { color: #2aa198; } /* Literal.String */ + .na { color: #586e75; } /* Name.Attribute */ + .nb { color: #b58900; } /* Name.Builtin */ + .nc { color: #268bd2; } /* Name.Class */ + .no { color: #cb4b16; } /* Name.Constant */ + .nd { color: #268bd2; } /* Name.Decorator */ + .ni { color: #cb4b16; } /* Name.Entity */ + .ne { color: #cb4b16; } /* Name.Exception */ + .nf { color: #268bd2; } /* Name.Function */ + .nl { color: #586e75; } /* Name.Label */ + .nn { color: #586e75; } /* Name.Namespace */ + .nx { color: #586e75; } /* Name.Other */ + .py { color: #586e75; } /* Name.Property */ + .nt { color: #268bd2; } /* Name.Tag */ + .nv { color: #268bd2; } /* Name.Variable */ + .ow { color: #859900; } /* Operator.Word */ + .w { color: #586e75; } /* Text.Whitespace */ + .mf { color: #2aa198; } /* Literal.Number.Float */ + .mh { color: #2aa198; } /* Literal.Number.Hex */ + .mi { color: #2aa198; } /* Literal.Number.Integer */ + .mo { color: #2aa198; } /* Literal.Number.Oct */ + .sb { color: #93a1a1; } /* Literal.String.Backtick */ + .sc { color: #2aa198; } /* Literal.String.Char */ + .sd { color: #586e75; } /* Literal.String.Doc */ + .s2 { color: #2aa198; } /* Literal.String.Double */ + .se { color: #cb4b16; } /* Literal.String.Escape */ + .sh { color: #586e75; } /* Literal.String.Heredoc */ + .si { color: #2aa198; } /* Literal.String.Interpol */ + .sx { color: #2aa198; } /* Literal.String.Other */ + .sr { color: #dc322f; } /* Literal.String.Regex */ + .s1 { color: #2aa198; } /* Literal.String.Single */ + .ss { color: #2aa198; } /* Literal.String.Symbol */ + .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ + .vc { color: #268bd2; } /* Name.Variable.Class */ + .vg { color: #268bd2; } /* Name.Variable.Global */ + .vi { color: #268bd2; } /* Name.Variable.Instance */ + .il { color: #2aa198; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 36a80a916b2..6f31a5235c0 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -86,7 +86,7 @@ background-color: #fafe3d !important; } - .hll { background-color: #f8f8f8 } + .hll { background-color: #f8f8f8; } .c { color: #998; font-style: italic; } .err { color: #a61717; background-color: #e3d2d2; } .k { font-weight: bold; } diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss index 5bfe9bcb443..8d1a6020ca4 100644 --- a/app/assets/stylesheets/mailers/repository_push_email.scss +++ b/app/assets/stylesheets/mailers/repository_push_email.scss @@ -78,7 +78,7 @@ span.highlight_word { background-color: #fafe3d !important; } -.hll { background-color: #f8f8f8 } +.hll { background-color: #f8f8f8; } .c { color: #998; font-style: italic; } .err { color: #a61717; background-color: #e3d2d2; } .k { font-weight: bold; } diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index fc12964872d..ced8c4a9907 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -2,22 +2,28 @@ img { max-width: 100%; height: auto; } + p.details { font-style: italic; - color: #777 + color: #777; } + .footer > p { font-size: small; - color: #777 + color: #777; } + pre.commit-message { white-space: pre-wrap; } + .file-stats > a { text-decoration: none; + > .new-file { color: #090; } + > .deleted-file { color: #b00; } diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index 8f71381f5c4..140d589024b 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -22,7 +22,7 @@ .admin-filter form { .select2-container { - width: 100% + width: 100%; } .controls { @@ -31,7 +31,7 @@ .form-actions { padding-left: 130px; - background: #fff + background: #fff; } .visibility-levels { @@ -106,26 +106,33 @@ .table { table-layout: fixed; } + .subheading { padding-bottom: $gl-padding; } + .message { word-wrap: break-word; } + .btn { white-space: normal; padding: $gl-btn-padding; } + th { width: 15%; + &.wide { width: 55%; } } + @media (max-width: $screen-sm-max) { th { width: 100%; } + td { width: 100%; float: left; @@ -137,6 +144,7 @@ margin-left: $btn-side-margin; margin-top: 3px; } + span { font-size: 19px; } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index fcaba954615..2fbf0cf34bf 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -137,6 +137,7 @@ .retry-link { color: $gl-link-color; + &:hover { text-decoration: underline; } @@ -218,6 +219,7 @@ .build-detail-row { margin-bottom: 5px; + &:last-of-type { margin-bottom: 0; } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 53ec0002afe..264e7e01a34 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -51,6 +51,7 @@ margin-left: 4px; } } + .commit-committer-link, .commit-author-link { color: $gl-gray; @@ -108,21 +109,25 @@ line-height: 20px; } } + .new-file { a { color: $gl-text-green; } } + .renamed-file { a { color: $gl-text-orange; } } + .deleted-file { a { color: $gl-text-red; } } + .edit-file { a { color: $gl-text-color; @@ -158,6 +163,7 @@ position: absolute; z-index: 1; } + > textarea { background-color: rgba(0, 0, 0, 0.0); font-family: inherit; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index dc57a837155..2b5621e20d6 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -161,6 +161,7 @@ .branch-commit { color: $gl-gray; + .commit-id, .commit-row-message { color: $gl-gray; } diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index 42928ee279c..76225ed8d06 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -5,6 +5,7 @@ background: $background-color; border-top-left-radius: 0; } + border-top-left-radius: 0; } } @@ -17,6 +18,7 @@ float: left; @extend .col-md-2; } + .btn { margin-left: 5px; float: left; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 84094b0cb1b..bdc82a8f0f5 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -136,15 +136,18 @@ max-width: 50px; width: 35px; @include user-select(none); + a { float: left; width: 35px; font-weight: normal; + &:hover { text-decoration: underline; } } } + .line_content { display: block; margin: 0; @@ -164,10 +167,12 @@ white-space: pre-wrap; } } + .image { background: #ddd; text-align: center; padding: 30px; + .wrap { display: inline-block; } @@ -176,6 +181,7 @@ display: inline-block; background-color: #fff; line-height: 0; + img { border: 1px solid #fff; background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%), @@ -184,6 +190,7 @@ background-position: 0 0, 5px 5px; max-width: 100%; } + &.deleted { border: 1px solid $deleted; } @@ -192,6 +199,7 @@ border: 1px solid $added; } } + .image-info { font-size: 12px; margin: 5px 0 0; @@ -206,6 +214,7 @@ margin: auto; position: relative; } + .swipe-wrap { overflow: hidden; border-left: 1px solid #999; @@ -214,10 +223,12 @@ top: 13px; right: 7px; } + .frame { top: 0; right: 0; position: absolute; + &.deleted { margin: 0; display: block; @@ -225,6 +236,7 @@ right: 7px; } } + .swipe-bar { display: block; height: 100%; @@ -232,14 +244,17 @@ z-index: 100; position: absolute; cursor: pointer; + &:hover { .top-handle { background-position: -15px 3px; } + .bottom-handle { background-position: -15px -11px; } } + .top-handle { display: block; height: 14px; @@ -248,6 +263,7 @@ top: 0; background: image-url('swipemode_sprites.gif') 0 3px no-repeat; } + .bottom-handle { display: block; height: 14px; @@ -265,12 +281,14 @@ margin: auto; position: relative; } + .frame.added, .frame.deleted { position: absolute; display: block; top: 0; left: 0; } + .controls { display: block; height: 14px; @@ -324,6 +342,7 @@ } //.view.onion-skin } + .view-modes { padding: 10px; text-align: center; @@ -341,19 +360,24 @@ border-left: 1px solid #c1c1c1; padding: 0 12px 0 16px; cursor: pointer; + &:first-child { border-left: none; } + &:hover { text-decoration: underline; } + &.active { &:hover { text-decoration: none; } + cursor: default; color: #333; } + &.disabled { display: none; } diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index fcc5f32c738..029dabd2138 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -15,6 +15,7 @@ .cancel-btn { color: #b94a48; + &:hover { color: #b94a48; } @@ -70,16 +71,20 @@ .soft-wrap-toggle { margin: 0 $btn-side-margin; + .soft-wrap { display: block; } + .no-wrap { display: none; } + &.soft-wrap-active { .soft-wrap { display: none; } + .no-wrap { display: block; } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 789d6237df8..5d9a76dac05 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -78,6 +78,7 @@ margin-bottom: 0; } } + .event-note-icon { color: #777; float: left; @@ -86,6 +87,7 @@ margin-right: 5px; } } + .event_icon { position: relative; float: right; @@ -95,12 +97,13 @@ background: $gray-light; margin-left: 10px; top: -6px; + img { width: 20px; } } - &:last-child { border: none } + &:last-child { border: none; } .event_commits { li { @@ -109,6 +112,7 @@ padding: 3px; padding-left: 0; border: none; + .commit-row-title { font-size: $gl-font-size; } @@ -117,6 +121,7 @@ &.commits-stat { display: block; padding: 0 3px 0 0; + &:hover { background: none; } @@ -158,6 +163,7 @@ overflow: visible; max-width: 100%; } + .avatar { display: none; } diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index edc9592f564..ee2a398f031 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -34,6 +34,7 @@ .group-right-buttons { position: absolute; right: 16px; + .btn { @include btn-gray; padding: 3px 10px; diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 00ab42bec5c..a48b4c65db8 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -23,28 +23,28 @@ color: #555; tbody:first-child tr:first-child { - padding-top: 0 + padding-top: 0; } th { padding-top: 15px; line-height: 1.5; color: #333; - text-align: left + text-align: left; } td { padding-top: 3px; padding-bottom: 3px; vertical-align: top; - line-height: 20px + line-height: 20px; } .shortcut { padding-right: 10px; color: #999; text-align: right; - white-space: nowrap + white-space: nowrap; } .key { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 41079b6eeb5..230b927a17d 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -27,6 +27,7 @@ margin-right: 5px; margin-bottom: 5px; display: inline-block; + .color-label { padding: 6px 10px; } @@ -128,7 +129,7 @@ } .selectbox { - display: none + display: none; } .btn-clipboard { @@ -199,7 +200,7 @@ display: none; /* Small devices (tablets, 768px and up) */ @media (min-width: $screen-sm-min) { - display: block + display: block; } width: $sidebar_collapsed_width; @@ -276,7 +277,7 @@ } &.btn-primary { - @extend .btn-primary + @extend .btn-primary; } } @@ -400,6 +401,7 @@ .js-issuable-selector { width: 100%; } + @media (max-width: $screen-sm-max) { margin-bottom: $gl-padding; } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 3ac34cbc829..623da67a239 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -37,6 +37,7 @@ ul.related-merge-requests > li { display: -ms-flexbox; display: -webkit-flex; display: flex; + .merge-request-id { flex-shrink: 0; } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 701c29a3986..9bac6d46355 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -1,5 +1,6 @@ .suggest-colors { margin-top: 5px; + a { border-radius: 4px; width: 30px; diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss index 6926448519e..8290519dc25 100644 --- a/app/assets/stylesheets/pages/lint.scss +++ b/app/assets/stylesheets/pages/lint.scss @@ -3,6 +3,7 @@ font-size: 19px; color: red; } + .correct-syntax { font-size: 19px; color: #47a447; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index a08b033dff9..47d112dbbe3 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -68,7 +68,7 @@ a.forgot { float: right; - padding-top: 6px + padding-top: 6px; } .nav .active a { diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 5ec660799e3..49013d7cac9 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -131,6 +131,7 @@ $colors: ( } } } + &.head { background-color: map-get($colors, #{$color}_header_head_neutral); border-color: map-get($colors, #{$color}_header_head_neutral); @@ -174,6 +175,7 @@ $colors: ( background-color: map-get($colors, #{$color}_line_not_chosen); } } + &.head { background-color: map-get($colors, #{$color}_line_head_neutral); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 0ccb0ccfd14..afc4e517fde 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -10,6 +10,7 @@ form { margin-bottom: 0; + .clearfix { margin-bottom: 0; } @@ -46,6 +47,7 @@ &.right { float: right; + a { color: $gl-gray; } @@ -192,6 +194,7 @@ padding-top: 2px; padding-bottom: 2px; list-style: none; + &:hover { background: none; } @@ -215,6 +218,7 @@ padding-top: 20px; padding-bottom: 10px; } + svg { width: 230px; } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 8c2ba3ed58c..dd6d1783667 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -59,6 +59,7 @@ color: $gl-placeholder-color; margin-right: 5px; } + .avatar { float: none; } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index bd875b9823f..17f28959414 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -11,6 +11,7 @@ filter: alpha(opacity=100); } } + .diff-file, .discussion { .new-note { @@ -194,6 +195,7 @@ min-height: 140px; max-height: 500px; } + .note-form-actions { background: transparent; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index efeea96373f..fffcdc812a7 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -158,6 +158,7 @@ ul.notes { .diff-header > span { margin-right: 10px; } + .line_content { white-space: pre-wrap; } @@ -353,6 +354,7 @@ ul.notes { width: 32px; // "hide" it by default display: none; + &:hover { background: $gl-info; color: #fff; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 29dd03bfd60..7b71876b822 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -183,7 +183,7 @@ &::after { content: ''; width: 8px; - position: absolute;; + position: absolute; right: -7px; bottom: 8px; border-bottom: 2px solid $border-color; @@ -360,6 +360,7 @@ &:hover { background-color: $gray-lighter; + .dropdown-menu-toggle { background-color: transparent; } @@ -543,6 +544,7 @@ height: 29px; top: -9px; } + .curve { display: block; } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index c7eac5cf4b9..ed80d2beec2 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -243,6 +243,7 @@ .btn { -webkit-flex-grow: 1; flex-grow: 1; + &:first-child { margin-left: 0; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 530fb0c0d05..d30f02340b9 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -17,34 +17,43 @@ &.features .control-label { font-weight: normal; } + .form-group { margin-bottom: 5px; } + &> .form-group { padding-left: 0; } } + .help-block { margin-bottom: 10px; } + .project-path { padding-right: 0; + .form-control { border-radius: $border-radius-base; } } + .input-group > div { &:last-child { padding-right: 0; } } + @media (max-width: $screen-xs-max) { .input-group > div { margin-bottom: 14px; + &:last-child { margin-bottom: 0; } } + fieldset > .form-group:first-child { padding-right: 0; } @@ -56,6 +65,7 @@ border-radius: 3px; border: 1px solid #e5e5e5; } + &+ .select2 a { border-top-left-radius: 0; border-bottom-left-radius: 0; @@ -201,6 +211,7 @@ pointer-events: none; } } + .count { @include btn-gray; display: inline-block; @@ -361,10 +372,12 @@ a.deploy-project-label { margin: $gl-padding; text-align: center; width: 169px; + &:hover, &.forked { background-color: $row-hover; border-color: $row-hover-border; } + .no-avatar { width: 100px; height: 100px; @@ -372,17 +385,20 @@ a.deploy-project-label { border: 1px solid $gray-dark; margin: 0 auto; border-radius: 50%; + i { font-size: 100px; color: $gray-dark; } } + a { display: block; width: 100%; height: 100%; padding-top: $gl-padding; color: $gl-gray; + .caption { min-height: 30px; padding: $gl-padding 0; @@ -644,6 +660,7 @@ pre.light-well { .clone-options { display: table-cell; + a.btn { width: 100%; } @@ -832,6 +849,7 @@ pre.light-well { .form-control { min-width: 100px; } + .select2-choice { border-top-right-radius: 0; border-bottom-right-radius: 0; diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index eec22c5dc96..7b3878c91df 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -6,6 +6,7 @@ &.runner-state-shared { background: #32b186; } + &.runner-state-specific { background: #3498db; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index c05f3d5ff32..f1d53c7b8bc 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -65,6 +65,7 @@ .ci-status-icon-success { color: $gl-success; } + .ci-status-icon-failed { color: $gl-danger; } @@ -77,6 +78,7 @@ .ci-status-icon-running { color: $blue-normal; } + .ci-status-icon-canceled, .ci-status-icon-disabled, .ci-status-icon-not-found, diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 41ad10f07bd..99c0f6362d0 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -5,6 +5,7 @@ .file-finder { width: 50%; + .file-finder-input { width: 95%; display: inline-block; diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss index c9846103762..3fa7fa3d7e3 100644 --- a/app/assets/stylesheets/pages/xterm.scss +++ b/app/assets/stylesheets/pages/xterm.scss @@ -23,15 +23,19 @@ .term-bold { font-weight: bold; } + .term-italic { font-style: italic; } + .term-conceal { visibility: hidden; } + .term-underline { text-decoration: underline; } + .term-cross { text-decoration: line-through; } @@ -39,48 +43,63 @@ .term-fg-black { color: $black; } + .term-fg-red { color: $red; } + .term-fg-green { color: $green; } + .term-fg-yellow { color: $yellow; } + .term-fg-blue { color: $blue; } + .term-fg-magenta { color: $magenta; } + .term-fg-cyan { color: $cyan; } + .term-fg-white { color: $white; } + .term-fg-l-black { color: $l-black; } + .term-fg-l-red { color: $l-red; } + .term-fg-l-green { color: $l-green; } + .term-fg-l-yellow { color: $l-yellow; } + .term-fg-l-blue { color: $l-blue; } + .term-fg-l-magenta { color: $l-magenta; } + .term-fg-l-cyan { color: $l-cyan; } + .term-fg-l-white { color: $l-white; } @@ -88,818 +107,1087 @@ .term-bg-black { background-color: $black; } + .term-bg-red { background-color: $red; } + .term-bg-green { background-color: $green; } + .term-bg-yellow { background-color: $yellow; } + .term-bg-blue { background-color: $blue; } + .term-bg-magenta { background-color: $magenta; } + .term-bg-cyan { background-color: $cyan; } + .term-bg-white { background-color: $white; } + .term-bg-l-black { background-color: $l-black; } + .term-bg-l-red { background-color: $l-red; } + .term-bg-l-green { background-color: $l-green; } + .term-bg-l-yellow { background-color: $l-yellow; } + .term-bg-l-blue { background-color: $l-blue; } + .term-bg-l-magenta { background-color: $l-magenta; } + .term-bg-l-cyan { background-color: $l-cyan; } + .term-bg-l-white { background-color: $l-white; } - .xterm-fg-0 { color: #000; } + .xterm-fg-1 { color: #800000; } + .xterm-fg-2 { color: #008000; } + .xterm-fg-3 { color: #808000; } + .xterm-fg-4 { color: #000080; } + .xterm-fg-5 { color: #800080; } + .xterm-fg-6 { color: #008080; } + .xterm-fg-7 { color: #c0c0c0; } + .xterm-fg-8 { color: #808080; } + .xterm-fg-9 { color: #f00; } + .xterm-fg-10 { color: #0f0; } + .xterm-fg-11 { color: #ff0; } + .xterm-fg-12 { color: #00f; } + .xterm-fg-13 { color: #f0f; } + .xterm-fg-14 { color: #0ff; } + .xterm-fg-15 { color: #fff; } + .xterm-fg-16 { color: #000; } + .xterm-fg-17 { color: #00005f; } + .xterm-fg-18 { color: #000087; } + .xterm-fg-19 { color: #0000af; } + .xterm-fg-20 { color: #0000d7; } + .xterm-fg-21 { color: #00f; } + .xterm-fg-22 { color: #005f00; } + .xterm-fg-23 { color: #005f5f; } + .xterm-fg-24 { color: #005f87; } + .xterm-fg-25 { color: #005faf; } + .xterm-fg-26 { color: #005fd7; } + .xterm-fg-27 { color: #005fff; } + .xterm-fg-28 { color: #008700; } + .xterm-fg-29 { color: #00875f; } + .xterm-fg-30 { color: #008787; } + .xterm-fg-31 { color: #0087af; } + .xterm-fg-32 { color: #0087d7; } + .xterm-fg-33 { color: #0087ff; } + .xterm-fg-34 { color: #00af00; } + .xterm-fg-35 { color: #00af5f; } + .xterm-fg-36 { color: #00af87; } + .xterm-fg-37 { color: #00afaf; } + .xterm-fg-38 { color: #00afd7; } + .xterm-fg-39 { color: #00afff; } + .xterm-fg-40 { color: #00d700; } + .xterm-fg-41 { color: #00d75f; } + .xterm-fg-42 { color: #00d787; } + .xterm-fg-43 { color: #00d7af; } + .xterm-fg-44 { color: #00d7d7; } + .xterm-fg-45 { color: #00d7ff; } + .xterm-fg-46 { color: #0f0; } + .xterm-fg-47 { color: #00ff5f; } + .xterm-fg-48 { color: #00ff87; } + .xterm-fg-49 { color: #00ffaf; } + .xterm-fg-50 { color: #00ffd7; } + .xterm-fg-51 { color: #0ff; } + .xterm-fg-52 { color: #5f0000; } + .xterm-fg-53 { color: #5f005f; } + .xterm-fg-54 { color: #5f0087; } + .xterm-fg-55 { color: #5f00af; } + .xterm-fg-56 { color: #5f00d7; } + .xterm-fg-57 { color: #5f00ff; } + .xterm-fg-58 { color: #5f5f00; } + .xterm-fg-59 { color: #5f5f5f; } + .xterm-fg-60 { color: #5f5f87; } + .xterm-fg-61 { color: #5f5faf; } + .xterm-fg-62 { color: #5f5fd7; } + .xterm-fg-63 { color: #5f5fff; } + .xterm-fg-64 { color: #5f8700; } + .xterm-fg-65 { color: #5f875f; } + .xterm-fg-66 { color: #5f8787; } + .xterm-fg-67 { color: #5f87af; } + .xterm-fg-68 { color: #5f87d7; } + .xterm-fg-69 { color: #5f87ff; } + .xterm-fg-70 { color: #5faf00; } + .xterm-fg-71 { color: #5faf5f; } + .xterm-fg-72 { color: #5faf87; } + .xterm-fg-73 { color: #5fafaf; } + .xterm-fg-74 { color: #5fafd7; } + .xterm-fg-75 { color: #5fafff; } + .xterm-fg-76 { color: #5fd700; } + .xterm-fg-77 { color: #5fd75f; } + .xterm-fg-78 { color: #5fd787; } + .xterm-fg-79 { color: #5fd7af; } + .xterm-fg-80 { color: #5fd7d7; } + .xterm-fg-81 { color: #5fd7ff; } + .xterm-fg-82 { color: #5fff00; } + .xterm-fg-83 { color: #5fff5f; } + .xterm-fg-84 { color: #5fff87; } + .xterm-fg-85 { color: #5fffaf; } + .xterm-fg-86 { color: #5fffd7; } + .xterm-fg-87 { color: #5fffff; } + .xterm-fg-88 { color: #870000; } + .xterm-fg-89 { color: #87005f; } + .xterm-fg-90 { color: #870087; } + .xterm-fg-91 { color: #8700af; } + .xterm-fg-92 { color: #8700d7; } + .xterm-fg-93 { color: #8700ff; } + .xterm-fg-94 { color: #875f00; } + .xterm-fg-95 { color: #875f5f; } + .xterm-fg-96 { color: #875f87; } + .xterm-fg-97 { color: #875faf; } + .xterm-fg-98 { color: #875fd7; } + .xterm-fg-99 { color: #875fff; } + .xterm-fg-100 { color: #878700; } + .xterm-fg-101 { color: #87875f; } + .xterm-fg-102 { color: #878787; } + .xterm-fg-103 { color: #8787af; } + .xterm-fg-104 { color: #8787d7; } + .xterm-fg-105 { color: #8787ff; } + .xterm-fg-106 { color: #87af00; } + .xterm-fg-107 { color: #87af5f; } + .xterm-fg-108 { color: #87af87; } + .xterm-fg-109 { color: #87afaf; } + .xterm-fg-110 { color: #87afd7; } + .xterm-fg-111 { color: #87afff; } + .xterm-fg-112 { color: #87d700; } + .xterm-fg-113 { color: #87d75f; } + .xterm-fg-114 { color: #87d787; } + .xterm-fg-115 { color: #87d7af; } + .xterm-fg-116 { color: #87d7d7; } + .xterm-fg-117 { color: #87d7ff; } + .xterm-fg-118 { color: #87ff00; } + .xterm-fg-119 { color: #87ff5f; } + .xterm-fg-120 { color: #87ff87; } + .xterm-fg-121 { color: #87ffaf; } + .xterm-fg-122 { color: #87ffd7; } + .xterm-fg-123 { color: #87ffff; } + .xterm-fg-124 { color: #af0000; } + .xterm-fg-125 { color: #af005f; } + .xterm-fg-126 { color: #af0087; } + .xterm-fg-127 { color: #af00af; } + .xterm-fg-128 { color: #af00d7; } + .xterm-fg-129 { color: #af00ff; } + .xterm-fg-130 { color: #af5f00; } + .xterm-fg-131 { color: #af5f5f; } + .xterm-fg-132 { color: #af5f87; } + .xterm-fg-133 { color: #af5faf; } + .xterm-fg-134 { color: #af5fd7; } + .xterm-fg-135 { color: #af5fff; } + .xterm-fg-136 { color: #af8700; } + .xterm-fg-137 { color: #af875f; } + .xterm-fg-138 { color: #af8787; } + .xterm-fg-139 { color: #af87af; } + .xterm-fg-140 { color: #af87d7; } + .xterm-fg-141 { color: #af87ff; } + .xterm-fg-142 { color: #afaf00; } + .xterm-fg-143 { color: #afaf5f; } + .xterm-fg-144 { color: #afaf87; } + .xterm-fg-145 { color: #afafaf; } + .xterm-fg-146 { color: #afafd7; } + .xterm-fg-147 { color: #afafff; } + .xterm-fg-148 { color: #afd700; } + .xterm-fg-149 { color: #afd75f; } + .xterm-fg-150 { color: #afd787; } + .xterm-fg-151 { color: #afd7af; } + .xterm-fg-152 { color: #afd7d7; } + .xterm-fg-153 { color: #afd7ff; } + .xterm-fg-154 { color: #afff00; } + .xterm-fg-155 { color: #afff5f; } + .xterm-fg-156 { color: #afff87; } + .xterm-fg-157 { color: #afffaf; } + .xterm-fg-158 { color: #afffd7; } + .xterm-fg-159 { color: #afffff; } + .xterm-fg-160 { color: #d70000; } + .xterm-fg-161 { color: #d7005f; } + .xterm-fg-162 { color: #d70087; } + .xterm-fg-163 { color: #d700af; } + .xterm-fg-164 { color: #d700d7; } + .xterm-fg-165 { color: #d700ff; } + .xterm-fg-166 { color: #d75f00; } + .xterm-fg-167 { color: #d75f5f; } + .xterm-fg-168 { color: #d75f87; } + .xterm-fg-169 { color: #d75faf; } + .xterm-fg-170 { color: #d75fd7; } + .xterm-fg-171 { color: #d75fff; } + .xterm-fg-172 { color: #d78700; } + .xterm-fg-173 { color: #d7875f; } + .xterm-fg-174 { color: #d78787; } + .xterm-fg-175 { color: #d787af; } + .xterm-fg-176 { color: #d787d7; } + .xterm-fg-177 { color: #d787ff; } + .xterm-fg-178 { color: #d7af00; } + .xterm-fg-179 { color: #d7af5f; } + .xterm-fg-180 { color: #d7af87; } + .xterm-fg-181 { color: #d7afaf; } + .xterm-fg-182 { color: #d7afd7; } + .xterm-fg-183 { color: #d7afff; } + .xterm-fg-184 { color: #d7d700; } + .xterm-fg-185 { color: #d7d75f; } + .xterm-fg-186 { color: #d7d787; } + .xterm-fg-187 { color: #d7d7af; } + .xterm-fg-188 { color: #d7d7d7; } + .xterm-fg-189 { color: #d7d7ff; } + .xterm-fg-190 { color: #d7ff00; } + .xterm-fg-191 { color: #d7ff5f; } + .xterm-fg-192 { color: #d7ff87; } + .xterm-fg-193 { color: #d7ffaf; } + .xterm-fg-194 { color: #d7ffd7; } + .xterm-fg-195 { color: #d7ffff; } + .xterm-fg-196 { color: #f00; } + .xterm-fg-197 { color: #ff005f; } + .xterm-fg-198 { color: #ff0087; } + .xterm-fg-199 { color: #ff00af; } + .xterm-fg-200 { color: #ff00d7; } + .xterm-fg-201 { color: #f0f; } + .xterm-fg-202 { color: #ff5f00; } + .xterm-fg-203 { color: #ff5f5f; } + .xterm-fg-204 { color: #ff5f87; } + .xterm-fg-205 { color: #ff5faf; } + .xterm-fg-206 { color: #ff5fd7; } + .xterm-fg-207 { color: #ff5fff; } + .xterm-fg-208 { color: #ff8700; } + .xterm-fg-209 { color: #ff875f; } + .xterm-fg-210 { color: #ff8787; } + .xterm-fg-211 { color: #ff87af; } + .xterm-fg-212 { color: #ff87d7; } + .xterm-fg-213 { color: #ff87ff; } + .xterm-fg-214 { color: #ffaf00; } + .xterm-fg-215 { color: #ffaf5f; } + .xterm-fg-216 { color: #ffaf87; } + .xterm-fg-217 { color: #ffafaf; } + .xterm-fg-218 { color: #ffafd7; } + .xterm-fg-219 { color: #ffafff; } + .xterm-fg-220 { color: #ffd700; } + .xterm-fg-221 { color: #ffd75f; } + .xterm-fg-222 { color: #ffd787; } + .xterm-fg-223 { color: #ffd7af; } + .xterm-fg-224 { color: #ffd7d7; } + .xterm-fg-225 { color: #ffd7ff; } + .xterm-fg-226 { color: #ff0; } + .xterm-fg-227 { color: #ffff5f; } + .xterm-fg-228 { color: #ffff87; } + .xterm-fg-229 { color: #ffffaf; } + .xterm-fg-230 { color: #ffffd7; } + .xterm-fg-231 { color: #fff; } + .xterm-fg-232 { color: #080808; } + .xterm-fg-233 { color: #121212; } + .xterm-fg-234 { color: #1c1c1c; } + .xterm-fg-235 { color: #262626; } + .xterm-fg-236 { color: #303030; } + .xterm-fg-237 { color: #3a3a3a; } + .xterm-fg-238 { color: #444; } + .xterm-fg-239 { color: #4e4e4e; } + .xterm-fg-240 { color: #585858; } + .xterm-fg-241 { color: #626262; } + .xterm-fg-242 { color: #6c6c6c; } + .xterm-fg-243 { color: #767676; } + .xterm-fg-244 { color: #808080; } + .xterm-fg-245 { color: #8a8a8a; } + .xterm-fg-246 { color: #949494; } + .xterm-fg-247 { color: #9e9e9e; } + .xterm-fg-248 { color: #a8a8a8; } + .xterm-fg-249 { color: #b2b2b2; } + .xterm-fg-250 { color: #bcbcbc; } + .xterm-fg-251 { color: #c6c6c6; } + .xterm-fg-252 { color: #d0d0d0; } + .xterm-fg-253 { color: #dadada; } + .xterm-fg-254 { color: #e4e4e4; } + .xterm-fg-255 { color: #eee; } -- cgit v1.2.1 From 2b9a25bd5a69c3c6a5bb24bb67838a4d354204e1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 15 Oct 2016 00:11:19 +0300 Subject: Handle unmatched routing with not_found method We need this to prevent routing error when user access URL like /123 when there is no resource located under such name Signed-off-by: Dmitriy Zaporozhets --- app/controllers/application_controller.rb | 4 ++++ config/routes.rb | 2 ++ 2 files changed, 6 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b3455e04c29..705824502eb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -45,6 +45,10 @@ class ApplicationController < ActionController::Base redirect_to request.referer.present? ? :back : default, options end + def not_found + render_404 + end + protected # This filter handles both private tokens and personal access tokens diff --git a/config/routes.rb b/config/routes.rb index 83c3a42c19f..659ea51bc75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -88,4 +88,6 @@ Rails.application.routes.draw do get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } root to: "root#index" + + get '*unmatched_route', to: 'application#not_found' end -- cgit v1.2.1 From d60d5fe4e422ecd83437653bc5764c6269162125 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 15 Oct 2016 01:36:05 +0300 Subject: Improve ExtractsPath logic related to atom format * Don't set request format to atom if '.atom' suffix was not provided * Don't try '.atom' detection logic on request that uses extended_sha1 Signed-off-by: Dmitriy Zaporozhets --- lib/extracts_path.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index e4d996a3fb6..9b74364849e 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -113,17 +113,18 @@ module ExtractsPath @id = get_id @ref, @path = extract_ref(@id) @repo = @project.repository - if @options[:extended_sha1].blank? - @commit = @repo.commit(@ref) - else - @commit = @repo.commit(@options[:extended_sha1]) - end - if @path.empty? && !@commit - @id = @ref = extract_ref_without_atom(@id) + if @options[:extended_sha1].present? + @commit = @repo.commit(@options[:extended_sha1]) + else @commit = @repo.commit(@ref) - request.format = :atom if @commit + if @path.empty? && !@commit && @id.ends_with?('.atom') + @id = @ref = extract_ref_without_atom(@id) + @commit = @repo.commit(@ref) + + request.format = :atom if @commit + end end raise InvalidPathError unless @commit -- cgit v1.2.1 From b0622d657893b156ac0c265f397efeb28d5b0b4c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 15 Oct 2016 01:48:14 +0300 Subject: Revert "Update git over http test to match new routing" This reverts commit 68ab7047dae98172a0bd8b92956f2ee51b9167a0. --- spec/requests/git_http_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 5a1ed7d4a25..27f0fd22ae6 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -412,9 +412,10 @@ describe 'Git HTTP requests', lib: true do context "when the params are anything else" do let(:params) { { service: 'git-implode-pack' } } + before { get path, params } - it "fails to find a route" do - expect { get(path, params) }.to raise_error(ActionController::RoutingError) + it "redirects to the sign-in page" do + expect(response).to redirect_to(new_user_session_path) end end end -- cgit v1.2.1 From 5b5c7e048b6e975a655478b0e5d421c2438276aa Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Oct 2016 16:58:55 -0700 Subject: Fix trending projects Spinach failure The trending projects list is now pre-calculated. To make this work with the Spinach test, we have to manually refresh the list. Partial fix to #23378 --- features/explore/projects.feature | 1 + features/steps/shared/project.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/features/explore/projects.feature b/features/explore/projects.feature index 092e18d1b86..4e0f4486ab7 100644 --- a/features/explore/projects.feature +++ b/features/explore/projects.feature @@ -128,6 +128,7 @@ Feature: Explore Projects And project "Archive" has comments And I sign in as a user And project "Community" has comments + And trending projects are refreshed When I visit the explore trending projects Then I should see project "Community" And I should not see project "Internal" diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index afbd8ef1233..cab85a48396 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -218,6 +218,10 @@ module SharedProject 2.times { create(:note_on_issue, project: project) } end + step 'trending projects are refreshed' do + TrendingProject.refresh! + end + step 'project "Shop" has labels: "bug", "feature", "enhancement"' do project = Project.find_by(name: "Shop") create(:label, project: project, title: 'bug') -- cgit v1.2.1 From 762b63e56965223bd217224a407f15ec6b1b1d3d Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Oct 2016 17:07:38 -0700 Subject: Fix Spinach failure due overprecise percentage matching The percentages in the language match changed by a tenth of a percentage point for Ruby and JavaScript, which led to this failure. Partial fix to #23378 --- features/steps/project/graph.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb index b09ec86e5df..7490d2bc6e7 100644 --- a/features/steps/project/graph.rb +++ b/features/steps/project/graph.rb @@ -19,8 +19,8 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps end step 'page should have languages graphs' do - expect(page).to have_content "Ruby 66.63 %" - expect(page).to have_content "JavaScript 22.96 %" + expect(page).to have_content /Ruby 66.* %/ + expect(page).to have_content /JavaScript 22.* %/ end step 'page should have commits graphs' do -- cgit v1.2.1 From 1dd826d4aad2ce6c195bad24b458b1967b74db1d Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 14:21:00 +0200 Subject: Make UX upgrades to SignIn/Register views. - Tab between register and sign in forms - Add individual input validation error messages - Validate username - Update many styles for all login-box forms --- CHANGELOG | 1 + app/assets/javascripts/dispatcher.js | 11 + app/assets/javascripts/gl_field_errors.js.es6 | 100 +++ app/assets/javascripts/username_validator.js.es6 | 131 ++++ app/assets/stylesheets/framework/buttons.scss | 3 +- app/assets/stylesheets/framework/forms.scss | 13 +- app/assets/stylesheets/pages/login.scss | 138 +++- app/controllers/users_controller.rb | 6 +- app/views/admin/appearances/preview.html.haml | 17 +- app/views/devise/confirmations/new.html.haml | 12 +- app/views/devise/passwords/edit.html.haml | 20 +- app/views/devise/passwords/new.html.haml | 10 +- app/views/devise/sessions/_new_base.html.haml | 10 +- app/views/devise/sessions/_new_crowd.html.haml | 10 +- app/views/devise/sessions/_new_ldap.html.haml | 10 +- app/views/devise/sessions/new.html.haml | 32 +- app/views/devise/sessions/two_factor.html.haml | 17 +- app/views/devise/shared/_omniauth_box.html.haml | 15 +- app/views/devise/shared/_sign_in_link.html.haml | 1 - app/views/devise/shared/_signin_box.html.haml | 37 +- app/views/devise/shared/_signup_box.html.haml | 36 +- app/views/devise/shared/_tab_single.html.haml | 4 + app/views/devise/shared/_tabs_ldap.html.haml | 10 + app/views/devise/shared/_tabs_normal.html.haml | 5 + app/views/devise/unlocks/new.html.haml | 10 +- app/views/layouts/devise.html.haml | 59 +- app/views/u2f/_authenticate.html.haml | 2 +- config/routes.rb | 792 +++++++++++++++++++++++ spec/features/signup_spec.rb | 8 +- spec/features/u2f_spec.rb | 14 +- spec/features/users_spec.rb | 32 +- spec/javascripts/u2f/authenticate_spec.js | 2 +- 32 files changed, 1391 insertions(+), 177 deletions(-) create mode 100644 app/assets/javascripts/gl_field_errors.js.es6 create mode 100644 app/assets/javascripts/username_validator.js.es6 create mode 100644 app/views/devise/shared/_tab_single.html.haml create mode 100644 app/views/devise/shared/_tabs_ldap.html.haml create mode 100644 app/views/devise/shared/_tabs_normal.html.haml diff --git a/CHANGELOG b/CHANGELOG index 194ee18b74c..e3201cd2250 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -377,6 +377,7 @@ v 8.11.7 - Avoid conflict with admin labels when importing GitHub labels. !6158 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - Allow the Rails cookie to be used for API authentication. + - Login/Register UX upgrade !6328 v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 858621218f8..fb6e82cd37c 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -8,6 +8,7 @@ Dispatcher = (function() { function Dispatcher() { this.initSearch(); + this.initFieldErrors(); this.initPageScripts(); } @@ -20,6 +21,10 @@ path = page.split(':'); shortcut_handler = null; switch (page) { + case 'sessions:new': + case 'sessions:create': + new UsernameValidator(); + break; case 'projects:boards:show': case 'projects:boards:index': shortcut_handler = new ShortcutsNavigation(); @@ -291,6 +296,12 @@ } }; + Dispatcher.prototype.initFieldErrors = function() { + $('form.show-gl-field-errors').each(function(i, form) { + new gl.GlFieldErrors(form); + }); + }; + return Dispatcher; })(); diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 new file mode 100644 index 00000000000..42a2ddeeafe --- /dev/null +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -0,0 +1,100 @@ +((global) => { + /* + * This class overrides the browser's validation error bubbles, displaying custom + * error messages for invalid fields instead. To begin validating any form, add the + * class `show-gl-field-errors` to the form element, and ensure error messages are + * declared in each inputs' title attribute. + * + * Example: + * + *
      + * + *
      + * + * */ + + const fieldErrorClass = 'gl-field-error'; + const fieldErrorSelector = `.${fieldErrorClass}`; + const inputErrorClass = 'gl-field-error-outline'; + + class GlFieldErrors { + constructor(form) { + this.form = $(form); + this.initValidators(); + } + + initValidators () { + this.inputs = this.form.find(':input:not([type=hidden])').toArray(); + this.inputs.forEach((input) => { + $(input).off('invalid').on('invalid', this.handleInvalidInput.bind(this)); + }); + this.form.on('submit', this.catchInvalidFormSubmit); + } + + /* Neccessary because Safari & iOS quietly allow form submission when form is invalid */ + catchInvalidFormSubmit (event) { + if (!event.currentTarget.checkValidity()) { + event.preventDefault(); + // Prevents disabling of invalid submit button by application.js + event.stopPropagation(); + } + } + + handleInvalidInput (event) { + event.preventDefault(); + this.updateFieldValidityState(event); + + const $input = $(event.currentTarget); + + // For UX, wait til after first invalid submission to check each keyup + $input.off('keyup.field_validator') + .on('keyup.field_validator', this.updateFieldValidityState.bind(this)); + + } + + displayFieldValidity (target, isValid) { + const $input = $(target).removeClass(inputErrorClass); + const $existingError = $input.siblings(fieldErrorSelector); + const alreadyInvalid = !!$existingError.length; + const implicitErrorMessage = $input.attr('title'); + const $errorToDisplay = alreadyInvalid ? $existingError.detach() : $(`

      ${implicitErrorMessage}

      `); + + if (!isValid) { + $input.after($errorToDisplay); + $input.addClass(inputErrorClass); + } + + this.updateFieldSiblings($errorToDisplay, isValid); + } + + updateFieldSiblings($target, isValid) { + const siblings = $target.siblings(`p${fieldErrorSelector}`); + return isValid ? siblings.show() : siblings.hide(); + } + + checkFieldValidity(target) { + return target.validity.valid; + } + + updateFieldValidityState(event) { + const target = event.currentTarget; + const isKeyup = event.type === 'keyup'; + const isValid = this.checkFieldValidity(target); + + this.displayFieldValidity(target, isValid); + + // prevent changing focus while user is typing. + if (!isKeyup) { + this.focusOnFirstInvalid.apply(this); + } + } + + focusOnFirstInvalid () { + const firstInvalid = this.inputs.find((input) => !input.validity.valid); + $(firstInvalid).focus(); + } + } + + global.GlFieldErrors = GlFieldErrors; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 new file mode 100644 index 00000000000..f8be356af04 --- /dev/null +++ b/app/assets/javascripts/username_validator.js.es6 @@ -0,0 +1,131 @@ +((global) => { + const debounceTimeoutDuration = 1000; + const inputErrorClass = 'gl-field-error-outline'; + const inputSuccessClass = 'gl-field-success-outline'; + const messageErrorSelector = '.username .validation-error'; + const messageSuccessSelector = '.username .validation-success'; + const messagePendingSelector = '.username .validation-pending'; + + class UsernameValidator { + constructor() { + this.inputElement = $('#new_user_username'); + this.inputDomElement = this.inputElement.get(0); + + this.available = false; + this.valid = false; + this.pending = false; + this.fresh = true; + this.empty = true; + + const debounceTimeout = _.debounce((username) => { + this.validateUsername(username); + }, debounceTimeoutDuration); + + this.inputElement.on('keyup.username_check', () => { + const username = this.inputElement.val(); + + this.valid = this.inputDomElement.validity.valid; + this.fresh = false; + this.empty = !username.length; + + if (this.valid) { + return debounceTimeout(username); + } + + this.renderState(); + }); + + // Override generic field validation + this.inputElement.on('invalid', this.handleInvalidInput.bind(this)); + } + + renderState() { + // Clear all state + this.clearFieldValidationState(); + + if (this.valid && this.available) { + return this.setSuccessState(); + } + + if (this.empty) { + return this.clearFieldValidationState(); + } + + if (this.pending) { + return this.setPendingState(); + } + + if (!this.available) { + return this.setUnavailableState(); + } + + if (!this.valid) { + return this.setInvalidState(); + } + } + + handleInvalidInput(event) { + event.preventDefault(); + event.stopPropagation(); + } + + validateUsername(username) { + if (this.valid) { + this.pending = true; + this.available = false; + this.renderState(); + return $.ajax({ + type: 'GET', + url: `/u/${username}/exists`, + dataType: 'json', + success: (res) => this.updateValidationState(res.exists) + }); + } + } + + updateValidationState(usernameTaken) { + if (usernameTaken) { + this.valid = false; + this.available = false; + } else { + this.available = true; + } + this.pending = false; + this.renderState(); + } + + clearFieldValidationState() { + this.inputElement.siblings('p').hide(); + this.inputElement.removeClass(inputErrorClass); + this.inputElement.removeClass(inputSuccessClass); + } + + setUnavailableState() { + const $usernameErrorMessage = this.inputElement.siblings(messageErrorSelector); + this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); + $usernameErrorMessage.show(); + } + + setSuccessState() { + const $usernameSuccessMessage = this.inputElement.siblings(messageSuccessSelector); + this.inputElement.addClass(inputSuccessClass).removeClass(inputErrorClass); + $usernameSuccessMessage.show(); + } + + setPendingState(show) { + const $usernamePendingMessage = $(messagePendingSelector); + if (this.pending) { + $usernamePendingMessage.show(); + } else { + $usernamePendingMessage.hide(); + } + } + + setInvalidState() { + this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); + $(`.gl-field-error`).show(); + } + } + + global.UsernameValidator = UsernameValidator; +})(window); diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 7c0ed72dbc5..e6656c2d69a 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -152,7 +152,8 @@ @include btn-blue-medium; } - &.btn-info { + &.btn-info, + &.btn-register { @include btn-blue; } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 311e3fa1a35..761c07384f4 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -73,8 +73,8 @@ label { } .form-control { - box-shadow: none; - border-radius: 3px; + @include box-shadow(none); + border-radius: 2px; padding: $gl-vert-padding $gl-input-padding; } @@ -127,3 +127,12 @@ label { border-right: 0; } } + +.help-block { + margin-bottom: 0; +} + +.gl-field-error { + color: $red-normal; +} + diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 47d112dbbe3..06b90fbefab 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -17,6 +17,7 @@ line-height: 1.5; p { + font-size: 18px; color: #888; } @@ -36,10 +37,13 @@ } } + p { + font-size: 13px; + } .login-box { - background: #fafafa; - border-radius: 10px; - box-shadow: 0 0 2px #ccc; + box-shadow: 0 0 0 1px $border-color; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 2px; padding: 15px; .login-heading h3 { @@ -74,7 +78,6 @@ .nav .active a { background: transparent; } - } .form-control { font-size: 14px; @@ -92,18 +95,109 @@ border-top: 0; margin-bottom: 20px; } + } + + // Styles the glowing border of focused input for username async validation + .login-body { + font-size: 13px; + + + input + p { + margin-top: 5px; + } + + .gl-field-success-outline { + border: 1px solid $green-normal; + + &:focus { + box-shadow: 0 0 0 1px $green-normal inset, 0 0 4px 0 $green-normal; + border: 0 none; + } + } + + .gl-field-error-outline { + border: 1px solid $red-normal; + + &:focus { + opacity: .6; + box-shadow: 0 0 0 1px $red-normal inset, 0 0 4px 0 $red-normal; + border: 0 none; + } + } + + .username .validation-success, + .gl-field-success-message { + color: $green-normal; + } + + .username .validation-error, + .gl-field-error-message { + color: $red-normal; + } + + .gl-field-hint { + color: $gl-text-color; + } + + } + + .new-session-tabs { // Are these being applied to other login-related screens? They need to be. + display: flex; + box-shadow: 0 0 0 1px $border-color; + border-top-right-radius: 2px; + border-top-left-radius: 2px; + + li { + flex: 1; + text-align: center; &.middle { border-top: 0; margin-bottom: 0; border-radius: 0; + &:last-of-type { + border-left: 1px solid $border-color; + } + + &:not(.active) { + background-color: $gray-light; + } + + a { + width: 100%; + font-size: 18px; + &:hover { + border: 1px solid transparent; + } + } + + &.active { + border-bottom: 1px solid $border-color; + + a { + border: none; + border-bottom: 2px solid $link-underline-blue; + color: $black; + + &:hover { + border-bottom: 2px solid $link-underline-blue; + } + } + } } + } + + .form-control { &:active, &:focus { background-color: #fff; } } + label { + font-weight: normal; + } + .devise-errors { h2 { margin-top: 0; @@ -111,14 +205,6 @@ color: #a00; } } - - .remember-me { - margin-top: -10px; - - label { - font-weight: normal; - } - } } @media (max-width: $screen-xs-max) { @@ -137,3 +223,31 @@ height: 32px; } } + +.devise-layout-html { + margin: 0; + padding: 0; + height: 100%; +} + +// Fixes footer container to bottom of viewport +.devise-layout-html body { + // offset height of fixed header + 1 to avoid scroll + height: calc(100% - 51px); + margin: 0; + padding: 0; + + .page-wrap { + min-height: 100%; + position: relative; + } + + .footer-container, hr.footer-fixed { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 40px; + background: $white-light; + } +} diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 838ecc837e4..30f0118254a 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,6 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! - before_action :user + before_action :user, except: [:exists] before_action :authorize_read_user!, only: [:show] def show @@ -85,6 +85,10 @@ class UsersController < ApplicationController render 'calendar_activities', layout: false end + def exists + render json: { exists: !User.find_by_username(params[:username]).nil? } + end + private def authorize_read_user! diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml index 6c51639b840..0d35702c634 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview.html.haml @@ -1,9 +1,12 @@ -- page_title "Preview | Appearance" += render 'devise/shared/tab_single', { :tab_title => 'Sign in preview' } .login-box - .login-heading - %h3 Existing user? Sign in - %form - = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email" - = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password" - = button_tag "Sign in", class: "btn-create btn" + %form.show-gl-field-errors + .form-group + = label_tag :login + = text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.' + .form-group + = label_tag :password + = password_field_tag :password, nil, class: "form-control bottom", title: 'This field is required.' + .form-group + = button_tag "Sign in", class: "btn-create btn" diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 970ba147111..443a316c6e2 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,14 +1,14 @@ += render 'devise/shared/tab_single', { :tab_title => 'Resend confirmation instructions' } .login-box - .login-heading - %h3 Resend confirmation instructions .login-body - = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| + = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! - .clearfix.append-bottom-20 - = f.email_field :email, placeholder: 'Email', class: "form-control", required: true + .form-group + = f.label :email + = f.email_field :email, class: "form-control", required: true, title: 'Please provide a valid email address.' .clearfix - = f.submit "Resend confirmation instructions", class: 'btn btn-success' + = f.submit "Resend", class: 'btn btn-success' .clearfix.prepend-top-20 = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 56048e99c17..9c533ef9916 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,19 +1,21 @@ += render 'devise/shared/tab_single', { :tab_title => 'Change your password' } .login-box - .login-heading - %h3 Change your password .login-body - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! = f.hidden_field :reset_password_token - %div - = f.password_field :password, class: "form-control top", placeholder: "New password", required: true - %div - = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true + .form-group + = f.label 'New password', for: :password + = f.password_field :password, class: "form-control top", required: true, title: 'This field is required' + .form-group + = f.label 'Confirm new password', for: :password_confirmation + = f.password_field :password_confirmation, class: "form-control bottom", title: 'This field is required', required: true .clearfix = f.submit "Change your password", class: "btn btn-primary" .clearfix.prepend-top-20 %p - = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) - = render 'devise/shared/sign_in_link' + %span.light Didn't receive a confirmation email? + = link_to "Request a new one", new_confirmation_path(resource_name) += render 'devise/shared/sign_in_link' diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 535e85869e5..91b46a12ac0 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,12 +1,12 @@ += render 'devise/shared/tab_single', { :tab_title => 'Reset Password' } .login-box - .login-heading - %h3 Reset password .login-body - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! - .clearfix.append-bottom-20 - = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email], autofocus: true + .form-group + = f.label :email + = f.email_field :email, class: "form-control", required: true, value: params[:user_email], autofocus: true, title: 'Please provide a valid email address.' .clearfix = f.submit "Reset password", class: "btn-primary btn" diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 781fd1b32a6..cfb1b964d76 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,6 +1,10 @@ -= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| - = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off" - = f.password_field :password, class: "form-control bottom", placeholder: "Password" += form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f| + %div.form-group + = f.label "Username or email", for: :login + = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required." + %div.form-group + = f.label :password + = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required." .sign-in = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index b7d3acac2b1..5a192c63c7c 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,6 +1,10 @@ -= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' ) do - = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"} - = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} += form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' class: 'show-gl-field-errors') do + .form-group + = label_tag 'Username or email', for: :username + = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } + .form-group + = label_tag :password + = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "remember_me"} diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 2ef383960f4..b26efbb4535 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,6 +1,10 @@ -= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user') do - = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"} - = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} += form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do + .form-group + = label_tag "#{server['label']} Login", for: :username + = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } + .form-group + = label_tag :password + = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "remember_me"} diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 28194506acc..2fb05b9456b 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,19 +1,23 @@ - page_title "Sign in" %div - - if signin_enabled? || ldap_enabled? || crowd_enabled? - = render 'devise/shared/signin_box' + - if form_based_providers.any? + = render 'devise/shared/tabs_ldap' + - else + = render 'devise/shared/tabs_normal' + .tab-content + - if signin_enabled? || ldap_enabled? || crowd_enabled? + = render 'devise/shared/signin_box' - -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box - - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix.prepend-top-20 - = render 'devise/shared/omniauth_box' - - -# Signup only makes sense if you can also sign-in - - if signin_enabled? && signup_enabled? - .prepend-top-20 + -# Signup only makes sense if you can also sign-in + - if signin_enabled? && signup_enabled? = render 'devise/shared/signup_box' - -# Show a message if none of the mechanisms above are enabled - - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) - %div - No authentication methods configured. + - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? + .clearfix + = render 'devise/shared/omniauth_box' + + -# Show a message if none of the mechanisms above are enabled + - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) + %div + No authentication methods configured. + diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index e623f7cff88..56074c057d7 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -3,20 +3,19 @@ = page_specific_javascript_tag('u2f.js') %div + = render 'devise/shared/tab_single', { :tab_title => 'Two-Factor Authentication' } .login-box - .login-heading - %h3 Two-Factor Authentication .login-body - if @user.two_factor_otp_enabled? - %h5 Authenticate via Two-Factor App - = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| + = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f| - resource_params = params[resource_name].presence || params = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) - = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-Factor Authentication code', required: true, autofocus: true, autocomplete: 'off' - %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. - .prepend-top-20 - = f.submit "Verify code", class: "btn btn-save" + .form-group + = f.label 'Two-Factor Authentication code', name: :otp_attempt + = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.' + %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. + .prepend-top-20 + = f.submit "Verify code", class: "btn btn-save" - if @user.two_factor_u2f_enabled? - %hr = render "u2f/authenticate", locals: { params: params, resource: resource, resource_name: resource_name } diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 2e7da2747d0..d5b6db48a29 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -1,8 +1,9 @@ -%p - %span.light - Sign in with   - - providers = enabled_button_based_providers - - providers.each do |provider| +%div.login-box + %p %span.light - - has_icon = provider_has_icon?(provider) - = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" + Sign in with   + - providers = enabled_button_based_providers + - providers.each do |provider| + %span.light + - has_icon = provider_has_icon?(provider) + = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" diff --git a/app/views/devise/shared/_sign_in_link.html.haml b/app/views/devise/shared/_sign_in_link.html.haml index fafc4b82f53..289bf40f3de 100644 --- a/app/views/devise/shared/_sign_in_link.html.haml +++ b/app/views/devise/shared/_sign_in_link.html.haml @@ -1,5 +1,4 @@ %p %span.light Already have login and password? - %strong = link_to "Sign in", new_session_path(resource_name) diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 2c15e2c4891..810dd5ab687 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -1,32 +1,15 @@ -.login-box - - if signup_enabled? - .login-heading - %h3 Existing user? Sign in - - else - .login-heading - %h3 Sign in +#login-pane.login-box{ role: 'tabpanel', class: 'tab-pane active' } .login-body - if form_based_providers.any? - %ul.nav-links - - if crowd_enabled? - %li.active - = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' - - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero? && !crowd_enabled?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - - if signin_enabled? - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' - .tab-content - - if crowd_enabled? - %div.tab-pane.active{id: "tab-crowd"} - = render 'devise/sessions/new_crowd' - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} - = render 'devise/sessions/new_ldap', server: server - - if signin_enabled? - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' + - if crowd_enabled? + %div.tab-pane.active{id: "tab-crowd"} + = render 'devise/sessions/new_crowd' + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} + = render 'devise/sessions/new_ldap', server: server + - if signin_enabled? + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' - elsif signin_enabled? = render 'devise/sessions/new_base' diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 905a8dbcd84..c43a6aa3e49 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -1,28 +1,30 @@ -.login-box - - if signin_enabled? - .login-heading - %h3 New user? Create an account - - else - .login-heading - %h3 Create an account +#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' } .login-body - = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f| + = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f| .devise-errors = devise_error_messages! - %div - = f.text_field :name, class: "form-control top", placeholder: "Name", required: true - %div - = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true - %div - = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true + %div.form-group + = f.label :name + = f.text_field :name, class: "form-control top", required: true, title: "This field is required." + %div.username.form-group + = f.label :username + = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true + %p.gl-field-error.hide Please create a username with only alphanumeric characters. + %p.validation-error.hide Username is already taken. + %p.validation-success.hide Username is available. + %p.validation-pending.hide Checking username availability... + %div.form-group + = f.label :email + = f.email_field :email, class: "form-control middle", required: true, title: "Please provide a valid email address." .form-group.append-bottom-20#password-strength - = f.password_field :password, class: "form-control bottom", placeholder: "Password - minimum length #{@minimum_password_length} characters", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters" + = f.label :password + = f.password_field :password, class: "form-control bottom", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters." + %p.gl-field-hint Minimum length is #{@minimum_password_length} characters %div - if current_application_settings.recaptcha_enabled = recaptcha_tags %div - = f.submit "Sign up", class: "btn-create btn" - + = f.submit "Register", class: "btn-register btn" .clearfix.prepend-top-20 %p %span.light Didn't receive a confirmation email? diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml new file mode 100644 index 00000000000..8590c43d54d --- /dev/null +++ b/app/views/devise/shared/_tab_single.html.haml @@ -0,0 +1,4 @@ +// = render 'devise/shared/tab_single', :tab_title => 'Tab Title' +%ul.nav-links.nav-tabs.new-session-tabs.single-tab + %li.active + = link_to tab_title, '#', disabled: true diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml new file mode 100644 index 00000000000..e276e91433a --- /dev/null +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -0,0 +1,10 @@ +%ul.new-session-tabs.nav-links.nav-tabs + - if crowd_enabled? + %li.active + = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero? && !crowd_enabled?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if signin_enabled? + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml new file mode 100644 index 00000000000..48abd6519d6 --- /dev/null +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -0,0 +1,5 @@ +%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'} + %li.active{ role: 'presentation' } + %a{ href: '#login-pane', data: {'toggle':'tab'}, role: 'tab'} Sign in + %li{ role: 'presentation'} + %a{ href: '#register-pane', data: {'toggle':'tab'}, role: 'tab'} Register diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml index 49c087c0646..0036f3b98e5 100644 --- a/app/views/devise/unlocks/new.html.haml +++ b/app/views/devise/unlocks/new.html.haml @@ -1,12 +1,12 @@ += render 'devise/shared/tab_single', { :tab_title => 'Resend unlock instructions' } .login-box - .login-heading - %h3 Resend unlock email .login-body - = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| + = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! - .clearfix.append-bottom-20 - = f.email_field :email, class: 'form-control', placeholder: 'Email', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off' + .form-group.append-bottom-20 + = f.label :email + = f.email_field :email, class: 'form-control', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', title: 'Please provide a valid email address.' .clearfix = f.submit 'Resend unlock instructions', class: 'btn btn-success' diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index a9a384bd5f3..825e540cb0c 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,36 +1,37 @@ !!! 5 -%html{ lang: "en"} +%html{ lang: "en", class: "devise-layout-html"} = render "layouts/head" - %body.ui_charcoal.login-page.application.navless - = Gon::Base.render_data - = render "layouts/header/empty" - = render "layouts/broadcast" - .container.navless-container - .content - = render "layouts/flash" - .row - .col-sm-5.pull-right - = yield - .col-sm-7.brand-holder.pull-left - %h1 - = brand_title - - if brand_item - = brand_image - = brand_text - - else - %h3 Open source software to collaborate on code + %body{ class: "ui_charcoal login-page application navless", data: {page: body_data_page}} + .page-wrap + = Gon::Base.render_data + = render "layouts/header/empty" + = render "layouts/broadcast" + .container.navless-container + .content + = render "layouts/flash" + .row + .col-sm-5.pull-right.new-session-forms-container + = yield + .col-sm-7.brand-holder.pull-left + %h1 + = brand_title + - if brand_item + = brand_image + = brand_text + - else + %h3 Open source software to collaborate on code - %p - Manage git repositories with fine grained access controls that keep your code secure. - Perform code reviews and enhance collaboration with merge requests. - Each project can also have an issue tracker and a wiki. + %p + Manage git repositories with fine grained access controls that keep your code secure. + Perform code reviews and enhance collaboration with merge requests. + Each project can also have an issue tracker and a wiki. - if current_application_settings.sign_in_text.present? = markdown_field(current_application_settings, :sign_in_text) - %hr - .container - .footer-links - = link_to "Explore", explore_root_path - = link_to "Help", help_path - = link_to "About GitLab", "https://about.gitlab.com/" + %hr.footer-fixed + .container.footer-container + .footer-links + = link_to "Explore", explore_root_path + = link_to "Help", help_path + = link_to "About GitLab", "https://about.gitlab.com/" diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml index 9657101ace5..232ca26c1af 100644 --- a/app/views/u2f/_authenticate.html.haml +++ b/app/views/u2f/_authenticate.html.haml @@ -6,7 +6,7 @@ %script#js-authenticate-u2f-setup{ type: "text/template" } %div %p Insert your security key (if you haven't already), and press the button below. - %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Login Via U2F Device + %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Sign in via U2F device %script#js-authenticate-u2f-in-progress{ type: "text/template" } %p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now. diff --git a/config/routes.rb b/config/routes.rb index 83c3a42c19f..93d7f99fb90 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,6 +83,798 @@ Rails.application.routes.draw do draw :group draw :user draw :project + # + # Import + # + namespace :import do + resource :github, only: [:create, :new], controller: :github do + post :personal_access_token + get :status + get :callback + get :jobs + end + + resource :gitlab, only: [:create], controller: :gitlab do + get :status + get :callback + get :jobs + end + + resource :bitbucket, only: [:create], controller: :bitbucket do + get :status + get :callback + get :jobs + end + + resource :google_code, only: [:create, :new], controller: :google_code do + get :status + post :callback + get :jobs + + get :new_user_map, path: :user_map + post :create_user_map, path: :user_map + end + + resource :fogbugz, only: [:create, :new], controller: :fogbugz do + get :status + post :callback + get :jobs + + get :new_user_map, path: :user_map + post :create_user_map, path: :user_map + end + + resource :gitlab_project, only: [:create, :new] do + post :create + end + end + + # + # Uploads + # + + scope path: :uploads do + # Note attachments and User/Group/Project avatars + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } + + # Appearance + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } + + # Project markdown uploads + get ":namespace_id/:project_id/:secret/:filename", + to: "projects/uploads#show", + constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } + end + + # Redirect old note attachments path to new uploads path. + get "files/note/:id/:filename", + to: redirect("uploads/note/attachment/%{id}/%{filename}"), + constraints: { filename: /[^\/]+/ } + + # + # Explore area + # + namespace :explore do + resources :projects, only: [:index] do + collection do + get :trending + get :starred + end + end + + resources :groups, only: [:index] + resources :snippets, only: [:index] + root to: 'projects#trending' + end + + # Compatibility with old routing + get 'public' => 'explore/projects#index' + get 'public/projects' => 'explore/projects#index' + + # + # Admin Area + # + namespace :admin do + resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do + resources :keys, only: [:show, :destroy] + resources :identities, except: [:show] + + member do + get :projects + get :keys + get :groups + put :block + put :unblock + put :unlock + put :confirm + post :impersonate + patch :disable_two_factor + delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' + end + end + + resource :impersonation, only: :destroy + + resources :abuse_reports, only: [:index, :destroy] + resources :spam_logs, only: [:index, :destroy] do + member do + post :mark_as_ham + end + end + + resources :applications + + resources :groups, constraints: { id: /[^\/]+/ } do + member do + put :members_update + end + end + + resources :deploy_keys, only: [:index, :new, :create, :destroy] + + resources :hooks, only: [:index, :create, :destroy] do + get :test + end + + resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do + post :preview, on: :collection + end + + resource :logs, only: [:show] + resource :health_check, controller: 'health_check', only: [:show] + resource :background_jobs, controller: 'background_jobs', only: [:show] + resource :system_info, controller: 'system_info', only: [:show] + resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } + + resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do + root to: 'projects#index', as: :projects + + resources(:projects, + path: '/', + constraints: { id: /[a-zA-Z.0-9_\-]+/ }, + only: [:index, :show]) do + root to: 'projects#show' + + member do + put :transfer + post :repository_check + end + + resources :runner_projects, only: [:create, :destroy] + end + end + + resource :appearances, only: [:show, :create, :update], path: 'appearance' do + member do + get :preview + delete :logo + delete :header_logos + end + end + + resource :application_settings, only: [:show, :update] do + resources :services, only: [:index, :edit, :update] + put :reset_runners_token + put :reset_health_check_token + put :clear_repository_check_states + end + + resources :labels + + resources :runners, only: [:index, :show, :update, :destroy] do + member do + get :resume + get :pause + end + end + + resources :builds, only: :index do + collection do + post :cancel_all + end + end + + root to: 'dashboard#index' + end + + # + # Profile Area + # + resource :profile, only: [:show, :update] do + member do + get :audit_log + get :applications, to: 'oauth/applications#index' + + put :reset_private_token + put :update_username + end + + scope module: :profiles do + resource :account, only: [:show] do + member do + delete :unlink + end + end + resource :notifications, only: [:show, :update] + resource :password, only: [:new, :create, :edit, :update] do + member do + put :reset + end + end + resource :preferences, only: [:show, :update] + resources :keys, only: [:index, :show, :new, :create, :destroy] + resources :emails, only: [:index, :create, :destroy] + resource :avatar, only: [:destroy] + + resources :personal_access_tokens, only: [:index, :create] do + member do + put :revoke + end + end + + resource :two_factor_auth, only: [:show, :create, :destroy] do + member do + post :create_u2f + post :codes + patch :skip + end + end + + resources :u2f_registrations, only: [:destroy] + end + end + + scope(path: 'u/:username', + as: :user, + constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error + get '/users/almost_there' => 'confirmations#almost_there' + end + + root to: "root#index" + + # + # Project Area + # + resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do + resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template + + scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + to: 'blob#destroy', + constraints: { id: /.+/, format: false } + ) + put( + '/blob/*id', + to: 'blob#update', + constraints: { id: /.+/, format: false } + ) + post( + '/blob/*id', + to: 'blob#create', + constraints: { id: /.+/, format: false } + ) + end + + scope do + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) + end + + scope do + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) + end + + scope do + get( + '/find_file/*id', + to: 'find_file#show', + constraints: { id: /.+/, format: /html/ }, + as: :find_file + ) + end + + scope do + get( + '/files/*id', + to: 'find_file#list', + constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, + as: :files + ) + end + + scope do + post( + '/create_dir/*id', + to: 'tree#create_dir', + constraints: { id: /.+/ }, + as: 'create_dir' + ) + end + + scope do + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) + end + + scope do + get( + '/commits/*id', + to: 'commits#show', + constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, + as: :commits + ) + end + + resource :avatar, only: [:show, :destroy] + resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do + member do + get :branches + get :builds + get :pipelines + post :cancel_builds + post :retry_builds + post :revert + post :cherry_pick + get :diff_for_path + end + end + + resources :compare, only: [:index, :create] do + collection do + get :diff_for_path + end + end + + get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } + + # Don't use format parameter as file extension (old 3.0.x behavior) + # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments + scope format: false do + resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } + + resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do + member do + get :commits + get :ci + get :languages + end + end + end + + resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do + member do + get 'raw' + end + end + + WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID + + scope do + # Order matters to give priority to these matches + get '/wikis/git_access', to: 'wikis#git_access' + get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' + post '/wikis', to: 'wikis#create' + + get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID + get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID + + get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID + delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID + put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID + post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' + end + + resource :repository, only: [:create] do + member do + get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } + end + end + + resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do + member do + get :test + end + end + + resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do + member do + put :enable + put :disable + end + end + + resources :forks, only: [:index, :new, :create] + resource :import, only: [:new, :create, :show] + + resources :refs, only: [] do + collection do + get 'switch' + end + + member do + # tree viewer logs + get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } + # Directories with leading dots erroneously get rejected if git + # ref regex used in constraints. Regex verification now done in controller. + get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { + id: /.*/, + path: /.*/ + } + end + end + + resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do + member do + get :commits + get :diffs + get :conflicts + get :builds + get :pipelines + get :merge_check + post :merge + post :cancel_merge_when_build_succeeds + get :ci_status + post :toggle_subscription + post :remove_wip + get :diff_for_path + post :resolve_conflicts + end + + collection do + get :branch_from + get :branch_to + get :update_branches + get :diff_for_path + post :bulk_update + end + + resources :discussions, only: [], constraints: { id: /\h{40}/ } do + member do + post :resolve + delete :resolve, action: :unresolve + end + end + end + + resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do + resource :release, only: [:edit, :update] + end + + resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :variables, only: [:index, :show, :update, :create, :destroy] + resources :triggers, only: [:index, :create, :destroy] + + resources :pipelines, only: [:index, :new, :create, :show] do + collection do + resource :pipelines_settings, path: 'settings', only: [:show, :update] + end + + member do + post :cancel + post :retry + end + end + + resources :environments + + resource :cycle_analytics, only: [:show] + + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do + collection do + post :cancel_all + + resources :artifacts, only: [] do + collection do + get :latest_succeeded, + path: '*ref_name_and_path', + format: false + end + end + end + + member do + get :status + post :cancel + post :retry + post :play + post :erase + get :trace + get :raw + end + + resource :artifacts, only: [] do + get :download + get :browse, path: 'browse(/*path)', format: false + get :file, path: 'file/*path', format: false + post :keep + end + end + + resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do + member do + get :test + end + end + + resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } + + resources :milestones, constraints: { id: /\d+/ } do + member do + put :sort_issues + put :sort_merge_requests + end + end + + resources :labels, except: [:show], constraints: { id: /\d+/ } do + collection do + post :generate + post :set_priorities + end + + member do + post :toggle_subscription + delete :remove_priority + end + end + + resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do + member do + post :toggle_subscription + post :mark_as_spam + get :referenced_merge_requests + get :related_branches + get :can_create_branch + end + collection do + post :bulk_update + end + end + + resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do + collection do + delete :leave + + # Used for import team + # from another project + get :import + post :apply_import + end + + member do + post :resend_invite + end + end + + resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } + + resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do + member do + delete :delete_attachment + post :resolve + delete :resolve, action: :unresolve + end + end + + resource :board, only: [:show] do + scope module: :boards do + resources :issues, only: [:update] + + resources :lists, only: [:index, :create, :update, :destroy] do + collection do + post :generate + end + + resources :issues, only: [:index] + end + end + end + + resources :todos, only: [:create] + + resources :uploads, only: [:create] do + collection do + get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } + end + end + + resources :runners, only: [:index, :edit, :update, :destroy, :show] do + member do + get :resume + get :pause + end + + collection do + post :toggle_shared_runners + end + end + + resources :runner_projects, only: [:create, :destroy] + resources :badges, only: [:index] do + collection do + scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do + constraints format: /svg/ do + get :build + get :coverage + end + end + end + end + end + end + end # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index a752c1d7235..65544f79eba 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -14,7 +14,7 @@ feature 'Signup', feature: true do fill_in 'new_user_username', with: user.username fill_in 'new_user_email', with: user.email fill_in 'new_user_password', with: user.password - click_button "Sign up" + click_button "Register" expect(current_path).to eq users_almost_there_path expect(page).to have_content("Please check your email to confirm your account") @@ -33,7 +33,7 @@ feature 'Signup', feature: true do fill_in 'new_user_username', with: user.username fill_in 'new_user_email', with: user.email fill_in 'new_user_password', with: user.password - click_button "Sign up" + click_button "Register" expect(current_path).to eq dashboard_projects_path expect(page).to have_content("Welcome! You have signed up successfully.") @@ -52,7 +52,7 @@ feature 'Signup', feature: true do fill_in 'new_user_username', with: user.username fill_in 'new_user_email', with: existing_user.email fill_in 'new_user_password', with: user.password - click_button "Sign up" + click_button "Register" expect(current_path).to eq user_registration_path expect(page).to have_content("error prohibited this user from being saved") @@ -69,7 +69,7 @@ feature 'Signup', feature: true do fill_in 'new_user_username', with: user.username fill_in 'new_user_email', with: existing_user.email fill_in 'new_user_password', with: user.password - click_button "Sign up" + click_button "Register" expect(current_path).to eq user_registration_path expect(page.body).not_to match(/#{user.password}/) diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index ff6933dc8d9..b750f27ea72 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -160,7 +160,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -174,7 +174,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -186,7 +186,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user, remember: true) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') within 'div#js-authenticate-u2f' do @@ -209,7 +209,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # Try authenticating user with the old U2F device login_as(current_user) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -230,7 +230,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # Try authenticating user with the same U2F device login_as(current_user) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -244,7 +244,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: unregistered_device = FakeU2fDevice.new(page, FFaker::Name.first_name) login_as(user) unregistered_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -271,7 +271,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: [first_device, second_device].each do |device| login_as(user) device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 6498b7317b4..63743169302 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -1,15 +1,16 @@ require 'spec_helper' -feature 'Users', feature: true do +feature 'Users', feature: true, js: true do let(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') } scenario 'GET /users/sign_in creates a new user account' do visit new_user_session_path + click_link 'Register' fill_in 'new_user_name', with: 'Name Surname' fill_in 'new_user_username', with: 'Great' fill_in 'new_user_email', with: 'name@mail.com' fill_in 'new_user_password', with: 'password1234' - expect { click_button 'Sign up' }.to change { User.count }.by(1) + expect { click_button 'Register' }.to change { User.count }.by(1) end scenario 'Successful user signin invalidates password reset token' do @@ -31,11 +32,12 @@ feature 'Users', feature: true do scenario 'Should show one error if email is already taken' do visit new_user_session_path + click_link 'Register' fill_in 'new_user_name', with: 'Another user name' fill_in 'new_user_username', with: 'anotheruser' fill_in 'new_user_email', with: user.email fill_in 'new_user_password', with: '12341234' - expect { click_button 'Sign up' }.to change { User.count }.by(0) + expect { click_button 'Register' }.to change { User.count }.by(0) expect(page).to have_text('Email has already been taken') expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}' end @@ -51,6 +53,30 @@ feature 'Users', feature: true do end end + feature 'username validation' do + include WaitForAjax + let(:loading_icon) { '.fa.fa-spinner' } + let(:username_input) { 'new_user_username' } + + before(:each) do + visit new_user_session_path + click_link 'Register' + @username_field = find '.username' + end + + scenario 'shows an error border if the username already exists' do + fill_in username_input, with: user.username + wait_for_ajax + expect(@username_field).to have_css '.gl-field-error-outline' + end + + scenario 'doesn\'t show an error border if the username is available' do + fill_in username_input, with: 'new-user' + wait_for_ajax + expect(@username_field).not_to have_css '.gl-field-error-outline' + end + end + def errors_on_page(page) page.find('#error_explanation').find('ul').all('li').map{ |item| item.text }.join("\n") end diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index 7ce3884f844..784b43d4846 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -21,7 +21,7 @@ setupButton = this.container.find("#js-login-u2f-device"); setupMessage = this.container.find("p"); expect(setupMessage.text()).toContain('Insert your security key'); - expect(setupButton.text()).toBe('Login Via U2F Device'); + expect(setupButton.text()).toBe('Sign in via U2F device'); setupButton.trigger('click'); inProgressMessage = this.container.find("p"); expect(inProgressMessage.text()).toContain("Trying to communicate with your device"); -- cgit v1.2.1 From d2bad46efee65688ab56d9d7c850775f1339f491 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 06:47:53 +0200 Subject: Add padding to fixed footer, to more quickly support scrolling. --- app/assets/stylesheets/pages/login.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 06b90fbefab..9cc1bf90122 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -250,4 +250,8 @@ height: 40px; background: $white-light; } + + .navless-container { + padding: 65px; // height of footer + bottom padding of email confirmation link + } } -- cgit v1.2.1 From 768cd071afa41acc5b9c91958ac280e1d6870dae Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 07:25:47 +0200 Subject: Clean up username_validator private vars and members. --- app/assets/javascripts/username_validator.js.es6 | 91 ++++++++++++------------ 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index f8be356af04..a22f598b753 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -1,34 +1,34 @@ ((global) => { const debounceTimeoutDuration = 1000; - const inputErrorClass = 'gl-field-error-outline'; - const inputSuccessClass = 'gl-field-success-outline'; - const messageErrorSelector = '.username .validation-error'; - const messageSuccessSelector = '.username .validation-success'; - const messagePendingSelector = '.username .validation-pending'; + const invalidInputClass = 'gl-field-error-outline'; + const successInputClass = 'gl-field-success-outline'; + const unavailableMessageSelector = '.username .validation-error'; + const successMessageSelector = '.username .validation-success'; + const pendingMessageSelector = '.username .validation-pending'; + const invalidMessageSelector = '.username .gl-field-error'; class UsernameValidator { constructor() { this.inputElement = $('#new_user_username'); this.inputDomElement = this.inputElement.get(0); - - this.available = false; - this.valid = false; - this.pending = false; - this.fresh = true; - this.empty = true; + this.state = { + available: false, + valid: false, + pending: false, + empty: true + }; const debounceTimeout = _.debounce((username) => { - this.validateUsername(username); + this.state.validateUsername(username); }, debounceTimeoutDuration); this.inputElement.on('keyup.username_check', () => { const username = this.inputElement.val(); - this.valid = this.inputDomElement.validity.valid; - this.fresh = false; - this.empty = !username.length; + this.state.valid = this.inputDomElement.validity.valid; + this.state.empty = !username.length; - if (this.valid) { + if (this.state.valid) { return debounceTimeout(username); } @@ -36,43 +36,43 @@ }); // Override generic field validation - this.inputElement.on('invalid', this.handleInvalidInput.bind(this)); + this.inputElement.on('invalid', this.interceptInvalid.bind(this)); } renderState() { // Clear all state this.clearFieldValidationState(); - if (this.valid && this.available) { + if (this.state.valid && this.state.available) { return this.setSuccessState(); } - if (this.empty) { + if (this.state.empty) { return this.clearFieldValidationState(); } - if (this.pending) { + if (this.state.pending) { return this.setPendingState(); } - if (!this.available) { + if (!this.state.available) { return this.setUnavailableState(); } - if (!this.valid) { + if (!this.state.valid) { return this.setInvalidState(); } } - handleInvalidInput(event) { + interceptInvalid(event) { event.preventDefault(); event.stopPropagation(); } validateUsername(username) { - if (this.valid) { - this.pending = true; - this.available = false; + if (this.state.valid) { + this.state.pending = true; + this.state.available = false; this.renderState(); return $.ajax({ type: 'GET', @@ -83,38 +83,40 @@ } } - updateValidationState(usernameTaken) { + setAvailabilityState(usernameTaken) { if (usernameTaken) { - this.valid = false; - this.available = false; + this.state.valid = false; + this.state.available = false; } else { - this.available = true; + this.state.available = true; } - this.pending = false; + this.state.pending = false; this.renderState(); } clearFieldValidationState() { - this.inputElement.siblings('p').hide(); - this.inputElement.removeClass(inputErrorClass); - this.inputElement.removeClass(inputSuccessClass); + // TODO: Double check if this is valid chaining + const $input = this.inputElement + .siblings('p').hide().end() + .removeClass(invalidInputClass); + removeClass(successInputClass); } setUnavailableState() { - const $usernameErrorMessage = this.inputElement.siblings(messageErrorSelector); - this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); - $usernameErrorMessage.show(); + const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $usernameUnavailableMessage.show(); } setSuccessState() { - const $usernameSuccessMessage = this.inputElement.siblings(messageSuccessSelector); - this.inputElement.addClass(inputSuccessClass).removeClass(inputErrorClass); + const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector); + this.inputElement.addClass(successInputClass).removeClass(invalidInputClass); $usernameSuccessMessage.show(); } - setPendingState(show) { - const $usernamePendingMessage = $(messagePendingSelector); - if (this.pending) { + setPendingState() { + const $usernamePendingMessage = $(pendingMessageSelector); + if (this.state.pending) { $usernamePendingMessage.show(); } else { $usernamePendingMessage.hide(); @@ -122,8 +124,9 @@ } setInvalidState() { - this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); - $(`.gl-field-error`).show(); + const $inputErrorMessage = $(invalidMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $inputErrorMessage.show(); } } -- cgit v1.2.1 From a449b9b8a171c659c7b3ce37f685624e3e079192 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 08:31:04 +0200 Subject: Refactor gl field errors for simpler state management. --- app/assets/javascripts/gl_field_errors.js.es6 | 134 +++++++++++++++++--------- 1 file changed, 89 insertions(+), 45 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 42a2ddeeafe..f4c09dd407d 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -13,79 +13,123 @@ * * */ - const fieldErrorClass = 'gl-field-error'; - const fieldErrorSelector = `.${fieldErrorClass}`; + const errorMessageClass = 'gl-field-error'; const inputErrorClass = 'gl-field-error-outline'; - class GlFieldErrors { - constructor(form) { - this.form = $(form); - this.initValidators(); + class GlFieldError { + constructor({ input, form }) { + this.inputElement = $(input); + this.inputDomElement = this.inputElement.get(0); + this.form = form; + this.errorMessage = this.inputElement.attr('title') || 'This field is required.'; + this.fieldErrorElement = $(`

      ${ this.errorMessage }

      `); + + this.state = { + valid: false, + empty: true + }; + + this.initFieldValidation(); } - initValidators () { - this.inputs = this.form.find(':input:not([type=hidden])').toArray(); - this.inputs.forEach((input) => { - $(input).off('invalid').on('invalid', this.handleInvalidInput.bind(this)); - }); - this.form.on('submit', this.catchInvalidFormSubmit); + initFieldValidation() { + // hidden when injected into DOM + $input.after(this.fieldErrorElement); + this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); } - /* Neccessary because Safari & iOS quietly allow form submission when form is invalid */ - catchInvalidFormSubmit (event) { - if (!event.currentTarget.checkValidity()) { - event.preventDefault(); - // Prevents disabling of invalid submit button by application.js - event.stopPropagation(); + renderValidity() { + this.setClearState(); + + if (this.state.valid) { + this.setValidState(); + } + + if (this.state.empty) { + this.setEmptyState(); + } + + if (!this.state.valid) { + this.setInvalidState(); } + + this.form.focusOnFirstInvalid.apply(this); } - handleInvalidInput (event) { + handleInvalidInput(event) { event.preventDefault(); - this.updateFieldValidityState(event); - const $input = $(event.currentTarget); + this.state.valid = true; + this.state.empty = false; + + this.renderValidity(); // For UX, wait til after first invalid submission to check each keyup - $input.off('keyup.field_validator') - .on('keyup.field_validator', this.updateFieldValidityState.bind(this)); + this.inputElement.off('keyup.field_validator') + .on('keyup.field_validator', this.updateValidityState.bind(this)); } - displayFieldValidity (target, isValid) { - const $input = $(target).removeClass(inputErrorClass); - const $existingError = $input.siblings(fieldErrorSelector); - const alreadyInvalid = !!$existingError.length; - const implicitErrorMessage = $input.attr('title'); - const $errorToDisplay = alreadyInvalid ? $existingError.detach() : $(`

      ${implicitErrorMessage}

      `); + getInputValidity() { + return this.inputDomElement.validity.valid; + } - if (!isValid) { - $input.after($errorToDisplay); - $input.addClass(inputErrorClass); - } + updateValidityState() { + const inputVal = this.inputElement.val(); + this.state.empty = !!inputVal.length; + this.state.valid = this.getInputValidity; - this.updateFieldSiblings($errorToDisplay, isValid); + this.renderValidity(); } - updateFieldSiblings($target, isValid) { - const siblings = $target.siblings(`p${fieldErrorSelector}`); - return isValid ? siblings.show() : siblings.hide(); + setValidState() { + return this.setClearState(); + } + + setEmptyState() { + return this.setClearState(); + } + + setInvalidState() { + $input.addClass(inputErrorClass); + return this.$fieldErrorElement.show(); + } + + setClearState() { + $input.removeClass(inputErrorClass); + return this.fieldErrorElement.hide(); } checkFieldValidity(target) { return target.validity.valid; } + } - updateFieldValidityState(event) { - const target = event.currentTarget; - const isKeyup = event.type === 'keyup'; - const isValid = this.checkFieldValidity(target); + class GlFieldErrors { + constructor(form) { + this.form = $(form); + this.initValidators(); + } - this.displayFieldValidity(target, isValid); + initValidators () { + // select all non-hidden inputs in form + const form = this.form; + + this.inputs = this.form.find(':input:not([type=hidden])') + .toArray() + .map((input) => new GlFieldError({ input, form })); + + this.form.on('submit', this.catchInvalidFormSubmit); + } - // prevent changing focus while user is typing. - if (!isKeyup) { - this.focusOnFirstInvalid.apply(this); + /* Neccessary to prevent intercept and override invalid form submit + * because Safari & iOS quietly allow form submission when form is invalid + * and prevents disabling of invalid submit button by application.js */ + + catchInvalidFormSubmit (event) { + if (!event.currentTarget.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); } } -- cgit v1.2.1 From 00bfb645e16b24ce929211bf5080aa3083f8543b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 10:29:21 +0200 Subject: Fix errors, get validation running for signup box and sign in. --- app/assets/javascripts/gl_field_errors.js.es6 | 52 ++++++++++++++---------- app/assets/javascripts/username_validator.js.es6 | 12 +++--- app/views/devise/shared/_signup_box.html.haml | 2 +- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index f4c09dd407d..e1de7f78efc 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -17,10 +17,10 @@ const inputErrorClass = 'gl-field-error-outline'; class GlFieldError { - constructor({ input, form }) { + constructor({ input, formErrors }) { this.inputElement = $(input); this.inputDomElement = this.inputElement.get(0); - this.form = form; + this.form = formErrors; this.errorMessage = this.inputElement.attr('title') || 'This field is required.'; this.fieldErrorElement = $(`

      ${ this.errorMessage }

      `); @@ -34,7 +34,7 @@ initFieldValidation() { // hidden when injected into DOM - $input.after(this.fieldErrorElement); + this.inputElement.after(this.fieldErrorElement); this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); } @@ -42,24 +42,24 @@ this.setClearState(); if (this.state.valid) { - this.setValidState(); + return this.setValidState(); } if (this.state.empty) { - this.setEmptyState(); + return this.setEmptyState(); } if (!this.state.valid) { - this.setInvalidState(); + return this.setInvalidState(); } - this.form.focusOnFirstInvalid.apply(this); + this.form.focusOnFirstInvalid.apply(this.form); } handleInvalidInput(event) { event.preventDefault(); - this.state.valid = true; + this.state.valid = false; this.state.empty = false; this.renderValidity(); @@ -77,8 +77,7 @@ updateValidityState() { const inputVal = this.inputElement.val(); this.state.empty = !!inputVal.length; - this.state.valid = this.getInputValidity; - + this.state.valid = this.getInputValidity(); this.renderValidity(); } @@ -87,17 +86,24 @@ } setEmptyState() { - return this.setClearState(); + return this.setInvalidState(); } setInvalidState() { - $input.addClass(inputErrorClass); - return this.$fieldErrorElement.show(); + this.inputElement.addClass(inputErrorClass); + this.inputElement.siblings('p').hide(); + return this.fieldErrorElement.show(); } setClearState() { - $input.removeClass(inputErrorClass); - return this.fieldErrorElement.hide(); + const inputVal = this.inputElement.val(); + if (!inputVal.split(' ').length) { + const trimmedInput = this.inputElement.val().trim(); + this.inputElement.val(trimmedInput); + } + this.inputElement.removeClass(inputErrorClass); + this.inputElement.siblings('p').hide(); + this.fieldErrorElement.hide(); } checkFieldValidity(target) { @@ -105,19 +111,23 @@ } } + const customValidationFlag = 'no-gl-field-errors'; + class GlFieldErrors { constructor(form) { this.form = $(form); + this.state = { + inputs: [], + valid: false + }; this.initValidators(); } initValidators () { // select all non-hidden inputs in form - const form = this.form; - - this.inputs = this.form.find(':input:not([type=hidden])') - .toArray() - .map((input) => new GlFieldError({ input, form })); + this.state.inputs = this.form.find(':input:not([type=hidden])').toArray() + .filter((input) => !input.classList.contains(customValidationFlag)) + .map((input) => new GlFieldError({ input, formErrors: this })); this.form.on('submit', this.catchInvalidFormSubmit); } @@ -134,7 +144,7 @@ } focusOnFirstInvalid () { - const firstInvalid = this.inputs.find((input) => !input.validity.valid); + const firstInvalid = this.state.inputs.find((input) => !input.inputDomElement.validity.valid); $(firstInvalid).focus(); } } diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index a22f598b753..b19fb9b4771 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -19,7 +19,7 @@ }; const debounceTimeout = _.debounce((username) => { - this.state.validateUsername(username); + this.validateUsername(username); }, debounceTimeoutDuration); this.inputElement.on('keyup.username_check', () => { @@ -78,7 +78,7 @@ type: 'GET', url: `/u/${username}/exists`, dataType: 'json', - success: (res) => this.updateValidationState(res.exists) + success: (res) => this.setAvailabilityState(res.exists) }); } } @@ -96,10 +96,10 @@ clearFieldValidationState() { // TODO: Double check if this is valid chaining - const $input = this.inputElement - .siblings('p').hide().end() - .removeClass(invalidInputClass); - removeClass(successInputClass); + this.inputElement.siblings('p').hide(); + + this.inputElement.removeClass(invalidInputClass) + .removeClass(successInputClass); } setUnavailableState() { diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index c43a6aa3e49..7382042cc50 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -8,7 +8,7 @@ = f.text_field :name, class: "form-control top", required: true, title: "This field is required." %div.username.form-group = f.label :username - = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true + = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true %p.gl-field-error.hide Please create a username with only alphanumeric characters. %p.validation-error.hide Username is already taken. %p.validation-success.hide Username is available. -- cgit v1.2.1 From 83fb1190205c3d1fbe16a684794ed1af10ed1bff Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 11:15:07 +0200 Subject: Get tests passing. --- spec/features/users_spec.rb | 9 +++++---- spec/support/login_helpers.rb | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 63743169302..73d6fb6b651 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -61,16 +61,17 @@ feature 'Users', feature: true, js: true do before(:each) do visit new_user_session_path click_link 'Register' - @username_field = find '.username' + @username_form_group = find '.username' + @username_field = find '#new_user_username' end - scenario 'shows an error border if the username already exists' do + scenario 'shows an error border if the username already exists', focus: true do fill_in username_input, with: user.username wait_for_ajax - expect(@username_field).to have_css '.gl-field-error-outline' + expect(@username_form_group).to have_css '.gl-field-error-outline' end - scenario 'doesn\'t show an error border if the username is available' do + scenario 'doesn\'t show an error border if the username is available', focus: true do fill_in username_input, with: 'new-user' wait_for_ajax expect(@username_field).not_to have_css '.gl-field-error-outline' diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index c0b3e83244d..3e90c95918c 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -33,7 +33,9 @@ module LoginHelpers fill_in "user_login", with: user.email fill_in "user_password", with: "12345678" check 'user_remember_me' if remember - click_button "Sign in" + page.within '.login-box' do + click_button "Sign in" + end Thread.current[:current_user] = user end -- cgit v1.2.1 From 74bfba72263ae28a1a9a9fd53984e5d11a125fa7 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 11:25:30 +0200 Subject: Include correct validation error with username invalid. --- app/views/devise/shared/_signup_box.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 7382042cc50..fb7ee2dbd3b 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -8,8 +8,7 @@ = f.text_field :name, class: "form-control top", required: true, title: "This field is required." %div.username.form-group = f.label :username - = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true - %p.gl-field-error.hide Please create a username with only alphanumeric characters. + = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.' %p.validation-error.hide Username is already taken. %p.validation-success.hide Username is available. %p.validation-pending.hide Checking username availability... -- cgit v1.2.1 From bd0bf40763ed7acd43d24413f4b4a421f0076242 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 13:30:26 +0200 Subject: Make style fixes, make all submit buttons full-width btn-block. --- app/assets/stylesheets/pages/login.scss | 5 +++++ app/views/devise/sessions/_new_base.html.haml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 9cc1bf90122..f680fd68d70 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -198,6 +198,11 @@ font-weight: normal; } + input[type="submit"] { + @extend .btn-block; + margin-bottom: 0px; + } + .devise-errors { h2 { margin-top: 0; diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index cfb1b964d76..d0cd418236a 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -5,8 +5,6 @@ %div.form-group = f.label :password = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required." - .sign-in - = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "user_remember_me"} @@ -14,3 +12,5 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) + %div.prepend-top-20 + = f.submit "Sign in", class: "btn btn-save" -- cgit v1.2.1 From 2c7a1af66f45e475b5aa7b8b91e7f8c85134a762 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 13:38:39 +0200 Subject: Add submit button contain with custom margin. --- app/assets/stylesheets/pages/login.scss | 4 ++++ app/views/devise/sessions/_new_base.html.haml | 2 +- app/views/devise/shared/_signup_box.html.haml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index f680fd68d70..b235eb845b0 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -198,6 +198,10 @@ font-weight: normal; } + .submit-container { + margin-top: 16px; + } + input[type="submit"] { @extend .btn-block; margin-bottom: 0px; diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index d0cd418236a..a96b579c593 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -12,5 +12,5 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) - %div.prepend-top-20 + %div.submit-container = f.submit "Sign in", class: "btn btn-save" diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index fb7ee2dbd3b..d0bbcf3115e 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -24,7 +24,7 @@ = recaptcha_tags %div = f.submit "Register", class: "btn-register btn" -.clearfix.prepend-top-20 +.clearfix.submit-container %p %span.light Didn't receive a confirmation email? = succeed '.' do -- cgit v1.2.1 From 49688d399a3b3c7d3ec50ae0bb728ff0d25671b0 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 14:06:18 +0200 Subject: Add third box shadow to validation focus, for depth. --- app/assets/stylesheets/pages/login.scss | 90 ++++++++++++++------------------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index b235eb845b0..f1d15417705 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -40,7 +40,7 @@ p { font-size: 13px; } - .login-box { + .login-box, .omniauth-container { box-shadow: 0 0 0 1px $border-color; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; @@ -79,69 +79,57 @@ background: transparent; } - .form-control { - font-size: 14px; - padding: 10px 8px; - width: 100%; - height: auto; - - &.top { - border-radius: 5px 5px 0 0; - margin-bottom: 0; - } + // Styles the glowing border of focused input for username async validation + .login-body { + font-size: 13px; - &.bottom { - border-radius: 0 0 5px 5px; - border-top: 0; - margin-bottom: 20px; - } - } - // Styles the glowing border of focused input for username async validation - .login-body { - font-size: 13px; + input + p { + margin-top: 5px; + } + .gl-field-success-outline { + border: 1px solid $green-normal; - input + p { - margin-top: 5px; - } + &:focus { + box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal; + border: 0 none; + } + } - .gl-field-success-outline { - border: 1px solid $green-normal; + .gl-field-error-outline { + border: 1px solid $red-normal; - &:focus { - box-shadow: 0 0 0 1px $green-normal inset, 0 0 4px 0 $green-normal; - border: 0 none; + &:focus { + box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6); + border: 0 none; + } } - } - .gl-field-error-outline { - border: 1px solid $red-normal; + .username .validation-success, + .gl-field-success-message { + color: $green-normal; + } - &:focus { - opacity: .6; - box-shadow: 0 0 0 1px $red-normal inset, 0 0 4px 0 $red-normal; - border: 0 none; + .username .validation-error, + .gl-field-error-message { + color: $red-normal; } - } - .username .validation-success, - .gl-field-success-message { - color: $green-normal; - } + .gl-field-hint { + color: $gl-text-color; + } - .username .validation-error, - .gl-field-error-message { - color: $red-normal; } + } - .gl-field-hint { - color: $gl-text-color; + .omniauth-container { + p { + margin: 0; } - } - - .new-session-tabs { // Are these being applied to other login-related screens? They need to be. + .new-session-tabs { + display: -webkit-flex; display: flex; box-shadow: 0 0 0 1px $border-color; border-top-right-radius: 2px; @@ -151,10 +139,6 @@ flex: 1; text-align: center; - &.middle { - border-top: 0; - margin-bottom: 0; - border-radius: 0; &:last-of-type { border-left: 1px solid $border-color; } @@ -204,7 +188,7 @@ input[type="submit"] { @extend .btn-block; - margin-bottom: 0px; + margin-bottom: 0; } .devise-errors { -- cgit v1.2.1 From 076749645a51c4fe948cdf5ed16dc8455a9aae05 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 14:54:15 +0200 Subject: Shush rubocop. --- spec/features/users_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 73d6fb6b651..17a555e4673 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -65,13 +65,13 @@ feature 'Users', feature: true, js: true do @username_field = find '#new_user_username' end - scenario 'shows an error border if the username already exists', focus: true do + scenario 'shows an error border if the username already exists' do fill_in username_input, with: user.username wait_for_ajax expect(@username_form_group).to have_css '.gl-field-error-outline' end - scenario 'doesn\'t show an error border if the username is available', focus: true do + scenario 'doesn\'t show an error border if the username is available' do fill_in username_input, with: 'new-user' wait_for_ajax expect(@username_field).not_to have_css '.gl-field-error-outline' -- cgit v1.2.1 From 80cbc9838eae8836a9bf85bac1dca7e965d1d77d Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 17:07:51 +0200 Subject: Fix omniauth box styling. --- app/views/devise/sessions/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 2fb05b9456b..dbdbc9689aa 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -13,7 +13,7 @@ = render 'devise/shared/signup_box' - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix + .clearfix.omniauth-box = render 'devise/shared/omniauth_box' -# Show a message if none of the mechanisms above are enabled -- cgit v1.2.1 From 503dcacaa42bc5870a87b579009c53c991b03c4e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 12:59:58 +0200 Subject: Properly implement focus on first invalid. --- app/assets/javascripts/gl_field_errors.js.es6 | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index e1de7f78efc..91c25047f7b 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -53,17 +53,16 @@ return this.setInvalidState(); } - this.form.focusOnFirstInvalid.apply(this.form); } handleInvalidInput(event) { event.preventDefault(); - + const currentValue = this.inputElement.val(); this.state.valid = false; - this.state.empty = false; + this.state.empty = currentValue === ''; this.renderValidity(); - + this.form.focusOnFirstInvalid.apply(this.form); // For UX, wait til after first invalid submission to check each keyup this.inputElement.off('keyup.field_validator') .on('keyup.field_validator', this.updateValidityState.bind(this)); @@ -76,7 +75,7 @@ updateValidityState() { const inputVal = this.inputElement.val(); - this.state.empty = !!inputVal.length; + this.state.empty = !inputVal.length; this.state.valid = this.getInputValidity(); this.renderValidity(); } @@ -105,10 +104,6 @@ this.inputElement.siblings('p').hide(); this.fieldErrorElement.hide(); } - - checkFieldValidity(target) { - return target.validity.valid; - } } const customValidationFlag = 'no-gl-field-errors'; @@ -144,8 +139,8 @@ } focusOnFirstInvalid () { - const firstInvalid = this.state.inputs.find((input) => !input.inputDomElement.validity.valid); - $(firstInvalid).focus(); + const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0]; + firstInvalid.inputElement.focus(); } } -- cgit v1.2.1 From 69f9c00cfb7d80959a523a10ef7d5f53a37b21e5 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 13:01:03 +0200 Subject: Add tests for gl_field_errors. --- .../javascripts/fixtures/gl_field_errors.html.haml | 15 +++ spec/javascripts/gl_field_errors_spec.js.es6 | 111 +++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 spec/javascripts/fixtures/gl_field_errors.html.haml create mode 100644 spec/javascripts/gl_field_errors_spec.js.es6 diff --git a/spec/javascripts/fixtures/gl_field_errors.html.haml b/spec/javascripts/fixtures/gl_field_errors.html.haml new file mode 100644 index 00000000000..2526e5e33a5 --- /dev/null +++ b/spec/javascripts/fixtures/gl_field_errors.html.haml @@ -0,0 +1,15 @@ +%form.show-gl-field-errors{action: 'submit', method: 'post'} + .form-group + %input.required-text{required: true, type: 'text'} Text + .form-group + %input.email{type: 'email', title: 'Please provide a valid email address.', required: true } Email + .form-group + %input.password{type: 'password', required: true} Password + .form-group + %input.alphanumeric{type: 'text', pattern: '[a-zA-Z0-9]', required: true} Alphanumeric + .form-group + %input.hidden{ type:'hidden' } + .form-group + %input.custom.no-gl-field-errors{ type:'text' } Custom, do not validate + .form-group + %input.submit{type: 'submit'} Submit diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6 new file mode 100644 index 00000000000..36feb2b2aa5 --- /dev/null +++ b/spec/javascripts/gl_field_errors_spec.js.es6 @@ -0,0 +1,111 @@ +//= require jquery +//= require gl_field_errors + +((global) => { + fixture.preload('gl_field_errors.html'); + + describe('GL Style Field Errors', function() { + beforeEach(function() { + fixture.load('gl_field_errors.html'); + const $form = this.$form = $('form.show-gl-field-errors'); + this.fieldErrors = new global.GlFieldErrors($form); + }); + + it('should properly initialize the form', function() { + expect(this.$form).toBeDefined(); + expect(this.$form.length).toBe(1); + expect(this.fieldErrors).toBeDefined(); + const inputs = this.fieldErrors.state.inputs; + expect(inputs.length).toBe(5); + }); + + it('should ignore elements with custom error handling', function() { + const customErrorFlag = 'no-gl-field-errors'; + const customErrorElem = $(`.${customErrorFlag}`); + + expect(customErrorElem.length).toBe(1); + + const customErrors = this.fieldErrors.state.inputs.filter((input) => { + return input.inputElement.hasClass(customErrorFlag); + }); + expect(customErrors.length).toBe(0); + }); + + it('should not show any errors before submit attempt', function() { + this.$form.find('.email').val('not-a-valid-email').keyup(); + this.$form.find('.text-required').val('').keyup(); + this.$form.find('.alphanumberic').val('?---*').keyup(); + + const errorsShown = this.$form.find('.gl-field-error-outline'); + expect(errorsShown.length).toBe(0); + }); + + it('should show errors when input valid is submitted', function() { + this.$form.find('.email').val('not-a-valid-email').keyup(); + this.$form.find('.text-required').val('').keyup(); + this.$form.find('.alphanumberic').val('?---*').keyup(); + + this.$form.submit(); + + const errorsShown = this.$form.find('.gl-field-error-outline'); + expect(errorsShown.length).toBe(4); + }); + + it('should properly track validity state on input after invalid submission attempt', function() { + this.$form.submit(); + + const emailInputModel = this.fieldErrors.state.inputs[1]; + const fieldState = emailInputModel.state; + const emailInputElement = emailInputModel.inputElement; + + // No input + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(true); + expect(fieldState.valid).toBe(false); + + // Then invalid input + emailInputElement.val('not-a-valid-email').keyup(); + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(false); + + // Then valid input + emailInputElement.val('email@gitlab.com').keyup(); + expect(emailInputElement).not.toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(true); + + // Then invalid input + emailInputElement.val('not-a-valid-email').keyup(); + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(false); + + // Then empty input + emailInputElement.val('').keyup(); + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(true); + expect(fieldState.valid).toBe(false); + + // Then valid input + emailInputElement.val('email@gitlab.com').keyup(); + expect(emailInputElement).not.toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(true); + }); + + it('should properly infer error messages', function() { + this.$form.submit(); + const trackedInputs = this.fieldErrors.state.inputs; + const inputHasTitle = trackedInputs[1]; + const hasTitleErrorElem = inputHasTitle.inputElement.siblings('.gl-field-error'); + const inputNoTitle = trackedInputs[2]; + const noTitleErrorElem = inputNoTitle.inputElement.siblings('.gl-field-error'); + + expect(noTitleErrorElem.text()).toBe('This field is required.'); + expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.'); + }); + + }); + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 673c23dd7ebef7d6c370a0d0d5c024c1c042261c Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 13:26:37 +0200 Subject: Unbreak all the tests relying on one login-box. --- app/views/devise/sessions/new.html.haml | 2 +- app/views/devise/shared/_omniauth_box.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index dbdbc9689aa..2fb05b9456b 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -13,7 +13,7 @@ = render 'devise/shared/signup_box' - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix.omniauth-box + .clearfix = render 'devise/shared/omniauth_box' -# Show a message if none of the mechanisms above are enabled diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index d5b6db48a29..8908b64cdac 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -1,4 +1,4 @@ -%div.login-box +%div.omniauth-container %p %span.light Sign in with   -- cgit v1.2.1 From 1fd09260826413da928363c3199d4dfbb770d2c3 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 15:47:21 +0200 Subject: Click first Sign In in login_helper. --- spec/support/login_helpers.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 3e90c95918c..ea9edc85248 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -33,9 +33,7 @@ module LoginHelpers fill_in "user_login", with: user.email fill_in "user_password", with: "12345678" check 'user_remember_me' if remember - page.within '.login-box' do - click_button "Sign in" - end + first('.login-box').click_button('Sign in') Thread.current[:current_user] = user end -- cgit v1.2.1 From 445711675ca198660dcaee014cd4229b8eb266ab Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 17:13:42 +0200 Subject: Fix nesting on sessions/new. --- app/views/devise/sessions/new.html.haml | 15 +++++++-------- spec/support/login_helpers.rb | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 2fb05b9456b..fa8e7979461 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -12,12 +12,11 @@ - if signin_enabled? && signup_enabled? = render 'devise/shared/signup_box' - - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix - = render 'devise/shared/omniauth_box' - - -# Show a message if none of the mechanisms above are enabled - - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) - %div - No authentication methods configured. + -# Show a message if none of the mechanisms above are enabled + - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) + %div + No authentication methods configured. + - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? + .clearfix + = render 'devise/shared/omniauth_box' diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index ea9edc85248..c0b3e83244d 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -33,7 +33,7 @@ module LoginHelpers fill_in "user_login", with: user.email fill_in "user_password", with: "12345678" check 'user_remember_me' if remember - first('.login-box').click_button('Sign in') + click_button "Sign in" Thread.current[:current_user] = user end -- cgit v1.2.1 From c2766614ddc04b8df54fe8649d1dbe516f532987 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 29 Sep 2016 11:58:43 +0200 Subject: Fix syntax error -- missing comma in _new_crowd. --- app/views/devise/sessions/_new_crowd.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index 5a192c63c7c..e82a08cdb0c 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,4 +1,4 @@ -= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' class: 'show-gl-field-errors') do += form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do .form-group = label_tag 'Username or email', for: :username = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } -- cgit v1.2.1 From 091f4cb26a79bf20542f5e260d461a235ec85011 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 29 Sep 2016 12:00:12 +0200 Subject: Update filled in field for two_factor auth to use id. --- spec/features/login_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 2523b4b7898..996f39ea06d 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -29,7 +29,7 @@ feature 'Login', feature: true do describe 'with two-factor authentication' do def enter_code(code) - fill_in 'Two-Factor Authentication code', with: code + fill_in 'user_otp_attempt', with: code click_button 'Verify code' end -- cgit v1.2.1 From 2486078d5b93aaaf5bb0f60e580f65fa3da0271a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:45:12 +0200 Subject: Prevent unneccessary loading of UsernameValidator. --- app/assets/javascripts/dispatcher.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index fb6e82cd37c..956e6085bfb 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -22,7 +22,6 @@ shortcut_handler = null; switch (page) { case 'sessions:new': - case 'sessions:create': new UsernameValidator(); break; case 'projects:boards:show': -- cgit v1.2.1 From cbd68e5bd10b8cc8b933a8bfffd446a433628ddc Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:45:53 +0200 Subject: Remove superfluous comment. --- app/assets/javascripts/username_validator.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index b19fb9b4771..2517f778365 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -95,7 +95,6 @@ } clearFieldValidationState() { - // TODO: Double check if this is valid chaining this.inputElement.siblings('p').hide(); this.inputElement.removeClass(invalidInputClass) -- cgit v1.2.1 From 80864a1939e495ca5e85ba3c0c3ea869d40abda4 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 5 Oct 2016 14:28:17 +0200 Subject: Safely scope siblings of validated input. --- app/assets/javascripts/gl_field_errors.js.es6 | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 91c25047f7b..e54a10a1901 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -36,6 +36,19 @@ // hidden when injected into DOM this.inputElement.after(this.fieldErrorElement); this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); + this.scopedSiblings = this.safelySelectSiblings(); + } + + safelySelectSiblings() { + // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity + const ignoreSelector = '.validation-ignore'; + const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`); + const parentContainer = this.inputElement.parent('.form-group'); + + // Only select siblings when they're scoped within a form-group with one input + const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1; + + return safelyScoped ? unignoredSiblings : this.fieldErrorElement; } renderValidity() { @@ -90,7 +103,7 @@ setInvalidState() { this.inputElement.addClass(inputErrorClass); - this.inputElement.siblings('p').hide(); + this.scopedSiblings.hide(); return this.fieldErrorElement.show(); } @@ -101,7 +114,7 @@ this.inputElement.val(trimmedInput); } this.inputElement.removeClass(inputErrorClass); - this.inputElement.siblings('p').hide(); + this.scopedSiblings.hide(); this.fieldErrorElement.hide(); } } -- cgit v1.2.1 From cdf232752a4c2bfc6ebb57bcca3b33bcf8755b40 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 5 Oct 2016 14:28:52 +0200 Subject: Add accessor for current val for convenience. --- app/assets/javascripts/gl_field_errors.js.es6 | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index e54a10a1901..86aea25fbbb 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -68,9 +68,13 @@ } + accessCurrentVal(newVal) { + return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); + } + handleInvalidInput(event) { event.preventDefault(); - const currentValue = this.inputElement.val(); + const currentValue = this.accessCurrentVal(); this.state.valid = false; this.state.empty = currentValue === ''; @@ -87,7 +91,7 @@ } updateValidityState() { - const inputVal = this.inputElement.val(); + const inputVal = this.accessCurrentVal(); this.state.empty = !inputVal.length; this.state.valid = this.getInputValidity(); this.renderValidity(); @@ -108,10 +112,10 @@ } setClearState() { - const inputVal = this.inputElement.val(); + const inputVal = this.accessCurrentVal(); if (!inputVal.split(' ').length) { - const trimmedInput = this.inputElement.val().trim(); - this.inputElement.val(trimmedInput); + const trimmedInput = inputVal.trim(); + this.accessCurrentVal(trimmedInput); } this.inputElement.removeClass(inputErrorClass); this.scopedSiblings.hide(); -- cgit v1.2.1 From 349caec3088905cf4dc740b8809f9fd6fbdeeb0e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 11:35:23 +0200 Subject: Stringify username before passing to ActiveRecord. --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 30f0118254a..7a2bd83f22f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ class UsersController < ApplicationController end def exists - render json: { exists: !User.find_by_username(params[:username]).nil? } + render json: { exists: User.where(username: params[:username].to_s).any? } end private -- cgit v1.2.1 From 3c536bcc9d89e6d28d5a9bfb3ac8d1191ad03b88 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 11:51:02 +0200 Subject: Improve method naming in gl_field_errors. --- app/assets/javascripts/gl_field_errors.js.es6 | 43 ++++++++++++++------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 86aea25fbbb..bd8f3c52234 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -35,7 +35,7 @@ initFieldValidation() { // hidden when injected into DOM this.inputElement.after(this.fieldErrorElement); - this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); + this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this)); this.scopedSiblings = this.safelySelectSiblings(); } @@ -52,29 +52,25 @@ } renderValidity() { - this.setClearState(); + this.renderClear(); if (this.state.valid) { - return this.setValidState(); + return this.renderValid(); } if (this.state.empty) { - return this.setEmptyState(); + return this.renderEmpty(); } if (!this.state.valid) { - return this.setInvalidState(); + return this.renderInvalid(); } } - accessCurrentVal(newVal) { - return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); - } - - handleInvalidInput(event) { + handleInvalidSubmit(event) { event.preventDefault(); - const currentValue = this.accessCurrentVal(); + const currentValue = this.accessCurrentValue(); this.state.valid = false; this.state.empty = currentValue === ''; @@ -86,36 +82,41 @@ } + /* Get or set current input value */ + accessCurrentValue(newVal) { + return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); + } + getInputValidity() { return this.inputDomElement.validity.valid; } - updateValidityState() { - const inputVal = this.accessCurrentVal(); + updateValidity() { + const inputVal = this.accessCurrentValue(); this.state.empty = !inputVal.length; this.state.valid = this.getInputValidity(); this.renderValidity(); } - setValidState() { - return this.setClearState(); + renderValid() { + return this.renderClear(); } - setEmptyState() { - return this.setInvalidState(); + renderEmpty() { + return this.renderInvalid(); } - setInvalidState() { + renderInvalid() { this.inputElement.addClass(inputErrorClass); this.scopedSiblings.hide(); return this.fieldErrorElement.show(); } - setClearState() { - const inputVal = this.accessCurrentVal(); + renderClear() { + const inputVal = this.accessCurrentValue(); if (!inputVal.split(' ').length) { const trimmedInput = inputVal.trim(); - this.accessCurrentVal(trimmedInput); + this.accessCurrentValue(trimmedInput); } this.inputElement.removeClass(inputErrorClass); this.scopedSiblings.hide(); -- cgit v1.2.1 From 0f57245f0c81fe00ecedbb39c1e93fc48ef55099 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 11:57:49 +0200 Subject: Use native dom in dispatcher field error init. --- app/assets/javascripts/dispatcher.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 956e6085bfb..846884ea793 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -296,9 +296,10 @@ }; Dispatcher.prototype.initFieldErrors = function() { - $('form.show-gl-field-errors').each(function(i, form) { - new gl.GlFieldErrors(form); - }); + return document.querySelectorAll('.show-gl-field-errors') + .forEach(function(form) { + new gl.GlFieldErrors(form); + }); }; return Dispatcher; -- cgit v1.2.1 From 80aa0a8f86a02e7b36a866906b902a3dfce325f3 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 12:06:45 +0200 Subject: Revert conflict resolution in routes.rb. --- config/routes.rb | 792 ------------------------------------------------------- 1 file changed, 792 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 93d7f99fb90..83c3a42c19f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,798 +83,6 @@ Rails.application.routes.draw do draw :group draw :user draw :project - # - # Import - # - namespace :import do - resource :github, only: [:create, :new], controller: :github do - post :personal_access_token - get :status - get :callback - get :jobs - end - - resource :gitlab, only: [:create], controller: :gitlab do - get :status - get :callback - get :jobs - end - - resource :bitbucket, only: [:create], controller: :bitbucket do - get :status - get :callback - get :jobs - end - - resource :google_code, only: [:create, :new], controller: :google_code do - get :status - post :callback - get :jobs - - get :new_user_map, path: :user_map - post :create_user_map, path: :user_map - end - - resource :fogbugz, only: [:create, :new], controller: :fogbugz do - get :status - post :callback - get :jobs - - get :new_user_map, path: :user_map - post :create_user_map, path: :user_map - end - - resource :gitlab_project, only: [:create, :new] do - post :create - end - end - - # - # Uploads - # - - scope path: :uploads do - # Note attachments and User/Group/Project avatars - get ":model/:mounted_as/:id/:filename", - to: "uploads#show", - constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } - - # Appearance - get ":model/:mounted_as/:id/:filename", - to: "uploads#show", - constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } - - # Project markdown uploads - get ":namespace_id/:project_id/:secret/:filename", - to: "projects/uploads#show", - constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } - end - - # Redirect old note attachments path to new uploads path. - get "files/note/:id/:filename", - to: redirect("uploads/note/attachment/%{id}/%{filename}"), - constraints: { filename: /[^\/]+/ } - - # - # Explore area - # - namespace :explore do - resources :projects, only: [:index] do - collection do - get :trending - get :starred - end - end - - resources :groups, only: [:index] - resources :snippets, only: [:index] - root to: 'projects#trending' - end - - # Compatibility with old routing - get 'public' => 'explore/projects#index' - get 'public/projects' => 'explore/projects#index' - - # - # Admin Area - # - namespace :admin do - resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do - resources :keys, only: [:show, :destroy] - resources :identities, except: [:show] - - member do - get :projects - get :keys - get :groups - put :block - put :unblock - put :unlock - put :confirm - post :impersonate - patch :disable_two_factor - delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' - end - end - - resource :impersonation, only: :destroy - - resources :abuse_reports, only: [:index, :destroy] - resources :spam_logs, only: [:index, :destroy] do - member do - post :mark_as_ham - end - end - - resources :applications - - resources :groups, constraints: { id: /[^\/]+/ } do - member do - put :members_update - end - end - - resources :deploy_keys, only: [:index, :new, :create, :destroy] - - resources :hooks, only: [:index, :create, :destroy] do - get :test - end - - resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do - post :preview, on: :collection - end - - resource :logs, only: [:show] - resource :health_check, controller: 'health_check', only: [:show] - resource :background_jobs, controller: 'background_jobs', only: [:show] - resource :system_info, controller: 'system_info', only: [:show] - resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } - - resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - root to: 'projects#index', as: :projects - - resources(:projects, - path: '/', - constraints: { id: /[a-zA-Z.0-9_\-]+/ }, - only: [:index, :show]) do - root to: 'projects#show' - - member do - put :transfer - post :repository_check - end - - resources :runner_projects, only: [:create, :destroy] - end - end - - resource :appearances, only: [:show, :create, :update], path: 'appearance' do - member do - get :preview - delete :logo - delete :header_logos - end - end - - resource :application_settings, only: [:show, :update] do - resources :services, only: [:index, :edit, :update] - put :reset_runners_token - put :reset_health_check_token - put :clear_repository_check_states - end - - resources :labels - - resources :runners, only: [:index, :show, :update, :destroy] do - member do - get :resume - get :pause - end - end - - resources :builds, only: :index do - collection do - post :cancel_all - end - end - - root to: 'dashboard#index' - end - - # - # Profile Area - # - resource :profile, only: [:show, :update] do - member do - get :audit_log - get :applications, to: 'oauth/applications#index' - - put :reset_private_token - put :update_username - end - - scope module: :profiles do - resource :account, only: [:show] do - member do - delete :unlink - end - end - resource :notifications, only: [:show, :update] - resource :password, only: [:new, :create, :edit, :update] do - member do - put :reset - end - end - resource :preferences, only: [:show, :update] - resources :keys, only: [:index, :show, :new, :create, :destroy] - resources :emails, only: [:index, :create, :destroy] - resource :avatar, only: [:destroy] - - resources :personal_access_tokens, only: [:index, :create] do - member do - put :revoke - end - end - - resource :two_factor_auth, only: [:show, :create, :destroy] do - member do - post :create_u2f - post :codes - patch :skip - end - end - - resources :u2f_registrations, only: [:destroy] - end - end - - scope(path: 'u/:username', - as: :user, - constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error - get '/users/almost_there' => 'confirmations#almost_there' - end - - root to: "root#index" - - # - # Project Area - # - resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template - - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) - end - - scope do - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end - - scope do - get( - '/find_file/*id', - to: 'find_file#show', - constraints: { id: /.+/, format: /html/ }, - as: :find_file - ) - end - - scope do - get( - '/files/*id', - to: 'find_file#list', - constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, - as: :files - ) - end - - scope do - post( - '/create_dir/*id', - to: 'tree#create_dir', - constraints: { id: /.+/ }, - as: 'create_dir' - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/commits/*id', - to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, - as: :commits - ) - end - - resource :avatar, only: [:show, :destroy] - resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do - member do - get :branches - get :builds - get :pipelines - post :cancel_builds - post :retry_builds - post :revert - post :cherry_pick - get :diff_for_path - end - end - - resources :compare, only: [:index, :create] do - collection do - get :diff_for_path - end - end - - get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } - - # Don't use format parameter as file extension (old 3.0.x behavior) - # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments - scope format: false do - resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } - - resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do - member do - get :commits - get :ci - get :languages - end - end - end - - resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get 'raw' - end - end - - WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID - - scope do - # Order matters to give priority to these matches - get '/wikis/git_access', to: 'wikis#git_access' - get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' - post '/wikis', to: 'wikis#create' - - get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID - get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID - - get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID - delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID - put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID - post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' - end - - resource :repository, only: [:create] do - member do - get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } - end - end - - resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do - member do - get :test - end - end - - resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do - member do - put :enable - put :disable - end - end - - resources :forks, only: [:index, :new, :create] - resource :import, only: [:new, :create, :show] - - resources :refs, only: [] do - collection do - get 'switch' - end - - member do - # tree viewer logs - get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } - # Directories with leading dots erroneously get rejected if git - # ref regex used in constraints. Regex verification now done in controller. - get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { - id: /.*/, - path: /.*/ - } - end - end - - resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get :commits - get :diffs - get :conflicts - get :builds - get :pipelines - get :merge_check - post :merge - post :cancel_merge_when_build_succeeds - get :ci_status - post :toggle_subscription - post :remove_wip - get :diff_for_path - post :resolve_conflicts - end - - collection do - get :branch_from - get :branch_to - get :update_branches - get :diff_for_path - post :bulk_update - end - - resources :discussions, only: [], constraints: { id: /\h{40}/ } do - member do - post :resolve - delete :resolve, action: :unresolve - end - end - end - - resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do - resource :release, only: [:edit, :update] - end - - resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :variables, only: [:index, :show, :update, :create, :destroy] - resources :triggers, only: [:index, :create, :destroy] - - resources :pipelines, only: [:index, :new, :create, :show] do - collection do - resource :pipelines_settings, path: 'settings', only: [:show, :update] - end - - member do - post :cancel - post :retry - end - end - - resources :environments - - resource :cycle_analytics, only: [:show] - - resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do - collection do - post :cancel_all - - resources :artifacts, only: [] do - collection do - get :latest_succeeded, - path: '*ref_name_and_path', - format: false - end - end - end - - member do - get :status - post :cancel - post :retry - post :play - post :erase - get :trace - get :raw - end - - resource :artifacts, only: [] do - get :download - get :browse, path: 'browse(/*path)', format: false - get :file, path: 'file/*path', format: false - post :keep - end - end - - resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do - member do - get :test - end - end - - resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } - - resources :milestones, constraints: { id: /\d+/ } do - member do - put :sort_issues - put :sort_merge_requests - end - end - - resources :labels, except: [:show], constraints: { id: /\d+/ } do - collection do - post :generate - post :set_priorities - end - - member do - post :toggle_subscription - delete :remove_priority - end - end - - resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do - member do - post :toggle_subscription - post :mark_as_spam - get :referenced_merge_requests - get :related_branches - get :can_create_branch - end - collection do - post :bulk_update - end - end - - resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do - collection do - delete :leave - - # Used for import team - # from another project - get :import - post :apply_import - end - - member do - post :resend_invite - end - end - - resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } - - resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do - member do - delete :delete_attachment - post :resolve - delete :resolve, action: :unresolve - end - end - - resource :board, only: [:show] do - scope module: :boards do - resources :issues, only: [:update] - - resources :lists, only: [:index, :create, :update, :destroy] do - collection do - post :generate - end - - resources :issues, only: [:index] - end - end - end - - resources :todos, only: [:create] - - resources :uploads, only: [:create] do - collection do - get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } - end - end - - resources :runners, only: [:index, :edit, :update, :destroy, :show] do - member do - get :resume - get :pause - end - - collection do - post :toggle_shared_runners - end - end - - resources :runner_projects, only: [:create, :destroy] - resources :badges, only: [:index] do - collection do - scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do - constraints format: /svg/ do - get :build - get :coverage - end - end - end - end - end - end - end # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } -- cgit v1.2.1 From bad1feb23c63a86f8f4103e4b75ec590fc90da4f Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 21:45:55 +0200 Subject: Convert field error NodeList to an Array. --- app/assets/javascripts/dispatcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 846884ea793..6452ae675b4 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -296,8 +296,8 @@ }; Dispatcher.prototype.initFieldErrors = function() { - return document.querySelectorAll('.show-gl-field-errors') - .forEach(function(form) { + var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); + return [...taggedForms].forEach(function(form) { new gl.GlFieldErrors(form); }); }; -- cgit v1.2.1 From f141cdb3dddd16c729a828838e907473132a3a18 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 21:50:35 +0200 Subject: Fixup invalid refs. --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/gl_field_errors.js.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 6452ae675b4..abc077a404e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -297,7 +297,7 @@ Dispatcher.prototype.initFieldErrors = function() { var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); - return [...taggedForms].forEach(function(form) { + return [...flaggedForms].forEach(function(form) { new gl.GlFieldErrors(form); }); }; diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index bd8f3c52234..8657e7b4abf 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -78,7 +78,7 @@ this.form.focusOnFirstInvalid.apply(this.form); // For UX, wait til after first invalid submission to check each keyup this.inputElement.off('keyup.field_validator') - .on('keyup.field_validator', this.updateValidityState.bind(this)); + .on('keyup.field_validator', this.updateValidity.bind(this)); } -- cgit v1.2.1 From 23fd1f1630f4977ecc452ede086eaebfd836b978 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 10 Oct 2016 16:34:57 +0200 Subject: Use Ruby 1.9 syntax in tab_single def and usage. --- app/views/admin/appearances/preview.html.haml | 2 +- app/views/devise/confirmations/new.html.haml | 2 +- app/views/devise/passwords/edit.html.haml | 2 +- app/views/devise/passwords/new.html.haml | 2 +- app/views/devise/sessions/two_factor.html.haml | 2 +- app/views/devise/shared/_tab_single.html.haml | 3 +-- app/views/devise/unlocks/new.html.haml | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml index 0d35702c634..acbe17036f7 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Sign in preview' } += render 'devise/shared/tab_single', tab_title: 'Sign in preview' .login-box %form.show-gl-field-errors .form-group diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 443a316c6e2..5d25dd398d6 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Resend confirmation instructions' } += render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions' .login-box .login-body = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 9c533ef9916..b518fae7c95 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Change your password' } += render 'devise/shared/tab_single', tab_title:'Change your password' .login-box .login-body = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f| diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 91b46a12ac0..1fcfd06419a 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Reset Password' } += render 'devise/shared/tab_single', tab_title: 'Reset Password' .login-box .login-body = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 56074c057d7..0e865b807c1 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -3,7 +3,7 @@ = page_specific_javascript_tag('u2f.js') %div - = render 'devise/shared/tab_single', { :tab_title => 'Two-Factor Authentication' } + = render 'devise/shared/tab_single', tab_title: 'Two-Factor Authentication' .login-box .login-body - if @user.two_factor_otp_enabled? diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml index 8590c43d54d..f943d25e41a 100644 --- a/app/views/devise/shared/_tab_single.html.haml +++ b/app/views/devise/shared/_tab_single.html.haml @@ -1,4 +1,3 @@ -// = render 'devise/shared/tab_single', :tab_title => 'Tab Title' %ul.nav-links.nav-tabs.new-session-tabs.single-tab %li.active - = link_to tab_title, '#', disabled: true + %a= tab_title diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml index 0036f3b98e5..49b2f77111f 100644 --- a/app/views/devise/unlocks/new.html.haml +++ b/app/views/devise/unlocks/new.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Resend unlock instructions' } += render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions' .login-box .login-body = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| -- cgit v1.2.1 From 85db5ba847ffc8f4b71ad9fe0ba22ab42f90b3e4 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 10 Oct 2016 16:35:27 +0200 Subject: Clean up layouts/devise.html. --- app/views/layouts/devise.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 825e540cb0c..6922f1e153f 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en", class: "devise-layout-html"} = render "layouts/head" - %body{ class: "ui_charcoal login-page application navless", data: {page: body_data_page}} + %body.ui_charcoal.login-page.application.navless{ data: { page: body_data_page }} .page-wrap = Gon::Base.render_data = render "layouts/header/empty" @@ -22,7 +22,7 @@ %h3 Open source software to collaborate on code %p - Manage git repositories with fine grained access controls that keep your code secure. + Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki. -- cgit v1.2.1 From 5439bd9f952c7e7ee9fa10613655a9761396e1f9 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 10 Oct 2016 16:54:55 +0200 Subject: Attempt to fix username validation ruby. --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7a2bd83f22f..1aa22995692 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ class UsersController < ApplicationController end def exists - render json: { exists: User.where(username: params[:username].to_s).any? } + render json: { exists: !Namespace.where(name: params[:username]).nil? } end private -- cgit v1.2.1 From 568a405ce43834d5b4da3ff81f1e8a6972db802b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 11 Oct 2016 18:28:00 +0200 Subject: Add exists to users routes and fix endpoint. --- app/controllers/users_controller.rb | 2 +- config/routes/user.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 1aa22995692..6a881b271d7 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ class UsersController < ApplicationController end def exists - render json: { exists: !Namespace.where(name: params[:username]).nil? } + render json: { exists: Namespace.where(path: params[:username].downcase).any? } end private diff --git a/config/routes/user.rb b/config/routes/user.rb index 54bbcb18f6a..dfb5d2a2ba4 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -33,5 +33,6 @@ scope(path: 'u/:username', get :projects get :contributed, as: :contributed_projects get :snippets + get :exists get '/', to: redirect('/%{username}') end -- cgit v1.2.1 From eb866dfe05c82b2a45da69d924bcbe15b151a054 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 12 Oct 2016 18:34:38 +0200 Subject: Convert dispatcher to es6. --- app/assets/javascripts/dispatcher.js | 309 ------------------------------- app/assets/javascripts/dispatcher.js.es6 | 309 +++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+), 309 deletions(-) delete mode 100644 app/assets/javascripts/dispatcher.js create mode 100644 app/assets/javascripts/dispatcher.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js deleted file mode 100644 index abc077a404e..00000000000 --- a/app/assets/javascripts/dispatcher.js +++ /dev/null @@ -1,309 +0,0 @@ -(function() { - var Dispatcher; - - $(function() { - return new Dispatcher(); - }); - - Dispatcher = (function() { - function Dispatcher() { - this.initSearch(); - this.initFieldErrors(); - this.initPageScripts(); - } - - Dispatcher.prototype.initPageScripts = function() { - var page, path, shortcut_handler; - page = $('body').attr('data-page'); - if (!page) { - return false; - } - path = page.split(':'); - shortcut_handler = null; - switch (page) { - case 'sessions:new': - new UsernameValidator(); - break; - case 'projects:boards:show': - case 'projects:boards:index': - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:merge_requests:index': - case 'projects:issues:index': - Issuable.init(); - new gl.IssuableBulkActions(); - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:issues:show': - new Issue(); - shortcut_handler = new ShortcutsIssuable(); - new ZenMode(); - break; - case 'projects:milestones:show': - case 'groups:milestones:show': - case 'dashboard:milestones:show': - new Milestone(); - break; - case 'dashboard:todos:index': - new gl.Todos(); - break; - case 'projects:milestones:new': - case 'projects:milestones:edit': - new ZenMode(); - new DueDateSelect(); - new GLForm($('.milestone-form')); - break; - case 'groups:milestones:new': - new ZenMode(); - break; - case 'projects:compare:show': - new Diff(); - break; - case 'projects:issues:new': - case 'projects:issues:edit': - shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.issue-form')); - new IssuableForm($('.issue-form')); - new LabelsSelect(); - new MilestoneSelect(); - new gl.IssuableTemplateSelectors(); - break; - case 'projects:merge_requests:new': - case 'projects:merge_requests:edit': - new Diff(); - shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.merge-request-form')); - new IssuableForm($('.merge-request-form')); - new LabelsSelect(); - new MilestoneSelect(); - new gl.IssuableTemplateSelectors(); - break; - case 'projects:tags:new': - new ZenMode(); - new GLForm($('.tag-form')); - break; - case 'projects:releases:edit': - new ZenMode(); - new GLForm($('.release-form')); - break; - case 'projects:merge_requests:show': - new Diff(); - shortcut_handler = new ShortcutsIssuable(true); - new ZenMode(); - new MergedButtons(); - break; - case 'projects:merge_requests:commits': - case 'projects:merge_requests:builds': - new MergedButtons(); - break; - case "projects:merge_requests:diffs": - new Diff(); - new ZenMode(); - new MergedButtons(); - break; - case "projects:merge_requests:conflicts": - window.mcui = new MergeConflictResolver() - break; - case 'projects:merge_requests:index': - shortcut_handler = new ShortcutsNavigation(); - Issuable.init(); - break; - case 'dashboard:activity': - new Activities(); - break; - case 'dashboard:projects:starred': - new Activities(); - break; - case 'projects:commit:show': - new Commit(); - new Diff(); - new ZenMode(); - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:commits:show': - case 'projects:activity': - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:show': - shortcut_handler = new ShortcutsNavigation(); - new NotificationsForm(); - if ($('#tree-slider').length) { - new TreeView(); - } - break; - case 'projects:pipelines:show': - new gl.Pipelines(); - break; - case 'groups:activity': - new Activities(); - break; - case 'groups:show': - shortcut_handler = new ShortcutsNavigation(); - new NotificationsForm(); - new NotificationsDropdown(); - break; - case 'groups:group_members:index': - new gl.MemberExpirationDate(); - new gl.Members(); - new UsersSelect(); - break; - case 'projects:project_members:index': - new gl.MemberExpirationDate(); - new gl.Members(); - new UsersSelect(); - break; - case 'groups:new': - case 'groups:edit': - case 'admin:groups:edit': - case 'admin:groups:new': - new GroupAvatar(); - break; - case 'projects:tree:show': - shortcut_handler = new ShortcutsNavigation(); - new TreeView(); - break; - case 'projects:find_file:show': - shortcut_handler = true; - break; - case 'projects:blob:show': - case 'projects:blame:show': - new LineHighlighter(); - shortcut_handler = new ShortcutsNavigation(); - new ShortcutsBlob(true); - break; - case 'projects:labels:new': - case 'projects:labels:edit': - new Labels(); - break; - case 'projects:labels:index': - if ($('.prioritized-labels').length) { - new gl.LabelManager(); - } - break; - case 'projects:network:show': - // Ensure we don't create a particular shortcut handler here. This is - // already created, where the network graph is created. - shortcut_handler = true; - break; - case 'projects:forks:new': - new ProjectFork(); - break; - case 'projects:artifacts:browse': - new BuildArtifacts(); - break; - case 'projects:group_links:index': - new gl.MemberExpirationDate(); - new GroupsSelect(); - break; - case 'search:show': - new Search(); - break; - case 'projects:protected_branches:index': - new gl.ProtectedBranchCreate(); - new gl.ProtectedBranchEditList(); - break; - case 'projects:cycle_analytics:show': - new gl.CycleAnalytics(); - break; - } - switch (path.first()) { - case 'admin': - new Admin(); - switch (path[1]) { - case 'groups': - new UsersSelect(); - break; - case 'projects': - new NamespaceSelects(); - break; - case 'labels': - switch (path[2]) { - case 'new': - case 'edit': - new Labels(); - } - case 'abuse_reports': - new gl.AbuseReports(); - break; - } - break; - case 'dashboard': - case 'root': - shortcut_handler = new ShortcutsDashboardNavigation(); - break; - case 'profiles': - new NotificationsForm(); - new NotificationsDropdown(); - break; - case 'projects': - new Project(); - new ProjectAvatar(); - switch (path[1]) { - case 'compare': - new CompareAutocomplete(); - break; - case 'edit': - shortcut_handler = new ShortcutsNavigation(); - new ProjectNew(); - break; - case 'new': - new ProjectNew(); - break; - case 'show': - new Star(); - new ProjectNew(); - new ProjectShow(); - new NotificationsDropdown(); - break; - case 'wikis': - new Wikis(); - shortcut_handler = new ShortcutsNavigation(); - new ZenMode(); - new GLForm($('.wiki-form')); - break; - case 'snippets': - shortcut_handler = new ShortcutsNavigation(); - if (path[2] === 'show') { - new ZenMode(); - } - break; - case 'labels': - case 'graphs': - case 'compare': - case 'pipelines': - case 'forks': - case 'milestones': - case 'project_members': - case 'deploy_keys': - case 'builds': - case 'hooks': - case 'services': - case 'protected_branches': - shortcut_handler = new ShortcutsNavigation(); - } - } - // If we haven't installed a custom shortcut handler, install the default one - if (!shortcut_handler) { - return new Shortcuts(); - } - }; - - Dispatcher.prototype.initSearch = function() { - // Only when search form is present - if ($('.search').length) { - return new gl.SearchAutocomplete(); - } - }; - - Dispatcher.prototype.initFieldErrors = function() { - var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); - return [...flaggedForms].forEach(function(form) { - new gl.GlFieldErrors(form); - }); - }; - - return Dispatcher; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 new file mode 100644 index 00000000000..abc077a404e --- /dev/null +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -0,0 +1,309 @@ +(function() { + var Dispatcher; + + $(function() { + return new Dispatcher(); + }); + + Dispatcher = (function() { + function Dispatcher() { + this.initSearch(); + this.initFieldErrors(); + this.initPageScripts(); + } + + Dispatcher.prototype.initPageScripts = function() { + var page, path, shortcut_handler; + page = $('body').attr('data-page'); + if (!page) { + return false; + } + path = page.split(':'); + shortcut_handler = null; + switch (page) { + case 'sessions:new': + new UsernameValidator(); + break; + case 'projects:boards:show': + case 'projects:boards:index': + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:merge_requests:index': + case 'projects:issues:index': + Issuable.init(); + new gl.IssuableBulkActions(); + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:issues:show': + new Issue(); + shortcut_handler = new ShortcutsIssuable(); + new ZenMode(); + break; + case 'projects:milestones:show': + case 'groups:milestones:show': + case 'dashboard:milestones:show': + new Milestone(); + break; + case 'dashboard:todos:index': + new gl.Todos(); + break; + case 'projects:milestones:new': + case 'projects:milestones:edit': + new ZenMode(); + new DueDateSelect(); + new GLForm($('.milestone-form')); + break; + case 'groups:milestones:new': + new ZenMode(); + break; + case 'projects:compare:show': + new Diff(); + break; + case 'projects:issues:new': + case 'projects:issues:edit': + shortcut_handler = new ShortcutsNavigation(); + new GLForm($('.issue-form')); + new IssuableForm($('.issue-form')); + new LabelsSelect(); + new MilestoneSelect(); + new gl.IssuableTemplateSelectors(); + break; + case 'projects:merge_requests:new': + case 'projects:merge_requests:edit': + new Diff(); + shortcut_handler = new ShortcutsNavigation(); + new GLForm($('.merge-request-form')); + new IssuableForm($('.merge-request-form')); + new LabelsSelect(); + new MilestoneSelect(); + new gl.IssuableTemplateSelectors(); + break; + case 'projects:tags:new': + new ZenMode(); + new GLForm($('.tag-form')); + break; + case 'projects:releases:edit': + new ZenMode(); + new GLForm($('.release-form')); + break; + case 'projects:merge_requests:show': + new Diff(); + shortcut_handler = new ShortcutsIssuable(true); + new ZenMode(); + new MergedButtons(); + break; + case 'projects:merge_requests:commits': + case 'projects:merge_requests:builds': + new MergedButtons(); + break; + case "projects:merge_requests:diffs": + new Diff(); + new ZenMode(); + new MergedButtons(); + break; + case "projects:merge_requests:conflicts": + window.mcui = new MergeConflictResolver() + break; + case 'projects:merge_requests:index': + shortcut_handler = new ShortcutsNavigation(); + Issuable.init(); + break; + case 'dashboard:activity': + new Activities(); + break; + case 'dashboard:projects:starred': + new Activities(); + break; + case 'projects:commit:show': + new Commit(); + new Diff(); + new ZenMode(); + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:commits:show': + case 'projects:activity': + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:show': + shortcut_handler = new ShortcutsNavigation(); + new NotificationsForm(); + if ($('#tree-slider').length) { + new TreeView(); + } + break; + case 'projects:pipelines:show': + new gl.Pipelines(); + break; + case 'groups:activity': + new Activities(); + break; + case 'groups:show': + shortcut_handler = new ShortcutsNavigation(); + new NotificationsForm(); + new NotificationsDropdown(); + break; + case 'groups:group_members:index': + new gl.MemberExpirationDate(); + new gl.Members(); + new UsersSelect(); + break; + case 'projects:project_members:index': + new gl.MemberExpirationDate(); + new gl.Members(); + new UsersSelect(); + break; + case 'groups:new': + case 'groups:edit': + case 'admin:groups:edit': + case 'admin:groups:new': + new GroupAvatar(); + break; + case 'projects:tree:show': + shortcut_handler = new ShortcutsNavigation(); + new TreeView(); + break; + case 'projects:find_file:show': + shortcut_handler = true; + break; + case 'projects:blob:show': + case 'projects:blame:show': + new LineHighlighter(); + shortcut_handler = new ShortcutsNavigation(); + new ShortcutsBlob(true); + break; + case 'projects:labels:new': + case 'projects:labels:edit': + new Labels(); + break; + case 'projects:labels:index': + if ($('.prioritized-labels').length) { + new gl.LabelManager(); + } + break; + case 'projects:network:show': + // Ensure we don't create a particular shortcut handler here. This is + // already created, where the network graph is created. + shortcut_handler = true; + break; + case 'projects:forks:new': + new ProjectFork(); + break; + case 'projects:artifacts:browse': + new BuildArtifacts(); + break; + case 'projects:group_links:index': + new gl.MemberExpirationDate(); + new GroupsSelect(); + break; + case 'search:show': + new Search(); + break; + case 'projects:protected_branches:index': + new gl.ProtectedBranchCreate(); + new gl.ProtectedBranchEditList(); + break; + case 'projects:cycle_analytics:show': + new gl.CycleAnalytics(); + break; + } + switch (path.first()) { + case 'admin': + new Admin(); + switch (path[1]) { + case 'groups': + new UsersSelect(); + break; + case 'projects': + new NamespaceSelects(); + break; + case 'labels': + switch (path[2]) { + case 'new': + case 'edit': + new Labels(); + } + case 'abuse_reports': + new gl.AbuseReports(); + break; + } + break; + case 'dashboard': + case 'root': + shortcut_handler = new ShortcutsDashboardNavigation(); + break; + case 'profiles': + new NotificationsForm(); + new NotificationsDropdown(); + break; + case 'projects': + new Project(); + new ProjectAvatar(); + switch (path[1]) { + case 'compare': + new CompareAutocomplete(); + break; + case 'edit': + shortcut_handler = new ShortcutsNavigation(); + new ProjectNew(); + break; + case 'new': + new ProjectNew(); + break; + case 'show': + new Star(); + new ProjectNew(); + new ProjectShow(); + new NotificationsDropdown(); + break; + case 'wikis': + new Wikis(); + shortcut_handler = new ShortcutsNavigation(); + new ZenMode(); + new GLForm($('.wiki-form')); + break; + case 'snippets': + shortcut_handler = new ShortcutsNavigation(); + if (path[2] === 'show') { + new ZenMode(); + } + break; + case 'labels': + case 'graphs': + case 'compare': + case 'pipelines': + case 'forks': + case 'milestones': + case 'project_members': + case 'deploy_keys': + case 'builds': + case 'hooks': + case 'services': + case 'protected_branches': + shortcut_handler = new ShortcutsNavigation(); + } + } + // If we haven't installed a custom shortcut handler, install the default one + if (!shortcut_handler) { + return new Shortcuts(); + } + }; + + Dispatcher.prototype.initSearch = function() { + // Only when search form is present + if ($('.search').length) { + return new gl.SearchAutocomplete(); + } + }; + + Dispatcher.prototype.initFieldErrors = function() { + var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); + return [...flaggedForms].forEach(function(form) { + new gl.GlFieldErrors(form); + }); + }; + + return Dispatcher; + + })(); + +}).call(this); -- cgit v1.2.1 From 716406bc71fc3866588ccca8c132060d4f33e53e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 12 Oct 2016 18:47:31 +0200 Subject: Back off the array spreading, bc poltergeist freaks out. --- app/assets/javascripts/dispatcher.js.es6 | 7 +++---- spec/features/merge_requests/user_uses_slash_commands_spec.rb | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index abc077a404e..f3957ed374b 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -296,10 +296,9 @@ }; Dispatcher.prototype.initFieldErrors = function() { - var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); - return [...flaggedForms].forEach(function(form) { - new gl.GlFieldErrors(form); - }); + $('.show-gl-field-errors').each((i, form) => { + new gl.GlFieldErrors(form); + }); }; return Dispatcher; diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index cb3cea3fd51..7b8af555f0e 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -20,7 +20,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do login_with(user) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end - + after do wait_for_ajax end @@ -34,7 +34,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do expect(page).to have_content 'Your commands have been executed!' expect(merge_request.reload.work_in_progress?).to eq true - end + end it 'removes the WIP: prefix from the title' do merge_request.title = merge_request.wip_title @@ -45,7 +45,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do expect(page).to have_content 'Your commands have been executed!' expect(merge_request.reload.work_in_progress?).to eq false - end + end end context 'when the current user cannot toggle the WIP prefix' do -- cgit v1.2.1 From 75962a2ddd320fa03fc18d04861b42342c91a978 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 13 Oct 2016 17:50:35 +0200 Subject: Clean up stray Sign up ref. --- app/views/projects/notes/_notes_with_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 8352eba7446..00b62a595ff 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -14,7 +14,7 @@ .disabled-comment.text-center .disabled-comment-text.inline Please - = link_to "sign up", new_session_path(:user, redirect_to_referer: 'yes') + = link_to "register", new_session_path(:user, redirect_to_referer: 'yes') or = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes') to post a comment -- cgit v1.2.1 From b46307d3c59e3cf24eef3a2411ea8af2f25e78ba Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 13 Oct 2016 17:59:55 +0200 Subject: Don't use member properties in users_spec, and remove loading ref. --- spec/features/users_spec.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 17a555e4673..ec4c4d62f53 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -61,20 +61,17 @@ feature 'Users', feature: true, js: true do before(:each) do visit new_user_session_path click_link 'Register' - @username_form_group = find '.username' - @username_field = find '#new_user_username' end - scenario 'shows an error border if the username already exists' do fill_in username_input, with: user.username wait_for_ajax - expect(@username_form_group).to have_css '.gl-field-error-outline' + expect(find('.username')).to have_css '.gl-field-error-outline' end scenario 'doesn\'t show an error border if the username is available' do fill_in username_input, with: 'new-user' wait_for_ajax - expect(@username_field).not_to have_css '.gl-field-error-outline' + expect(find('#new_user_username')).not_to have_css '.gl-field-error-outline' end end -- cgit v1.2.1 From 391e109c813542d5f32953088e91a6ea82dfdcfc Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Sat, 15 Oct 2016 09:05:37 +0200 Subject: Revert "Improve tabbing usability for sign in page" This reverts commit 8751491b8db471dc661daa19bc82a9dbd58e4aae. --- app/assets/stylesheets/pages/login.scss | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index f1d15417705..e210dc4c0b8 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -52,16 +52,6 @@ margin: 0 0 10px; } - .new_user { - position: relative; - padding-bottom: 35px; - } - - .sign-in { - position: absolute; - bottom: 0; - } - .login-footer { margin-top: 10px; -- cgit v1.2.1 From 54c0286a3f7306f8e5bfbbbf321cd098e4fc6e11 Mon Sep 17 00:00:00 2001 From: De Wet Blomerus Date: Fri, 14 Oct 2016 21:03:27 +0200 Subject: remove ashley since she no longer works here --- doc/development/doc_styleguide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 0b725cf200c..975ff9a4612 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -96,7 +96,7 @@ merge request. - When introducing a new document, be careful for the headings to be grammatically and syntactically correct. It is advised to mention one or all of the following GitLab members for a review: `@axil`, `@rspeicher`, - `@dblessing`, `@ashleys`. This is to ensure that no document + `@dblessing`. This is to ensure that no document with wrong heading is going live without an audit, thus preventing dead links and redirection issues when corrected - Leave exactly one newline after a heading -- cgit v1.2.1 From 5a8738a7b43ae567829bca36db6f13f430b56c83 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 15 Oct 2016 14:49:20 -0700 Subject: Fix Spinach merge request diff failures gitlab-git-test `master` was updated in ff076d88, and this caused the merge request diffs to change in a way that broke assumptions in the Spinach tests. Partial fix to #23378 --- features/steps/project/merge_requests.rb | 7 ++++--- spec/support/test_env.rb | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 4a67cf06fba..b0c33aa6f2f 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -114,7 +114,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps source_project: project, target_project: project, source_branch: 'fix', - target_branch: 'master', + target_branch: 'merge-test', author: project.users.first, description: "# Description header" ) @@ -137,7 +137,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps title: "Bug NS-05", source_project: project, target_project: project, - author: project.users.first) + author: project.users.first, + source_branch: 'merge-test') end step 'project "Shop" have "Feature NS-05" merged merge request' do @@ -508,7 +509,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see new target branch changes' do expect(page).to have_content 'Request to merge fix into feature' - expect(page).to have_content 'Target branch changed from master to feature' + expect(page).to have_content 'Target branch changed from merge-test to feature' end step 'I click on "Email Patches"' do diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index d56274d0979..ad8ae763f6d 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -17,6 +17,7 @@ module TestEnv 'markdown' => '0ed8c6c', 'lfs' => 'be93687', 'master' => 'b83d6e3', + 'merge-test' => '5937ac0', "'test'" => 'e56497b', 'orphaned-branch' => '45127a9', 'binary-encoding' => '7b1cf43', -- cgit v1.2.1 From fa25f61d474849a7a1ac893af9896b3227c33404 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 15 Oct 2016 20:31:40 -0700 Subject: Fix Hash syntax to work for both Ruby 2.1 and 2.3 --- app/views/devise/shared/_tabs_normal.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index 48abd6519d6..79b1d447a92 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -1,5 +1,5 @@ %ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'} %li.active{ role: 'presentation' } - %a{ href: '#login-pane', data: {'toggle':'tab'}, role: 'tab'} Sign in + %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab'} Sign in %li{ role: 'presentation'} - %a{ href: '#register-pane', data: {'toggle':'tab'}, role: 'tab'} Register + %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab'} Register -- cgit v1.2.1 From 4cea4d25270f604fb4b9335442657168b294e633 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 15 Oct 2016 20:40:25 -0700 Subject: Fix broken SCSS linter errors due to missing newlines --- app/assets/stylesheets/pages/login.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index e210dc4c0b8..4c0c0839fe2 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -40,6 +40,7 @@ p { font-size: 13px; } + .login-box, .omniauth-container { box-shadow: 0 0 0 1px $border-color; border-bottom-right-radius: 2px; @@ -118,6 +119,7 @@ margin: 0; } } + .new-session-tabs { display: -webkit-flex; display: flex; @@ -140,6 +142,7 @@ a { width: 100%; font-size: 18px; + &:hover { border: 1px solid transparent; } -- cgit v1.2.1 From 5174b7ad7e4b90761467388a29fa016d77e7a16d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 16 Oct 2016 11:35:51 +0200 Subject: Add the tech writers usernames in the doc_sytleguide doc [ci skip] --- doc/development/doc_styleguide.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 0b725cf200c..47ebcebc770 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -95,10 +95,10 @@ merge request. someone in the Merge Request - When introducing a new document, be careful for the headings to be grammatically and syntactically correct. It is advised to mention one or all - of the following GitLab members for a review: `@axil`, `@rspeicher`, - `@dblessing`, `@ashleys`. This is to ensure that no document - with wrong heading is going live without an audit, thus preventing dead links - and redirection issues when corrected + of the following GitLab members for a review: `@axil`, `@rspeicher`, `@marcia`, + `@SeanPackham`. This is to ensure that no document with wrong heading is going + live without an audit, thus preventing dead links and redirection issues when + corrected - Leave exactly one newline after a heading ## Links -- cgit v1.2.1 From c48f7153181eab0d872d84b1c00e83cec5f2f046 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 16 Oct 2016 12:49:23 +0300 Subject: Fix 404 when group path has dot in the name Signed-off-by: Dmitriy Zaporozhets --- config/routes/group.rb | 5 ++++- spec/routing/routing_spec.rb | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/config/routes/group.rb b/config/routes/group.rb index 33143f0dfa2..06b464d79c8 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -1,7 +1,10 @@ require 'constraints/group_url_constrainer' constraints(GroupUrlConstrainer.new) do - scope(path: ':id', as: :group, controller: :groups) do + scope(path: ':id', + as: :group, + constraints: { id: /[a-zA-Z.0-9_\-]+(? Date: Sun, 16 Oct 2016 15:03:44 +0300 Subject: Fix 500 error when creating mileston from group page Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/_init_auto_complete.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 67ff4b272b9..e138ebab018 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,7 +1,8 @@ - project = @target_project || @project - noteable_type = @noteable.class if @noteable.present? -:javascript - GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}" - GitLab.GfmAutoComplete.cachedData = undefined; - GitLab.GfmAutoComplete.setup(); +- if project + :javascript + GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}" + GitLab.GfmAutoComplete.cachedData = undefined; + GitLab.GfmAutoComplete.setup(); -- cgit v1.2.1 From 7e3ff1852340c33f5b8853190d6a24813d5920a2 Mon Sep 17 00:00:00 2001 From: Ebrahim Byagowi Date: Sun, 11 Sep 2016 20:38:09 +0430 Subject: Add RTL support to markdown renderer --- CHANGELOG | 1 + app/assets/stylesheets/framework/typography.scss | 13 +++++++++++++ lib/banzai/filter/set_direction_filter.rb | 15 +++++++++++++++ lib/banzai/pipeline/gfm_pipeline.rb | 4 +++- spec/features/atom/users_spec.rb | 2 +- spec/lib/banzai/object_renderer_spec.rb | 6 +++--- spec/lib/banzai/pipeline/description_pipeline_spec.rb | 12 +++++++++--- spec/models/concerns/cache_markdown_field_spec.rb | 2 +- 8 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 lib/banzai/filter/set_direction_filter.rb diff --git a/CHANGELOG b/CHANGELOG index e3201cd2250..76b483d9019 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -55,6 +55,7 @@ v 8.13.0 (unreleased) - Added soft wrap button to repository file/blob editor - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - Show the time ago a merge request was deployed to an environment + - Add RTL support to markdown renderer (Ebrahim Byagowi) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 8df0067fac1..287653beac5 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -90,6 +90,11 @@ border-left: 3px solid #e7e9ed; } + blockquote:dir(rtl) { + border-left: 0; + border-right: 3px solid #e7e9ed; + } + blockquote p { color: #7f8fa4 !important; font-size: inherit; @@ -112,6 +117,10 @@ } } + table:dir(rtl) th { + text-align: right; + } + pre { margin: 12px 0; font-size: 13px; @@ -129,6 +138,10 @@ margin: 3px 0 3px 28px !important; } + ul:dir(rtl), ol:dir(rtl) { + margin: 3px 28px 3px 0 !important; + } + li { line-height: 1.6em; } diff --git a/lib/banzai/filter/set_direction_filter.rb b/lib/banzai/filter/set_direction_filter.rb new file mode 100644 index 00000000000..c2976aeb7c6 --- /dev/null +++ b/lib/banzai/filter/set_direction_filter.rb @@ -0,0 +1,15 @@ +module Banzai + module Filter + # HTML filter that sets dir="auto" for RTL languages support + class SetDirectionFilter < HTML::Pipeline::Filter + def call + # select these elements just on top level of the document + doc.xpath('p|h1|h2|h3|h4|h5|h6|ol|ul[not(@class="section-nav")]|blockquote|table').each do |el| + el['dir'] = 'auto' + end + + doc + end + end + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 8d94b199c66..5da2d0b008c 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -25,7 +25,9 @@ module Banzai Filter::MilestoneReferenceFilter, Filter::TaskListFilter, - Filter::InlineDiffFilter + Filter::InlineDiffFilter, + + Filter::SetDirectionFilter ] end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index a8833194421..f8c3ccb416b 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -53,7 +53,7 @@ describe "User Feed", feature: true do end it 'has XHTML summaries in issue descriptions' do - expect(body).to match /we have a bug!<\/p>\n\n
      \n\n

      I guess/ + expect(body).to match /we have a bug!<\/p>\n\n


      \n\n

      I guess/ end it 'has XHTML summaries in notes' do diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 90da78a67dd..6bcda87c999 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -24,7 +24,7 @@ describe Banzai::ObjectRenderer do with(an_instance_of(Array)). and_call_original - expect(object).to receive(:redacted_note_html=).with('

      hello

      ') + expect(object).to receive(:redacted_note_html=).with('

      hello

      ') expect(object).to receive(:user_visible_reference_count=).with(0) renderer.render([object], :note) @@ -92,10 +92,10 @@ describe Banzai::ObjectRenderer do docs = renderer.render_attributes(objects, :note) expect(docs[0]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment) - expect(docs[0].to_html).to eq('

      hello

      ') + expect(docs[0].to_html).to eq('

      hello

      ') expect(docs[1]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment) - expect(docs[1].to_html).to eq('

      bye

      ') + expect(docs[1].to_html).to eq('

      bye

      ') end it 'returns when no objects to render' do diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb index 76f42071810..8cce1b96698 100644 --- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb @@ -4,11 +4,11 @@ describe Banzai::Pipeline::DescriptionPipeline do def parse(html) # When we pass HTML to Redcarpet, it gets wrapped in `p` tags... # ...except when we pass it pre-wrapped text. Rabble rabble. - unwrap = !html.start_with?('

      ') + unwrap = !html.start_with?('

      (.*)

      (.*)\z}, '\1\2') if unwrap + output.gsub!(%r{\A

      (.*)

      (.*)\z}, '\1\2') if unwrap output end @@ -27,11 +27,17 @@ describe Banzai::Pipeline::DescriptionPipeline do end end - %w(b i strong em a ins del sup sub p).each do |elem| + %w(b i strong em a ins del sup sub).each do |elem| it "still allows '#{elem}' elements" do exp = act = "<#{elem}>Description" expect(parse(act).strip).to eq exp end end + + it "still allows 'p' elements" do + exp = act = "

      Description

      " + + expect(parse(act).strip).to eq exp + end end diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 15cd3a7ed70..2e3702f7520 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -64,7 +64,7 @@ describe CacheMarkdownField do let(:html) { "

      Foo

      " } let(:updated_markdown) { "`Bar`" } - let(:updated_html) { "

      Bar

      " } + let(:updated_html) { "

      Bar

      " } subject { ThingWithMarkdownFields.new(foo: markdown, foo_html: html) } -- cgit v1.2.1 From b1b197ae5e42c7f9a645f249bff28235bdba4c80 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 16 Oct 2016 20:03:07 +0300 Subject: Wait for ajax call in merge request unsubscribe test Signed-off-by: Dmitriy Zaporozhets --- features/steps/project/merge_requests.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index b0c33aa6f2f..de065dffbc2 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -7,6 +7,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps include SharedMarkdown include SharedDiffNote include SharedUser + include WaitForAjax step 'I click link "New Merge Request"' do click_link "New Merge Request" @@ -90,6 +91,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I click button "Unsubscribe"' do click_on "Unsubscribe" + wait_for_ajax end step 'I click link "Close"' do -- cgit v1.2.1 From 9e30b75e9c5ab91de955b6212df825f5e85c0e57 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 16 Oct 2016 20:40:52 +0300 Subject: Fix merge requests feature tests Signed-off-by: Dmitriy Zaporozhets --- features/steps/shared/note.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb index 3d7c6ef9d2d..d3b5b0bdebe 100644 --- a/features/steps/shared/note.rb +++ b/features/steps/shared/note.rb @@ -1,5 +1,8 @@ +require Rails.root.join('features/support/wait_for_ajax') + module SharedNote include Spinach::DSL + include WaitForAjax step 'I delete a comment' do page.within('.main-notes-list') do @@ -116,8 +119,9 @@ module SharedNote page.within(".js-main-target-form") do fill_in "note[note]", with: "# Comment with a header" click_button "Comment" - sleep 0.05 end + + wait_for_ajax end step 'The comment with the header should not have an ID' do -- cgit v1.2.1 From cd6af26de21b1d321306f69f5e0311d19e7c6b28 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 16 Oct 2016 20:49:05 +0300 Subject: Fix active tab test for branches page Signed-off-by: Dmitriy Zaporozhets --- features/steps/project/active_tab.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index 80043463188..58225032859 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -54,7 +54,7 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps end step 'I click the "Branches" tab' do - page.within '.content' do + page.within '.sub-nav' do click_link('Branches') end end -- cgit v1.2.1 From 9f7a7115a4c7b90e0203516f5aabf913ce02cbfe Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 16 Oct 2016 20:29:45 +0200 Subject: Convert CHANGELOG to Markdown All this does is convert the version sections into headers. The list items shouldn't really be indented by two spaces, but it makes no difference to the rendering and this way we retain authorship history for the actual changes. Related to https://gitlab.com/gitlab-org/release-tools/merge_requests/29 --- .gitattributes | 2 +- CHANGELOG | 2335 ---------------------------------------- CHANGELOG.md | 2465 +++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 2 +- doc/workflow/gitlab_flow.md | 2 +- scripts/lint-doc.sh | 6 +- 6 files changed, 2471 insertions(+), 2341 deletions(-) delete mode 100644 CHANGELOG create mode 100644 CHANGELOG.md diff --git a/.gitattributes b/.gitattributes index 17cbaa5eef5..ab791a4cd6c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ -CHANGELOG merge=union +CHANGELOG.md merge=union *.js.es6 gitlab-language=javascript diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index e3201cd2250..00000000000 --- a/CHANGELOG +++ /dev/null @@ -1,2335 +0,0 @@ -Please view this file on the master branch, on stable branches it's out of date. - -v 8.13.0 (unreleased) - - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - - Respond with 404 Not Found for non-existent tags (Linus Thiel) - - Truncate long labels with ellipsis in labels page - - Improve tabbing usability for sign in page (ClemMakesApps) - - Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint - - Adding members no longer silently fails when there is extra whitespace - - Update runner version only when updating contacted_at - - Add link from system note to compare with previous version - - Use gitlab-shell v3.6.6 - - Add `/projects/visible` API endpoint (Ben Boeckel) - - Fix centering of custom header logos (Ashley Dumaine) - - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - - Updating verbiage on git basics to be more intuitive - - Clarify documentation for Runners API (Gennady Trafimenkov) - - The instrumentation for Banzai::Renderer has been restored - - Change user & group landing page routing from /u/:username to /:username - - Prevent running GfmAutocomplete setup for each diff note !6569 - - Added documentation for .gitattributes files - - AbstractReferenceFilter caches project_refs on RequestStore when active - - Replaced the check sign to arrow in the show build view. !6501 - - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - - Fix Error 500 when viewing old merge requests with bad diff data - - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - - Speed-up group milestones show page - - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService - - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - - Add tag shortcut from the Commit page. !6543 - - Keep refs for each deployment - - Allow browsing branches that end with '.atom' - - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - - Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps) - - Add more tests for calendar contribution (ClemMakesApps) - - Update Gitlab Shell to fix some problems with moving projects between storages - - Cache rendered markdown in the database, rather than Redis - - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified - - Simplify Mentionable concern instance methods - - API: Ability to retrieve version information (Robert Schilling) - - Fix permission for setting an issue's due date - - API: Multi-file commit !6096 (mahcsig) - - Unicode emoji are now converted to images - - Revert "Label list shows all issues (opened or closed) with that label" - - Expose expires_at field when sharing project on API - - Fix VueJS template tags being rendered in code comments - - Added copy file path button to merge request diff files - - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - - Add Issue Board API support (andrebsguedes) - - Allow the Koding integration to be configured through the API - - Add new issue button to each list on Issues Board - - Added soft wrap button to repository file/blob editor - - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - - Show the time ago a merge request was deployed to an environment - - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - - Fix todos page mobile viewport layout (ClemMakesApps) - - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - - Remove redundant mixins (ClemMakesApps) - - Added 'Download' button to the Snippets page (Justin DiPierro) - - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - - Fix that manual jobs would no longer block jobs in the next stage. !6604 - - Add configurable email subject suffix (Fu Xu) - - Use defined colour for a language when available !6748 (nilsding) - - Added tooltip to fork count on project show page. (Justin DiPierro) - - Use a ConnectionPool for Rails.cache on Sidekiq servers - - Replace `alias_method_chain` with `Module#prepend` - - Enable GitLab Import/Export for non-admin users. - - Preserve label filters when sorting !6136 (Joseph Frazier) - - MergeRequest#new form load diff asynchronously - - Only update issuable labels if they have been changed - - Take filters in account in issuable counters. !6496 - - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) - - Prevent flash alert text from being obscured when container is fluid - - Append issue template to existing description !6149 (Joseph Frazier) - - Trending projects now only show public projects and the list of projects is cached for a day - - Memoize Gitlab Shell's secret token (!6599, Justin DiPierro) - - Revoke button in Applications Settings underlines on hover. - - Use higher size on Gitlab::Redis connection pool on Sidekiq servers - - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - - Fix Long commit messages overflow viewport in file tree - - Revert avoid touching file system on Build#artifacts? - - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - - Add disabled delete button to protected branches (ClemMakesApps) - - Add broadcast messages and alerts below sub-nav - - Better empty state for Groups view - - API: New /users/:id/events endpoint - - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - - Add organization field to user profile - - Ignore deployment for statistics in Cycle Analytics, except in staging and production stages - - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - - Fix deploy status responsiveness error !6633 - - Make searching for commits case insensitive - - Fix resolved discussion display in side-by-side diff view !6575 - - Optimize GitHub importing for speed and memory - - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - - Notify the Merger about merge after successful build (Dimitris Karakasilis) - - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) - - Reduce queries needed to find users using their SSH keys when pushing commits - - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - - Fix broken repository 500 errors in project list - - Fix Pipeline list commit column width should be adjusted - - Close todos when accepting merge requests via the API !6486 (tonygambone) - - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) - - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - - Retouch environments list and deployments list - - Add multiple command support for all label related slash commands !6780 (barthc) - - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - - Allow empty merge requests !6384 (Artem Sidorenko) - - Grouped pipeline dropdown is a scrollable container - - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - - Fixes padding in all clipboard icons that have .btn class - - Fix a typo in doc/api/labels.md - - API: all unknown routing will be handled with 404 Not Found - - Add docs for request profiling - - Make guests unable to view MRs on private projects - -v 8.12.7 - - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 - - Fix GFM autocomplete setup being called several times - -v 8.12.6 - - Update mailroom to 0.8.1 in Gemfile.lock !6814 - -v 8.12.5 - - Switch from request to env in ::API::Helpers. !6615 - - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 - - Improve issue load time performance by avoiding ORDER BY in find_by call. !6724 - - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 - - Don't send Private-Token (API authentication) headers to Sentry - - Share projects via the API only with groups the authenticated user can access - -v 8.12.4 - - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) - - Fix padding in build sidebar. !6506 - - Changed compare dropdowns to dropdowns with isolated search input. !6550 - - Fix race condition on LFS Token. !6592 - - Fix type mismatch bug when closing Jira issue. !6619 - - Fix lint-doc error. !6623 - - Skip wiki creation when GitHub project has wiki enabled. !6665 - - Fix issues importing services via Import/Export. !6667 - - Restrict failed login attempts for users with 2FA enabled. !6668 - - Fix failed project deletion when feature visibility set to private. !6688 - - Prevent claiming associated model IDs via import. - - Set GitLab project exported file permissions to owner only - - Improve the way merge request versions are compared with each other - -v 8.12.3 - - Update Gitlab Shell to support low IO priority for storage moves - -v 8.12.2 - - Fix Import/Export not recognising correctly the imported services. - - Fix snippets pagination - - Fix "Create project" button layout when visibility options are restricted - - Fix List-Unsubscribe header in emails - - Fix IssuesController#show degradation including project on loaded notes - - Fix an issue with the "Commits" section of the cycle analytics summary. !6513 - - Fix errors importing project feature and milestone models using GitLab project import - - Make JWT messages Docker-compatible - - Fix duplicate branch entry in the merge request version compare dropdown - - Respect the fork_project permission when forking projects - - Only update issuable labels if they have been changed - - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - - Fix resolve discussion buttons endpoint path - - Refactor remnants of CoffeeScript destructured opts and super !6261 - -v 8.12.1 - - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - - Fix issue with search filter labels not displaying - -v 8.12.0 - - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 - - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - - Only check :can_resolve permission if the note is resolvable - - Bump fog-aws to v0.11.0 to support ap-south-1 region - - Add ability to fork to a specific namespace using API. (ritave) - - Allow to set request_access_enabled for groups and projects - - Cleanup misalignments in Issue list view !6206 - - Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist - - Add Pipelines for Commit - - Prune events older than 12 months. (ritave) - - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - - Fix issues/merge-request templates dropdown for forked projects - - Filter tags by name !6121 - - Update gitlab shell secret file also when it is empty. !3774 (glensc) - - Give project selection dropdowns responsive width, make non-wrapping. - - Fix note form hint showing slash commands supported for commits. - - Make push events have equal vertical spacing. - - API: Ensure invitees are not returned in Members API. - - Preserve applied filters on issues search. - - Add two-factor recovery endpoint to internal API !5510 - - Pass the "Remember me" value to the U2F authentication form - - Display stages in valid order in stages dropdown on build page - - Only update projects.last_activity_at once per hour when creating a new event - - Cycle analytics (first iteration) !5986 - - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) - - Move pushes_since_gc from the database to Redis - - Limit number of shown environments on Merge Request: show only environments for target_branch, source_branch and tags - - Add font color contrast to external label in admin area (ClemMakesApps) - - Fix find file navigation links (ClemMakesApps) - - Change logo animation to CSS (ClemMakesApps) - - Instructions for enabling Git packfile bitmaps !6104 - - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - - Fix long comments in diffs messing with table width - - Add spec covering 'Gitlab::Git::committer_hash' !6433 (dandunckelman) - - Fix pagination on user snippets page - - Honor "fixed layout" preference in more places !6422 - - Run CI builds with the permissions of users !5735 - - Fix sorting of issues in API - - Fix download artifacts button links !6407 - - Sort project variables by key. !6275 (Diego Souza) - - Ensure specs on sorting of issues in API are deterministic on MySQL - - Added ability to use predefined CI variables for environment name - - Added ability to specify URL in environment configuration in gitlab-ci.yml - - Escape search term before passing it to Regexp.new !6241 (winniehell) - - Fix pinned sidebar behavior in smaller viewports !6169 - - Fix file permissions change when updating a file on the Gitlab UI !5979 - - Added horizontal padding on build page sidebar on code coverage block. !6196 (Vitaly Baev) - - Change merge_error column from string to text type - - Fix issue with search filter labels not displaying - - Reduce contributions calendar data payload (ClemMakesApps) - - Show all pipelines for merge requests even from discarded commits !6414 - - Replace contributions calendar timezone payload with dates (ClemMakesApps) - - Changed MR widget build status to pipeline status !6335 - - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - - Enable pipeline events by default !6278 - - Move parsing of sidekiq ps into helper !6245 (pascalbetz) - - Added go to issue boards keyboard shortcut - - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - - Emoji can be awarded on Snippets !4456 - - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - - Fix blame table layout width - - Spec testing if issue authors can read issues on private projects - - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - - Request only the LDAP attributes we need !6187 - - Center build stage columns in pipeline overview (ClemMakesApps) - - Fix bug with tooltip not hiding on discussion toggle button - - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - - Fix bug stopping issue description being scrollable after selecting issue template - - Remove suggested colors hover underline (ClemMakesApps) - - Fix jump to discussion button being displayed on commit notes - - Shorten task status phrase (ClemMakesApps) - - Fix project visibility level fields on settings - - Add hover color to emoji icon (ClemMakesApps) - - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files - - Add textarea autoresize after comment (ClemMakesApps) - - Do not write SSH public key 'comments' to authorized_keys !6381 - - Add due date to issue todos - - Refresh todos count cache when an Issue/MR is deleted - - Fix branches page dropdown sort alignment (ClemMakesApps) - - Hides merge request button on branches page is user doesn't have permissions - - Add white background for no readme container (ClemMakesApps) - - API: Expose issue confidentiality flag. (Robert Schilling) - - Fix markdown anchor icon interaction (ClemMakesApps) - - Test migration paths from 8.5 until current release !4874 - - Replace animateEmoji timeout with eventListener (ClemMakesApps) - - Show badges in Milestone tabs. !5946 (Dan Rowden) - - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - - Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto) - - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - - Remove Gitorious import - - Loads GFM autocomplete source only when required - - Fix issue with slash commands not loading on new issue page - - Fix inconsistent background color for filter input field (ClemMakesApps) - - Remove prefixes from transition CSS property (ClemMakesApps) - - Add Sentry logging to API calls - - Add BroadcastMessage API - - Merge request tabs are fixed when scrolling page - - Use 'git update-ref' for safer web commits !6130 - - Sort pipelines requested through the API - - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - - Fix issue boards loading on large screens - - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 - - Show queued time when showing a pipeline !6084 - - Remove unused mixins (ClemMakesApps) - - Fix issue board label filtering appending already filtered labels - - Add search to all issue board lists - - Scroll active tab into view on mobile - - Fix groups sort dropdown alignment (ClemMakesApps) - - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - - Use JavaScript tooltips for mentions !5301 (winniehell) - - Add hover state to todos !5361 (winniehell) - - Fix icon alignment of star and fork buttons !5451 (winniehell) - - Fix alignment of icon buttons !5887 (winniehell) - - Added Ubuntu 16.04 support for packager.io (JonTheNiceGuy) - - Fix markdown help references (ClemMakesApps) - - Add last commit time to repo view (ClemMakesApps) - - Fix accessibility and visibility of project list dropdown button !6140 - - Fix missing flash messages on service edit page (airatshigapov) - - Added project-specific enable/disable setting for LFS !5997 - - Added group-specific enable/disable setting for LFS !6164 - - Add optional 'author' param when making commits. !5822 (dandunckelman) - - Don't expose a user's token in the `/api/v3/user` API (!6047) - - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - - Ability to manage project issues, snippets, wiki, merge requests and builds access level - - Remove inconsistent font weight for sidebar's labels (ClemMakesApps) - - Align add button on repository view (ClemMakesApps) - - Fix contributions calendar month label truncation (ClemMakesApps) - - Import release note descriptions from GitHub (EspadaV8) - - Added tests for diff notes - - Add pipeline events to Slack integration !5525 - - Add a button to download latest successful artifacts for branches and tags !5142 - - Remove redundant pipeline tooltips (ClemMakesApps) - - Expire commit info views after one day, instead of two weeks, to allow for user email updates - - Add delimiter to project stars and forks count (ClemMakesApps) - - Fix badge count alignment (ClemMakesApps) - - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - - Fix repo title alignment (ClemMakesApps) - - Change update interval of contacted_at - - Add LFS support to SSH !6043 - - Fix branch title trailing space on hover (ClemMakesApps) - - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) - - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) - - Order award emoji tooltips in order they were added (EspadaV8) - - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) - - Update merge_requests.md with a simpler way to check out a merge request. !5944 - - Fix button missing type (ClemMakesApps) - - Gitlab::Checks is now instrumented - - Move to project dropdown with infinite scroll for better performance - - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz) - - Load branches asynchronously in Cherry Pick and Revert dialogs. - - Convert datetime coffeescript spec to ES6 (ClemMakesApps) - - Add merge request versions !5467 - - Change using size to use count and caching it for number of group members. !5935 - - Replace play icon font with svg (ClemMakesApps) - - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - - Reduce number of database queries on builds tab - - Wrap text in commit message containers - - Capitalize mentioned issue timeline notes (ClemMakesApps) - - Fix inconsistent checkbox alignment (ClemMakesApps) - - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - - Adds response mime type to transaction metric action when it's not HTML - - Fix hover leading space bug in pipeline graph !5980 - - Avoid conflict with admin labels when importing GitHub labels - - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - - Fix repository page ui issues - - Avoid protected branches checks when verifying access without branch name - - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov) - - Fixed invisible scroll controls on build page on iPhone - - Fix error on raw build trace download for old builds stored in database !4822 - - Refactor the triggers page and documentation !6217 - - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska) - - Use default clone protocol on "check out, review, and merge locally" help page URL - - Let the user choose a namespace and name on GitHub imports - - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - - Allow bulk update merge requests from merge requests index page - - Ensure validation messages are shown within the milestone form - - Add notification_settings API calls !5632 (mahcsig) - - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) - - Fix URLs with anchors in wiki !6300 (houqp) - - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) - - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 - - Fix Gitlab::Popen.popen thread-safety issue - - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) - - Clean environment variables when running git hooks - - Fix Import/Export issues importing protected branches and some specific models - - Fix non-master branch readme display in tree view - - Add UX improvements for merge request version diffs - -v 8.11.9 - - Don't send Private-Token (API authentication) headers to Sentry - - Share projects via the API only with groups the authenticated user can access - -v 8.11.8 - - Respect the fork_project permission when forking projects - - Set a restrictive CORS policy on the API for credentialed requests - - API: disable rails session auth for non-GET/HEAD requests - - Escape HTML nodes in builds commands in CI linter - -v 8.11.7 - - Avoid conflict with admin labels when importing GitHub labels. !6158 - - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - - Allow the Rails cookie to be used for API authentication. - - Login/Register UX upgrade !6328 - -v 8.11.6 - - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 - - Make merge conflict file size limit 200 KB, to match the docs. !6052 - - Fix an error where we were unable to create a CommitStatus for running state. !6107 - - Optimize discussion notes resolving and unresolving. !6141 - - Fix GitLab import button. !6167 - - Restore SSH Key title auto-population behavior. !6186 - - Fix DB schema to match latest migration. !6256 - - Exclude some pending or inactivated rows in Member scopes. - -v 8.11.5 - - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 - - Fix member expiration date picker after update. !6184 - - Fix suggested colors options for new labels in the admin area. !6138 - - Optimize discussion notes resolving and unresolving - - Fix GitLab import button - - Fix confidential issues being exposed as public using gitlab.com export - - Remove gitorious from import_sources. !6180 - - Scope webhooks/services that will run for confidential issues - - Remove gitorious from import_sources - - Fix confidential issues being exposed as public using gitlab.com export - - Use oj gem for faster JSON processing - -v 8.11.4 - - Fix resolving conflicts on forks. !6082 - - Fix diff commenting on merge requests created prior to 8.10. !6029 - - Fix pipelines tab layout regression. !5952 - - Fix "Wiki" link not appearing in navigation for projects with external wiki. !6057 - - Do not enforce using hash with hidden key in CI configuration. !6079 - - Fix hover leading space bug in pipeline graph !5980 - - Fix sorting issues by "last updated" doesn't work after import from GitHub - - GitHub importer use default project visibility for non-private projects - - Creating an issue through our API now emails label subscribers !5720 - - Block concurrent updates for Pipeline - - Don't create groups for unallowed users when importing projects - - Fix issue boards leak private label names and descriptions - - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - - Remove gitorious. !5866 - - Allow compare merge request versions - -v 8.11.3 - - Allow system info page to handle case where info is unavailable - - Label list shows all issues (opened or closed) with that label - - Don't show resolve conflicts link before MR status is updated - - Fix IE11 fork button bug !5982 - - Don't prevent viewing the MR when git refs for conflicts can't be found on disk - - Fix external issue tracker "Issues" link leading to 404s - - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters - - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - - Issues filters reset button - -v 8.11.2 - - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 - - Use gitlab-workhorse 0.7.11 !5983 - - Does not halt the GitHub import process when an error occurs. !5763 - - Fix file links on project page when default view is Files !5933 - - Fixed enter key in search input not working !5888 - -v 8.11.1 - - Pulled due to packaging error. - -v 8.11.0 - - Use test coverage value from the latest successful pipeline in badge. !5862 - - Add test coverage report badge. !5708 - - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - - Add Koding (online IDE) integration - - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) - - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - - Fix adding line comments on the initial commit to a repo !5900 - - Fix the title of the toggle dropdown button. !5515 (herminiotorres) - - Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz) - - Update to Ruby 2.3.1. !4948 - - Add Issues Board !5548 - - Allow resolving merge conflicts in the UI !5479 - - Improve diff performance by eliminating redundant checks for text blobs - - Ensure that branch names containing escapable characters (e.g. %20) aren't unescaped indiscriminately. !5770 (ewiltshi) - - Convert switch icon into icon font (ClemMakesApps) - - API: Endpoints for enabling and disabling deploy keys - - API: List access requests, request access, approve, and deny access requests to a project or a group. !4833 - - Use long options for curl examples in documentation !5703 (winniehell) - - Added tooltip listing label names to the labels value in the collapsed issuable sidebar - - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - - GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository - - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - - Allow naming U2F devices !5833 - - Ignore URLs starting with // in Markdown links !5677 (winniehell) - - Fix CI status icon link underline (ClemMakesApps) - - The Repository class is now instrumented - - Fix commit mention font inconsistency (ClemMakesApps) - - Do not escape URI when extracting path !5878 (winniehell) - - Fix filter label tooltip HTML rendering (ClemMakesApps) - - Cache the commit author in RequestStore to avoid extra lookups in PostReceive - - Expand commit message width in repo view (ClemMakesApps) - - Cache highlighted diff lines for merge requests - - Pre-create all builds for a Pipeline when the new Pipeline is created !5295 - - Allow merge request diff notes and discussions to be explicitly marked as resolved - - API: Add deployment endpoints - - API: Add Play endpoint on Builds - - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - - Show wall clock time when showing a pipeline. !5734 - - Show member roles to all users on members page - - Project.visible_to_user is instrumented again - - Fix awardable button mutuality loading spinners (ClemMakesApps) - - Sort todos by date and priority - - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - - Optimize maximum user access level lookup in loading of notes - - Send notification emails to users newly mentioned in issue and MR edits !5800 - - Add "No one can push" as an option for protected branches. !5081 - - Improve performance of AutolinkFilter#text_parse by using XPath - - Add experimental Redis Sentinel support !1877 - - Rendering of SVGs as blobs is now limited to SVGs with a size smaller or equal to 2MB - - Fix branches page dropdown sort initial state (ClemMakesApps) - - Environments have an url to link to - - Various redundant database indexes have been removed - - Update `timeago` plugin to use multiple string/locale settings - - Remove unused images (ClemMakesApps) - - Get issue and merge request description templates from repositories - - Enforce 2FA restrictions on API authentication endpoints !5820 - - Limit git rev-list output count to one in forced push check - - Show deployment status on merge requests with external URLs - - Clean up unused routes (Josef Strzibny) - - Fix issue on empty project to allow developers to only push to protected branches if given permission - - API: Add enpoints for pipelines - - Add green outline to New Branch button. !5447 (winniehell) - - Optimize generating of cache keys for issues and notes - - Fix repository push email formatting in Outlook - - Improve performance of syntax highlighting Markdown code blocks - - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects - - Remove delay when hitting "Reply..." button on page with a lot of discussions - - Retrieve rendered HTML from cache in one request - - Fix renaming repository when name contains invalid chararacters under project settings - - Upgrade Grape from 0.13.0 to 0.15.0. !4601 - - Trigram indexes for the "ci_runners" table have been removed to speed up UPDATE queries - - Fix devise deprecation warnings. - - Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764 - - Update version_sorter and use new interface for faster tag sorting - - Optimize checking if a user has read access to a list of issues !5370 - - Store all DB secrets in secrets.yml, under descriptive names !5274 - - Fix syntax highlighting in file editor - - Support slash commands in issue and merge request descriptions as well as comments. !5021 - - Nokogiri's various parsing methods are now instrumented - - Add archived badge to project list !5798 - - Add simple identifier to public SSH keys (muteor) - - Admin page now references docs instead of a specific file !5600 (AnAverageHuman) - - Fix filter input alignment (ClemMakesApps) - - Include old revision in merge request update hooks (Ben Boeckel) - - Add build event color in HipChat messages (David Eisner) - - Make fork counter always clickable. !5463 (winniehell) - - Document that webhook secret token is sent in X-Gitlab-Token HTTP header !5664 (lycoperdon) - - Gitlab::Highlight is now instrumented - - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 - - Allow users to import cross-repository pull requests from GitHub - - The overhead of instrumented method calls has been reduced - - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - - Add pipeline events hook - - Bump gitlab_git to speedup DiffCollection iterations - - Rewrite description of a blocked user in admin settings. (Elias Werberich) - - Make branches sortable without push permission !5462 (winniehell) - - Check for Ci::Build artifacts at database level on pipeline partial - - Convert image diff background image to CSS (ClemMakesApps) - - Remove unnecessary index_projects_on_builds_enabled index from the projects table - - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) - - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration - - Fix search for notes which belongs to deleted objects - - Allow Akismet to be trained by submitting issues as spam or ham !5538 - - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - - Allow branch names ending with .json for graph and network page !5579 (winniehell) - - Add the `sprockets-es6` gem - - Improve OAuth2 client documentation (muteor) - - Fix diff comments inverted toggle bug (ClemMakesApps) - - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - - Profile requests when a header is passed - - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. - - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible - - Add commit stats in commit api. !5517 (dixpac) - - Add CI configuration button on project page - - Fix merge request new view not changing code view rendering style - - edit_blob_link will use blob passed onto the options parameter - - Make error pages responsive (Takuya Noguchi) - - The performance of the project dropdown used for moving issues has been improved - - Fix skip_repo parameter being ignored when destroying a namespace - - Add all builds into stage/job dropdowns on builds page - - Change requests_profiles resource constraint to catch virtually any file - - Bump gitlab_git to lazy load compare commits - - Reduce number of queries made for merge_requests/:id/diffs - - Add the option to set the expiration date for the project membership when giving a user access to a project. !5599 (Adam Niedzielski) - - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - - Fix bug where destroying a namespace would not always destroy projects - - Fix RequestProfiler::Middleware error when code is reloaded in development - - Allow horizontal scrolling of code blocks in issue body - - Catch what warden might throw when profiling requests to re-throw it - - Avoid commit lookup on diff_helper passing existing local variable to the helper method - - Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac) - - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker - - Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko) - - Adds support for pending invitation project members importing projects - - Add pipeline visualization/graph on pipeline page - - Update devise initializer to turn on changed password notification emails. !5648 (tombell) - - Avoid to show the original password field when password is automatically set. !5712 (duduribeiro) - - Fix importing GitLab projects with an invalid MR source project - - Sort folders with submodules in Files view !5521 - - Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0 - - Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska) - - Add pipelines tab to merge requests - - Fix notification_service argument error of declined invitation emails - - Fix a memory leak caused by Banzai::Filter::SanitizationFilter - - Speed up todos queries by limiting the projects set we join with - - Ensure file editing in UI does not overwrite commited changes without warning user - - Eliminate unneeded calls to Repository#blob_at when listing commits with no path - - Update gitlab_git gem to 10.4.7 - - Simplify SQL queries of marking a todo as done - -v 8.10.12 - - Don't send Private-Token (API authentication) headers to Sentry - - Share projects via the API only with groups the authenticated user can access - -v 8.10.11 - - Respect the fork_project permission when forking projects - - Set a restrictive CORS policy on the API for credentialed requests - - API: disable rails session auth for non-GET/HEAD requests - - Escape HTML nodes in builds commands in CI linter - -v 8.10.10 - - Allow the Rails cookie to be used for API authentication. - -v 8.10.9 - - Exclude some pending or inactivated rows in Member scopes - -v 8.10.8 - - Fix information disclosure in issue boards. - - Fix privilege escalation in project import. - -v 8.10.7 - - Upgrade Hamlit to 2.6.1. !5873 - - Upgrade Doorkeeper to 4.2.0. !5881 - -v 8.10.6 - - Upgrade Rails to 4.2.7.1 for security fixes. !5781 - - Restore "Largest repository" sort option on Admin > Projects page. !5797 - - Fix privilege escalation via project export. - - Require administrator privileges to perform a project import. - -v 8.10.5 - - Add a data migration to fix some missing timestamps in the members table. !5670 - - Revert the "Defend against 'Host' header injection" change in the source NGINX templates. !5706 - - Cache project count for 5 minutes to reduce DB load. !5746 & !5754 - -v 8.10.4 - - Don't close referenced upstream issues from a forked project. - - Fixes issue with dropdowns `enter` key not working correctly. !5544 - - Fix Import/Export project import not working in HA mode. !5618 - - Fix Import/Export error checking versions. !5638 - -v 8.10.3 - - Fix Import/Export issue importing milestones and labels not associated properly. !5426 - - Fix timing problems running imports on production. !5523 - - Add a log message when a project is scheduled for destruction for debugging. !5540 - - Fix hooks missing on imported GitLab projects. !5549 - - Properly abort a merge when merge conflicts occur. !5569 - - Fix importer for GitHub Pull Requests when a branch was removed. !5573 - - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584 - - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588 - - Fix label already exist error message in the right sidebar. - -v 8.10.2 - - User can now search branches by name. !5144 - - Page is now properly rendered after committing the first file and creating the first branch. !5399 - - Add branch or tag icon to ref in builds page. !5434 - - Fix backup restore. !5459 - - Use project ID in repository cache to prevent stale data from persisting across projects. !5460 - - Fix issue with autocomplete search not working with enter key. !5466 - - Add iid to MR API response. !5468 - - Disable MySQL foreign key checks before dropping all tables. !5472 - - Ensure relative paths for video are rewritten as we do for images. !5474 - - Ensure current user can retry a build before showing the 'Retry' button. !5476 - - Add ENV variable to skip repository storages validations. !5478 - - Added `*.js.es6 gitlab-language=javascript` to `.gitattributes`. !5486 - - Don't show comment button in gutter of diffs on MR discussion tab. !5493 - - Rescue Rugged::OSError (lock exists) when creating references. !5497 - - Fix expand all diffs button in compare view. !5500 - - Show release notes in tags list. !5503 - - Fix a bug where forking a project from a repository storage to another would fail. !5509 - - Fix missing schema update for `20160722221922`. !5512 - - Update `gitlab-shell` version to 3.2.1 in the 8.9->8.10 update guide. !5516 - -v 8.10.1 - - Refactor repository storages documentation. !5428 - - Gracefully handle case when keep-around references are corrupted or exist already. !5430 - - Add detailed info on storage path mountpoints. !5437 - - Fix Error 500 when creating Wiki pages with hyphens or spaces. !5444 - - Fix bug where replies to commit notes displayed in the MR discussion tab wouldn't show up on the commit page. !5446 - - Ignore invalid trusted proxies in X-Forwarded-For header. !5454 - - Add links to the real markdown.md file for all GFM examples. !5458 - -v 8.10.0 - - Fix profile activity heatmap to show correct day name (eanplatter) - - Speed up ExternalWikiHelper#get_project_wiki_path - - Expose {should,force}_remove_source_branch (Ben Boeckel) - - Add the functionality to be able to rename a file. !5049 - - Disable PostgreSQL statement timeout during migrations - - Fix projects dropdown loading performance with a simplified api cal. !5113 - - Fix commit builds API, return all builds for all pipelines for given commit. !4849 - - Replace Haml with Hamlit to make view rendering faster. !3666 - - Refresh the branch cache after `git gc` runs - - Allow to disable request access button on projects/groups - - Refactor repository paths handling to allow multiple git mount points - - Optimize system note visibility checking by memoizing the visible reference count. !5070 - - Add Application Setting to configure default Repository Path for new projects - - Delete award emoji when deleting a user - - Remove pinTo from Flash and make inline flash messages look nicer. !4854 (winniehell) - - Add an API for downloading latest successful build from a particular branch or tag. !5347 - - Avoid data-integrity issue when cleaning up repository archive cache. - - Add link to profile to commit avatar. !5163 (winniehell) - - Wrap code blocks on Activies and Todos page. !4783 (winniehell) - - Align flash messages with left side of page content. !4959 (winniehell) - - Display tooltip for "Copy to Clipboard" button. !5164 (winniehell) - - Use default cursor for table header of project files. !5165 (winniehell) - - Store when and yaml variables in builds table - - Display last commit of deleted branch in push events. !4699 (winniehell) - - Escape file extension when parsing search results. !5141 (winniehell) - - Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004 - - Add image border in Markdown preview. !5162 (winniehell) - - Apply the trusted_proxies config to the rack request object for use with rack_attack - - Added the ability to block sign ups using a domain blacklist. !5259 - - Upgrade to Rails 4.2.7. !5236 - - Extend exposed environment variables for CI builds - - Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead - - Add API "deploy_keys" for admins to get all deploy keys - - Allow to pull code with deploy key from public projects - - Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts) - - Add Sidekiq queue duration to transaction metrics. - - Add a new column `artifacts_size` to table `ci_builds`. !4964 - - Let Workhorse serve format-patch diffs - - Display tooltip for mentioned users and groups. !5261 (winniehell) - - Allow build email service to be tested - - Added day name to contribution calendar tooltips - - Refactor user authorization check for a single project to avoid querying all user projects - - Make images fit to the size of the viewport. !4810 - - Fix check for New Branch button on Issue page. !4630 (winniehell) - - Fix GFM autocomplete not working on wiki pages - - Fixed enter key not triggering click on first row when searching in a dropdown - - Updated dropdowns in issuable form to use new GitLab dropdown style - - Make images fit to the size of the viewport !4810 - - Fix check for New Branch button on Issue page !4630 (winniehell) - - Fix MR-auto-close text added to description. !4836 - - Support U2F devices in Firefox. !5177 - - Fix issue, preventing users w/o push access to sort tags. !5105 (redetection) - - Add Spring EmojiOne updates. - - Added Rake task for tracking deployments. !5320 - - Fix fetching LFS objects for private CI projects - - Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237 - - Add syntax for multiline blockquote using `>>>` fence. !3954 - - Fix viewing notification settings when a project is pending deletion - - Updated compare dropdown menus to use GL dropdown - - Redirects back to issue after clicking login link - - Eager load award emoji on notes - - Allow to define manual actions/builds on Pipelines and Environments - - Fix pagination when sorting by columns with lots of ties (like priority) - - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020 - - Updated project header design - - Issuable collapsed assignee tooltip is now the users name - - Fix compare view not changing code view rendering style - - Exclude email check from the standard health check - - Updated layout for Projects, Groups, Users on Admin area. !4424 - - Fix changing issue state columns in milestone view - - Update health_check gem to version 2.1.0 - - Add notification settings dropdown for groups - - Render inline diffs for multiple changed lines following eachother - - Wildcards for protected branches. !4665 - - Allow importing from Github using Personal Access Tokens. (Eric K Idema) - - API: Expose `due_date` for issues (Robert Schilling) - - API: Todos. !3188 (Robert Schilling) - - API: Expose shared groups for projects and shared projects for groups. !5050 (Robert Schilling) - - API: Expose `developers_can_push` and `developers_can_merge` for branches. !5208 (Robert Schilling) - - Add "Enabled Git access protocols" to Application Settings - - Diffs will create button/diff form on demand no on server side - - Reduce size of HTML used by diff comment forms - - Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard) - - Fix user creation with stronger minimum password requirements. !4054 (nathan-pmt) - - Only show New Snippet button to users that can create snippets. - - PipelinesFinder uses git cache data - - Track a user who created a pipeline - - Actually render old and new sections of parallel diff next to each other - - Throttle the update of `project.pushes_since_gc` to 1 minute. - - Allow expanding and collapsing files in diff view. !4990 - - Collapse large diffs by default (!4990) - - Fix mentioned users list on diff notes - - Add support for inline videos in GitLab Flavored Markdown. !5215 (original implementation by Eric Hayes) - - Fix creation of deployment on build that is retried, redeployed or rollback - - Don't parse Rinku returned value to DocFragment when it didn't change the original html string. - - Check for conflicts with existing Project's wiki path when creating a new project. - - Show last push widget in upstream after push to fork - - Fix stage status shown for pipelines - - Cache todos pending/done dashboard query counts. - - Don't instantiate a git tree on Projects show default view - - Bump Rinku to 2.0.0 - - Remove unused front-end variable -> default_issues_tracker - - ObjectRenderer retrieve renderer content using Rails.cache.read_multi - - Better caching of git calls on ProjectsController#show. - - Avoid to retrieve MR closes_issues as much as possible. - - Hide project name in project activities. !5068 (winniehell) - - Add API endpoint for a group issues. !4520 (mahcsig) - - Add Bugzilla integration. !4930 (iamtjg) - - Fix new snippet style bug (elliotec) - - Instrument Rinku usage - - Be explicit to define merge request discussion variables - - Use cache for todos counter calling TodoService - - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab - - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. - - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - - Made project list visibility icon fixed width - - Set import_url validation to be more strict - - Memoize MR merged/closed events retrieval - - Don't render discussion notes when requesting diff tab through AJAX - - Add basic system information like memory and disk usage to the admin panel - - Don't garbage collect commits that have related DB records like comments - - Allow to setup event by channel on slack service - - More descriptive message for git hooks and file locks - - Aliases of award emoji should be stored as original name. !5060 (dixpac) - - Handle custom Git hook result in GitLab UI - - Allow to access Container Registry for Public and Internal projects - - Allow '?', or '&' for label names - - Support redirected blobs for Container Registry integration - - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests - - Add date when user joined the team on the member page - - Fix 404 redirect after validation fails importing a GitLab project - - Added setting to set new users by default as external. !4545 (Dravere) - - Add min value for project limit field on user's form. !3622 (jastkand) - - Reset project pushes_since_gc when we enqueue the git gc call - - Add reminder to not paste private SSH keys. !4399 (Ingo Blechschmidt) - - Collapsed diffs lines/size don't acumulate to overflow diffs. - - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel) - - Style of import project buttons were fixed in the new project page. !5183 (rdemirbay) - - Fix GitHub client requests when rate limit is disabled - - Optimistic locking for Issues and Merge Requests (Title and description overriding prevention) - - Redesign Builds and Pipelines pages - - Change status color and icon for running builds - - Fix commenting issue in side by side diff view for unchanged lines - - Fix markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.` - - Project export filename now includes the project and namespace path - - Fix last update timestamp on issues not preserved on gitlab.com and project imports - - Fix issues importing projects from EE to CE - - Fix creating group with space in group path - - Improve cron_jobs loading error messages. !5318 / !5360 - - Prevent toggling sidebar when clipboard icon clicked - - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) - - Limit the number of retries on error to 3 for exporting projects - - Allow empty repositories on project import/export - - Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska) - - Allow bulk (un)subscription from issues in issue index - - Fix MR diff encoding issues exporting GitLab projects - - Move builds settings out of project settings and rename Pipelines - - Add builds badge to Pipelines settings page - - Export and import avatar as part of project import/export - - Fix migration corrupting import data for old version upgrades - - Show tooltip on GitLab export link in new project page - - Fix import_data wrongly saved as a result of an invalid import_url !5206 - -v 8.9.11 - - Respect the fork_project permission when forking projects - - Set a restrictive CORS policy on the API for credentialed requests - - API: disable rails session auth for non-GET/HEAD requests - - Escape HTML nodes in builds commands in CI linter - -v 8.9.10 - - Allow the Rails cookie to be used for API authentication. - -v 8.9.9 - - Exclude some pending or inactivated rows in Member scopes - -v 8.9.8 - - Upgrade Doorkeeper to 4.2.0. !5881 - -v 8.9.7 - - Upgrade Rails to 4.2.7.1 for security fixes. !5781 - - Require administrator privileges to perform a project import. - -v 8.9.6 - - Fix importing of events under notes for GitLab projects. !5154 - - Fix log statements in import/export. !5129 - - Fix commit avatar alignment in compare view. !5128 - - Fix broken migration in MySQL. !5005 - - Overwrite Host and X-Forwarded-Host headers in NGINX !5213 - - Keeps issue number when importing from Gitlab.com - - Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska) - -v 8.9.5 - - Add more debug info to import/export and memory killer. !5108 - - Fixed avatar alignment in new MR view. !5095 - - Fix diff comments not showing up in activity feed. !5069 - - Add index on both Award Emoji user and name. !5061 - - Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq. !5056 - - Re-enable import button when import process fails due to namespace already being taken. !5053 - - Fix snippets comments not displayed. !5045 - - Fix emoji paths in relative root configurations. !5027 - - Fix issues importing events in Import/Export. !4987 - - Fixed 'use shortcuts' button on docs. !4979 - - Admin should be able to turn shared runners into specific ones. !4961 - - Update RedCloth to 4.3.2 for CVE-2012-6684. !4929 (Takuya Noguchi) - - Improve the request / withdraw access button. !4860 - -v 8.9.4 - - Fix privilege escalation issue with OAuth external users. - - Ensure references to private repos aren't shown to logged-out users. - - Fixed search field blur not removing focus. !4704 - - Resolve "Sub nav isn't showing on file view". !4890 - - Fixes middle click and double request when navigating through the file browser. !4891 - - Fixed URL on label button when filtering. !4897 - - Fixed commit avatar alignment. !4933 - - Do not show build retry link when build is active. !4967 - - Fix restore Rake task warning message output. !4980 - - Handle external issues in IssueReferenceFilter. !4988 - - Expiry date on pinned nav cookie. !5009 - - Updated breakpoint for sidebar pinning. !5019 - -v 8.9.3 - - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963 - - Fix rendering of commit notes. !4953 - - Resolve "Pin should show up at 1280px min". !4947 - - Switched mobile button icons to ellipsis and angle. !4944 - - Correctly returns todo ID after creating todo. !4941 - - Better debugging for memory killer middleware. !4936 - - Remove duplicate new page btn from edit wiki. !4904 - - Use clock_gettime for all performance timestamps. !4899 - - Use memorized tags array when searching tags by name. !4859 - - Fixed avatar alignment in new MR view. !4901 - - Removed fade when filtering results. !4932 - - Fix missing avatar on system notes. !4954 - - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973 - - Use update_columns to bypass all the dirty code on active_record. !4985 - - Fix restore Rake task warning message output !4980 - -v 8.9.2 - - Fix visibility of snippets when searching. - - Fix an information disclosure when requesting access to a group containing private projects. - - Update omniauth-saml to 1.6.0 !4951 - -v 8.9.1 - - Refactor labels documentation. !3347 - - Eager load award emoji on notes. !4628 - - Fix some CI wording in documentation. !4660 - - Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720 - - Add documentation for the export & import features. !4732 - - Add some docs for Docker Registry configuration. !4738 - - Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744 - - Display group/project access requesters separately in the admin area. !4798 - - Add documentation and examples for configuring cloud storage for registry images. !4812 - - Clarifies documentation about artifact expiry. !4831 - - Fix the Network graph links. !4832 - - Fix MR-auto-close text added to description. !4836 - - Add documentation for award emoji now that comments can be awarded with emojis. !4839 - - Fix typo in export failure email. !4847 - - Fix header vertical centering. !4170 - - Fix subsequent SAML sign ins. !4718 - - Set button label when picking an option from status dropdown. !4771 - - Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775 - - Handle external issues in IssueReferenceFilter. !4789 - - Support for rendering/redacting multiple documents. !4828 - - Update Todos documentation and screenshots to include new functionality. !4840 - - Hide nav arrows by default. !4843 - - Added bottom padding to label color suggestion link. !4845 - - Use jQuery objects in ref dropdown. !4850 - - Fix GitLab project import issues related to notes and builds. !4855 - - Restrict header logo to 36px so it doesn't overflow. !4861 - - Fix unwanted label unassignment. !4863 - - Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869 - - Restore old behavior around diff notes to outdated discussions. !4870 - - Fix merge requests project settings help link anchor. !4873 - - Fix 404 when accessing pipelines as guest user on public projects. !4881 - - Remove width restriction for logo on sign-in page. !4888 - - Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884 - - Apply selected value as label. !4886 - - Change Retry to Re-deploy on Deployments page - - Fix temp file being deleted after the request while importing a GitLab project. !4894 - - Fix pagination when sorting by columns with lots of ties (like priority) - - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. - - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - - Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912 - - Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915 - - Remove duplicate 'New Page' button on edit wiki page - -v 8.9.0 - - Fix group visibility form layout in application settings - - Fix builds API response not including commit data - - Fix error when CI job variables key specified but not defined - - Fix pipeline status when there are no builds in pipeline - - Fix Error 500 when using closes_issues API with an external issue tracker - - Add more information into RSS feed for issues (Alexander Matyushentsev) - - Bulk assign/unassign labels to issues. - - Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - - Show Star and Fork buttons on mobile. - - Performance improvements on RelativeLinkFilter - - Fix endless redirections when accessing user OAuth applications when they are disabled - - Allow enabling wiki page events from Webhook management UI - - Bump rouge to 1.11.0 - - Fix issue with arrow keys not working in search autocomplete dropdown - - Fix an issue where note polling stopped working if a window was in the - background during a refresh. - - Pre-processing Markdown now only happens when needed - - Make EmailsOnPushWorker use Sidekiq mailers queue - - Redesign all Devise emails. !4297 - - Don't show 'Leave Project' to group members - - Fix wiki page events' webhook to point to the wiki repository - - Add a border around images to differentiate them from the background. - - Don't show tags for revert and cherry-pick operations - - Show image ID on registry page - - Fix issue todo not remove when leave project !4150 (Long Nguyen) - - Allow customisable text on the 'nearly there' page after a user signs up - - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support - - Fix SVG sanitizer to allow more elements - - Allow forking projects with restricted visibility level - - Added descriptions to notification settings dropdown - - Improve note validation to prevent errors when creating invalid note via API - - Reduce number of fog gem dependencies - - Add number of merge requests for a given milestone to the milestones view. - - Implement a fair usage of shared runners - - Remove project notification settings associated with deleted projects - - Fix 404 page when viewing TODOs that contain milestones or labels in different projects - - Add a metric for the number of new Redis connections created by a transaction - - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark - - Redesign navigation for project pages - - Fix images in sign-up confirmation email - - Added shortcut 'y' for copying a files content hash URL #14470 - - Fix groups API to list only user's accessible projects - - Fix horizontal scrollbar for long commit message. - - GitLab Performance Monitoring now tracks the total method execution time and call count per method - - Add Environments and Deployments - - Redesign account and email confirmation emails - - Don't fail builds for projects that are deleted - - Support Docker Registry manifest v1 - - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix - - Bump nokogiri to 1.6.8 - - Use gitlab-shell v3.0.0 - - Fixed alignment of download dropdown in merge requests - - Upgrade to jQuery 2 - - Adds selected branch name to the dropdown toggle - - Add API endpoint for Sidekiq Metrics !4653 - - Refactoring Award Emoji with API support for Issues and MergeRequests - - Use Knapsack to evenly distribute tests across multiple nodes - - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged - - Don't allow MRs to be merged when commits were added since the last review / page load - - Add DB index on users.state - - Limit email on push diff size to 30 files / 150 KB - - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database - - Changed the Slack build message to use the singular duration if necessary (Aran Koning) - - Fix race condition on merge when build succeeds - - Added shortcut to focus filter search fields and added documentation #18120 - - Links from a wiki page to other wiki pages should be rewritten as expected - - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos) - - Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393 - - Fix issues filter when ordering by milestone - - Disable SAML account unlink feature - - Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3 - - Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid) - - TeamCity Service: Fix URL handling when base URL contains a path - - Todos will display target state if issuable target is 'Closed' or 'Merged' - - Validate only and except regexp - - Fix bug when sorting issues by milestone due date and filtering by two or more labels - - POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project - - Add support for using Yubikeys (U2F) for two-factor authentication - - Link to blank group icon doesn't throw a 404 anymore - - Remove 'main language' feature - - Toggle whitespace button now available for compare branches diffs #17881 - - Pipelines can be canceled only when there are running builds - - Allow authentication using personal access tokens - - Use downcased path to container repository as this is expected path by Docker - - Allow to use CI token to fetch LFS objects - - Custom notification settings - - Projects pending deletion will render a 404 page - - Measure queue duration between gitlab-workhorse and Rails - - Added Gfm autocomplete for labels - - Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114 - - Make Omniauth providers specs to not modify global configuration - - Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir) - - Make authentication service for Container Registry to be compatible with < Docker 1.11 - - Make it possible to lock a runner from being enabled for other projects - - Add Application Setting to configure Container Registry token expire delay (default 5min) - - Cache assigned issue and merge request counts in sidebar nav - - Use Knapsack only in CI environment - - Updated project creation page to match new UI #2542 - - Cache project build count in sidebar nav - - Add milestone expire date to the right sidebar - - Manually mark a issue or merge request as a todo - - Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing - - Reduce number of queries needed to render issue labels in the sidebar - - Improve error handling importing projects - - Remove duplicated notification settings - - Put project Files and Commits tabs under Code tab - - Decouple global notification level from user model - - Replace Colorize with Rainbow for coloring console output in Rake tasks. - - Add workhorse controller and API helpers - - An indicator is now displayed at the top of the comment field for confidential issues. - - Show categorised search queries in the search autocomplete - - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented - - Dropdown for `.gitlab-ci.yml` templates - - Improve issuables APIs performance when accessing notes !4471 - - Add sorting dropdown to tags page !4423 - - External links now open in a new tab - - Prevent default actions of disabled buttons and links - - Markdown editor now correctly resets the input value on edit cancellation !4175 - - Toggling a task list item in a issue/mr description does not creates a Todo for mentions - - Improved UX of date pickers on issue & milestone forms - - Cache on the database if a project has an active external issue tracker. - - Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav - - GitLab project import and export functionality - - All classes in the Banzai::ReferenceParser namespace are now instrumented - - Remove deprecated issues_tracker and issues_tracker_id from project model - - Allow users to create confidential issues in private projects - - Measure CPU time for instrumented methods - - Instrument private methods and private instance methods by default instead just public methods - - Only show notes through JSON on confidential issues that the user has access to - - Updated the allocations Gem to version 1.0.5 - - The background sampler now ignores classes without names - - Update design for `Close` buttons - - New custom icons for navigation - - Horizontally scrolling navigation on project, group, and profile settings pages - - Hide global side navigation by default - - Fix project Star/Unstar project button tooltip - - Remove tanuki logo from side navigation; center on top nav - - Include user relationships when retrieving award_emoji - - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - - Set inverse_of for Project/Service association to reduce the number of queries - - Update tanuki logo highlight/loading colors - - Remove explicit Gitlab::Metrics.action assignments, are already automatic. - - Use Git cached counters for branches and tags on project page - - Cache participable participants in an instance variable. - - Filter parameters for request_uri value on instrumented transactions. - - Remove duplicated keys add UNIQUE index to keys fingerprint column - - ExtractsPath get ref_names from repository cache, if not there access git. - - Show a flash warning about the error detail of XHR requests which failed with status code 404 and 500 - - Cache user todo counts from TodoService - - Ensure Todos counters doesn't count Todos for projects pending delete - - Add left/right arrows horizontal navigation - - Add tooltip to pin/unpin navbar - - Add new sub nav style to Wiki and Graphs sub navigation - -v 8.8.9 - - Upgrade Doorkeeper to 4.2.0. !5881 - -v 8.8.8 - - Upgrade Rails to 4.2.7.1 for security fixes. !5781 - -v 8.8.7 - - Fix privilege escalation issue with OAuth external users. - - Ensure references to private repos aren't shown to logged-out users. - -v 8.8.6 - - Fix visibility of snippets when searching. - - Update omniauth-saml to 1.6.0 !4951 - -v 8.8.5 - - Import GitHub repositories respecting the API rate limit !4166 - - Fix todos page throwing errors when you have a project pending deletion !4300 - - Disable Webhooks before proceeding with the GitHub import !4470 - - Fix importer for GitHub comments on diff !4488 - - Adjust the SAML control flow to allow LDAP identities to be added to an existing SAML user !4498 - - Fix incremental trace upload API when using multi-byte UTF-8 chars in trace !4541 - - Prevent unauthorized access for projects build traces - - Forbid scripting for wiki files - - Only show notes through JSON on confidential issues that the user has access to - - Banzai::Filter::UploadLinkFilter use XPath instead CSS expressions - - Banzai::Filter::ExternalLinkFilter use XPath instead CSS expressions - -v 8.8.4 - - Fix LDAP-based login for users with 2FA enabled. !4493 - - Added descriptions to notification settings dropdown - - Due date can be removed from milestones - -v 8.8.3 - - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312 - - Fixed JS error when trying to remove discussion form. !4303 - - Fixed issue with button color when no CI enabled. !4287 - - Fixed potential issue with 2 CI status polling events happening. !3869 - - Improve design of Pipeline view. !4230 - - Fix gitlab importer failing to import new projects due to missing credentials. !4301 - - Fix import URL migration not rescuing with the correct Error. !4321 - - Fix health check access token changing due to old application settings being used. !4332 - - Make authentication service for Container Registry to be compatible with Docker versions before 1.11. !4363 - - Add Application Setting to configure Container Registry token expire delay (default 5 min). !4364 - - Pass the "Remember me" value to the 2FA token form. !4369 - - Fix incorrect links on pipeline page when merge request created from fork. !4376 - - Use downcased path to container repository as this is expected path by Docker. !4420 - - Fix wiki project clone address error (chujinjin). !4429 - - Fix serious performance bug with rendering Markdown with InlineDiffFilter. !4392 - - Fix missing number on generated ordered list element. !4437 - - Prevent disclosure of notes on confidential issues in search results. - -v 8.8.2 - - Added remove due date button. !4209 - - Fix Error 500 when accessing application settings due to nil disabled OAuth sign-in sources. !4242 - - Fix Error 500 in CI charts by gracefully handling commits with no durations. !4245 - - Fix table UI on CI builds page. !4249 - - Fix backups if registry is disabled. !4263 - - Fixed issue with merge button color. !4211 - - Fixed issue with enter key selecting wrong option in dropdown. !4210 - - When creating a .gitignore file a dropdown with templates will be provided. !4075 - - Fix concurrent request when updating build log in browser. !4183 - -v 8.8.1 - - Add documentation for the "Health Check" feature - - Allow anonymous users to access a public project's pipelines !4233 - - Fix MySQL compatibility in zero downtime migrations helpers - - Fix the CI login to Container Registry (the gitlab-ci-token user) - -v 8.8.0 - - Implement GFM references for milestones (Alejandro Rodríguez) - - Snippets tab under user profile. !4001 (Long Nguyen) - - Fix error when using link to uploads in global snippets - - Fix Error 500 when attempting to retrieve project license when HEAD points to non-existent ref - - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) - - Use a case-insensitive comparison in sanitizing URI schemes - - Toggle sign-up confirmation emails in application settings - - Make it possible to prevent tagged runner from picking untagged jobs - - Added `InlineDiffFilter` to the markdown parser. (Adam Butler) - - Added inline diff styling for `change_title` system notes. (Adam Butler) - - Project#open_branches has been cleaned up and no longer loads entire records into memory. - - Escape HTML in commit titles in system note messages - - Improve design of Pipeline View - - Fix scope used when accessing container registry - - Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios - - Improve multiple branch push performance by memoizing permission checking - - Log to application.log when an admin starts and stops impersonating a user - - Changing the confidentiality of an issue now creates a new system note (Alex Moore-Niemi) - - Updated gitlab_git to 10.1.0 - - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists - - Reduce delay in destroying a project from 1-minute to immediately - - Make build status canceled if any of the jobs was canceled and none failed - - Upgrade Sidekiq to 4.1.2 - - Added /health_check endpoint for checking service status - - Make 'upcoming' filter for milestones work better across projects - - Sanitize repo paths in new project error message - - Bump mail_room to 0.7.0 to fix stuck IDLE connections - - Remove future dates from contribution calendar graph. - - Support e-mail notifications for comments on project snippets - - Fix API leak of notes of unauthorized issues, snippets and merge requests - - Use ActionDispatch Remote IP for Akismet checking - - Fix error when visiting commit builds page before build was updated - - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project - - Update SVG sanitizer to conform to SVG 1.1 - - Speed up push emails with multiple recipients by only generating the email once - - Updated search UI - - Added authentication service for Container Registry - - Display informative message when new milestone is created - - Sanitize milestones and labels titles - - Support multi-line tag messages. !3833 (Calin Seciu) - - Force users to reset their password after an admin changes it - - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - - Added button to toggle whitespaces changes on diff view - - Backport GitHub Enterprise import support from EE - - Create tags using Rugged for performance reasons. !3745 - - Allow guests to set notification level in projects - - API: Expose Issue#user_notes_count. !3126 (Anton Popov) - - Don't show forks button when user can't view forks - - Fix atom feed links and rendering - - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) - - Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724 - - Added multiple colors for labels in dropdowns when dups happen. - - Show commits in the same order as `git log` - - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) - - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) - - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) - - Expire repository exists? and has_visible_content? caches after a push if necessary - - Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi) - - Fix adding a todo for private group members (Ahmad Sherif) - - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 - - Total method execution timings are no longer tracked - - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga) - - Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif) - - Hide left sidebar on phone screens to give more space for content - - Redesign navigation for profile and group pages - - Add counter metrics for rails cache - - Import pull requests from GitHub where the source or target branches were removed - - All Grape API helpers are now instrumented - - Improve Issue formatting for the Slack Service (Jeroen van Baarsen) - - Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine) - - Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs) - - When creating a .gitignore file a dropdown with templates will be provided - - Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562 - -v 8.7.9 - - Fix privilege escalation issue with OAuth external users. - - Ensure references to private repos aren't shown to logged-out users. - -v 8.7.8 - - Fix visibility of snippets when searching. - - Update omniauth-saml to 1.6.0 !4951 - -v 8.7.7 - - Fix import by `Any Git URL` broken if the URL contains a space - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - - Only show notes through JSON on confidential issues that the user has access to - -v 8.7.6 - - Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko) - - Fix import from GitLab.com to a private instance failure. !4181 - - Fix external imports not finding the import data. !4106 - - Fix notification delay when changing status of an issue - - Bump Workhorse to 0.7.5 so it can serve raw diffs - -v 8.7.5 - - Fix relative links in wiki pages. !4050 - - Fix always showing build notification message when switching between merge requests !4086 - - Fix an issue when filtering merge requests with more than one label. !3886 - - Fix short note for the default scope on build page (Takuya Noguchi) - -v 8.7.4 - - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss) - - Fix setting trusted proxies !3970 - - Fix BitBucket importer bug when throwing exceptions !3941 - - Use sign out path only if not empty !3989 - - Running rake gitlab:db:drop_tables now drops tables with cascade !4020 - - Running rake gitlab:db:drop_tables uses "IF EXISTS" as a precaution !4100 - - Use a case-insensitive comparison in sanitizing URI schemes - -v 8.7.3 - - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented - - Merge request widget displays TeamCity build state and code coverage correctly again. - - Fix the line code when importing PR review comments from GitHub. !4010 - - Wikis are now initialized on legacy projects when checking repositories - - Remove animate.css in favor of a smaller subset of animations. !3937 (Connor Shea) - -v 8.7.2 - - The "New Branch" button is now loaded asynchronously - - Fix error 500 when trying to create a wiki page - - Updated spacing between notification label and button - - Label titles in filters are now escaped properly - -v 8.7.1 - - Throttle the update of `project.last_activity_at` to 1 minute. !3848 - - Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849 - - Fix license detection to detect all license files, not only known licenses. !3878 - - Use the `can?` helper instead of `current_user.can?`. !3882 - - Prevent users from deleting Webhooks via API they do not own - - Fix Error 500 due to stale cache when projects are renamed or transferred - - Update width of search box to fix Safari bug. !3900 (Jedidiah) - - Use the `can?` helper instead of `current_user.can?` - -v 8.7.0 - - Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented - - Fix vulnerability that made it possible to gain access to private labels and milestones - - The number of InfluxDB points stored per UDP packet can now be configured - - Fix error when cross-project label reference used with non-existent project - - Transactions for /internal/allowed now have an "action" tag set - - Method instrumentation now uses Module#prepend instead of aliasing methods - - Repository.clean_old_archives is now instrumented - - Add support for environment variables on a job level in CI configuration file - - SQL query counts are now tracked per transaction - - The Projects::HousekeepingService class has extra instrumentation - - All service classes (those residing in app/services) are now instrumented - - Developers can now add custom tags to transactions - - Loading of an issue's referenced merge requests and related branches is now done asynchronously - - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - - Add support to cherry-pick any commit into any branch in the web interface (Minqi Pan) - - Project switcher uses new dropdown styling - - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - - Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles) - - Restrict user profiles when public visibility level is restricted. - - Add ability set due date to issues, sort and filter issues by due date (Mehmet Beydogan) - - 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) - - Add setting for customizing the list of trusted proxies !3524 - - Allow projects to be transfered to a lower visibility level group - - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - - Improved Markdown rendering performance !3389 - - Make shared runners text in box configurable - - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - - Expose project badges in project settings - - Make /profile/keys/new redirect to /profile/keys for back-compat. !3717 - - Preserve time notes/comments have been updated at when moving issue - - Make HTTP(s) label consistent on clone bar (Stan Hu) - - Add support for `after_script`, requires Runner 1.2 (Kamil Trzciński) - - Expose label description in API (Mariusz Jachimowicz) - - API: Ability to update a group (Robert Schilling) - - API: Ability to move issues (Robert Schilling) - - Fix Error 500 after renaming a project path (Stan Hu) - - Fix a bug whith trailing slash in teamcity_url (Charles May) - - Allow back dating on issues when created or updated through the API - - Allow back dating on issue notes when created through the API - - Propose license template when creating a new LICENSE file - - API: Expose /licenses and /licenses/:key - - Fix avatar stretching by providing a cropping feature - - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - - Allow SAML to handle external users based on user's information !3530 - - Allow Omniauth providers to be marked as `external` !3657 - - Add endpoints to archive or unarchive a project !3372 - - Fix a bug whith trailing slash in bamboo_url - - Add links to CI setup documentation from project settings and builds pages - - Display project members page to all members - - Handle nil descriptions in Slack issue messages (Stan Hu) - - Add automated repository integrity checks (OFF by default) - - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) - - API: Ability to star and unstar a project (Robert Schilling) - - Add default scope to projects to exclude projects pending deletion - - Allow to close merge requests which source projects(forks) are deleted. - - Ensure empty recipients are rejected in BuildsEmailService - - Use rugged to change HEAD in Project#change_head (P.S.V.R) - - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - - API: Fix milestone filtering by `iid` (Robert Schilling) - - Make before_script and after_script overridable on per-job (Kamil Trzciński) - - API: Delete notes of issues, snippets, and merge requests (Robert Schilling) - - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - - Better errors handling when creating milestones inside groups - - Fix high CPU usage when PostReceive receives refs/merge-requests/ - - Hide `Create a group` help block when creating a new project in a group - - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - - Allow issues and merge requests to be assigned to the author !2765 - - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) - - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - - Decouple membership and notifications - - Fix creation of merge requests for orphaned branches (Stan Hu) - - API: Ability to retrieve a single tag (Robert Schilling) - - While signing up, don't persist the user password across form redisplays - - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) - - Fix admin/projects when using visibility levels on search (PotHix) - - Build status notifications - - Update email confirmation interface - - API: Expose user location (Robert Schilling) - - API: Do not leak group existence via return code (Robert Schilling) - - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - - Update number of Todos in the sidebar when it's marked as "Done". !3600 - - Sanitize branch names created for confidential issues - - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) - - API: User can leave a project through the API when not master or owner. !3613 - - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) - - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) - - Improved markdown forms - - Diff design updates (colors, button styles, etc) - - Copying and pasting a diff no longer pastes the line numbers or +/- - - Add null check to formData when updating profile content to fix Firefox bug - - Disable spellcheck and autocorrect for username field in admin page - - Delete tags using Rugged for performance reasons (Robert Schilling) - - Add Slack notifications when Wiki is edited (Sebastian Klier) - - Diffs load at the correct point when linking from from number - - Selected diff rows highlight - - Fix emoji categories in the emoji picker - - API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling) - - Add encrypted credentials for imported projects and migrate old ones - - Properly format all merge request references with ! rather than # !3740 (Ben Bodenmiller) - - Author and participants are displayed first on users autocompletion - - Show number sign on external issue reference text (Florent Baldino) - - Updated print style for issues - - Use GitHub Issue/PR number as iid to keep references - - Import GitHub labels - - Add option to filter by "Owned projects" on dashboard page - - Import GitHub milestones - - Execute system web hooks on push to the project - - Allow enable/disable push events for system hooks - - Fix GitHub project's link in the import page when provider has a custom URL - - Add RAW build trace output and button on build page - - Add incremental build trace update into CI API - -v 8.6.9 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - - Only show notes through JSON on confidential issues that the user has access to - -v 8.6.8 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via Git branch and tag names - - Prevent XSS via custom issue tracker URL - - Prevent XSS via `window.opener` - - Prevent XSS via label drop-down - - Prevent information disclosure via milestone API - - Prevent information disclosure via snippet API - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.6.7 - - Fix persistent XSS vulnerability in `commit_person_link` helper - - Fix persistent XSS vulnerability in Label and Milestone dropdowns - - Fix vulnerability that made it possible to enumerate private projects belonging to group - -v 8.6.6 - - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 - - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654 - - Fix revoking of authorized OAuth applications (Connor Shea). !3690 - - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) - - Issuable header is consistent between issues and merge requests - - Improved spacing in issuable header on mobile - -v 8.6.5 - - Fix importing from GitHub Enterprise. !3529 - - Perform the language detection after updating merge requests in `GitPushService`, leading to faster visual feedback for the end-user. !3533 - - Check permissions when user attempts to import members from another project. !3535 - - Only update repository language if it is not set to improve performance. !3556 - - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583 - - Unblock user when active_directory is disabled and it can be found !3550 - - Fix a 2FA authentication spoofing vulnerability. - -v 8.6.4 - - Don't attempt to fetch any tags from a forked repo (Stan Hu) - - Redesign the Labels page - -v 8.6.3 - - Mentions on confidential issues doesn't create todos for non-members. !3374 - - Destroy related todos when an Issue/MR is deleted. !3376 - - Fix error 500 when target is nil on todo list. !3376 - - Fix copying uploads when moving issue to another project. !3382 - - Ensuring Merge Request API returns boolean values for work_in_progress (Abhi Rao). !3432 - - Fix raw/rendered diff producing different results on merge requests. !3450 - - Fix commit comment alignment (Stan Hu). !3466 - - Fix Error 500 when searching for a comment in a project snippet. !3468 - - Allow temporary email as notification email. !3477 - - Fix issue with dropdowns not selecting values. !3478 - - Update gitlab-shell version and doc to 2.6.12. gitlab-org/gitlab-ee!280 - -v 8.6.2 - - Fix dropdown alignment. !3298 - - Fix issuable sidebar overlaps on tablet. !3299 - - Make dropdowns pixel perfect. !3337 - - Fix order of steps to prevent PostgreSQL errors when running migration. !3355 - - Fix bold text in issuable sidebar. !3358 - - Fix error with anonymous token in applications settings. !3362 - - Fix the milestone 'upcoming' filter. !3364 + !3368 - - Fix comments on confidential issues showing up in activity feed to non-members. !3375 - - Fix `NoMethodError` when visiting CI root path at `/ci`. !3377 - - Add a tooltip to new branch button in issue page. !3380 - - Fix an issue hiding the password form when signed-in with a linked account. !3381 - - Add links to CI setup documentation from project settings and builds pages. !3384 - - Fix an issue with width of project select dropdown. !3386 - - Remove redundant `require`s from Banzai files. !3391 - - Fix error 500 with cancel button on issuable edit form. !3392 + !3417 - - Fix background when editing a highlighted note. !3423 - - Remove tabstop from the WIP toggle links. !3426 - - Ensure private project snippets are not viewable by unauthorized people. - - Gracefully handle notes on deleted commits in merge requests (Stan Hu). !3402 - - Fixed issue with notification settings not saving. !3452 - -v 8.6.1 - - Add option to reload the schema before restoring a database backup. !2807 - - Display navigation controls on mobile. !3214 - - Fixed bug where participants would not work correctly on merge requests. !3329 - - Fix sorting issues by votes on the groups issues page results in SQL errors. !3333 - - Restrict notifications for confidential issues. !3334 - - Do not allow to move issue if it has not been persisted. !3340 - - Add a confirmation step before deleting an issuable. !3341 - - Fixes issue with signin button overflowing on mobile. !3342 - - Auto collapses the navigation sidebar when resizing. !3343 - - Fix build dependencies, when the dependency is a string. !3344 - - Shows error messages when trying to create label in dropdown menu. !3345 - - Fixes issue with assign milestone not loading milestone list. !3346 - - Fix an issue causing the Dashboard/Milestones page to be blank. !3348 - -v 8.6.0 - - Add ability to move issue to another project - - Prevent tokens in the import URL to be showed by the UI - - Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu) - - Add confidential issues - - Bump gitlab_git to 9.0.3 (Stan Hu) - - Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu) - - Support Golang subpackage fetching (Stan Hu) - - Bump Capybara gem to 2.6.2 (Stan Hu) - - New branch button appears on issues where applicable - - Contributions to forked projects are included in calendar - - Improve the formatting for the user page bio (Connor Shea) - - Easily (un)mark merge request as WIP using link - - Use specialized system notes when MR is (un)marked as WIP - - Removed the default password from the initial admin account created during - setup. A password can be provided during setup (see installation docs), or - GitLab will ask the user to create a new one upon first visit. - - Fix issue when pushing to projects ending in .wiki - - Properly display YAML front matter in Markdown - - Add support for wiki with UTF-8 page names (Hiroyuki Sato) - - Fix wiki search results point to raw source (Hiroyuki Sato) - - Don't load all of GitLab in mail_room - - Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner) - - HTTP error pages work independently from location and config (Artem Sidorenko) - - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - - Memoize @group in Admin::GroupsController (Yatish Mehta) - - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) - - Added omniauth-auth0 Gem (Daniel Carraro) - - Add label description in tooltip to labels in issue index and sidebar - - Strip leading and trailing spaces in URL validator (evuez) - - Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez) - - Return empty array instead of 404 when commit has no statuses in commit status API - - Decrease the font size and the padding of the `.anchor` icons used in the README (Roberto Dip) - - Rewrite logo to simplify SVG code (Sean Lang) - - Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach) - - Ignore jobs that start with `.` (hidden jobs) - - Hide builds from project's settings when the feature is disabled - - Allow to pass name of created artifacts archive in `.gitlab-ci.yml` - - Refactor and greatly improve search performance - - Add support for cross-project label references - - Ensure "new SSH key" email do not ends up as dead Sidekiq jobs - - Update documentation to reflect Guest role not being enforced on internal projects - - Allow search for logged out users - - Allow to define on which builds the current one depends on - - Allow user subscription to a label: get notified for issues/merge requests related to that label (Timothy Andrew) - - Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio) - - Don't show Issues/MRs from archived projects in Groups view - - Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs - - Fix empty source_sha on Merge Request when there is no diff (Pierre de La Morinerie) - - Increase the notes polling timeout over time (Roberto Dip) - - Add shortcut to toggle markdown preview (Florent Baldino) - - Show labels in dashboard and group milestone views - - Fix an issue when the target branch of a MR had been deleted - - Add main language of a project in the list of projects (Tiago Botelho) - - Add #upcoming filter to Milestone filter (Tiago Botelho) - - Add ability to show archived projects on dashboard, explore and group pages - - Remove fork link closes all merge requests opened on source project (Florent Baldino) - - Move group activity to separate page - - Create external users which are excluded of internal and private projects unless access was explicitly granted - - Continue parameters are checked to ensure redirection goes to the same instance - - User deletion is now done in the background so the request can not time out - - Canceled builds are now ignored in compound build status if marked as `allowed to fail` - - Trigger a todo for mentions on commits page - - Let project owners and admins soft delete issues and merge requests - -v 8.5.13 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - -v 8.5.12 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via Git branch and tag names - - Prevent XSS via custom issue tracker URL - - Prevent XSS via `window.opener` - - Prevent information disclosure via snippet API - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.5.11 - - Fix persistent XSS vulnerability in `commit_person_link` helper - -v 8.5.10 - - Fix a 2FA authentication spoofing vulnerability. - -v 8.5.9 - - Don't attempt to fetch any tags from a forked repo (Stan Hu). - -v 8.5.8 - - Bump Git version requirement to 2.7.4 - -v 8.5.7 - - Bump Git version requirement to 2.7.3 - -v 8.5.6 - - Obtain a lease before querying LDAP - -v 8.5.5 - - Ensure removing a project removes associated Todo entries - - Prevent a 500 error in Todos when author was removed - - Fix pagination for filtered dashboard and explore pages - - Fix "Show all" link behavior - -v 8.5.4 - - Do not cache requests for badges (including builds badge) - -v 8.5.3 - - Flush repository caches before renaming projects - - Sort starred projects on dashboard based on last activity by default - - Show commit message in JIRA mention comment - - Makes issue page and merge request page usable on mobile browsers. - - Improved UI for profile settings - -v 8.5.2 - - Fix sidebar overlapping content when screen width was below 1200px - - Don't repeat labels listed on Labels tab - - Bring the "branded appearance" feature from EE to CE - - Fix error 500 when commenting on a commit - - Show days remaining instead of elapsed time for Milestone - - Fix broken icons on installations with relative URL (Artem Sidorenko) - - Fix issue where tag list wasn't refreshed after deleting a tag - - Fix import from gitlab.com (KazSawada) - - Improve implementation to check read access to forks and add pagination - - Don't show any "2FA required" message if it's not actually required - - Fix help keyboard shortcut on relative URL setups (Artem Sidorenko) - - Update Rails to 4.2.5.2 - - Fix permissions for deprecated CI build status badge - - Don't show "Welcome to GitLab" when the search didn't return any projects - - Add Todos documentation - -v 8.5.1 - - Fix group projects styles - - Show Crowd login tab when sign in is disabled and Crowd is enabled (Peter Hudec) - - Fix a set of small UI glitches in project, profile, and wiki pages - - Restrict permissions on public/uploads - - Fix the merge request side-by-side view after loading diff results - - Fix the look of tooltip for the "Revert" button - - Add when the Builds & Runners API changes got introduced - - Fix error 500 on some merged merge requests - - Fix an issue causing the content of the issuable sidebar to disappear - - Fix error 500 when trying to mark an already done todo as "done" - - Fix an issue where MRs weren't sortable - - Issues can now be dragged & dropped into empty milestone lists. This is also - possible with MRs - - Changed padding & background color for highlighted notes - - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu) - - Update sentry-raven gem to 0.15.6 - - Add build coverage in project's builds page (Steffen Köhler) - - Changed # to ! for merge requests in activity view - -v 8.5.0 - - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) - - Cache various Repository methods to improve performance - - Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu) - - Ensure rake tasks that don't need a DB connection can be run without one - - Update New Relic gem to 3.14.1.311 (Stan Hu) - - Add "visibility" flag to GET /projects api endpoint - - Add an option to supply root email through an environmental variable (Koichiro Mikami) - - Ignore binary files in code search to prevent Error 500 (Stan Hu) - - Render sanitized SVG images (Stan Hu) - - Support download access by PRIVATE-TOKEN header (Stan Hu) - - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push - - Add option to include the sender name in body of Notify email (Jason Lee) - - New UI for pagination - - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet - set it up - - API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger) - - Fix diff comments loaded by AJAX to load comment with diff in discussion tab - - Fix relative links in other markup formats (Ben Boeckel) - - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) - - Fix label links for a merge request pointing to issues list - - Don't vendor minified JS - - Increase project import timeout to 15 minutes - - Be more permissive with email address validation: it only has to contain a single '@' - - Display 404 error on group not found - - Track project import failure - - Support Two-factor Authentication for LDAP users - - Display database type and version in Administration dashboard - - Allow limited Markdown in Broadcast Messages - - Fix visibility level text in admin area (Zeger-Jan van de Weg) - - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg) - - Update the ExternalIssue regex pattern (Blake Hitchcock) - - Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson) - - Optimized performance of finding issues to be closed by a merge request - - Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`, `path_with_namespace` - and `default_branch` in `project` in push, issue, merge-request and note webhooks data (Kirill Zaitsev) - - Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in favor of `git_http_url` - in `project` for push, issue, merge-request and note webhooks data (Kirill Zaitsev) - - Deprecate the `repository` key in push, issue, merge-request and note webhooks data, use `project` instead (Kirill Zaitsev) - - API: Expose MergeRequest#merge_status (Andrei Dziahel) - - Revert "Add IP check against DNSBLs at account sign-up" - - Actually use the `skip_merges` option in Repository#commits (Tony Chu) - - Fix API to keep request parameters in Link header (Michael Potthoff) - - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead - - Prevent parse error when name of project ends with .atom and prevent path issues - - Discover branches for commit statuses ref-less when doing merge when succeeded - - Mark inline difference between old and new paths when a file is renamed - - Support Akismet spam checking for creation of issues via API (Stan Hu) - - API: Allow to set or update a merge-request's milestone (Kirill Skachkov) - - Improve UI consistency between projects and groups lists - - Add sort dropdown to dashboard projects page - - Fixed logo animation on Safari (Roman Rott) - - Fix Merge When Succeeded when multiple stages - - Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg) - - In seach autocomplete show only groups and projects you are member of - - Don't process cross-reference notes from forks - - Fix: init.d script not working on OS X - - Faster snippet search - - Added API to download build artifacts - - Title for milestones should be unique (Zeger-Jan van de Weg) - - Validate correctness of maximum attachment size application setting - - Replaces "Create merge request" link with one to the "Merge Request" when one exists - - Fix CI builds badge, add a new link to builds badge, deprecate the old one - - Fix broken link to project in build notification emails - - Ability to see and sort on vote count from Issues and MR lists - - Fix builds scheduler when first build in stage was allowed to fail - - User project limit is reached notice is hidden if the projects limit is zero - - Add API support for managing runners and project's runners - - Allow SAML users to login with no previous account without having to allow - all Omniauth providers to do so. - - Allow existing users to auto link their SAML credentials by logging in via SAML - - Make it possible to erase a build (trace, artifacts) using UI and API - - Ability to revert changes from a Merge Request or Commit - - Emoji comment on diffs are not award emoji - - Add label description (Nuttanart Pornprasitsakul) - - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - - Add Todos - -v 8.4.11 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - -v 8.4.10 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via Git branch and tag names - - Prevent XSS via custom issue tracker URL - - Prevent XSS via `window.opener` - - Prevent information disclosure via snippet API - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.4.9 - - Fix persistent XSS vulnerability in `commit_person_link` helper - -v 8.4.8 - - Fix a 2FA authentication spoofing vulnerability. - -v 8.4.7 - - Don't attempt to fetch any tags from a forked repo (Stan Hu). - -v 8.4.6 - - Bump Git version requirement to 2.7.4 - -v 8.4.5 - - No CE-specific changes - -v 8.4.4 - - Update omniauth-saml gem to 1.4.2 - - Prevent long-running backup tasks from timing out the database connection - - Add a Project setting to allow guests to view build logs (defaults to true) - - Sort project milestones by due date including issue editor (Oliver Rogers / Orih) - -v 8.4.3 - - Increase lfs_objects size column to 8-byte integer to allow files larger - than 2.1GB - - Correctly highlight MR diff when MR has merge conflicts - - Fix highlighting in blame view - - Update sentry-raven gem to prevent "Not a git repository" console output - when running certain commands - - Add instrumentation to additional Gitlab::Git and Rugged methods for - performance monitoring - - Allow autosize textareas to also be manually resized - -v 8.4.2 - - Bump required gitlab-workhorse version to bring in a fix for missing - artifacts in the build artifacts browser - - Get rid of those ugly borders on the file tree view - - Fix updating the runner information when asking for builds - - Bump gitlab_git version to 7.2.24 in order to bring in a performance - improvement when checking if a repository was empty - - Add instrumentation for Gitlab::Git::Repository instance methods so we can - track them in Performance Monitoring. - - Increase contrast between highlighted code comments and inline diff marker - - Fix method undefined when using external commit status in builds - - Fix highlighting in blame view. - -v 8.4.1 - - Apply security updates for Rails (4.2.5.1), rails-html-sanitizer (1.0.3), - and Nokogiri (1.6.7.2) - - Fix redirect loop during import - - Fix diff highlighting for all syntax themes - - Delete project and associations in a background worker - -v 8.4.0 - - Allow LDAP users to change their email if it was not set by the LDAP server - - Ensure Gravatar host looks like an actual host - - Consider re-assign as a mention from a notification point of view - - Add pagination headers to already paginated API resources - - Properly generate diff of orphan commits, like the first commit in a repository - - Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages - - Autocomplete data is now always loaded, instead of when focusing a comment text area - - Improved performance of finding issues for an entire group - - Added custom application performance measuring system powered by InfluxDB - - Add syntax highlighting to diffs - - Gracefully handle invalid UTF-8 sequences in Markdown links (Stan Hu) - - Bump fog to 1.36.0 (Stan Hu) - - Add user's last used IP addresses to admin page (Stan Hu) - - Add housekeeping function to project settings page - - The default GitLab logo now acts as a loading indicator - - Fix caching issue where build status was not updating in project dashboard (Stan Hu) - - Accept 2xx status codes for successful Webhook triggers (Stan Hu) - - Fix missing date of month in network graph when commits span a month (Stan Hu) - - Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu) - - Don't notify users twice if they are both project watchers and subscribers (Stan Hu) - - Remove gray background from layout in UI - - Fix signup for OAuth providers that don't provide a name - - Implement new UI for group page - - Implement search inside emoji picker - - Let the CI runner know about builds that this build depends on - - Add API support for looking up a user by username (Stan Hu) - - Add project permissions to all project API endpoints (Stan Hu) - - Link to milestone in "Milestone changed" system note - - Only allow group/project members to mention `@all` - - Expose Git's version in the admin area (Trey Davis) - - Add "Frequently used" category to emoji picker - - Add CAS support (tduehr) - - Add link to merge request on build detail page - - Fix: Problem with projects ending with .keys (Jose Corcuera) - - Revert back upvote and downvote button to the issue and MR pages - - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) - - Add system hook messages for project rename and transfer (Steve Norman) - - Fix version check image in Safari - - Show 'All' tab by default in the builds page - - Add Open Graph and Twitter Card data to all pages - - Fix API project lookups when querying with a namespace with dots (Stan Hu) - - Enable forcing Two-factor authentication sitewide, with optional grace period - - Import GitHub Pull Requests into GitLab - - Change single user API endpoint to return more detailed data (Michael Potthoff) - - Update version check images to use SVG - - Validate README format before displaying - - Enable Microsoft Azure OAuth2 support (Janis Meybohm) - - Properly set task-list class on single item task lists - - Add file finder feature in tree view (Kyungchul Shin) - - Ajax filter by message for commits page - - API: Add support for deleting a tag via the API (Robert Schilling) - - Allow subsequent validations in CI Linter - - Show referenced MRs & Issues only when the current viewer can access them - - Fix Encoding::CompatibilityError bug when markdown content has some complex URL (Jason Lee) - - Add API support for managing project's builds - - Add API support for managing project's build triggers - - Add API support for managing project's build variables - - Allow broadcast messages to be edited - - Autosize Markdown textareas - - Import GitHub wiki into GitLab - - Add reporters ability to download and browse build artifacts (Andrew Johnson) - - Autofill referring url in message box when reporting user abuse. - - Remove leading comma on award emoji when the user is the first to award the emoji (Zeger-Jan van de Weg) - - Add build artifacts browser - - Improve UX in builds artifacts browser - - Increase default size of `data` column in `events` table when using MySQL - - Expose button to CI Lint tool on project builds page - - Fix: Creator should be added as a master of the project on creation - - Added X-GitLab-... headers to emails from CI and Email On Push services (Anton Baklanov) - - Add IP check against DNSBLs at account sign-up - - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching - -v 8.3.10 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - -v 8.3.9 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via custom issue tracker URL - - Prevent XSS via `window.opener` - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.3.8 - - Fix persistent XSS vulnerability in `commit_person_link` helper - -v 8.3.7 - - Fix a 2FA authentication spoofing vulnerability. - -v 8.3.6 - - Don't attempt to fetch any tags from a forked repo (Stan Hu). - -v 8.3.5 - - Bump Git version requirement to 2.7.4 - -v 8.3.4 - - Use gitlab-workhorse 0.5.4 (fixes API routing bug) - -v 8.3.3 - - Preserve CE behavior with JIRA integration by only calling API if URL is set - - Fix duplicated branch creation/deletion events when using Web UI (Stan Hu) - - Add configurable LDAP server query timeout - - Get "Merge when build succeeds" to work when commits were pushed to MR target branch while builds were running - - Suppress e-mails on failed builds if allow_failure is set (Stan Hu) - - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) - - Better support for referencing and closing issues in Asana service (Mike Wyatt) - - Enable "Add key" button when user fills in a proper key (Stan Hu) - - Fix error in processing reply-by-email messages (Jason Lee) - - Fix Error 500 when visiting build page of project with nil runners_token (Stan Hu) - - Use WOFF versions of SourceSansPro fonts - - Fix regression when builds were not generated for tags created through web/api interface - - Fix: maintain milestone filter between Open and Closed tabs (Greg Smethells) - - Fix missing artifacts and build traces for build created before 8.3 - -v 8.3.2 - - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) - - Add support for Google reCAPTCHA in user registration - -v 8.3.1 - - Fix Error 500 when global milestones have slashes (Stan Hu) - - Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu) - - Fix LDAP identity and user retrieval when special characters are used - - Move Sidekiq-cron configuration to gitlab.yml - -v 8.3.0 - - Bump rack-attack to 4.3.1 for security fix (Stan Hu) - - API support for starred projects for authorized user (Zeger-Jan van de Weg) - - Add open_issues_count to project API (Stan Hu) - - Expand character set of usernames created by Omniauth (Corey Hinshaw) - - Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg) - - Add unsubscribe link in the email footer (Zeger-Jan van de Weg) - - Provide better diagnostic message upon project creation errors (Stan Hu) - - Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu) - - Remove api credentials from link to build_page - - Deprecate GitLabCiService making it to always be inactive - - Bump gollum-lib to 4.1.0 (Stan Hu) - - Fix broken group avatar upload under "New group" (Stan Hu) - - Update project repositorize size and commit count during import:repos task (Stan Hu) - - Fix API setting of 'public' attribute to false will make a project private (Stan Hu) - - Handle and report SSL errors in Webhook test (Stan Hu) - - Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu) - - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - - WIP identifier on merge requests no longer requires trailing space - - Add rake tasks for git repository maintainance (Zeger-Jan van de Weg) - - Fix 500 error when update group member permission - - Fix: As an admin, cannot add oneself as a member to a group/project - - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - - Recognize issue/MR/snippet/commit links as references - - Backport JIRA features from EE to CE - - Add ignore whitespace change option to commit view - - Fire update hook from GitLab - - Allow account unlock via email - - Style warning about mentioning many people in a comment - - Fix: sort milestones by due date once again (Greg Smethells) - - Migrate all CI::Services and CI::WebHooks to Services and WebHooks - - Don't show project fork event as "imported" - - Add API endpoint to fetch merge request commits list - - Don't create CI status for refs that doesn't have .gitlab-ci.yml, even if the builds are enabled - - Expose events API with comment information and author info - - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583 - - Run custom Git hooks when branch is created or deleted. - - Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch - - Add languages page to graphs - - Block LDAP user when they are no longer found in the LDAP server - - Improve wording on project visibility levels (Zeger-Jan van de Weg) - - Fix editing notes on a merge request diff - - Automatically select default clone protocol based on user preferences (Eirik Lygre) - - Make Network page as sub tab of Commits - - Add copy-to-clipboard button for Snippets - - Add indication to merge request list item that MR cannot be merged automatically - - Default target branch to patch-n when editing file in protected branch - - Add Builds tab to merge request detail page - - Allow milestones, issues and MRs to be created from dashboard and group indexes - - Use new style for wiki - - Use new style for milestone detail page - - Fix sidebar tooltips when collapsed - - Prevent possible XSS attack with award-emoji - - Upgraded Sidekiq to 4.x - - Accept COPYING,COPYING.lesser, and licence as license file (Zeger-Jan van de Weg) - - Fix emoji aliases problem - - Fix award-emojis Flash alert's width - - Fix deleting notes on a merge request diff - - Display referenced merge request statuses in the issue description (Greg Smethells) - - Implement new sidebar for issue and merge request pages - - Emoji picker improvements - - Suppress warning about missing `.gitlab-ci.yml` if builds are disabled - - Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present - - Persist runners registration token in database - - Fix online editor should not remove newlines at the end of the file - - Expose Git's version in the admin area - - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye) - -v 8.2.6 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - -v 8.2.5 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via `window.opener` - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.2.4 - - Bump Git version requirement to 2.7.4 - -v 8.2.3 - - Fix application settings cache not expiring after changes (Stan Hu) - - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) - - Update documentation for "Guest" permissions - - Properly convert Emoji-only comments into Award Emojis - - Enable devise paranoid mode to prevent user enumeration attack - - Webhook payload has an added, modified and removed properties for each commit - - Fix 500 error when creating a merge request that removes a submodule - -v 8.2.2 - - Fix 404 in redirection after removing a project (Stan Hu) - - Ensure cached application settings are refreshed at startup (Stan Hu) - - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) - - Fix: Raw private snippets access workflow - - Prevent "413 Request entity too large" errors when pushing large files with LFS - - Fix invalid links within projects dashboard header - - Make current user the first user in assignee dropdown in issues detail page (Stan Hu) - - Fix: duplicate email notifications on issue comments - -v 8.2.1 - - Forcefully update builds that didn't want to update with state machine - - Fix: saving GitLabCiService as Admin Template - -v 8.2.0 - - Improved performance of finding projects and groups in various places - - Improved performance of rendering user profile pages and Atom feeds - - Expose build artifacts path as config option - - Fix grouping of contributors by email in graph. - - Improved performance of finding issues with/without labels - - Fix Drone CI service template not saving properly (Stan Hu) - - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) - - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - - Improved performance of finding users by one of their Email addresses - - Add allow_failure field to commit status API (Stan Hu) - - Commits without .gitlab-ci.yml are marked as skipped - - Save detailed error when YAML syntax is invalid - - Since GitLab CI is enabled by default, remove enabling it by pushing .gitlab-ci.yml - - Added build artifacts - - Improved performance of replacing references in comments - - Show last project commit to default branch on project home page - - Highlight comment based on anchor in URL - - Adds ability to remove the forked relationship from project settings screen. (Han Loong Liauw) - - Improved performance of sorting milestone issues - - Allow users to select the Files view as default project view (Cristian Bica) - - Show "Empty Repository Page" for repository without branches (Artem V. Navrotskiy) - - Fix: Inability to reply to code comments in the MR view, if the MR comes from a fork - - Use git follow flag for commits page when retrieve history for file or directory - - Show merge request CI status on merge requests index page - - Send build name and stage in CI notification e-mail - - Extend yml syntax for only and except to support specifying repository path - - Enable shared runners to all new projects - - Bump GitLab-Workhorse to 0.4.1 - - Allow to define cache in `.gitlab-ci.yml` - - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - - Remove deprecated CI events from project settings page - - Use issue editor as cross reference comment author when issue is edited with a new mention. - - Add graphs of commits ahead and behind default branch (Jeff Stubler) - - Improve personal snippet access workflow (Douglas Alexandre) - - [API] Add ability to fetch the commit ID of the last commit that actually touched a file - - Fix omniauth documentation setting for omnibus configuration (Jon Cairns) - - Add "New file" link to dropdown on project page - - Include commit logs in project search - - Add "added", "modified" and "removed" properties to commit object in webhook - - Rename "Back to" links to "Go to" because its not always a case it point to place user come from - - Allow groups to appear in the search results if the group owner allows it - - Add email notification to former assignee upon unassignment (Adam Lieskovský) - - New design for project graphs page - - Remove deprecated dumped yaml file generated from previous job definitions - - Show specific runners from projects where user is master or owner - - MR target branch is now visible on a list view when it is different from project's default one - - Improve Continuous Integration graphs page - - Make color of "Accept Merge Request" button consistent with current build status - - Add ignore white space option in merge request diff and commit and compare view - - Ability to add release notes (markdown text and attachments) to git tags (aka Releases) - - Relative links from a repositories README.md now link to the default branch - - Fix trailing whitespace issue in merge request/issue title - - Fix bug when milestone/label filter was empty for dashboard issues page - - Add ability to create milestone in group projects from single form - - Add option to create merge request when editing/creating a file (Dirceu Tiegs) - - Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez) - - Add Award Emoji to issue and merge request pages - -v 8.1.4 - - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - - Prevent redirect loop when home_page_url is set to the root URL - - Fix incoming email config defaults - - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - -v 8.1.3 - - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) - - Spread out runner contacted_at updates - - Use issue editor as cross reference comment author when issue is edited with a new mention - - Add Facebook authentication - -v 8.1.2 - - Fix cloning Wiki repositories via HTTP (Stan Hu) - - Add migration to remove satellites directory - - Fix specific runners visibility - - Fix 500 when editing CI service - - Require CI jobs to be named - - Fix CSS for runner status - - Fix CI badge - - Allow developer to manage builds - -v 8.1.1 - - Removed, see 8.1.2 - -v 8.1.0 - - Ensure MySQL CI limits DB migrations occur after the fields have been created (Stan Hu) - - Fix duplicate repositories in GitHub import page (Stan Hu) - - Redirect to a default path if HTTP_REFERER is not set (Stan Hu) - - Adds ability to create directories using the web editor (Ben Ford) - - Cleanup stuck CI builds - - Send an email to admin email when a user is reported for spam (Jonathan Rochkind) - - Show notifications button when user is member of group rather than project (Grzegorz Bizon) - - Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge. - - Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu) - - Don't show "Add README" link in an empty repository if user doesn't have access to push (Stan Hu) - - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) - - Speed up load times of issue detail pages by roughly 1.5x - - Fix CI rendering regressions - - If a merge request is to close an issue, show this on the issue page (Zeger-Jan van de Weg) - - Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu) - - Make diff file view easier to use on mobile screens (Stan Hu) - - Improved performance of finding users by username or Email address - - Fix bug where merge request comments created by API would not trigger notifications (Stan Hu) - - Add support for creating directories from Files page (Stan Hu) - - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) - - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu) - - Improved performance of the trending projects page - - Remove CI migration task - - Improved performance of finding projects by their namespace - - Add assignee data to Issuables' hook_data (Bram Daams) - - Fix bug where transferring a project would result in stale commit links (Stan Hu) - - Fix build trace updating - - Include full path of source and target branch names in New Merge Request page (Stan Hu) - - Add user preference to view activities as default dashboard (Stan Hu) - - Add option to admin area to sign in as a specific user (Pavel Forkert) - - Show CI status on all pages where commits list is rendered - - Automatically enable CI when push .gitlab-ci.yml file to repository - - Move CI charts to project graphs area - - Fix cases where Markdown did not render links in activity feed (Stan Hu) - - Add first and last to pagination (Zeger-Jan van de Weg) - - Added Commit Status API - - Added Builds View - - Added when to .gitlab-ci.yml - - Show CI status on commit page - - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds - - Show CI status on Your projects page and Starred projects page - - Remove "Continuous Integration" page from dashboard - - Add notes and SSL verification entries to hook APIs (Ben Boeckel) - - Fix grammar in admin area "labels" .nothing-here-block when no labels exist. - - Move CI runners page to project settings area - - Move CI variables page to project settings area - - Move CI triggers page to project settings area - - Move CI project settings page to CE project settings area - - Fix bug when removed file was not appearing in merge request diff - - Show warning when build cannot be served by any of the available CI runners - - Note the original location of a moved project when notifying users of the move - - Improve error message when merging fails - - Add support of multibyte characters in LDAP UID (Roman Petrov) - - Show additions/deletions stats on merge request diff - - Remove footer text in emails (Zeger-Jan van de Weg) - - Ensure code blocks are properly highlighted after a note is updated - - Fix wrong access level badge on MR comments - - Hide password in the service settings form - - Move CI webhooks page to project settings area - - Fix User Identities API. It now allows you to properly create or update user's identities. - - Add user preference to change layout width (Peter Göbel) - - Use commit status in merge request widget as preferred source of CI status - - Integrate CI commit and build pages into project pages - - Move CI services page to project settings area - - Add "Quick Submit" behavior to input fields throughout the application. Use - Cmd+Enter on Mac and Ctrl+Enter on Windows/Linux. - - Fix position of hamburger in header for smaller screens (Han Loong Liauw) - - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) - - Persist filters when sorting on admin user page (Jerry Lukins) - - Update style of snippets pages (Han Loong Liauw) - - Allow dashboard and group issues/MRs to be filtered by label - - Add spellcheck=false to certain input fields - - Invalidate stored service password if the endpoint URL is changed - - Project names are not fully shown if group name is too big, even on group page view - - Apply new design for Files page - - Add "New Page" button to Wiki Pages tab (Stan Hu) - - Only render 404 page from /public - - Hide passwords from services API (Alex Lossent) - - Fix: Images cannot show when projects' path was changed - - Let gitlab-git-http-server generate and serve 'git archive' downloads - - Optimize query when filtering on issuables (Zeger-Jan van de Weg) - - Fix padding of outdated discussion item. - - Animate the logo on hover - -v 8.0.5 - - Correct lookup-by-email for LDAP logins - - Fix loading spinner sometimes not being hidden on Merge Request tab switches - -v 8.0.4 - - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) - - Fix referrals for :back and relative URL installs - - Fix anchors to comments in diffs - - Remove CI token from build traces - - Fix "Assign All" button on Runner admin page - - Fix search in Files - - Add full project namespace to payload of system webhooks (Ricardo Band) - -v 8.0.3 - - Fix URL shown in Slack notifications - - Fix bug where projects would appear to be stuck in the forked import state (Stan Hu) - - Fix Error 500 in creating merge requests with > 1000 diffs (Stan Hu) - - Add work_in_progress key to MR webhooks (Ben Boeckel) - -v 8.0.2 - - Fix default avatar not rendering in network graph (Stan Hu) - - Skip check_initd_configured_correctly on omnibus installs - - Prevent double-prefixing of help page paths - - Clarify confirmation text on user deletion - - Make commit graphs responsive to window width changes (Stan Hu) - - Fix top margin for sign-in button on public pages - - Fix LDAP attribute mapping - - Remove git refs used internally by GitLab from network graph (Stan Hu) - - Use standard Markdown font in Markdown preview instead of fixed-width font (Stan Hu) - - Fix Reply by email for non-UTF-8 messages. - - Add option to use StartTLS with Reply by email IMAP server. - - Allow AWS S3 Server-Side Encryption with Amazon S3-Managed Keys for backups (Paul Beattie) - -v 8.0.1 - - Improve CI migration procedure and documentation - -v 8.0.0 - - Fix Markdown links not showing up in dashboard activity feed (Stan Hu) - - Remove milestones from merge requests when milestones are deleted (Stan Hu) - - Fix HTML link that was improperly escaped in new user e-mail (Stan Hu) - - Fix broken sort in merge request API (Stan Hu) - - Bump rouge to 1.10.1 to remove warning noise and fix other syntax highlighting bugs (Stan Hu) - - Gracefully handle errors in syntax highlighting by leaving the block unformatted (Stan Hu) - - Add "replace" and "upload" functionalities to allow user replace existing file and upload new file into current repository - - Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu) - - Fix emoji URLs in Markdown when relative_url_root is used (Stan Hu) - - Omit filename in Content-Disposition header in raw file download to avoid RFC 6266 encoding issues (Stan HU) - - Fix broken Wiki Page History (Stan Hu) - - Import forked repositories asynchronously to prevent large repositories from timing out (Stan Hu) - - Prevent anchors from being hidden by header (Stan Hu) - - Fix bug where only the first 15 Bitbucket issues would be imported (Stan Hu) - - Sort issues by creation date in Bitbucket importer (Stan Hu) - - Prevent too many redirects upon login when home page URL is set to external_url (Stan Hu) - - Improve dropdown positioning on the project home page (Hannes Rosenögger) - - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu) - - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) - - Restrict users API endpoints to use integer IDs (Stan Hu) - - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - - Remove satellites - - Better performance for web editor (switched from satellites to rugged) - - Faster merge - - Ability to fetch merge requests from refs/merge-requests/:id - - Allow displaying of archived projects in the admin interface (Artem Sidorenko) - - Allow configuration of import sources for new projects (Artem Sidorenko) - - Search for comments should be case insensetive - - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) - - Ability to search milestones - - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu) - - Move dashboard activity to separate page (for your projects and starred projects) - - Improve performance of git blame - - Limit content width to 1200px for most of pages to improve readability on big screens - - Fix 500 error when submit project snippet without body - - Improve search page usability - - Bring more UI consistency in way how projects, snippets and groups lists are rendered - - Make all profiles and group public - - Fixed login failure when extern_uid changes (Joel Koglin) - - Don't notify users without access to the project when they are (accidentally) mentioned in a note. - - Retrieving oauth token with LDAP credentials - - Load Application settings from running database unless env var USE_DB=false - - Added Drone CI integration (Kirill Zaitsev) - - Allow developers to retry builds - - Hide advanced project options for non-admin users - - Fail builds if no .gitlab-ci.yml is found - - Refactored service API and added automatically service docs generator (Kirill Zaitsev) - - Added web_url key project hook_attrs (Kirill Zaitsev) - - Add ability to get user information by ID of an SSH key via the API - - Fix bug which IE cannot show image at markdown when the image is raw file of gitlab - - Add support for Crowd - - Global Labels that are available to all projects - - Fix highlighting of deleted lines in diffs. - - Project notification level can be set on the project page itself - - Added service API endpoint to retrieve service parameters (Petheő Bence) - - Add FogBugz project import (Jared Szechy) - - Sort users autocomplete lists by user (Allister Antosik) - - Webhook for issue now contains repository field (Jungkook Park) - - Add ability to add custom text to the help page (Jeroen van Baarsen) - - Add pg_schema to backup config - - Fix references to target project issues in Merge Requests markdown preview and textareas (Francesco Levorato) - - Redirect from incorrectly cased group or project path to correct one (Francesco Levorato) - - Removed API calls from CE to CI - -v 7.14.3 through 0.8.0 - - See changelogs/archive.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..46f718fc88a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2465 @@ +Please view this file on the master branch, on stable branches it's out of date. + +## 8.13.0 (2016-10-22) + + - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) + - Respond with 404 Not Found for non-existent tags (Linus Thiel) + - Truncate long labels with ellipsis in labels page + - Improve tabbing usability for sign in page (ClemMakesApps) + - Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint + - Adding members no longer silently fails when there is extra whitespace + - Update runner version only when updating contacted_at + - Add link from system note to compare with previous version + - Use gitlab-shell v3.6.6 + - Add `/projects/visible` API endpoint (Ben Boeckel) + - Fix centering of custom header logos (Ashley Dumaine) + - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup + - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) + - Updating verbiage on git basics to be more intuitive + - Clarify documentation for Runners API (Gennady Trafimenkov) + - The instrumentation for Banzai::Renderer has been restored + - Change user & group landing page routing from /u/:username to /:username + - Prevent running GfmAutocomplete setup for each diff note !6569 + - Added documentation for .gitattributes files + - AbstractReferenceFilter caches project_refs on RequestStore when active + - Replaced the check sign to arrow in the show build view. !6501 + - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - Fix Error 500 when viewing old merge requests with bad diff data + - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) + - Speed-up group milestones show page + - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) + - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService + - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) + - Add tag shortcut from the Commit page. !6543 + - Keep refs for each deployment + - Allow browsing branches that end with '.atom' + - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) + - Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps) + - Add more tests for calendar contribution (ClemMakesApps) + - Update Gitlab Shell to fix some problems with moving projects between storages + - Cache rendered markdown in the database, rather than Redis + - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references + - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified + - Simplify Mentionable concern instance methods + - API: Ability to retrieve version information (Robert Schilling) + - Fix permission for setting an issue's due date + - API: Multi-file commit !6096 (mahcsig) + - Unicode emoji are now converted to images + - Revert "Label list shows all issues (opened or closed) with that label" + - Expose expires_at field when sharing project on API + - Fix VueJS template tags being rendered in code comments + - Added copy file path button to merge request diff files + - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) + - Add Issue Board API support (andrebsguedes) + - Allow the Koding integration to be configured through the API + - Add new issue button to each list on Issues Board + - Added soft wrap button to repository file/blob editor + - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) + - Show the time ago a merge request was deployed to an environment + - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) + - Fix todos page mobile viewport layout (ClemMakesApps) + - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) + - Remove redundant mixins (ClemMakesApps) + - Added 'Download' button to the Snippets page (Justin DiPierro) + - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) + - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) + - Fix that manual jobs would no longer block jobs in the next stage. !6604 + - Add configurable email subject suffix (Fu Xu) + - Use defined colour for a language when available !6748 (nilsding) + - Added tooltip to fork count on project show page. (Justin DiPierro) + - Use a ConnectionPool for Rails.cache on Sidekiq servers + - Replace `alias_method_chain` with `Module#prepend` + - Enable GitLab Import/Export for non-admin users. + - Preserve label filters when sorting !6136 (Joseph Frazier) + - MergeRequest#new form load diff asynchronously + - Only update issuable labels if they have been changed + - Take filters in account in issuable counters. !6496 + - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) + - Prevent flash alert text from being obscured when container is fluid + - Append issue template to existing description !6149 (Joseph Frazier) + - Trending projects now only show public projects and the list of projects is cached for a day + - Memoize Gitlab Shell's secret token (!6599, Justin DiPierro) + - Revoke button in Applications Settings underlines on hover. + - Use higher size on Gitlab::Redis connection pool on Sidekiq servers + - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) + - Fix Long commit messages overflow viewport in file tree + - Revert avoid touching file system on Build#artifacts? + - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created + - Add disabled delete button to protected branches (ClemMakesApps) + - Add broadcast messages and alerts below sub-nav + - Better empty state for Groups view + - API: New /users/:id/events endpoint + - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) + - Replace bootstrap caret with fontawesome caret (ClemMakesApps) + - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 + - Add organization field to user profile + - Ignore deployment for statistics in Cycle Analytics, except in staging and production stages + - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) + - Fix deploy status responsiveness error !6633 + - Make searching for commits case insensitive + - Fix resolved discussion display in side-by-side diff view !6575 + - Optimize GitHub importing for speed and memory + - API: expose pipeline data in builds API (!6502, Guilherme Salazar) + - Notify the Merger about merge after successful build (Dimitris Karakasilis) + - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) + - Reduce queries needed to find users using their SSH keys when pushing commits + - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) + - Fix broken repository 500 errors in project list + - Fix Pipeline list commit column width should be adjusted + - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) + - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) + - Retouch environments list and deployments list + - Add multiple command support for all label related slash commands !6780 (barthc) + - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Allow empty merge requests !6384 (Artem Sidorenko) + - Grouped pipeline dropdown is a scrollable container + - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) + - Fixes padding in all clipboard icons that have .btn class + - Fix a typo in doc/api/labels.md + - API: all unknown routing will be handled with 404 Not Found + - Add docs for request profiling + - Make guests unable to view MRs on private projects + +## 8.12.7 + + - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 + - Fix GFM autocomplete setup being called several times + +## 8.12.6 + + - Update mailroom to 0.8.1 in Gemfile.lock !6814 + +## 8.12.5 + + - Switch from request to env in ::API::Helpers. !6615 + - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 + - Improve issue load time performance by avoiding ORDER BY in find_by call. !6724 + - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + +## 8.12.4 + + - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) + - Fix padding in build sidebar. !6506 + - Changed compare dropdowns to dropdowns with isolated search input. !6550 + - Fix race condition on LFS Token. !6592 + - Fix type mismatch bug when closing Jira issue. !6619 + - Fix lint-doc error. !6623 + - Skip wiki creation when GitHub project has wiki enabled. !6665 + - Fix issues importing services via Import/Export. !6667 + - Restrict failed login attempts for users with 2FA enabled. !6668 + - Fix failed project deletion when feature visibility set to private. !6688 + - Prevent claiming associated model IDs via import. + - Set GitLab project exported file permissions to owner only + - Improve the way merge request versions are compared with each other + +## 8.12.3 + + - Update Gitlab Shell to support low IO priority for storage moves + +## 8.12.2 + + - Fix Import/Export not recognising correctly the imported services. + - Fix snippets pagination + - Fix "Create project" button layout when visibility options are restricted + - Fix List-Unsubscribe header in emails + - Fix IssuesController#show degradation including project on loaded notes + - Fix an issue with the "Commits" section of the cycle analytics summary. !6513 + - Fix errors importing project feature and milestone models using GitLab project import + - Make JWT messages Docker-compatible + - Fix duplicate branch entry in the merge request version compare dropdown + - Respect the fork_project permission when forking projects + - Only update issuable labels if they have been changed + - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) + - Fix resolve discussion buttons endpoint path + - Refactor remnants of CoffeeScript destructured opts and super !6261 + +## 8.12.1 + + - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST + - Fix issue with search filter labels not displaying + +## 8.12.0 (2016-09-22) + + - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 + - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 + - Only check :can_resolve permission if the note is resolvable + - Bump fog-aws to v0.11.0 to support ap-south-1 region + - Add ability to fork to a specific namespace using API. (ritave) + - Allow to set request_access_enabled for groups and projects + - Cleanup misalignments in Issue list view !6206 + - Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist + - Add Pipelines for Commit + - Prune events older than 12 months. (ritave) + - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) + - Fix issues/merge-request templates dropdown for forked projects + - Filter tags by name !6121 + - Update gitlab shell secret file also when it is empty. !3774 (glensc) + - Give project selection dropdowns responsive width, make non-wrapping. + - Fix note form hint showing slash commands supported for commits. + - Make push events have equal vertical spacing. + - API: Ensure invitees are not returned in Members API. + - Preserve applied filters on issues search. + - Add two-factor recovery endpoint to internal API !5510 + - Pass the "Remember me" value to the U2F authentication form + - Display stages in valid order in stages dropdown on build page + - Only update projects.last_activity_at once per hour when creating a new event + - Cycle analytics (first iteration) !5986 + - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) + - Move pushes_since_gc from the database to Redis + - Limit number of shown environments on Merge Request: show only environments for target_branch, source_branch and tags + - Add font color contrast to external label in admin area (ClemMakesApps) + - Fix find file navigation links (ClemMakesApps) + - Change logo animation to CSS (ClemMakesApps) + - Instructions for enabling Git packfile bitmaps !6104 + - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint + - Fix long comments in diffs messing with table width + - Add spec covering 'Gitlab::Git::committer_hash' !6433 (dandunckelman) + - Fix pagination on user snippets page + - Honor "fixed layout" preference in more places !6422 + - Run CI builds with the permissions of users !5735 + - Fix sorting of issues in API + - Fix download artifacts button links !6407 + - Sort project variables by key. !6275 (Diego Souza) + - Ensure specs on sorting of issues in API are deterministic on MySQL + - Added ability to use predefined CI variables for environment name + - Added ability to specify URL in environment configuration in gitlab-ci.yml + - Escape search term before passing it to Regexp.new !6241 (winniehell) + - Fix pinned sidebar behavior in smaller viewports !6169 + - Fix file permissions change when updating a file on the Gitlab UI !5979 + - Added horizontal padding on build page sidebar on code coverage block. !6196 (Vitaly Baev) + - Change merge_error column from string to text type + - Fix issue with search filter labels not displaying + - Reduce contributions calendar data payload (ClemMakesApps) + - Show all pipelines for merge requests even from discarded commits !6414 + - Replace contributions calendar timezone payload with dates (ClemMakesApps) + - Changed MR widget build status to pipeline status !6335 + - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) + - Enable pipeline events by default !6278 + - Move parsing of sidekiq ps into helper !6245 (pascalbetz) + - Added go to issue boards keyboard shortcut + - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) + - Emoji can be awarded on Snippets !4456 + - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) + - Fix blame table layout width + - Spec testing if issue authors can read issues on private projects + - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) + - Request only the LDAP attributes we need !6187 + - Center build stage columns in pipeline overview (ClemMakesApps) + - Fix bug with tooltip not hiding on discussion toggle button + - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) + - Fix bug stopping issue description being scrollable after selecting issue template + - Remove suggested colors hover underline (ClemMakesApps) + - Fix jump to discussion button being displayed on commit notes + - Shorten task status phrase (ClemMakesApps) + - Fix project visibility level fields on settings + - Add hover color to emoji icon (ClemMakesApps) + - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files + - Add textarea autoresize after comment (ClemMakesApps) + - Do not write SSH public key 'comments' to authorized_keys !6381 + - Add due date to issue todos + - Refresh todos count cache when an Issue/MR is deleted + - Fix branches page dropdown sort alignment (ClemMakesApps) + - Hides merge request button on branches page is user doesn't have permissions + - Add white background for no readme container (ClemMakesApps) + - API: Expose issue confidentiality flag. (Robert Schilling) + - Fix markdown anchor icon interaction (ClemMakesApps) + - Test migration paths from 8.5 until current release !4874 + - Replace animateEmoji timeout with eventListener (ClemMakesApps) + - Show badges in Milestone tabs. !5946 (Dan Rowden) + - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) + - Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto) + - Add `wiki_page_events` to project hook APIs (Ben Boeckel) + - Remove Gitorious import + - Loads GFM autocomplete source only when required + - Fix issue with slash commands not loading on new issue page + - Fix inconsistent background color for filter input field (ClemMakesApps) + - Remove prefixes from transition CSS property (ClemMakesApps) + - Add Sentry logging to API calls + - Add BroadcastMessage API + - Merge request tabs are fixed when scrolling page + - Use 'git update-ref' for safer web commits !6130 + - Sort pipelines requested through the API + - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Fix issue boards loading on large screens + - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 + - Show queued time when showing a pipeline !6084 + - Remove unused mixins (ClemMakesApps) + - Fix issue board label filtering appending already filtered labels + - Add search to all issue board lists + - Scroll active tab into view on mobile + - Fix groups sort dropdown alignment (ClemMakesApps) + - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) + - Use JavaScript tooltips for mentions !5301 (winniehell) + - Add hover state to todos !5361 (winniehell) + - Fix icon alignment of star and fork buttons !5451 (winniehell) + - Fix alignment of icon buttons !5887 (winniehell) + - Added Ubuntu 16.04 support for packager.io (JonTheNiceGuy) + - Fix markdown help references (ClemMakesApps) + - Add last commit time to repo view (ClemMakesApps) + - Fix accessibility and visibility of project list dropdown button !6140 + - Fix missing flash messages on service edit page (airatshigapov) + - Added project-specific enable/disable setting for LFS !5997 + - Added group-specific enable/disable setting for LFS !6164 + - Add optional 'author' param when making commits. !5822 (dandunckelman) + - Don't expose a user's token in the `/api/v3/user` API (!6047) + - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) + - Ability to manage project issues, snippets, wiki, merge requests and builds access level + - Remove inconsistent font weight for sidebar's labels (ClemMakesApps) + - Align add button on repository view (ClemMakesApps) + - Fix contributions calendar month label truncation (ClemMakesApps) + - Import release note descriptions from GitHub (EspadaV8) + - Added tests for diff notes + - Add pipeline events to Slack integration !5525 + - Add a button to download latest successful artifacts for branches and tags !5142 + - Remove redundant pipeline tooltips (ClemMakesApps) + - Expire commit info views after one day, instead of two weeks, to allow for user email updates + - Add delimiter to project stars and forks count (ClemMakesApps) + - Fix badge count alignment (ClemMakesApps) + - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) + - Fix repo title alignment (ClemMakesApps) + - Change update interval of contacted_at + - Add LFS support to SSH !6043 + - Fix branch title trailing space on hover (ClemMakesApps) + - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) + - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) + - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) + - Order award emoji tooltips in order they were added (EspadaV8) + - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) + - Update merge_requests.md with a simpler way to check out a merge request. !5944 + - Fix button missing type (ClemMakesApps) + - Gitlab::Checks is now instrumented + - Move to project dropdown with infinite scroll for better performance + - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz) + - Load branches asynchronously in Cherry Pick and Revert dialogs. + - Convert datetime coffeescript spec to ES6 (ClemMakesApps) + - Add merge request versions !5467 + - Change using size to use count and caching it for number of group members. !5935 + - Replace play icon font with svg (ClemMakesApps) + - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) + - Reduce number of database queries on builds tab + - Wrap text in commit message containers + - Capitalize mentioned issue timeline notes (ClemMakesApps) + - Fix inconsistent checkbox alignment (ClemMakesApps) + - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) + - Adds response mime type to transaction metric action when it's not HTML + - Fix hover leading space bug in pipeline graph !5980 + - Avoid conflict with admin labels when importing GitHub labels + - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 + - Fix repository page ui issues + - Avoid protected branches checks when verifying access without branch name + - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov) + - Fixed invisible scroll controls on build page on iPhone + - Fix error on raw build trace download for old builds stored in database !4822 + - Refactor the triggers page and documentation !6217 + - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska) + - Use default clone protocol on "check out, review, and merge locally" help page URL + - Let the user choose a namespace and name on GitHub imports + - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) + - Allow bulk update merge requests from merge requests index page + - Ensure validation messages are shown within the milestone form + - Add notification_settings API calls !5632 (mahcsig) + - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) + - Fix URLs with anchors in wiki !6300 (houqp) + - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) + - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 + - Fix Gitlab::Popen.popen thread-safety issue + - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) + - Clean environment variables when running git hooks + - Fix Import/Export issues importing protected branches and some specific models + - Fix non-master branch readme display in tree view + - Add UX improvements for merge request version diffs + +## 8.11.9 + + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + +## 8.11.8 + + - Respect the fork_project permission when forking projects + - Set a restrictive CORS policy on the API for credentialed requests + - API: disable rails session auth for non-GET/HEAD requests + - Escape HTML nodes in builds commands in CI linter + +## 8.11.7 + + - Avoid conflict with admin labels when importing GitHub labels. !6158 + - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 + - Allow the Rails cookie to be used for API authentication. + - Login/Register UX upgrade !6328 + +## 8.11.6 + + - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 + - Make merge conflict file size limit 200 KB, to match the docs. !6052 + - Fix an error where we were unable to create a CommitStatus for running state. !6107 + - Optimize discussion notes resolving and unresolving. !6141 + - Fix GitLab import button. !6167 + - Restore SSH Key title auto-population behavior. !6186 + - Fix DB schema to match latest migration. !6256 + - Exclude some pending or inactivated rows in Member scopes. + +## 8.11.5 + + - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 + - Fix member expiration date picker after update. !6184 + - Fix suggested colors options for new labels in the admin area. !6138 + - Optimize discussion notes resolving and unresolving + - Fix GitLab import button + - Fix confidential issues being exposed as public using gitlab.com export + - Remove gitorious from import_sources. !6180 + - Scope webhooks/services that will run for confidential issues + - Remove gitorious from import_sources + - Fix confidential issues being exposed as public using gitlab.com export + - Use oj gem for faster JSON processing + +## 8.11.4 + + - Fix resolving conflicts on forks. !6082 + - Fix diff commenting on merge requests created prior to 8.10. !6029 + - Fix pipelines tab layout regression. !5952 + - Fix "Wiki" link not appearing in navigation for projects with external wiki. !6057 + - Do not enforce using hash with hidden key in CI configuration. !6079 + - Fix hover leading space bug in pipeline graph !5980 + - Fix sorting issues by "last updated" doesn't work after import from GitHub + - GitHub importer use default project visibility for non-private projects + - Creating an issue through our API now emails label subscribers !5720 + - Block concurrent updates for Pipeline + - Don't create groups for unallowed users when importing projects + - Fix issue boards leak private label names and descriptions + - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) + - Remove gitorious. !5866 + - Allow compare merge request versions + +## 8.11.3 + + - Allow system info page to handle case where info is unavailable + - Label list shows all issues (opened or closed) with that label + - Don't show resolve conflicts link before MR status is updated + - Fix IE11 fork button bug !5982 + - Don't prevent viewing the MR when git refs for conflicts can't be found on disk + - Fix external issue tracker "Issues" link leading to 404s + - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters + - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Issues filters reset button + +## 8.11.2 + + - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 + - Use gitlab-workhorse 0.7.11 !5983 + - Does not halt the GitHub import process when an error occurs. !5763 + - Fix file links on project page when default view is Files !5933 + - Fixed enter key in search input not working !5888 + +## 8.11.1 + + - Pulled due to packaging error. + +## 8.11.0 (2016-08-22) + + - Use test coverage value from the latest successful pipeline in badge. !5862 + - Add test coverage report badge. !5708 + - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) + - Add Koding (online IDE) integration + - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) + - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) + - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) + - Fix adding line comments on the initial commit to a repo !5900 + - Fix the title of the toggle dropdown button. !5515 (herminiotorres) + - Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz) + - Update to Ruby 2.3.1. !4948 + - Add Issues Board !5548 + - Allow resolving merge conflicts in the UI !5479 + - Improve diff performance by eliminating redundant checks for text blobs + - Ensure that branch names containing escapable characters (e.g. %20) aren't unescaped indiscriminately. !5770 (ewiltshi) + - Convert switch icon into icon font (ClemMakesApps) + - API: Endpoints for enabling and disabling deploy keys + - API: List access requests, request access, approve, and deny access requests to a project or a group. !4833 + - Use long options for curl examples in documentation !5703 (winniehell) + - Added tooltip listing label names to the labels value in the collapsed issuable sidebar + - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) + - GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository + - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) + - Allow naming U2F devices !5833 + - Ignore URLs starting with // in Markdown links !5677 (winniehell) + - Fix CI status icon link underline (ClemMakesApps) + - The Repository class is now instrumented + - Fix commit mention font inconsistency (ClemMakesApps) + - Do not escape URI when extracting path !5878 (winniehell) + - Fix filter label tooltip HTML rendering (ClemMakesApps) + - Cache the commit author in RequestStore to avoid extra lookups in PostReceive + - Expand commit message width in repo view (ClemMakesApps) + - Cache highlighted diff lines for merge requests + - Pre-create all builds for a Pipeline when the new Pipeline is created !5295 + - Allow merge request diff notes and discussions to be explicitly marked as resolved + - API: Add deployment endpoints + - API: Add Play endpoint on Builds + - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' + - Show wall clock time when showing a pipeline. !5734 + - Show member roles to all users on members page + - Project.visible_to_user is instrumented again + - Fix awardable button mutuality loading spinners (ClemMakesApps) + - Sort todos by date and priority + - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable + - Optimize maximum user access level lookup in loading of notes + - Send notification emails to users newly mentioned in issue and MR edits !5800 + - Add "No one can push" as an option for protected branches. !5081 + - Improve performance of AutolinkFilter#text_parse by using XPath + - Add experimental Redis Sentinel support !1877 + - Rendering of SVGs as blobs is now limited to SVGs with a size smaller or equal to 2MB + - Fix branches page dropdown sort initial state (ClemMakesApps) + - Environments have an url to link to + - Various redundant database indexes have been removed + - Update `timeago` plugin to use multiple string/locale settings + - Remove unused images (ClemMakesApps) + - Get issue and merge request description templates from repositories + - Enforce 2FA restrictions on API authentication endpoints !5820 + - Limit git rev-list output count to one in forced push check + - Show deployment status on merge requests with external URLs + - Clean up unused routes (Josef Strzibny) + - Fix issue on empty project to allow developers to only push to protected branches if given permission + - API: Add enpoints for pipelines + - Add green outline to New Branch button. !5447 (winniehell) + - Optimize generating of cache keys for issues and notes + - Fix repository push email formatting in Outlook + - Improve performance of syntax highlighting Markdown code blocks + - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects + - Remove delay when hitting "Reply..." button on page with a lot of discussions + - Retrieve rendered HTML from cache in one request + - Fix renaming repository when name contains invalid chararacters under project settings + - Upgrade Grape from 0.13.0 to 0.15.0. !4601 + - Trigram indexes for the "ci_runners" table have been removed to speed up UPDATE queries + - Fix devise deprecation warnings. + - Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764 + - Update version_sorter and use new interface for faster tag sorting + - Optimize checking if a user has read access to a list of issues !5370 + - Store all DB secrets in secrets.yml, under descriptive names !5274 + - Fix syntax highlighting in file editor + - Support slash commands in issue and merge request descriptions as well as comments. !5021 + - Nokogiri's various parsing methods are now instrumented + - Add archived badge to project list !5798 + - Add simple identifier to public SSH keys (muteor) + - Admin page now references docs instead of a specific file !5600 (AnAverageHuman) + - Fix filter input alignment (ClemMakesApps) + - Include old revision in merge request update hooks (Ben Boeckel) + - Add build event color in HipChat messages (David Eisner) + - Make fork counter always clickable. !5463 (winniehell) + - Document that webhook secret token is sent in X-Gitlab-Token HTTP header !5664 (lycoperdon) + - Gitlab::Highlight is now instrumented + - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 + - Allow users to import cross-repository pull requests from GitHub + - The overhead of instrumented method calls has been reduced + - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) + - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` + - Add pipeline events hook + - Bump gitlab_git to speedup DiffCollection iterations + - Rewrite description of a blocked user in admin settings. (Elias Werberich) + - Make branches sortable without push permission !5462 (winniehell) + - Check for Ci::Build artifacts at database level on pipeline partial + - Convert image diff background image to CSS (ClemMakesApps) + - Remove unnecessary index_projects_on_builds_enabled index from the projects table + - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) + - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration + - Fix search for notes which belongs to deleted objects + - Allow Akismet to be trained by submitting issues as spam or ham !5538 + - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) + - Allow branch names ending with .json for graph and network page !5579 (winniehell) + - Add the `sprockets-es6` gem + - Improve OAuth2 client documentation (muteor) + - Fix diff comments inverted toggle bug (ClemMakesApps) + - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) + - Profile requests when a header is passed + - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. + - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible + - Add commit stats in commit api. !5517 (dixpac) + - Add CI configuration button on project page + - Fix merge request new view not changing code view rendering style + - edit_blob_link will use blob passed onto the options parameter + - Make error pages responsive (Takuya Noguchi) + - The performance of the project dropdown used for moving issues has been improved + - Fix skip_repo parameter being ignored when destroying a namespace + - Add all builds into stage/job dropdowns on builds page + - Change requests_profiles resource constraint to catch virtually any file + - Bump gitlab_git to lazy load compare commits + - Reduce number of queries made for merge_requests/:id/diffs + - Add the option to set the expiration date for the project membership when giving a user access to a project. !5599 (Adam Niedzielski) + - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) + - Fix bug where destroying a namespace would not always destroy projects + - Fix RequestProfiler::Middleware error when code is reloaded in development + - Allow horizontal scrolling of code blocks in issue body + - Catch what warden might throw when profiling requests to re-throw it + - Avoid commit lookup on diff_helper passing existing local variable to the helper method + - Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac) + - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker + - Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko) + - Adds support for pending invitation project members importing projects + - Add pipeline visualization/graph on pipeline page + - Update devise initializer to turn on changed password notification emails. !5648 (tombell) + - Avoid to show the original password field when password is automatically set. !5712 (duduribeiro) + - Fix importing GitLab projects with an invalid MR source project + - Sort folders with submodules in Files view !5521 + - Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0 + - Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska) + - Add pipelines tab to merge requests + - Fix notification_service argument error of declined invitation emails + - Fix a memory leak caused by Banzai::Filter::SanitizationFilter + - Speed up todos queries by limiting the projects set we join with + - Ensure file editing in UI does not overwrite commited changes without warning user + - Eliminate unneeded calls to Repository#blob_at when listing commits with no path + - Update gitlab_git gem to 10.4.7 + - Simplify SQL queries of marking a todo as done + +## 8.10.12 + + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + +## 8.10.11 + + - Respect the fork_project permission when forking projects + - Set a restrictive CORS policy on the API for credentialed requests + - API: disable rails session auth for non-GET/HEAD requests + - Escape HTML nodes in builds commands in CI linter + +## 8.10.10 + + - Allow the Rails cookie to be used for API authentication. + +## 8.10.9 + + - Exclude some pending or inactivated rows in Member scopes + +## 8.10.8 + + - Fix information disclosure in issue boards. + - Fix privilege escalation in project import. + +## 8.10.7 + + - Upgrade Hamlit to 2.6.1. !5873 + - Upgrade Doorkeeper to 4.2.0. !5881 + +## 8.10.6 + + - Upgrade Rails to 4.2.7.1 for security fixes. !5781 + - Restore "Largest repository" sort option on Admin > Projects page. !5797 + - Fix privilege escalation via project export. + - Require administrator privileges to perform a project import. + +## 8.10.5 + + - Add a data migration to fix some missing timestamps in the members table. !5670 + - Revert the "Defend against 'Host' header injection" change in the source NGINX templates. !5706 + - Cache project count for 5 minutes to reduce DB load. !5746 & !5754 + +## 8.10.4 + + - Don't close referenced upstream issues from a forked project. + - Fixes issue with dropdowns `enter` key not working correctly. !5544 + - Fix Import/Export project import not working in HA mode. !5618 + - Fix Import/Export error checking versions. !5638 + +## 8.10.3 + + - Fix Import/Export issue importing milestones and labels not associated properly. !5426 + - Fix timing problems running imports on production. !5523 + - Add a log message when a project is scheduled for destruction for debugging. !5540 + - Fix hooks missing on imported GitLab projects. !5549 + - Properly abort a merge when merge conflicts occur. !5569 + - Fix importer for GitHub Pull Requests when a branch was removed. !5573 + - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584 + - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588 + - Fix label already exist error message in the right sidebar. + +## 8.10.2 + + - User can now search branches by name. !5144 + - Page is now properly rendered after committing the first file and creating the first branch. !5399 + - Add branch or tag icon to ref in builds page. !5434 + - Fix backup restore. !5459 + - Use project ID in repository cache to prevent stale data from persisting across projects. !5460 + - Fix issue with autocomplete search not working with enter key. !5466 + - Add iid to MR API response. !5468 + - Disable MySQL foreign key checks before dropping all tables. !5472 + - Ensure relative paths for video are rewritten as we do for images. !5474 + - Ensure current user can retry a build before showing the 'Retry' button. !5476 + - Add ENV variable to skip repository storages validations. !5478 + - Added `*.js.es6 gitlab-language=javascript` to `.gitattributes`. !5486 + - Don't show comment button in gutter of diffs on MR discussion tab. !5493 + - Rescue Rugged::OSError (lock exists) when creating references. !5497 + - Fix expand all diffs button in compare view. !5500 + - Show release notes in tags list. !5503 + - Fix a bug where forking a project from a repository storage to another would fail. !5509 + - Fix missing schema update for `20160722221922`. !5512 + - Update `gitlab-shell` version to 3.2.1 in the 8.9->8.10 update guide. !5516 + +## 8.10.1 + + - Refactor repository storages documentation. !5428 + - Gracefully handle case when keep-around references are corrupted or exist already. !5430 + - Add detailed info on storage path mountpoints. !5437 + - Fix Error 500 when creating Wiki pages with hyphens or spaces. !5444 + - Fix bug where replies to commit notes displayed in the MR discussion tab wouldn't show up on the commit page. !5446 + - Ignore invalid trusted proxies in X-Forwarded-For header. !5454 + - Add links to the real markdown.md file for all GFM examples. !5458 + +## 8.10.0 (2016-07-22) + + - Fix profile activity heatmap to show correct day name (eanplatter) + - Speed up ExternalWikiHelper#get_project_wiki_path + - Expose {should,force}_remove_source_branch (Ben Boeckel) + - Add the functionality to be able to rename a file. !5049 + - Disable PostgreSQL statement timeout during migrations + - Fix projects dropdown loading performance with a simplified api cal. !5113 + - Fix commit builds API, return all builds for all pipelines for given commit. !4849 + - Replace Haml with Hamlit to make view rendering faster. !3666 + - Refresh the branch cache after `git gc` runs + - Allow to disable request access button on projects/groups + - Refactor repository paths handling to allow multiple git mount points + - Optimize system note visibility checking by memoizing the visible reference count. !5070 + - Add Application Setting to configure default Repository Path for new projects + - Delete award emoji when deleting a user + - Remove pinTo from Flash and make inline flash messages look nicer. !4854 (winniehell) + - Add an API for downloading latest successful build from a particular branch or tag. !5347 + - Avoid data-integrity issue when cleaning up repository archive cache. + - Add link to profile to commit avatar. !5163 (winniehell) + - Wrap code blocks on Activies and Todos page. !4783 (winniehell) + - Align flash messages with left side of page content. !4959 (winniehell) + - Display tooltip for "Copy to Clipboard" button. !5164 (winniehell) + - Use default cursor for table header of project files. !5165 (winniehell) + - Store when and yaml variables in builds table + - Display last commit of deleted branch in push events. !4699 (winniehell) + - Escape file extension when parsing search results. !5141 (winniehell) + - Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004 + - Add image border in Markdown preview. !5162 (winniehell) + - Apply the trusted_proxies config to the rack request object for use with rack_attack + - Added the ability to block sign ups using a domain blacklist. !5259 + - Upgrade to Rails 4.2.7. !5236 + - Extend exposed environment variables for CI builds + - Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead + - Add API "deploy_keys" for admins to get all deploy keys + - Allow to pull code with deploy key from public projects + - Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts) + - Add Sidekiq queue duration to transaction metrics. + - Add a new column `artifacts_size` to table `ci_builds`. !4964 + - Let Workhorse serve format-patch diffs + - Display tooltip for mentioned users and groups. !5261 (winniehell) + - Allow build email service to be tested + - Added day name to contribution calendar tooltips + - Refactor user authorization check for a single project to avoid querying all user projects + - Make images fit to the size of the viewport. !4810 + - Fix check for New Branch button on Issue page. !4630 (winniehell) + - Fix GFM autocomplete not working on wiki pages + - Fixed enter key not triggering click on first row when searching in a dropdown + - Updated dropdowns in issuable form to use new GitLab dropdown style + - Make images fit to the size of the viewport !4810 + - Fix check for New Branch button on Issue page !4630 (winniehell) + - Fix MR-auto-close text added to description. !4836 + - Support U2F devices in Firefox. !5177 + - Fix issue, preventing users w/o push access to sort tags. !5105 (redetection) + - Add Spring EmojiOne updates. + - Added Rake task for tracking deployments. !5320 + - Fix fetching LFS objects for private CI projects + - Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237 + - Add syntax for multiline blockquote using `>>>` fence. !3954 + - Fix viewing notification settings when a project is pending deletion + - Updated compare dropdown menus to use GL dropdown + - Redirects back to issue after clicking login link + - Eager load award emoji on notes + - Allow to define manual actions/builds on Pipelines and Environments + - Fix pagination when sorting by columns with lots of ties (like priority) + - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020 + - Updated project header design + - Issuable collapsed assignee tooltip is now the users name + - Fix compare view not changing code view rendering style + - Exclude email check from the standard health check + - Updated layout for Projects, Groups, Users on Admin area. !4424 + - Fix changing issue state columns in milestone view + - Update health_check gem to version 2.1.0 + - Add notification settings dropdown for groups + - Render inline diffs for multiple changed lines following eachother + - Wildcards for protected branches. !4665 + - Allow importing from Github using Personal Access Tokens. (Eric K Idema) + - API: Expose `due_date` for issues (Robert Schilling) + - API: Todos. !3188 (Robert Schilling) + - API: Expose shared groups for projects and shared projects for groups. !5050 (Robert Schilling) + - API: Expose `developers_can_push` and `developers_can_merge` for branches. !5208 (Robert Schilling) + - Add "Enabled Git access protocols" to Application Settings + - Diffs will create button/diff form on demand no on server side + - Reduce size of HTML used by diff comment forms + - Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard) + - Fix user creation with stronger minimum password requirements. !4054 (nathan-pmt) + - Only show New Snippet button to users that can create snippets. + - PipelinesFinder uses git cache data + - Track a user who created a pipeline + - Actually render old and new sections of parallel diff next to each other + - Throttle the update of `project.pushes_since_gc` to 1 minute. + - Allow expanding and collapsing files in diff view. !4990 + - Collapse large diffs by default (!4990) + - Fix mentioned users list on diff notes + - Add support for inline videos in GitLab Flavored Markdown. !5215 (original implementation by Eric Hayes) + - Fix creation of deployment on build that is retried, redeployed or rollback + - Don't parse Rinku returned value to DocFragment when it didn't change the original html string. + - Check for conflicts with existing Project's wiki path when creating a new project. + - Show last push widget in upstream after push to fork + - Fix stage status shown for pipelines + - Cache todos pending/done dashboard query counts. + - Don't instantiate a git tree on Projects show default view + - Bump Rinku to 2.0.0 + - Remove unused front-end variable -> default_issues_tracker + - ObjectRenderer retrieve renderer content using Rails.cache.read_multi + - Better caching of git calls on ProjectsController#show. + - Avoid to retrieve MR closes_issues as much as possible. + - Hide project name in project activities. !5068 (winniehell) + - Add API endpoint for a group issues. !4520 (mahcsig) + - Add Bugzilla integration. !4930 (iamtjg) + - Fix new snippet style bug (elliotec) + - Instrument Rinku usage + - Be explicit to define merge request discussion variables + - Use cache for todos counter calling TodoService + - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab + - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. + - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) + - Made project list visibility icon fixed width + - Set import_url validation to be more strict + - Memoize MR merged/closed events retrieval + - Don't render discussion notes when requesting diff tab through AJAX + - Add basic system information like memory and disk usage to the admin panel + - Don't garbage collect commits that have related DB records like comments + - Allow to setup event by channel on slack service + - More descriptive message for git hooks and file locks + - Aliases of award emoji should be stored as original name. !5060 (dixpac) + - Handle custom Git hook result in GitLab UI + - Allow to access Container Registry for Public and Internal projects + - Allow '?', or '&' for label names + - Support redirected blobs for Container Registry integration + - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests + - Add date when user joined the team on the member page + - Fix 404 redirect after validation fails importing a GitLab project + - Added setting to set new users by default as external. !4545 (Dravere) + - Add min value for project limit field on user's form. !3622 (jastkand) + - Reset project pushes_since_gc when we enqueue the git gc call + - Add reminder to not paste private SSH keys. !4399 (Ingo Blechschmidt) + - Collapsed diffs lines/size don't acumulate to overflow diffs. + - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel) + - Style of import project buttons were fixed in the new project page. !5183 (rdemirbay) + - Fix GitHub client requests when rate limit is disabled + - Optimistic locking for Issues and Merge Requests (Title and description overriding prevention) + - Redesign Builds and Pipelines pages + - Change status color and icon for running builds + - Fix commenting issue in side by side diff view for unchanged lines + - Fix markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.` + - Project export filename now includes the project and namespace path + - Fix last update timestamp on issues not preserved on gitlab.com and project imports + - Fix issues importing projects from EE to CE + - Fix creating group with space in group path + - Improve cron_jobs loading error messages. !5318 / !5360 + - Prevent toggling sidebar when clipboard icon clicked + - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) + - Limit the number of retries on error to 3 for exporting projects + - Allow empty repositories on project import/export + - Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska) + - Allow bulk (un)subscription from issues in issue index + - Fix MR diff encoding issues exporting GitLab projects + - Move builds settings out of project settings and rename Pipelines + - Add builds badge to Pipelines settings page + - Export and import avatar as part of project import/export + - Fix migration corrupting import data for old version upgrades + - Show tooltip on GitLab export link in new project page + - Fix import_data wrongly saved as a result of an invalid import_url !5206 + +## 8.9.11 + + - Respect the fork_project permission when forking projects + - Set a restrictive CORS policy on the API for credentialed requests + - API: disable rails session auth for non-GET/HEAD requests + - Escape HTML nodes in builds commands in CI linter + +## 8.9.10 + + - Allow the Rails cookie to be used for API authentication. + +## 8.9.9 + + - Exclude some pending or inactivated rows in Member scopes + +## 8.9.8 + + - Upgrade Doorkeeper to 4.2.0. !5881 + +## 8.9.7 + + - Upgrade Rails to 4.2.7.1 for security fixes. !5781 + - Require administrator privileges to perform a project import. + +## 8.9.6 + + - Fix importing of events under notes for GitLab projects. !5154 + - Fix log statements in import/export. !5129 + - Fix commit avatar alignment in compare view. !5128 + - Fix broken migration in MySQL. !5005 + - Overwrite Host and X-Forwarded-Host headers in NGINX !5213 + - Keeps issue number when importing from Gitlab.com + - Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska) + +## 8.9.5 + + - Add more debug info to import/export and memory killer. !5108 + - Fixed avatar alignment in new MR view. !5095 + - Fix diff comments not showing up in activity feed. !5069 + - Add index on both Award Emoji user and name. !5061 + - Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq. !5056 + - Re-enable import button when import process fails due to namespace already being taken. !5053 + - Fix snippets comments not displayed. !5045 + - Fix emoji paths in relative root configurations. !5027 + - Fix issues importing events in Import/Export. !4987 + - Fixed 'use shortcuts' button on docs. !4979 + - Admin should be able to turn shared runners into specific ones. !4961 + - Update RedCloth to 4.3.2 for CVE-2012-6684. !4929 (Takuya Noguchi) + - Improve the request / withdraw access button. !4860 + +## 8.9.4 + + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + - Fixed search field blur not removing focus. !4704 + - Resolve "Sub nav isn't showing on file view". !4890 + - Fixes middle click and double request when navigating through the file browser. !4891 + - Fixed URL on label button when filtering. !4897 + - Fixed commit avatar alignment. !4933 + - Do not show build retry link when build is active. !4967 + - Fix restore Rake task warning message output. !4980 + - Handle external issues in IssueReferenceFilter. !4988 + - Expiry date on pinned nav cookie. !5009 + - Updated breakpoint for sidebar pinning. !5019 + +## 8.9.3 + + - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963 + - Fix rendering of commit notes. !4953 + - Resolve "Pin should show up at 1280px min". !4947 + - Switched mobile button icons to ellipsis and angle. !4944 + - Correctly returns todo ID after creating todo. !4941 + - Better debugging for memory killer middleware. !4936 + - Remove duplicate new page btn from edit wiki. !4904 + - Use clock_gettime for all performance timestamps. !4899 + - Use memorized tags array when searching tags by name. !4859 + - Fixed avatar alignment in new MR view. !4901 + - Removed fade when filtering results. !4932 + - Fix missing avatar on system notes. !4954 + - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973 + - Use update_columns to bypass all the dirty code on active_record. !4985 + - Fix restore Rake task warning message output !4980 + +## 8.9.2 + + - Fix visibility of snippets when searching. + - Fix an information disclosure when requesting access to a group containing private projects. + - Update omniauth-saml to 1.6.0 !4951 + +## 8.9.1 + + - Refactor labels documentation. !3347 + - Eager load award emoji on notes. !4628 + - Fix some CI wording in documentation. !4660 + - Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720 + - Add documentation for the export & import features. !4732 + - Add some docs for Docker Registry configuration. !4738 + - Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744 + - Display group/project access requesters separately in the admin area. !4798 + - Add documentation and examples for configuring cloud storage for registry images. !4812 + - Clarifies documentation about artifact expiry. !4831 + - Fix the Network graph links. !4832 + - Fix MR-auto-close text added to description. !4836 + - Add documentation for award emoji now that comments can be awarded with emojis. !4839 + - Fix typo in export failure email. !4847 + - Fix header vertical centering. !4170 + - Fix subsequent SAML sign ins. !4718 + - Set button label when picking an option from status dropdown. !4771 + - Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775 + - Handle external issues in IssueReferenceFilter. !4789 + - Support for rendering/redacting multiple documents. !4828 + - Update Todos documentation and screenshots to include new functionality. !4840 + - Hide nav arrows by default. !4843 + - Added bottom padding to label color suggestion link. !4845 + - Use jQuery objects in ref dropdown. !4850 + - Fix GitLab project import issues related to notes and builds. !4855 + - Restrict header logo to 36px so it doesn't overflow. !4861 + - Fix unwanted label unassignment. !4863 + - Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869 + - Restore old behavior around diff notes to outdated discussions. !4870 + - Fix merge requests project settings help link anchor. !4873 + - Fix 404 when accessing pipelines as guest user on public projects. !4881 + - Remove width restriction for logo on sign-in page. !4888 + - Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884 + - Apply selected value as label. !4886 + - Change Retry to Re-deploy on Deployments page + - Fix temp file being deleted after the request while importing a GitLab project. !4894 + - Fix pagination when sorting by columns with lots of ties (like priority) + - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. + - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) + - Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912 + - Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915 + - Remove duplicate 'New Page' button on edit wiki page + +## 8.9.0 (2016-06-22) + + - Fix group visibility form layout in application settings + - Fix builds API response not including commit data + - Fix error when CI job variables key specified but not defined + - Fix pipeline status when there are no builds in pipeline + - Fix Error 500 when using closes_issues API with an external issue tracker + - Add more information into RSS feed for issues (Alexander Matyushentsev) + - Bulk assign/unassign labels to issues. + - Ability to prioritize labels !4009 / !3205 (Thijs Wouters) + - Show Star and Fork buttons on mobile. + - Performance improvements on RelativeLinkFilter + - Fix endless redirections when accessing user OAuth applications when they are disabled + - Allow enabling wiki page events from Webhook management UI + - Bump rouge to 1.11.0 + - Fix issue with arrow keys not working in search autocomplete dropdown + - Fix an issue where note polling stopped working if a window was in the + background during a refresh. + - Pre-processing Markdown now only happens when needed + - Make EmailsOnPushWorker use Sidekiq mailers queue + - Redesign all Devise emails. !4297 + - Don't show 'Leave Project' to group members + - Fix wiki page events' webhook to point to the wiki repository + - Add a border around images to differentiate them from the background. + - Don't show tags for revert and cherry-pick operations + - Show image ID on registry page + - Fix issue todo not remove when leave project !4150 (Long Nguyen) + - Allow customisable text on the 'nearly there' page after a user signs up + - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support + - Fix SVG sanitizer to allow more elements + - Allow forking projects with restricted visibility level + - Added descriptions to notification settings dropdown + - Improve note validation to prevent errors when creating invalid note via API + - Reduce number of fog gem dependencies + - Add number of merge requests for a given milestone to the milestones view. + - Implement a fair usage of shared runners + - Remove project notification settings associated with deleted projects + - Fix 404 page when viewing TODOs that contain milestones or labels in different projects + - Add a metric for the number of new Redis connections created by a transaction + - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark + - Redesign navigation for project pages + - Fix images in sign-up confirmation email + - Added shortcut 'y' for copying a files content hash URL #14470 + - Fix groups API to list only user's accessible projects + - Fix horizontal scrollbar for long commit message. + - GitLab Performance Monitoring now tracks the total method execution time and call count per method + - Add Environments and Deployments + - Redesign account and email confirmation emails + - Don't fail builds for projects that are deleted + - Support Docker Registry manifest v1 + - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix + - Bump nokogiri to 1.6.8 + - Use gitlab-shell v3.0.0 + - Fixed alignment of download dropdown in merge requests + - Upgrade to jQuery 2 + - Adds selected branch name to the dropdown toggle + - Add API endpoint for Sidekiq Metrics !4653 + - Refactoring Award Emoji with API support for Issues and MergeRequests + - Use Knapsack to evenly distribute tests across multiple nodes + - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged + - Don't allow MRs to be merged when commits were added since the last review / page load + - Add DB index on users.state + - Limit email on push diff size to 30 files / 150 KB + - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database + - Changed the Slack build message to use the singular duration if necessary (Aran Koning) + - Fix race condition on merge when build succeeds + - Added shortcut to focus filter search fields and added documentation #18120 + - Links from a wiki page to other wiki pages should be rewritten as expected + - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos) + - Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393 + - Fix issues filter when ordering by milestone + - Disable SAML account unlink feature + - Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3 + - Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid) + - TeamCity Service: Fix URL handling when base URL contains a path + - Todos will display target state if issuable target is 'Closed' or 'Merged' + - Validate only and except regexp + - Fix bug when sorting issues by milestone due date and filtering by two or more labels + - POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project + - Add support for using Yubikeys (U2F) for two-factor authentication + - Link to blank group icon doesn't throw a 404 anymore + - Remove 'main language' feature + - Toggle whitespace button now available for compare branches diffs #17881 + - Pipelines can be canceled only when there are running builds + - Allow authentication using personal access tokens + - Use downcased path to container repository as this is expected path by Docker + - Allow to use CI token to fetch LFS objects + - Custom notification settings + - Projects pending deletion will render a 404 page + - Measure queue duration between gitlab-workhorse and Rails + - Added Gfm autocomplete for labels + - Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114 + - Make Omniauth providers specs to not modify global configuration + - Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir) + - Make authentication service for Container Registry to be compatible with < Docker 1.11 + - Make it possible to lock a runner from being enabled for other projects + - Add Application Setting to configure Container Registry token expire delay (default 5min) + - Cache assigned issue and merge request counts in sidebar nav + - Use Knapsack only in CI environment + - Updated project creation page to match new UI #2542 + - Cache project build count in sidebar nav + - Add milestone expire date to the right sidebar + - Manually mark a issue or merge request as a todo + - Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing + - Reduce number of queries needed to render issue labels in the sidebar + - Improve error handling importing projects + - Remove duplicated notification settings + - Put project Files and Commits tabs under Code tab + - Decouple global notification level from user model + - Replace Colorize with Rainbow for coloring console output in Rake tasks. + - Add workhorse controller and API helpers + - An indicator is now displayed at the top of the comment field for confidential issues. + - Show categorised search queries in the search autocomplete + - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented + - Dropdown for `.gitlab-ci.yml` templates + - Improve issuables APIs performance when accessing notes !4471 + - Add sorting dropdown to tags page !4423 + - External links now open in a new tab + - Prevent default actions of disabled buttons and links + - Markdown editor now correctly resets the input value on edit cancellation !4175 + - Toggling a task list item in a issue/mr description does not creates a Todo for mentions + - Improved UX of date pickers on issue & milestone forms + - Cache on the database if a project has an active external issue tracker. + - Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav + - GitLab project import and export functionality + - All classes in the Banzai::ReferenceParser namespace are now instrumented + - Remove deprecated issues_tracker and issues_tracker_id from project model + - Allow users to create confidential issues in private projects + - Measure CPU time for instrumented methods + - Instrument private methods and private instance methods by default instead just public methods + - Only show notes through JSON on confidential issues that the user has access to + - Updated the allocations Gem to version 1.0.5 + - The background sampler now ignores classes without names + - Update design for `Close` buttons + - New custom icons for navigation + - Horizontally scrolling navigation on project, group, and profile settings pages + - Hide global side navigation by default + - Fix project Star/Unstar project button tooltip + - Remove tanuki logo from side navigation; center on top nav + - Include user relationships when retrieving award_emoji + - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed + - Set inverse_of for Project/Service association to reduce the number of queries + - Update tanuki logo highlight/loading colors + - Remove explicit Gitlab::Metrics.action assignments, are already automatic. + - Use Git cached counters for branches and tags on project page + - Cache participable participants in an instance variable. + - Filter parameters for request_uri value on instrumented transactions. + - Remove duplicated keys add UNIQUE index to keys fingerprint column + - ExtractsPath get ref_names from repository cache, if not there access git. + - Show a flash warning about the error detail of XHR requests which failed with status code 404 and 500 + - Cache user todo counts from TodoService + - Ensure Todos counters doesn't count Todos for projects pending delete + - Add left/right arrows horizontal navigation + - Add tooltip to pin/unpin navbar + - Add new sub nav style to Wiki and Graphs sub navigation + +## 8.8.9 + + - Upgrade Doorkeeper to 4.2.0. !5881 + +## 8.8.8 + + - Upgrade Rails to 4.2.7.1 for security fixes. !5781 + +## 8.8.7 + + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + +## 8.8.6 + + - Fix visibility of snippets when searching. + - Update omniauth-saml to 1.6.0 !4951 + +## 8.8.5 + + - Import GitHub repositories respecting the API rate limit !4166 + - Fix todos page throwing errors when you have a project pending deletion !4300 + - Disable Webhooks before proceeding with the GitHub import !4470 + - Fix importer for GitHub comments on diff !4488 + - Adjust the SAML control flow to allow LDAP identities to be added to an existing SAML user !4498 + - Fix incremental trace upload API when using multi-byte UTF-8 chars in trace !4541 + - Prevent unauthorized access for projects build traces + - Forbid scripting for wiki files + - Only show notes through JSON on confidential issues that the user has access to + - Banzai::Filter::UploadLinkFilter use XPath instead CSS expressions + - Banzai::Filter::ExternalLinkFilter use XPath instead CSS expressions + +## 8.8.4 + + - Fix LDAP-based login for users with 2FA enabled. !4493 + - Added descriptions to notification settings dropdown + - Due date can be removed from milestones + +## 8.8.3 + + - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312 + - Fixed JS error when trying to remove discussion form. !4303 + - Fixed issue with button color when no CI enabled. !4287 + - Fixed potential issue with 2 CI status polling events happening. !3869 + - Improve design of Pipeline view. !4230 + - Fix gitlab importer failing to import new projects due to missing credentials. !4301 + - Fix import URL migration not rescuing with the correct Error. !4321 + - Fix health check access token changing due to old application settings being used. !4332 + - Make authentication service for Container Registry to be compatible with Docker versions before 1.11. !4363 + - Add Application Setting to configure Container Registry token expire delay (default 5 min). !4364 + - Pass the "Remember me" value to the 2FA token form. !4369 + - Fix incorrect links on pipeline page when merge request created from fork. !4376 + - Use downcased path to container repository as this is expected path by Docker. !4420 + - Fix wiki project clone address error (chujinjin). !4429 + - Fix serious performance bug with rendering Markdown with InlineDiffFilter. !4392 + - Fix missing number on generated ordered list element. !4437 + - Prevent disclosure of notes on confidential issues in search results. + +## 8.8.2 + + - Added remove due date button. !4209 + - Fix Error 500 when accessing application settings due to nil disabled OAuth sign-in sources. !4242 + - Fix Error 500 in CI charts by gracefully handling commits with no durations. !4245 + - Fix table UI on CI builds page. !4249 + - Fix backups if registry is disabled. !4263 + - Fixed issue with merge button color. !4211 + - Fixed issue with enter key selecting wrong option in dropdown. !4210 + - When creating a .gitignore file a dropdown with templates will be provided. !4075 + - Fix concurrent request when updating build log in browser. !4183 + +## 8.8.1 + + - Add documentation for the "Health Check" feature + - Allow anonymous users to access a public project's pipelines !4233 + - Fix MySQL compatibility in zero downtime migrations helpers + - Fix the CI login to Container Registry (the gitlab-ci-token user) + +## 8.8.0 (2016-05-22) + + - Implement GFM references for milestones (Alejandro Rodríguez) + - Snippets tab under user profile. !4001 (Long Nguyen) + - Fix error when using link to uploads in global snippets + - Fix Error 500 when attempting to retrieve project license when HEAD points to non-existent ref + - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) + - Use a case-insensitive comparison in sanitizing URI schemes + - Toggle sign-up confirmation emails in application settings + - Make it possible to prevent tagged runner from picking untagged jobs + - Added `InlineDiffFilter` to the markdown parser. (Adam Butler) + - Added inline diff styling for `change_title` system notes. (Adam Butler) + - Project#open_branches has been cleaned up and no longer loads entire records into memory. + - Escape HTML in commit titles in system note messages + - Improve design of Pipeline View + - Fix scope used when accessing container registry + - Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios + - Improve multiple branch push performance by memoizing permission checking + - Log to application.log when an admin starts and stops impersonating a user + - Changing the confidentiality of an issue now creates a new system note (Alex Moore-Niemi) + - Updated gitlab_git to 10.1.0 + - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists + - Reduce delay in destroying a project from 1-minute to immediately + - Make build status canceled if any of the jobs was canceled and none failed + - Upgrade Sidekiq to 4.1.2 + - Added /health_check endpoint for checking service status + - Make 'upcoming' filter for milestones work better across projects + - Sanitize repo paths in new project error message + - Bump mail_room to 0.7.0 to fix stuck IDLE connections + - Remove future dates from contribution calendar graph. + - Support e-mail notifications for comments on project snippets + - Fix API leak of notes of unauthorized issues, snippets and merge requests + - Use ActionDispatch Remote IP for Akismet checking + - Fix error when visiting commit builds page before build was updated + - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project + - Update SVG sanitizer to conform to SVG 1.1 + - Speed up push emails with multiple recipients by only generating the email once + - Updated search UI + - Added authentication service for Container Registry + - Display informative message when new milestone is created + - Sanitize milestones and labels titles + - Support multi-line tag messages. !3833 (Calin Seciu) + - Force users to reset their password after an admin changes it + - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) + - Added button to toggle whitespaces changes on diff view + - Backport GitHub Enterprise import support from EE + - Create tags using Rugged for performance reasons. !3745 + - Allow guests to set notification level in projects + - API: Expose Issue#user_notes_count. !3126 (Anton Popov) + - Don't show forks button when user can't view forks + - Fix atom feed links and rendering + - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 + - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) + - Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724 + - Added multiple colors for labels in dropdowns when dups happen. + - Show commits in the same order as `git log` + - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) + - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) + - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) + - Expire repository exists? and has_visible_content? caches after a push if necessary + - Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi) + - Fix adding a todo for private group members (Ahmad Sherif) + - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 + - Total method execution timings are no longer tracked + - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga) + - Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif) + - Hide left sidebar on phone screens to give more space for content + - Redesign navigation for profile and group pages + - Add counter metrics for rails cache + - Import pull requests from GitHub where the source or target branches were removed + - All Grape API helpers are now instrumented + - Improve Issue formatting for the Slack Service (Jeroen van Baarsen) + - Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine) + - Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs) + - When creating a .gitignore file a dropdown with templates will be provided + - Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562 + +## 8.7.9 + + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + +## 8.7.8 + + - Fix visibility of snippets when searching. + - Update omniauth-saml to 1.6.0 !4951 + +## 8.7.7 + + - Fix import by `Any Git URL` broken if the URL contains a space + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + - Only show notes through JSON on confidential issues that the user has access to + +## 8.7.6 + + - Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko) + - Fix import from GitLab.com to a private instance failure. !4181 + - Fix external imports not finding the import data. !4106 + - Fix notification delay when changing status of an issue + - Bump Workhorse to 0.7.5 so it can serve raw diffs + +## 8.7.5 + + - Fix relative links in wiki pages. !4050 + - Fix always showing build notification message when switching between merge requests !4086 + - Fix an issue when filtering merge requests with more than one label. !3886 + - Fix short note for the default scope on build page (Takuya Noguchi) + +## 8.7.4 + + - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss) + - Fix setting trusted proxies !3970 + - Fix BitBucket importer bug when throwing exceptions !3941 + - Use sign out path only if not empty !3989 + - Running rake gitlab:db:drop_tables now drops tables with cascade !4020 + - Running rake gitlab:db:drop_tables uses "IF EXISTS" as a precaution !4100 + - Use a case-insensitive comparison in sanitizing URI schemes + +## 8.7.3 + + - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented + - Merge request widget displays TeamCity build state and code coverage correctly again. + - Fix the line code when importing PR review comments from GitHub. !4010 + - Wikis are now initialized on legacy projects when checking repositories + - Remove animate.css in favor of a smaller subset of animations. !3937 (Connor Shea) + +## 8.7.2 + + - The "New Branch" button is now loaded asynchronously + - Fix error 500 when trying to create a wiki page + - Updated spacing between notification label and button + - Label titles in filters are now escaped properly + +## 8.7.1 + + - Throttle the update of `project.last_activity_at` to 1 minute. !3848 + - Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849 + - Fix license detection to detect all license files, not only known licenses. !3878 + - Use the `can?` helper instead of `current_user.can?`. !3882 + - Prevent users from deleting Webhooks via API they do not own + - Fix Error 500 due to stale cache when projects are renamed or transferred + - Update width of search box to fix Safari bug. !3900 (Jedidiah) + - Use the `can?` helper instead of `current_user.can?` + +## 8.7.0 (2016-04-22) + + - Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented + - Fix vulnerability that made it possible to gain access to private labels and milestones + - The number of InfluxDB points stored per UDP packet can now be configured + - Fix error when cross-project label reference used with non-existent project + - Transactions for /internal/allowed now have an "action" tag set + - Method instrumentation now uses Module#prepend instead of aliasing methods + - Repository.clean_old_archives is now instrumented + - Add support for environment variables on a job level in CI configuration file + - SQL query counts are now tracked per transaction + - The Projects::HousekeepingService class has extra instrumentation + - All service classes (those residing in app/services) are now instrumented + - Developers can now add custom tags to transactions + - Loading of an issue's referenced merge requests and related branches is now done asynchronously + - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) + - Add support to cherry-pick any commit into any branch in the web interface (Minqi Pan) + - Project switcher uses new dropdown styling + - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) + - Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles) + - Restrict user profiles when public visibility level is restricted. + - Add ability set due date to issues, sort and filter issues by due date (Mehmet Beydogan) + - 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) + - Add setting for customizing the list of trusted proxies !3524 + - Allow projects to be transfered to a lower visibility level group + - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 + - Improved Markdown rendering performance !3389 + - Make shared runners text in box configurable + - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) + - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) + - Expose project badges in project settings + - Make /profile/keys/new redirect to /profile/keys for back-compat. !3717 + - Preserve time notes/comments have been updated at when moving issue + - Make HTTP(s) label consistent on clone bar (Stan Hu) + - Add support for `after_script`, requires Runner 1.2 (Kamil Trzciński) + - Expose label description in API (Mariusz Jachimowicz) + - API: Ability to update a group (Robert Schilling) + - API: Ability to move issues (Robert Schilling) + - Fix Error 500 after renaming a project path (Stan Hu) + - Fix a bug whith trailing slash in teamcity_url (Charles May) + - Allow back dating on issues when created or updated through the API + - Allow back dating on issue notes when created through the API + - Propose license template when creating a new LICENSE file + - API: Expose /licenses and /licenses/:key + - Fix avatar stretching by providing a cropping feature + - API: Expose `subscribed` for issues and merge requests (Robert Schilling) + - Allow SAML to handle external users based on user's information !3530 + - Allow Omniauth providers to be marked as `external` !3657 + - Add endpoints to archive or unarchive a project !3372 + - Fix a bug whith trailing slash in bamboo_url + - Add links to CI setup documentation from project settings and builds pages + - Display project members page to all members + - Handle nil descriptions in Slack issue messages (Stan Hu) + - Add automated repository integrity checks (OFF by default) + - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) + - API: Ability to star and unstar a project (Robert Schilling) + - Add default scope to projects to exclude projects pending deletion + - Allow to close merge requests which source projects(forks) are deleted. + - Ensure empty recipients are rejected in BuildsEmailService + - Use rugged to change HEAD in Project#change_head (P.S.V.R) + - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) + - API: Fix milestone filtering by `iid` (Robert Schilling) + - Make before_script and after_script overridable on per-job (Kamil Trzciński) + - API: Delete notes of issues, snippets, and merge requests (Robert Schilling) + - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) + - Better errors handling when creating milestones inside groups + - Fix high CPU usage when PostReceive receives refs/merge-requests/ + - Hide `Create a group` help block when creating a new project in a group + - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) + - Allow issues and merge requests to be assigned to the author !2765 + - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) + - Gracefully handle notes on deleted commits in merge requests (Stan Hu) + - Decouple membership and notifications + - Fix creation of merge requests for orphaned branches (Stan Hu) + - API: Ability to retrieve a single tag (Robert Schilling) + - While signing up, don't persist the user password across form redisplays + - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) + - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) + - Fix admin/projects when using visibility levels on search (PotHix) + - Build status notifications + - Update email confirmation interface + - API: Expose user location (Robert Schilling) + - API: Do not leak group existence via return code (Robert Schilling) + - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 + - Update number of Todos in the sidebar when it's marked as "Done". !3600 + - Sanitize branch names created for confidential issues + - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) + - API: User can leave a project through the API when not master or owner. !3613 + - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) + - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) + - Improved markdown forms + - Diff design updates (colors, button styles, etc) + - Copying and pasting a diff no longer pastes the line numbers or +/- + - Add null check to formData when updating profile content to fix Firefox bug + - Disable spellcheck and autocorrect for username field in admin page + - Delete tags using Rugged for performance reasons (Robert Schilling) + - Add Slack notifications when Wiki is edited (Sebastian Klier) + - Diffs load at the correct point when linking from from number + - Selected diff rows highlight + - Fix emoji categories in the emoji picker + - API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling) + - Add encrypted credentials for imported projects and migrate old ones + - Properly format all merge request references with ! rather than # !3740 (Ben Bodenmiller) + - Author and participants are displayed first on users autocompletion + - Show number sign on external issue reference text (Florent Baldino) + - Updated print style for issues + - Use GitHub Issue/PR number as iid to keep references + - Import GitHub labels + - Add option to filter by "Owned projects" on dashboard page + - Import GitHub milestones + - Execute system web hooks on push to the project + - Allow enable/disable push events for system hooks + - Fix GitHub project's link in the import page when provider has a custom URL + - Add RAW build trace output and button on build page + - Add incremental build trace update into CI API + +## 8.6.9 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + - Only show notes through JSON on confidential issues that the user has access to + +## 8.6.8 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via Git branch and tag names + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent XSS via label drop-down + - Prevent information disclosure via milestone API + - Prevent information disclosure via snippet API + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.6.7 + + - Fix persistent XSS vulnerability in `commit_person_link` helper + - Fix persistent XSS vulnerability in Label and Milestone dropdowns + - Fix vulnerability that made it possible to enumerate private projects belonging to group + +## 8.6.6 + + - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 + - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654 + - Fix revoking of authorized OAuth applications (Connor Shea). !3690 + - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) + - Issuable header is consistent between issues and merge requests + - Improved spacing in issuable header on mobile + +## 8.6.5 + + - Fix importing from GitHub Enterprise. !3529 + - Perform the language detection after updating merge requests in `GitPushService`, leading to faster visual feedback for the end-user. !3533 + - Check permissions when user attempts to import members from another project. !3535 + - Only update repository language if it is not set to improve performance. !3556 + - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583 + - Unblock user when active_directory is disabled and it can be found !3550 + - Fix a 2FA authentication spoofing vulnerability. + +## 8.6.4 + + - Don't attempt to fetch any tags from a forked repo (Stan Hu) + - Redesign the Labels page + +## 8.6.3 + + - Mentions on confidential issues doesn't create todos for non-members. !3374 + - Destroy related todos when an Issue/MR is deleted. !3376 + - Fix error 500 when target is nil on todo list. !3376 + - Fix copying uploads when moving issue to another project. !3382 + - Ensuring Merge Request API returns boolean values for work_in_progress (Abhi Rao). !3432 + - Fix raw/rendered diff producing different results on merge requests. !3450 + - Fix commit comment alignment (Stan Hu). !3466 + - Fix Error 500 when searching for a comment in a project snippet. !3468 + - Allow temporary email as notification email. !3477 + - Fix issue with dropdowns not selecting values. !3478 + - Update gitlab-shell version and doc to 2.6.12. gitlab-org/gitlab-ee!280 + +## 8.6.2 + + - Fix dropdown alignment. !3298 + - Fix issuable sidebar overlaps on tablet. !3299 + - Make dropdowns pixel perfect. !3337 + - Fix order of steps to prevent PostgreSQL errors when running migration. !3355 + - Fix bold text in issuable sidebar. !3358 + - Fix error with anonymous token in applications settings. !3362 + - Fix the milestone 'upcoming' filter. !3364 + !3368 + - Fix comments on confidential issues showing up in activity feed to non-members. !3375 + - Fix `NoMethodError` when visiting CI root path at `/ci`. !3377 + - Add a tooltip to new branch button in issue page. !3380 + - Fix an issue hiding the password form when signed-in with a linked account. !3381 + - Add links to CI setup documentation from project settings and builds pages. !3384 + - Fix an issue with width of project select dropdown. !3386 + - Remove redundant `require`s from Banzai files. !3391 + - Fix error 500 with cancel button on issuable edit form. !3392 + !3417 + - Fix background when editing a highlighted note. !3423 + - Remove tabstop from the WIP toggle links. !3426 + - Ensure private project snippets are not viewable by unauthorized people. + - Gracefully handle notes on deleted commits in merge requests (Stan Hu). !3402 + - Fixed issue with notification settings not saving. !3452 + +## 8.6.1 + + - Add option to reload the schema before restoring a database backup. !2807 + - Display navigation controls on mobile. !3214 + - Fixed bug where participants would not work correctly on merge requests. !3329 + - Fix sorting issues by votes on the groups issues page results in SQL errors. !3333 + - Restrict notifications for confidential issues. !3334 + - Do not allow to move issue if it has not been persisted. !3340 + - Add a confirmation step before deleting an issuable. !3341 + - Fixes issue with signin button overflowing on mobile. !3342 + - Auto collapses the navigation sidebar when resizing. !3343 + - Fix build dependencies, when the dependency is a string. !3344 + - Shows error messages when trying to create label in dropdown menu. !3345 + - Fixes issue with assign milestone not loading milestone list. !3346 + - Fix an issue causing the Dashboard/Milestones page to be blank. !3348 + +## 8.6.0 (2016-03-22) + + - Add ability to move issue to another project + - Prevent tokens in the import URL to be showed by the UI + - Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu) + - Add confidential issues + - Bump gitlab_git to 9.0.3 (Stan Hu) + - Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu) + - Support Golang subpackage fetching (Stan Hu) + - Bump Capybara gem to 2.6.2 (Stan Hu) + - New branch button appears on issues where applicable + - Contributions to forked projects are included in calendar + - Improve the formatting for the user page bio (Connor Shea) + - Easily (un)mark merge request as WIP using link + - Use specialized system notes when MR is (un)marked as WIP + - Removed the default password from the initial admin account created during + setup. A password can be provided during setup (see installation docs), or + GitLab will ask the user to create a new one upon first visit. + - Fix issue when pushing to projects ending in .wiki + - Properly display YAML front matter in Markdown + - Add support for wiki with UTF-8 page names (Hiroyuki Sato) + - Fix wiki search results point to raw source (Hiroyuki Sato) + - Don't load all of GitLab in mail_room + - Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner) + - HTTP error pages work independently from location and config (Artem Sidorenko) + - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set + - Memoize @group in Admin::GroupsController (Yatish Mehta) + - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) + - Added omniauth-auth0 Gem (Daniel Carraro) + - Add label description in tooltip to labels in issue index and sidebar + - Strip leading and trailing spaces in URL validator (evuez) + - Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez) + - Return empty array instead of 404 when commit has no statuses in commit status API + - Decrease the font size and the padding of the `.anchor` icons used in the README (Roberto Dip) + - Rewrite logo to simplify SVG code (Sean Lang) + - Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach) + - Ignore jobs that start with `.` (hidden jobs) + - Hide builds from project's settings when the feature is disabled + - Allow to pass name of created artifacts archive in `.gitlab-ci.yml` + - Refactor and greatly improve search performance + - Add support for cross-project label references + - Ensure "new SSH key" email do not ends up as dead Sidekiq jobs + - Update documentation to reflect Guest role not being enforced on internal projects + - Allow search for logged out users + - Allow to define on which builds the current one depends on + - Allow user subscription to a label: get notified for issues/merge requests related to that label (Timothy Andrew) + - Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio) + - Don't show Issues/MRs from archived projects in Groups view + - Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs + - Fix empty source_sha on Merge Request when there is no diff (Pierre de La Morinerie) + - Increase the notes polling timeout over time (Roberto Dip) + - Add shortcut to toggle markdown preview (Florent Baldino) + - Show labels in dashboard and group milestone views + - Fix an issue when the target branch of a MR had been deleted + - Add main language of a project in the list of projects (Tiago Botelho) + - Add #upcoming filter to Milestone filter (Tiago Botelho) + - Add ability to show archived projects on dashboard, explore and group pages + - Remove fork link closes all merge requests opened on source project (Florent Baldino) + - Move group activity to separate page + - Create external users which are excluded of internal and private projects unless access was explicitly granted + - Continue parameters are checked to ensure redirection goes to the same instance + - User deletion is now done in the background so the request can not time out + - Canceled builds are now ignored in compound build status if marked as `allowed to fail` + - Trigger a todo for mentions on commits page + - Let project owners and admins soft delete issues and merge requests + +## 8.5.13 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + +## 8.5.12 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via Git branch and tag names + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent information disclosure via snippet API + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.5.11 + + - Fix persistent XSS vulnerability in `commit_person_link` helper + +## 8.5.10 + + - Fix a 2FA authentication spoofing vulnerability. + +## 8.5.9 + + - Don't attempt to fetch any tags from a forked repo (Stan Hu). + +## 8.5.8 + + - Bump Git version requirement to 2.7.4 + +## 8.5.7 + + - Bump Git version requirement to 2.7.3 + +## 8.5.6 + + - Obtain a lease before querying LDAP + +## 8.5.5 + + - Ensure removing a project removes associated Todo entries + - Prevent a 500 error in Todos when author was removed + - Fix pagination for filtered dashboard and explore pages + - Fix "Show all" link behavior + +## 8.5.4 + + - Do not cache requests for badges (including builds badge) + +## 8.5.3 + + - Flush repository caches before renaming projects + - Sort starred projects on dashboard based on last activity by default + - Show commit message in JIRA mention comment + - Makes issue page and merge request page usable on mobile browsers. + - Improved UI for profile settings + +## 8.5.2 + + - Fix sidebar overlapping content when screen width was below 1200px + - Don't repeat labels listed on Labels tab + - Bring the "branded appearance" feature from EE to CE + - Fix error 500 when commenting on a commit + - Show days remaining instead of elapsed time for Milestone + - Fix broken icons on installations with relative URL (Artem Sidorenko) + - Fix issue where tag list wasn't refreshed after deleting a tag + - Fix import from gitlab.com (KazSawada) + - Improve implementation to check read access to forks and add pagination + - Don't show any "2FA required" message if it's not actually required + - Fix help keyboard shortcut on relative URL setups (Artem Sidorenko) + - Update Rails to 4.2.5.2 + - Fix permissions for deprecated CI build status badge + - Don't show "Welcome to GitLab" when the search didn't return any projects + - Add Todos documentation + +## 8.5.1 + + - Fix group projects styles + - Show Crowd login tab when sign in is disabled and Crowd is enabled (Peter Hudec) + - Fix a set of small UI glitches in project, profile, and wiki pages + - Restrict permissions on public/uploads + - Fix the merge request side-by-side view after loading diff results + - Fix the look of tooltip for the "Revert" button + - Add when the Builds & Runners API changes got introduced + - Fix error 500 on some merged merge requests + - Fix an issue causing the content of the issuable sidebar to disappear + - Fix error 500 when trying to mark an already done todo as "done" + - Fix an issue where MRs weren't sortable + - Issues can now be dragged & dropped into empty milestone lists. This is also + possible with MRs + - Changed padding & background color for highlighted notes + - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu) + - Update sentry-raven gem to 0.15.6 + - Add build coverage in project's builds page (Steffen Köhler) + - Changed # to ! for merge requests in activity view + +## 8.5.0 (2016-02-22) + + - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) + - Cache various Repository methods to improve performance + - Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu) + - Ensure rake tasks that don't need a DB connection can be run without one + - Update New Relic gem to 3.14.1.311 (Stan Hu) + - Add "visibility" flag to GET /projects api endpoint + - Add an option to supply root email through an environmental variable (Koichiro Mikami) + - Ignore binary files in code search to prevent Error 500 (Stan Hu) + - Render sanitized SVG images (Stan Hu) + - Support download access by PRIVATE-TOKEN header (Stan Hu) + - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push + - Add option to include the sender name in body of Notify email (Jason Lee) + - New UI for pagination + - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet + set it up + - API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger) + - Fix diff comments loaded by AJAX to load comment with diff in discussion tab + - Fix relative links in other markup formats (Ben Boeckel) + - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) + - Fix label links for a merge request pointing to issues list + - Don't vendor minified JS + - Increase project import timeout to 15 minutes + - Be more permissive with email address validation: it only has to contain a single '@' + - Display 404 error on group not found + - Track project import failure + - Support Two-factor Authentication for LDAP users + - Display database type and version in Administration dashboard + - Allow limited Markdown in Broadcast Messages + - Fix visibility level text in admin area (Zeger-Jan van de Weg) + - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg) + - Update the ExternalIssue regex pattern (Blake Hitchcock) + - Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson) + - Optimized performance of finding issues to be closed by a merge request + - Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`, `path_with_namespace` + and `default_branch` in `project` in push, issue, merge-request and note webhooks data (Kirill Zaitsev) + - Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in favor of `git_http_url` + in `project` for push, issue, merge-request and note webhooks data (Kirill Zaitsev) + - Deprecate the `repository` key in push, issue, merge-request and note webhooks data, use `project` instead (Kirill Zaitsev) + - API: Expose MergeRequest#merge_status (Andrei Dziahel) + - Revert "Add IP check against DNSBLs at account sign-up" + - Actually use the `skip_merges` option in Repository#commits (Tony Chu) + - Fix API to keep request parameters in Link header (Michael Potthoff) + - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead + - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead + - Prevent parse error when name of project ends with .atom and prevent path issues + - Discover branches for commit statuses ref-less when doing merge when succeeded + - Mark inline difference between old and new paths when a file is renamed + - Support Akismet spam checking for creation of issues via API (Stan Hu) + - API: Allow to set or update a merge-request's milestone (Kirill Skachkov) + - Improve UI consistency between projects and groups lists + - Add sort dropdown to dashboard projects page + - Fixed logo animation on Safari (Roman Rott) + - Fix Merge When Succeeded when multiple stages + - Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg) + - In seach autocomplete show only groups and projects you are member of + - Don't process cross-reference notes from forks + - Fix: init.d script not working on OS X + - Faster snippet search + - Added API to download build artifacts + - Title for milestones should be unique (Zeger-Jan van de Weg) + - Validate correctness of maximum attachment size application setting + - Replaces "Create merge request" link with one to the "Merge Request" when one exists + - Fix CI builds badge, add a new link to builds badge, deprecate the old one + - Fix broken link to project in build notification emails + - Ability to see and sort on vote count from Issues and MR lists + - Fix builds scheduler when first build in stage was allowed to fail + - User project limit is reached notice is hidden if the projects limit is zero + - Add API support for managing runners and project's runners + - Allow SAML users to login with no previous account without having to allow + all Omniauth providers to do so. + - Allow existing users to auto link their SAML credentials by logging in via SAML + - Make it possible to erase a build (trace, artifacts) using UI and API + - Ability to revert changes from a Merge Request or Commit + - Emoji comment on diffs are not award emoji + - Add label description (Nuttanart Pornprasitsakul) + - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) + - Add Todos + +## 8.4.11 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + +## 8.4.10 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via Git branch and tag names + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent information disclosure via snippet API + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.4.9 + + - Fix persistent XSS vulnerability in `commit_person_link` helper + +## 8.4.8 + + - Fix a 2FA authentication spoofing vulnerability. + +## 8.4.7 + + - Don't attempt to fetch any tags from a forked repo (Stan Hu). + +## 8.4.6 + + - Bump Git version requirement to 2.7.4 + +## 8.4.5 + + - No CE-specific changes + +## 8.4.4 + + - Update omniauth-saml gem to 1.4.2 + - Prevent long-running backup tasks from timing out the database connection + - Add a Project setting to allow guests to view build logs (defaults to true) + - Sort project milestones by due date including issue editor (Oliver Rogers / Orih) + +## 8.4.3 + + - Increase lfs_objects size column to 8-byte integer to allow files larger + than 2.1GB + - Correctly highlight MR diff when MR has merge conflicts + - Fix highlighting in blame view + - Update sentry-raven gem to prevent "Not a git repository" console output + when running certain commands + - Add instrumentation to additional Gitlab::Git and Rugged methods for + performance monitoring + - Allow autosize textareas to also be manually resized + +## 8.4.2 + + - Bump required gitlab-workhorse version to bring in a fix for missing + artifacts in the build artifacts browser + - Get rid of those ugly borders on the file tree view + - Fix updating the runner information when asking for builds + - Bump gitlab_git version to 7.2.24 in order to bring in a performance + improvement when checking if a repository was empty + - Add instrumentation for Gitlab::Git::Repository instance methods so we can + track them in Performance Monitoring. + - Increase contrast between highlighted code comments and inline diff marker + - Fix method undefined when using external commit status in builds + - Fix highlighting in blame view. + +## 8.4.1 + + - Apply security updates for Rails (4.2.5.1), rails-html-sanitizer (1.0.3), + and Nokogiri (1.6.7.2) + - Fix redirect loop during import + - Fix diff highlighting for all syntax themes + - Delete project and associations in a background worker + +## 8.4.0 (2016-01-22) + + - Allow LDAP users to change their email if it was not set by the LDAP server + - Ensure Gravatar host looks like an actual host + - Consider re-assign as a mention from a notification point of view + - Add pagination headers to already paginated API resources + - Properly generate diff of orphan commits, like the first commit in a repository + - Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages + - Autocomplete data is now always loaded, instead of when focusing a comment text area + - Improved performance of finding issues for an entire group + - Added custom application performance measuring system powered by InfluxDB + - Add syntax highlighting to diffs + - Gracefully handle invalid UTF-8 sequences in Markdown links (Stan Hu) + - Bump fog to 1.36.0 (Stan Hu) + - Add user's last used IP addresses to admin page (Stan Hu) + - Add housekeeping function to project settings page + - The default GitLab logo now acts as a loading indicator + - Fix caching issue where build status was not updating in project dashboard (Stan Hu) + - Accept 2xx status codes for successful Webhook triggers (Stan Hu) + - Fix missing date of month in network graph when commits span a month (Stan Hu) + - Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu) + - Don't notify users twice if they are both project watchers and subscribers (Stan Hu) + - Remove gray background from layout in UI + - Fix signup for OAuth providers that don't provide a name + - Implement new UI for group page + - Implement search inside emoji picker + - Let the CI runner know about builds that this build depends on + - Add API support for looking up a user by username (Stan Hu) + - Add project permissions to all project API endpoints (Stan Hu) + - Link to milestone in "Milestone changed" system note + - Only allow group/project members to mention `@all` + - Expose Git's version in the admin area (Trey Davis) + - Add "Frequently used" category to emoji picker + - Add CAS support (tduehr) + - Add link to merge request on build detail page + - Fix: Problem with projects ending with .keys (Jose Corcuera) + - Revert back upvote and downvote button to the issue and MR pages + - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) + - Add system hook messages for project rename and transfer (Steve Norman) + - Fix version check image in Safari + - Show 'All' tab by default in the builds page + - Add Open Graph and Twitter Card data to all pages + - Fix API project lookups when querying with a namespace with dots (Stan Hu) + - Enable forcing Two-factor authentication sitewide, with optional grace period + - Import GitHub Pull Requests into GitLab + - Change single user API endpoint to return more detailed data (Michael Potthoff) + - Update version check images to use SVG + - Validate README format before displaying + - Enable Microsoft Azure OAuth2 support (Janis Meybohm) + - Properly set task-list class on single item task lists + - Add file finder feature in tree view (Kyungchul Shin) + - Ajax filter by message for commits page + - API: Add support for deleting a tag via the API (Robert Schilling) + - Allow subsequent validations in CI Linter + - Show referenced MRs & Issues only when the current viewer can access them + - Fix Encoding::CompatibilityError bug when markdown content has some complex URL (Jason Lee) + - Add API support for managing project's builds + - Add API support for managing project's build triggers + - Add API support for managing project's build variables + - Allow broadcast messages to be edited + - Autosize Markdown textareas + - Import GitHub wiki into GitLab + - Add reporters ability to download and browse build artifacts (Andrew Johnson) + - Autofill referring url in message box when reporting user abuse. + - Remove leading comma on award emoji when the user is the first to award the emoji (Zeger-Jan van de Weg) + - Add build artifacts browser + - Improve UX in builds artifacts browser + - Increase default size of `data` column in `events` table when using MySQL + - Expose button to CI Lint tool on project builds page + - Fix: Creator should be added as a master of the project on creation + - Added X-GitLab-... headers to emails from CI and Email On Push services (Anton Baklanov) + - Add IP check against DNSBLs at account sign-up + - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching + +## 8.3.10 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + +## 8.3.9 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.3.8 + + - Fix persistent XSS vulnerability in `commit_person_link` helper + +## 8.3.7 + + - Fix a 2FA authentication spoofing vulnerability. + +## 8.3.6 + + - Don't attempt to fetch any tags from a forked repo (Stan Hu). + +## 8.3.5 + + - Bump Git version requirement to 2.7.4 + +## 8.3.4 + + - Use gitlab-workhorse 0.5.4 (fixes API routing bug) + +## 8.3.3 + + - Preserve CE behavior with JIRA integration by only calling API if URL is set + - Fix duplicated branch creation/deletion events when using Web UI (Stan Hu) + - Add configurable LDAP server query timeout + - Get "Merge when build succeeds" to work when commits were pushed to MR target branch while builds were running + - Suppress e-mails on failed builds if allow_failure is set (Stan Hu) + - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) + - Better support for referencing and closing issues in Asana service (Mike Wyatt) + - Enable "Add key" button when user fills in a proper key (Stan Hu) + - Fix error in processing reply-by-email messages (Jason Lee) + - Fix Error 500 when visiting build page of project with nil runners_token (Stan Hu) + - Use WOFF versions of SourceSansPro fonts + - Fix regression when builds were not generated for tags created through web/api interface + - Fix: maintain milestone filter between Open and Closed tabs (Greg Smethells) + - Fix missing artifacts and build traces for build created before 8.3 + +## 8.3.2 + + - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) + - Add support for Google reCAPTCHA in user registration + +## 8.3.1 + + - Fix Error 500 when global milestones have slashes (Stan Hu) + - Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu) + - Fix LDAP identity and user retrieval when special characters are used + - Move Sidekiq-cron configuration to gitlab.yml + +## 8.3.0 (2015-12-22) + + - Bump rack-attack to 4.3.1 for security fix (Stan Hu) + - API support for starred projects for authorized user (Zeger-Jan van de Weg) + - Add open_issues_count to project API (Stan Hu) + - Expand character set of usernames created by Omniauth (Corey Hinshaw) + - Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg) + - Add unsubscribe link in the email footer (Zeger-Jan van de Weg) + - Provide better diagnostic message upon project creation errors (Stan Hu) + - Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu) + - Remove api credentials from link to build_page + - Deprecate GitLabCiService making it to always be inactive + - Bump gollum-lib to 4.1.0 (Stan Hu) + - Fix broken group avatar upload under "New group" (Stan Hu) + - Update project repositorize size and commit count during import:repos task (Stan Hu) + - Fix API setting of 'public' attribute to false will make a project private (Stan Hu) + - Handle and report SSL errors in Webhook test (Stan Hu) + - Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu) + - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) + - WIP identifier on merge requests no longer requires trailing space + - Add rake tasks for git repository maintainance (Zeger-Jan van de Weg) + - Fix 500 error when update group member permission + - Fix: As an admin, cannot add oneself as a member to a group/project + - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) + - Recognize issue/MR/snippet/commit links as references + - Backport JIRA features from EE to CE + - Add ignore whitespace change option to commit view + - Fire update hook from GitLab + - Allow account unlock via email + - Style warning about mentioning many people in a comment + - Fix: sort milestones by due date once again (Greg Smethells) + - Migrate all CI::Services and CI::WebHooks to Services and WebHooks + - Don't show project fork event as "imported" + - Add API endpoint to fetch merge request commits list + - Don't create CI status for refs that doesn't have .gitlab-ci.yml, even if the builds are enabled + - Expose events API with comment information and author info + - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583 + - Run custom Git hooks when branch is created or deleted. + - Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch + - Add languages page to graphs + - Block LDAP user when they are no longer found in the LDAP server + - Improve wording on project visibility levels (Zeger-Jan van de Weg) + - Fix editing notes on a merge request diff + - Automatically select default clone protocol based on user preferences (Eirik Lygre) + - Make Network page as sub tab of Commits + - Add copy-to-clipboard button for Snippets + - Add indication to merge request list item that MR cannot be merged automatically + - Default target branch to patch-n when editing file in protected branch + - Add Builds tab to merge request detail page + - Allow milestones, issues and MRs to be created from dashboard and group indexes + - Use new style for wiki + - Use new style for milestone detail page + - Fix sidebar tooltips when collapsed + - Prevent possible XSS attack with award-emoji + - Upgraded Sidekiq to 4.x + - Accept COPYING,COPYING.lesser, and licence as license file (Zeger-Jan van de Weg) + - Fix emoji aliases problem + - Fix award-emojis Flash alert's width + - Fix deleting notes on a merge request diff + - Display referenced merge request statuses in the issue description (Greg Smethells) + - Implement new sidebar for issue and merge request pages + - Emoji picker improvements + - Suppress warning about missing `.gitlab-ci.yml` if builds are disabled + - Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present + - Persist runners registration token in database + - Fix online editor should not remove newlines at the end of the file + - Expose Git's version in the admin area + - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye) + +## 8.2.6 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + +## 8.2.5 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via `window.opener` + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.2.4 + + - Bump Git version requirement to 2.7.4 + +## 8.2.3 + + - Fix application settings cache not expiring after changes (Stan Hu) + - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) + - Update documentation for "Guest" permissions + - Properly convert Emoji-only comments into Award Emojis + - Enable devise paranoid mode to prevent user enumeration attack + - Webhook payload has an added, modified and removed properties for each commit + - Fix 500 error when creating a merge request that removes a submodule + +## 8.2.2 + + - Fix 404 in redirection after removing a project (Stan Hu) + - Ensure cached application settings are refreshed at startup (Stan Hu) + - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) + - Fix: Raw private snippets access workflow + - Prevent "413 Request entity too large" errors when pushing large files with LFS + - Fix invalid links within projects dashboard header + - Make current user the first user in assignee dropdown in issues detail page (Stan Hu) + - Fix: duplicate email notifications on issue comments + +## 8.2.1 + + - Forcefully update builds that didn't want to update with state machine + - Fix: saving GitLabCiService as Admin Template + +## 8.2.0 (2015-11-22) + + - Improved performance of finding projects and groups in various places + - Improved performance of rendering user profile pages and Atom feeds + - Expose build artifacts path as config option + - Fix grouping of contributors by email in graph. + - Improved performance of finding issues with/without labels + - Fix Drone CI service template not saving properly (Stan Hu) + - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) + - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) + - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) + - Improved performance of finding users by one of their Email addresses + - Add allow_failure field to commit status API (Stan Hu) + - Commits without .gitlab-ci.yml are marked as skipped + - Save detailed error when YAML syntax is invalid + - Since GitLab CI is enabled by default, remove enabling it by pushing .gitlab-ci.yml + - Added build artifacts + - Improved performance of replacing references in comments + - Show last project commit to default branch on project home page + - Highlight comment based on anchor in URL + - Adds ability to remove the forked relationship from project settings screen. (Han Loong Liauw) + - Improved performance of sorting milestone issues + - Allow users to select the Files view as default project view (Cristian Bica) + - Show "Empty Repository Page" for repository without branches (Artem V. Navrotskiy) + - Fix: Inability to reply to code comments in the MR view, if the MR comes from a fork + - Use git follow flag for commits page when retrieve history for file or directory + - Show merge request CI status on merge requests index page + - Send build name and stage in CI notification e-mail + - Extend yml syntax for only and except to support specifying repository path + - Enable shared runners to all new projects + - Bump GitLab-Workhorse to 0.4.1 + - Allow to define cache in `.gitlab-ci.yml` + - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) + - Remove deprecated CI events from project settings page + - Use issue editor as cross reference comment author when issue is edited with a new mention. + - Add graphs of commits ahead and behind default branch (Jeff Stubler) + - Improve personal snippet access workflow (Douglas Alexandre) + - [API] Add ability to fetch the commit ID of the last commit that actually touched a file + - Fix omniauth documentation setting for omnibus configuration (Jon Cairns) + - Add "New file" link to dropdown on project page + - Include commit logs in project search + - Add "added", "modified" and "removed" properties to commit object in webhook + - Rename "Back to" links to "Go to" because its not always a case it point to place user come from + - Allow groups to appear in the search results if the group owner allows it + - Add email notification to former assignee upon unassignment (Adam Lieskovský) + - New design for project graphs page + - Remove deprecated dumped yaml file generated from previous job definitions + - Show specific runners from projects where user is master or owner + - MR target branch is now visible on a list view when it is different from project's default one + - Improve Continuous Integration graphs page + - Make color of "Accept Merge Request" button consistent with current build status + - Add ignore white space option in merge request diff and commit and compare view + - Ability to add release notes (markdown text and attachments) to git tags (aka Releases) + - Relative links from a repositories README.md now link to the default branch + - Fix trailing whitespace issue in merge request/issue title + - Fix bug when milestone/label filter was empty for dashboard issues page + - Add ability to create milestone in group projects from single form + - Add option to create merge request when editing/creating a file (Dirceu Tiegs) + - Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez) + - Add Award Emoji to issue and merge request pages + +## 8.1.4 + + - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) + - Prevent redirect loop when home_page_url is set to the root URL + - Fix incoming email config defaults + - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) + +## 8.1.3 + + - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) + - Spread out runner contacted_at updates + - Use issue editor as cross reference comment author when issue is edited with a new mention + - Add Facebook authentication + +## 8.1.2 + + - Fix cloning Wiki repositories via HTTP (Stan Hu) + - Add migration to remove satellites directory + - Fix specific runners visibility + - Fix 500 when editing CI service + - Require CI jobs to be named + - Fix CSS for runner status + - Fix CI badge + - Allow developer to manage builds + +## 8.1.1 + + - Removed, see 8.1.2 + +## 8.1.0 (2015-10-22) + + - Ensure MySQL CI limits DB migrations occur after the fields have been created (Stan Hu) + - Fix duplicate repositories in GitHub import page (Stan Hu) + - Redirect to a default path if HTTP_REFERER is not set (Stan Hu) + - Adds ability to create directories using the web editor (Ben Ford) + - Cleanup stuck CI builds + - Send an email to admin email when a user is reported for spam (Jonathan Rochkind) + - Show notifications button when user is member of group rather than project (Grzegorz Bizon) + - Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge. + - Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu) + - Don't show "Add README" link in an empty repository if user doesn't have access to push (Stan Hu) + - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) + - Speed up load times of issue detail pages by roughly 1.5x + - Fix CI rendering regressions + - If a merge request is to close an issue, show this on the issue page (Zeger-Jan van de Weg) + - Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu) + - Make diff file view easier to use on mobile screens (Stan Hu) + - Improved performance of finding users by username or Email address + - Fix bug where merge request comments created by API would not trigger notifications (Stan Hu) + - Add support for creating directories from Files page (Stan Hu) + - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) + - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu) + - Improved performance of the trending projects page + - Remove CI migration task + - Improved performance of finding projects by their namespace + - Add assignee data to Issuables' hook_data (Bram Daams) + - Fix bug where transferring a project would result in stale commit links (Stan Hu) + - Fix build trace updating + - Include full path of source and target branch names in New Merge Request page (Stan Hu) + - Add user preference to view activities as default dashboard (Stan Hu) + - Add option to admin area to sign in as a specific user (Pavel Forkert) + - Show CI status on all pages where commits list is rendered + - Automatically enable CI when push .gitlab-ci.yml file to repository + - Move CI charts to project graphs area + - Fix cases where Markdown did not render links in activity feed (Stan Hu) + - Add first and last to pagination (Zeger-Jan van de Weg) + - Added Commit Status API + - Added Builds View + - Added when to .gitlab-ci.yml + - Show CI status on commit page + - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds + - Show CI status on Your projects page and Starred projects page + - Remove "Continuous Integration" page from dashboard + - Add notes and SSL verification entries to hook APIs (Ben Boeckel) + - Fix grammar in admin area "labels" .nothing-here-block when no labels exist. + - Move CI runners page to project settings area + - Move CI variables page to project settings area + - Move CI triggers page to project settings area + - Move CI project settings page to CE project settings area + - Fix bug when removed file was not appearing in merge request diff + - Show warning when build cannot be served by any of the available CI runners + - Note the original location of a moved project when notifying users of the move + - Improve error message when merging fails + - Add support of multibyte characters in LDAP UID (Roman Petrov) + - Show additions/deletions stats on merge request diff + - Remove footer text in emails (Zeger-Jan van de Weg) + - Ensure code blocks are properly highlighted after a note is updated + - Fix wrong access level badge on MR comments + - Hide password in the service settings form + - Move CI webhooks page to project settings area + - Fix User Identities API. It now allows you to properly create or update user's identities. + - Add user preference to change layout width (Peter Göbel) + - Use commit status in merge request widget as preferred source of CI status + - Integrate CI commit and build pages into project pages + - Move CI services page to project settings area + - Add "Quick Submit" behavior to input fields throughout the application. Use + Cmd+Enter on Mac and Ctrl+Enter on Windows/Linux. + - Fix position of hamburger in header for smaller screens (Han Loong Liauw) + - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) + - Persist filters when sorting on admin user page (Jerry Lukins) + - Update style of snippets pages (Han Loong Liauw) + - Allow dashboard and group issues/MRs to be filtered by label + - Add spellcheck=false to certain input fields + - Invalidate stored service password if the endpoint URL is changed + - Project names are not fully shown if group name is too big, even on group page view + - Apply new design for Files page + - Add "New Page" button to Wiki Pages tab (Stan Hu) + - Only render 404 page from /public + - Hide passwords from services API (Alex Lossent) + - Fix: Images cannot show when projects' path was changed + - Let gitlab-git-http-server generate and serve 'git archive' downloads + - Optimize query when filtering on issuables (Zeger-Jan van de Weg) + - Fix padding of outdated discussion item. + - Animate the logo on hover + +## 8.0.5 + + - Correct lookup-by-email for LDAP logins + - Fix loading spinner sometimes not being hidden on Merge Request tab switches + +## 8.0.4 + + - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) + - Fix referrals for :back and relative URL installs + - Fix anchors to comments in diffs + - Remove CI token from build traces + - Fix "Assign All" button on Runner admin page + - Fix search in Files + - Add full project namespace to payload of system webhooks (Ricardo Band) + +## 8.0.3 + + - Fix URL shown in Slack notifications + - Fix bug where projects would appear to be stuck in the forked import state (Stan Hu) + - Fix Error 500 in creating merge requests with > 1000 diffs (Stan Hu) + - Add work_in_progress key to MR webhooks (Ben Boeckel) + +## 8.0.2 + + - Fix default avatar not rendering in network graph (Stan Hu) + - Skip check_initd_configured_correctly on omnibus installs + - Prevent double-prefixing of help page paths + - Clarify confirmation text on user deletion + - Make commit graphs responsive to window width changes (Stan Hu) + - Fix top margin for sign-in button on public pages + - Fix LDAP attribute mapping + - Remove git refs used internally by GitLab from network graph (Stan Hu) + - Use standard Markdown font in Markdown preview instead of fixed-width font (Stan Hu) + - Fix Reply by email for non-UTF-8 messages. + - Add option to use StartTLS with Reply by email IMAP server. + - Allow AWS S3 Server-Side Encryption with Amazon S3-Managed Keys for backups (Paul Beattie) + +## 8.0.1 + + - Improve CI migration procedure and documentation + +## 8.0.0 (2015-09-22) + + - Fix Markdown links not showing up in dashboard activity feed (Stan Hu) + - Remove milestones from merge requests when milestones are deleted (Stan Hu) + - Fix HTML link that was improperly escaped in new user e-mail (Stan Hu) + - Fix broken sort in merge request API (Stan Hu) + - Bump rouge to 1.10.1 to remove warning noise and fix other syntax highlighting bugs (Stan Hu) + - Gracefully handle errors in syntax highlighting by leaving the block unformatted (Stan Hu) + - Add "replace" and "upload" functionalities to allow user replace existing file and upload new file into current repository + - Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu) + - Fix emoji URLs in Markdown when relative_url_root is used (Stan Hu) + - Omit filename in Content-Disposition header in raw file download to avoid RFC 6266 encoding issues (Stan HU) + - Fix broken Wiki Page History (Stan Hu) + - Import forked repositories asynchronously to prevent large repositories from timing out (Stan Hu) + - Prevent anchors from being hidden by header (Stan Hu) + - Fix bug where only the first 15 Bitbucket issues would be imported (Stan Hu) + - Sort issues by creation date in Bitbucket importer (Stan Hu) + - Prevent too many redirects upon login when home page URL is set to external_url (Stan Hu) + - Improve dropdown positioning on the project home page (Hannes Rosenögger) + - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu) + - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) + - Restrict users API endpoints to use integer IDs (Stan Hu) + - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) + - Remove satellites + - Better performance for web editor (switched from satellites to rugged) + - Faster merge + - Ability to fetch merge requests from refs/merge-requests/:id + - Allow displaying of archived projects in the admin interface (Artem Sidorenko) + - Allow configuration of import sources for new projects (Artem Sidorenko) + - Search for comments should be case insensetive + - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) + - Ability to search milestones + - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu) + - Move dashboard activity to separate page (for your projects and starred projects) + - Improve performance of git blame + - Limit content width to 1200px for most of pages to improve readability on big screens + - Fix 500 error when submit project snippet without body + - Improve search page usability + - Bring more UI consistency in way how projects, snippets and groups lists are rendered + - Make all profiles and group public + - Fixed login failure when extern_uid changes (Joel Koglin) + - Don't notify users without access to the project when they are (accidentally) mentioned in a note. + - Retrieving oauth token with LDAP credentials + - Load Application settings from running database unless env var USE_DB=false + - Added Drone CI integration (Kirill Zaitsev) + - Allow developers to retry builds + - Hide advanced project options for non-admin users + - Fail builds if no .gitlab-ci.yml is found + - Refactored service API and added automatically service docs generator (Kirill Zaitsev) + - Added web_url key project hook_attrs (Kirill Zaitsev) + - Add ability to get user information by ID of an SSH key via the API + - Fix bug which IE cannot show image at markdown when the image is raw file of gitlab + - Add support for Crowd + - Global Labels that are available to all projects + - Fix highlighting of deleted lines in diffs. + - Project notification level can be set on the project page itself + - Added service API endpoint to retrieve service parameters (Petheő Bence) + - Add FogBugz project import (Jared Szechy) + - Sort users autocomplete lists by user (Allister Antosik) + - Webhook for issue now contains repository field (Jungkook Park) + - Add ability to add custom text to the help page (Jeroen van Baarsen) + - Add pg_schema to backup config + - Fix references to target project issues in Merge Requests markdown preview and textareas (Francesco Levorato) + - Redirect from incorrectly cased group or project path to correct one (Francesco Levorato) + - Removed API calls from CE to CI + +## 7.14.3 through 0.8.0 + +- See [changelogs/archive.md](changelogs/archive.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0cdcb54b0ae..b4635e50c28 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -247,7 +247,7 @@ request is as follows: 1. Fork the project into your personal space on GitLab.com 1. Create a feature branch, branch away from `master` 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code -1. Add your changes to the [CHANGELOG](CHANGELOG): +1. Add your changes to the [CHANGELOG.md](CHANGELOG.md): 1. If you are fixing a ~regression issue, you can add your entry to the next patch release (e.g. `8.12.5` if current version is `8.12.4`) 1. Otherwise, add your entry to the next minor release (e.g. `8.13.0` if diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index 7c0eb90d540..2215f37b81a 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -228,7 +228,7 @@ We'll discuss the three reasons to merge in master: leveraging code, merge confl If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit. If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this. You can prevent some merge conflicts by using [gitattributes](http://git-scm.com/docs/gitattributes) for files that can be in a random order. -For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG merge=union` so that there are fewer merge conflicts in it. +For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG.md merge=union` so that there are fewer merge conflicts in it. The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project. Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI). At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit. diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index fb4d8463981..7c4e8276902 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -10,11 +10,11 @@ then exit 1 fi -# Ensure that the CHANGELOG does not contain duplicate versions -DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^v [0-9.]+' CHANGELOG | sed 's| (unreleased)||' | sort | uniq -d) +# Ensure that the CHANGELOG.md does not contain duplicate versions +DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^## .+' CHANGELOG.md | sed -E 's| \(.+\)||' | sort -r | uniq -d) if [ "${DUPLICATE_CHANGELOG_VERSIONS}" != "" ] then - echo '✖ ERROR: Duplicate versions in CHANGELOG:' >&2 + echo '✖ ERROR: Duplicate versions in CHANGELOG.md:' >&2 echo "${DUPLICATE_CHANGELOG_VERSIONS}" >&2 exit 1 fi -- cgit v1.2.1 From d78c667d581dca6b75895f70f0ae6ebdd0f5d815 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Oct 2016 16:47:55 -0700 Subject: Fix broken Spinach tests caused by changes in !6550 Partial fix to #23378 --- features/steps/project/commits/commits.rb | 20 ++++++++++++++------ spec/features/compare_spec.rb | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index b8264f97687..fd7b1debd68 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -42,15 +42,16 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I fill compare fields with branches' do - fill_in 'from', with: 'feature' - fill_in 'to', with: 'master' + select_using_dropdown('from', 'feature') + select_using_dropdown('to', 'master') click_button 'Compare' end step 'I fill compare fields with refs' do - fill_in "from", with: sample_commit.parent_id - fill_in "to", with: sample_commit.id + select_using_dropdown('from', sample_commit.parent_id) + select_using_dropdown('to', sample_commit.id) + click_button "Compare" end @@ -97,8 +98,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I fill compare fields with branches' do - fill_in 'from', with: 'master' - fill_in 'to', with: 'feature' + select_using_dropdown('from', 'master') + select_using_dropdown('to', 'feature') click_button 'Compare' end @@ -182,4 +183,11 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(page).to have_content "More submodules" expect(page).not_to have_content "Change some files" end + + def select_using_dropdown(dropdown_type, selection) + dropdown = find(".js-compare-#{dropdown_type}-dropdown") + dropdown.find(".compare-dropdown-toggle").click + dropdown.fill_in("Filter by branch/tag", with: selection) + find_link(selection, visible: true).click + end end diff --git a/spec/features/compare_spec.rb b/spec/features/compare_spec.rb index 33dfd0d5b62..c22109d19b6 100644 --- a/spec/features/compare_spec.rb +++ b/spec/features/compare_spec.rb @@ -45,6 +45,6 @@ describe "Compare", js: true do dropdown = find(".js-compare-#{dropdown_type}-dropdown") dropdown.find(".compare-dropdown-toggle").click dropdown.fill_in("Filter by branch/tag", with: selection) - click_link selection + find_link(selection, visible: true).click end end -- cgit v1.2.1 From 93e464f454ec997743048e34db5c848b4146d452 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 16 Oct 2016 03:30:31 +0100 Subject: Added logic to handle a revision input that does not exist in the menu --- app/assets/javascripts/compare_autocomplete.js | 52 ------------------ app/assets/javascripts/compare_autocomplete.js.es6 | 63 ++++++++++++++++++++++ app/views/projects/compare/_ref_dropdown.html.haml | 4 +- features/steps/project/commits/commits.rb | 14 +++-- 4 files changed, 74 insertions(+), 59 deletions(-) delete mode 100644 app/assets/javascripts/compare_autocomplete.js create mode 100644 app/assets/javascripts/compare_autocomplete.js.es6 diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js deleted file mode 100644 index 294d2c9052c..00000000000 --- a/app/assets/javascripts/compare_autocomplete.js +++ /dev/null @@ -1,52 +0,0 @@ -(function() { - this.CompareAutocomplete = (function() { - function CompareAutocomplete() { - this.initDropdown(); - } - - CompareAutocomplete.prototype.initDropdown = function() { - return $('.js-compare-dropdown').each(function() { - var $dropdown, selected; - $dropdown = $(this); - selected = $dropdown.data('selected'); - return $dropdown.glDropdown({ - data: function(term, callback) { - return $.ajax({ - url: $dropdown.data('refs-url'), - data: { - ref: $dropdown.data('ref') - } - }).done(function(refs) { - return callback(refs); - }); - }, - selectable: true, - filterable: true, - filterByText: true, - toggleLabel: true, - fieldName: $dropdown.data('field-name'), - filterInput: 'input[type="search"]', - renderRow: function(ref) { - var link; - if (ref.header != null) { - return $('
    • ').addClass('dropdown-header').text(ref.header); - } else { - link = $('').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); - return $('
    • ').append(link); - } - }, - id: function(obj, $el) { - return $el.attr('data-ref'); - }, - toggleLabel: function(obj, $el) { - return $el.text().trim(); - } - }); - }); - }; - - return CompareAutocomplete; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/compare_autocomplete.js.es6 b/app/assets/javascripts/compare_autocomplete.js.es6 new file mode 100644 index 00000000000..9a2082d97e0 --- /dev/null +++ b/app/assets/javascripts/compare_autocomplete.js.es6 @@ -0,0 +1,63 @@ +(function() { + this.CompareAutocomplete = (function() { + function CompareAutocomplete() { + this.initDropdown(); + } + + CompareAutocomplete.prototype.initDropdown = function() { + return $('.js-compare-dropdown').each(function() { + var $dropdown, selected; + $dropdown = $(this); + selected = $dropdown.data('selected'); + const $dropdownContainer = $dropdown.closest('.dropdown'); + const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer); + const $filterInput = $('input[type="search"]', $dropdownContainer); + $dropdown.glDropdown({ + data: function(term, callback) { + return $.ajax({ + url: $dropdown.data('refs-url'), + data: { + ref: $dropdown.data('ref') + } + }).done(function(refs) { + return callback(refs); + }); + }, + selectable: true, + filterable: true, + filterByText: true, + toggleLabel: true, + fieldName: $dropdown.data('field-name'), + filterInput: 'input[type="search"]', + renderRow: function(ref) { + var link; + if (ref.header != null) { + return $('
    • ').addClass('dropdown-header').text(ref.header); + } else { + link = $('').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); + return $('
    • ').append(link); + } + }, + id: function(obj, $el) { + return $el.attr('data-ref'); + }, + toggleLabel: function(obj, $el) { + return $el.text().trim(); + } + }); + $filterInput.on('keyup', (e) => { + const keyCode = e.keyCode || e.which; + if (keyCode !== 13) return; + const text = $filterInput.val(); + $fieldInput.val(text); + $('.dropdown-toggle-text', $dropdown).text(text); + $dropdownContainer.removeClass('open'); + }); + }); + }; + + return CompareAutocomplete; + + })(); + +}).call(this); diff --git a/app/views/projects/compare/_ref_dropdown.html.haml b/app/views/projects/compare/_ref_dropdown.html.haml index 27d928c87a0..05fb37cdc0f 100644 --- a/app/views/projects/compare/_ref_dropdown.html.haml +++ b/app/views/projects/compare/_ref_dropdown.html.haml @@ -1,5 +1,5 @@ .dropdown-menu.dropdown-menu-selectable - = dropdown_title "Select branch/tag" - = dropdown_filter "Filter by branch/tag" + = dropdown_title "Select Git revision" + = dropdown_filter "Filter by Git revision" = dropdown_content = dropdown_loading diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index fd7b1debd68..b08912de25f 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -49,8 +49,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I fill compare fields with refs' do - select_using_dropdown('from', sample_commit.parent_id) - select_using_dropdown('to', sample_commit.id) + select_using_dropdown('from', sample_commit.parent_id, true) + select_using_dropdown('to', sample_commit.id, true) click_button "Compare" end @@ -184,10 +184,14 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(page).not_to have_content "Change some files" end - def select_using_dropdown(dropdown_type, selection) + def select_using_dropdown(dropdown_type, selection, is_commit = false) dropdown = find(".js-compare-#{dropdown_type}-dropdown") dropdown.find(".compare-dropdown-toggle").click - dropdown.fill_in("Filter by branch/tag", with: selection) - find_link(selection, visible: true).click + dropdown.fill_in("Filter by Git revision", with: selection) + if is_commit + dropdown.find('input[type="search"]').send_keys(:return) + else + find_link(selection, visible: true).click + end end end -- cgit v1.2.1 From 913c1cd87aee7450fa0f8b6571302cff9b5f1dc1 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 16 Oct 2016 21:09:38 -0700 Subject: Fix broken rspec in compare text !6910 changed the filter text from "Filter by branch/tag" to "Filter by Git revision" --- spec/features/compare_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/compare_spec.rb b/spec/features/compare_spec.rb index c22109d19b6..43eb4000e58 100644 --- a/spec/features/compare_spec.rb +++ b/spec/features/compare_spec.rb @@ -44,7 +44,7 @@ describe "Compare", js: true do def select_using_dropdown(dropdown_type, selection) dropdown = find(".js-compare-#{dropdown_type}-dropdown") dropdown.find(".compare-dropdown-toggle").click - dropdown.fill_in("Filter by branch/tag", with: selection) + dropdown.fill_in("Filter by Git revision", with: selection) find_link(selection, visible: true).click end end -- cgit v1.2.1 From 9c6c5c79f8d3a555ded0539e06a922bc058d5c20 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 19:08:48 +0200 Subject: Add Pipeline metrics worker --- CHANGELOG.md | 1 + app/models/ci/pipeline.rb | 10 ++++++---- app/workers/pipeline_metrics_worker.rb | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 app/workers/pipeline_metrics_worker.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f718fc88a..06269b79f14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Change user & group landing page routing from /u/:username to /:username - Prevent running GfmAutocomplete setup for each diff note !6569 - Added documentation for .gitattributes files + - Move Pipeline Metrics to separate worker - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4fdb5fef4fb..c7b9d6cc223 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -49,6 +49,10 @@ module Ci transition any => :canceled end + # IMPORTANT + # Do not add any operations to this state_machine + # Create a separate worker for each new operation + before_transition [:created, :pending] => :running do |pipeline| pipeline.started_at = Time.now end @@ -62,13 +66,11 @@ module Ci end after_transition [:created, :pending] => :running do |pipeline| - MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). - update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) + pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) } end after_transition any => [:success] do |pipeline| - MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). - update_all(latest_build_finished_at: pipeline.finished_at) + pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) } end after_transition [:created, :pending, :running] => :success do |pipeline| diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb new file mode 100644 index 00000000000..3d1cd770515 --- /dev/null +++ b/app/workers/pipeline_metrics_worker.rb @@ -0,0 +1,15 @@ +class PipelineMetricsWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| + merge_requests = pipeline.merge_requests.map(&:id) + + metrics = MergeRequest::Metrics.where(merge_request_id: merge_requests) + metrics.update_all(latest_build_started_at: pipeline.started_at) if pipeline.active? + metrics.update_all(latest_build_finished_at: pipeline.finished_at) if pipeline.success? + end + end +end -- cgit v1.2.1 From 0ea7f32afe9ff806f912a25efb4e967ee5da1e10 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Oct 2016 15:45:39 +0800 Subject: Make cancelled pipelines being able to retry Closes #23326 --- app/models/ci/pipeline.rb | 2 +- spec/models/ci/pipeline_spec.rb | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4fdb5fef4fb..a78b33988be 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -152,7 +152,7 @@ module Ci def retryable? builds.latest.any? do |build| - build.failed? && build.retryable? + (build.failed? || build.canceled?) && build.retryable? end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 550a890797e..4e0ce10603d 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -88,24 +88,38 @@ describe Ci::Pipeline, models: true do context 'no failed builds' do before do - FactoryGirl.create :ci_build, name: "rspec", pipeline: pipeline, status: 'success' + create_build('rspec', 'success') end - it 'be not retryable' do + it 'is not retryable' do is_expected.to be_falsey end + + context 'one canceled job' do + before do + create_build('rubocop', 'canceled') + end + + it 'is retryable' do + is_expected.to be_truthy + end + end end context 'with failed builds' do before do - FactoryGirl.create :ci_build, name: "rspec", pipeline: pipeline, status: 'running' - FactoryGirl.create :ci_build, name: "rubocop", pipeline: pipeline, status: 'failed' + create_build('rspec', 'running') + create_build('rubocop', 'failed') end - it 'be retryable' do + it 'is retryable' do is_expected.to be_truthy end end + + def create_build(name, status) + create(:ci_build, name: name, status: status, pipeline: pipeline) + end end describe '#stages' do -- cgit v1.2.1 From 88328c5729182bf6289b5f34ed3eb2a23098df25 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 17 Oct 2016 16:24:21 +0800 Subject: Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f718fc88a..0f3f020f44b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) + - Cancelled pipelines could be retried. !6927 - Updating verbiage on git basics to be more intuitive - Clarify documentation for Runners API (Gennady Trafimenkov) - The instrumentation for Banzai::Renderer has been restored -- cgit v1.2.1 From a497803072edb4c8edbf9f4daf3832c122ee50e2 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 09:55:47 +0200 Subject: Improve spec for pipeline metrics worker --- app/workers/pipeline_metrics_worker.rb | 25 ++++++++++++--- spec/models/ci/pipeline_spec.rb | 27 ++++++---------- spec/workers/pipeline_metrics_worker_spec.rb | 46 ++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 spec/workers/pipeline_metrics_worker_spec.rb diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb index 3d1cd770515..7bb92df3bbd 100644 --- a/app/workers/pipeline_metrics_worker.rb +++ b/app/workers/pipeline_metrics_worker.rb @@ -5,11 +5,26 @@ class PipelineMetricsWorker def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| - merge_requests = pipeline.merge_requests.map(&:id) - - metrics = MergeRequest::Metrics.where(merge_request_id: merge_requests) - metrics.update_all(latest_build_started_at: pipeline.started_at) if pipeline.active? - metrics.update_all(latest_build_finished_at: pipeline.finished_at) if pipeline.success? + update_metrics_for_active_pipeline(pipeline) if pipeline.active? + update_metrics_for_succeeded_pipeline(pipeline) if pipeline.success? end end + + private + + def update_metrics_for_active_pipeline(pipeline) + metrics(pipeline).update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) + end + + def update_metrics_for_succeeded_pipeline(pipeline) + metrics(pipeline).update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: pipeline.finished_at) + end + + def metrics(pipeline) + MergeRequest::Metrics.where(merge_request_id: merge_requests(pipeline)) + end + + def merge_requests(pipeline) + pipeline.merge_requests.map(&:id) + end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 550a890797e..163c0b5c516 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -187,33 +187,24 @@ describe Ci::Pipeline, models: true do end end - describe "merge request metrics" do + describe 'merge request metrics' do let(:project) { FactoryGirl.create :project } let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } - context 'when transitioning to running' do - it 'records the build start time' do - time = Time.now - Timecop.freeze(time) { build.run } - - expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(time) - end - - it 'clears the build end time' do - build.run + before do + expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id) + end - expect(merge_request.reload.metrics.latest_build_finished_at).to be_nil + context 'when transitioning to running' do + it 'schedules metrics workers' do + pipeline.run end end context 'when transitioning to success' do - it 'records the build end time' do - build.run - time = Time.now - Timecop.freeze(time) { build.success } - - expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(time) + it 'schedules metrics workers' do + pipeline.succeed end end end diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb new file mode 100644 index 00000000000..f58df3f0d6e --- /dev/null +++ b/spec/workers/pipeline_metrics_worker_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe PipelineMetricsWorker do + let(:project) { create(:project) } + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } + + let(:pipeline) do + create(:ci_empty_pipeline, + status: status, + project: project, + ref: 'master', + sha: project.repository.commit('master').id, + started_at: 1.hour.ago, + finished_at: Time.now) + end + + describe '#perform' do + subject { described_class.new.perform(pipeline.id) } + + context 'when pipeline is running' do + let(:status) { 'running' } + + it 'records the build start time' do + subject + + expect(merge_request.reload.metrics.latest_build_started_at).to eq(pipeline.started_at) + end + + it 'clears the build end time' do + subject + + expect(merge_request.reload.metrics.latest_build_finished_at).to be_nil + end + end + + context 'when pipeline succeeded' do + let(:status) { 'success' } + + it 'records the build end time' do + subject + + expect(merge_request.reload.metrics.latest_build_finished_at).to eq(pipeline.finished_at) + end + end + end +end -- cgit v1.2.1 From db9c03bf4426346c631a6cd366a84626ead8d6e3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 11:33:24 +0200 Subject: Add environment stop action [ci skip] --- app/controllers/projects/environments_controller.rb | 6 +++++- app/controllers/projects/merge_requests_controller.rb | 1 + app/models/ci/build.rb | 10 ---------- config/routes/project.rb | 6 +++++- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 4fe8c3a1889..2ec316a1ebd 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -2,7 +2,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_update_environment!, only: [:edit, :update, :destroy] + before_action :authorize_update_environment!, only: [:edit, :update, :stop, :destroy] before_action :environment, only: [:show, :edit, :update, :destroy] def index @@ -44,6 +44,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController end end + def stop + + end + def destroy if @environment.destroy flash[:notice] = 'Environment was successfully removed.' diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9207c954335..1c1938f957b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -416,6 +416,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController id: environment.id, name: environment.name, url: namespace_project_environment_path(project.namespace, project, environment), + stop_url: (stop_namespace_project_environment_path(project.namespace, project, environment) if environment.closeable?), external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, deployed_at: deployment.try(:created_at), diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index fd762b8c5ce..6f3e83976e7 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -125,16 +125,6 @@ module Ci end end - def play_type - return nil unless playable? - - if close_environment? - :close - else - :play - end - end - def retryable? project.builds_enabled? && commands.present? && complete? end diff --git a/config/routes/project.rb b/config/routes/project.rb index 2cd8c60794a..d73f76cd091 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -318,7 +318,11 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end end - resources :environments + resources :environments do + member do + post :stop + end + end resource :cycle_analytics, only: [:show] -- cgit v1.2.1 From 89bb889c4541a477aff4202fd6dad23ad3fe38d6 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 11 Oct 2016 10:18:36 +0200 Subject: Stop directly parsing due_date with Date.parse, prefer parsing implicitly. --- app/assets/javascripts/due_date_select.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index bf68b7e3a9b..0fcf0823d4e 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -43,7 +43,7 @@ // Create the post date value = $("input[name='" + fieldName + "']").val(); if (value !== '') { - date = new Date(value.replace(new RegExp('-', 'g'), ',')); + date = new Date(value); mediumDate = $.datepicker.formatDate('M d, yy', date); } else { mediumDate = 'No due date'; @@ -64,7 +64,7 @@ $selectbox.hide(); } $value.css('display', ''); - cssClass = Date.parse(mediumDate) ? 'bold' : 'no-value'; + cssClass = new Date(mediumDate) ? 'bold' : 'no-value'; $valueContent.html("" + mediumDate + ""); $sidebarValue.html(mediumDate); if (value !== '') { -- cgit v1.2.1 From 039ccc169a4114a53acb16c0b031f704748c3cae Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 11 Oct 2016 10:19:18 +0200 Subject: Convert due_date_select.js filetype to es6. --- CHANGELOG.md | 1 + app/assets/javascripts/dispatcher.js.es6 | 2 +- app/assets/javascripts/due_date_select.js | 107 ----------------- app/assets/javascripts/due_date_select.js.es6 | 161 ++++++++++++++++++++++++++ app/views/shared/issuable/_sidebar.html.haml | 2 +- 5 files changed, 164 insertions(+), 109 deletions(-) delete mode 100644 app/assets/javascripts/due_date_select.js create mode 100644 app/assets/javascripts/due_date_select.js.es6 diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f718fc88a..671bb01c003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Grouped pipeline dropdown is a scrollable container - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fixes padding in all clipboard icons that have .btn class + - Fix due date being displayed as NaN in Safari - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index f3957ed374b..5be35cf4b41 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -50,7 +50,7 @@ case 'projects:milestones:new': case 'projects:milestones:edit': new ZenMode(); - new DueDateSelect(); + new gl.DueDateSelectors(); new GLForm($('.milestone-form')); break; case 'groups:milestones:new': diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js deleted file mode 100644 index 0fcf0823d4e..00000000000 --- a/app/assets/javascripts/due_date_select.js +++ /dev/null @@ -1,107 +0,0 @@ -(function() { - this.DueDateSelect = (function() { - function DueDateSelect() { - var $datePicker, $dueDate, $loading; - // Milestone edit/new form - $datePicker = $('.datepicker'); - if ($datePicker.length) { - $dueDate = $('#milestone_due_date'); - $datePicker.datepicker({ - dateFormat: 'yy-mm-dd', - onSelect: function(dateText, inst) { - return $dueDate.val(dateText); - } - }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val())); - } - $('.js-clear-due-date').on('click', function(e) { - e.preventDefault(); - return $.datepicker._clearDate($datePicker); - }); - // Issuable sidebar - $loading = $('.js-issuable-update .due_date').find('.block-loading').hide(); - $('.js-due-date-select').each(function(i, dropdown) { - var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL; - $dropdown = $(dropdown); - $dropdownParent = $dropdown.closest('.dropdown'); - $datePicker = $dropdownParent.find('.js-due-date-calendar'); - $block = $dropdown.closest('.block'); - $selectbox = $dropdown.closest('.selectbox'); - $value = $block.find('.value'); - $valueContent = $block.find('.value-content'); - $sidebarValue = $('.js-due-date-sidebar-value', $block); - fieldName = $dropdown.data('field-name'); - abilityName = $dropdown.data('ability-name'); - issueUpdateURL = $dropdown.data('issue-update'); - $dropdown.glDropdown({ - hidden: function() { - $selectbox.hide(); - return $value.css('display', ''); - } - }); - addDueDate = function(isDropdown) { - var data, date, mediumDate, value; - // Create the post date - value = $("input[name='" + fieldName + "']").val(); - if (value !== '') { - date = new Date(value); - mediumDate = $.datepicker.formatDate('M d, yy', date); - } else { - mediumDate = 'No due date'; - } - data = {}; - data[abilityName] = {}; - data[abilityName].due_date = value; - return $.ajax({ - type: 'PUT', - url: issueUpdateURL, - data: data, - dataType: 'json', - beforeSend: function() { - var cssClass; - $loading.fadeIn(); - if (isDropdown) { - $dropdown.trigger('loading.gl.dropdown'); - $selectbox.hide(); - } - $value.css('display', ''); - cssClass = new Date(mediumDate) ? 'bold' : 'no-value'; - $valueContent.html("" + mediumDate + ""); - $sidebarValue.html(mediumDate); - if (value !== '') { - return $('.js-remove-due-date-holder').removeClass('hidden'); - } else { - return $('.js-remove-due-date-holder').addClass('hidden'); - } - } - }).done(function(data) { - if (isDropdown) { - $dropdown.trigger('loaded.gl.dropdown'); - $dropdown.dropdown('toggle'); - } - return $loading.fadeOut(); - }); - }; - $block.on('click', '.js-remove-due-date', function(e) { - e.preventDefault(); - $("input[name='" + fieldName + "']").val(''); - return addDueDate(false); - }); - return $datePicker.datepicker({ - dateFormat: 'yy-mm-dd', - defaultDate: $("input[name='" + fieldName + "']").val(), - altField: "input[name='" + fieldName + "']", - onSelect: function() { - return addDueDate(true); - } - }); - }); - $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', function(e) { - return e.stopImmediatePropagation(); - }); - } - - return DueDateSelect; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6 new file mode 100644 index 00000000000..41925fcc8e3 --- /dev/null +++ b/app/assets/javascripts/due_date_select.js.es6 @@ -0,0 +1,161 @@ +(function(global) { + class DueDateSelect { + constructor({ $dropdown, $loading } = {}) { + const $dropdownParent = $dropdown.closest('.dropdown'); + const $block = $dropdown.closest('.block'); + this.$loading = $loading; + this.$dropdown = $dropdown; + this.$dropdownParent = $dropdownParent; + this.$datePicker = $dropdownParent.find('.js-due-date-calendar'); + this.$block = $block; + this.$selectbox = $dropdown.closest('.selectbox'); + this.$value = $block.find('.value'); + this.$valueContent = $block.find('.value-content'); + this.$sidebarValue = $('.js-due-date-sidebar-value', $block); + this.fieldName = $dropdown.data('field-name'), + this.abilityName = $dropdown.data('ability-name'), + this.issueUpdateURL = $dropdown.data('issue-update') + + this.rawSelectedDate = null; + this.displayedDate = null; + this.datePayload = null; + + this.initGlDropdown(); + this.initRemoveDueDate(); + this.initDatePicker(); + this.initStopPropagation(); + } + + initGlDropdown() { + this.$dropdown.glDropdown({ + hidden: () => { + this.$selectbox.hide(); + this.$value.css('display', ''); + } + }); + } + + initDatePicker() { + this.$datePicker.datepicker({ + dateFormat: 'yy-mm-dd', + defaultDate: $("input[name='" + this.fieldName + "']").val(), + altField: "input[name='" + this.fieldName + "']", + onSelect: () => { + return this.saveDueDate(true); + } + }); + } + + initRemoveDueDate() { + this.$block.on('click', '.js-remove-due-date', (e) => { + e.preventDefault(); + $("input[name='" + this.fieldName + "']").val(''); + return this.saveDueDate(false); + }); + } + + initStopPropagation() { + $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', (e) => { + return e.stopImmediatePropagation(); + }); + } + + saveDueDate(isDropdown) { + this.parseSelectedDate(); + this.prepSelectedDate(); + this.submitSelectedDate(isDropdown); + } + + parseSelectedDate() { + this.rawSelectedDate = $("input[name='" + this.fieldName + "']").val(); + if (this.rawSelectedDate.length) { + let dateObj = new Date(this.rawSelectedDate); + this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj); + } else { + this.displayedDate = 'No due date'; + } + } + + prepSelectedDate() { + const datePayload = {}; + datePayload[this.abilityName] = {}; + datePayload[this.abilityName].due_date = this.rawSelectedDate; + this.datePayload = datePayload; + } + + submitSelectedDate(isDropdown) { + return $.ajax({ + type: 'PUT', + url: this.issueUpdateURL, + data: this.datePayload, + dataType: 'json', + beforeSend: () => { + const selectedDateValue = this.datePayload[this.abilityName].due_date; + const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value'; + + this.$loading.fadeIn(); + + if (isDropdown) { + this.$dropdown.trigger('loading.gl.dropdown'); + this.$selectbox.hide(); + } + + this.$value.css('display', ''); + this.$valueContent.html(`${this.displayedDate}`); + this.$sidebarValue.html(this.displayedDate); + + return selectedDateValue.length ? + $('.js-remove-due-date-holder').removeClass('hidden') : + $('.js-remove-due-date-holder').addClass('hidden'); + + } + }).done((data) => { + if (isDropdown) { + this.$dropdown.trigger('loaded.gl.dropdown'); + this.$dropdown.dropdown('toggle'); + } + return this.$loading.fadeOut(); + }); + } + } + + class DueDateSelectors { + constructor() { + this.initMilestoneDueDate(); + this.initIssuableSelect(); + } + + initMilestoneDueDate() { + const $datePicker = $('.datepicker'); + + if ($datePicker.length) { + const $dueDate = $('#milestone_due_date'); + $datePicker.datepicker({ + dateFormat: 'yy-mm-dd', + onSelect: (dateText, inst) => { + $dueDate.val(dateText); + } + }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val())); + } + $('.js-clear-due-date').on('click', (e) => { + e.preventDefault(); + $.datepicker._clearDate($datePicker); + }); + } + + initIssuableSelect() { + const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide(); + + $('.js-due-date-select').each((i, dropdown) => { + const $dropdown = $(dropdown); + new DueDateSelect({ + $dropdown, + $loading + }); + }); + } + } + + global.DueDateSelectors = DueDateSelectors; + +})(window.gl || (window.gl = {})); diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index f8059988038..ba9f0c27661 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -171,5 +171,5 @@ new LabelsSelect(); new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}'); new Subscription('.subscription') - new DueDateSelect(); + new gl.DueDateSelectors(); sidebar = new Sidebar(); -- cgit v1.2.1 From aa76fa55f7ab2dfd1ecc3df5737d6b5f48ed5759 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 16 Oct 2016 18:38:35 +0100 Subject: Remove carriage returns from commit description as summary is on a newline and will always include carriage returns --- features/steps/project/commits/commits.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index b08912de25f..c2a15c1a19a 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -21,7 +21,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(response_headers['Content-Type']).to have_content("application/atom+xml") expect(body).to have_selector("title", text: "#{@project.name}:master commits") expect(body).to have_selector("author email", text: commit.author_email) - expect(body).to have_selector("entry summary", text: commit.description[0..10]) + expect(body).to have_selector("entry summary", text: commit.description[0..10].gsub("\r", "")) end step 'I click on tag link' do -- cgit v1.2.1 From 7df7a92b0a4a5d9731086177f0cb3f618578d122 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 17 Oct 2016 11:57:33 +0300 Subject: [Great spinach fix] Replace gsub with delete --- features/steps/project/commits/commits.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index c2a15c1a19a..244306e8464 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -21,7 +21,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(response_headers['Content-Type']).to have_content("application/atom+xml") expect(body).to have_selector("title", text: "#{@project.name}:master commits") expect(body).to have_selector("author email", text: commit.author_email) - expect(body).to have_selector("entry summary", text: commit.description[0..10].gsub("\r", "")) + expect(body).to have_selector("entry summary", text: commit.description[0..10].delete("\r")) end step 'I click on tag link' do -- cgit v1.2.1 From 92d12fab66e66358e8fb9204870053e4bab66b71 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 11:44:08 +0100 Subject: Updates MR template to include stop link --- app/assets/javascripts/merge_request_widget.js.es6 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index fcadc4bc515..fb55d13a223 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -17,6 +17,12 @@ View on <%- external_url_formatted %> + + + + Stop environment + + `; @@ -205,6 +211,11 @@ if ($(`.mr-state-widget #${ environment.id }`).length) return; const $template = $(DEPLOYMENT_TEMPLATE); if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); + + if (!environment.stop_url) { + $('.js-close-env-link', $template).remove(); + } + if (environment.deployed_at && environment.deployed_at_formatted) { environment.deployed_at = $.timeago(environment.deployed_at) + '.'; } else { -- cgit v1.2.1 From 6cdbb27ec3cf72ce6728986909aa3df54b7a26c6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:45:31 +0200 Subject: Refactor code to use available and stopped statuses and refactor views to use separate renders --- .../projects/environments_controller.rb | 8 ++-- .../projects/merge_requests_controller.rb | 2 +- app/models/ci/build.rb | 8 ---- app/models/deployment.rb | 15 ++++--- app/models/environment.rb | 20 +++++----- app/models/project.rb | 2 +- app/services/create_deployment_service.rb | 14 +++---- app/views/projects/deployments/_actions.haml | 46 +++++++--------------- .../projects/deployments/_deployment.html.haml | 4 +- app/views/projects/deployments/_rollback.haml | 6 +++ .../projects/environments/_environment.html.haml | 6 ++- .../projects/environments/_external_url.html.haml | 3 ++ app/views/projects/environments/_stop.html.haml | 5 +++ app/views/projects/environments/index.html.haml | 10 ++--- app/views/projects/environments/show.html.haml | 10 +++-- db/fixtures/development/14_pipelines.rb | 4 +- .../20161006104309_add_state_to_environment.rb | 2 +- .../20161017095000_add_properties_to_deployment.rb | 29 ++++++++++++++ db/schema.rb | 11 +----- 19 files changed, 111 insertions(+), 94 deletions(-) create mode 100644 app/views/projects/deployments/_rollback.haml create mode 100644 app/views/projects/environments/_external_url.html.haml create mode 100644 app/views/projects/environments/_stop.html.haml create mode 100644 db/migrate/20161017095000_add_properties_to_deployment.rb diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 2ec316a1ebd..40da5be2e49 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -10,8 +10,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController @all_environments = project.environments @environments = case @scope - when 'closed' then @all_environments.closed - else @all_environments.opened + when 'stopped' then @all_environments.stopped + else @all_environments.available end end @@ -45,7 +45,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def stop - + action = @environment.stop_action + new_action = action.active? ? action : action.play(current_user) + redirect_to [project.namespace.become(Namespace), project, new_action] end def destroy diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 1c1938f957b..86e12660c23 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -416,7 +416,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController id: environment.id, name: environment.name, url: namespace_project_environment_path(project.namespace, project, environment), - stop_url: (stop_namespace_project_environment_path(project.namespace, project, environment) if environment.closeable?), + stop_url: (stop_namespace_project_environment_path(project.namespace, project, environment) if environment.stoppable?), external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, deployed_at: deployment.try(:created_at), diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6f3e83976e7..87475119b23 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -106,14 +106,6 @@ module Ci project.builds_enabled? && commands.present? && manual? && skipped? end - def close_environment? - options.fetch(:environment, {}).fetch(:close, false) - end - - def closes_environment?(name) - environment == name && close_environment? - end - def play(current_user = nil) # Try to queue a current build if self.enqueue diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 054d54f124e..18d9e96301c 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -11,6 +11,8 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true + store :properties, accessors: [:on_stop] + after_save :create_ref def commit @@ -34,7 +36,7 @@ class Deployment < ActiveRecord::Base end def manual_actions - deployable.try(:other_actions) + @manual_actions ||= deployable.try(:other_actions) end def includes_commit?(commit) @@ -84,17 +86,14 @@ class Deployment < ActiveRecord::Base take end - def close_action + def stop_action return nil unless manual_actions - @close_action ||= - manual_actions.find do |manual_action| - manual_action.try(:closes_environment?, deployable.environment) - end + @stop_action ||= manual_actions.find_by(name: on_stop) end - def closeable? - close_action.present? + def stoppable? + on_stop.present? && stop_action.present? end def formatted_deployment_time diff --git a/app/models/environment.rb b/app/models/environment.rb index 07f14a7ad8d..93e7dedd6f8 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -19,22 +19,22 @@ class Environment < ActiveRecord::Base allow_nil: true, addressable_url: true - delegate :closeable?, :close_action, to: :last_deployment, allow_nil: true + delegate :stoppable?, :stop_action, to: :last_deployment, allow_nil: true - scope :opened, -> { where(state: [:opened]) } - scope :closed, -> { where(state: [:closed]) } + scope :available, -> { where(state: [:available]) } + scope :stopped, -> { where(state: [:stopped]) } - state_machine :state, initial: :opened do - event :close do - transition opened: :closed + state_machine :state, initial: :available do + event :start do + transition stopped: :available end - event :reopen do - transition closed: :opened + event :stop do + transition available: :stopped end - state :opened - state :closed + state :available + state :stopped end def last_deployment diff --git a/app/models/project.rb b/app/models/project.rb index 2e8073733d4..197edabe821 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1292,7 +1292,7 @@ class Project < ActiveRecord::Base environment_ids.where(ref: ref) end - environments.opened.where(id: environment_ids).select do |environment| + environments.available.where(id: environment_ids).select do |environment| environment.includes_commit?(commit) end end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 923de58b244..47c740addb0 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -8,12 +8,12 @@ class CreateDeploymentService < BaseService @deployable = deployable @environment = prepare_environment - if close? - @environment.close + if stop? + @environment.stop return end - @environment.reopen + @environment.start deploy.tap do |deployment| deployment.update_merge_request_metrics! @@ -61,10 +61,6 @@ class CreateDeploymentService < BaseService options[:url] end - def close? - options[:close] - end - def options params[:options] || {} end @@ -72,4 +68,8 @@ class CreateDeploymentService < BaseService def variables params[:variables] || [] end + + def stop? + params[:options].fetch(:stop, false) + end end diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 387ff018073..58a214bdbd1 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -1,33 +1,15 @@ -- if can?(current_user, :create_deployment, deployment) && deployment.deployable - .pull-right - - - external_url = deployment.environment.external_url - - if external_url - = link_to external_url, target: '_blank', class: 'btn external-url' do - = icon('external-link') - - - actions = deployment.manual_actions - - if actions.present? - .inline - .dropdown - %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} - = custom_icon('icon_play') - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right - - actions.each do |action| - %li - = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do - = custom_icon('icon_play') - %span= action.name.humanize +- if can?(current_user, :create_deployment, deployment) + - actions = deployment.manual_actions + - if actions.present? + .inline + .dropdown + %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} + = custom_icon('icon_play') + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right + - actions.each do |action| + %li + = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do + = custom_icon('icon_play') + %span= action.name.humanize - - if local_assigns.fetch(:allow_close, false) && deployment.closeable? - .inline - = link_to [:play, @project.namespace.becomes(Namespace), @project, deployment.close_action], method: :post, class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do - = icon('stop', class: 'close-env-icon') - - - if local_assigns.fetch(:allow_rollback, false) - = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn' do - - if deployment.last? - Re-deploy - - else - Rollback diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index ca0005abd0c..9238f232c7e 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -17,4 +17,6 @@ #{time_ago_with_tooltip(deployment.created_at)} %td.hidden-xs - = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true + .pull-right + = render 'projects/deployments/actions', deployment: deployment + = render 'projects/deployments/rollback', deployment: deployment diff --git a/app/views/projects/deployments/_rollback.haml b/app/views/projects/deployments/_rollback.haml new file mode 100644 index 00000000000..5941e01c6f1 --- /dev/null +++ b/app/views/projects/deployments/_rollback.haml @@ -0,0 +1,6 @@ +- if can?(current_user, :create_deployment, deployment) && deployment.deployable + = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do + - if deployment.last? + Re-deploy + - else + Rollback diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 19c536897bd..b75d5df4150 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -28,4 +28,8 @@ #{time_ago_with_tooltip(last_deployment.created_at)} %td.hidden-xs - = render 'projects/deployments/actions', deployment: last_deployment, allow_close: environment.opened? + .pull-right + = render 'projects/environments/external_url', environment: environment + = render 'projects/deployments/actions', deployment: last_deployment + = render 'projects/environments/stop', environment: environment + = render 'projects/deployments/rollback', deployment: last_deployment diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml new file mode 100644 index 00000000000..6255e4baea0 --- /dev/null +++ b/app/views/projects/environments/_external_url.html.haml @@ -0,0 +1,3 @@ +- if environment.external_url + = link_to environment.external_url, target: '_blank', class: 'btn external-url' do + = icon('external-link') diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml new file mode 100644 index 00000000000..6ed6aee141b --- /dev/null +++ b/app/views/projects/environments/_stop.html.haml @@ -0,0 +1,5 @@ +- if environment.available? && environment.stoppable? + .inline + = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, + class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + = icon('stop', class: 'close-env-icon') diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index f0b64a8775b..70185176222 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -8,14 +8,14 @@ %li{class: ('active' if @scope.nil?)} = link_to project_environments_path(@project) do Available - %span.badge.js-avaibale-environments-count - = number_with_delimiter(@all_environments.opened.count) + %span.badge.js-available-environments-count + = number_with_delimiter(@all_environments.available.count) - %li{class: ('active' if @scope == 'closed')} - = link_to project_environments_path(@project, scope: :closed) do + %li{class: ('active' if @scope == 'stopped')} + = link_to project_environments_path(@project, scope: :stopped) do Stopped %span.badge.js-stopped-environments-count - = number_with_delimiter(@all_environments.closed.count) + = number_with_delimiter(@all_environments.stopped.count) .nav-controls - if can?(current_user, :create_environment, @project) && !@all_environments.blank? diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index c4209d499ad..3b4d0395db0 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -2,18 +2,20 @@ - page_title "Environments" = render "projects/pipelines/head" -- last_deployment = @environment.last_deployment - %div{ class: container_class } .top-area .col-md-9 %h3.page-title= @environment.name.capitalize .col-md-3 .nav-controls + - if can?(current_user, :read_environmnet, @environment) + = render 'projects/environments/external_url', environment: @environment + - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - - if @environment.opened? && last_deployment.try(:close_action) - = link_to 'Close', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + - if @environment.available? && @environment.stoppable? + = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete .deployments-container - if @deployments.blank? diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index d3fabe111a1..08ad3097d34 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -16,8 +16,8 @@ class Gitlab::Seeder::Pipelines { name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending }, { name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running }, { name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled }, - { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success }, - { name: 'close staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped, options: { environment: { close: true } } }, + { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, options: { environment: { on_stop: 'stop staging' } } }, + { name: 'stop staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped }, { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped }, { name: 'slack', stage: 'notify', when: 'manual', status: :created }, ] diff --git a/db/migrate/20161006104309_add_state_to_environment.rb b/db/migrate/20161006104309_add_state_to_environment.rb index 83dff07069f..ccb546654f9 100644 --- a/db/migrate/20161006104309_add_state_to_environment.rb +++ b/db/migrate/20161006104309_add_state_to_environment.rb @@ -6,7 +6,7 @@ class AddStateToEnvironment < ActiveRecord::Migration DOWNTIME = false def up - add_column_with_default(:environments, :state, :string, default: :opened) + add_column_with_default(:environments, :state, :string, default: :available) end def down diff --git a/db/migrate/20161017095000_add_properties_to_deployment.rb b/db/migrate/20161017095000_add_properties_to_deployment.rb new file mode 100644 index 00000000000..6371166a4d2 --- /dev/null +++ b/db/migrate/20161017095000_add_properties_to_deployment.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddPropertiesToDeployment < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :deployments, :properties, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index ba2cf686fa9..b574f4d4a6a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -398,22 +398,13 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "environments", force: :cascade do |t| -<<<<<<< HEAD - t.integer "project_id" - t.string "name", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.string "external_url" - t.string "environment_type" - t.string "state", default: "opened", null: false -======= t.integer "project_id" t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" t.string "external_url" t.string "environment_type" ->>>>>>> origin/master + t.string "state", default: "available", null: false end add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree -- cgit v1.2.1 From 5f98d059396dc8c0faab4defd0414049c909b6c1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:46:00 +0200 Subject: Add `action` and `on_stop` to `environment` in .gitlab-ci.yml --- app/services/create_deployment_service.rb | 22 +++++++++------------- lib/gitlab/ci/config/node/environment.rb | 14 +++++++++++--- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 47c740addb0..2e859a30cac 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -6,14 +6,12 @@ class CreateDeploymentService < BaseService ActiveRecord::Base.transaction do @deployable = deployable - @environment = prepare_environment + @environment = environment + @environment.external_url = expanded_url if expanded_url + @environment.state_event = action + @environment.save - if stop? - @environment.stop - return - end - - @environment.start + return if @environment.stopped? deploy.tap do |deployment| deployment.update_merge_request_metrics! @@ -37,10 +35,8 @@ class CreateDeploymentService < BaseService deployable: @deployable) end - def prepare_environment - project.environments.find_or_create_by(name: expanded_name) do |environment| - environment.external_url = expanded_url - end + def environment + @environment ||= project.environments.find_or_create_by(name: expanded_name) end def expanded_name @@ -69,7 +65,7 @@ class CreateDeploymentService < BaseService params[:variables] || [] end - def stop? - params[:options].fetch(:stop, false) + def action + params[:options].fetch(:action, 'start') end end diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index daa115f9017..1c1d07843b1 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -8,7 +8,7 @@ module Gitlab class Environment < Entry include Validatable - ALLOWED_KEYS = %i[name url close] + ALLOWED_KEYS = %i[name url action on_stop] validations do validate do @@ -36,7 +36,11 @@ module Gitlab addressable_url: true, allow_nil: true - validates :close, boolean: true, allow_nil: true + validates :action, + inclusion: { in: %w[start stop], message: 'should be start or stop, ' }, + allow_nil: true + + validates :on_stop, string: true, allow_nil: true end end @@ -56,9 +60,13 @@ module Gitlab value[:url] end + def action + value[:action] || 'start' + end + def value case @config - when String then { name: @config } + when String then { name: @config, action: 'start' } when Hash then @config else {} end -- cgit v1.2.1 From 021263916c3c1560a461ab0d519a309a6f918800 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:48:19 +0200 Subject: Save `on_stop` in deployment --- app/services/create_deployment_service.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 2e859a30cac..5e6745cdd4e 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -32,7 +32,8 @@ class CreateDeploymentService < BaseService tag: params[:tag], sha: params[:sha], user: current_user, - deployable: @deployable) + deployable: @deployable, + on_stop: options.fetch(:on_stop, nil)) end def environment -- cgit v1.2.1 From a8cc76e2a82ed225b246a31d45d41d5ca52a529e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 11:48:56 +0100 Subject: Renames button to show 'Stop' instead of 'Close' --- app/views/projects/environments/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index c4209d499ad..6507768ddc5 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -13,7 +13,7 @@ - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - if @environment.opened? && last_deployment.try(:close_action) - = link_to 'Close', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + = link_to 'Stop', [:play, @project.namespace.becomes(Namespace), @project, last_deployment.close_action], data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post .deployments-container - if @deployments.blank? -- cgit v1.2.1 From 53d79469b0dd2b3c054dabe685231747352aee9f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:51:37 +0200 Subject: Update `db/schema.rb` --- db/schema.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index b574f4d4a6a..f568a5ec699 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161007133303) do +ActiveRecord::Schema.define(version: 20161017095000) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -380,6 +380,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.string "deployable_type" t.datetime "created_at" t.datetime "updated_at" + t.text "properties" end add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree -- cgit v1.2.1 From 77bbd9857fefde0d2a89acd23463732240585f23 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 17 Oct 2016 13:59:59 +0300 Subject: Fix randomly crashing spinach test for merge request Signed-off-by: Dmitriy Zaporozhets --- features/steps/project/merge_requests.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index de065dffbc2..44346d99f44 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -512,6 +512,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see new target branch changes' do expect(page).to have_content 'Request to merge fix into feature' expect(page).to have_content 'Target branch changed from merge-test to feature' + wait_for_ajax end step 'I click on "Email Patches"' do -- cgit v1.2.1 From 7762d8f5a350c92295fc11cdffa703b3cb498974 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 17 Oct 2016 14:10:04 +0300 Subject: Fix Test Env (proper error handling when gitlab-shell is not clonned) --- spec/support/test_env.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index ad8ae763f6d..725299031d0 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -98,7 +98,9 @@ module TestEnv def setup_gitlab_shell unless File.directory?(Gitlab.config.gitlab_shell.path) - `rake gitlab:shell:install` + unless system('rake', 'gitlab:shell:install') + raise 'Can`t clone gitlab-shell' + end end end -- cgit v1.2.1 From 009da2fc80924778ba61c6363b6e12d7daf1fa75 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 14:31:43 +0200 Subject: Fix broken specs on MySQL after https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6896 --- spec/workers/pipeline_metrics_worker_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb index f58df3f0d6e..232478c9735 100644 --- a/spec/workers/pipeline_metrics_worker_spec.rb +++ b/spec/workers/pipeline_metrics_worker_spec.rb @@ -23,7 +23,7 @@ describe PipelineMetricsWorker do it 'records the build start time' do subject - expect(merge_request.reload.metrics.latest_build_started_at).to eq(pipeline.started_at) + expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(pipeline.started_at) end it 'clears the build end time' do @@ -39,7 +39,7 @@ describe PipelineMetricsWorker do it 'records the build end time' do subject - expect(merge_request.reload.metrics.latest_build_finished_at).to eq(pipeline.finished_at) + expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(pipeline.finished_at) end end end -- cgit v1.2.1 From 18bb0a5696cd701d3a77c059fabb5d2f798f3a83 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 15:34:30 +0200 Subject: Add on_stop column [ci skip] --- app/models/deployment.rb | 2 -- .../20161017095000_add_properties_to_deployment.rb | 22 +--------------------- db/schema.rb | 2 +- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 18d9e96301c..f6cccae4334 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -11,8 +11,6 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true - store :properties, accessors: [:on_stop] - after_save :create_ref def commit diff --git a/db/migrate/20161017095000_add_properties_to_deployment.rb b/db/migrate/20161017095000_add_properties_to_deployment.rb index 6371166a4d2..f620ee0de1c 100644 --- a/db/migrate/20161017095000_add_properties_to_deployment.rb +++ b/db/migrate/20161017095000_add_properties_to_deployment.rb @@ -1,29 +1,9 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - class AddPropertiesToDeployment < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers - # Set this constant to true if this migration requires downtime. DOWNTIME = false - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - def change - add_column :deployments, :properties, :text + add_column :deployments, :on_stop, :string end end diff --git a/db/schema.rb b/db/schema.rb index f568a5ec699..d4e35626e9b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -380,7 +380,7 @@ ActiveRecord::Schema.define(version: 20161017095000) do t.string "deployable_type" t.datetime "created_at" t.datetime "updated_at" - t.text "properties" + t.string "on_stop" end add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree -- cgit v1.2.1 From 50d3cc2b677dac855a6270f5ffed7085df8edcf8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 15:40:18 +0200 Subject: Remove destroy from environments [ci skip] --- app/controllers/projects/environments_controller.rb | 16 +++------------- config/routes/project.rb | 2 +- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 40da5be2e49..efdfbd24cae 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -2,8 +2,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_update_environment!, only: [:edit, :update, :stop, :destroy] - before_action :environment, only: [:show, :edit, :update, :destroy] + before_action :authorize_update_environment!, only: [:edit, :update, :stop] + before_action :environment, only: [:show, :edit, :update, :stop] def index @scope = params[:scope] @@ -47,17 +47,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController def stop action = @environment.stop_action new_action = action.active? ? action : action.play(current_user) - redirect_to [project.namespace.become(Namespace), project, new_action] - end - - def destroy - if @environment.destroy - flash[:notice] = 'Environment was successfully removed.' - else - flash[:alert] = 'Failed to remove environment.' - end - - redirect_to namespace_project_environments_path(project.namespace, project) + redirect_to [project.namespace.becomes(Namespace), project, new_action] end private diff --git a/config/routes/project.rb b/config/routes/project.rb index d73f76cd091..5e01d9a9e8f 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -318,7 +318,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end end - resources :environments do + resources :environments, except: [:destroy] do member do post :stop end -- cgit v1.2.1 From bfb20200e9d1e7edd82a27d18d849ffba043845a Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 17 Oct 2016 13:40:02 +0100 Subject: Add a be_like_time matcher and use it in specs The amount of precision times have in databases is variable, so we need tolerances when comparing in specs. It's better to have the tolerance defined in one place than several. --- spec/models/issue/metrics_spec.rb | 8 ++++---- spec/models/merge_request/metrics_spec.rb | 2 +- spec/requests/api/issues_spec.rb | 4 ++-- spec/requests/api/notes_spec.rb | 2 +- spec/services/create_deployment_service_spec.rb | 6 +++--- spec/services/git_push_service_spec.rb | 2 +- spec/support/matchers/be_like_time.rb | 13 +++++++++++++ spec/workers/pipeline_metrics_worker_spec.rb | 4 ++-- 8 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 spec/support/matchers/be_like_time.rb diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index e170b087ebc..2459a49f095 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -13,7 +13,7 @@ describe Issue::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.first_associated_with_milestone_at).to be_within(1.second).of(time) + expect(metrics.first_associated_with_milestone_at).to be_like_time(time) end it "does not record the second time an issue is associated with a milestone" do @@ -24,7 +24,7 @@ describe Issue::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.first_associated_with_milestone_at).to be_within(1.second).of(time) + expect(metrics.first_associated_with_milestone_at).to be_like_time(time) end end @@ -36,7 +36,7 @@ describe Issue::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.first_added_to_board_at).to be_within(1.second).of(time) + expect(metrics.first_added_to_board_at).to be_like_time(time) end it "does not record the second time an issue is associated with a list label" do @@ -48,7 +48,7 @@ describe Issue::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.first_added_to_board_at).to be_within(1.second).of(time) + expect(metrics.first_added_to_board_at).to be_like_time(time) end end end diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb index a79dd215d41..255db41cb19 100644 --- a/spec/models/merge_request/metrics_spec.rb +++ b/spec/models/merge_request/metrics_spec.rb @@ -12,7 +12,7 @@ describe MergeRequest::Metrics, models: true do metrics = subject.metrics expect(metrics).to be_present - expect(metrics.merged_at).to be_within(1.second).of(time) + expect(metrics.merged_at).to be_like_time(time) end end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index f840778ae9b..beed53d1e5c 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -694,7 +694,7 @@ describe API::API, api: true do title: 'new issue', labels: 'label, label2', created_at: creation_time expect(response).to have_http_status(201) - expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) + expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) end end end @@ -895,7 +895,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response['labels']).to include 'label3' - expect(Time.parse(json_response['updated_at'])).to be_within(1.second).of(update_time) + expect(Time.parse(json_response['updated_at'])).to be_like_time(update_time) end end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 063a8706e76..d58bedc3bf7 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -217,7 +217,7 @@ describe API::API, api: true do expect(response).to have_http_status(201) expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) - expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) + expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 5fe56e7725f..0b84c7262c3 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -201,7 +201,7 @@ describe CreateDeploymentService, services: true do time = Time.now Timecop.freeze(time) { service.execute } - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time) + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time) end it "doesn't set the time if the deploy's environment is not 'production'" do @@ -227,13 +227,13 @@ describe CreateDeploymentService, services: true do time = Time.now Timecop.freeze(time) { service.execute } - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time) + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time) # Current deploy service = described_class.new(project, user, params) Timecop.freeze(time + 12.hours) { service.execute } - expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_within(1.second).of(time) + expect(merge_request.reload.metrics.first_deployed_to_production_at).to be_like_time(time) end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index dd2a9e9903a..8dda34c7a03 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -364,7 +364,7 @@ describe GitPushService, services: true do it 'sets the metric for referenced issues' do execute_service(project, user, @oldrev, @newrev, @ref) - expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_within(1.second).of(commit_time) + expect(issue.reload.metrics.first_mentioned_in_commit_at).to be_like_time(commit_time) end it 'does not set the metric for non-referenced issues' do diff --git a/spec/support/matchers/be_like_time.rb b/spec/support/matchers/be_like_time.rb new file mode 100644 index 00000000000..1f27390eab7 --- /dev/null +++ b/spec/support/matchers/be_like_time.rb @@ -0,0 +1,13 @@ +RSpec::Matchers.define :be_like_time do |expected| + match do |actual| + expect(actual).to be_within(1.second).of(expected) + end + + description do + "within one second of #{expected}" + end + + failure_message do |actual| + "expected #{actual} to be within one second of #{expected}" + end +end diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb index 232478c9735..2c9e7c2cd02 100644 --- a/spec/workers/pipeline_metrics_worker_spec.rb +++ b/spec/workers/pipeline_metrics_worker_spec.rb @@ -23,7 +23,7 @@ describe PipelineMetricsWorker do it 'records the build start time' do subject - expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(pipeline.started_at) + expect(merge_request.reload.metrics.latest_build_started_at).to be_like_time(pipeline.started_at) end it 'clears the build end time' do @@ -39,7 +39,7 @@ describe PipelineMetricsWorker do it 'records the build end time' do subject - expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(pipeline.finished_at) + expect(merge_request.reload.metrics.latest_build_finished_at).to be_like_time(pipeline.finished_at) end end end -- cgit v1.2.1 From 409fee3300e387a781008155b6a4682098fb3d68 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 14:51:33 +0100 Subject: Updates close to say stop in confirm message --- app/assets/javascripts/merge_request_widget.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index fb55d13a223..639859ab96f 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -18,7 +18,7 @@ - + Stop environment -- cgit v1.2.1 From f7f6e0c07f6066d1c7d5d7d467e5200453e2a233 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 14:54:27 +0100 Subject: Adds entry to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38765b347d0..15699bcf273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,6 +120,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling + - Delete dynamic environments (aka Review Apps) - Make guests unable to view MRs on private projects ## 8.12.7 -- cgit v1.2.1 From b8fd2dddb29d0b3d2a117c7234e19ce25ced168c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 15:08:01 +0100 Subject: Changes after review --- app/views/projects/environments/_stop.html.haml | 2 +- app/views/projects/environments/index.html.haml | 6 +++--- app/views/projects/environments/show.html.haml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml index 6ed6aee141b..ccda591590a 100644 --- a/app/views/projects/environments/_stop.html.haml +++ b/app/views/projects/environments/_stop.html.haml @@ -1,5 +1,5 @@ - if environment.available? && environment.stoppable? .inline = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, - class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do = icon('stop', class: 'close-env-icon') diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 70185176222..eeac6e3be1a 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -16,9 +16,9 @@ Stopped %span.badge.js-stopped-environments-count = number_with_delimiter(@all_environments.stopped.count) - - .nav-controls - - if can?(current_user, :create_environment, @project) && !@all_environments.blank? + + - if can?(current_user, :create_environment, @project) && !@all_environments.blank? + .nav-controls = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 3b4d0395db0..d765ddb97ce 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -14,7 +14,7 @@ - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - if @environment.available? && @environment.stoppable? - = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post + = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete .deployments-container -- cgit v1.2.1 From 9b790f1cf97157240178601c62d2e557a404503e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 16:13:19 +0200 Subject: Improve after code review --- app/assets/javascripts/merge_request_widget.js.es6 | 2 +- .../projects/environments_controller.rb | 11 ++-- app/models/deployment.rb | 3 +- app/models/environment.rb | 9 ++-- app/services/create_deployment_service.rb | 5 +- app/views/projects/environments/_stop.html.haml | 4 +- app/views/projects/environments/index.html.haml | 4 +- app/views/projects/environments/show.html.haml | 5 +- lib/gitlab/ci/config/node/environment.rb | 4 +- spec/lib/gitlab/ci/config/node/environment_spec.rb | 62 ++++++++++++++++++++++ 10 files changed, 86 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index fb55d13a223..639859ab96f 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -18,7 +18,7 @@ - + Stop environment diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index efdfbd24cae..86bc17a720a 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -8,11 +8,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController def index @scope = params[:scope] @all_environments = project.environments - @environments = - case @scope - when 'stopped' then @all_environments.stopped - else @all_environments.available - end + @environments = @scope == 'stopped' ? + @all_environments.stopped : @all_environments.available end def show @@ -45,9 +42,11 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def stop + return render_404 unless @environment.stoppable? + action = @environment.stop_action new_action = action.active? ? action : action.play(current_user) - redirect_to [project.namespace.becomes(Namespace), project, new_action] + redirect_to polymorphic_path([project.namespace.becomes(Namespace), project, new_action]) end private diff --git a/app/models/deployment.rb b/app/models/deployment.rb index f6cccae4334..1f8c5fb3d85 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -85,13 +85,14 @@ class Deployment < ActiveRecord::Base end def stop_action + return nil unless on_stop.present? return nil unless manual_actions @stop_action ||= manual_actions.find_by(name: on_stop) end def stoppable? - on_stop.present? && stop_action.present? + stop_action.present? end def formatted_deployment_time diff --git a/app/models/environment.rb b/app/models/environment.rb index 93e7dedd6f8..20da71ccb3f 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -19,10 +19,7 @@ class Environment < ActiveRecord::Base allow_nil: true, addressable_url: true - delegate :stoppable?, :stop_action, to: :last_deployment, allow_nil: true - - scope :available, -> { where(state: [:available]) } - scope :stopped, -> { where(state: [:stopped]) } + delegate :stop_action, to: :last_deployment, allow_nil: true state_machine :state, initial: :available do event :start do @@ -84,4 +81,8 @@ class Environment < ActiveRecord::Base external_url.gsub(/\A.*?:\/\//, '') end + + def stoppable? + available? && stop_action.present? + end end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 5e6745cdd4e..85c0bf72074 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -6,10 +6,11 @@ class CreateDeploymentService < BaseService ActiveRecord::Base.transaction do @deployable = deployable + @environment = environment @environment.external_url = expanded_url if expanded_url @environment.state_event = action - @environment.save + @environment.save! return if @environment.stopped? @@ -33,7 +34,7 @@ class CreateDeploymentService < BaseService sha: params[:sha], user: current_user, deployable: @deployable, - on_stop: options.fetch(:on_stop, nil)) + on_stop: options[:on_stop]) end def environment diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml index 6ed6aee141b..c7dec086890 100644 --- a/app/views/projects/environments/_stop.html.haml +++ b/app/views/projects/environments/_stop.html.haml @@ -1,5 +1,5 @@ -- if environment.available? && environment.stoppable? +- if environment.stoppable? .inline = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, - class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do + class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do = icon('stop', class: 'close-env-icon') diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 70185176222..705a1360ec5 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -17,8 +17,8 @@ %span.badge.js-stopped-environments-count = number_with_delimiter(@all_environments.stopped.count) - .nav-controls - - if can?(current_user, :create_environment, @project) && !@all_environments.blank? + - if can?(current_user, :create_environment, @project) && !@all_environments.blank? + .nav-controls = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 3b4d0395db0..b6a1a7fc89e 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -13,9 +13,8 @@ - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - - if @environment.available? && @environment.stoppable? - = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete + - if @environment.stoppable? + = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post .deployments-container - if @deployments.blank? diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index 1c1d07843b1..b392f272bd6 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -37,10 +37,10 @@ module Gitlab allow_nil: true validates :action, - inclusion: { in: %w[start stop], message: 'should be start or stop, ' }, + inclusion: { in: %w[start stop], message: 'should be start or stop' }, allow_nil: true - validates :on_stop, string: true, allow_nil: true + validates :on_stop, type: String, allow_nil: true end end diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb index df453223da7..430e18a816f 100644 --- a/spec/lib/gitlab/ci/config/node/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb @@ -87,6 +87,68 @@ describe Gitlab::Ci::Config::Node::Environment do end end + context 'when valid action is used' do + let(:config) do + { name: 'production', + action: 'start' } + end + + it 'is valid' do + expect(entry).to be_valid + end + end + + context 'when invalid action is used' do + let(:config) do + { name: 'production', + action: false } + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'contains error about invalid action' do + expect(entry.errors) + .to include 'environment action should be start or stop' + end + end + end + + context 'when on_stop is used' do + let(:config) do + { name: 'production', + on_stop: 'close_app' } + end + + it 'is valid' do + expect(entry).to be_valid + end + end + + context 'when invalid on_stop is used' do + let(:config) do + { name: 'production', + on_stop: false } + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'contains error about invalid action' do + expect(entry.errors) + .to include 'environment action should be start or stop' + end + end + end + context 'when variables are used for environment' do let(:config) do { name: 'review/$CI_BUILD_REF_NAME', -- cgit v1.2.1 From 4e386a2c221432ccba32810725fbfefb1d60ec5c Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 17 Oct 2016 09:17:12 -0500 Subject: Add API as trigger --- app/assets/stylesheets/pages/pipelines.scss | 4 ++++ app/views/projects/ci/pipelines/_pipeline.html.haml | 2 ++ 2 files changed, 6 insertions(+) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 385ac07fdaf..6655cd43656 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -93,6 +93,10 @@ float: none; } + .api { + color: $code-color; + } + .branch-commit { .branch-name { diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 263a3ff61f7..c6f359f5679 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -16,6 +16,8 @@ %span by - if pipeline.user = user_avatar(user: pipeline.user, size: 20) + - else + %span.api.monospace API - if pipeline.latest? %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest - if pipeline.triggered? -- cgit v1.2.1 From 25dd1712ca01d017fd3b9c2d230a62e82444b5a1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 16:23:17 +0200 Subject: Add specs to test on_stop and action on environments --- lib/gitlab/ci/config/node/environment.rb | 4 ++++ spec/lib/gitlab/ci/config/node/environment_spec.rb | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index b392f272bd6..9a95ef43628 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -64,6 +64,10 @@ module Gitlab value[:action] || 'start' end + def on_stop + value[:on_stop] + end + def value case @config when String then { name: @config, action: 'start' } diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb index 430e18a816f..dbeb28c8aad 100644 --- a/spec/lib/gitlab/ci/config/node/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb @@ -101,7 +101,7 @@ describe Gitlab::Ci::Config::Node::Environment do context 'when invalid action is used' do let(:config) do { name: 'production', - action: false } + action: 'invalid' } end describe '#valid?' do @@ -144,7 +144,7 @@ describe Gitlab::Ci::Config::Node::Environment do describe '#errors' do it 'contains error about invalid action' do expect(entry.errors) - .to include 'environment action should be start or stop' + .to include 'environment on stop should be a string' end end end -- cgit v1.2.1 From cbfdc37a241fd2a8851cf9fa85d817e44e8436e0 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 14 Oct 2016 10:57:10 -0500 Subject: Smaller min-width for MR pipeline table --- app/assets/stylesheets/pages/pipelines.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 7b71876b822..21b58b90a5f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -634,6 +634,10 @@ &.pipelines { + .ci-table { + min-width: 900px; + } + .content-list.pipelines { overflow: auto; } -- cgit v1.2.1 From 3fe36c5b5356ee76edc7010e4d91073b2063495a Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Mon, 17 Oct 2016 10:55:35 -0400 Subject: Updated logo from @luke --- doc/raketasks/backup_hrz.png | Bin 8907 -> 31784 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/raketasks/backup_hrz.png b/doc/raketasks/backup_hrz.png index 42084717ebe..287587609a1 100644 Binary files a/doc/raketasks/backup_hrz.png and b/doc/raketasks/backup_hrz.png differ -- cgit v1.2.1 From 4a369185d77013b2138f2daf6d85b1358425e75c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 17:10:26 +0200 Subject: Work on specs --- app/services/create_deployment_service.rb | 4 +- spec/models/deployment_spec.rb | 46 +++++++++++++++++++ spec/models/environment_spec.rb | 32 +++++++++++++ spec/services/create_deployment_service_spec.rb | 61 ++++++++++++++++++++++++- 4 files changed, 140 insertions(+), 3 deletions(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 85c0bf72074..c55d2ed231a 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -9,7 +9,7 @@ class CreateDeploymentService < BaseService @environment = environment @environment.external_url = expanded_url if expanded_url - @environment.state_event = action + @environment.fire_state_event(action) @environment.save! return if @environment.stopped? @@ -68,6 +68,6 @@ class CreateDeploymentService < BaseService end def action - params[:options].fetch(:action, 'start') + options[:action] || 'start' end end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 01a4a53a264..ca594a320c0 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -48,4 +48,50 @@ describe Deployment, models: true do end end end + + describe '#stop_action' do + let(:build) { create(:ci_build) } + + subject { deployment.stop_action } + + context 'when no other actions' do + let(:deployment) { FactoryGirl.build(:deployment, deployable: build) } + + it { is_expected.to be_nil } + end + + context 'with other actions' do + let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) } + + context 'when matching action is defined' do + let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_other_app') } + + it { is_expected.to be_nil } + end + + context 'when no matching action is defined' do + let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_app') } + + it { is_expected.to eq(close_action) } + end + end + end + + describe '#stoppable?' do + subject { deployment.stoppable? } + + context 'when no other actions' do + let(:deployment) { build(:deployment) } + + it { is_expected.to be_falsey } + end + + context 'when matching action is defined' do + let(:build) { create(:ci_build) } + let(:deployment) { FactoryGirl.build(:deployment, deployable: build, on_stop: 'close_app') } + let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) } + + it { is_expected.to be_truthy } + end + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index e172ee8e590..b019f2ddb77 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -8,6 +8,8 @@ describe Environment, models: true do it { is_expected.to delegate_method(:last_deployment).to(:deployments).as(:last) } + it { is_expected.to delegate_method(:stop_action).to(:last_deployment).as(:last) } + it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } it { is_expected.to validate_length_of(:name).is_within(0..255) } @@ -96,4 +98,34 @@ describe Environment, models: true do is_expected.to be_nil end end + + describe '#stoppable?' do + subject { environment.stoppable? } + + context 'when no other actions' do + it { is_expected.to be_falsey } + end + + context 'when matching action is defined' do + let(:build) { create(:ci_build) } + let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + let!(:close_action) { create(:ci_build, pipeline: build.pipeline, name: 'close_app', when: :manual) } + + context 'when environment is available' do + before do + environment.start + end + + it { is_expected.to be_truthy } + end + + context 'when environment is stopped' do + before do + environment.stop + end + + it { is_expected.to be_falsey } + end + end + end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 5fe56e7725f..d4ceb83caf8 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -7,11 +7,13 @@ describe CreateDeploymentService, services: true do let(:service) { described_class.new(project, user, params) } describe '#execute' do + let(:options) { nil } let(:params) do { environment: 'production', ref: 'master', tag: false, sha: '97de212e80737a608d939f648d959671fb0a0142', + options: options } end @@ -28,7 +30,7 @@ describe CreateDeploymentService, services: true do end context 'when environment exist' do - before { create(:environment, project: project, name: 'production') } + let!(:environment) { create(:environment, project: project, name: 'production') } it 'does not create a new environment' do expect { subject }.not_to change { Environment.count } @@ -37,6 +39,46 @@ describe CreateDeploymentService, services: true do it 'does create a deployment' do expect(subject).to be_persisted end + + context 'and start action is defined' do + let(:options) { { action: 'start' } } + + context 'and environment is stopped' do + before do + environment.stop + end + + it 'makes environment available' do + subject + + expect(environment.reload).to be_available + end + + it 'does not create a deployment' do + expect(subject).not_to be_persisted + end + end + end + + context 'and stop action is defined' do + let(:options) { { action: 'stop' } } + + context 'and environment is available' do + before do + environment.start + end + + it 'makes environment stopped' do + subject + + expect(environment.reload).to be_stopped + end + + it 'does not create a deployment' do + expect(subject).to be_nil + end + end + end end context 'for environment with invalid name' do @@ -83,6 +125,23 @@ describe CreateDeploymentService, services: true do it 'does create a new deployment' do expect(subject).to be_persisted end + + context 'and environment exist' do + it 'does not create a new environment' do + expect { subject }.not_to change { Environment.count } + end + + it 'updates external url' do + subject + + expect(subject.environment.name).to eq('review-apps/feature-review-apps') + expect(subject.environment.external_url).to eq('http://feature-review-apps.review-apps.gitlab.com') + end + + it 'does create a new deployment' do + expect(subject).to be_persisted + end + end end context 'when project was removed' do -- cgit v1.2.1 From 855e8524b208e60612976aed0d66c52a51d965a6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:52:16 +0200 Subject: Enable activerecord_sane_schema_dumper for test --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 5f754c1b66f..05166b6a828 100644 --- a/Gemfile +++ b/Gemfile @@ -262,8 +262,6 @@ group :development do # thin instead webrick gem 'thin', '~> 1.7.0' - - gem 'activerecord_sane_schema_dumper', '0.2' end group :development, :test do @@ -310,6 +308,8 @@ group :development, :test do gem 'license_finder', '~> 2.1.0', require: false gem 'knapsack', '~> 1.11.0' + + gem 'activerecord_sane_schema_dumper', '0.2' end group :test do -- cgit v1.2.1 From 6aca8b570c929e10f08a599f7bd4f3c7004f96d3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 16 Oct 2016 14:01:58 +0100 Subject: Added download-button class and applied button margin --- app/assets/stylesheets/pages/tree.scss | 4 ++++ app/views/projects/buttons/_download.html.haml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 99c0f6362d0..6ea7a2b5498 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -169,4 +169,8 @@ margin-top: 11px; position: relative; z-index: 2; + + .download-button { + margin-left: $btn-side-margin; + } } diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 9089586a89d..7e83a88913a 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,5 +1,5 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - %span{class: 'hidden-xs hidden-sm'} + %span{class: 'hidden-xs hidden-sm download-button'} .dropdown.inline %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') -- cgit v1.2.1 From 0ff28c706d33c889348115cef6de6f779e1349da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 17 Oct 2016 17:32:50 +0200 Subject: Update CHANGELOG for 8.12.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- CHANGELOG.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 447b7ffdfdf..fd37d9bcde6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,6 @@ Please view this file on the master branch, on stable branches it's out of date. - Clarify documentation for Runners API (Gennady Trafimenkov) - The instrumentation for Banzai::Renderer has been restored - Change user & group landing page routing from /u/:username to /:username - - Prevent running GfmAutocomplete setup for each diff note !6569 - Added documentation for .gitattributes files - Move Pipeline Metrics to separate worker - AbstractReferenceFilter caches project_refs on RequestStore when active @@ -40,7 +39,6 @@ Please view this file on the master branch, on stable branches it's out of date. - Update Gitlab Shell to fix some problems with moving projects between storages - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified - Simplify Mentionable concern instance methods - API: Ability to retrieve version information (Robert Schilling) - Fix permission for setting an issue's due date @@ -77,14 +75,12 @@ Please view this file on the master branch, on stable branches it's out of date. - Only update issuable labels if they have been changed - Take filters in account in issuable counters. !6496 - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) - - Prevent flash alert text from being obscured when container is fluid - Append issue template to existing description !6149 (Joseph Frazier) - Trending projects now only show public projects and the list of projects is cached for a day - Memoize Gitlab Shell's secret token (!6599, Justin DiPierro) - Revoke button in Applications Settings underlines on hover. - Use higher size on Gitlab::Redis connection pool on Sidekiq servers - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - Add disabled delete button to protected branches (ClemMakesApps) @@ -118,7 +114,6 @@ Please view this file on the master branch, on stable branches it's out of date. - Grouped pipeline dropdown is a scrollable container - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fixes padding in all clipboard icons that have .btn class - - Fix due date being displayed as NaN in Safari - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling @@ -126,8 +121,15 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.12.7 - - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 - - Fix GFM autocomplete setup being called several times + - Prevent running `GfmAutocomplete` setup for each diff note. !6569 + - Fix long commit messages overflow viewport in file tree. !6573 + - Use `gitlab-markup` gem instead of `github-markup` to fix `.rst` file rendering. !6659 + - Prevent flash alert text from being obscured when container is fluid. !6694 + - Fix due date being displayed as `NaN` in Safari. !6797 + - Fix JS bug with select2 because of missing `data-field` attribute in select box. !6812 + - Do not alter `force_remove_source_branch` options on MergeRequest unless specified. !6817 + - Fix GFM autocomplete setup being called several times. !6840 + - Handle case where deployment ref no longer exists. !6855 ## 8.12.6 -- cgit v1.2.1 From f91df171c53f230937b538ffdec04d12f7726ffc Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 17 Oct 2016 16:55:34 +0100 Subject: Fixed find file keyboard navigation Closes #23423 --- app/assets/javascripts/project_find_file.js | 9 +++++ .../projects/files/find_file_keyboard_spec.rb | 42 ++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 spec/features/projects/files/find_file_keyboard_spec.rb diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 8e38ccf7e44..b8347367717 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -7,6 +7,7 @@ function ProjectFindFile(element1, options) { this.element = element1; this.options = options; + this.goToBlob = bind(this.goToBlob, this); this.goToTree = bind(this.goToTree, this); this.selectRowDown = bind(this.selectRowDown, this); this.selectRowUp = bind(this.selectRowUp, this); @@ -154,6 +155,14 @@ return location.href = this.options.treeUrl; }; + ProjectFindFile.prototype.goToBlob = function() { + var $link = this.element.find(".tree-item.selected .tree-item-file-name a"); + + if ($link.length) { + $link.get(0).click(); + } + }; + return ProjectFindFile; })(); diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb new file mode 100644 index 00000000000..fc88fd74af8 --- /dev/null +++ b/spec/features/projects/files/find_file_keyboard_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +feature 'Find file keyboard shortcuts', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + project.team << [user, :master] + login_as user + + visit namespace_project_find_file_path(project.namespace, project, project.repository.root_ref) + + wait_for_ajax + end + + it 'opens file when pressing enter key' do + fill_in 'file_find', with: 'CHANGELOG' + + find('#file_find').native.send_keys(:enter) + + expect(page).to have_selector('.blob-content-holder') + + page.within('.file-title') do + expect(page).to have_content('CHANGELOG') + end + end + + it 'navigates files with arrow keys' do + fill_in 'file_find', with: 'application.' + + find('#file_find').native.send_keys(:down) + find('#file_find').native.send_keys(:enter) + + expect(page).to have_selector('.blob-content-holder') + + page.within('.file-title') do + expect(page).to have_content('application.js') + end + end +end -- cgit v1.2.1 From 317e48193fe5d75d6671c900a765ead719e6b4df Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Wed, 12 Oct 2016 15:34:47 +0200 Subject: Fix the diff in the merge request view when converting a symlink to a regular file. In this specific case using file_path as a cache key is not enough, because there are two entries with the same path. Closes #21610. --- CHANGELOG.md | 1 + lib/gitlab/diff/file.rb | 4 ++++ lib/gitlab/diff/file_collection/merge_request_diff.rb | 10 +++++----- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd37d9bcde6..015d520db0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Reduce queries needed to find users using their SSH keys when pushing commits - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list + - Fix the diff in the merge request view when converting a symlink to a regular file - Fix Pipeline list commit column width should be adjusted - Close todos when accepting merge requests via the API !6486 (tonygambone) - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index e47df508ca2..ce85e5e0123 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -125,6 +125,10 @@ module Gitlab repository.blob_at(commit.id, file_path) end + + def cache_key + "#{file_path}-#{new_file}-#{deleted_file}-#{renamed_file}" + end end end end diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb index 36348b33943..dc4d47c878b 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -35,16 +35,16 @@ module Gitlab # for the highlighted ones, so we just skip their execution. # If the highlighted diff files lines are not cached we calculate and cache them. # - # The content of the cache is a Hash where the key correspond to the file_path and the values are Arrays of + # The content of the cache is a Hash where the key identifies the file and the values are Arrays of # hashes that represent serialized diff lines. # def cache_highlight!(diff_file) - file_path = diff_file.file_path + item_key = diff_file.cache_key - if highlight_cache[file_path] - highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path]) + if highlight_cache[item_key] + highlight_diff_file_from_cache!(diff_file, highlight_cache[item_key]) else - highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash) + highlight_cache[item_key] = diff_file.highlighted_diff_lines.map(&:to_hash) end end -- cgit v1.2.1 From 9ff64b299bdcca767464f26e25c7e0017ec0fc38 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 14 Oct 2016 18:13:03 +0100 Subject: Swapped button text manipulation outcomes for the toggle query --- app/assets/javascripts/pipelines.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index 6bf63ee6979..a7624de6089 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -15,7 +15,7 @@ $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + graphCollapsed ? $btnText.text('Hide') : $btnText.text('Expand') } addMarginToBuildColumns() { -- cgit v1.2.1 From 501d1485edf321d9af242af4721dec604b8dfbcc Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Fri, 14 Oct 2016 13:55:10 -0700 Subject: Apply better hierarchy to markdown headers and issue/mr titles --- app/assets/stylesheets/framework/typography.scss | 22 ++++++++++------------ app/assets/stylesheets/pages/detail_page.scss | 6 ++++-- app/assets/stylesheets/pages/merge_requests.scss | 7 ------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 287653beac5..8f8ad728269 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -45,40 +45,38 @@ } h1 { - font-size: 2em; + font-size: 1.75em; font-weight: 600; - margin: 1em 0 10px; + margin: 16px 0 10px; padding: 0 0 0.3em; border-bottom: 1px solid $btn-default-border; color: $gl-gray-dark; } h2 { - font-size: 1.6em; + font-size: 1.5em; font-weight: 600; - margin: 1em 0 10px; - padding-bottom: 0.3em; - border-bottom: 1px solid $btn-default-border; + margin: 16px 0 10px; color: $gl-gray-dark; } h3 { - margin: 1em 0 10px; - font-size: 1.4em; + margin: 16px 0 10px; + font-size: 1.3em; } h4 { - margin: 1em 0 10px; - font-size: 1.25em; + margin: 16px 0 10px; + font-size: 1.2em; } h5 { - margin: 1em 0 10px; + margin: 16px 0 10px; font-size: 1em; } h6 { - margin: 1em 0 10px; + margin: 16px 0 10px; font-size: 0.95em; } diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 4d9c73c6840..7aa156bc47a 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -20,9 +20,11 @@ .detail-page-description { .title { - margin: 0; - font-size: 23px; + margin: 0 0 16px; + font-size: 2em; color: $gl-gray-dark; + padding: 0 0 0.3em; + border-bottom: 1px solid #e7e9ed; } .description { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index afc4e517fde..101472278e2 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -429,13 +429,6 @@ } } -.merge-request-details { - - .title { - margin-bottom: 20px; - } -} - .merge-request-tabs { background-color: #fff; -- cgit v1.2.1 From 28dbfd5c4c7793be4147ba217e7d9e162905088b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 17 Oct 2016 12:43:17 -0500 Subject: Provide better error message to the user --- .../javascripts/merge_conflicts/components/diff_file_editor.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 3379414343f..5012bdfe997 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -62,7 +62,7 @@ this.saveDiffResolution(); }) .fail(() => { - console.log('error'); + new Flash('Failed to load the file, please try again.'); }) .always(() => { this.loading = false; -- cgit v1.2.1 From 22ef066862bffc443baf8a630b0752ce37dab01c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 11:30:06 +0200 Subject: Avoid race condition when expiring artifacts It may happen that job meant to remove expired artifacts will be executed asynchronously when, in the meantime, project associated with given build gets removed by another asynchronous job. In that case we should not remove artifacts because such build will be removed anyway, when project removal is complete. --- .../expire_build_instance_artifacts_worker.rb | 10 +++-- .../expire_build_instance_artifacts_worker_spec.rb | 44 ++++++++++++++++------ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb index 916c2e633c1..d9e2cc37bb3 100644 --- a/app/workers/expire_build_instance_artifacts_worker.rb +++ b/app/workers/expire_build_instance_artifacts_worker.rb @@ -2,10 +2,14 @@ class ExpireBuildInstanceArtifactsWorker include Sidekiq::Worker def perform(build_id) - build = Ci::Build.with_expired_artifacts.reorder(nil).find_by(id: build_id) - return unless build + build = Ci::Build + .with_expired_artifacts + .reorder(nil) + .find_by(id: build_id) - Rails.logger.info "Removing artifacts build #{build.id}..." + return unless build.try(:project) + + Rails.logger.info "Removing artifacts for build #{build.id}..." build.erase_artifacts! end end diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb index 2b140f2ba28..d202b3de77e 100644 --- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -6,28 +6,48 @@ describe ExpireBuildInstanceArtifactsWorker do let(:worker) { described_class.new } describe '#perform' do - before { build } - - subject! { worker.perform(build.id) } + before do + worker.perform(build.id) + end context 'with expired artifacts' do - let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) } + let(:artifacts_expiry) { { artifacts_expire_at: Time.now - 7.days } } - it 'does expire' do - expect(build.reload.artifacts_expired?).to be_truthy - end + context 'when associated project is valid' do + let(:build) do + create(:ci_build, :artifacts, artifacts_expiry) + end - it 'does remove files' do - expect(build.reload.artifacts_file.exists?).to be_falsey + it 'does expire' do + expect(build.reload.artifacts_expired?).to be_truthy + end + + it 'does remove files' do + expect(build.reload.artifacts_file.exists?).to be_falsey + end + + it 'does nullify artifacts_file column' do + expect(build.reload.artifacts_file_identifier).to be_nil + end end - it 'does nullify artifacts_file column' do - expect(build.reload.artifacts_file_identifier).to be_nil + context 'when associated project was removed' do + let(:build) do + create(:ci_build, :artifacts, artifacts_expiry) do |build| + build.project.delete + end + end + + it 'does not remove artifacts' do + expect(build.reload.artifacts_file.exists?).to be_truthy + end end end context 'with not yet expired artifacts' do - let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) } + let(:build) do + create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) + end it 'does not expire' do expect(build.reload.artifacts_expired?).to be_falsey -- cgit v1.2.1 From 81f6e6e7768bed7fb63b1a8516dd0d0daf14f5d9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 13:23:02 +0200 Subject: Add Changelog entry to fix for removing artifacts --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd37d9bcde6..45b17a9f219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.13.0 (2016-10-22) +v 8.13.0 (unreleased) + - Avoid race condition when asynchronously removing expired artifacts. (!6881) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page -- cgit v1.2.1 From a5f5c02598d189428c583572d42f38e478669771 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Oct 2016 16:07:16 +0300 Subject: Add todo for deprecated user routes and more information about deprecation to changelog Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 +- config/routes/user.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2cbf5bc3277..1e887de6867 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -78,6 +78,7 @@ v 8.13.0 (unreleased) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile + - Change user pages routing from /u/:username/PATH to /users/:username/PATH. Old routes will redirect to the new ones for the time being. - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - Fix deploy status responsiveness error !6633 - Make searching for commits case insensitive @@ -102,7 +103,6 @@ v 8.13.0 (unreleased) - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects - - Change user pages routing from /u/:username/PATH to /users/:username/PATH v 8.12.6 - Update mailroom to 0.8.1 in Gemfile.lock !6814 diff --git a/config/routes/user.rb b/config/routes/user.rb index ae15b9d02a3..53f9aafc107 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -34,7 +34,9 @@ scope(path: 'users/:username', end # Compatibility with old routing +# TODO (dzaporozhets): remove in 10.0 get '/u/:username', to: redirect('/%{username}'), constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Mon, 17 Oct 2016 21:06:10 +0200 Subject: Fix environments specs --- .../projects/environments_controller.rb | 5 +- app/models/environment.rb | 3 + app/services/create_deployment_service.rb | 2 +- .../projects/environments/_external_url.html.haml | 2 +- app/views/projects/environments/_stop.html.haml | 2 +- app/views/projects/environments/show.html.haml | 8 +-- spec/features/environments_spec.rb | 76 ++++++++++++++++------ spec/services/create_deployment_service_spec.rb | 8 ++- 8 files changed, 74 insertions(+), 32 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 86bc17a720a..02a659d3894 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -1,8 +1,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! - before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_update_environment!, only: [:edit, :update, :stop] + before_action :authorize_create_environment!, only: [:new, :create, :stop] + before_action :authorize_create_deployment!, only: [:stop] + before_action :authorize_update_environment!, only: [:edit, :update] before_action :environment, only: [:show, :edit, :update, :stop] def index diff --git a/app/models/environment.rb b/app/models/environment.rb index 20da71ccb3f..ff55e751f70 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -21,6 +21,9 @@ class Environment < ActiveRecord::Base delegate :stop_action, to: :last_deployment, allow_nil: true + scope :available, -> { with_state(:available) } + scope :stopped, -> { with_state(:stopped) } + state_machine :state, initial: :available do event :start do transition stopped: :available diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index c55d2ed231a..8ae15ad32f4 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -10,8 +10,8 @@ class CreateDeploymentService < BaseService @environment = environment @environment.external_url = expanded_url if expanded_url @environment.fire_state_event(action) - @environment.save! + return unless @environment.save return if @environment.stopped? deploy.tap do |deployment| diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml index 6255e4baea0..4c8fe1c271b 100644 --- a/app/views/projects/environments/_external_url.html.haml +++ b/app/views/projects/environments/_external_url.html.haml @@ -1,3 +1,3 @@ -- if environment.external_url +- if environment.external_url && can?(current_user, :read_environment, environment) = link_to environment.external_url, target: '_blank', class: 'btn external-url' do = icon('external-link') diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml index c7dec086890..880f8c8c62c 100644 --- a/app/views/projects/environments/_stop.html.haml +++ b/app/views/projects/environments/_stop.html.haml @@ -1,4 +1,4 @@ -- if environment.stoppable? +- if can?(current_user, :create_deployment, environment) && environment.stoppable? .inline = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index b6a1a7fc89e..bf082a05c39 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -8,13 +8,11 @@ %h3.page-title= @environment.name.capitalize .col-md-3 .nav-controls - - if can?(current_user, :read_environmnet, @environment) - = render 'projects/environments/external_url', environment: @environment - + = render 'projects/environments/external_url', environment: @environment - if can?(current_user, :update_environment, @environment) = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' - - if @environment.stoppable? - = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post + - if can?(current_user, :create_deployment, @environment) && @environment.stoppable? + = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post .deployments-container - if @deployments.blank? diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 35f5706f920..1b51bf9ef66 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -35,7 +35,7 @@ feature 'Environments', feature: true do end scenario 'does show 0 as counter for environments in both tabs' do - expect(page.find('.js-avaibale-environments-count').text).to eq('0') + expect(page.find('.js-available-environments-count').text).to eq('0') expect(page.find('.js-stopped-environments-count').text).to eq('0') end end @@ -48,7 +48,7 @@ feature 'Environments', feature: true do end scenario 'does show number of opened environments in Availabe tab' do - expect(page.find('.js-avaibale-environments-count').text).to eq('1') + expect(page.find('.js-available-environments-count').text).to eq('1') end scenario 'does show number of closed environments in Stopped tab' do @@ -92,6 +92,14 @@ feature 'Environments', feature: true do scenario 'does show build name and id' do expect(page).to have_link("#{build.name} (##{build.id})") end + + scenario 'does not show stop button' do + expect(page).not_to have_selector('.close-env-link') + end + + scenario 'does not show external link button' do + expect(page).not_to have_css('external-url') + end context 'with external_url' do given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } @@ -103,14 +111,27 @@ feature 'Environments', feature: true do end end - scenario 'does show close button' do - # TODO: Add test to verify if close button is visible - # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? - end - - scenario 'does allow to close environment' do - # TODO: Add test to verify if close environment works - # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + context 'with stop action' do + given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } + given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + + scenario 'does show stop button' do + expect(page).to have_selector('.close-env-link') + end + + scenario 'does allow to stop environment' do + first('.close-env-link').click + + expect(page).to have_content('close_app') + end + + context 'for reporter' do + let(:role) { :reporter } + + scenario 'does not show stop button' do + expect(page).not_to have_selector('.close-env-link') + end + end end end end @@ -160,6 +181,10 @@ feature 'Environments', feature: true do expect(page).to have_link('Re-deploy') end + scenario 'does not show stop button' do + expect(page).not_to have_link('Stop') + end + context 'with manual action' do given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') } @@ -178,20 +203,33 @@ feature 'Environments', feature: true do given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } given(:build) { create(:ci_build, pipeline: pipeline) } given(:deployment) { create(:deployment, environment: environment, deployable: build) } - + scenario 'does show an external link button' do expect(page).to have_link(nil, href: environment.external_url) end end - scenario 'does show close button' do - # TODO: Add test to verify if close button is visible - # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? - end - - scenario 'does allow to close environment' do - # TODO: Add test to verify if close environment works - # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable? + context 'with stop action' do + given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } + given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + + scenario 'does show stop button' do + expect(page).to have_link('Stop') + end + + scenario 'does allow to stop environment' do + click_link('Stop') + + expect(page).to have_content('close_app') + end + + context 'for reporter' do + let(:role) { :reporter } + + scenario 'does not show stop button' do + expect(page).not_to have_link('Stop') + end + end end end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index d4ceb83caf8..5a4562b939b 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -54,8 +54,8 @@ describe CreateDeploymentService, services: true do expect(environment.reload).to be_available end - it 'does not create a deployment' do - expect(subject).not_to be_persisted + it 'does create a deployment' do + expect(subject).to be_persisted end end end @@ -95,7 +95,7 @@ describe CreateDeploymentService, services: true do end it 'does not create a deployment' do - expect(subject).not_to be_persisted + expect(subject).to be_nil end end @@ -127,6 +127,8 @@ describe CreateDeploymentService, services: true do end context 'and environment exist' do + let!(:environment) { create(:environment, project: project, name: 'review-apps/feature-review-apps') } + it 'does not create a new environment' do expect { subject }.not_to change { Environment.count } end -- cgit v1.2.1 From 34e19b9b8dccd7cd2e6c2bb408e75c70f3b6f3b9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 22:01:56 +0200 Subject: Fix specs --- .../projects/environments_controller.rb | 2 +- spec/features/environments_spec.rb | 42 +------------------ .../merge_when_build_succeeds_spec.rb | 9 ----- .../merge_requests/widget_deployments_spec.rb | 47 ++++++++++++++++++---- 4 files changed, 43 insertions(+), 57 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 02a659d3894..e243253c5f1 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -1,7 +1,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! - before_action :authorize_create_environment!, only: [:new, :create, :stop] + before_action :authorize_create_environment!, only: [:new, :create] before_action :authorize_create_deployment!, only: [:stop] before_action :authorize_update_environment!, only: [:edit, :update] before_action :environment, only: [:show, :edit, :update, :stop] diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 1b51bf9ef66..16c44e42f63 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -119,7 +119,7 @@ feature 'Environments', feature: true do expect(page).to have_selector('.close-env-link') end - scenario 'does allow to stop environment' do + scenario 'starts build when stop button clicked' do first('.close-env-link').click expect(page).to have_content('close_app') @@ -217,7 +217,7 @@ feature 'Environments', feature: true do expect(page).to have_link('Stop') end - scenario 'does allow to stop environment' do + scenario ' scenario 'does allow to stop environment' do' do click_link('Stop') expect(page).to have_content('close_app') @@ -277,42 +277,4 @@ feature 'Environments', feature: true do end end end - - describe 'when deleting existing environment' do - given(:environment) { create(:environment, project: project) } - - before do - visit namespace_project_environment_path(project.namespace, project, environment) - end - - context 'when logged as master' do - given(:role) { :master } - - scenario 'does not have a Close link' do - expect(page).not_to have_link('Close') - end - - context 'when environment is opened and can be closed' do - let(:project) { create(:project) } - let(:environment) { create(:environment, project: project) } - - let!(:deployment) do - create(:deployment, environment: environment, sha: project.commit('master').id) - end - - scenario 'does have a Close link' do - # TODO: Add missing validation. In order to have Close link - # this must be true: last_deployment.try(:close_action) - end - end - end - - context 'when logged as developer' do - given(:role) { :developer } - - scenario 'does not have a Close link' do - expect(page).not_to have_link('Close') - end - end - end end diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 5d6ce6e1830..c3c3ab33872 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -101,15 +101,6 @@ feature 'Merge When Build Succeeds', feature: true, js: true do expect(page).not_to have_link "Merge When Build Succeeds" end end - - context 'Has Environment' do - let(:environment) { create(:environment, project: project) } - - it 'does show link to close the environment' do - # TODO add test to verify if the button is visible when this condition - # is met: if environment.closeable? - end - end def visit_merge_request(merge_request) visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request) diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb index 8e23ec50d4a..0c8ee844b47 100644 --- a/spec/features/merge_requests/widget_deployments_spec.rb +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -4,23 +4,56 @@ feature 'Widget Deployments Header', feature: true, js: true do include WaitForAjax describe 'when deployed to an environment' do - let(:project) { merge_request.target_project } - let(:merge_request) { create(:merge_request, :merged) } - let(:environment) { create(:environment, project: project) } - let!(:deployment) do + given(:user) { create(:user) } + given(:project) { merge_request.target_project } + given(:merge_request) { create(:merge_request, :merged) } + given(:environment) { create(:environment, project: project) } + given(:role) { :developer } + given!(:deployment) do create(:deployment, environment: environment, sha: project.commit('master').id) end + given!(:manual) { } - before do - login_as :admin + background do + login_as(user) + project.team << [user, role] visit namespace_project_merge_request_path(project.namespace, project, merge_request) end - it 'displays that the environment is deployed' do + scenario 'displays that the environment is deployed' do wait_for_ajax expect(page).to have_content("Deployed to #{environment.name}") expect(find('.ci_widget > span > span')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium)) end + + context 'with stop action' do + given(:pipeline) { create(:ci_pipeline, project: project) } + given(:build) { create(:ci_build, pipeline: pipeline) } + given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } + given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + + background do + wait_for_ajax + end + + scenario 'does show stop button' do + expect(page).to have_link('Stop environment') + end + + scenario 'does start build when stop button clicked' do + click_link('Stop environment') + + expect(page).to have_content('close_app') + end + + context 'for reporter' do + given(:role) { :reporter } + + scenario 'does not show stop button' do + expect(page).not_to have_link('Stop environment') + end + end + end end end -- cgit v1.2.1 From da07c2e4d3d382c05ec287ee60f639b870074fe7 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 16 Sep 2016 16:15:39 -0300 Subject: Add visibility level to project repository --- CHANGELOG.md | 1 + app/assets/javascripts/project_new.js | 38 +++++- app/assets/stylesheets/pages/projects.scss | 83 +++++-------- app/controllers/projects_controller.rb | 35 ++++-- app/helpers/preferences_helper.rb | 16 ++- app/helpers/projects_helper.rb | 33 ++++-- app/models/project_feature.rb | 19 ++- app/policies/project_policy.rb | 14 ++- app/views/projects/_customize_workflow.html.haml | 8 ++ app/views/projects/_home_panel.html.haml | 5 +- app/views/projects/_wiki.html.haml | 19 +++ app/views/projects/edit.html.haml | 56 +++++---- app/views/projects/issues/_issues.html.haml | 2 +- app/views/projects/show.html.haml | 130 +++++++++++---------- ...d_repository_access_level_to_project_feature.rb | 14 +++ db/schema.rb | 3 +- spec/controllers/projects_controller_spec.rb | 40 +++++++ spec/factories/projects.rb | 2 + spec/features/projects/features_visibility_spec.rb | 30 +++++ .../gitlab/import_export/safe_model_attributes.yml | 1 + spec/models/project_feature_spec.rb | 23 +++- 21 files changed, 398 insertions(+), 174 deletions(-) create mode 100644 app/views/projects/_customize_workflow.html.haml create mode 100644 app/views/projects/_wiki.html.haml create mode 100644 db/migrate/20161012180455_add_repository_access_level_to_project_feature.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 37383ea1b72..44ddc1e03a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - Remove redundant mixins (ClemMakesApps) - Added 'Download' button to the Snippets page (Justin DiPierro) + - Add visibility level to project repository - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index 3cf41505814..478e82aa14d 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -4,9 +4,8 @@ this.ProjectNew = (function() { function ProjectNew() { this.toggleSettings = bind(this.toggleSettings, this); - this.$selects = $('.features select').filter(function () { - return $(this).data('field'); - }); + this.$selects = $('.features select'); + this.$repoSelects = this.$selects.filter('.js-repo-select'); $('.project-edit-container').on('ajax:before', (function(_this) { return function() { @@ -16,6 +15,7 @@ })(this)); this.toggleSettings(); this.toggleSettingsOnclick(); + this.toggleRepoVisibility(); } ProjectNew.prototype.toggleSettings = function() { @@ -43,6 +43,38 @@ } }; + ProjectNew.prototype.toggleRepoVisibility = function () { + var $repoAccessLevel = $('.js-repo-access-level select'); + + this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']") + .nextAll() + .hide(); + + $repoAccessLevel.off('change') + .on('change', function () { + var selectedVal = parseInt($repoAccessLevel.val()); + + this.$repoSelects.each(function () { + var $this = $(this), + repoSelectVal = parseInt($this.val()); + + $this.find('option').show(); + + if (selectedVal < repoSelectVal) { + $this.val(selectedVal); + } + + $this.find("option[value='" + selectedVal + "']").nextAll().hide(); + }); + + if (selectedVal) { + this.$repoSelects.removeClass('disabled'); + } else { + this.$repoSelects.addClass('disabled'); + } + }.bind(this)); + }; + return ProjectNew; })(); diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index d30f02340b9..1062d7effb0 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -761,62 +761,6 @@ pre.light-well { .dropdown-menu { width: 300px; } - - &.from .compare-dropdown-toggle { - width: 237px; - } - - &.to .compare-dropdown-toggle { - width: 254px; - } - - .dropdown-toggle-text { - display: block; - height: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 100%; - } -} - -.compare-ellipsis { - display: inline; -} - -@media (max-width: $screen-xs-max) { - .compare-form-group { - .input-group { - width: 100%; - - & > .compare-dropdown-toggle { - width: 100%; - } - } - - .dropdown-menu { - width: 100%; - } - } - - .compare-switch-container { - text-align: center; - padding: 0 0 $gl-padding; - - .commits-compare-switch { - float: none; - } - } - - .compare-ellipsis { - display: block; - text-align: center; - padding: 0 0 $gl-padding; - } - - .commits-compare-btn { - width: 100%; - } } .clearable-input { @@ -855,3 +799,30 @@ pre.light-well { border-bottom-right-radius: 0; } } + +.project-home-empty { + border-top: 0; + + .container-fluid { + background: none; + } + + p { + margin-left: auto; + margin-right: auto; + max-width: 650px; + } +} + +.project-feature-nested { + @media (min-width: $screen-sm-min) { + padding-left: 45px; + } +} + +.project-repo-select { + &.disabled { + opacity: 0.5; + pointer-events: none; + } +} diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 62916270172..76b730198d4 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,4 +1,5 @@ class ProjectsController < Projects::ApplicationController + include IssuableCollections include ExtractsPath before_action :authenticate_user!, except: [:show, :activity, :refs] @@ -103,16 +104,7 @@ class ProjectsController < Projects::ApplicationController respond_to do |format| format.html do @notification_setting = current_user.notification_settings_for(@project) if current_user - - if @project.repository_exists? - if @project.empty_repo? - render 'projects/empty' - else - render :show - end - else - render 'projects/no_repo' - end + render_landing_page end format.atom do @@ -285,6 +277,26 @@ class ProjectsController < Projects::ApplicationController private + # Render project landing depending of which features are available + # So if page is not availble in the list it renders the next page + # + # pages list order: repository readme, wiki home, issues list, customize workflow + def render_landing_page + if @project.feature_available?(:repository, current_user) + return render 'projects/no_repo' unless @project.repository_exists? + render 'projects/empty' if @project.empty_repo? + else + if @project.wiki_enabled? + @wiki_home = @project.wiki.find_page('home', params[:version_id]) + elsif @project.feature_available?(:issues, current_user) + @issues = issues_collection + @issues = @issues.page(params[:page]) + end + + render :show + end + end + def determine_layout if [:new, :create].include?(action_name.to_sym) 'application' @@ -308,7 +320,8 @@ class ProjectsController < Projects::ApplicationController project_feature_attributes: [ :issues_access_level, :builds_access_level, - :wiki_access_level, :merge_requests_access_level, :snippets_access_level + :wiki_access_level, :merge_requests_access_level, + :snippets_access_level, :repository_access_level ] } diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index c3832cf5d65..a46f2c6e17d 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -50,6 +50,20 @@ module PreferencesHelper end def default_project_view - current_user ? current_user.project_view : 'readme' + return 'readme' unless current_user + + user_view = current_user.project_view + + if @project.feature_available?(:repository, current_user) + user_view + elsif user_view == "activity" + "activity" + elsif @project.wiki_enabled? + "wiki" + elsif @project.feature_available?(:issues, current_user) + "projects/issues/issues" + else + "customize_workflow" + end end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e667c9e4e2e..d26b4018be6 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -134,16 +134,35 @@ module ProjectsHelper options = project_feature_options if @project.private? + level = @project.project_feature.send(field) options.delete('Everyone with access') - highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED + highest_available_option = options.values.max if level == ProjectFeature::ENABLED end options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) - content_tag(:select, options, name: "project[project_feature_attributes][#{field}]", id: "project_project_feature_attributes_#{field}", class: "pull-right form-control", data: { field: field }).html_safe + + content_tag( + :select, + options, + name: "project[project_feature_attributes][#{field}]", + id: "project_project_feature_attributes_#{field}", + class: "pull-right form-control #{repo_children_classes(field)}", + data: { field: field } + ).html_safe end private + def repo_children_classes(field) + needs_repo_check = [:merge_requests_access_level, :builds_access_level] + return unless needs_repo_check.include?(field) + + classes = "project-repo-select js-repo-select" + classes << " disabled" unless @project.feature_available?(:repository, current_user) + + classes + end + def get_project_nav_tabs(project, current_user) nav_tabs = [:home] @@ -155,12 +174,8 @@ module ProjectsHelper nav_tabs << :merge_requests end - if can?(current_user, :read_pipeline, project) - nav_tabs << :pipelines - end - if can?(current_user, :read_build, project) - nav_tabs << :builds + nav_tabs << :pipelines end if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project) @@ -435,4 +450,8 @@ module ProjectsHelper 'Everyone with access' => ProjectFeature::ENABLED } end + + def project_child_container_class(view_path) + view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}" + end end diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 530f7d5a30e..b37ce1d3cf6 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -13,23 +13,26 @@ class ProjectFeature < ActiveRecord::Base # Enabled: enabled for everyone able to access the project # - # Permision levels + # Permission levels DISABLED = 0 PRIVATE = 10 ENABLED = 20 - FEATURES = %i(issues merge_requests wiki snippets builds) + FEATURES = %i(issues merge_requests wiki snippets builds repository) # Default scopes force us to unscope here since a service may need to check # permissions for a project in pending_delete # http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to belongs_to :project, -> { unscope(where: :pending_delete) } + validate :repository_children_level + default_value_for :builds_access_level, value: ENABLED, allows_nil: false default_value_for :issues_access_level, value: ENABLED, allows_nil: false default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false default_value_for :snippets_access_level, value: ENABLED, allows_nil: false default_value_for :wiki_access_level, value: ENABLED, allows_nil: false + default_value_for :repository_access_level, value: ENABLED, allows_nil: false def feature_available?(feature, user) raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature) @@ -57,6 +60,18 @@ class ProjectFeature < ActiveRecord::Base private + # Validates builds and merge requests access level + # which cannot be higher than repository access level + def repository_children_level + validator = lambda do |field| + level = public_send(field) || ProjectFeature::ENABLED + not_allowed = level > repository_access_level + self.errors.add(field, "cannot have higher visibility level than repository access level") if not_allowed + end + + %i(merge_requests_access_level builds_access_level).each(&validator) + end + def get_permission(user, level) case level when DISABLED diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index be4721d7a51..fbb3d4507d6 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -162,11 +162,13 @@ class ProjectPolicy < BasePolicy end def disabled_features! + repository_enabled = project.feature_available?(:repository, user) + unless project.feature_available?(:issues, user) cannot!(*named_abilities(:issue)) end - unless project.feature_available?(:merge_requests, user) + unless project.feature_available?(:merge_requests, user) && repository_enabled cannot!(*named_abilities(:merge_request)) end @@ -183,13 +185,21 @@ class ProjectPolicy < BasePolicy cannot!(*named_abilities(:wiki)) end - unless project.feature_available?(:builds, user) + unless project.feature_available?(:builds, user) && repository_enabled cannot!(*named_abilities(:build)) cannot!(*named_abilities(:pipeline)) cannot!(*named_abilities(:environment)) cannot!(*named_abilities(:deployment)) end + unless repository_enabled + cannot! :push_code + cannot! :push_code_to_protected_branches + cannot! :download_code + cannot! :fork_project + cannot! :read_commit_status + end + unless project.container_registry_enabled cannot!(*named_abilities(:container_image)) end diff --git a/app/views/projects/_customize_workflow.html.haml b/app/views/projects/_customize_workflow.html.haml new file mode 100644 index 00000000000..d2c1e943db1 --- /dev/null +++ b/app/views/projects/_customize_workflow.html.haml @@ -0,0 +1,8 @@ +.row-content-block.project-home-empty + %div.text-center{ class: container_class } + %h4 + Customize your workflow! + %p + Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and builds, GitLab can help manage your workflow from idea to production! + - if can?(current_user, :admin_project, @project) + = link_to "Get started", edit_project_path(@project), class: "btn btn-success" diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 5590198a20e..d3987fc9c4f 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -22,5 +22,6 @@ = render 'projects/buttons/star' = render 'projects/buttons/fork' - .project-clone-holder - = render "shared/clone_panel" + - if @project.feature_available?(:repository, current_user) + .project-clone-holder + = render "shared/clone_panel" diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml new file mode 100644 index 00000000000..f00422dd7c0 --- /dev/null +++ b/app/views/projects/_wiki.html.haml @@ -0,0 +1,19 @@ +- if @wiki_home.present? + %div{ class: container_class } + .wiki-holder.prepend-top-default.append-bottom-default + .wiki + = preserve do + = render_wiki_content(@wiki_home) +- else + - can_create_wiki = can?(current_user, :create_wiki, @project) + .project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] } + %div.text-center{ class: container_class } + %h4 + This project does not have a wiki homepage yet + - if can_create_wiki + %p + Add a homepage to your wiki that contains information about your project + %p + We recommend you + = link_to "add a homepage", namespace_project_wiki_path(@project.namespace, @project, :home) + to your project's wiki and GitLab will show it here instead of this message. diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index c8f84b96cb7..fb776e3a3e7 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -50,24 +50,39 @@ .form_group.prepend-top-20 .row .col-md-9 - = feature_fields.label :issues_access_level, "Issues", class: 'label-light' - %span.help-block Lightweight issue tracking system for this project - .col-md-3 - = project_feature_access_select(:issues_access_level) + = feature_fields.label :repository_access_level, "Repository", class: 'label-light' + %span.help-block Push files to be stored in this project + .col-md-3.js-repo-access-level + = project_feature_access_select(:repository_access_level) + + .col-sm-12 + .row + .col-md-9.project-feature-nested + = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light' + %span.help-block Submit changes to be merged upstream + .col-md-3 + = project_feature_access_select(:merge_requests_access_level) + + .row + .col-md-9.project-feature-nested + = feature_fields.label :builds_access_level, "Builds", class: 'label-light' + %span.help-block Submit, test and deploy your changes before merge + .col-md-3 + = project_feature_access_select(:builds_access_level) .row .col-md-9 - = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light' - %span.help-block Submit changes to be merged upstream + = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light' + %span.help-block Share code pastes with others out of Git repository .col-md-3 - = project_feature_access_select(:merge_requests_access_level) + = project_feature_access_select(:snippets_access_level) .row .col-md-9 - = feature_fields.label :builds_access_level, "Builds", class: 'label-light' - %span.help-block Submit Test and deploy your changes before merge + = feature_fields.label :issues_access_level, "Issues", class: 'label-light' + %span.help-block Lightweight issue tracking system for this project .col-md-3 - = project_feature_access_select(:builds_access_level) + = project_feature_access_select(:issues_access_level) .row .col-md-9 @@ -76,24 +91,17 @@ .col-md-3 = project_feature_access_select(:wiki_access_level) - .row - .col-md-9 - = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light' - %span.help-block Share code pastes with others out of Git repository - .col-md-3 - = project_feature_access_select(:snippets_access_level) - - if Gitlab.config.lfs.enabled && current_user.admin? - .row - .col-md-9 - = f.label :lfs_enabled, 'LFS', class: 'label-light' - %span.help-block + .checkbox + = f.label :lfs_enabled do + = f.check_box :lfs_enabled + %strong LFS + %br + %span.descr Git Large File Storage = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') - .col-md-3 - = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' } - - if Gitlab.config.registry.enabled + - if Gitlab.config.lfs.enabled && current_user.admin? .form-group .checkbox = f.label :container_registry_enabled do diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index a2c31c0b4c5..a4b752ad86d 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,5 +1,5 @@ %ul.content-list.issues-list.issuable-list - = render @issues + = render partial: "projects/issues/issue", collection: @issues - if @issues.blank? %li .nothing-here-block No issues to show diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index ea4deb6cb28..ba16c641462 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -12,72 +12,74 @@ = render 'projects/last_push' = render "home_panel" -%nav.project-stats{ class: (container_class) } - %ul.nav - %li - = link_to project_files_path(@project) do - Files (#{repository_size}) - %li - = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do - #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)}) - %li - = link_to namespace_project_branches_path(@project.namespace, @project) do - #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) - %li - = link_to namespace_project_tags_path(@project.namespace, @project) do - #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)}) - - - if default_project_view != 'readme' && @repository.readme +- if @project.feature_available?(:repository, current_user) + %nav.project-stats{ class: container_class } + %ul.nav %li - = link_to 'Readme', readme_path(@project) - - - if @repository.changelog + = link_to project_files_path(@project) do + Files (#{repository_size}) %li - = link_to 'Changelog', changelog_path(@project) - - - if @repository.license_blob + = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do + #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)}) %li - = link_to license_short_name(@project), license_path(@project) - - - if @repository.contribution_guide + = link_to namespace_project_branches_path(@project.namespace, @project) do + #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) %li - = link_to 'Contribution guide', contribution_guide_path(@project) + = link_to namespace_project_tags_path(@project.namespace, @project) do + #{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)}) - - if @repository.gitlab_ci_yml - %li - = link_to 'CI configuration', ci_configuration_path(@project) - - - if current_user && can_push_branch?(@project, @project.default_branch) - - unless @repository.changelog - %li.missing - = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do - Add Changelog - - unless @repository.license_blob - %li.missing - = link_to add_special_file_path(@project, file_name: 'LICENSE') do - Add License - - unless @repository.contribution_guide - %li.missing - = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do - Add Contribution guide - - unless @repository.gitlab_ci_yml - %li.missing - = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do - Set Up CI - - %li.project-repo-buttons-right - .project-repo-buttons.project-right-buttons - - if current_user - = render 'shared/members/access_request_buttons', source: @project - = render "projects/buttons/koding" - - = render 'projects/buttons/download', project: @project, ref: @ref - = render 'projects/buttons/dropdown' - - = render 'shared/notifications/button', notification_setting: @notification_setting -- if @repository.commit - .project-last-commit{ class: container_class } - = render 'projects/last_commit', commit: @repository.commit, project: @project + - if default_project_view != 'readme' && @repository.readme + %li + = link_to 'Readme', readme_path(@project) + + - if @repository.changelog + %li + = link_to 'Changelog', changelog_path(@project) + + - if @repository.license_blob + %li + = link_to license_short_name(@project), license_path(@project) + + - if @repository.contribution_guide + %li + = link_to 'Contribution guide', contribution_guide_path(@project) + + - if @repository.gitlab_ci_yml + %li + = link_to 'CI configuration', ci_configuration_path(@project) + + - if current_user && can_push_branch?(@project, @project.default_branch) + - unless @repository.changelog + %li.missing + = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do + Add Changelog + - unless @repository.license_blob + %li.missing + = link_to add_special_file_path(@project, file_name: 'LICENSE') do + Add License + - unless @repository.contribution_guide + %li.missing + = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do + Add Contribution guide + - unless @repository.gitlab_ci_yml + %li.missing + = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do + Set Up CI + + %li.project-repo-buttons-right + .project-repo-buttons.project-right-buttons + - if current_user + = render 'shared/members/access_request_buttons', source: @project + = render "projects/buttons/koding" + + .btn-group.project-repo-btn-group + = render 'projects/buttons/download', project: @project, ref: @ref + = render 'projects/buttons/dropdown' + + = render 'shared/notifications/button', notification_setting: @notification_setting + - if @repository.commit + .project-last-commit{ class: container_class } + = render 'projects/last_commit', commit: @repository.commit, project: @project %div{ class: container_class } - if @project.archived? @@ -86,5 +88,7 @@ = icon("exclamation-triangle fw") Archived project! Repository is read-only - %div{class: "project-show-#{default_project_view}"} - = render default_project_view + - view_path = default_project_view + + %div{ class: project_child_container_class(view_path) } + = render view_path diff --git a/db/migrate/20161012180455_add_repository_access_level_to_project_feature.rb b/db/migrate/20161012180455_add_repository_access_level_to_project_feature.rb new file mode 100644 index 00000000000..7b33da3ea11 --- /dev/null +++ b/db/migrate/20161012180455_add_repository_access_level_to_project_feature.rb @@ -0,0 +1,14 @@ +class AddRepositoryAccessLevelToProjectFeature < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + add_column_with_default(:project_features, :repository_access_level, :integer, default: ProjectFeature::ENABLED) + end + + def down + remove_column :project_features, :repository_access_level + end +end diff --git a/db/schema.rb b/db/schema.rb index a362fd8f228..51ac0fbaeb5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161007133303) do +ActiveRecord::Schema.define(version: 20161012180455) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -830,6 +830,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.integer "builds_access_level" t.datetime "created_at" t.datetime "updated_at" + t.integer "repository_access_level", default: 20, null: false end add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index da0fdce39db..8eefa284ba0 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -41,6 +41,46 @@ describe ProjectsController do end end end + + describe "when project repository is disabled" do + render_views + + before do + project.team << [user, :developer] + project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) + end + + it 'shows wiki homepage' do + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to render_template('projects/_wiki') + end + + it 'shows issues list page if wiki is disabled' do + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) + + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to render_template('projects/issues/_issues') + end + + it 'shows customize workflow page if wiki and issues are disabled' do + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) + project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) + + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to render_template("projects/_customize_workflow") + end + + it 'shows activity if enabled by user' do + user.update_attribute(:project_view, 'activity') + + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to render_template("projects/_activity") + end + end end context "project with empty repo" do diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 719ef17f57e..4065e2defbc 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -45,6 +45,7 @@ FactoryGirl.define do snippets_access_level ProjectFeature::ENABLED issues_access_level ProjectFeature::ENABLED merge_requests_access_level ProjectFeature::ENABLED + repository_access_level ProjectFeature::ENABLED end after(:create) do |project, evaluator| @@ -55,6 +56,7 @@ FactoryGirl.define do snippets_access_level: evaluator.snippets_access_level, issues_access_level: evaluator.issues_access_level, merge_requests_access_level: evaluator.merge_requests_access_level, + repository_access_level: evaluator.repository_access_level ) end end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 9b487e350f2..1d4484a9edd 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -2,8 +2,11 @@ require 'spec_helper' include WaitForAjax describe 'Edit Project Settings', feature: true do + include WaitForAjax + let(:member) { create(:user) } let!(:project) { create(:project, :public, path: 'gitlab', name: 'sample') } + let!(:issue) { create(:issue, project: project) } let(:non_member) { create(:user) } describe 'project features visibility selectors', js: true do @@ -119,4 +122,31 @@ describe 'Edit Project Settings', feature: true do end end end + + describe 'repository visibility', js: true do + before do + project.team << [member, :master] + login_as(member) + visit edit_namespace_project_path(project.namespace, project) + end + + it "disables repository related features" do + select "Disabled", from: "project_project_feature_attributes_repository_access_level" + + expect(find(".edit-project")).to have_selector("select.disabled", count: 2) + end + + it "shows empty features project homepage" do + select "Disabled", from: "project_project_feature_attributes_repository_access_level" + select "Disabled", from: "project_project_feature_attributes_issues_access_level" + select "Disabled", from: "project_project_feature_attributes_wiki_access_level" + + click_button "Save changes" + wait_for_ajax + + visit namespace_project_path(project.namespace, project) + + expect(page).to have_content "Customize your workflow!" + end + end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 8bccd313d6c..8c8be66df9f 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -307,6 +307,7 @@ ProjectFeature: - wiki_access_level - snippets_access_level - builds_access_level +- repository_access_level - created_at - updated_at ProtectedBranch::MergeAccessLevel: diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index 8d554a01be5..a55d43ab2f9 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -5,7 +5,7 @@ describe ProjectFeature do let(:user) { create(:user) } describe '#feature_available?' do - let(:features) { %w(issues wiki builds merge_requests snippets) } + let(:features) { %w(issues wiki builds merge_requests snippets repository) } context 'when features are disabled' do it "returns false" do @@ -64,6 +64,27 @@ describe ProjectFeature do end end + context 'repository related features' do + before do + project.project_feature.update_attributes( + merge_requests_access_level: ProjectFeature::DISABLED, + builds_access_level: ProjectFeature::DISABLED, + repository_access_level: ProjectFeature::PRIVATE + ) + end + + it "does not allow repository related features have higher level" do + features = %w(builds merge_requests) + project_feature = project.project_feature + + features.each do |feature| + field = "#{feature}_access_level".to_sym + project_feature.update_attribute(field, ProjectFeature::ENABLED) + expect(project_feature.valid?).to be_falsy + end + end + end + describe '#*_enabled?' do let(:features) { %w(wiki builds merge_requests) } -- cgit v1.2.1 From eb574e4a0ad19a2848abff56a0ce7c44d3f6780e Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Mon, 17 Oct 2016 13:23:28 -0700 Subject: change border color to variable --- app/assets/stylesheets/framework/typography.scss | 6 +++--- app/assets/stylesheets/pages/detail_page.scss | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 8f8ad728269..55de9053be5 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -49,7 +49,7 @@ font-weight: 600; margin: 16px 0 10px; padding: 0 0 0.3em; - border-bottom: 1px solid $btn-default-border; + border-bottom: 1px solid $white-dark; color: $gl-gray-dark; } @@ -85,12 +85,12 @@ font-size: inherit; padding: 8px 21px; margin: 12px 0; - border-left: 3px solid #e7e9ed; + border-left: 3px solid $white-dark; } blockquote:dir(rtl) { border-left: 0; - border-right: 3px solid #e7e9ed; + border-right: 3px solid $white-dark; } blockquote p { diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 7aa156bc47a..2357671c2ae 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -24,7 +24,7 @@ font-size: 2em; color: $gl-gray-dark; padding: 0 0 0.3em; - border-bottom: 1px solid #e7e9ed; + border-bottom: 1px solid $white-dark; } .description { -- cgit v1.2.1 From c3cf103fee47c056c7258d2921a34cf68a02022c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 22:10:43 +0100 Subject: Updates class name for consistency --- app/assets/javascripts/merge_request_widget.js.es6 | 4 ++-- app/assets/stylesheets/pages/environments.scss | 4 ++-- app/assets/stylesheets/pages/merge_requests.scss | 2 +- app/views/projects/environments/_stop.html.haml | 4 ++-- spec/features/environments_spec.rb | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 639859ab96f..3ff6851d59b 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -17,7 +17,7 @@ View on <%- external_url_formatted %> - + Stop environment @@ -213,7 +213,7 @@ if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); if (!environment.stop_url) { - $('.js-close-env-link', $template).remove(); + $('.js-stop-env-link', $template).remove(); } if (environment.deployed_at && environment.deployed_at_formatted) { diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 9d3492abfb5..12ee0a5dc3d 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -38,10 +38,10 @@ color: $gl-dark-link-color; } - .close-env-link { + .stop-env-link { color: $table-text-gray; - .close-env-icon { + .stop-env-icon { font-size: 14px; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index f14f13494e0..e27a82ee5f1 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -184,7 +184,7 @@ float: right; } - .close-env-container { + .stop-env-container { color: $gl-text-color; float: right; diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml index 880f8c8c62c..69848123c17 100644 --- a/app/views/projects/environments/_stop.html.haml +++ b/app/views/projects/environments/_stop.html.haml @@ -1,5 +1,5 @@ - if can?(current_user, :create_deployment, environment) && environment.stoppable? .inline = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post, - class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do - = icon('stop', class: 'close-env-icon') + class: 'btn stop-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do + = icon('stop', class: 'stop-env-icon') diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 16c44e42f63..c4f230d2007 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -94,7 +94,7 @@ feature 'Environments', feature: true do end scenario 'does not show stop button' do - expect(page).not_to have_selector('.close-env-link') + expect(page).not_to have_selector('.stop-env-link') end scenario 'does not show external link button' do @@ -116,11 +116,11 @@ feature 'Environments', feature: true do given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } scenario 'does show stop button' do - expect(page).to have_selector('.close-env-link') + expect(page).to have_selector('.stop-env-link') end scenario 'starts build when stop button clicked' do - first('.close-env-link').click + first('.stop-env-link').click expect(page).to have_content('close_app') end @@ -129,7 +129,7 @@ feature 'Environments', feature: true do let(:role) { :reporter } scenario 'does not show stop button' do - expect(page).not_to have_selector('.close-env-link') + expect(page).not_to have_selector('.stop-env-link') end end end -- cgit v1.2.1 From 5a46e22a6521af8058078e00a8e6f2252b21e8f7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 22:12:26 +0100 Subject: Fixes typo --- spec/features/environments_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index c4f230d2007..7f67ff7df92 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -47,7 +47,7 @@ feature 'Environments', feature: true do expect(page).to have_link(environment.name) end - scenario 'does show number of opened environments in Availabe tab' do + scenario 'does show number of opened environments in Available tab' do expect(page.find('.js-available-environments-count').text).to eq('1') end -- cgit v1.2.1 From c4e0c051251a37b9afc5630b65701ae5a28de15b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 17 Oct 2016 22:39:52 +0100 Subject: Fixes broken test --- spec/features/environments_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 7f67ff7df92..6cc13290f2c 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -217,7 +217,7 @@ feature 'Environments', feature: true do expect(page).to have_link('Stop') end - scenario ' scenario 'does allow to stop environment' do' do + scenario 'does allow to stop environment' do click_link('Stop') expect(page).to have_content('close_app') -- cgit v1.2.1 From bebfceb1df03e8afa10af5aead8e657654a14f01 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 23:40:44 +0200 Subject: Fix specs --- spec/features/environments_spec.rb | 18 +++++++++--------- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/environment_spec.rb | 2 +- spec/models/environment_spec.rb | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 7f67ff7df92..7200e9ad4c1 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -18,7 +18,7 @@ feature 'Environments', feature: true do before do visit namespace_project_environments_path(project.namespace, project) end - + context 'shows two tabs' do scenario 'does show Available tab with link' do expect(page).to have_link('Available') @@ -33,7 +33,7 @@ feature 'Environments', feature: true do scenario 'does show no environments' do expect(page).to have_content('You don\'t have any environments right now.') end - + scenario 'does show 0 as counter for environments in both tabs' do expect(page.find('.js-available-environments-count').text).to eq('0') expect(page.find('.js-stopped-environments-count').text).to eq('0') @@ -46,7 +46,7 @@ feature 'Environments', feature: true do scenario 'does show environment name' do expect(page).to have_link(environment.name) end - + scenario 'does show number of opened environments in Available tab' do expect(page.find('.js-available-environments-count').text).to eq('1') end @@ -67,7 +67,7 @@ feature 'Environments', feature: true do scenario 'does show deployment SHA' do expect(page).to have_link(deployment.short_sha) end - + scenario 'does show deployment internal id' do expect(page).to have_content(deployment.iid) end @@ -88,7 +88,7 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end - + scenario 'does show build name and id' do expect(page).to have_link("#{build.name} (##{build.id})") end @@ -100,12 +100,12 @@ feature 'Environments', feature: true do scenario 'does not show external link button' do expect(page).not_to have_css('external-url') end - + context 'with external_url' do given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } given(:build) { create(:ci_build, pipeline: pipeline) } given(:deployment) { create(:deployment, environment: environment, deployable: build) } - + scenario 'does show an external link button' do expect(page).to have_link(nil, href: environment.external_url) end @@ -198,7 +198,7 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end - + context 'with external_url' do given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } given(:build) { create(:ci_build, pipeline: pipeline) } @@ -217,7 +217,7 @@ feature 'Environments', feature: true do expect(page).to have_link('Stop') end - scenario ' scenario 'does allow to stop environment' do' do + scenario 'does allow to stop environment' do click_link('Stop') expect(page).to have_content('close_app') diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 6dedd25e9d3..a46ff07f625 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -754,7 +754,7 @@ module Ci it 'does return production' do expect(builds.size).to eq(1) expect(builds.first[:environment]).to eq(environment) - expect(builds.first[:options]).to include(environment: { name: environment }) + expect(builds.first[:options]).to include(environment: { name: environment, action: "start" }) end end diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb index dbeb28c8aad..df925ff1afd 100644 --- a/spec/lib/gitlab/ci/config/node/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb @@ -28,7 +28,7 @@ describe Gitlab::Ci::Config::Node::Environment do describe '#value' do it 'returns valid hash' do - expect(entry.value).to eq(name: 'production') + expect(entry.value).to include(name: 'production') end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index b019f2ddb77..1c1fef57fc4 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -8,7 +8,7 @@ describe Environment, models: true do it { is_expected.to delegate_method(:last_deployment).to(:deployments).as(:last) } - it { is_expected.to delegate_method(:stop_action).to(:last_deployment).as(:last) } + it { is_expected.to delegate_method(:stop_action).to(:last_deployment) } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } -- cgit v1.2.1 From 73988dd994d6c0d7f5cec24ca5c4c20137cc3844 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 17 Oct 2016 17:47:07 -0500 Subject: Update endpoint to username validator --- app/assets/javascripts/username_validator.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index 2517f778365..bf4b2e320cd 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -76,7 +76,7 @@ this.renderState(); return $.ajax({ type: 'GET', - url: `/u/${username}/exists`, + url: `/users/${username}/exists`, dataType: 'json', success: (res) => this.setAvailabilityState(res.exists) }); -- cgit v1.2.1 From 6a16697ad29bc2136411faf2431baa94f2a599e0 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 13 Oct 2016 13:01:27 +0200 Subject: Execute specific named route method from toggle_award_url helper method --- CHANGELOG.md | 1 + app/helpers/award_emoji_helper.rb | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dbe1832de0..81c25445ff9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Add Issue Board API support (andrebsguedes) - Allow the Koding integration to be configured through the API - Add new issue button to each list on Issues Board + - Execute specific named route method from toggle_award_url helper method - Added soft wrap button to repository file/blob editor - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - Show the time ago a merge request was deployed to an environment diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb index aa134cea31c..493f14f6f9d 100644 --- a/app/helpers/award_emoji_helper.rb +++ b/app/helpers/award_emoji_helper.rb @@ -1,9 +1,12 @@ module AwardEmojiHelper def toggle_award_url(awardable) - if @project - url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) + return url_for([:toggle_award_emoji, awardable]) unless @project + + if awardable.is_a?(Note) + # We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (6.5x) + toggle_award_emoji_namespace_project_note_url(namespace_id: @project.namespace_id, project_id: @project.id, id: awardable.id) else - url_for([:toggle_award_emoji, awardable]) + url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) end end end -- cgit v1.2.1 From 4de883bc81d605d8cd0d138be5d0e2ff2a263d77 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Sat, 15 Oct 2016 12:09:02 +0200 Subject: Use GrapeDSL for commits --- lib/api/commits.rb | 129 ++++++++++++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 14ddc8c9a62..617a240318a 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -6,33 +6,40 @@ module API before { authenticate! } before { authorize! :download_code, user_project } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do - # Get a project repository commits - # - # Parameters: - # id (required) - The ID of a project - # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used - # since (optional) - Only commits after or in this date will be returned - # until (optional) - Only commits before or in this date will be returned - # Example Request: - # GET /projects/:id/repository/commits + desc 'Get a project repository commits' do + success Entities::RepoCommit + end + params do + optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used' + optional :since, type: String, desc: 'Only commits after or in this date will be returned' + optional :until, type: String, desc: 'Only commits before or in this date will be returned' + optional :page, type: Integer, default: 0, desc: 'The page for pagination' + optional :per_page, type: Integer, default: 20, desc: 'The number of results per page' + end get ":id/repository/commits" do + # TODO remove the next line for 9.0, use DateTime type in the params block datetime_attributes! :since, :until - page = (params[:page] || 0).to_i - per_page = (params[:per_page] || 20).to_i ref = params[:ref_name] || user_project.try(:default_branch) || 'master' - after = params[:since] - before = params[:until] + offset = params[:page] * params[:per_page] + + commits = user_project.repository.commits(ref, + limit: params[:per_page], + offset: offset, + after: params[:since], + before: params[:until]) - commits = user_project.repository.commits(ref, limit: per_page, offset: page * per_page, after: after, before: before) present commits, with: Entities::RepoCommit end desc 'Commit multiple file changes as one commit' do + success Entities::RepoCommitDetail detail 'This feature was introduced in GitLab 8.13' end - params do requires :id, type: Integer, desc: 'The project ID' requires :branch_name, type: String, desc: 'The name of branch' @@ -41,7 +48,6 @@ module API optional :author_email, type: String, desc: 'Author email for commit' optional :author_name, type: String, desc: 'Author name for commit' end - post ":id/repository/commits" do authorize! :push_code, user_project @@ -65,79 +71,82 @@ module API end end - # Get a specific commit of a project - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit hash or name of a repository branch or tag - # Example Request: - # GET /projects/:id/repository/commits/:sha + desc 'Get a specific commit of a project' do + success Entities::RepoCommitDetail + failure [[404, 'Not Found']] + end + params do + requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' + end get ":id/repository/commits/:sha" do - sha = params[:sha] - commit = user_project.commit(sha) + commit = user_project.commit(params[:sha]) + not_found! "Commit" unless commit + present commit, with: Entities::RepoCommitDetail end - # Get the diff for a specific commit of a project - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit or branch name - # Example Request: - # GET /projects/:id/repository/commits/:sha/diff + desc 'Get the diff for a specific commit of a project' do + failure [[404, 'Not Found']] + end + params do + requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' + end get ":id/repository/commits/:sha/diff" do - sha = params[:sha] - commit = user_project.commit(sha) + commit = user_project.commit(params[:sha]) + not_found! "Commit" unless commit + commit.raw_diffs.to_a end - # Get a commit's comments - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit hash - # Examples: - # GET /projects/:id/repository/commits/:sha/comments + desc "Get a commit's comments" do + success Entities::CommitNote + failure [[404, 'Not Found']] + end + params do + requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag' + optional :per_page, type: Integer, desc: 'The amount of items per page for paginaion' + optional :page, type: Integer, desc: 'The page number for pagination' + end get ':id/repository/commits/:sha/comments' do - sha = params[:sha] - commit = user_project.commit(sha) + commit = user_project.commit(params[:sha]) + not_found! 'Commit' unless commit notes = Note.where(commit_id: commit.id).order(:created_at) + present paginate(notes), with: Entities::CommitNote end - # Post comment to commit - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit hash - # note (required) - Text of comment - # path (optional) - The file path - # line (optional) - The line number - # line_type (optional) - The type of line (new or old) - # Examples: - # POST /projects/:id/repository/commits/:sha/comments + desc 'Post comment to commit' do + success Entities::CommitNote + end + params do + requires :sha, type: String, regexp: /\A\h{6,40}\z/, desc: "The commit's SHA" + requires :note, type: String, desc: 'The text of the comment' + optional :path, type: String, desc: 'The file path' + given :path do + requires :line, type: Integer, desc: 'The line number' + requires :line_type, type: String, values: ['new', 'old'], default: 'new', desc: 'The type of the line' + end + end post ':id/repository/commits/:sha/comments' do - required_attributes! [:note] - - sha = params[:sha] - commit = user_project.commit(sha) + commit = user_project.commit(params[:sha]) not_found! 'Commit' unless commit + opts = { note: params[:note], noteable_type: 'Commit', commit_id: commit.id } - if params[:path] && params[:line] && params[:line_type] + if params[:path] commit.raw_diffs(all_diffs: true).each do |diff| next unless diff.new_path == params[:path] lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line) lines.each do |line| - next unless line.new_pos == params[:line].to_i && line.type == params[:line_type] + next unless line.new_pos == params[:line] && line.type == params[:line_type] break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos) end -- cgit v1.2.1 From d3e64a951c3369e4b60e0aec626f4ed682045aad Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Oct 2016 17:44:16 +0200 Subject: Fix aesthetic issues on LDAP login. --- app/views/devise/sessions/_new_ldap.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index b26efbb4535..f83231bc9aa 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,6 +1,6 @@ = form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do .form-group - = label_tag "#{server['label']} Login", for: :username + = label_tag :username, "#{server['label']} Login" = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } .form-group = label_tag :password @@ -10,4 +10,4 @@ %label{for: "remember_me"} = check_box_tag :remember_me, '1', false, id: 'remember_me' %span Remember me - = button_tag "Sign in", class: "btn-save btn" + = button_tag "Sign in", class: "btn-save btn btn-block" -- cgit v1.2.1 From 4477d68bfb5a16046687062a874155ca2038079e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Oct 2016 17:44:43 +0200 Subject: Split login box into different panes, for ldap enabled screens. --- app/views/devise/shared/_signin_box.html.haml | 29 ++++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 810dd5ab687..736f5b3ffc9 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -1,15 +1,16 @@ -#login-pane.login-box{ role: 'tabpanel', class: 'tab-pane active' } - .login-body - - if form_based_providers.any? - - if crowd_enabled? - %div.tab-pane.active{id: "tab-crowd"} - = render 'devise/sessions/new_crowd' - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} - = render 'devise/sessions/new_ldap', server: server - - if signin_enabled? - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' +- if form_based_providers.any? + - if crowd_enabled? + .login-box.tab-pane.active{id: "crowd", role: 'tabpanel', class: 'tab-pane'} + .login-body + = render 'devise/sessions/new_crowd' + - @ldap_servers.each_with_index do |server, i| + .login-box.tab-pane{id: "#{server['provider_name']}", role: 'tabpanel', class: (:active if i.zero? && !crowd_enabled?)} + .login-body + = render 'devise/sessions/new_ldap', server: server + - if signin_enabled? + .login-box.tab-pane{id: 'ldap-standard', role: 'tabpanel'} + .login-body + = render 'devise/sessions/new_base' - - elsif signin_enabled? - = render 'devise/sessions/new_base' +- elsif signin_enabled? + = render 'devise/sessions/new_base' -- cgit v1.2.1 From a185c32bef3305371e02df0044c17ac00d9cb4f2 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Oct 2016 17:45:27 +0200 Subject: Fix tab refs in ldap tabs. --- app/views/devise/shared/_tabs_ldap.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index e276e91433a..239170d5b16 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -1,10 +1,10 @@ %ul.new-session-tabs.nav-links.nav-tabs - if crowd_enabled? %li.active - = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' + = link_to "Crowd", "#crowd", 'data-toggle' => 'tab' - @ldap_servers.each_with_index do |server, i| %li{class: (:active if i.zero? && !crowd_enabled?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + = link_to server['label'], "#{server['provider_name']}", 'data-toggle' => 'tab' - if signin_enabled? %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' + = link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab' -- cgit v1.2.1 From 8a62d0cec518f54dd93d8d6c6571dc2d545f46a1 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Oct 2016 17:49:13 +0200 Subject: Fix href to server pane. --- app/views/devise/shared/_tabs_ldap.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index 239170d5b16..a057f126c45 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -4,7 +4,7 @@ = link_to "Crowd", "#crowd", 'data-toggle' => 'tab' - @ldap_servers.each_with_index do |server, i| %li{class: (:active if i.zero? && !crowd_enabled?)} - = link_to server['label'], "#{server['provider_name']}", 'data-toggle' => 'tab' + = link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab' - if signin_enabled? %li = link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab' -- cgit v1.2.1 From b4f84ff728e28ea8d1f516f2fefb2e99c62ee076 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Oct 2016 18:13:21 +0200 Subject: Use correct verbiage for username in login form. --- app/views/devise/sessions/_new_ldap.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index f83231bc9aa..91196730499 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,6 +1,6 @@ = form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do .form-group - = label_tag :username, "#{server['label']} Login" + = label_tag :username, "#{server['label']} Username" = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } .form-group = label_tag :password -- cgit v1.2.1 From 3bc25264a1d8d8a758068a06dcb31e718acfcac7 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Oct 2016 19:06:43 +0200 Subject: Fix superfluous margin on new_ldap submit. --- app/views/devise/sessions/_new_ldap.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 91196730499..c18bc2ac413 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -10,4 +10,4 @@ %label{for: "remember_me"} = check_box_tag :remember_me, '1', false, id: 'remember_me' %span Remember me - = button_tag "Sign in", class: "btn-save btn btn-block" + = submit_tag "Sign in", class: "btn-save btn" -- cgit v1.2.1 From 60e6eca0d0b0926aeb248332c88be886f2f8e69f Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Oct 2016 19:53:33 +0200 Subject: Fix login tests for crowd to use #crowd. --- spec/views/devise/shared/_signin_box.html.haml_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb index ee362e6fcb3..1397bfa5864 100644 --- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb +++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb @@ -12,13 +12,13 @@ describe 'devise/shared/_signin_box' do render - expect(rendered).to have_selector('#tab-crowd form') + expect(rendered).to have_selector('#crowd form') end it 'is not shown when Crowd is disabled' do render - expect(rendered).not_to have_selector('#tab-crowd') + expect(rendered).not_to have_selector('#crowd') end end -- cgit v1.2.1 From 357ff6700dcb0e13c5888a9157f8890654e5ca4a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Oct 2016 20:04:21 +0200 Subject: Contain normal signin in loginbox. --- app/views/devise/shared/_signin_box.html.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 736f5b3ffc9..86edaf14e43 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -13,4 +13,6 @@ = render 'devise/sessions/new_base' - elsif signin_enabled? - = render 'devise/sessions/new_base' + .login-box.tab-pane.active{id: 'login-pane', role: 'tabpanel'} + .login-body + = render 'devise/sessions/new_base' -- cgit v1.2.1 From b63b13f930804c83682f6424b8dddf2236649d6d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 11:15:44 +0200 Subject: Fix Changelog entry for fix to CI artifacts erase [ci skip] --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b17a9f219..a2b8641d12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.13.0 (2016-10-22) -v 8.13.0 (unreleased) - Avoid race condition when asynchronously removing expired artifacts. (!6881) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) -- cgit v1.2.1 From 829a708a970b31afdcda21fff072eda0c61dfd4c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 11:22:22 +0200 Subject: Fix remaining specs failures --- app/controllers/projects/merge_requests_controller.rb | 7 ++++++- app/views/projects/environments/index.html.haml | 2 +- app/views/projects/environments/show.html.haml | 2 +- spec/features/merge_requests/widget_deployments_spec.rb | 10 ++++++---- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 86e12660c23..7bac0a2b1c7 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -412,11 +412,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController project = environment.project deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + stop_url = + if environment.stoppable? && can?(current_user, :create_deployment, environment) + stop_namespace_project_environment_path(project.namespace, project, environment) + end + { id: environment.id, name: environment.name, url: namespace_project_environment_path(project.namespace, project, environment), - stop_url: (stop_namespace_project_environment_path(project.namespace, project, environment) if environment.stoppable?), + stop_url: stop_url, external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, deployed_at: deployment.try(:created_at), diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 705a1360ec5..8f555afcf11 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -23,7 +23,7 @@ New environment .environments-container - - if @environments.blank? + - if @all_environments.blank? .blank-state.blank-state-no-icon %h2.blank-state-title You don't have any environments right now. diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index bf082a05c39..bcac73d3698 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -3,7 +3,7 @@ = render "projects/pipelines/head" %div{ class: container_class } - .top-area + .top-area.adjust .col-md-9 %h3.page-title= @environment.name.capitalize .col-md-3 diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb index 0c8ee844b47..1be35b27d84 100644 --- a/spec/features/merge_requests/widget_deployments_spec.rb +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -9,9 +9,8 @@ feature 'Widget Deployments Header', feature: true, js: true do given(:merge_request) { create(:merge_request, :merged) } given(:environment) { create(:environment, project: project) } given(:role) { :developer } - given!(:deployment) do - create(:deployment, environment: environment, sha: project.commit('master').id) - end + given(:sha) { project.commit('master').id } + given!(:deployment) { create(:deployment, environment: environment, sha: sha) } given!(:manual) { } background do @@ -31,7 +30,10 @@ feature 'Widget Deployments Header', feature: true, js: true do given(:pipeline) { create(:ci_pipeline, project: project) } given(:build) { create(:ci_build, pipeline: pipeline) } given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } - given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + given(:deployment) do + create(:deployment, environment: environment, ref: merge_request.target_branch, + sha: sha, deployable: build, on_stop: 'close_app') + end background do wait_for_ajax -- cgit v1.2.1 From 0aa232704c5df68f0ed111e355a07cfaf241e8a9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 12:02:50 +0200 Subject: Add `stop!` to `environment` --- .../projects/environments_controller.rb | 11 ++++--- app/models/environment.rb | 6 ++++ spec/models/environment_spec.rb | 37 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index e243253c5f1..5837f913d16 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -9,8 +9,12 @@ class Projects::EnvironmentsController < Projects::ApplicationController def index @scope = params[:scope] @all_environments = project.environments - @environments = @scope == 'stopped' ? - @all_environments.stopped : @all_environments.available + @environments = + if @scope == 'stopped' then + @all_environments.stopped + else + @all_environments.available + end end def show @@ -45,8 +49,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController def stop return render_404 unless @environment.stoppable? - action = @environment.stop_action - new_action = action.active? ? action : action.play(current_user) + new_action = @environment.stop!(current_user) redirect_to polymorphic_path([project.namespace.becomes(Namespace), project, new_action]) end diff --git a/app/models/environment.rb b/app/models/environment.rb index ff55e751f70..d575f1dc73a 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -88,4 +88,10 @@ class Environment < ActiveRecord::Base def stoppable? available? && stop_action.present? end + + def stop!(current_user) + return unless stoppable? + + stop_action.play(current_user) + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 1c1fef57fc4..e1755f940b3 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -128,4 +128,41 @@ describe Environment, models: true do end end end + + describe '#stop!' do + let(:user) { create(:user) } + + subject { environment.stop!(user) } + + before do + expect(environment).to receive(:stoppable?).and_call_original + end + + context 'when no other actions' do + it { is_expected.to be_nil } + end + + context 'when matching action is defined' do + let(:build) { create(:ci_build) } + let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } + + context 'when action did not yet finish' do + let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } + + it 'returns the same action' do + is_expected.to eq(close_action) + is_expected.to include(user: user) + end + end + + context 'if action did finish' do + let!(:close_action) { create(:ci_build, :manual, :success, pipeline: build.pipeline, name: 'close_app') } + + it 'returns a new action of the same type' do + is_expected.to be_persisted + is_expected.to include(name: close_action.name, user: user) + end + end + end + end end -- cgit v1.2.1 From ad85928752ea6c41863c6b8225ff2279e3044bef Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 12:22:51 +0200 Subject: Add logical validation to gitlab-ci.yml --- .../projects/environments_controller.rb | 2 +- lib/ci/gitlab_ci_yaml_processor.rb | 30 ++++++++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 46 ++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 5837f913d16..ea22b2dcc15 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -10,7 +10,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController @scope = params[:scope] @all_environments = project.environments @environments = - if @scope == 'stopped' then + if @scope == 'stopped' @all_environments.stopped else @all_environments.available diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 2fd1fced65c..3e33c9399e2 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -109,6 +109,7 @@ module Ci validate_job_stage!(name, job) validate_job_dependencies!(name, job) + validate_job_environment!(name, job) end end @@ -150,6 +151,35 @@ module Ci end end + def validate_job_environment!(name, job) + return unless job[:environment] + return unless job[:environment].is_a?(Hash) + + environment = job[:environment] + validate_on_stop_job!(name, environment, environment[:on_stop]) + end + + def validate_on_stop_job!(name, environment, on_stop) + return unless on_stop + + on_stop_job = @jobs[on_stop.to_sym] + unless on_stop_job + raise ValidationError, "#{name} job: on_stop job #{on_stop} is not defined" + end + + unless on_stop_job[:environment] + raise ValidationError, "#{name} job: on_stop job #{on_stop} does not have environment defined" + end + + unless on_stop_job[:environment][:name] == environment[:name] + raise ValidationError, "#{name} job: on_stop job #{on_stop} have different environment name" + end + + unless on_stop_job[:environment][:action] == 'stop' + raise ValidationError, "#{name} job: on_stop job #{on_stop} needs to have action stop defined" + end + end + def process?(only_params, except_params, ref, tag, trigger_request) if only_params.present? return false unless matching?(only_params, ref, tag, trigger_request) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index a46ff07f625..84f21631719 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -796,6 +796,52 @@ module Ci expect { builds }.to raise_error("jobs:deploy_to_production:environment name #{Gitlab::Regex.environment_name_regex_message}") end end + + context 'when on_stop is specified' do + let(:review) { { stage: 'deploy', script: 'test', environment: { name: 'review', on_stop: 'close_review' } } } + let(:config) { { review: review, close_review: close_review }.compact } + + context 'with matching job' do + let(:close_review) { { stage: 'deploy', script: 'test', environment: { name: 'review', action: 'stop' } } } + + it 'does return a list of builds' do + expect(builds.size).to eq(2) + expect(builds.first[:environment]).to eq('review') + end + end + + context 'without matching job' do + let(:close_review) { nil } + + it 'raises error' do + expect { builds }.to raise_error('review job: on_stop job close_review is not defined') + end + end + + context 'with close job without environment' do + let(:close_review) { { stage: 'deploy', script: 'test' } } + + it 'raises error' do + expect { builds }.to raise_error('review job: on_stop job close_review does not have environment defined') + end + end + + context 'with close job for different environment' do + let(:close_review) { { stage: 'deploy', script: 'test', environment: 'production' } } + + it 'raises error' do + expect { builds }.to raise_error('review job: on_stop job close_review have different environment name') + end + end + + context 'with close job without stop action' do + let(:close_review) { { stage: 'deploy', script: 'test', environment: { name: 'review' } } } + + it 'raises error' do + expect { builds }.to raise_error('review job: on_stop job close_review needs to have action stop defined') + end + end + end end describe "Dependencies" do -- cgit v1.2.1 From f8907566a50b6fe1d7101557d53e7335c7803984 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 17 Oct 2016 20:04:21 +0200 Subject: Fix top border-radius of tabs. --- app/assets/stylesheets/pages/login.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 4c0c0839fe2..b7789ad3991 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -131,8 +131,13 @@ flex: 1; text-align: center; + &:first-of-type { + border-top-left-radius: 2px; + } + &:last-of-type { border-left: 1px solid $border-color; + border-top-right-radius: 2px; } &:not(.active) { -- cgit v1.2.1 From 348236ed93676e5360b167c397cc7205c6f80f9a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 18 Oct 2016 12:46:37 +0200 Subject: Fix aesthetic issues on CROWD login. --- app/views/devise/sessions/_new_crowd.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index e82a08cdb0c..1d381ad7893 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,6 +1,6 @@ = form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do .form-group - = label_tag 'Username or email', for: :username + = label_tag :username, 'Username or email' = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } .form-group = label_tag :password @@ -10,4 +10,4 @@ %label{for: "remember_me"} = check_box_tag :remember_me, '1', false, id: 'remember_me' %span Remember me - = button_tag "Sign in", class: "btn-save btn" + = submit_tag "Sign in", class: "btn-save btn" -- cgit v1.2.1 From 4622f7e298edeb7211b94f3634f79dfc78db3918 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 12:53:30 +0200 Subject: Reload pipeline settings when saving new settings --- app/views/projects/pipelines_settings/show.html.haml | 2 +- spec/features/pipelines_settings_spec.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml index 8c7222bfe3d..0740e9b56ab 100644 --- a/app/views/projects/pipelines_settings/show.html.haml +++ b/app/views/projects/pipelines_settings/show.html.haml @@ -7,7 +7,7 @@ .col-lg-9 %h5.prepend-top-0 Pipelines - = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project), remote: true, authenticity_token: true do |f| + = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f| %fieldset.builds-feature - unless @repository.gitlab_ci_yml .form-group diff --git a/spec/features/pipelines_settings_spec.rb b/spec/features/pipelines_settings_spec.rb index dcc364a3d01..76cb240ea98 100644 --- a/spec/features/pipelines_settings_spec.rb +++ b/spec/features/pipelines_settings_spec.rb @@ -24,11 +24,12 @@ feature "Pipelines settings", feature: true do context 'for master' do given(:role) { :master } - scenario 'be allowed to change' do + scenario 'be allowed to change', js: true do fill_in('Test coverage parsing', with: 'coverage_regex') click_on 'Save changes' expect(page.status_code).to eq(200) + expect(page).to have_button('Save changes', disabled: false) expect(page).to have_field('Test coverage parsing', with: 'coverage_regex') end end -- cgit v1.2.1 From 9a6f73a6338a8ca6262bf5fff31042ad84569613 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 18 Oct 2016 12:54:02 +0200 Subject: Use border-radius-default instead of hardcoding value. --- app/assets/stylesheets/pages/login.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index b7789ad3991..e6d9be5185d 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -124,20 +124,20 @@ display: -webkit-flex; display: flex; box-shadow: 0 0 0 1px $border-color; - border-top-right-radius: 2px; - border-top-left-radius: 2px; + border-top-right-radius: $border-radius-default; + border-top-left-radius: $border-radius-default; li { flex: 1; text-align: center; &:first-of-type { - border-top-left-radius: 2px; + border-top-left-radius: $border-radius-default; } &:last-of-type { border-left: 1px solid $border-color; - border-top-right-radius: 2px; + border-top-right-radius: $border-radius-default; } &:not(.active) { -- cgit v1.2.1 From b8a49e574a5cf513042f8e12d536d25fe08da801 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 12:58:11 +0200 Subject: Move specs for project pipeline settings page --- spec/features/pipelines_settings_spec.rb | 36 ---------------------- .../projects/settings/pipelines_settings_spec.rb | 36 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 36 deletions(-) delete mode 100644 spec/features/pipelines_settings_spec.rb create mode 100644 spec/features/projects/settings/pipelines_settings_spec.rb diff --git a/spec/features/pipelines_settings_spec.rb b/spec/features/pipelines_settings_spec.rb deleted file mode 100644 index 76cb240ea98..00000000000 --- a/spec/features/pipelines_settings_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'spec_helper' - -feature "Pipelines settings", feature: true do - include GitlabRoutingHelper - - let(:project) { create(:empty_project) } - let(:user) { create(:user) } - let(:role) { :developer } - - background do - login_as(user) - project.team << [user, role] - visit namespace_project_pipelines_settings_path(project.namespace, project) - end - - context 'for developer' do - given(:role) { :developer } - - scenario 'to be disallowed to view' do - expect(page.status_code).to eq(404) - end - end - - context 'for master' do - given(:role) { :master } - - scenario 'be allowed to change', js: true do - fill_in('Test coverage parsing', with: 'coverage_regex') - click_on 'Save changes' - - expect(page.status_code).to eq(200) - expect(page).to have_button('Save changes', disabled: false) - expect(page).to have_field('Test coverage parsing', with: 'coverage_regex') - end - end -end diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb new file mode 100644 index 00000000000..76cb240ea98 --- /dev/null +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +feature "Pipelines settings", feature: true do + include GitlabRoutingHelper + + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + let(:role) { :developer } + + background do + login_as(user) + project.team << [user, role] + visit namespace_project_pipelines_settings_path(project.namespace, project) + end + + context 'for developer' do + given(:role) { :developer } + + scenario 'to be disallowed to view' do + expect(page.status_code).to eq(404) + end + end + + context 'for master' do + given(:role) { :master } + + scenario 'be allowed to change', js: true do + fill_in('Test coverage parsing', with: 'coverage_regex') + click_on 'Save changes' + + expect(page.status_code).to eq(200) + expect(page).to have_button('Save changes', disabled: false) + expect(page).to have_field('Test coverage parsing', with: 'coverage_regex') + end + end +end -- cgit v1.2.1 From e01e2ad5d3371ee6ebdceedf023e1052aa9e6c06 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 12:59:16 +0200 Subject: Add Changelog entry for pipeline settings button fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90379e011d6..a33c837a1b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.13.0 (2016-10-22) + - Fix save button on project pipeline settings page. (!6955) - Avoid race condition when asynchronously removing expired artifacts. (!6881) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) -- cgit v1.2.1 From c4e45d89d407016c89c5217cb91a7a82cf8710f1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 13:51:12 +0200 Subject: Fix remaining offenses --- spec/features/merge_requests/widget_deployments_spec.rb | 2 +- spec/models/environment_spec.rb | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb index 1be35b27d84..6676821b807 100644 --- a/spec/features/merge_requests/widget_deployments_spec.rb +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -32,7 +32,7 @@ feature 'Widget Deployments Header', feature: true, js: true do given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') } given(:deployment) do create(:deployment, environment: environment, ref: merge_request.target_branch, - sha: sha, deployable: build, on_stop: 'close_app') + sha: sha, deployable: build, on_stop: 'close_app') end background do diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index e1755f940b3..a94e6d0165f 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -150,8 +150,8 @@ describe Environment, models: true do let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } it 'returns the same action' do - is_expected.to eq(close_action) - is_expected.to include(user: user) + expect(subject).to eq(close_action) + expect(subject.user).to eq(user) end end @@ -160,7 +160,8 @@ describe Environment, models: true do it 'returns a new action of the same type' do is_expected.to be_persisted - is_expected.to include(name: close_action.name, user: user) + expect(subject.name).to eq(close_action.name) + expect(subject.user).to eq(user) end end end -- cgit v1.2.1 From 9c8c5e9dc050f32cec05f6903105ff34d726979b Mon Sep 17 00:00:00 2001 From: amaia Date: Mon, 17 Oct 2016 05:53:12 -0700 Subject: fix: commit messages being double-escaped in activies tab --- CHANGELOG.md | 1 + lib/banzai/filter/html_entity_filter.rb | 2 +- spec/lib/banzai/filter/html_entity_filter_spec.rb | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90379e011d6..5d6d5bc591c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,6 +119,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fixes padding in all clipboard icons that have .btn class - Fix a typo in doc/api/labels.md + - Fix double-escaping in activities tab (Alexandre Maia) - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling - Make guests unable to view MRs on private projects diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb index e008fd428b0..f3bd587c28b 100644 --- a/lib/banzai/filter/html_entity_filter.rb +++ b/lib/banzai/filter/html_entity_filter.rb @@ -5,7 +5,7 @@ module Banzai # Text filter that escapes these HTML entities: & " < > class HtmlEntityFilter < HTML::Pipeline::TextFilter def call - ERB::Util.html_escape(text) + ERB::Util.html_escape_once(text) end end end diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb index 4c68ce6d6e4..f9e6bd609f0 100644 --- a/spec/lib/banzai/filter/html_entity_filter_spec.rb +++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb @@ -11,4 +11,9 @@ describe Banzai::Filter::HtmlEntityFilter, lib: true do expect(output).to eq(escaped) end + + it 'does not double-escape' do + escaped = ERB::Util.html_escape("Merge branch 'blabla' into 'master'") + expect(filter(escaped)).to eq(escaped) + end end -- cgit v1.2.1 From 4012c695cb17f77f3fc928e9eef5c2fd679defc1 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 17 Oct 2016 11:07:44 +0100 Subject: Stop event_commit_title from escaping its output Return a non-html-safe, unescaped String instead of ActiveSupport::SafeBuffer to preserve safety when the output is misused. Currently there's oly one user, which does the right thing. Closes #23311 --- app/helpers/events_helper.rb | 2 +- spec/helpers/events_helper_spec.rb | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index bfedcb1c42b..f8ded05c31a 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -154,7 +154,7 @@ module EventsHelper end def event_commit_title(message) - escape_once(truncate(message.split("\n").first, length: 70)) + (message.split("\n").first || "").truncate(70) rescue "--broken encoding" end diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index 022aba0c0d0..594b40303bc 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -62,4 +62,21 @@ describe EventsHelper do expect(helper.event_note(input)).to eq(expected) end end + + describe '#event_commit_title' do + let(:message) { "foo & bar " + "A" * 70 + "\n" + "B" * 80 } + subject { helper.event_commit_title(message) } + + it "returns the first line, truncated to 70 chars" do + is_expected.to eq(message[0..66] + "...") + end + + it "is not html-safe" do + is_expected.not_to be_a(ActiveSupport::SafeBuffer) + end + + it "handles empty strings" do + expect(helper.event_commit_title("")).to eq("") + end + end end -- cgit v1.2.1 From 3db585d27c005397aab3fa05cbe77853bc1019be Mon Sep 17 00:00:00 2001 From: the-undefined Date: Wed, 12 Oct 2016 06:12:31 +0100 Subject: Add Nofollow for uppercased scheme in external url Ensure that external URLs with non-lowercase protocols will be attributed with 'nofollow noreferrer' and open up in a new window. Covers the edge cases to skip: - HTTPS schemes - relative links Closes #22782 --- CHANGELOG.md | 1 + lib/banzai/filter/external_link_filter.rb | 34 +++++++++++++++++++--- .../lib/banzai/filter/external_link_filter_spec.rb | 34 ++++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dbe1832de0..8adc80052e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Retouch environments list and deployments list - Add multiple command support for all label related slash commands !6780 (barthc) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Add Nofollow for uppercased scheme in external urls !6820 (the-undefined) - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb index 0a29c547a4d..2f19b59e725 100644 --- a/lib/banzai/filter/external_link_filter.rb +++ b/lib/banzai/filter/external_link_filter.rb @@ -3,10 +3,17 @@ module Banzai # HTML Filter to modify the attributes of external links class ExternalLinkFilter < HTML::Pipeline::Filter def call - # Skip non-HTTP(S) links and internal links - doc.xpath("descendant-or-self::a[starts-with(@href, 'http') and not(starts-with(@href, '#{internal_url}'))]").each do |node| - node.set_attribute('rel', 'nofollow noreferrer') - node.set_attribute('target', '_blank') + links.each do |node| + href = href_to_lowercase_scheme(node["href"].to_s) + + unless node["href"].to_s == href + node.set_attribute('href', href) + end + + if href =~ /\Ahttp(s)?:\/\// && external_url?(href) + node.set_attribute('rel', 'nofollow noreferrer') + node.set_attribute('target', '_blank') + end end doc @@ -14,6 +21,25 @@ module Banzai private + def links + query = 'descendant-or-self::a[@href and not(@href = "")]' + doc.xpath(query) + end + + def href_to_lowercase_scheme(href) + scheme_match = href.match(/\A(\w+):\/\//) + + if scheme_match + scheme_match.to_s.downcase + scheme_match.post_match + else + href + end + end + + def external_url?(url) + !url.start_with?(internal_url) + end + def internal_url @internal_url ||= Gitlab.config.gitlab.url end diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb index 695a5bc6fd4..167397c736b 100644 --- a/spec/lib/banzai/filter/external_link_filter_spec.rb +++ b/spec/lib/banzai/filter/external_link_filter_spec.rb @@ -46,4 +46,38 @@ describe Banzai::Filter::ExternalLinkFilter, lib: true do expect(doc.at_css('a')['rel']).to include 'noreferrer' end end + + context 'for non-lowercase scheme links' do + let(:doc_with_http) { filter %q() } + let(:doc_with_https) { filter %q(

      Google

      ) } + + it 'adds rel="nofollow" to external links' do + expect(doc_with_http.at_css('a')).to have_attribute('rel') + expect(doc_with_https.at_css('a')).to have_attribute('rel') + + expect(doc_with_http.at_css('a')['rel']).to include 'nofollow' + expect(doc_with_https.at_css('a')['rel']).to include 'nofollow' + end + + it 'adds rel="noreferrer" to external links' do + expect(doc_with_http.at_css('a')).to have_attribute('rel') + expect(doc_with_https.at_css('a')).to have_attribute('rel') + + expect(doc_with_http.at_css('a')['rel']).to include 'noreferrer' + expect(doc_with_https.at_css('a')['rel']).to include 'noreferrer' + end + + it 'skips internal links' do + internal_link = Gitlab.config.gitlab.url + "/sign_in" + url = internal_link.gsub(/\Ahttp/, 'HtTp') + act = %Q(Login) + exp = %Q(Login) + expect(filter(act).to_html).to eq(exp) + end + + it 'skips relative links' do + exp = act = %q(Relative URL) + expect(filter(act).to_html).to eq(exp) + end + end end -- cgit v1.2.1 From baa3d0b3744b6ee7ef067ec39238257748fde71b Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 18 Sep 2016 17:20:40 +0100 Subject: Added tooltip with jobs full name to build items in graph Added status to build tooltip and removed status icon tooltip Added Pipelines class to force tooltips ontop of the dropdown, we cannot do this with data attributes because dropdown toggle is already set Corrected dispatcher invocation --- app/assets/javascripts/pipelines.js.es6 | 11 +++++++++++ app/helpers/ci_status_helper.rb | 4 ++-- app/views/projects/commit/_pipeline_stage.html.haml | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index a7624de6089..dd320c52e44 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -4,6 +4,17 @@ constructor() { $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); this.addMarginToBuildColumns(); + this.initGroupedPipelineTooltips(); + } + + initGroupedPipelineTooltips() { + $('.dropdown-menu-toggle', $('.grouped-pipeline-dropdown').parent()).each(function() { + const $this = $(this); + $this.tooltip({ + title: $this.data('tooltip-title'), + placement: 'bottom' + }); + }); } toggleGraph() { diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index b7f48630bd4..0e727f3dcf0 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -71,10 +71,10 @@ module CiStatusHelper Ci::Runner.shared.blank? end - def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body') + def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body', show_tooltip: true) klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}" title = "#{type.titleize}: #{ci_label_for_status(status)}" - data = { toggle: 'tooltip', placement: tooltip_placement, container: container } + data = { toggle: 'tooltip', placement: tooltip_placement, container: container } if show_tooltip if path link_to ci_icon_for_status(status), path, diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 289aa5178b1..993fc89d23d 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -5,7 +5,7 @@ - is_playable = status.playable? && can?(current_user, :update_build, @project) %li.build{ class: ("playable" if is_playable) } .curve - .build-content + .build-content{ { data: { toggle: 'tooltip', title: "#{group_name} - #{status.status}", placement: 'bottom' } } } = render "projects/#{status.to_partial_path}_pipeline", subject: status - else %li.build -- cgit v1.2.1 From 5dcb57e5ec76ea04ee5175b826226f843633a19b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 30 Aug 2016 13:48:22 -0500 Subject: Add tooltip icon to retried builds --- app/assets/stylesheets/pages/builds.scss | 2 +- app/views/projects/builds/_sidebar.html.haml | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 2fbf0cf34bf..a7b23a24e58 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -195,7 +195,7 @@ .build-job { position: relative; - .fa { + .fa-check { position: absolute; left: 15px; top: 20px; diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 966633f1f89..2c77d438048 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -135,11 +135,5 @@ = build.name - else = build.id - - - if @build.retried? - %li.active - %a - Build ##{@build.id} - · - %i.fa.fa-warning - This build was retried. + - if @build.retried? + %i.fa.fa-warning.has-tooltip{data: { container: 'body', placement: 'bottom' }, title: 'This build was retried'} -- cgit v1.2.1 From 0912022d32fccfeeb40ddbb9efc7456ae149203b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 8 Sep 2016 09:30:44 -0500 Subject: Add background color and icon to retried builds --- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/builds.scss | 9 +++++++++ app/assets/stylesheets/pages/pipelines.scss | 4 ++++ app/views/projects/builds/_sidebar.html.haml | 4 ++-- app/views/projects/ci/builds/_build.html.haml | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 7690d65de8e..eafe84570a8 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -16,6 +16,7 @@ $white-light: #fff; $white-normal: #ededed; $white-dark: #ececec; +$gray-lightest: #fdfdfd; $gray-light: #fafafa; $gray-lighter: #f9f9f9; $gray-normal: #f5f5f5; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index a7b23a24e58..2793282c1cb 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -210,9 +210,18 @@ } } + &.retried { + background-color: $gray-lightest; + } + &:hover { background-color: $row-hover; } + + .fa-refresh { + font-size: 13px; + margin-left: 3px; + } } } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 7b71876b822..e40ce6d76b1 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -73,6 +73,10 @@ border-top-width: 1px; } + .build.retried { + background-color: $gray-lightest; + } + .commit-link { .ci-status { diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 2c77d438048..687b93916b0 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -126,7 +126,7 @@ .builds-container - statuses.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| - .build-job{class: ('active' if build == @build), data: {stage: build.stage}} + .build-job{class: ('active' if build == @build; 'retried' if @build.retried?), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do = icon('arrow-right') = ci_icon_for_status(build.status) @@ -136,4 +136,4 @@ - else = build.id - if @build.retried? - %i.fa.fa-warning.has-tooltip{data: { container: 'body', placement: 'bottom' }, title: 'This build was retried'} + %i.fa.fa-refresh.has-tooltip{data: { container: 'body', placement: 'bottom' }, title: 'This build was retried'} diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 9248adfde80..cc6f9472c8b 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -6,7 +6,7 @@ - coverage = local_assigns.fetch(:coverage, false) - allow_retry = local_assigns.fetch(:allow_retry, false) -%tr.build.commit +%tr.build.commit{class: ('retried' if defined?(retried) && retried)} %td.status - if can?(current_user, :read_build, build) = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) -- cgit v1.2.1 From d0f35b04555bddf9337c564c757aa913b0d2e739 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 8 Sep 2016 09:40:10 -0500 Subject: Replace retried build icon with fa-refresh --- app/assets/stylesheets/pages/pipelines.scss | 3 ++- app/views/projects/ci/builds/_build.html.haml | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e40ce6d76b1..c79b29743ee 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -113,7 +113,8 @@ .fa { font-size: 12px; - color: $table-text-gray; + color: $gl-text-color; + margin-left: 3px; } .commit-id { diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index cc6f9472c8b..1f01c3d5657 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -35,8 +35,9 @@ - if build.stuck? = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if retried - = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') + + - if defined?(retried) && retried + = icon('refresh', class: 'text-warning has-tooltip', title: 'Build was retried.') .label-container - if build.tags.any? -- cgit v1.2.1 From aa5aa273855054a2b3dcdbb5e4cd4177c98ec022 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 13 Oct 2016 13:59:45 -0500 Subject: Remove retried build label --- app/views/projects/ci/builds/_build.html.haml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 1f01c3d5657..c007f5f2e60 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -48,8 +48,6 @@ %span.label.label-info triggered - if build.try(:allow_failure) %span.label.label-danger allowed to fail - - if retried - %span.label.label-warning retried - if build.manual? %span.label.label-info manual -- cgit v1.2.1 From 87222e27e30e45024c2d930650a876e4447d3708 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 13 Oct 2016 14:35:40 -0500 Subject: show all builds in sidebar, including retried --- app/assets/stylesheets/pages/builds.scss | 4 ++-- app/assets/stylesheets/pages/pipelines.scss | 2 +- app/views/projects/builds/_sidebar.html.haml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 2793282c1cb..d6a55fbd464 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -195,7 +195,7 @@ .build-job { position: relative; - .fa-check { + .fa-arrow-right { position: absolute; left: 15px; top: 20px; @@ -205,7 +205,7 @@ &.active { font-weight: bold; - .fa { + .fa-arrow-right { display: block; } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index c79b29743ee..75aa44b6cea 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -114,7 +114,7 @@ .fa { font-size: 12px; color: $gl-text-color; - margin-left: 3px; + margin-left: 5px; } .commit-id { diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 687b93916b0..5ec214a31d0 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -1,4 +1,4 @@ -- builds = @build.pipeline.builds.latest.to_a +- builds = @build.pipeline.builds.to_a - statuses = ["failed", "pending", "running", "canceled", "success", "skipped"] %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar @@ -126,7 +126,7 @@ .builds-container - statuses.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| - .build-job{class: ('active' if build == @build; 'retried' if @build.retried?), data: {stage: build.stage}} + .build-job{class: ('active' if build == @build; 'retried' if build.retried?), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do = icon('arrow-right') = ci_icon_for_status(build.status) @@ -135,5 +135,5 @@ = build.name - else = build.id - - if @build.retried? + - if build.retried? %i.fa.fa-refresh.has-tooltip{data: { container: 'body', placement: 'bottom' }, title: 'This build was retried'} -- cgit v1.2.1 From 0bd525005ba279eae2733c0c68df280e4fb25b70 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 13 Oct 2016 14:59:23 -0500 Subject: Move sidebar build class into helper --- app/helpers/builds_helper.rb | 7 +++++++ app/views/projects/builds/_sidebar.html.haml | 4 ++-- app/views/projects/ci/builds/_build.html.haml | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 app/helpers/builds_helper.rb diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb new file mode 100644 index 00000000000..b2004b99961 --- /dev/null +++ b/app/helpers/builds_helper.rb @@ -0,0 +1,7 @@ +module BuildsHelper + def sidebar_build_class(build, current_build) + build_class = '' + build_class += ' active' if build == current_build + build_class += ' retried' if build.retried? + end +end diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 5ec214a31d0..839abd82bba 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -126,7 +126,7 @@ .builds-container - statuses.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| - .build-job{class: ('active' if build == @build; 'retried' if build.retried?), data: {stage: build.stage}} + .build-job{class: sidebar_build_class(build, @build), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do = icon('arrow-right') = ci_icon_for_status(build.status) @@ -136,4 +136,4 @@ - else = build.id - if build.retried? - %i.fa.fa-refresh.has-tooltip{data: { container: 'body', placement: 'bottom' }, title: 'This build was retried'} + %i.fa.fa-refresh.has-tooltip{data: { container: 'body', placement: 'bottom' }, title: 'Build was retried'} diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index c007f5f2e60..583fd958849 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -37,7 +37,7 @@ = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - if defined?(retried) && retried - = icon('refresh', class: 'text-warning has-tooltip', title: 'Build was retried.') + = icon('refresh', class: 'text-warning has-tooltip', title: 'Build was retried') .label-container - if build.tags.any? -- cgit v1.2.1 From 2115c360d0e096ca945bb2a39862ae8d14abfa4f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 14 Oct 2016 09:20:55 -0500 Subject: Remove unnecessary retried check & fix test failures --- app/helpers/builds_helper.rb | 1 + app/views/projects/ci/builds/_build.html.haml | 4 ++-- spec/features/projects/pipelines_spec.rb | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb index b2004b99961..f3aaff9140d 100644 --- a/app/helpers/builds_helper.rb +++ b/app/helpers/builds_helper.rb @@ -3,5 +3,6 @@ module BuildsHelper build_class = '' build_class += ' active' if build == current_build build_class += ' retried' if build.retried? + build_class end end diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 583fd958849..bf157e4f64a 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -6,7 +6,7 @@ - coverage = local_assigns.fetch(:coverage, false) - allow_retry = local_assigns.fetch(:allow_retry, false) -%tr.build.commit{class: ('retried' if defined?(retried) && retried)} +%tr.build.commit{class: ('retried' if retried)} %td.status - if can?(current_user, :read_build, build) = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) @@ -36,7 +36,7 @@ - if build.stuck? = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if defined?(retried) && retried + - if retried = icon('refresh', class: 'text-warning has-tooltip', title: 'Build was retried') .label-container diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb index 47482bc3cc9..db56a50e058 100644 --- a/spec/features/projects/pipelines_spec.rb +++ b/spec/features/projects/pipelines_spec.rb @@ -177,7 +177,7 @@ describe "Pipelines" do before { click_on 'Retry failed' } it { expect(page).not_to have_content('Retry failed') } - it { expect(page).to have_content('retried') } + it { expect(page).to have_selector('.retried') } end end -- cgit v1.2.1 From d61f8a18e0f7e9d0ed162827f4e8ae2de3756f5c Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 17 Oct 2016 12:15:57 -0500 Subject: Move build order array to HasStatus module --- app/models/concerns/has_status.rb | 1 + app/views/projects/builds/_sidebar.html.haml | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 9f64f76721d..ef3e73a4072 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -5,6 +5,7 @@ module HasStatus STARTED_STATUSES = %w[running success failed skipped] ACTIVE_STATUSES = %w[pending running] COMPLETED_STATUSES = %w[success failed canceled] + ORDERED_STATUSES = %w[failed pending running canceled success skipped] class_methods do def status_sql diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index 839abd82bba..b1053028279 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -1,5 +1,4 @@ - builds = @build.pipeline.builds.to_a -- statuses = ["failed", "pending", "running", "canceled", "success", "skipped"] %aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar .block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default @@ -124,7 +123,7 @@ %a.stage-item= stage .builds-container - - statuses.each do |build_status| + - HasStatus::ORDERED_STATUSES.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| .build-job{class: sidebar_build_class(build, @build), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do -- cgit v1.2.1 From bc493bd88e35deb9e228aadb029320506814f863 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 30 Aug 2016 15:25:41 -0500 Subject: Add pipelines tab to new MR --- app/views/projects/merge_requests/_new_submit.html.haml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index da6927879a4..3a670cb70dd 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -18,6 +18,7 @@ = f.hidden_field :target_branch .mr-compare.merge-request +<<<<<<< 4a8cb230c6ba28ccdcd6da91855005e4fa0c46bf - if @commits.empty? .commits-empty %h4 @@ -30,6 +31,10 @@ Commits %span.badge= @commits.size - if @pipeline + %li.builds-tab + = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do + Pipelines + %span.badge= @statuses.size %li.builds-tab = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do Builds @@ -47,6 +52,8 @@ - if @pipeline #builds.builds.tab-pane = render "projects/merge_requests/show/builds" + #pipelines.pipelines.tab-pane + = render "projects/merge_requests/show/pipelines" .mr-loading-status = spinner -- cgit v1.2.1 From 04c03d9d5ce1b6240ad0a7f500b2faeba453401e Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 10:22:04 -0500 Subject: Fix pipeline tab content display and badge count --- app/views/projects/merge_requests/show/_pipelines.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/show/_pipelines.html.haml index afe3f3430c6..db0ba88417b 100644 --- a/app/views/projects/merge_requests/show/_pipelines.html.haml +++ b/app/views/projects/merge_requests/show/_pipelines.html.haml @@ -1 +1 @@ -= render "projects/commit/pipelines_list", pipelines: @pipelines, link_to_commit: true += render "projects/commit/pipelines_list", pipelines: @pipeline, link_to_commit: true -- cgit v1.2.1 From 4945959b7c3c930778d1b88367d5e60fd06b52a0 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 11 Oct 2016 08:46:53 -0500 Subject: Fix variable to show multiple pipelines for MR --- app/views/projects/merge_requests/_new_submit.html.haml | 1 - app/views/projects/merge_requests/show/_pipelines.html.haml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 3a670cb70dd..bfe461e6943 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -18,7 +18,6 @@ = f.hidden_field :target_branch .mr-compare.merge-request -<<<<<<< 4a8cb230c6ba28ccdcd6da91855005e4fa0c46bf - if @commits.empty? .commits-empty %h4 diff --git a/app/views/projects/merge_requests/show/_pipelines.html.haml b/app/views/projects/merge_requests/show/_pipelines.html.haml index db0ba88417b..afe3f3430c6 100644 --- a/app/views/projects/merge_requests/show/_pipelines.html.haml +++ b/app/views/projects/merge_requests/show/_pipelines.html.haml @@ -1 +1 @@ -= render "projects/commit/pipelines_list", pipelines: @pipeline, link_to_commit: true += render "projects/commit/pipelines_list", pipelines: @pipelines, link_to_commit: true -- cgit v1.2.1 From 27c762b8db3e2b6896ef88ea1a078745ae33909d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 11:48:36 +0200 Subject: Remove obsolete variable assigned to MR widget view --- app/controllers/projects/merge_requests_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a39b47b6d95..cfa463b6452 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -517,7 +517,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_widget_vars @pipeline = @merge_request.pipeline - @pipelines = [@pipeline].compact end def define_commit_vars -- cgit v1.2.1 From db0e700d2ff9a372a8bd903faf305c0debd9b372 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 12:55:18 +0200 Subject: Use all pipelines variable when creating merge request --- app/controllers/projects/merge_requests_controller.rb | 4 ++-- app/views/projects/merge_requests/_new_submit.html.haml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index cfa463b6452..bce89d0a9ad 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -558,8 +558,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses.relevant if @pipeline + @pipelines = @merge_request.all_pipelines + @statuses = @pipelines.first.statuses.relevant if @pipelines.any? @note_counts = Note.where(commit_id: @commits.map(&:id)). group(:commit_id).count end diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index bfe461e6943..9c6f562f7db 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -29,11 +29,11 @@ = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do Commits %span.badge= @commits.size - - if @pipeline + - if @pipelines.any? %li.builds-tab = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do Pipelines - %span.badge= @statuses.size + %span.badge= @pipelines.size %li.builds-tab = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do Builds @@ -48,7 +48,7 @@ = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane - # This tab is always loaded via AJAX - - if @pipeline + - if @pipelines.any? #builds.builds.tab-pane = render "projects/merge_requests/show/builds" #pipelines.pipelines.tab-pane @@ -65,5 +65,5 @@ :javascript var merge_request = new MergeRequest({ action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}", - buildsLoaded: "#{@pipeline ? 'true' : 'false'}" + buildsLoaded: "#{@pipelines.any? ? 'true' : 'false'}" }); -- cgit v1.2.1 From f7da506529ab0989069fe1c9d9bf9d819c13211d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 13:55:30 +0200 Subject: Extract pipeline vars in merge requests controller --- app/controllers/projects/merge_requests_controller.rb | 18 +++++++++++++----- app/views/projects/merge_requests/_show.html.haml | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index bce89d0a9ad..4df0648a502 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -483,13 +483,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController @noteable = @merge_request @commits_count = @merge_request.commits.count - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses.relevant if @pipeline - if @merge_request.locked_long_ago? @merge_request.unlock_mr @merge_request.close end + + define_pipelines_vars end # Discussion tab data is rendered on html responses of actions @@ -543,6 +542,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController ) end + def define_pipelines_vars + @pipelines = @merge_request.all_pipelines + + if @pipelines.any? + @pipeline = @pipelines.first + @statuses = @pipeline.statuses.relevant + end + end + def define_new_vars @noteable = @merge_request @@ -558,10 +566,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit - @pipelines = @merge_request.all_pipelines - @statuses = @pipelines.first.statuses.relevant if @pipelines.any? @note_counts = Note.where(commit_id: @commits.map(&:id)). group(:commit_id).count + + define_pipelines_vars end def invalid_mr diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 662463bc72b..fb4afe3bff2 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -61,7 +61,7 @@ %li.pipelines-tab = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do Pipelines - %span.badge= @merge_request.all_pipelines.size + %span.badge= @pipelines.size %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do Builds -- cgit v1.2.1 From d5b1d0ea501bb6eb0e18f63f4ddc0ba3f32d372a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 14:15:06 +0200 Subject: Use temporary compare commits when MR not persisted --- app/models/merge_request.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 8c6905a442d..88e46ecdc8b 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -787,21 +787,19 @@ class MergeRequest < ActiveRecord::Base def all_pipelines return unless source_project - @all_pipelines ||= begin - sha = if persisted? - all_commits_sha - else - diff_head_sha - end - - source_project.pipelines.order(id: :desc). - where(sha: sha, ref: source_branch) - end + @all_pipelines ||= source_project.pipelines + .where(sha: all_commits_sha, ref: source_branch) + .order(id: :desc) end # Note that this could also return SHA from now dangling commits + # def all_commits_sha - merge_request_diffs.flat_map(&:commits_sha).uniq + if persisted? + merge_request_diffs.flat_map(&:commits_sha).uniq + else + compare_commits.reverse.map(&:id) + end end def merge_commit -- cgit v1.2.1 From ab8ef17fb2a2aa85a76d9106894280be9b910fda Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 17 Oct 2016 15:31:21 +0200 Subject: Extend merge request tests for all commits method --- app/models/merge_request.rb | 7 +++-- spec/models/merge_request_spec.rb | 58 +++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 88e46ecdc8b..ced0c13b837 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -788,8 +788,8 @@ class MergeRequest < ActiveRecord::Base return unless source_project @all_pipelines ||= source_project.pipelines - .where(sha: all_commits_sha, ref: source_branch) - .order(id: :desc) + .where(sha: all_commits_sha, ref: source_branch) + .order(id: :desc) end # Note that this could also return SHA from now dangling commits @@ -798,7 +798,8 @@ class MergeRequest < ActiveRecord::Base if persisted? merge_request_diffs.flat_map(&:commits_sha).uniq else - compare_commits.reverse.map(&:id) + cached_commits = compare_commits.to_a.reverse.map(&:id) + cached_commits.any? ? cached_commits : [diff_head_sha] end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 91a423b670c..344f69a703e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -640,32 +640,56 @@ describe MergeRequest, models: true do end describe '#all_commits_sha' do - let(:all_commits_sha) do - subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq - end + context 'when merge request is persisted' do + let(:all_commits_sha) do + subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq + end - shared_examples 'returning all SHA' do - it 'returns all SHA from all merge_request_diffs' do - expect(subject.merge_request_diffs.size).to eq(2) - expect(subject.all_commits_sha).to eq(all_commits_sha) + shared_examples 'returning all SHA' do + it 'returns all SHA from all merge_request_diffs' do + expect(subject.merge_request_diffs.size).to eq(2) + expect(subject.all_commits_sha).to eq(all_commits_sha) + end end - end - context 'with a completely different branch' do - before do - subject.update(target_branch: 'v1.0.0') + context 'with a completely different branch' do + before do + subject.update(target_branch: 'v1.0.0') + end + + it_behaves_like 'returning all SHA' end - it_behaves_like 'returning all SHA' + context 'with a branch having no difference' do + before do + subject.update(target_branch: 'v1.1.0') + subject.reload # make sure commits were not cached + end + + it_behaves_like 'returning all SHA' + end end - context 'with a branch having no difference' do - before do - subject.update(target_branch: 'v1.1.0') - subject.reload # make sure commits were not cached + context 'when merge request is not persisted' do + context 'when compare commits are set in the service' do + let(:commit) { spy('commit') } + + subject do + build(:merge_request, compare_commits: [commit, commit]) + end + + it 'returns commits from compare commits temporary data' do + expect(subject.all_commits_sha).to eq [commit, commit] + end end - it_behaves_like 'returning all SHA' + context 'when compare commits are not set in the service' do + subject { build(:merge_request) } + + it 'returns array with diff head sha element only' do + expect(subject.all_commits_sha).to eq [subject.diff_head_sha] + end + end end end -- cgit v1.2.1 From 72d84e48511fcf88b1d9efb622eb37cdff95aa1c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 18 Oct 2016 12:05:47 +0200 Subject: Improve code that creates a list of commits for MR --- app/models/merge_request.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ced0c13b837..fedc35102ef 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -797,9 +797,10 @@ class MergeRequest < ActiveRecord::Base def all_commits_sha if persisted? merge_request_diffs.flat_map(&:commits_sha).uniq + elsif compare_commits + compare_commits.to_a.reverse.map(&:id) else - cached_commits = compare_commits.to_a.reverse.map(&:id) - cached_commits.any? ? cached_commits : [diff_head_sha] + [diff_head_sha] end end -- cgit v1.2.1 From 3032a0ca891808c9aa1ba69d540bf97e40a84670 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 18 Oct 2016 16:46:19 +0200 Subject: Merge two scenarios into one --- spec/features/environments_spec.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 7200e9ad4c1..b565586ee14 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -20,11 +20,8 @@ feature 'Environments', feature: true do end context 'shows two tabs' do - scenario 'does show Available tab with link' do + scenario 'shows "Available" and "Stopped" tab with links' do expect(page).to have_link('Available') - end - - scenario 'does show Stopped tab with link' do expect(page).to have_link('Stopped') end end @@ -47,11 +44,8 @@ feature 'Environments', feature: true do expect(page).to have_link(environment.name) end - scenario 'does show number of opened environments in Available tab' do + scenario 'does show number of available and stopped environments' do expect(page.find('.js-available-environments-count').text).to eq('1') - end - - scenario 'does show number of closed environments in Stopped tab' do expect(page.find('.js-stopped-environments-count').text).to eq('0') end -- cgit v1.2.1 From 82033e2edc43b34823625f886f229e5ae944d4d6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 18 Oct 2016 18:55:11 +0300 Subject: Wait for ajax for every merge request spinach test Signed-off-by: Dmitriy Zaporozhets --- features/steps/project/merge_requests.rb | 4 ++++ features/steps/shared/note.rb | 2 -- features/support/env.rb | 2 +- features/support/wait_for_ajax.rb | 11 ----------- spec/support/wait_for_ajax.rb | 4 ++++ 5 files changed, 9 insertions(+), 14 deletions(-) delete mode 100644 features/support/wait_for_ajax.rb diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 44346d99f44..2ccab4334eb 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -9,6 +9,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps include SharedUser include WaitForAjax + after do + wait_for_ajax if javascript_test? + end + step 'I click link "New Merge Request"' do click_link "New Merge Request" end diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb index d3b5b0bdebe..9dc1fc41b3b 100644 --- a/features/steps/shared/note.rb +++ b/features/steps/shared/note.rb @@ -1,5 +1,3 @@ -require Rails.root.join('features/support/wait_for_ajax') - module SharedNote include Spinach::DSL include WaitForAjax diff --git a/features/support/env.rb b/features/support/env.rb index 569fd444e86..8dbe3624410 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -15,7 +15,7 @@ if ENV['CI'] Knapsack::Adapters::SpinachAdapter.bind end -%w(select2_helper test_env repo_helpers).each do |f| +%w(select2_helper test_env repo_helpers wait_for_ajax).each do |f| require Rails.root.join('spec', 'support', f) end diff --git a/features/support/wait_for_ajax.rb b/features/support/wait_for_ajax.rb deleted file mode 100644 index b90fc112671..00000000000 --- a/features/support/wait_for_ajax.rb +++ /dev/null @@ -1,11 +0,0 @@ -module WaitForAjax - def wait_for_ajax - Timeout.timeout(Capybara.default_max_wait_time) do - loop until finished_all_ajax_requests? - end - end - - def finished_all_ajax_requests? - page.evaluate_script('jQuery.active').zero? - end -end diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb index b90fc112671..0f9dc2dee75 100644 --- a/spec/support/wait_for_ajax.rb +++ b/spec/support/wait_for_ajax.rb @@ -8,4 +8,8 @@ module WaitForAjax def finished_all_ajax_requests? page.evaluate_script('jQuery.active').zero? end + + def javascript_test? + [:selenium, :webkit, :chrome, :poltergeist].include?(Capybara.current_driver) + end end -- cgit v1.2.1 From bb56450acc8d9a8c88aec8abf68da548bdfdb4da Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 18 Oct 2016 17:03:36 +0100 Subject: Fixed height of issue board blank state --- app/assets/stylesheets/pages/boards.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 6e81c12aa55..d8fabbdcebe 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -1,4 +1,3 @@ -lex [v-cloak] { display: none; } @@ -132,7 +131,7 @@ lex } .board-blank-state { - height: 100%; + height: calc(100% - 49px); padding: $gl-padding; background-color: #fff; } -- cgit v1.2.1 From cc6d42861bed8fc4c050ee8906d77bdb49783de5 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 18 Oct 2016 17:28:57 +0100 Subject: Backport git access spec changes from EE These were introduced in: --- spec/lib/gitlab/git_access_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index de68e32e5b4..a5aa387f4f7 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -185,6 +185,7 @@ describe Gitlab::GitAccess, lib: true do end end + # Run permission checks for a user def self.run_permission_checks(permissions_matrix) permissions_matrix.keys.each do |role| describe "#{role} access" do @@ -194,13 +195,12 @@ describe Gitlab::GitAccess, lib: true do else project.team << [user, role] end - end - - permissions_matrix[role].each do |action, allowed| - context action do - subject { access.push_access_check(changes[action]) } - it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } + permissions_matrix[role].each do |action, allowed| + context action do + subject { access.push_access_check(changes[action]) } + it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } + end end end end -- cgit v1.2.1 From 6588f361dc93e9cc7f16a257abd610f920190718 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 18 Oct 2016 13:40:16 -0500 Subject: Require missing Vue dependency --- app/assets/javascripts/cycle_analytics.js.es6 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/cycle_analytics.js.es6 b/app/assets/javascripts/cycle_analytics.js.es6 index cd9886ba58d..bd9accacb8c 100644 --- a/app/assets/javascripts/cycle_analytics.js.es6 +++ b/app/assets/javascripts/cycle_analytics.js.es6 @@ -1,3 +1,5 @@ +//= require vue + ((global) => { const COOKIE_NAME = 'cycle_analytics_help_dismissed'; -- cgit v1.2.1 From 7424c2904cf7317b3e5a400a567b650d05b1947e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 16:14:11 -0700 Subject: Fix missing semicolon to make scss-lint happy --- app/assets/stylesheets/pages/pipelines.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e6c3781ab56..fb0236e273d 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -31,7 +31,7 @@ width: 30%; .branch-name { - width: 195px + width: 195px; } } } -- cgit v1.2.1 From 4f24fdc1f9b5aaa77f125ecc91d2d55adb37bf62 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 11:41:24 -0700 Subject: Use Hash rocket syntax to maintain Ruby 2.1 compatibility Builds in Ruby 2.1 specs were failing in master: https://gitlab.com/gitlab-org/gitlab-ce/builds/5246591 Closes #23498 --- app/views/notify/pipeline_failed_email.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index ec02c2e2d90..0995826775a 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -1,9 +1,9 @@ %html{lang: "en"} %head - %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/ + %meta{content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %meta{content: "width=device-width, initial-scale=1", name: "viewport"}/ - %meta{content: "IE=edge", "http-equiv": "X-UA-Compatible"}/ + %meta{content: "IE=edge", "http-equiv" => "X-UA-Compatible"}/ %title= message.subject :css /* CLIENT-SPECIFIC STYLES */ -- cgit v1.2.1 From 8989baaae7091832855b976b0eeda32d8b545dcb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 15:10:31 -0700 Subject: Fix broken award emoji for comments Closes #23506 --- app/helpers/award_emoji_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb index 493f14f6f9d..592ffe7b89f 100644 --- a/app/helpers/award_emoji_helper.rb +++ b/app/helpers/award_emoji_helper.rb @@ -4,7 +4,7 @@ module AwardEmojiHelper if awardable.is_a?(Note) # We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (6.5x) - toggle_award_emoji_namespace_project_note_url(namespace_id: @project.namespace_id, project_id: @project.id, id: awardable.id) + toggle_award_emoji_namespace_project_note_url(namespace_id: @project.namespace, project_id: @project, id: awardable.id) else url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) end -- cgit v1.2.1 From b3fb7f5d5746d95db718d5b418f015b7e45ecbeb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 16:12:26 -0700 Subject: Add spec for toggling award emoji on issue notes --- spec/features/issues/award_emoji_spec.rb | 46 ++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb index 79cc50bc18e..ef00f209998 100644 --- a/spec/features/issues/award_emoji_spec.rb +++ b/spec/features/issues/award_emoji_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' describe 'Awards Emoji', feature: true do + include WaitForAjax + let!(:project) { create(:project) } let!(:user) { create(:user) } @@ -16,20 +18,22 @@ describe 'Awards Emoji', feature: true do project: project) end + let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") } + before do visit namespace_project_issue_path(project.namespace, project, issue) end it 'increments the thumbsdown emoji', js: true do find('[data-emoji="thumbsdown"]').click - sleep 2 + wait_for_ajax expect(thumbsdown_emoji).to have_text("1") end context 'click the thumbsup emoji' do it 'increments the thumbsup emoji', js: true do find('[data-emoji="thumbsup"]').click - sleep 2 + wait_for_ajax expect(thumbsup_emoji).to have_text("1") end @@ -41,7 +45,7 @@ describe 'Awards Emoji', feature: true do context 'click the thumbsdown emoji' do it 'increments the thumbsdown emoji', js: true do find('[data-emoji="thumbsdown"]').click - sleep 2 + wait_for_ajax expect(thumbsdown_emoji).to have_text("1") end @@ -49,13 +53,45 @@ describe 'Awards Emoji', feature: true do expect(thumbsup_emoji).to have_text("0") end end + + it 'toggles the smiley emoji on a note', js: true do + toggle_smiley_emoji(true) + + within('.note-awards') do + expect(find(emoji_counter)).to have_text("1") + end + + toggle_smiley_emoji(false) + + within('.note-awards') do + expect(page).not_to have_selector(emoji_counter) + end + end end def thumbsup_emoji - page.all('span.js-counter').first + page.all(emoji_counter).first end def thumbsdown_emoji - page.all('span.js-counter').last + page.all(emoji_counter).last + end + + def emoji_counter + 'span.js-counter' + end + + def toggle_smiley_emoji(status) + within('.note') do + find('.note-emoji-button').click + end + + unless status + first('[data-emoji="smiley"]').click + else + find('[data-emoji="smiley"]').click + end + + wait_for_ajax end end -- cgit v1.2.1 From b47622b26ec677ec8124fbe549379a21aa822e22 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 18 Oct 2016 19:04:32 -0500 Subject: Fix visual weirdness in pipelines --- app/assets/stylesheets/pages/pipelines.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index fb0236e273d..247339648fa 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -31,7 +31,7 @@ width: 30%; .branch-name { - width: 195px; + max-width: 195px; } } } @@ -130,7 +130,6 @@ .fa { font-size: 12px; color: $gl-text-color; - margin-left: 5px; } .commit-id { -- cgit v1.2.1 From 83b570676b514788e698fec318dbeec6a2f9134e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 19:20:12 -0700 Subject: Use Hash rocket syntax to maintain Ruby 2.1 compatibility Builds in Ruby 2.1 specs were failing in master: https://gitlab.com/gitlab-org/gitlab-ce/builds/5258614 Closes #23498 --- app/views/notify/pipeline_success_email.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 0fdf118c9bc..cf9c1d4d72c 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -1,9 +1,9 @@ %html{lang: "en"} %head - %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/ + %meta{content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ %meta{content: "width=device-width, initial-scale=1", name: "viewport"}/ - %meta{content: "IE=edge", "http-equiv": "X-UA-Compatible"}/ + %meta{content: "IE=edge", "http-equiv" => "X-UA-Compatible"}/ %title= message.subject :css /* CLIENT-SPECIFIC STYLES */ -- cgit v1.2.1 From 4e028d333bbbcafcd89b04778b0b37775b6015c4 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 19 Oct 2016 04:55:26 +0100 Subject: Return the title for id of 'No label' --- app/assets/javascripts/labels_select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index f1e719937c7..b4f6e70f694 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -266,7 +266,7 @@ }, fieldName: $dropdown.data('field-name'), id: function(label) { - if (label.id <= 0) return; + if (label.id <= 0) return label.title; if ($dropdown.hasClass('js-issuable-form-dropdown')) { return label.id; -- cgit v1.2.1 From 47b0edbe746d5ca2a1502d3f0bf0213fe1789567 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 21:26:20 -0700 Subject: Improve error logging of MergeService Relates to #23505 --- app/services/merge_requests/merge_service.rb | 14 ++++++++++---- spec/services/merge_requests/merge_service_spec.rb | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index b037780c431..38477c1f321 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -11,14 +11,14 @@ module MergeRequests def execute(merge_request) @merge_request = merge_request - return error('Merge request is not mergeable') unless @merge_request.mergeable? + return log_merge_error('Merge request is not mergeable', true) unless @merge_request.mergeable? merge_request.in_locked_state do if commit after_merge success else - error('Can not merge changes') + log_merge_error('Can not merge changes', true) end end end @@ -46,8 +46,8 @@ module MergeRequests merge_request.update(merge_error: e.message) false rescue StandardError => e - merge_request.update(merge_error: "Something went wrong during merge") - Rails.logger.error(e.message) + merge_request.update(merge_error: "Something went wrong during merge: #{e.message}") + log_merge_error(e.message) false ensure merge_request.update(in_progress_merge_commit_sha: nil) @@ -65,5 +65,11 @@ module MergeRequests def branch_deletion_user @merge_request.force_remove_source_branch? ? @merge_request.author : current_user end + + def log_merge_error(message, http_error = false) + Rails.logger.error("MergeService error: #{message}") + + error(message) if http_error + end end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index ee53e110aee..9163c0c104e 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -120,13 +120,13 @@ describe MergeRequests::MergeService, services: true do let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') } it 'saves error if there is an exception' do - allow(service).to receive(:repository).and_raise("error") + allow(service).to receive(:repository).and_raise("error message") allow(service).to receive(:execute_hooks) service.execute(merge_request) - expect(merge_request.merge_error).to eq("Something went wrong during merge") + expect(merge_request.merge_error).to eq("Something went wrong during merge: error message") end it 'saves error if there is an PreReceiveError exception' do -- cgit v1.2.1 From 75b7ba3f7b89f10f7088b84b4594e747c571f016 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 18 Oct 2016 21:59:57 -0700 Subject: Identify merge request project and IID in log message --- app/services/merge_requests/merge_service.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 38477c1f321..ab9056a3250 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -67,9 +67,15 @@ module MergeRequests end def log_merge_error(message, http_error = false) - Rails.logger.error("MergeService error: #{message}") + Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{message}") error(message) if http_error end + + def merge_request_info + project = merge_request.project + + "#{project.to_reference}#{merge_request.to_reference}" + end end end -- cgit v1.2.1 From c8c3cd82d5f93fd325a10fa1c059499a78175ddb Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Wed, 19 Oct 2016 09:32:39 +0200 Subject: Use Hash rocket syntax to fix cycle analytics under Ruby 2.1 Refers to #23510 --- app/views/projects/cycle_analytics/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 7f346df8797..ef24ec0cf29 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -5,7 +5,7 @@ #cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}} .bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"} - = icon('times', class: 'dismiss-icon', "@click": "dismissLanding()") + = icon('times', class: 'dismiss-icon', "@click" => "dismissLanding()") .row .col-sm-3.col-xs-12.svg-container = custom_icon('icon_cycle_analytics_splash') -- cgit v1.2.1 From 9a14b0bb6993349c568b6f819b43200ae6441e69 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 19 Oct 2016 10:47:40 +0200 Subject: Fix name of feature that restricts builds traces --- app/views/projects/pipelines_settings/show.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml index 0740e9b56ab..bebf0ccd54d 100644 --- a/app/views/projects/pipelines_settings/show.html.haml +++ b/app/views/projects/pipelines_settings/show.html.haml @@ -64,8 +64,8 @@ .checkbox = f.label :public_builds do = f.check_box :public_builds - %strong Public pipelines - .help-block Allow everyone to access pipelines for Public and Internal projects + %strong Public builds + .help-block Allow everyone to access builds traces for Public and Internal projects .form-group.append-bottom-default = f.label :runners_token, "Runners token", class: 'label-light' -- cgit v1.2.1 From f744a5c12f0c82573f5247b9e7580c3eeb7768cf Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 7 Oct 2016 19:30:34 +0100 Subject: Added dyanmic position adjustment Added tooltips for dropdown items Reverted pretty much everything in favour of a DOM approach Simplified JS --- app/assets/javascripts/pipelines.js.es6 | 11 ----------- app/assets/stylesheets/pages/pipelines.scss | 14 ++++++++++---- app/helpers/ci_status_helper.rb | 4 ++-- app/views/projects/ci/builds/_build_pipeline.html.haml | 4 ++-- app/views/projects/commit/_pipeline_stage.html.haml | 2 +- app/views/projects/commit/_pipeline_status_group.html.haml | 2 +- .../_generic_commit_status_pipeline.html.haml | 13 +++++++------ 7 files changed, 23 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index dd320c52e44..a7624de6089 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -4,17 +4,6 @@ constructor() { $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); this.addMarginToBuildColumns(); - this.initGroupedPipelineTooltips(); - } - - initGroupedPipelineTooltips() { - $('.dropdown-menu-toggle', $('.grouped-pipeline-dropdown').parent()).each(function() { - const $this = $(this); - $this.tooltip({ - title: $this.data('tooltip-title'), - placement: 'bottom' - }); - }); } toggleGraph() { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 7b71876b822..46308742aaf 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -360,10 +360,6 @@ &:hover { background-color: $gray-lighter; - - .dropdown-menu-toggle { - background-color: transparent; - } } &.playable { @@ -393,6 +389,15 @@ } } + .tooltip { + white-space: nowrap; + + .tooltip-inner { + overflow: hidden; + text-overflow: ellipsis; + } + } + .ci-status-text { width: 135px; white-space: nowrap; @@ -410,6 +415,7 @@ } .dropdown-menu-toggle { + background-color: transparent; border: none; width: auto; padding: 0; diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 0e727f3dcf0..b7f48630bd4 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -71,10 +71,10 @@ module CiStatusHelper Ci::Runner.shared.blank? end - def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body', show_tooltip: true) + def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body') klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}" title = "#{type.titleize}: #{ci_label_for_status(status)}" - data = { toggle: 'tooltip', placement: tooltip_placement, container: container } if show_tooltip + data = { toggle: 'tooltip', placement: tooltip_placement, container: container } if path link_to ci_icon_for_status(status), path, diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 017d3ff6af2..55965172d3f 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,10 +1,10 @@ - is_playable = subject.playable? && can?(current_user, :update_build, @project) - if is_playable - = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do + = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.pipeline-graph', placement: 'bottom' } do = render_status_with_link('build', 'play') .ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) - = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do + = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } do %span.ci-status-icon = render_status_with_link('build', subject.status) .ci-status-text= subject.name diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 993fc89d23d..289aa5178b1 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -5,7 +5,7 @@ - is_playable = status.playable? && can?(current_user, :update_build, @project) %li.build{ class: ("playable" if is_playable) } .curve - .build-content{ { data: { toggle: 'tooltip', title: "#{group_name} - #{status.status}", placement: 'bottom' } } } + .build-content = render "projects/#{status.to_partial_path}_pipeline", subject: status - else %li.build diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 5d0d5ba0262..f2d71fa6989 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,5 +1,5 @@ - group_status = CommitStatus.where(id: subject).status -%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } +%button.dropdown-menu-toggle.has-tooltip{ type: 'button', data: { toggle: 'dropdown', title: "#{name} - #{group_status}" } } %span.ci-status-icon = render_status_with_link('build', group_status) %span.ci-status-text diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 0a66d60accc..c45b73e4225 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,9 +1,10 @@ -- if subject.target_url - = link_to subject.target_url do +%a{ data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } } + - if subject.target_url + = link_to subject.target_url do + %span.ci-status-icon + = render_status_with_link('commit status', subject.status) + %span.ci-status-text= subject.name + - else %span.ci-status-icon = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name -- else - %span.ci-status-icon - = render_status_with_link('commit status', subject.status) - %span.ci-status-text= subject.name -- cgit v1.2.1 From 8cf45f63613276f47099def265ab4cde8eb0c758 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 19 Oct 2016 12:22:35 +0200 Subject: Revert "Merge branch 'gjlaubenstein/gitlab-ce-21712-change-issue-show-html-title'" This reverts commit 434d98b22a6381447a9c59cec16fc324ade198df, reversing changes made to b6a83be65f6711a3cf808400c549bdd3ec7eda74. --- CHANGELOG.md | 1 - app/views/projects/issues/edit.html.haml | 2 +- app/views/projects/issues/show.html.haml | 2 +- app/views/projects/merge_requests/_show.html.haml | 2 +- app/views/projects/merge_requests/edit.html.haml | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d317fd1db..f1ef9238c10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,7 +105,6 @@ Please view this file on the master branch, on stable branches it's out of date. - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) - Reduce queries needed to find users using their SSH keys when pushing commits - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml index 3a6fbbc7fbc..7cf1923456e 100644 --- a/app/views/projects/issues/edit.html.haml +++ b/app/views/projects/issues/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@issue.to_reference} #{@issue.title}", "Issues" +- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues" %h3.page-title Edit Issue ##{@issue.iid} diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 09347ad5fff..49c47a7dc6a 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@issue.to_reference} #{@issue.title}", "Issues" +- page_title "#{@issue.title} (##{@issue.iid})", "Issues" - page_description @issue.description - page_card_attributes @issue.card_attributes diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 662463bc72b..5c41db36e44 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests" +- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" - page_description @merge_request.description - page_card_attributes @merge_request.card_attributes - content_for :page_specific_javascripts do diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml index 7c3ac6652ee..03159f123f3 100644 --- a/app/views/projects/merge_requests/edit.html.haml +++ b/app/views/projects/merge_requests/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests" +- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" %h3.page-title Edit Merge Request #{@merge_request.to_reference} -- cgit v1.2.1 From 31d369b796466a726bbfce9ab72df11c8644f945 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 19 Oct 2016 12:23:51 +0200 Subject: Use `to_reference` for Issue "show" and "edit" page titles Thanks to Greg Laubenstein for noticing in !6503 --- app/views/projects/issues/edit.html.haml | 2 +- app/views/projects/issues/show.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml index 7cf1923456e..1b7d878c38c 100644 --- a/app/views/projects/issues/edit.html.haml +++ b/app/views/projects/issues/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues" +- page_title "Edit", "#{@issue.title} (#{@issue.to_reference})", "Issues" %h3.page-title Edit Issue ##{@issue.iid} diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 49c47a7dc6a..6f3f238a436 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@issue.title} (##{@issue.iid})", "Issues" +- page_title "#{@issue.title} (#{@issue.to_reference})", "Issues" - page_description @issue.description - page_card_attributes @issue.card_attributes -- cgit v1.2.1 From c7282f89596293423c61d6676db60a9a347d09ef Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 14 Oct 2016 10:45:23 +0200 Subject: Grapify the commit status API --- lib/api/commit_statuses.rb | 53 ++++++++++++++++--------------- spec/requests/api/commit_statuses_spec.rb | 4 +-- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index dfbdd597d29..f282a3b9cd6 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -6,17 +6,17 @@ module API resource :projects do before { authenticate! } - # Get a commit's statuses - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit hash - # ref (optional) - The ref - # stage (optional) - The stage - # name (optional) - The name - # all (optional) - Show all statuses, default: false - # Examples: - # GET /projects/:id/repository/commits/:sha/statuses + desc "Get a commit's statuses" do + success Entities::CommitStatus + end + params do + requires :id, type: String, desc: 'The ID of a project' + requires :sha, type: String, desc: 'The commit hash' + optional :ref, type: String, desc: 'The ref' + optional :stage, type: String, desc: 'The stage' + optional :name, type: String, desc: 'The name' + optional :all, type: String, desc: 'Show all statuses, default: false' + end get ':id/repository/commits/:sha/statuses' do authorize!(:read_commit_status, user_project) @@ -31,22 +31,23 @@ module API present paginate(statuses), with: Entities::CommitStatus end - # Post status to commit - # - # Parameters: - # id (required) - The ID of a project - # sha (required) - The commit hash - # ref (optional) - The ref - # state (required) - The state of the status. Can be: pending, running, success, failed or canceled - # target_url (optional) - The target URL to associate with this status - # description (optional) - A short description of the status - # name or context (optional) - A string label to differentiate this status from the status of other systems. Default: "default" - # Examples: - # POST /projects/:id/statuses/:sha + desc 'Post status to a commit' do + success Entities::CommitStatus + end + params do + requires :id, type: String, desc: 'The ID of a project' + requires :sha, type: String, desc: 'The commit hash' + requires :state, type: String, desc: 'The state of the status', + values: ['pending', 'running', 'success', 'failed', 'canceled'] + optional :ref, type: String, desc: 'The ref' + optional :target_url, type: String, desc: 'The target URL to associate with this status' + optional :description, type: String, desc: 'A short description of the status' + optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"' + optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"' + end post ':id/statuses/:sha' do authorize! :create_commit_status, user_project - required_attributes! [:state] - attrs = attributes_for_keys [:target_url, :description] + commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit @@ -68,7 +69,7 @@ module API status = GenericCommitStatus.running_or_pending.find_or_initialize_by( project: @project, pipeline: pipeline, user: current_user, name: name, ref: ref) - status.attributes = attrs + status.attributes = declared(params).slice(:target_url, :description) begin case params[:state].to_s diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 7aa7e85a9e2..335efc4db6c 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -196,7 +196,7 @@ describe API::CommitStatuses, api: true do end context 'reporter user' do - before { post api(post_url, reporter) } + before { post api(post_url, reporter), state: 'running' } it 'does not create commit status' do expect(response).to have_http_status(403) @@ -204,7 +204,7 @@ describe API::CommitStatuses, api: true do end context 'guest user' do - before { post api(post_url, guest) } + before { post api(post_url, guest), state: 'running' } it 'does not create commit status' do expect(response).to have_http_status(403) -- cgit v1.2.1 From fa075771a62a6fdb1c0ce505a14a4eee87ff55a0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Oct 2016 14:13:44 +0300 Subject: Refactor group_members_controller_spec Signed-off-by: Dmitriy Zaporozhets --- .../groups/group_members_controller_spec.rb | 120 ++++++--------------- spec/factories/group_members.rb | 1 + 2 files changed, 35 insertions(+), 86 deletions(-) diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index a0870891cf4..ad15b3f8f40 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -2,16 +2,10 @@ require 'spec_helper' describe Groups::GroupMembersController do let(:user) { create(:user) } + let(:group) { create(:group, :public) } - describe '#index' do - let(:group) { create(:group) } - - before do - group.add_owner(user) - stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) - end - - it 'renders index with group members' do + describe 'GET index' do + it 'renders index with 200 status code' do get :index, group_id: group expect(response).to have_http_status(200) @@ -19,74 +13,56 @@ describe Groups::GroupMembersController do end end - describe '#destroy' do - let(:group) { create(:group, :public) } + describe 'DELETE destroy' do + let(:member) { create(:group_member, :developer, group: group) } + + before { sign_in(user) } context 'when member is not found' do it 'returns 403' do - delete :destroy, group_id: group, - id: 42 + delete :destroy, group_id: group, id: 42 expect(response).to have_http_status(403) end end context 'when member is found' do - let(:user) { create(:user) } - let(:group_user) { create(:user) } - let(:member) do - group.add_developer(group_user) - group.members.find_by(user_id: group_user) - end - context 'when user does not have enough rights' do - before do - group.add_developer(user) - sign_in(user) - end + before { group.add_developer(user) } it 'returns 403' do - delete :destroy, group_id: group, - id: member + delete :destroy, group_id: group, id: member expect(response).to have_http_status(403) - expect(group.users).to include group_user + expect(group.members).to include member end end context 'when user has enough rights' do - before do - group.add_owner(user) - sign_in(user) - end + before { group.add_owner(user) } it '[HTML] removes user from members' do - delete :destroy, group_id: group, - id: member + delete :destroy, group_id: group, id: member expect(response).to set_flash.to 'User was successfully removed from group.' expect(response).to redirect_to(group_group_members_path(group)) - expect(group.users).not_to include group_user + expect(group.members).not_to include member end it '[JS] removes user from members' do - xhr :delete, :destroy, group_id: group, - id: member + xhr :delete, :destroy, group_id: group, id: member expect(response).to be_success - expect(group.users).not_to include group_user + expect(group.members).not_to include member end end end end - describe '#leave' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } + describe 'DELETE leave' do + before { sign_in(user) } context 'when member is not found' do - before { sign_in(user) } - it 'returns 404' do delete :leave, group_id: group @@ -96,10 +72,7 @@ describe Groups::GroupMembersController do context 'when member is found' do context 'and is not an owner' do - before do - group.add_developer(user) - sign_in(user) - end + before { group.add_developer(user) } it 'removes user from members' do delete :leave, group_id: group @@ -111,10 +84,7 @@ describe Groups::GroupMembersController do end context 'and is an owner' do - before do - group.add_owner(user) - sign_in(user) - end + before { group.add_owner(user) } it 'cannot removes himself from the group' do delete :leave, group_id: group @@ -124,10 +94,7 @@ describe Groups::GroupMembersController do end context 'and is a requester' do - before do - group.request_access(user) - sign_in(user) - end + before { group.request_access(user) } it 'removes user from members' do delete :leave, group_id: group @@ -141,13 +108,8 @@ describe Groups::GroupMembersController do end end - describe '#request_access' do - let(:group) { create(:group, :public) } - let(:user) { create(:user) } - - before do - sign_in(user) - end + describe 'POST request_access' do + before { sign_in(user) } it 'creates a new GroupMember that is not a team member' do post :request_access, group_id: group @@ -159,53 +121,39 @@ describe Groups::GroupMembersController do end end - describe '#approve_access_request' do - let(:group) { create(:group, :public) } + describe 'POST approve_access_request' do + let(:member) { create(:group_member, :access_request, group: group) } + + before { sign_in(user) } context 'when member is not found' do it 'returns 403' do - post :approve_access_request, group_id: group, - id: 42 + post :approve_access_request, group_id: group, id: 42 expect(response).to have_http_status(403) end end context 'when member is found' do - let(:user) { create(:user) } - let(:group_requester) { create(:user) } - let(:member) do - group.request_access(group_requester) - group.requesters.find_by(user_id: group_requester) - end - context 'when user does not have enough rights' do - before do - group.add_developer(user) - sign_in(user) - end + before { group.add_developer(user) } it 'returns 403' do - post :approve_access_request, group_id: group, - id: member + post :approve_access_request, group_id: group, id: member expect(response).to have_http_status(403) - expect(group.users).not_to include group_requester + expect(group.members).not_to include member end end context 'when user has enough rights' do - before do - group.add_owner(user) - sign_in(user) - end + before { group.add_owner(user) } it 'adds user to members' do - post :approve_access_request, group_id: group, - id: member + post :approve_access_request, group_id: group, id: member expect(response).to redirect_to(group_group_members_path(group)) - expect(group.users).to include group_requester + expect(group.members).to include member end end end diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb index 795df5dfda9..080b2e75ea1 100644 --- a/spec/factories/group_members.rb +++ b/spec/factories/group_members.rb @@ -9,5 +9,6 @@ FactoryGirl.define do trait(:developer) { access_level GroupMember::DEVELOPER } trait(:master) { access_level GroupMember::MASTER } trait(:owner) { access_level GroupMember::OWNER } + trait(:access_request) { requested_at Time.now } end end -- cgit v1.2.1 From 67b96255d89de8e31234d68a3408adb8b9779dd9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 19 Oct 2016 14:03:31 +0200 Subject: Keep around commits only on pipeline create --- CHANGELOG.md | 1 + app/models/ci/pipeline.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d317fd1db..44c0b80bf6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Ability to resolve merge request conflicts with editor !6374 - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) + - Keep around commits only pipeline creation as pipeline data doesn't change over time - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - Cancelled pipelines could be retried. !6927 diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index e75fe6c222b..e84c91b417d 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -19,7 +19,7 @@ module Ci validates_presence_of :status, unless: :importing? validate :valid_commit_sha, unless: :importing? - after_save :keep_around_commits, unless: :importing? + after_create :keep_around_commits, unless: :importing? delegate :stages, to: :statuses -- cgit v1.2.1 From 357c794a49843ca9984642ddd091612e3200a8e3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Oct 2016 15:13:59 +0300 Subject: Refactor project_members_controller_spec Signed-off-by: Dmitriy Zaporozhets --- .../projects/project_members_controller_spec.rb | 113 +++++++-------------- spec/factories/project_members.rb | 1 + 2 files changed, 36 insertions(+), 78 deletions(-) diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 074f85157de..ea56bd72912 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -1,10 +1,11 @@ require('spec_helper') describe Projects::ProjectMembersController do - describe '#apply_import' do - let(:project) { create(:project) } + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + describe 'POST apply_import' do let(:another_project) { create(:project, :private) } - let(:user) { create(:user) } let(:member) { create(:user) } before do @@ -47,23 +48,19 @@ describe Projects::ProjectMembersController do end end - describe '#index' do - context 'when user is member' do - before do - project = create(:project, :private) - member = create(:user) - project.team << [member, :guest] - sign_in(member) - - get :index, namespace_id: project.namespace, project_id: project - end + describe 'GET index' do + it 'renders index with 200 status code' do + get :index, namespace_id: project.namespace, project_id: project - it { expect(response).to have_http_status(200) } + expect(response).to have_http_status(200) + expect(response).to render_template(:index) end end - describe '#destroy' do - let(:project) { create(:project, :public) } + describe 'DELETE destroy' do + let(:member) { create(:project_member, :developer, project: project) } + + before { sign_in(user) } context 'when member is not found' do it 'returns 404' do @@ -76,18 +73,8 @@ describe Projects::ProjectMembersController do end context 'when member is found' do - let(:user) { create(:user) } - let(:team_user) { create(:user) } - let(:member) do - project.team << [team_user, :developer] - project.members.find_by(user_id: team_user.id) - end - context 'when user does not have enough rights' do - before do - project.team << [user, :developer] - sign_in(user) - end + before { project.team << [user, :developer] } it 'returns 404' do delete :destroy, namespace_id: project.namespace, @@ -95,15 +82,12 @@ describe Projects::ProjectMembersController do id: member expect(response).to have_http_status(404) - expect(project.users).to include team_user + expect(project.members).to include member end end context 'when user has enough rights' do - before do - project.team << [user, :master] - sign_in(user) - end + before { project.team << [user, :master] } it '[HTML] removes user from members' do delete :destroy, namespace_id: project.namespace, @@ -113,7 +97,7 @@ describe Projects::ProjectMembersController do expect(response).to redirect_to( namespace_project_project_members_path(project.namespace, project) ) - expect(project.users).not_to include team_user + expect(project.members).not_to include member end it '[JS] removes user from members' do @@ -122,19 +106,16 @@ describe Projects::ProjectMembersController do id: member expect(response).to be_success - expect(project.users).not_to include team_user + expect(project.members).not_to include member end end end end - describe '#leave' do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } + describe 'DELETE leave' do + before { sign_in(user) } context 'when member is not found' do - before { sign_in(user) } - it 'returns 404' do delete :leave, namespace_id: project.namespace, project_id: project @@ -145,10 +126,7 @@ describe Projects::ProjectMembersController do context 'when member is found' do context 'and is not an owner' do - before do - project.team << [user, :developer] - sign_in(user) - end + before { project.team << [user, :developer] } it 'removes user from members' do delete :leave, namespace_id: project.namespace, @@ -161,11 +139,9 @@ describe Projects::ProjectMembersController do end context 'and is an owner' do - before do - project.update(namespace_id: user.namespace_id) - project.team << [user, :master, user] - sign_in(user) - end + let(:project) { create(:project, namespace: user.namespace) } + + before { project.team << [user, :master] } it 'cannot remove himself from the project' do delete :leave, namespace_id: project.namespace, @@ -176,10 +152,7 @@ describe Projects::ProjectMembersController do end context 'and is a requester' do - before do - project.request_access(user) - sign_in(user) - end + before { project.request_access(user) } it 'removes user from members' do delete :leave, namespace_id: project.namespace, @@ -194,13 +167,8 @@ describe Projects::ProjectMembersController do end end - describe '#request_access' do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - - before do - sign_in(user) - end + describe 'POST request_access' do + before { sign_in(user) } it 'creates a new ProjectMember that is not a team member' do post :request_access, namespace_id: project.namespace, @@ -215,8 +183,10 @@ describe Projects::ProjectMembersController do end end - describe '#approve' do - let(:project) { create(:project, :public) } + describe 'POST approve' do + let(:member) { create(:project_member, :access_request, project: project) } + + before { sign_in(user) } context 'when member is not found' do it 'returns 404' do @@ -229,18 +199,8 @@ describe Projects::ProjectMembersController do end context 'when member is found' do - let(:user) { create(:user) } - let(:team_requester) { create(:user) } - let(:member) do - project.request_access(team_requester) - project.requesters.find_by(user_id: team_requester.id) - end - context 'when user does not have enough rights' do - before do - project.team << [user, :developer] - sign_in(user) - end + before { project.team << [user, :developer] } it 'returns 404' do post :approve_access_request, namespace_id: project.namespace, @@ -248,15 +208,12 @@ describe Projects::ProjectMembersController do id: member expect(response).to have_http_status(404) - expect(project.users).not_to include team_requester + expect(project.members).not_to include member end end context 'when user has enough rights' do - before do - project.team << [user, :master] - sign_in(user) - end + before { project.team << [user, :master] } it 'adds user to members' do post :approve_access_request, namespace_id: project.namespace, @@ -266,7 +223,7 @@ describe Projects::ProjectMembersController do expect(response).to redirect_to( namespace_project_project_members_path(project.namespace, project) ) - expect(project.users).to include team_requester + expect(project.members).to include member end end end diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb index 1ddb305a8af..c21927640d1 100644 --- a/spec/factories/project_members.rb +++ b/spec/factories/project_members.rb @@ -8,5 +8,6 @@ FactoryGirl.define do trait(:reporter) { access_level ProjectMember::REPORTER } trait(:developer) { access_level ProjectMember::DEVELOPER } trait(:master) { access_level ProjectMember::MASTER } + trait(:access_request) { requested_at Time.now } end end -- cgit v1.2.1 From 43da118f85d1e53770d106905be3358c6452c37e Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 19 Oct 2016 10:40:25 -0500 Subject: Bump `omniauth-saml` to 1.7.0 to include security fixes and metadata support for IdP auto-configuration. --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 05166b6a828..46245ab62d1 100644 --- a/Gemfile +++ b/Gemfile @@ -29,7 +29,7 @@ gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-google-oauth2', '~> 0.4.1' gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos -gem 'omniauth-saml', '~> 1.6.0' +gem 'omniauth-saml', '~> 1.7.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' diff --git a/Gemfile.lock b/Gemfile.lock index a9892d1c130..442184b9228 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -473,9 +473,9 @@ GEM omniauth-oauth2 (1.3.1) oauth2 (~> 1.0) omniauth (~> 1.2) - omniauth-saml (1.6.0) + omniauth-saml (1.7.0) omniauth (~> 1.3) - ruby-saml (~> 1.3) + ruby-saml (~> 1.4) omniauth-shibboleth (1.2.1) omniauth (>= 1.0.0) omniauth-twitter (1.2.1) @@ -635,7 +635,7 @@ GEM crack (~> 0.4) ruby-prof (0.16.2) ruby-progressbar (1.8.1) - ruby-saml (1.3.0) + ruby-saml (1.4.1) nokogiri (>= 1.5.10) ruby_parser (3.8.2) sexp_processor (~> 4.1) @@ -915,7 +915,7 @@ DEPENDENCIES omniauth-gitlab (~> 1.0.0) omniauth-google-oauth2 (~> 0.4.1) omniauth-kerberos (~> 0.3.0) - omniauth-saml (~> 1.6.0) + omniauth-saml (~> 1.7.0) omniauth-shibboleth (~> 1.2.0) omniauth-twitter (~> 1.2.0) omniauth_crowd (~> 2.2.0) -- cgit v1.2.1 From 8e4301d982ce28d80e711865ac294a98ddce3ec6 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 6 Oct 2016 19:05:27 -0300 Subject: Prevent wrong markdown on issue ids when project has Jira service activated --- CHANGELOG.md | 1 + app/models/external_issue.rb | 5 -- app/models/project.rb | 4 ++ .../project_services/issue_tracker_service.rb | 6 ++ app/models/project_services/jira_service.rb | 5 ++ lib/banzai/filter/abstract_reference_filter.rb | 16 ++++- .../filter/external_issue_reference_filter.rb | 29 +++++---- lib/banzai/filter/issue_reference_filter.rb | 8 +++ .../filter/external_issue_reference_filter_spec.rb | 72 +++++++++++++++++++--- .../banzai/filter/issue_reference_filter_spec.rb | 17 +---- spec/models/external_issue_spec.rb | 15 ----- spec/models/project_services/jira_service_spec.rb | 9 +++ .../project_services/redmine_service_spec.rb | 8 +++ spec/services/git_push_service_spec.rb | 60 +++++++++++------- spec/services/merge_requests/merge_service_spec.rb | 12 ++++ .../issue_tracker_service_shared_example.rb | 15 +++++ 16 files changed, 199 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d317fd1db..c4fcca1b016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.6 + - Ignore references to internal issues when using external issues tracker - Ability to resolve merge request conflicts with editor !6374 - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb index b7894c99846..fd9a8c1b8b7 100644 --- a/app/models/external_issue.rb +++ b/app/models/external_issue.rb @@ -29,11 +29,6 @@ class ExternalIssue @project end - # Pattern used to extract `JIRA-123` issue references from text - def self.reference_pattern - @reference_pattern ||= %r{(?\b([A-Z][A-Z0-9_]+-)\d+)} - end - def to_reference(_from_project = nil) id end diff --git a/app/models/project.rb b/app/models/project.rb index db7301219e5..a645c9b29cc 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -664,6 +664,10 @@ class Project < ActiveRecord::Base end end + def issue_reference_pattern + issues_tracker.reference_pattern + end + def default_issues_tracker? !external_issue_tracker end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index d1df6d0292f..b26ddd518d7 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -3,6 +3,12 @@ class IssueTrackerService < Service default_value_for :category, 'issue_tracker' + # Pattern used to extract links from comments + # Override this method on services that uses different patterns + def reference_pattern + @reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?\d+)} + end + def default? default end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 97bcbacf2b9..f81b66fd219 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -13,6 +13,11 @@ class JiraService < IssueTrackerService before_update :reset_password + # {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1 + def reference_pattern + @reference_pattern ||= %r{(?\b([A-Z][A-Z0-9_]+-)\d+)} + end + def reset_password # don't reset the password if a new one is provided if api_url_changed? && !password_touched? diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index affe34394c2..cb213a76a05 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -208,8 +208,12 @@ module Banzai @references_per_project ||= begin refs = Hash.new { |hash, key| hash[key] = Set.new } - regex = Regexp.union(object_class.reference_pattern, - object_class.link_reference_pattern) + regex = + if uses_reference_pattern? + Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern) + else + object_class.link_reference_pattern + end nodes.each do |node| node.to_html.scan(regex) do @@ -295,6 +299,14 @@ module Banzai value end end + + # There might be special cases like filters + # that should ignore reference pattern + # eg: IssueReferenceFilter when using a external issues tracker + # In those cases this method should be overridden on the filter subclass + def uses_reference_pattern? + true + end end end end diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb index eaa702952cc..0d20be557a0 100644 --- a/lib/banzai/filter/external_issue_reference_filter.rb +++ b/lib/banzai/filter/external_issue_reference_filter.rb @@ -8,7 +8,7 @@ module Banzai # Public: Find `JIRA-123` issue references in text # - # ExternalIssueReferenceFilter.references_in(text) do |match, issue| + # ExternalIssueReferenceFilter.references_in(text, pattern) do |match, issue| # "##{issue}" # end # @@ -17,8 +17,8 @@ module Banzai # Yields the String match and the String issue reference. # # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(ExternalIssue.reference_pattern) do |match| + def self.references_in(text, pattern) + text.gsub(pattern) do |match| yield match, $~[:issue] end end @@ -27,7 +27,7 @@ module Banzai # Early return if the project isn't using an external tracker return doc if project.nil? || default_issues_tracker? - ref_pattern = ExternalIssue.reference_pattern + ref_pattern = issue_reference_pattern ref_start_pattern = /\A#{ref_pattern}\z/ each_node do |node| @@ -60,7 +60,7 @@ module Banzai def issue_link_filter(text, link_text: nil) project = context[:project] - self.class.references_in(text) do |match, id| + self.class.references_in(text, issue_reference_pattern) do |match, id| ExternalIssue.new(id, project) url = url_for_issue(id, project, only_path: context[:only_path]) @@ -82,18 +82,21 @@ module Banzai end def default_issues_tracker? - if RequestStore.active? - default_issues_tracker_cache[project.id] ||= - project.default_issues_tracker? - else - project.default_issues_tracker? - end + external_issues_cached(:default_issues_tracker?) + end + + def issue_reference_pattern + external_issues_cached(:issue_reference_pattern) end private - def default_issues_tracker_cache - RequestStore[:banzai_default_issues_tracker_cache] ||= {} + def external_issues_cached(attribute) + return project.public_send(attribute) unless RequestStore.active? + + cached_attributes = RequestStore[:banzai_external_issues_tracker_attributes] ||= Hash.new { |h, k| h[k] = {} } + cached_attributes[project.id][attribute] = project.public_send(attribute) if cached_attributes[project.id][attribute].nil? + cached_attributes[project.id][attribute] end end end diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 54c5f9a71a4..4d1bc687696 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -4,6 +4,10 @@ module Banzai # issues that do not exist are ignored. # # This filter supports cross-project references. + # + # When external issues tracker like Jira is activated we should not + # use issue reference pattern, but we should still be able + # to reference issues from other GitLab projects. class IssueReferenceFilter < AbstractReferenceFilter self.reference_type = :issue @@ -11,6 +15,10 @@ module Banzai Issue end + def uses_reference_pattern? + context[:project].default_issues_tracker? + end + def find_object(project, iid) issues_per_project[project][iid] end diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index 7116c09fb21..2f9343fadaf 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -7,12 +7,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do IssuesHelper end - let(:project) { create(:jira_project) } - - context 'JIRA issue references' do - let(:issue) { ExternalIssue.new('JIRA-123', project) } - let(:reference) { issue.to_reference } - + shared_examples_for "external issue tracker" do it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end @@ -20,6 +15,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do exp = act = "<#{elem}>Issue #{reference}" + expect(filter(act).to_html).to eq exp end end @@ -33,25 +29,30 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do it 'links to a valid reference' do doc = filter("Issue #{reference}") + issue_id = doc.css('a').first.attr("data-external-issue") + expect(doc.css('a').first.attr('href')) - .to eq helper.url_for_issue(reference, project) + .to eq helper.url_for_issue(issue_id, project) end it 'links to the external tracker' do doc = filter("Issue #{reference}") + link = doc.css('a').first.attr('href') + issue_id = doc.css('a').first.attr("data-external-issue") - expect(link).to eq "http://jira.example/browse/#{reference}" + expect(link).to eq(helper.url_for_issue(issue_id, project)) end it 'links with adjacent text' do doc = filter("Issue (#{reference}.)") + expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/) end it 'includes a title attribute' do doc = filter("Issue #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Issue in JIRA tracker" + expect(doc.css('a').first.attr('title')).to include("Issue in #{project.issues_tracker.title}") end it 'escapes the title attribute' do @@ -69,9 +70,60 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do it 'supports an :only_path context' do doc = filter("Issue #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + issue_id = doc.css('a').first["data-external-issue"] + + expect(link).to eq helper.url_for_issue(issue_id, project, only_path: true) + end + + context 'with RequestStore enabled' do + let(:reference_filter) { HTML::Pipeline.new([described_class]) } + + before { allow(RequestStore).to receive(:active?).and_return(true) } + + it 'queries the collection on the first call' do + expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original + expect_any_instance_of(Project).to receive(:issue_reference_pattern).once.and_call_original + + not_cached = reference_filter.call("look for #{reference}", { project: project }) + + expect_any_instance_of(Project).not_to receive(:default_issues_tracker?) + expect_any_instance_of(Project).not_to receive(:issue_reference_pattern) + + cached = reference_filter.call("look for #{reference}", { project: project }) - expect(link).to eq helper.url_for_issue("#{reference}", project, only_path: true) + # Links must be the same + expect(cached[:output].css('a').first[:href]).to eq(not_cached[:output].css('a').first[:href]) + end + end + end + + context "redmine project" do + let(:project) { create(:redmine_project) } + let(:issue) { ExternalIssue.new("#123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "jira project" do + let(:project) { create(:jira_project) } + let(:reference) { issue.to_reference } + + context "with right markdown" do + let(:issue) { ExternalIssue.new("JIRA-123", project) } + + it_behaves_like "external issue tracker" + end + + context "with wrong markdown" do + let(:issue) { ExternalIssue.new("#123", project) } + + it "ignores reference" do + exp = act = "Issue #{reference}" + expect(filter(act).to_html).to eq exp + end end end end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index fce86a9b6ad..a2025672ad9 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -25,9 +25,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do let(:reference) { issue.to_reference } it 'ignores valid references when using non-default tracker' do - expect_any_instance_of(described_class).to receive(:find_object). - with(project, issue.iid). - and_return(nil) + allow(project).to receive(:default_issues_tracker?).and_return(false) exp = act = "Issue #{reference}" expect(reference_filter(act).to_html).to eq exp @@ -199,19 +197,6 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do end end - context 'referencing external issues' do - let(:project) { create(:redmine_project) } - - it 'renders internal issue IDs as external issue links' do - doc = reference_filter('#1') - link = doc.css('a').first - - expect(link.attr('data-reference-type')).to eq('external_issue') - expect(link.attr('title')).to eq('Issue in Redmine') - expect(link.attr('data-external-issue')).to eq('1') - end - end - describe '#issues_per_Project' do context 'using an internal issue tracker' do it 'returns a Hash containing the issues per project' do diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb index 4fc3b065592..ebba6e14578 100644 --- a/spec/models/external_issue_spec.rb +++ b/spec/models/external_issue_spec.rb @@ -10,21 +10,6 @@ describe ExternalIssue, models: true do it { is_expected.to include_module(Referable) } end - describe '.reference_pattern' do - it 'allows underscores in the project name' do - expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' - end - - it 'allows numbers in the project name' do - expect(ExternalIssue.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234' - end - - it 'requires the project name to begin with A-Z' do - expect(ExternalIssue.reference_pattern.match('3EXT_EXT-1234')).to eq nil - expect(ExternalIssue.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' - end - end - describe '#to_reference' do it 'returns a String reference to the object' do expect(issue.to_reference).to eq issue.id diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index b48a3176007..6ff32aea018 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -30,6 +30,15 @@ describe JiraService, models: true do end end + describe '#reference_pattern' do + it_behaves_like 'allows project key on reference pattern' + + it 'does not allow # on the code' do + expect(subject.reference_pattern.match('#123')).to be_nil + expect(subject.reference_pattern.match('1#23#12')).to be_nil + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb index b8679cd2563..0a7b237a051 100644 --- a/spec/models/project_services/redmine_service_spec.rb +++ b/spec/models/project_services/redmine_service_spec.rb @@ -26,4 +26,12 @@ describe RedmineService, models: true do it { is_expected.not_to validate_presence_of(:new_issue_url) } end end + + describe '#reference_pattern' do + it_behaves_like 'allows project key on reference pattern' + + it 'does allow # on the reference' do + expect(subject.reference_pattern.match('#123')[:issue]).to eq('123') + end + end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 8dda34c7a03..ad5170afc21 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -415,7 +415,7 @@ describe GitPushService, services: true do it "doesn't close issues when external issue tracker is in use" do allow_any_instance_of(Project).to receive(:default_issues_tracker?). and_return(false) - external_issue_tracker = double(title: 'My Tracker', issue_path: issue.iid) + external_issue_tracker = double(title: 'My Tracker', issue_path: issue.iid, reference_pattern: project.issue_reference_pattern) allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(external_issue_tracker) # The push still shouldn't create cross-reference notes. @@ -484,30 +484,46 @@ describe GitPushService, services: true do end context "closing an issue" do - let(:message) { "this is some work.\n\ncloses JIRA-1" } - - it "initiates one api call to jira server to close the issue" do - transition_body = { - transition: { - id: '2' - } - }.to_json - - execute_service(project, commit_author, @oldrev, @newrev, @ref ) - expect(WebMock).to have_requested(:post, jira_api_transition_url).with( - body: transition_body - ).once + let(:message) { "this is some work.\n\ncloses JIRA-1" } + let(:transition_body) { { transition: { id: '2' } }.to_json } + let(:comment_body) { { body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]." }.to_json } + + context "using right markdown" do + it "initiates one api call to jira server to close the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).to have_requested(:post, jira_api_transition_url).with( + body: transition_body + ).once + end + + it "initiates one api call to jira server to comment on the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).to have_requested(:post, jira_api_comment_url).with( + body: comment_body + ).once + end end - it "initiates one api call to jira server to comment on the issue" do - comment_body = { - body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]." - }.to_json + context "using wrong markdown" do + let(:message) { "this is some work.\n\ncloses #1" } - execute_service(project, commit_author, @oldrev, @newrev, @ref ) - expect(WebMock).to have_requested(:post, jira_api_comment_url).with( - body: comment_body - ).once + it "does not initiates one api call to jira server to close the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).not_to have_requested(:post, jira_api_transition_url).with( + body: transition_body + ) + end + + it "does not initiates one api call to jira server to comment on the issue" do + execute_service(project, commit_author, @oldrev, @newrev, @ref ) + + expect(WebMock).not_to have_requested(:post, jira_api_comment_url).with( + body: comment_body + ).once + end end end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 9163c0c104e..f93d7732a9a 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -74,6 +74,18 @@ describe MergeRequests::MergeService, services: true do service.execute(merge_request) end + + context "wrong issue markdown" do + it 'does not close issues on JIRA issue tracker' do + jira_issue = ExternalIssue.new('#123', project) + commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}") + allow(merge_request).to receive(:commits).and_return([commit]) + + expect_any_instance_of(JiraService).not_to receive(:close_issue) + + service.execute(merge_request) + end + end end end diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb index b6d7436c360..e70b3963d9d 100644 --- a/spec/support/issue_tracker_service_shared_example.rb +++ b/spec/support/issue_tracker_service_shared_example.rb @@ -5,3 +5,18 @@ RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr| it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) } it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) } end + +RSpec.shared_examples 'allows project key on reference pattern' do |url_attr| + it 'allows underscores in the project name' do + expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' + end + + it 'allows numbers in the project name' do + expect(subject.reference_pattern.match('EXT3_EXT-1234')[0]).to eq 'EXT3_EXT-1234' + end + + it 'requires the project name to begin with A-Z' do + expect(subject.reference_pattern.match('3EXT_EXT-1234')).to eq nil + expect(subject.reference_pattern.match('EXT_EXT-1234')[0]).to eq 'EXT_EXT-1234' + end +end -- cgit v1.2.1 From ce78bdf96bf5744783f06a60d3c7078b4534390d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Oct 2016 18:46:47 +0300 Subject: Its time for 8.14 in master Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG.md | 2 ++ VERSION | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c866696889e..30b18ca84aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. +## 8.14.0 (2016-11-22) + ## 8.13.0 (2016-10-22) - Fix save button on project pipeline settings page. (!6955) diff --git a/VERSION b/VERSION index dff4cd02d5f..919f462addc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.13.0-pre +8.14.0-pre -- cgit v1.2.1 From 257c58ebeb2de8fe44f83c751b91e09c306aa588 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 18 Oct 2016 19:03:10 +0200 Subject: Set webkit-overflow-scrolling to auto for children of body. --- CHANGELOG.md | 1 + app/assets/stylesheets/framework/files.scss | 1 - app/assets/stylesheets/framework/layout.scss | 12 ++++++++++++ app/assets/stylesheets/pages/diff.scss | 1 - 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c866696889e..2c4d94c4cc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,7 @@ Please view this file on the master branch, on stable branches it's out of date. - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling - Delete dynamic environments + - Fix buggy iOS tooltip layering behavior. - Make guests unable to view MRs on private projects ## 8.12.7 diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 13c1bbf0359..f49d7b92a00 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -167,7 +167,6 @@ */ &.code { padding: 0; - -webkit-overflow-scrolling: auto; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987 } } } diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 8bb047db2dd..7baa4296abf 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -27,3 +27,15 @@ body { .container-limited { max-width: $fixed-layout-width; } + + +/* The following prevents side effects related to iOS Safari's implementation of -webkit-overflow-scrolling: touch, +which is applied to the body by jquery.nicescroling plugin to force hardware acceleration for momentum scrolling. Side +effects are commonly related to inconsisent z-index behavior (e.g. tooltips). By applying the following to direct children +of the body element here, we negate cascading side effects but allow momentum scrolling to be applied to the body */ + +.navbar, +.page-gutter, +.page-with-sidebar { + -webkit-overflow-scrolling: auto; +} diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index bdc82a8f0f5..fe6421f8b3f 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -52,7 +52,6 @@ background: #fff; color: #333; border-radius: 0 0 3px 3px; - -webkit-overflow-scrolling: auto; .unfold { cursor: pointer; -- cgit v1.2.1 From d820c090ec85f8118e4cea75bd63d800e812ea25 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 19 Sep 2016 12:04:38 -0300 Subject: Add GroupLabel model --- app/models/group.rb | 1 + app/models/group_label.rb | 5 +++++ db/migrate/20160919144305_add_type_to_labels.rb | 9 +++++++++ db/migrate/20160919145149_add_group_id_to_labels.rb | 13 +++++++++++++ db/schema.rb | 4 ++++ spec/models/group_label_spec.rb | 11 +++++++++++ spec/models/group_spec.rb | 1 + 7 files changed, 44 insertions(+) create mode 100644 app/models/group_label.rb create mode 100644 db/migrate/20160919144305_add_type_to_labels.rb create mode 100644 db/migrate/20160919145149_add_group_id_to_labels.rb create mode 100644 spec/models/group_label_spec.rb diff --git a/app/models/group.rb b/app/models/group.rb index a2f88cca828..00a595d2705 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -19,6 +19,7 @@ class Group < Namespace has_many :project_group_links, dependent: :destroy has_many :shared_projects, through: :project_group_links, source: :project has_many :notification_settings, dependent: :destroy, as: :source + has_many :labels, class_name: 'GroupLabel' validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :visibility_level_allowed_by_projects diff --git a/app/models/group_label.rb b/app/models/group_label.rb new file mode 100644 index 00000000000..a854d075820 --- /dev/null +++ b/app/models/group_label.rb @@ -0,0 +1,5 @@ +class GroupLabel < Label + belongs_to :group + + validates :group, presence: true +end diff --git a/db/migrate/20160919144305_add_type_to_labels.rb b/db/migrate/20160919144305_add_type_to_labels.rb new file mode 100644 index 00000000000..43aac7846d3 --- /dev/null +++ b/db/migrate/20160919144305_add_type_to_labels.rb @@ -0,0 +1,9 @@ +class AddTypeToLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :labels, :type, :string + end +end diff --git a/db/migrate/20160919145149_add_group_id_to_labels.rb b/db/migrate/20160919145149_add_group_id_to_labels.rb new file mode 100644 index 00000000000..05e21af0584 --- /dev/null +++ b/db/migrate/20160919145149_add_group_id_to_labels.rb @@ -0,0 +1,13 @@ +class AddGroupIdToLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def change + add_column :labels, :group_id, :integer + add_foreign_key :labels, :namespaces, column: :group_id, on_delete: :cascade + add_concurrent_index :labels, :group_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 5ce855fe08f..37f0be0e834 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -529,8 +529,11 @@ ActiveRecord::Schema.define(version: 20161017095000) do t.string "description" t.integer "priority" t.text "description_html" + t.string "type" + t.integer "group_id" end + add_index "labels", ["group_id"], name: "index_labels_on_group_id", using: :btree add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree add_index "labels", ["title"], name: "index_labels_on_title", using: :btree @@ -1213,6 +1216,7 @@ ActiveRecord::Schema.define(version: 20161017095000) do add_foreign_key "boards", "projects" add_foreign_key "issue_metrics", "issues", on_delete: :cascade + add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "lists", "boards" add_foreign_key "lists", "labels" add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb new file mode 100644 index 00000000000..a82d23bcc0b --- /dev/null +++ b/spec/models/group_label_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe GroupLabel, models: true do + describe 'relationships' do + it { is_expected.to belong_to(:group) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:group) } + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 0b3ef9b98fd..ac862055ebc 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -12,6 +12,7 @@ describe Group, models: true do it { is_expected.to have_many(:project_group_links).dependent(:destroy) } it { is_expected.to have_many(:shared_projects).through(:project_group_links) } it { is_expected.to have_many(:notification_settings).dependent(:destroy) } + it { is_expected.to have_many(:labels).class_name('GroupLabel') } describe '#members & #requesters' do let(:requester) { create(:user) } -- cgit v1.2.1 From 52e0c3b565b7b177abbf8ea3bc573651060179a2 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 19 Sep 2016 16:49:08 -0300 Subject: Add CRUD for Group Labels --- app/assets/javascripts/dispatcher.js.es6 | 2 + app/controllers/groups/labels_controller.rb | 67 ++++++++++++++++++++++++++++ app/helpers/labels_helper.rb | 28 +++++++++--- app/policies/group_policy.rb | 1 + app/views/groups/labels/_form.html.haml | 33 ++++++++++++++ app/views/groups/labels/_label.html.haml | 53 ++++++++++++++++++++++ app/views/groups/labels/_label_row.html.haml | 6 +++ app/views/groups/labels/destroy.js.haml | 2 + app/views/groups/labels/edit.html.haml | 8 ++++ app/views/groups/labels/index.html.haml | 24 ++++++++++ app/views/groups/labels/new.html.haml | 9 ++++ app/views/layouts/nav/_group.html.haml | 4 ++ config/routes/group.rb | 6 +++ 13 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 app/controllers/groups/labels_controller.rb create mode 100644 app/views/groups/labels/_form.html.haml create mode 100644 app/views/groups/labels/_label.html.haml create mode 100644 app/views/groups/labels/_label_row.html.haml create mode 100644 app/views/groups/labels/destroy.js.haml create mode 100644 app/views/groups/labels/edit.html.haml create mode 100644 app/views/groups/labels/index.html.haml create mode 100644 app/views/groups/labels/new.html.haml diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 73691f40c74..afc0d6f8c62 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -168,6 +168,8 @@ shortcut_handler = new ShortcutsNavigation(); new ShortcutsBlob(true); break; + case 'groups:labels:new': + case 'groups:labels:edit': case 'projects:labels:new': case 'projects:labels:edit': new Labels(); diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb new file mode 100644 index 00000000000..449298f51a8 --- /dev/null +++ b/app/controllers/groups/labels_controller.rb @@ -0,0 +1,67 @@ +class Groups::LabelsController < Groups::ApplicationController + include ToggleSubscriptionAction + + before_action :label, only: [:edit, :update, :destroy, :toggle_subscription] + before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] + + respond_to :html + + def index + @labels = @group.labels.page(params[:page]) + end + + def new + @label = @group.labels.new + end + + def create + @label = @group.labels.create(label_params) + + if @label.valid? + redirect_to group_labels_path(@group) + else + render :new + end + end + + def edit + end + + def update + if @label.update_attributes(label_params) + redirect_to group_labels_path(@group) + else + render :edit + end + end + + def destroy + @label.destroy + + respond_to do |format| + format.html do + redirect_to group_labels_path(@group), notice: 'Label was removed' + end + format.js + end + end + + protected + + def authorize_admin_labels! + return render_404 unless can?(current_user, :admin_label, @group) + end + + def authorize_read_labels! + return render_404 unless can?(current_user, :read_label, @group) + end + + def label + @label ||= @group.labels.find(params[:id]) + end + alias_method :subscribable_resource, :label + + def label_params + params.require(:label).permit(:title, :description, :color) + end +end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index b9f3d6c75c2..540eb6dd493 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -43,11 +43,29 @@ module LabelsHelper end end - def label_filter_path(project, label, type: issue) - send("namespace_project_#{type.to_s.pluralize}_path", - project.namespace, - project, - label_name: [label.name]) + def link_to_group_label(label, group: nil, type: :issue, tooltip: true, css_class: nil, &block) + group ||= @group || label.group + link = label_filter_path(group, label, type: type) + + if block_given? + link_to link, class: css_class, &block + else + link_to render_colored_label(label, tooltip: tooltip), link, class: css_class + end + end + + def label_filter_path(subject, label, type: issue) + case subject + when Project + send("namespace_project_#{type.to_s.pluralize}_path", + subject.namespace, + subject, + label_name: [label.name]) + when Group + send("#{type.to_s.pluralize}_group_path", + subject, + label_name: [label.name]) + end end def project_label_names diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 97ff6233968..b65fb68cd88 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -19,6 +19,7 @@ class GroupPolicy < BasePolicy if master can! :create_projects can! :admin_milestones + can! :admin_label end # Only group owner and administrators can admin group diff --git a/app/views/groups/labels/_form.html.haml b/app/views/groups/labels/_form.html.haml new file mode 100644 index 00000000000..008b5fb9ba1 --- /dev/null +++ b/app/views/groups/labels/_form.html.haml @@ -0,0 +1,33 @@ += form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| + = form_errors(@label) + + .form-group + = f.label :title, class: 'control-label' + .col-sm-10 + = f.text_field :title, class: "form-control", required: true, autofocus: true + .form-group + = f.label :description, class: 'control-label' + .col-sm-10 + = f.text_field :description, class: "form-control js-quick-submit" + .form-group + = f.label :color, "Background color", class: 'control-label' + .col-sm-10 + .input-group + .input-group-addon.label-color-preview   + = f.color_field :color, class: "form-control" + .help-block + Choose any color. + %br + Or you can choose one of suggested colors below + + .suggest-colors + - suggested_colors.each do |color| + = link_to '#', style: "background-color: #{color}", data: { color: color } do +   + + .form-actions + - if @label.persisted? + = f.submit 'Save changes', class: 'btn btn-save js-save-button' + - else + = f.submit 'Create Label', class: 'btn btn-create js-save-button' + = link_to "Cancel", group_labels_path(@group), class: 'btn btn-cancel' diff --git a/app/views/groups/labels/_label.html.haml b/app/views/groups/labels/_label.html.haml new file mode 100644 index 00000000000..b9aab76f057 --- /dev/null +++ b/app/views/groups/labels/_label.html.haml @@ -0,0 +1,53 @@ +- label_css_id = dom_id(label) +- open_issues_count = label.open_issues_count(current_user) +- open_merge_requests_count = label.open_merge_requests_count + +%li{id: label_css_id, data: { id: label.id } } + = render 'label_row', label: label + + .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown + %button.btn.btn-default.label-options-toggle{ data: { toggle: 'dropdown' } } + Options + %span.caret + .dropdown-menu.dropdown-menu-align-right + %ul + %li + = link_to_group_label(label, type: :merge_request) do + = pluralize open_merge_requests_count, 'merge request' + %li + = link_to_group_label(label) do + = pluralize open_issues_count, 'open issue' + - if current_user + %li.label-subscription{ data: { url: toggle_subscription_group_label_path(@group, label) } } + %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } + %span= label_subscription_toggle_button_text(label) + - if can? current_user, :admin_label, @group + %li + = link_to 'Edit', edit_group_label_path(@group, label) + %li + = link_to 'Delete', group_label_path(@group, label), title: 'Delete', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + + .pull-right.hidden-xs.hidden-sm.hidden-md + = link_to_group_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do + = pluralize open_merge_requests_count, 'merge request' + = link_to_group_label(label, css_class: 'btn btn-transparent btn-action') do + = pluralize open_issues_count, 'open issue' + + - if current_user + .label-subscription.inline{ data: { url: toggle_subscription_group_label_path(@group, label) } } + %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } + %span.sr-only= label_subscription_toggle_button_text(label) + = icon('eye', class: 'label-subscribe-button-icon') + = icon('spinner spin', class: 'label-subscribe-button-loading') + + - if can? current_user, :admin_label, @group + = link_to edit_group_label_path(@group, label), title: 'Edit', class: 'btn btn-transparent btn-action', data: {toggle: 'tooltip'} do + %span.sr-only Edit + = icon('pencil-square-o') + = link_to group_label_path(@group, label), title: 'Delete', class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?', toggle: 'tooltip'} do + %span.sr-only Delete + = icon('trash-o') + + - if current_user + :javascript + new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/app/views/groups/labels/_label_row.html.haml b/app/views/groups/labels/_label_row.html.haml new file mode 100644 index 00000000000..e21fac25b01 --- /dev/null +++ b/app/views/groups/labels/_label_row.html.haml @@ -0,0 +1,6 @@ +%span.label-row + %span.label-name + = link_to_group_label(label, tooltip: false) + - if label.description + %span.label-description + = markdown(label.description, pipeline: :single_line) diff --git a/app/views/groups/labels/destroy.js.haml b/app/views/groups/labels/destroy.js.haml new file mode 100644 index 00000000000..3dfbfc77c0d --- /dev/null +++ b/app/views/groups/labels/destroy.js.haml @@ -0,0 +1,2 @@ +- if @group.labels.empty? + $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000) diff --git a/app/views/groups/labels/edit.html.haml b/app/views/groups/labels/edit.html.haml new file mode 100644 index 00000000000..e247393abd5 --- /dev/null +++ b/app/views/groups/labels/edit.html.haml @@ -0,0 +1,8 @@ +- page_title 'Edit', @label.name, 'Labels' + +%h3.page-title + = icon('folder-open') + Edit Group Label +%hr + += render 'form', url: group_label_path(@group, @label) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml new file mode 100644 index 00000000000..d9f1d350cb3 --- /dev/null +++ b/app/views/groups/labels/index.html.haml @@ -0,0 +1,24 @@ +- page_title 'Labels' + +.top-area.adjust + .nav-text + Labels can be applied to issues and merge requests. + + .nav-controls + - if can?(current_user, :admin_label, @group) + = link_to new_group_label_path(@group), class: "btn btn-new" do + New label + +.labels + - hide = @group.labels.empty? || (params[:page].present? && params[:page] != '1') + .group-labels + %h5{ class: ('hide' if hide) } + = icon('folder-open') + Group Labels + - if @labels.present? + %ul.content-list.manage-labels-list.js-group-labels + = render partial: 'label', collection: @labels, as: :label + = paginate @labels, theme: 'gitlab' + - else + .nothing-here-block + No labels created yet. diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml new file mode 100644 index 00000000000..01a50607db4 --- /dev/null +++ b/app/views/groups/labels/new.html.haml @@ -0,0 +1,9 @@ +- page_title 'New Group Label' +- header_title group_title(@group, 'Labels', group_labels_path(@group)) + +%h3.page-title + = icon('folder-open') + New Group Label +%hr + += render 'form', url: group_labels_path diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 27ac1760166..f7edb47b666 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -13,6 +13,10 @@ = link_to activity_group_path(@group), title: 'Activity' do %span Activity + = nav_link(controller: [:group, :labels]) do + = link_to group_labels_path(@group), title: 'Labels' do + %span + Labels = nav_link(controller: [:group, :milestones]) do = link_to group_milestones_path(@group), title: 'Milestones' do %span diff --git a/config/routes/group.rb b/config/routes/group.rb index 06b464d79c8..7bb9aa50875 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -28,5 +28,11 @@ resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(? Date: Mon, 19 Sep 2016 17:16:16 -0300 Subject: Add LabelsFinder --- app/finders/labels_finder.rb | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 app/finders/labels_finder.rb diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb new file mode 100644 index 00000000000..cf7018cf8a2 --- /dev/null +++ b/app/finders/labels_finder.rb @@ -0,0 +1,58 @@ +class LabelsFinder + def initialize(current_user, params = {}) + @current_user = current_user + @params = params + end + + def execute + items = init_collection + items = with_title(items) + sort(items) + end + + private + + attr_reader :current_user, :params + + def init_collection + label_ids = [] + label_ids << Label.where(group_id: projects.where.not(group: nil).select(:namespace_id)).select(:id) + label_ids << Label.where(project_id: projects).select(:id) + + union = Gitlab::SQL::Union.new(label_ids) + + Label.where("labels.id IN (#{union.to_sql})") + .reorder(title: :asc) + end + + def with_title(items) + items = items.where(title: title) if title.present? + items + end + + def sort(items) + items.reorder(title: :asc) + end + + def project_id + params[:project_id].presence + end + + def title + params[:title].presence + end + + def projects + return @projects if defined?(@projects) + + if project_id + @projects = ProjectsFinder.new.execute(current_user) + .where(id: project_id) + .reorder(nil) + else + @projects = Project.none + end + + @projects + end +end -- cgit v1.2.1 From 398ab263fd08a5d9d7b19c5b3d06f33814a474eb Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 19 Sep 2016 17:21:39 -0300 Subject: Allow users to apply group labels on Issues/MRs --- app/controllers/dashboard/labels_controller.rb | 5 ++++- app/controllers/projects/issues_controller.rb | 9 ++++++--- app/controllers/projects/labels_controller.rb | 2 +- .../projects/merge_requests_controller.rb | 5 ++++- app/finders/issuable_finder.rb | 4 +++- app/models/label.rb | 6 +++++- app/services/issuable_base_service.rb | 15 ++++++++++----- app/services/projects/autocomplete_service.rb | 2 +- app/views/shared/issuable/_filter.html.haml | 9 ++++----- app/views/shared/issuable/_form.html.haml | 2 +- spec/services/issues/create_service_spec.rb | 21 +++++++++++++++++++++ 11 files changed, 60 insertions(+), 20 deletions(-) diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index 2a88350a4ca..797f8503b2d 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -1,6 +1,9 @@ class Dashboard::LabelsController < Dashboard::ApplicationController def index - labels = Label.where(project_id: projects).select(:id, :title, :color).uniq(:title) + labels = LabelsFinder.new(current_user, project_id: projects) + .execute + .select(:id, :title, :color) + .uniq(:title) respond_to do |format| format.json { render json: labels } diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 96041b07647..d85bc85092f 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -25,8 +25,7 @@ class Projects::IssuesController < Projects::ApplicationController def index @issues = issues_collection @issues = @issues.page(params[:page]) - - @labels = @project.labels.where(title: params[:label_name]) + @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute if params[:label_name].presence respond_to do |format| format.html @@ -45,11 +44,15 @@ class Projects::IssuesController < Projects::ApplicationController assignee_id: "" ) - @issue = @noteable = @project.issues.new(issue_params) + @issue = @noteable = @project.issues.new(issue_params) + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + respond_with(@issue) end def edit + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + respond_with(@issue) end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index a6626df4826..35224f965bf 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -17,7 +17,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @project.labels + render json: LabelsFinder.new(current_user, project_id: @project.id).execute end end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 04386258c19..c8970155497 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -40,7 +40,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_requests = @merge_requests.page(params[:page]) @merge_requests = @merge_requests.preload(:target_project) - @labels = @project.labels.where(title: params[:label_name]) + @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute if params[:label_name].presence respond_to do |format| format.html @@ -263,6 +263,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @source_project = @merge_request.source_project @target_project = @merge_request.target_project @target_branches = @merge_request.target_project.repository.branch_names + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute end def update @@ -575,6 +576,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController @note_counts = Note.where(commit_id: @commits.map(&:id)). group(:commit_id).count + @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + define_pipelines_vars end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 9f170428100..37151f8d134 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -274,8 +274,10 @@ class IssuableFinder items = items.without_label else items = items.with_label(label_names, params[:sort]) + if projects - items = items.where(labels: { project_id: projects }) + label_ids = LabelsFinder.new(current_user, project_id: projects).execute.select(:id) + items = items.where(labels: { id: label_ids }) end end end diff --git a/app/models/label.rb b/app/models/label.rb index e8e12e2904e..295c5bfaf70 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -23,7 +23,7 @@ class Label < ActiveRecord::Base has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest' validates :color, color: true, allow_blank: false - validates :project, presence: true, unless: Proc.new { |service| service.template? } + validates :project, presence: true, if: :project_label? # Don't allow ',' for label titles validates :title, @@ -127,6 +127,10 @@ class Label < ActiveRecord::Base private + def project_label? + type.blank? && !template? + end + def label_format_reference(format = :id) raise StandardError, 'Unknown format' unless [:id, :name].include?(format) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 57d521f2fea..b3e4f8dcf27 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -80,17 +80,18 @@ class IssuableBaseService < BaseService def filter_labels_in_param(key) return if params[key].to_a.empty? - params[key] = project.labels.where(id: params[key]).pluck(:id) + params[key] = available_labels.where(id: params[key]).pluck(:id) end def find_or_create_label_ids labels = params.delete(:labels) return unless labels - params[:label_ids] = labels.split(",").map do |label_name| - project.labels.create_with(color: Label::DEFAULT_COLOR) - .find_or_create_by(title: label_name.strip) - .id + params[:label_ids] = labels.split(',').map do |label_name| + label = available_labels.find_by(title: title).select(:id) + label ||= project.labels.create(title: label_name.strip, color: Label::DEFAULT_COLOR) + + label.id end end @@ -111,6 +112,10 @@ class IssuableBaseService < BaseService new_label_ids end + def available_labels + LabelsFinder.new(current_user, project_id: @project.id).execute + end + def merge_slash_commands_into_params!(issuable) description, command_params = SlashCommands::InterpretService.new(project, current_user). diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index f578f8dbea2..015f2828921 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -13,7 +13,7 @@ module Projects end def labels - @project.labels.select([:title, :color]) + LabelsFinder.new(current_user, project_id: project.id).execute.select([:title, :color]) end def commands(noteable, type) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 31620297be0..8c2036a1cde 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -77,11 +77,10 @@ = hidden_field_tag :state_event, params[:state_event] .filter-item.inline = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save" - - - if !@labels.nil? - .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) } - - if @labels.any? - = render "shared/labels_row", labels: @labels + - has_labels = @labels && @labels.any? + .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) } + - if has_labels + = render 'shared/labels_row', labels: @labels :javascript new UsersSelect(); diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index a7944a60130..34c66a17303 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -95,7 +95,7 @@ .issuable-form-select-holder = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - - has_labels = issuable.project.labels.any? + - has_labels = @labels && @labels.any? = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 1050502fa19..5c0331ebe66 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -67,6 +67,27 @@ describe Issues::CreateService, services: true do expect(Todo.where(attributes).count).to eq 1 end + context 'when label belongs to project group' do + let(:group) { create(:group) } + let(:group_labels) { create_pair(:group_label, group: group) } + + let(:opts) do + { + title: 'Title', + description: 'Description', + label_ids: group_labels.map(&:id) + } + end + + before do + project.update(group: group) + end + + it 'assigns group labels' do + expect(issue.labels).to match_array group_labels + end + end + context 'when label belongs to different project' do let(:label) { create(:label) } -- cgit v1.2.1 From bf9d928b45516e716b0f7f099361ca03aa1454f8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 19 Sep 2016 17:34:27 -0300 Subject: Allow user to create a board list based on a group label --- app/controllers/projects/issues_controller.rb | 5 ++++- app/services/boards/lists/create_service.rb | 6 +++++- spec/services/boards/lists/create_service_spec.rb | 4 ++++ spec/services/boards/lists/generate_service_spec.rb | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index d85bc85092f..4f6d7ca80df 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -25,7 +25,10 @@ class Projects::IssuesController < Projects::ApplicationController def index @issues = issues_collection @issues = @issues.page(params[:page]) - @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute if params[:label_name].presence + + if params[:label_name].presence + @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute + end respond_to do |format| format.html diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index abc7aeece39..fe0d762ccd2 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -3,7 +3,7 @@ module Boards class CreateService < BaseService def execute(board) List.transaction do - label = project.labels.find(params[:label_id]) + label = available_labels.find(params[:label_id]) position = next_position(board) create_list(board, label, position) @@ -12,6 +12,10 @@ module Boards private + def available_labels + LabelsFinder.new(current_user, project_id: project.id).execute + end + def next_position(board) max_position = board.lists.movable.maximum(:position) max_position.nil? ? 0 : max_position.succ diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index e7806add916..a7e9efcf93f 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -9,6 +9,10 @@ describe Boards::Lists::CreateService, services: true do subject(:service) { described_class.new(project, user, label_id: label.id) } + before do + project.team << [user, :developer] + end + context 'when board lists is empty' do it 'creates a new list at beginning of the list' do list = service.execute(board) diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 8b2f5e81338..ed0337662af 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -8,6 +8,10 @@ describe Boards::Lists::GenerateService, services: true do subject(:service) { described_class.new(project, user) } + before do + project.team << [user, :developer] + end + context 'when board lists is empty' do it 'creates the default lists' do expect { service.execute(board) }.to change(board.lists, :count).by(2) -- cgit v1.2.1 From bdb7bf4b5188ffd68e54cbf671ba9ce1a4ffb1d1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 00:09:57 -0300 Subject: List group labels on project labels page --- app/assets/stylesheets/pages/labels.scss | 10 +- app/controllers/groups/labels_controller.rb | 20 +++- app/controllers/projects/application_controller.rb | 4 + app/controllers/projects/issues_controller.rb | 4 +- app/controllers/projects/labels_controller.rb | 11 ++- .../projects/merge_requests_controller.rb | 2 +- app/helpers/labels_helper.rb | 43 +++++++++ app/models/label.rb | 24 +++-- app/views/groups/labels/_form.html.haml | 2 +- app/views/groups/labels/_label.html.haml | 2 +- app/views/projects/labels/_form.html.haml | 2 +- app/views/projects/labels/_label.html.haml | 23 +++-- app/views/projects/labels/destroy.js.haml | 2 +- app/views/projects/labels/edit.html.haml | 3 +- app/views/projects/labels/index.html.haml | 53 +++++++---- app/views/projects/labels/new.html.haml | 3 +- app/views/shared/_label_row.html.haml | 3 +- .../projects/labels/update_prioritization_spec.rb | 104 +++++++++++++-------- 18 files changed, 226 insertions(+), 89 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 9bac6d46355..cbd009ccd07 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -140,6 +140,10 @@ color: $gray-light; } + .label-type { + opacity: 0.3; + } + li:hover { .draggable-handler { display: inline-block; @@ -148,7 +152,11 @@ } } -.other-labels { +.group-labels + .project-labels { + margin-top: 30px; +} + +.group-labels, .project-labels { .remove-priority { display: none; } diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 449298f51a8..0ec2fcda0ef 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -3,6 +3,7 @@ class Groups::LabelsController < Groups::ApplicationController before_action :label, only: [:edit, :update, :destroy, :toggle_subscription] before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] + before_action :save_previous_label_path, only: [:edit] respond_to :html @@ -25,11 +26,12 @@ class Groups::LabelsController < Groups::ApplicationController end def edit + @previous_labels_path = previous_labels_path end def update if @label.update_attributes(label_params) - redirect_to group_labels_path(@group) + redirect_back_or_group_labels_path else render :edit end @@ -64,4 +66,20 @@ class Groups::LabelsController < Groups::ApplicationController def label_params params.require(:label).permit(:title, :description, :color) end + + def redirect_back_or_group_labels_path(options = {}) + redirect_to previous_labels_path, options + end + + def previous_labels_path + session.fetch(:previous_labels_path, fallback_path) + end + + def fallback_path + group_labels_path(@group) + end + + def save_previous_label_path + session[:previous_labels_path] = URI(request.referer || '').path + end end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index b2ff36f6538..5daf4311cc8 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -44,6 +44,10 @@ class Projects::ApplicationController < ApplicationController @project end + def project_labels + @project_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + end + def repository @repository ||= project.repository end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 4f6d7ca80df..1558426e1a4 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -48,13 +48,13 @@ class Projects::IssuesController < Projects::ApplicationController ) @issue = @noteable = @project.issues.new(issue_params) - @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + @labels = project_labels respond_with(@issue) end def edit - @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + @labels = project_labels respond_with(@issue) end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 35224f965bf..3db3c091da6 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -11,13 +11,15 @@ class Projects::LabelsController < Projects::ApplicationController respond_to :js, :html def index + @project_labels = project_labels + @prioritized_labels = project_labels.prioritized + @group_labels = @project.group.labels.unprioritized if @project.group.present? @labels = @project.labels.unprioritized.page(params[:page]) - @prioritized_labels = @project.labels.prioritized respond_to do |format| format.html format.json do - render json: LabelsFinder.new(current_user, project_id: @project.id).execute + render json: @project_labels end end end @@ -68,6 +70,7 @@ class Projects::LabelsController < Projects::ApplicationController def destroy @label.destroy + @project_labels = project_labels respond_to do |format| format.html do @@ -80,6 +83,8 @@ class Projects::LabelsController < Projects::ApplicationController def remove_priority respond_to do |format| + label = project_labels.find(params[:id]) + if label.update_attribute(:priority, nil) format.json { render json: label } else @@ -92,7 +97,7 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do params[:label_ids].each_with_index do |label_id, index| - label = @project.labels.find_by_id(label_id) + label = project_labels.find_by_id(label_id) label.update_attribute(:priority, index) if label end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index c8970155497..291c3f64914 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -263,7 +263,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @source_project = @merge_request.source_project @target_project = @merge_request.target_project @target_branches = @merge_request.target_project.repository.branch_names - @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + @labels = project_labels end def update diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 540eb6dd493..3f0e502fbc9 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -68,6 +68,49 @@ module LabelsHelper end end + def can_admin_label(label) + subject = + case label + when GroupLabel then label.group + else label.project + end + + can?(current_user, :admin_label, subject) + end + + def edit_label_path(label) + case label + when GroupLabel then edit_group_label_path(label.group, label) + else edit_namespace_project_label_path(label.project.namespace, label.project, label) + end + end + + def destroy_label_path(label) + case label + when GroupLabel then group_label_path(label.group, label) + else namespace_project_label_path(label.project.namespace, label.project, label) + end + end + + def label_type_icon(label, options = {}) + title, icon = + case label + when GroupLabel then ['Group', 'folder-open'] + else ['Project', 'bookmark'] + end + + options[:class] ||= '' + options[:class] << ' has-tooltip js-label-type' + + content_tag :span, + class: options[:class], + data: { 'placement' => 'top' }, + title: title, + aria: { label: title } do + icon(icon, base: true) + end + end + def project_label_names @project.labels.pluck(:title) end diff --git a/app/models/label.rb b/app/models/label.rb index 295c5bfaf70..f43bebbf71b 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -101,16 +101,16 @@ class Label < ActiveRecord::Base end end - def open_issues_count(user = nil) - issues.visible_to_user(user).opened.count + def open_issues_count(user = nil, project = nil) + issues_count(user, project_id: project.try(:id) || project_id, state: 'opened') end - def closed_issues_count(user = nil) - issues.visible_to_user(user).closed.count + def closed_issues_count(user = nil, project = nil) + issues_count(user, project_id: project.try(:id) || project_id, state: 'closed') end - def open_merge_requests_count - merge_requests.opened.count + def open_merge_requests_count(user = nil, project = nil) + merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened') end def template? @@ -127,6 +127,18 @@ class Label < ActiveRecord::Base private + def issues_count(user, params = {}) + IssuesFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) + .execute + .count + end + + def merge_requests_count(user, params = {}) + MergeRequestsFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) + .execute + .count + end + def project_label? type.blank? && !template? end diff --git a/app/views/groups/labels/_form.html.haml b/app/views/groups/labels/_form.html.haml index 008b5fb9ba1..a0b44b0dcfb 100644 --- a/app/views/groups/labels/_form.html.haml +++ b/app/views/groups/labels/_form.html.haml @@ -30,4 +30,4 @@ = f.submit 'Save changes', class: 'btn btn-save js-save-button' - else = f.submit 'Create Label', class: 'btn btn-create js-save-button' - = link_to "Cancel", group_labels_path(@group), class: 'btn btn-cancel' + = link_to 'Cancel', @previous_labels_path, class: 'btn btn-cancel' diff --git a/app/views/groups/labels/_label.html.haml b/app/views/groups/labels/_label.html.haml index b9aab76f057..9faf90c303e 100644 --- a/app/views/groups/labels/_label.html.haml +++ b/app/views/groups/labels/_label.html.haml @@ -1,6 +1,6 @@ - label_css_id = dom_id(label) - open_issues_count = label.open_issues_count(current_user) -- open_merge_requests_count = label.open_merge_requests_count +- open_merge_requests_count = label.open_merge_requests_count(current_user) %li{id: label_css_id, data: { id: label.id } } = render 'label_row', label: label diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index 6ab6ae50389..5f7be074f25 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -30,4 +30,4 @@ = f.submit 'Save changes', class: 'btn btn-save js-save-button' - else = f.submit 'Create Label', class: 'btn btn-create js-save-button' - = link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel' + = link_to 'Cancel', namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 71f7f354d72..2b7b79390f7 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -1,4 +1,7 @@ - label_css_id = dom_id(label) +- open_issues_count = label.open_issues_count(current_user, @project) +- open_merge_requests_count = label.open_merge_requests_count(current_user, @project) + %li{id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label @@ -10,25 +13,25 @@ %ul %li = link_to_label(label, type: :merge_request) do - = pluralize label.open_merge_requests_count, 'merge request' + = pluralize open_merge_requests_count, 'merge request' %li = link_to_label(label) do - = pluralize label.open_issues_count(current_user), 'open issue' + = pluralize open_issues_count, 'open issue' - if current_user %li.label-subscription{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } %span= label_subscription_toggle_button_text(label) - - if can? current_user, :admin_label, @project + - if can_admin_label(label) %li - = link_to "Edit", edit_namespace_project_label_path(@project.namespace, @project, label) + = link_to 'Edit', edit_label_path(label) %li - = link_to "Delete", namespace_project_label_path(@project.namespace, @project, label), title: "Delete", method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} .pull-right.hidden-xs.hidden-sm.hidden-md = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do - = pluralize label.open_merge_requests_count, 'merge request' + = pluralize open_merge_requests_count, 'merge request' = link_to_label(label, css_class: 'btn btn-transparent btn-action') do - = pluralize label.open_issues_count(current_user), 'open issue' + = pluralize open_issues_count, 'open issue' - if current_user .label-subscription.inline{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } @@ -37,11 +40,11 @@ = icon('eye', class: 'label-subscribe-button-icon') = icon('spinner spin', class: 'label-subscribe-button-loading') - - if can? current_user, :admin_label, @project - = link_to edit_namespace_project_label_path(@project.namespace, @project, label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do + - if can_admin_label(label) + = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do %span.sr-only Edit = icon('pencil-square-o') - = link_to namespace_project_label_path(@project.namespace, @project, label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do + = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do %span.sr-only Delete = icon('trash-o') diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml index d59563b122a..0b9937c4808 100644 --- a/app/views/projects/labels/destroy.js.haml +++ b/app/views/projects/labels/destroy.js.haml @@ -1,2 +1,2 @@ -- if @project.labels.size == 0 +- if @project_labels.none? $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000) diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 52b187e7e58..c9ec371c3e1 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -4,6 +4,7 @@ %div{ class: container_class } %h3.page-title - Edit Label + = icon('bookmark') + Edit Project Label %hr = render 'form' diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index db66a0edbd8..286b58f57c2 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -14,25 +14,38 @@ New label .labels - - if can?(current_user, :admin_label, @project) + - unless @project_labels.empty? -# Only show it in the first page - - hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1') - .prioritized-labels{ class: ('hide' if hide) } - %h5 Prioritized Labels - %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } - %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - - if @prioritized_labels.present? - = render @prioritized_labels - .other-labels + - hide = params[:page].present? && params[:page] != '1' - if can?(current_user, :admin_label, @project) - %h5{ class: ('hide' if hide) } Other Labels - - if @labels.present? - %ul.content-list.manage-labels-list.js-other-labels - = render @labels - = paginate @labels, theme: 'gitlab' - - else - .nothing-here-block - - if can?(current_user, :admin_label, @project) - Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. - - else - No labels created + .prioritized-labels{ class: ('hide' if hide) } + %h5 Prioritized Labels + %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } + %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet + - if @prioritized_labels.present? + = render partial: 'label', collection: @prioritized_labels, as: :label + + .group-labels{ class: ('hide' if hide || @project.group.blank?) } + %h5 + = icon('folder-open') + Group Labels + %ul.content-list.manage-labels-list.js-group-labels + %p.empty-message{ class: ('hidden' unless @group_labels.empty?) } No group labels + - if @group_labels.present? + = render partial: 'label', collection: @group_labels, as: :label + + .project-labels + %h5{ class: ('hide' if hide) } + = icon('bookmark') + Project Labels + %ul.content-list.manage-labels-list.js-project-labels + %p.empty-message{ class: ('hidden' unless @labels.empty?) } No project labels + - if @labels.present? + = render @labels + = paginate @labels, theme: 'gitlab' + - else + .nothing-here-block + - if can?(current_user, :admin_label, @project) + Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. + - else + No labels created yet. diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index a1bb66cfb6c..a1e2df6c55d 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -4,6 +4,7 @@ %div{ class: container_class } %h3.page-title - New Label + = icon('bookmark') + New Project Label %hr = render 'form' diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 6f593e8dff9..751b2d1c158 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -3,13 +3,14 @@ .draggable-handler = icon('bars') .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label), - dom_id: dom_id(label) } } + dom_id: dom_id(label), type: label.type } } %button.add-priority.btn.has-tooltip{ title: 'Prioritize', :'data-placement' => 'top' } = icon('star-o') %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' } = icon('star') %span.label-name = link_to_label(label, tooltip: false) + = label_type_icon(label, class: "#{'hidden' if label.priority.blank?}" ) - if label.description %span.label-description = markdown_field(label, :description) diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index cb7495da8eb..21896f0a787 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -3,76 +3,106 @@ require 'spec_helper' feature 'Prioritize labels', feature: true do include WaitForAjax - context 'when project belongs to user' do - let(:user) { create(:user) } - let(:project) { create(:project, name: 'test', namespace: user.namespace) } + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let!(:bug) { create(:label, project: project, title: 'bug') } + let!(:wontfix) { create(:label, project: project, title: 'wontfix') } + let!(:feature) { create(:group_label, group: group, title: 'feature') } - scenario 'user can prioritize a label', js: true do - bug = create(:label, title: 'bug') - wontfix = create(:label, title: 'wontfix') - - project.labels << bug - project.labels << wontfix + context 'when user belongs to project team' do + before do + project.team << [user, :developer] login_as user + end + + scenario 'user can prioritize a group label', js: true do visit namespace_project_labels_path(project.namespace, project) expect(page).to have_content('No prioritized labels yet') - page.within('.other-labels') do + page.within('.group-labels') do first('.js-toggle-priority').click wait_for_ajax - expect(page).not_to have_content('bug') + expect(page).not_to have_content('feature') end page.within('.prioritized-labels') do expect(page).not_to have_content('No prioritized labels yet') - expect(page).to have_content('bug') + expect(page).to have_content('feature') end end - scenario 'user can unprioritize a label', js: true do - bug = create(:label, title: 'bug', priority: 1) - wontfix = create(:label, title: 'wontfix') + scenario 'user can unprioritize a group label', js: true do + feature.update(priority: 1) - project.labels << bug - project.labels << wontfix + visit namespace_project_labels_path(project.namespace, project) - login_as user + page.within('.prioritized-labels') do + expect(page).to have_content('feature') + + first('.js-toggle-priority').click + wait_for_ajax + expect(page).not_to have_content('bug') + end + + page.within('.group-labels') do + expect(page).to have_content('feature') + end + end + + scenario 'user can prioritize a project label', js: true do visit namespace_project_labels_path(project.namespace, project) - expect(page).to have_content('bug') + expect(page).to have_content('No prioritized labels yet') + + page.within('.project-labels') do + first('.js-toggle-priority').click + wait_for_ajax + expect(page).not_to have_content('bug') + end + + page.within('.prioritized-labels') do + expect(page).not_to have_content('No prioritized labels yet') + expect(page).to have_content('bug') + end + end + + scenario 'user can unprioritize a project label', js: true do + bug.update(priority: 1) + + visit namespace_project_labels_path(project.namespace, project) page.within('.prioritized-labels') do + expect(page).to have_content('bug') + first('.js-toggle-priority').click wait_for_ajax expect(page).not_to have_content('bug') end - page.within('.other-labels') do + page.within('.project-labels') do expect(page).to have_content('bug') expect(page).to have_content('wontfix') end end scenario 'user can sort prioritized labels and persist across reloads', js: true do - bug = create(:label, title: 'bug', priority: 1) - wontfix = create(:label, title: 'wontfix', priority: 2) - - project.labels << bug - project.labels << wontfix + bug.update(priority: 1) + feature.update(priority: 2) - login_as user visit namespace_project_labels_path(project.namespace, project) expect(page).to have_content 'bug' + expect(page).to have_content 'feature' expect(page).to have_content 'wontfix' # Sort labels - find("#label_#{bug.id}").drag_to find("#label_#{wontfix.id}") + find("#project_label_#{bug.id}").drag_to find("#group_label_#{feature.id}") page.within('.prioritized-labels') do - expect(first('li')).to have_content('wontfix') + expect(first('li')).to have_content('feature') expect(page.all('li').last).to have_content('bug') end @@ -80,7 +110,7 @@ feature 'Prioritize labels', feature: true do wait_for_ajax page.within('.prioritized-labels') do - expect(first('li')).to have_content('wontfix') + expect(first('li')).to have_content('feature') expect(page.all('li').last).to have_content('bug') end end @@ -88,28 +118,26 @@ feature 'Prioritize labels', feature: true do context 'as a guest' do it 'does not prioritize labels' do - user = create(:user) guest = create(:user) - project = create(:project, name: 'test', namespace: user.namespace) - - create(:label, title: 'bug') login_as guest + visit namespace_project_labels_path(project.namespace, project) + expect(page).to have_content 'bug' + expect(page).to have_content 'wontfix' + expect(page).to have_content 'feature' expect(page).not_to have_css('.prioritized-labels') end end context 'as a non signed in user' do it 'does not prioritize labels' do - user = create(:user) - project = create(:project, name: 'test', namespace: user.namespace) - - create(:label, title: 'bug') - visit namespace_project_labels_path(project.namespace, project) + expect(page).to have_content 'bug' + expect(page).to have_content 'wontfix' + expect(page).to have_content 'feature' expect(page).not_to have_css('.prioritized-labels') end end -- cgit v1.2.1 From 9463551ece6c12574559a4768943ab90db7f617b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 00:17:04 -0300 Subject: Validates uniqueness of title unless label is a template --- app/models/label.rb | 6 ++---- spec/models/label_spec.rb | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/label.rb b/app/models/label.rb index f43bebbf71b..be0c20479d5 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -26,10 +26,8 @@ class Label < ActiveRecord::Base validates :project, presence: true, if: :project_label? # Don't allow ',' for label titles - validates :title, - presence: true, - format: { with: /\A[^,]+\z/ }, - uniqueness: { scope: :project_id } + validates :title, presence: true, format: { with: /\A[^,]+\z/ } + validates :title, uniqueness: true, unless: :template? before_save :nullify_priority diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 5a5d1a5d60c..894021dc8e6 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -19,6 +19,7 @@ describe Label, models: true do describe 'validation' do it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_uniqueness_of(:title) } it 'validates color code' do expect(label).not_to allow_value('G-ITLAB').for(:color) -- cgit v1.2.1 From 7f2e29ff3da54c4525dc55b4447fea2963e17fd3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 00:18:50 -0300 Subject: Remove unused method LabelsHelper#project_label_names --- app/helpers/labels_helper.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 3f0e502fbc9..9df8d37af9e 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -111,10 +111,6 @@ module LabelsHelper end end - def project_label_names - @project.labels.pluck(:title) - end - def render_colored_label(label, label_suffix = '', tooltip: true) label_color = label.color || Label::DEFAULT_COLOR text_color = text_color_for_bg(label_color) -- cgit v1.2.1 From 476c26deb22a6e958dc3251e9771560b058a34a3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 00:59:26 -0300 Subject: Replace label references with links for group labels --- lib/banzai/filter/label_reference_filter.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 8f262ef3d8d..3a09912f1be 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -9,7 +9,7 @@ module Banzai end def find_object(project, id) - project.labels.find(id) + find_labels(project).find(id) end def self.references_in(text, pattern = Label.reference_pattern) @@ -35,7 +35,17 @@ module Banzai return unless project label_params = label_params(label_id, label_name) - project.labels.find_by(label_params) + find_labels(project).find_by(label_params) + end + + def find_labels(project) + label_ids = [] + label_ids << project.group.labels.select(:id) if project.group.present? + label_ids << project.labels.select(:id) + + union = Gitlab::SQL::Union.new(label_ids) + + object_class.where("labels.id IN (#{union.to_sql})") end # Parameters to pass to `Label.find_by` based on the given arguments @@ -60,7 +70,7 @@ module Banzai end def object_link_text(object, matches) - if context[:project] == object.project + if object.project.nil? || object.project == context[:project] LabelsHelper.render_colored_label(object) else LabelsHelper.render_colored_cross_project_label(object) -- cgit v1.2.1 From cf14482a5aceb62c178c19cc70e9354dc23dd9e1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 01:08:04 -0300 Subject: LabelsFinder inherits from UnionFinder --- app/finders/labels_finder.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index cf7018cf8a2..a27ff56309b 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -1,11 +1,11 @@ -class LabelsFinder +class LabelsFinder < UnionFinder def initialize(current_user, params = {}) @current_user = current_user @params = params end def execute - items = init_collection + items = find_union(label_ids, Label) items = with_title(items) sort(items) end @@ -14,15 +14,10 @@ class LabelsFinder attr_reader :current_user, :params - def init_collection + def label_ids label_ids = [] label_ids << Label.where(group_id: projects.where.not(group: nil).select(:namespace_id)).select(:id) - label_ids << Label.where(project_id: projects).select(:id) - - union = Gitlab::SQL::Union.new(label_ids) - - Label.where("labels.id IN (#{union.to_sql})") - .reorder(title: :asc) + label_ids << Label.where(project_id: projects.select(:id)).select(:id) end def with_title(items) -- cgit v1.2.1 From baf47a0bd0e0563cbc99b3ae4b1336b8b3b4380a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 11:03:41 -0300 Subject: Remove project_labels from Projects::ApplicationController --- app/controllers/concerns/issuable_actions.rb | 5 ++ app/controllers/projects/application_controller.rb | 4 -- app/controllers/projects/issues_controller.rb | 6 +- app/controllers/projects/labels_controller.rb | 28 ++++---- .../projects/merge_requests_controller.rb | 1 - app/views/projects/labels/destroy.js.haml | 2 +- app/views/projects/labels/index.html.haml | 10 +-- .../controllers/projects/labels_controller_spec.rb | 80 ++++++++++++++++------ 8 files changed, 88 insertions(+), 48 deletions(-) diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index bb32bc502e6..27f1e91d865 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -2,6 +2,7 @@ module IssuableActions extend ActiveSupport::Concern included do + before_action :labels, only: [:new, :edit] before_action :authorize_destroy_issuable!, only: :destroy before_action :authorize_admin_issuable!, only: :bulk_update end @@ -25,6 +26,10 @@ module IssuableActions private + def labels + @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + end + def authorize_destroy_issuable! unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable) return access_denied! diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 5daf4311cc8..b2ff36f6538 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -44,10 +44,6 @@ class Projects::ApplicationController < ApplicationController @project end - def project_labels - @project_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute - end - def repository @repository ||= project.repository end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 1558426e1a4..9f18c8c03df 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -47,15 +47,11 @@ class Projects::IssuesController < Projects::ApplicationController assignee_id: "" ) - @issue = @noteable = @project.issues.new(issue_params) - @labels = project_labels - + @issue = @noteable = @project.issues.new(issue_params) respond_with(@issue) end def edit - @labels = project_labels - respond_with(@issue) end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 3db3c091da6..33c3b7f79c2 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -3,23 +3,23 @@ class Projects::LabelsController < Projects::ApplicationController before_action :module_enabled before_action :label, only: [:edit, :update, :destroy] + before_action :labels, only: [:index] before_action :authorize_read_label! - before_action :authorize_admin_labels!, only: [ - :new, :create, :edit, :update, :generate, :destroy, :remove_priority, :set_priorities - ] + before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, + :generate, :destroy, :remove_priority, + :set_priorities] respond_to :js, :html def index - @project_labels = project_labels - @prioritized_labels = project_labels.prioritized + @prioritized_labels = @labels.prioritized @group_labels = @project.group.labels.unprioritized if @project.group.present? - @labels = @project.labels.unprioritized.page(params[:page]) + @project_labels = @project.labels.unprioritized.page(params[:page]) respond_to do |format| format.html format.json do - render json: @project_labels + render json: @labels end end end @@ -38,7 +38,7 @@ class Projects::LabelsController < Projects::ApplicationController end else respond_to do |format| - format.html { render 'new' } + format.html { render :new } format.json { render json: { message: @label.errors.messages }, status: 400 } end end @@ -51,7 +51,7 @@ class Projects::LabelsController < Projects::ApplicationController if @label.update_attributes(label_params) redirect_to namespace_project_labels_path(@project.namespace, @project) else - render 'edit' + render :edit end end @@ -70,7 +70,7 @@ class Projects::LabelsController < Projects::ApplicationController def destroy @label.destroy - @project_labels = project_labels + @labels = labels respond_to do |format| format.html do @@ -83,7 +83,7 @@ class Projects::LabelsController < Projects::ApplicationController def remove_priority respond_to do |format| - label = project_labels.find(params[:id]) + label = labels.find(params[:id]) if label.update_attribute(:priority, nil) format.json { render json: label } @@ -97,7 +97,7 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do params[:label_ids].each_with_index do |label_id, index| - label = project_labels.find_by_id(label_id) + label = labels.find_by_id(label_id) label.update_attribute(:priority, index) if label end end @@ -124,6 +124,10 @@ class Projects::LabelsController < Projects::ApplicationController end alias_method :subscribable_resource, :label + def labels + @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + end + def authorize_admin_labels! return render_404 unless can?(current_user, :admin_label, @project) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 291c3f64914..9171b47cda1 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -263,7 +263,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @source_project = @merge_request.source_project @target_project = @merge_request.target_project @target_branches = @merge_request.target_project.repository.branch_names - @labels = project_labels end def update diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml index 0b9937c4808..8d09e2bda11 100644 --- a/app/views/projects/labels/destroy.js.haml +++ b/app/views/projects/labels/destroy.js.haml @@ -1,2 +1,2 @@ -- if @project_labels.none? +- if @labels.empty? $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 286b58f57c2..05282338493 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -14,7 +14,7 @@ New label .labels - - unless @project_labels.empty? + - unless @labels.empty? -# Only show it in the first page - hide = params[:page].present? && params[:page] != '1' - if can?(current_user, :admin_label, @project) @@ -39,10 +39,10 @@ = icon('bookmark') Project Labels %ul.content-list.manage-labels-list.js-project-labels - %p.empty-message{ class: ('hidden' unless @labels.empty?) } No project labels - - if @labels.present? - = render @labels - = paginate @labels, theme: 'gitlab' + %p.empty-message{ class: ('hidden' unless @project_labels.empty?) } No project labels + - if @project_labels.present? + = render @project_labels + = paginate @project_labels, theme: 'gitlab' - else .nothing-here-block - if can?(current_user, :admin_label, @project) diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 3492b6ffbbb..2b39f9cf0d1 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -1,52 +1,92 @@ require 'spec_helper' describe Projects::LabelsController do - let(:project) { create(:project) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } let(:user) { create(:user) } before do project.team << [user, :master] + sign_in(user) end describe 'GET #index' do - def create_label(attributes) - create(:label, attributes.merge(project: project)) - end + let!(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') } + let!(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') } + let!(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') } + let!(:label_4) { create(:label, project: project, priority: nil, title: 'Label 4') } + let!(:label_5) { create(:label, project: project, priority: nil, title: 'Label 5') } - before do - 15.times { |i| create_label(priority: (i % 3) + 1, title: "label #{15 - i}") } - 5.times { |i| create_label(title: "label #{100 - i}") } - - get :index, namespace_id: project.namespace.to_param, project_id: project.to_param - end + let!(:group_label_1) { create(:group_label, group: group, priority: 3, title: 'Group Label 1') } + let!(:group_label_2) { create(:group_label, group: group, priority: 1, title: 'Group Label 2') } + let!(:group_label_3) { create(:group_label, group: group, priority: nil, title: 'Group Label 3') } + let!(:group_label_4) { create(:group_label, group: group, priority: nil, title: 'Group Label 4') } context '@prioritized_labels' do - let(:prioritized_labels) { assigns(:prioritized_labels) } + before do + list_labels + end it 'contains only prioritized labels' do - expect(prioritized_labels).to all(have_attributes(priority: a_value > 0)) + expect(assigns(:prioritized_labels)).to all(have_attributes(priority: a_value > 0)) end it 'is sorted by priority, then label title' do - priorities_and_titles = prioritized_labels.pluck(:priority, :title) - - expect(priorities_and_titles.sort).to eq(priorities_and_titles) + expect(assigns(:prioritized_labels)).to match_array [group_label_2, label_1, label_3, group_label_1, label_2] end end - context '@labels' do - let(:labels) { assigns(:labels) } + context '@group_labels' do + it 'contains only group labels' do + list_labels + + expect(assigns(:group_labels)).to all(have_attributes(group_id: a_value > 0)) + end it 'contains only unprioritized labels' do - expect(labels).to all(have_attributes(priority: nil)) + list_labels + + expect(assigns(:group_labels)).to all(have_attributes(priority: nil)) end it 'is sorted by label title' do - titles = labels.pluck(:title) + list_labels - expect(titles.sort).to eq(titles) + expect(assigns(:group_labels)).to match_array [group_label_3, group_label_4] end + + it 'is nil when project does not belong to a group' do + project.update(namespace: create(:namespace)) + + list_labels + + expect(assigns(:group_labels)).to be_nil + end + end + + context '@project_labels' do + before do + list_labels + end + + it 'contains only project labels' do + list_labels + + expect(assigns(:project_labels)).to all(have_attributes(project_id: a_value > 0)) + end + + it 'contains only unprioritized labels' do + expect(assigns(:project_labels)).to all(have_attributes(priority: nil)) + end + + it 'is sorted by label title' do + expect(assigns(:project_labels)).to match_array [label_4, label_5] + end + end + + def list_labels + get :index, namespace_id: project.namespace.to_param, project_id: project.to_param end end end -- cgit v1.2.1 From b10e5764ac0765b556d64dfebb9dad948154e31a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 11:55:31 -0300 Subject: List only labels that belongs to the group on the group issues page --- app/controllers/dashboard/labels_controller.rb | 7 +------ app/controllers/groups/labels_controller.rb | 10 +++++++++- app/finders/labels_finder.rb | 19 ++++++++++--------- app/helpers/labels_helper.rb | 3 +++ 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index 797f8503b2d..05f7bc37952 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -1,12 +1,7 @@ class Dashboard::LabelsController < Dashboard::ApplicationController def index - labels = LabelsFinder.new(current_user, project_id: projects) - .execute - .select(:id, :title, :color) - .uniq(:title) - respond_to do |format| - format.json { render json: labels } + format.json { render json: LabelsFinder.new(current_user).execute } end end end diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 0ec2fcda0ef..0ebdee55c79 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -8,7 +8,15 @@ class Groups::LabelsController < Groups::ApplicationController respond_to :html def index - @labels = @group.labels.page(params[:page]) + respond_to do |format| + format.html do + @labels = @group.labels.page(params[:page]) + end + + format.json do + render json: LabelsFinder.new(current_user, group_id: @group.id).execute + end + end end def new diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index a27ff56309b..85ef9bea08d 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -20,13 +20,17 @@ class LabelsFinder < UnionFinder label_ids << Label.where(project_id: projects.select(:id)).select(:id) end + def sort(items) + items.reorder(title: :asc, type: :desc) + end + def with_title(items) items = items.where(title: title) if title.present? items end - def sort(items) - items.reorder(title: :asc) + def group_id + params[:group_id].presence end def project_id @@ -40,13 +44,10 @@ class LabelsFinder < UnionFinder def projects return @projects if defined?(@projects) - if project_id - @projects = ProjectsFinder.new.execute(current_user) - .where(id: project_id) - .reorder(nil) - else - @projects = Project.none - end + @projects = ProjectsFinder.new.execute(current_user) + @projects = @projects.joins(:namespace).where(namespaces: { id: group_id, type: 'Group' }) if group_id + @projects = @projects.where(id: project_id) if project_id + @projects = @projects.reorder(nil) @projects end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 9df8d37af9e..8e5321c05fa 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -172,7 +172,10 @@ module LabelsHelper end def labels_filter_path + return group_labels_path(@group, :json) if @group + project = @target_project || @project + if project namespace_project_labels_path(project.namespace, project, :json) else -- cgit v1.2.1 From 1c73d302e2ce5a27aba7171af741b3590d48aba9 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 12:00:18 -0300 Subject: Avoid an extra a query per label when setting label priority --- app/controllers/projects/labels_controller.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 33c3b7f79c2..919c6f239cb 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -97,8 +97,9 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do params[:label_ids].each_with_index do |label_id, index| - label = labels.find_by_id(label_id) - label.update_attribute(:priority, index) if label + next unless labels.where(id: label_id).any? + + Label.where(id: label_id).update_all(priority: index) end end -- cgit v1.2.1 From 2910896b53f107558904e228340009bb9fccca4e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 13:54:29 -0300 Subject: Remove duplication between global and the regular label partials --- app/helpers/labels_helper.rb | 52 ++++++++++----------- app/views/groups/labels/_label.html.haml | 53 ---------------------- app/views/groups/labels/_label_row.html.haml | 6 --- app/views/groups/labels/index.html.haml | 2 +- app/views/projects/issues/_issue.html.haml | 2 +- app/views/projects/labels/_label.html.haml | 53 ---------------------- app/views/projects/labels/index.html.haml | 6 +-- .../merge_requests/_merge_request.html.haml | 2 +- app/views/shared/_label.html.haml | 53 ++++++++++++++++++++++ app/views/shared/_label_row.html.haml | 3 +- 10 files changed, 84 insertions(+), 148 deletions(-) delete mode 100644 app/views/groups/labels/_label.html.haml delete mode 100644 app/views/groups/labels/_label_row.html.haml delete mode 100644 app/views/projects/labels/_label.html.haml create mode 100644 app/views/shared/_label.html.haml diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 8e5321c05fa..65fc460c670 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -4,9 +4,8 @@ module LabelsHelper # Link to a Label # # label - Label object to link to - # project - Project object which will be used as the context for the label's - # link. If omitted, defaults to `@project`, or the label's own - # project. + # subject - Project/Group object which will be used as the context for the + # label's link. If omitted, defaults to the label's own group/project. # type - The type of item the link will point to (:issue or # :merge_request). If omitted, defaults to :issue. # block - An optional block that will be passed to `link_to`, forming the @@ -18,12 +17,11 @@ module LabelsHelper # # Allow the generated link to use the label's own project # link_to_label(label) # - # # Force the generated link to use @project - # @project = Project.first - # link_to_label(label) + # # Force the generated link to use a provided group + # link_to_label(label, subject: Group.last) # # # Force the generated link to use a provided project - # link_to_label(label, project: Project.last) + # link_to_label(label, subject: Project.last) # # # Force the generated link to point to merge requests instead of issues # link_to_label(label, type: :merge_request) @@ -32,9 +30,8 @@ module LabelsHelper # link_to_label(label) { "My Custom Label Text" } # # Returns a String - def link_to_label(label, project: nil, type: :issue, tooltip: true, css_class: nil, &block) - project ||= @project || label.project - link = label_filter_path(project, label, type: type) + def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block) + link = label_filter_path(label, type: type) if block_given? link_to link, class: css_class, &block @@ -43,27 +40,16 @@ module LabelsHelper end end - def link_to_group_label(label, group: nil, type: :issue, tooltip: true, css_class: nil, &block) - group ||= @group || label.group - link = label_filter_path(group, label, type: type) - - if block_given? - link_to link, class: css_class, &block + def label_filter_path(label, type: issue) + case label + when GroupLabel + send("#{type.to_s.pluralize}_group_path", + label.group, + label_name: [label.name]) else - link_to render_colored_label(label, tooltip: tooltip), link, class: css_class - end - end - - def label_filter_path(subject, label, type: issue) - case subject - when Project send("namespace_project_#{type.to_s.pluralize}_path", - subject.namespace, - subject, - label_name: [label.name]) - when Group - send("#{type.to_s.pluralize}_group_path", - subject, + label.project.namespace, + label.project, label_name: [label.name]) end end @@ -92,6 +78,13 @@ module LabelsHelper end end + def toggle_subscription_label_path(label) + case label + when GroupLabel then toggle_subscription_group_label_path(label.group, label) + else toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) + end + end + def label_type_icon(label, options = {}) title, icon = case label @@ -101,6 +94,7 @@ module LabelsHelper options[:class] ||= '' options[:class] << ' has-tooltip js-label-type' + options[:class] << ' hidden' if options.fetch(:hidden, false) content_tag :span, class: options[:class], diff --git a/app/views/groups/labels/_label.html.haml b/app/views/groups/labels/_label.html.haml deleted file mode 100644 index 9faf90c303e..00000000000 --- a/app/views/groups/labels/_label.html.haml +++ /dev/null @@ -1,53 +0,0 @@ -- label_css_id = dom_id(label) -- open_issues_count = label.open_issues_count(current_user) -- open_merge_requests_count = label.open_merge_requests_count(current_user) - -%li{id: label_css_id, data: { id: label.id } } - = render 'label_row', label: label - - .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown - %button.btn.btn-default.label-options-toggle{ data: { toggle: 'dropdown' } } - Options - %span.caret - .dropdown-menu.dropdown-menu-align-right - %ul - %li - = link_to_group_label(label, type: :merge_request) do - = pluralize open_merge_requests_count, 'merge request' - %li - = link_to_group_label(label) do - = pluralize open_issues_count, 'open issue' - - if current_user - %li.label-subscription{ data: { url: toggle_subscription_group_label_path(@group, label) } } - %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } - %span= label_subscription_toggle_button_text(label) - - if can? current_user, :admin_label, @group - %li - = link_to 'Edit', edit_group_label_path(@group, label) - %li - = link_to 'Delete', group_label_path(@group, label), title: 'Delete', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} - - .pull-right.hidden-xs.hidden-sm.hidden-md - = link_to_group_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do - = pluralize open_merge_requests_count, 'merge request' - = link_to_group_label(label, css_class: 'btn btn-transparent btn-action') do - = pluralize open_issues_count, 'open issue' - - - if current_user - .label-subscription.inline{ data: { url: toggle_subscription_group_label_path(@group, label) } } - %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } - %span.sr-only= label_subscription_toggle_button_text(label) - = icon('eye', class: 'label-subscribe-button-icon') - = icon('spinner spin', class: 'label-subscribe-button-loading') - - - if can? current_user, :admin_label, @group - = link_to edit_group_label_path(@group, label), title: 'Edit', class: 'btn btn-transparent btn-action', data: {toggle: 'tooltip'} do - %span.sr-only Edit - = icon('pencil-square-o') - = link_to group_label_path(@group, label), title: 'Delete', class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?', toggle: 'tooltip'} do - %span.sr-only Delete - = icon('trash-o') - - - if current_user - :javascript - new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/app/views/groups/labels/_label_row.html.haml b/app/views/groups/labels/_label_row.html.haml deleted file mode 100644 index e21fac25b01..00000000000 --- a/app/views/groups/labels/_label_row.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -%span.label-row - %span.label-name - = link_to_group_label(label, tooltip: false) - - if label.description - %span.label-description - = markdown(label.description, pipeline: :single_line) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index d9f1d350cb3..8e93ea4f625 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -17,7 +17,7 @@ Group Labels - if @labels.present? %ul.content-list.manage-labels-list.js-group-labels - = render partial: 'label', collection: @labels, as: :label + = render partial: 'shared/label', collection: @labels, as: :label = paginate @labels, theme: 'gitlab' - else .nothing-here-block diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 8b1a8a8a2d9..c80210d6ff4 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -50,7 +50,7 @@ - if issue.labels.any?   - issue.labels.each do |label| - = link_to_label(label, project: issue.project) + = link_to_label(label, subject: issue.project) - if issue.tasks?   %span.task-status diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml deleted file mode 100644 index 2b7b79390f7..00000000000 --- a/app/views/projects/labels/_label.html.haml +++ /dev/null @@ -1,53 +0,0 @@ -- label_css_id = dom_id(label) -- open_issues_count = label.open_issues_count(current_user, @project) -- open_merge_requests_count = label.open_merge_requests_count(current_user, @project) - -%li{id: label_css_id, data: { id: label.id } } - = render "shared/label_row", label: label - - .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown - %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } } - Options - = icon('caret-down') - .dropdown-menu.dropdown-menu-align-right - %ul - %li - = link_to_label(label, type: :merge_request) do - = pluralize open_merge_requests_count, 'merge request' - %li - = link_to_label(label) do - = pluralize open_issues_count, 'open issue' - - if current_user - %li.label-subscription{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } - %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } - %span= label_subscription_toggle_button_text(label) - - if can_admin_label(label) - %li - = link_to 'Edit', edit_label_path(label) - %li - = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} - - .pull-right.hidden-xs.hidden-sm.hidden-md - = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do - = pluralize open_merge_requests_count, 'merge request' - = link_to_label(label, css_class: 'btn btn-transparent btn-action') do - = pluralize open_issues_count, 'open issue' - - - if current_user - .label-subscription.inline{ data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } } - %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } - %span.sr-only= label_subscription_toggle_button_text(label) - = icon('eye', class: 'label-subscribe-button-icon') - = icon('spinner spin', class: 'label-subscribe-button-loading') - - - if can_admin_label(label) - = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do - %span.sr-only Edit - = icon('pencil-square-o') - = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do - %span.sr-only Delete - = icon('trash-o') - - - if current_user - :javascript - new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 05282338493..8e6f84fc430 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -23,7 +23,7 @@ %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - if @prioritized_labels.present? - = render partial: 'label', collection: @prioritized_labels, as: :label + = render partial: 'shared/label', collection: @prioritized_labels, as: :label .group-labels{ class: ('hide' if hide || @project.group.blank?) } %h5 @@ -32,7 +32,7 @@ %ul.content-list.manage-labels-list.js-group-labels %p.empty-message{ class: ('hidden' unless @group_labels.empty?) } No group labels - if @group_labels.present? - = render partial: 'label', collection: @group_labels, as: :label + = render partial: 'shared/label', collection: @group_labels, as: :label .project-labels %h5{ class: ('hide' if hide) } @@ -41,7 +41,7 @@ %ul.content-list.manage-labels-list.js-project-labels %p.empty-message{ class: ('hidden' unless @project_labels.empty?) } No project labels - if @project_labels.present? - = render @project_labels + = render partial: 'shared/label', collection: @project_labels, as: :label = paginate @project_labels, theme: 'gitlab' - else .nothing-here-block diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 68fb7d5a414..ad62bf50b57 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -62,7 +62,7 @@ - if merge_request.labels.any?   - merge_request.labels.each do |label| - = link_to_label(label, project: merge_request.project, type: 'merge_request') + = link_to_label(label, subject: merge_request.project, type: 'merge_request') - if merge_request.tasks?   %span.task-status diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml new file mode 100644 index 00000000000..13795807ab8 --- /dev/null +++ b/app/views/shared/_label.html.haml @@ -0,0 +1,53 @@ +- label_css_id = dom_id(label) +- open_issues_count = label.open_issues_count(current_user, @project) +- open_merge_requests_count = label.open_merge_requests_count(current_user, @project) + +%li{id: label_css_id, data: { id: label.id } } + = render "shared/label_row", label: label + + .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown + %button.btn.btn-default.label-options-toggle{ data: { toggle: "dropdown" } } + Options + = icon('caret-down') + .dropdown-menu.dropdown-menu-align-right + %ul + %li + = link_to_label(label, type: :merge_request) do + = pluralize open_merge_requests_count, 'merge request' + %li + = link_to_label(label) do + = pluralize open_issues_count, 'open issue' + - if current_user + %li.label-subscription{ data: { url: toggle_subscription_label_path(label) } } + %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } + %span= label_subscription_toggle_button_text(label) + - if can_admin_label(label) + %li + = link_to 'Edit', edit_label_path(label) + %li + = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} + + .pull-right.hidden-xs.hidden-sm.hidden-md + = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do + = pluralize open_merge_requests_count, 'merge request' + = link_to_label(label, css_class: 'btn btn-transparent btn-action') do + = pluralize open_issues_count, 'open issue' + + - if current_user + .label-subscription.inline{ data: { url: toggle_subscription_label_path(label) } } + %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } + %span.sr-only= label_subscription_toggle_button_text(label) + = icon('eye', class: 'label-subscribe-button-icon') + = icon('spinner spin', class: 'label-subscribe-button-loading') + + - if can_admin_label(label) + = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do + %span.sr-only Edit + = icon('pencil-square-o') + = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do + %span.sr-only Delete + = icon('trash-o') + + - if current_user + :javascript + new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 751b2d1c158..8a1ebdd7fb6 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -10,7 +10,8 @@ = icon('star') %span.label-name = link_to_label(label, tooltip: false) - = label_type_icon(label, class: "#{'hidden' if label.priority.blank?}" ) + - if can?(current_user, :admin_label, @project) + = label_type_icon(label, hidden: label.priority.blank?) - if label.description %span.label-description = markdown_field(label, :description) -- cgit v1.2.1 From 701544fb48a5add0cc7cbba729e6438d7a040385 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 15:19:57 -0300 Subject: Hides project/group labels section if there are none --- app/views/projects/labels/index.html.haml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 8e6f84fc430..99f8e8095ad 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -18,28 +18,26 @@ -# Only show it in the first page - hide = params[:page].present? && params[:page] != '1' - if can?(current_user, :admin_label, @project) - .prioritized-labels{ class: ('hide' if hide) } + .prioritized-labels{ class: ('hidden' if hide) } %h5 Prioritized Labels %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - if @prioritized_labels.present? = render partial: 'shared/label', collection: @prioritized_labels, as: :label - .group-labels{ class: ('hide' if hide || @project.group.blank?) } + .group-labels{ class: ('hidden' if hide || @project.group.blank? || @group_labels.empty?) } %h5 = icon('folder-open') Group Labels %ul.content-list.manage-labels-list.js-group-labels - %p.empty-message{ class: ('hidden' unless @group_labels.empty?) } No group labels - if @group_labels.present? = render partial: 'shared/label', collection: @group_labels, as: :label - .project-labels - %h5{ class: ('hide' if hide) } + .project-labels{ class: ('hidden' if @project_labels.empty?) } + %h5{ class: ('hidden' if hide) } = icon('bookmark') Project Labels %ul.content-list.manage-labels-list.js-project-labels - %p.empty-message{ class: ('hidden' unless @project_labels.empty?) } No project labels - if @project_labels.present? = render partial: 'shared/label', collection: @project_labels, as: :label = paginate @project_labels, theme: 'gitlab' -- cgit v1.2.1 From 32c663ff248f6ad2f2fa10fd2e81d6535fb88fd6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 15:50:11 -0300 Subject: Use policies to handle with global/project label permissions --- app/helpers/labels_helper.rb | 10 ---------- app/policies/group_label_policy.rb | 5 +++++ app/policies/label_policy.rb | 7 +++++++ app/views/shared/_label.html.haml | 4 ++-- 4 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 app/policies/group_label_policy.rb create mode 100644 app/policies/label_policy.rb diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 65fc460c670..c14caa5e387 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -54,16 +54,6 @@ module LabelsHelper end end - def can_admin_label(label) - subject = - case label - when GroupLabel then label.group - else label.project - end - - can?(current_user, :admin_label, subject) - end - def edit_label_path(label) case label when GroupLabel then edit_group_label_path(label.group, label) diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb new file mode 100644 index 00000000000..4d4052c5800 --- /dev/null +++ b/app/policies/group_label_policy.rb @@ -0,0 +1,5 @@ +class GroupLabelPolicy < BasePolicy + def rules + can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.group) + end +end diff --git a/app/policies/label_policy.rb b/app/policies/label_policy.rb new file mode 100644 index 00000000000..1677ad7f1bb --- /dev/null +++ b/app/policies/label_policy.rb @@ -0,0 +1,7 @@ +class LabelPolicy < BasePolicy + def rules + return unless @user + + can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.project) + end +end diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 13795807ab8..c0b912b0584 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -21,7 +21,7 @@ %li.label-subscription{ data: { url: toggle_subscription_label_path(label) } } %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } %span= label_subscription_toggle_button_text(label) - - if can_admin_label(label) + - if can?(current_user, :admin_label, label) %li = link_to 'Edit', edit_label_path(label) %li @@ -40,7 +40,7 @@ = icon('eye', class: 'label-subscribe-button-icon') = icon('spinner spin', class: 'label-subscribe-button-loading') - - if can_admin_label(label) + - if can?(current_user, :admin_label, label) = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do %span.sr-only Edit = icon('pencil-square-o') -- cgit v1.2.1 From e2dd75c0a2d863c8e530e54f3a0a015bdec1e84f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 16:09:31 -0300 Subject: Makes the query to retrieve group labels more simpler --- app/finders/labels_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 85ef9bea08d..b8828bcdd32 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -16,7 +16,7 @@ class LabelsFinder < UnionFinder def label_ids label_ids = [] - label_ids << Label.where(group_id: projects.where.not(group: nil).select(:namespace_id)).select(:id) + label_ids << Label.where(group_id: projects.joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)).select(:id) label_ids << Label.where(project_id: projects.select(:id)).select(:id) end -- cgit v1.2.1 From cfedd42badc6b84457d1de35cb31988777462d5a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 20 Sep 2016 17:07:56 -0300 Subject: Add ProjectLabel model --- app/finders/issuable_finder.rb | 3 +- app/helpers/labels_helper.rb | 8 ++-- app/models/label.rb | 7 --- app/models/project.rb | 6 ++- app/models/project_label.rb | 5 +++ app/policies/label_policy.rb | 7 --- app/policies/project_label_policy.rb | 5 +++ app/views/projects/labels/_form.html.haml | 2 +- app/views/projects/labels/edit.html.haml | 2 +- app/views/projects/labels/new.html.haml | 2 +- ...60920191518_set_project_label_type_on_labels.rb | 17 +++++++ lib/banzai/filter/label_reference_filter.rb | 2 +- lib/gitlab/google_code_import/importer.rb | 2 +- spec/factories/labels.rb | 2 +- spec/models/label_spec.rb | 52 ++++++++++------------ spec/models/project_label_spec.rb | 11 +++++ spec/models/project_spec.rb | 2 +- 17 files changed, 79 insertions(+), 56 deletions(-) create mode 100644 app/models/project_label.rb delete mode 100644 app/policies/label_policy.rb create mode 100644 app/policies/project_label_policy.rb create mode 100644 db/migrate/20160920191518_set_project_label_type_on_labels.rb create mode 100644 spec/models/project_label_spec.rb diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 37151f8d134..6f2adf47c3a 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -128,7 +128,8 @@ class IssuableFinder @labels = Label.where(title: label_names) if projects - @labels = @labels.where(project: projects) + label_ids = LabelsFinder.new(current_user, project_id: projects).execute.select(:id) + @labels = @labels.where(labels: { id: label_ids }) end else @labels = Label.none diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index c14caa5e387..844bd3fd183 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -57,21 +57,21 @@ module LabelsHelper def edit_label_path(label) case label when GroupLabel then edit_group_label_path(label.group, label) - else edit_namespace_project_label_path(label.project.namespace, label.project, label) + when ProjectLabel then edit_namespace_project_label_path(label.project.namespace, label.project, label) end end def destroy_label_path(label) case label when GroupLabel then group_label_path(label.group, label) - else namespace_project_label_path(label.project.namespace, label.project, label) + when ProjectLabel then namespace_project_label_path(label.project.namespace, label.project, label) end end def toggle_subscription_label_path(label) case label when GroupLabel then toggle_subscription_group_label_path(label.group, label) - else toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) + when ProjectLabel then toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) end end @@ -79,7 +79,7 @@ module LabelsHelper title, icon = case label when GroupLabel then ['Group', 'folder-open'] - else ['Project', 'bookmark'] + when ProjectLabel then ['Project', 'bookmark'] end options[:class] ||= '' diff --git a/app/models/label.rb b/app/models/label.rb index be0c20479d5..0a68be7a30f 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -15,15 +15,12 @@ class Label < ActiveRecord::Base default_value_for :color, DEFAULT_COLOR - belongs_to :project - has_many :lists, dependent: :destroy has_many :label_links, dependent: :destroy has_many :issues, through: :label_links, source: :target, source_type: 'Issue' has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest' validates :color, color: true, allow_blank: false - validates :project, presence: true, if: :project_label? # Don't allow ',' for label titles validates :title, presence: true, format: { with: /\A[^,]+\z/ } @@ -137,10 +134,6 @@ class Label < ActiveRecord::Base .count end - def project_label? - type.blank? && !template? - end - def label_format_reference(format = :id) raise StandardError, 'Unknown format' unless [:id, :name].include?(format) diff --git a/app/models/project.rb b/app/models/project.rb index db7301219e5..41125223044 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -107,7 +107,7 @@ class Project < ActiveRecord::Base # Merge requests from source project should be kept when source project was removed has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest has_many :issues, dependent: :destroy - has_many :labels, dependent: :destroy + has_many :labels, dependent: :destroy, class_name: 'ProjectLabel' has_many :services, dependent: :destroy has_many :events, dependent: :destroy has_many :milestones, dependent: :destroy @@ -730,8 +730,10 @@ class Project < ActiveRecord::Base def create_labels Label.templates.each do |label| label = label.dup - label.template = nil + label.template = false label.project_id = self.id + label.type = 'ProjectLabel' + label.save end end diff --git a/app/models/project_label.rb b/app/models/project_label.rb new file mode 100644 index 00000000000..3e41113e340 --- /dev/null +++ b/app/models/project_label.rb @@ -0,0 +1,5 @@ +class ProjectLabel < Label + belongs_to :project + + validates :project, presence: true +end diff --git a/app/policies/label_policy.rb b/app/policies/label_policy.rb deleted file mode 100644 index 1677ad7f1bb..00000000000 --- a/app/policies/label_policy.rb +++ /dev/null @@ -1,7 +0,0 @@ -class LabelPolicy < BasePolicy - def rules - return unless @user - - can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.project) - end -end diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb new file mode 100644 index 00000000000..e7bd58372a6 --- /dev/null +++ b/app/policies/project_label_policy.rb @@ -0,0 +1,5 @@ +class ProjectLabelPolicy < BasePolicy + def rules + can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.project) + end +end diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index 5f7be074f25..28a062c7eb5 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| += form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| = form_errors(@label) .form-group diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index c9ec371c3e1..372abcb8773 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -7,4 +7,4 @@ = icon('bookmark') Edit Project Label %hr - = render 'form' + = render 'form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label) diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index a1e2df6c55d..f170c41bfc4 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -7,4 +7,4 @@ = icon('bookmark') New Project Label %hr - = render 'form' + = render 'form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project) diff --git a/db/migrate/20160920191518_set_project_label_type_on_labels.rb b/db/migrate/20160920191518_set_project_label_type_on_labels.rb new file mode 100644 index 00000000000..af47d0320e2 --- /dev/null +++ b/db/migrate/20160920191518_set_project_label_type_on_labels.rb @@ -0,0 +1,17 @@ +class SetProjectLabelTypeOnLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + update_column_in_batches(:labels, :type, 'ProjectLabel') do |table, query| + query.where(table[:project_id].not_eq(nil)) + end + end + + def down + update_column_in_batches(:labels, :type, nil) do |table, query| + query.where(table[:project_id].not_eq(nil)) + end + end +end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 3a09912f1be..4c4784b0052 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -70,7 +70,7 @@ module Banzai end def object_link_text(object, matches) - if object.project.nil? || object.project == context[:project] + if object.is_a?(GroupLabel) || object.project == context[:project] LabelsHelper.render_colored_label(object) else LabelsHelper.render_colored_cross_project_label(object) diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 62da327931f..ef8c3e35619 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -237,7 +237,7 @@ module Gitlab def create_label(name) color = nice_label_color(name) - Label.create!(project_id: project.id, name: name, color: color) + project.labels.create!(name: name, color: color) end def format_content(raw_content) diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index eb489099854..ec4c56457ea 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :label do + factory :label, class: ProjectLabel do sequence(:title) { |n| "label#{n}" } color "#990000" project diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 894021dc8e6..1f1fe45d5a7 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -1,47 +1,41 @@ require 'spec_helper' describe Label, models: true do - let(:label) { create(:label) } + describe 'modules' do + it { is_expected.to include_module(Referable) } + it { is_expected.to include_module(Subscribable) } + end describe 'associations' do - it { is_expected.to belong_to(:project) } - - it { is_expected.to have_many(:label_links).dependent(:destroy) } it { is_expected.to have_many(:issues).through(:label_links).source(:target) } + it { is_expected.to have_many(:label_links).dependent(:destroy) } it { is_expected.to have_many(:lists).dependent(:destroy) } end - describe 'modules' do - subject { described_class } - - it { is_expected.to include_module(Referable) } - end - describe 'validation' do - it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_uniqueness_of(:title) } it 'validates color code' do - expect(label).not_to allow_value('G-ITLAB').for(:color) - expect(label).not_to allow_value('AABBCC').for(:color) - expect(label).not_to allow_value('#AABBCCEE').for(:color) - expect(label).not_to allow_value('GGHHII').for(:color) - expect(label).not_to allow_value('#').for(:color) - expect(label).not_to allow_value('').for(:color) - - expect(label).to allow_value('#AABBCC').for(:color) - expect(label).to allow_value('#abcdef').for(:color) + is_expected.not_to allow_value('G-ITLAB').for(:color) + is_expected.not_to allow_value('AABBCC').for(:color) + is_expected.not_to allow_value('#AABBCCEE').for(:color) + is_expected.not_to allow_value('GGHHII').for(:color) + is_expected.not_to allow_value('#').for(:color) + is_expected.not_to allow_value('').for(:color) + + is_expected.to allow_value('#AABBCC').for(:color) + is_expected.to allow_value('#abcdef').for(:color) end it 'validates title' do - expect(label).not_to allow_value('G,ITLAB').for(:title) - expect(label).not_to allow_value('').for(:title) - - expect(label).to allow_value('GITLAB').for(:title) - expect(label).to allow_value('gitlab').for(:title) - expect(label).to allow_value('G?ITLAB').for(:title) - expect(label).to allow_value('G&ITLAB').for(:title) - expect(label).to allow_value("customer's request").for(:title) + is_expected.not_to allow_value('G,ITLAB').for(:title) + is_expected.not_to allow_value('').for(:title) + + is_expected.to allow_value('GITLAB').for(:title) + is_expected.to allow_value('gitlab').for(:title) + is_expected.to allow_value('G?ITLAB').for(:title) + is_expected.to allow_value('G&ITLAB').for(:title) + is_expected.to allow_value("customer's request").for(:title) end end @@ -53,6 +47,8 @@ describe Label, models: true do end describe '#to_reference' do + let(:label) { create(:label) } + context 'using id' do it 'returns a String reference to the object' do expect(label.to_reference).to eq "~#{label.id}" diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb new file mode 100644 index 00000000000..93062b9d402 --- /dev/null +++ b/spec/models/project_label_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe ProjectLabel, models: true do + describe 'relationships' do + it { is_expected.to belong_to(:project) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 67dbcc362f6..e6d98e25d0b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -56,7 +56,7 @@ describe Project, models: true do it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } - it { is_expected.to have_many(:labels).dependent(:destroy) } + it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) } it { is_expected.to have_many(:users_star_projects).dependent(:destroy) } it { is_expected.to have_many(:environments).dependent(:destroy) } it { is_expected.to have_many(:deployments).dependent(:destroy) } -- cgit v1.2.1 From e28058c4107ce454a84b3e3b5750f936dace7db1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 21 Sep 2016 17:47:58 -0300 Subject: Validate if project label title does not exist at group level --- app/models/label.rb | 5 +++-- app/models/project_label.rb | 14 ++++++++++++++ config/locales/en.yml | 1 + spec/factories/labels.rb | 6 ++++++ spec/models/label_spec.rb | 2 +- spec/models/project_label_spec.rb | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 59 insertions(+), 3 deletions(-) diff --git a/app/models/label.rb b/app/models/label.rb index 0a68be7a30f..f844a3d3378 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -24,13 +24,14 @@ class Label < ActiveRecord::Base # Don't allow ',' for label titles validates :title, presence: true, format: { with: /\A[^,]+\z/ } - validates :title, uniqueness: true, unless: :template? + validates :title, uniqueness: { scope: [:group_id, :project_id] } before_save :nullify_priority default_scope { order(title: :asc) } - scope :templates, -> { where(template: true) } + scope :templates, -> { where(template: true) } + scope :with_title, ->(title) { where(title: title) } def self.prioritized where.not(priority: nil).reorder(:priority, :title) diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 3e41113e340..1171aa2dbb3 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -2,4 +2,18 @@ class ProjectLabel < Label belongs_to :project validates :project, presence: true + + validate :title_must_not_exist_at_group_level + + delegate :group, to: :project, allow_nil: true + + private + + def title_must_not_exist_at_group_level + return unless group.present? + + if group.labels.with_title(self.title).exists? + errors.add(:title, :label_already_exists_at_group_level, group: group.name) + end + end end diff --git a/config/locales/en.yml b/config/locales/en.yml index cedb5e207bd..12a59be79f0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -5,6 +5,7 @@ en: hello: "Hello world" errors: messages: + label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one." wrong_size: "is the wrong size (should be %{file_size})" size_too_small: "is too small (should be at least %{file_size})" size_too_big: "is too big (should be at most %{file_size})" diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index ec4c56457ea..5c789d72bac 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -4,4 +4,10 @@ FactoryGirl.define do color "#990000" project end + + factory :group_label, class: GroupLabel do + sequence(:title) { |n| "label#{n}" } + color "#990000" + group + end end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 1f1fe45d5a7..ab640e216cf 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -13,7 +13,7 @@ describe Label, models: true do end describe 'validation' do - it { is_expected.to validate_uniqueness_of(:title) } + it { is_expected.to validate_uniqueness_of(:title).scoped_to([:group_id, :project_id]) } it 'validates color code' do is_expected.not_to allow_value('G-ITLAB').for(:color) diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 93062b9d402..355bb2a938c 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -7,5 +7,39 @@ describe ProjectLabel, models: true do describe 'validations' do it { is_expected.to validate_presence_of(:project) } + + context 'validates if title must not exist at group level' do + let(:group) { create(:group, name: 'gitlab-org') } + let(:project) { create(:empty_project, group: group) } + + before do + create(:group_label, group: group, title: 'Bug') + end + + it 'returns error if title already exists at group level' do + label = described_class.new(project: project, title: 'Bug') + + label.valid? + + expect(label.errors[:title]).to include 'already exists at group level for gitlab-org. Please choose another one.' + end + + it 'does not returns error if title does not exist at group level' do + label = described_class.new(project: project, title: 'Security') + + label.valid? + + expect(label.errors[:title]).to be_empty + end + + it 'does not returns error if project does not belong to group' do + another_project = create(:empty_project) + label = described_class.new(project: another_project, title: 'Bug') + + label.valid? + + expect(label.errors[:title]).to be_empty + end + end end end -- cgit v1.2.1 From 8522ef44bf4298a750d352ff17832b3f4fc6756d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 17:10:54 -0300 Subject: Recreates missing group labels when moving project to another group --- app/services/labels/transfer_service.rb | 52 +++++++++++++++++++++++++ app/services/projects/transfer_service.rb | 4 ++ spec/factories/merge_requests.rb | 10 +++++ spec/services/labels/transfer_service_spec.rb | 41 +++++++++++++++++++ spec/services/projects/transfer_service_spec.rb | 10 +++++ 5 files changed, 117 insertions(+) create mode 100644 app/services/labels/transfer_service.rb create mode 100644 spec/services/labels/transfer_service_spec.rb diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb new file mode 100644 index 00000000000..81897c62c0f --- /dev/null +++ b/app/services/labels/transfer_service.rb @@ -0,0 +1,52 @@ +# Labels::TransferService class +# +# User for recreate the missing group labels at project level +# +module Labels + class TransferService + def initialize(current_user, group, project) + @current_user = current_user + @group = group + @project = project + end + + def execute + return unless group.present? + + Label.transaction do + labels_to_transfer = Label.where(id: label_links.select(:label_id).uniq) + + labels_to_transfer.find_each do |label| + new_label_id = find_or_create_label!(label) + + LabelLink.where(label_id: label.id).update_all(label_id: new_label_id) + end + end + end + + private + + attr_reader :current_user, :group, :project + + def label_links + label_link_ids = [] + label_link_ids << LabelLink.where(target: project.issues, label: group.labels).select(:id) + label_link_ids << LabelLink.where(target: project.merge_requests, label: group.labels).select(:id) + + union = Gitlab::SQL::Union.new(label_link_ids) + + LabelLink.where("label_links.id IN (#{union.to_sql})") + end + + def labels + @labels ||= LabelsFinder.new(current_user, project_id: project.id).execute + end + + def find_or_create_label!(label) + new_label = labels.find_by(title: label.title) + new_label ||= project.labels.create!(label.attributes.slice("title", "description", "color")) + + new_label.id + end + end +end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index bc7f8bf433b..28470f59807 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -28,6 +28,7 @@ module Projects Project.transaction do old_path = project.path_with_namespace old_namespace = project.namespace + old_group = project.group new_path = File.join(new_namespace.try(:path) || '', project.path) if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present? @@ -57,6 +58,9 @@ module Projects # Move wiki repo also if present gitlab_shell.mv_repository(project.repository_storage_path, "#{old_path}.wiki", "#{new_path}.wiki") + # Move missing group labels to project + Labels::TransferService.new(current_user, old_group, project).execute + # clear project cached events project.reset_events_cache diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index c6a08d78b78..f780e01253c 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -68,5 +68,15 @@ FactoryGirl.define do factory :closed_merge_request, traits: [:closed] factory :reopened_merge_request, traits: [:reopened] factory :merge_request_with_diffs, traits: [:with_diffs] + + factory :labeled_merge_request do + transient do + labels [] + end + + after(:create) do |merge_request, evaluator| + merge_request.update_attributes(labels: evaluator.labels) + end + end end end diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb new file mode 100644 index 00000000000..a72a05f6c99 --- /dev/null +++ b/spec/services/labels/transfer_service_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Labels::TransferService, services: true do + describe '#execute' do + let(:user) { create(:user) } + let(:group_1) { create(:group) } + let(:group_2) { create(:group) } + let(:project) { create(:project, namespace: group_2) } + + let(:group_label_1) { create(:group_label, group: group_1, name: 'Group Label 1') } + let(:group_label_2) { create(:group_label, group: group_1, name: 'Group Label 2') } + let(:group_label_3) { create(:group_label, group: group_1, name: 'Group Label 3') } + let(:group_label_4) { create(:group_label, group: group_2, name: 'Group Label 4') } + let(:project_label_1) { create(:label, project: project, name: 'Project Label 1') } + + subject(:service) { described_class.new(user, group_1, project) } + + before do + create(:labeled_issue, project: project, labels: [group_label_1]) + create(:labeled_issue, project: project, labels: [group_label_4]) + create(:labeled_issue, project: project, labels: [project_label_1]) + create(:labeled_merge_request, source_project: project, labels: [group_label_1, group_label_2]) + end + + it 'recreates the missing group labels at project level' do + expect { service.execute }.to change(project.labels, :count).by(2) + end + + it 'does not recreate missing group labels that are not applied to issues or merge requests' do + service.execute + + expect(project.labels.where(title: group_label_3.title)).to be_empty + end + + it 'does not recreate missing group labels that already exist in the project group' do + service.execute + + expect(project.labels.where(title: group_label_4.title)).to be_empty + end + end +end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 57c71544dff..1540b90163a 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -71,4 +71,14 @@ describe Projects::TransferService, services: true do it { expect(private_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) } end end + + context 'missing group labels applied to issues or merge requests' do + it 'delegates tranfer to Labels::TransferService' do + group.add_owner(user) + + expect_any_instance_of(Labels::TransferService).to receive(:execute).once.and_call_original + + transfer_project(project, user, group) + end + end end -- cgit v1.2.1 From ae88126d13d05ea040af495d77dcd1a84253d282 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 18:06:46 -0300 Subject: Show labels widget on issuable sidebar if project has only group labels --- app/controllers/concerns/issuable_actions.rb | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 27f1e91d865..be86fa106f8 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -2,7 +2,7 @@ module IssuableActions extend ActiveSupport::Concern included do - before_action :labels, only: [:new, :edit] + before_action :labels, only: [:show, :new, :edit] before_action :authorize_destroy_issuable!, only: :destroy before_action :authorize_admin_issuable!, only: :bulk_update end diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index ba9f0c27661..7363ead09ff 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -107,7 +107,7 @@ = dropdown_content do .js-due-date-calendar - - if issuable.project.labels.any? + - if @labels && @labels.any? - selected_labels = issuable.labels .block.labels .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } } -- cgit v1.2.1 From b654229dcd3e4460ad7305ee7714395f044a72aa Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 19:58:36 -0300 Subject: Fix LabelsHelper#link_to_label to use the subject argument --- app/helpers/labels_helper.rb | 22 ++++++++++++++-------- app/views/shared/_label.html.haml | 8 ++++---- app/views/shared/_label_row.html.haml | 2 +- app/views/shared/_labels_row.html.haml | 2 +- spec/helpers/labels_helper_spec.rb | 27 +++++++++++++-------------- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 844bd3fd183..e26e82c6448 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -31,7 +31,13 @@ module LabelsHelper # # Returns a String def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block) - link = label_filter_path(label, type: type) + subject ||= + case label + when GroupLabel then label.group + when ProjectLabel then label.project + end + + link = label_filter_path(subject, label, type: type) if block_given? link_to link, class: css_class, &block @@ -40,16 +46,16 @@ module LabelsHelper end end - def label_filter_path(label, type: issue) - case label - when GroupLabel + def label_filter_path(subject, label, type: issue) + case subject + when Group send("#{type.to_s.pluralize}_group_path", - label.group, + subject, label_name: [label.name]) - else + when Project send("namespace_project_#{type.to_s.pluralize}_path", - label.project.namespace, - label.project, + subject.namespace, + subject, label_name: [label.name]) end end diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index c0b912b0584..ba8a3efccda 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -12,10 +12,10 @@ .dropdown-menu.dropdown-menu-align-right %ul %li - = link_to_label(label, type: :merge_request) do + = link_to_label(label, subject: @project, type: :merge_request) do = pluralize open_merge_requests_count, 'merge request' %li - = link_to_label(label) do + = link_to_label(label, subject: @project) do = pluralize open_issues_count, 'open issue' - if current_user %li.label-subscription{ data: { url: toggle_subscription_label_path(label) } } @@ -28,9 +28,9 @@ = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} .pull-right.hidden-xs.hidden-sm.hidden-md - = link_to_label(label, type: :merge_request, css_class: 'btn btn-transparent btn-action') do + = link_to_label(label, subject: @project, type: :merge_request, css_class: 'btn btn-transparent btn-action') do = pluralize open_merge_requests_count, 'merge request' - = link_to_label(label, css_class: 'btn btn-transparent btn-action') do + = link_to_label(label, subject: @project, css_class: 'btn btn-transparent btn-action') do = pluralize open_issues_count, 'open issue' - if current_user diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 8a1ebdd7fb6..a623bbc6b11 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -9,7 +9,7 @@ %button.remove-priority.btn.has-tooltip{ title: 'Remove priority', :'data-placement' => 'top' } = icon('star') %span.label-name - = link_to_label(label, tooltip: false) + = link_to_label(label, subject: @project, tooltip: false) - if can?(current_user, :admin_label, @project) = label_type_icon(label, hidden: label.priority.blank?) - if label.description diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml index e324d0e5203..21b37a7c9ae 100644 --- a/app/views/shared/_labels_row.html.haml +++ b/app/views/shared/_labels_row.html.haml @@ -1,5 +1,5 @@ - labels.each do |label| %span.label-row.btn-group{ role: "group", aria: { label: label.name }, style: "color: #{text_color_for_bg(label.color)}" } - = link_to_label(label, css_class: 'btn btn-transparent') + = link_to_label(label, subject: @project, css_class: 'btn btn-transparent') %button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } } = icon("times") diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 501f150cfda..d30daf47543 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -5,27 +5,26 @@ describe LabelsHelper do let(:project) { create(:empty_project) } let(:label) { create(:label, project: project) } - context 'with @project set' do - before do - @project = project - end - - it 'uses the instance variable' do - expect(link_to_label(label)).to match %r{} + context 'without subject' do + it "uses the label's project" do + expect(link_to_label(label)).to match %r{.*} end end - context 'without @project set' do - it "uses the label's project" do - expect(link_to_label(label)).to match %r{.*} + context 'with a project as subject' do + let(:namespace) { build(:namespace, name: 'foo3') } + let(:another_project) { build(:empty_project, namespace: namespace, name: 'bar3') } + + it 'links to project issues page' do + expect(link_to_label(label, subject: another_project)).to match %r{.*} end end - context 'with a project argument' do - let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') } + context 'with a group as subject' do + let(:group) { build(:group, name: 'bar') } - it 'links to merge requests page' do - expect(link_to_label(label, project: another_project)).to match %r{.*} + it 'links to group issues page' do + expect(link_to_label(label, subject: group)).to match %r{.*} end end -- cgit v1.2.1 From 20b6974a5c7866b24969937caadd5cf483c8f8a4 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 23:23:58 -0300 Subject: Fix Issuable#add_labels_by_names to validate if label exists on group --- app/models/concerns/issuable.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index c4b42ad82c7..1647d693a9d 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -235,9 +235,19 @@ module Issuable end def add_labels_by_names(label_names) + label_ids = [] + label_ids << project.group.labels.select(:id) if project.group.present? + label_ids << project.labels.select(:id) + + union = Gitlab::SQL::Union.new(label_ids) + + available_labels = Label.where("labels.id IN (#{union.to_sql})") + label_names.each do |label_name| - label = project.labels.create_with(color: Label::DEFAULT_COLOR). - find_or_create_by(title: label_name.strip) + title = label_name.strip + label = available_labels.find_by(title: title) + label = project.labels.build(title: title, color: Label::DEFAULT_COLOR) if label.nil? + self.labels << label end end -- cgit v1.2.1 From e00c739f975672eaba474824436ec70d979e1fcc Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 26 Sep 2016 23:36:31 -0300 Subject: Add Label attributes: type, and group_id to safe model attributes --- spec/lib/gitlab/import_export/safe_model_attributes.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 8c8be66df9f..26049914bac 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -64,7 +64,9 @@ Label: - id - title - color +- group_id - project_id +- type - created_at - updated_at - template -- cgit v1.2.1 From 1644276bac361c43a56936cbbadef2a15fe646a6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 27 Sep 2016 23:57:41 -0300 Subject: Add tests to LabelsFinder --- spec/finders/labels_finder_spec.rb | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 spec/finders/labels_finder_spec.rb diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb new file mode 100644 index 00000000000..27acc464ea2 --- /dev/null +++ b/spec/finders/labels_finder_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe LabelsFinder do + describe '#execute' do + let(:group_1) { create(:group) } + let(:group_2) { create(:group) } + let(:group_3) { create(:group) } + + let(:project_1) { create(:empty_project, namespace: group_1) } + let(:project_2) { create(:empty_project, namespace: group_2) } + let(:project_3) { create(:empty_project) } + let(:project_4) { create(:empty_project, :public) } + let(:project_5) { create(:empty_project, namespace: group_1) } + + let!(:project_label_1) { create(:label, project: project_1, title: 'Label 1') } + let!(:project_label_2) { create(:label, project: project_2, title: 'Label 2') } + let!(:project_label_4) { create(:label, project: project_4, title: 'Label 4') } + let!(:project_label_5) { create(:label, project: project_5, title: 'Label 5') } + + let!(:group_label_1) { create(:group_label, group: group_1, title: 'Label 1') } + let!(:group_label_2) { create(:group_label, group: group_1, title: 'Group Label 2') } + let!(:group_label_3) { create(:group_label, group: group_2, title: 'Group Label 3') } + + let(:user) { create(:user) } + + before do + create(:label, project: project_3, title: 'Label 3') + create(:group_label, group: group_3, title: 'Group Label 4') + + project_1.team << [user, :developer] + end + + context 'with no filter' do + it 'returns labels from projects the user have access' do + group_2.add_developer(user) + + finder = described_class.new(user) + + expect(finder.execute).to eq [group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4] + end + end + + context 'filtering by group_id' do + it 'returns labels available for any project within the group' do + group_1.add_developer(user) + + finder = described_class.new(user, group_id: group_1.id) + + expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1, project_label_5] + end + end + + context 'filtering by project_id' do + it 'returns labels available for the project' do + finder = described_class.new(user, project_id: project_1.id) + + expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1] + end + end + + context 'filtering by title' do + it 'returns label with that title' do + finder = described_class.new(user, title: 'Group Label 2') + + expect(finder.execute).to eq [group_label_2] + end + end + end +end -- cgit v1.2.1 From e036a72dca49766df3ee455d6ac955c30846f3fb Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Sep 2016 11:12:44 -0300 Subject: Add Lavel#type to methods in lib/gitlab/import_export/import_export.yml --- lib/gitlab/import_export/import_export.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index bb9d1080330..4204a13dd63 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -71,6 +71,8 @@ excluded_attributes: - :awardable_id methods: + labels: + - :type statuses: - :type services: -- cgit v1.2.1 From fd0ba37276f6246c4095c4879bf9e1186f7c5ad8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Sep 2016 11:23:42 -0300 Subject: Update project test file for project import integration test --- .../import_export/test_project_export.tar.gz | Bin 1363770 -> 680875 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz index d04bdea0fe4..50b42bcec13 100644 Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ -- cgit v1.2.1 From 484f19ed1c5c07cbf8ea26fab8b6759961fcf9ca Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 28 Sep 2016 16:17:41 -0300 Subject: Include global labels when moving an issue to another project --- app/services/issues/move_service.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index ab667456db7..a2a5f57d069 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -52,8 +52,12 @@ module Issues end def cloneable_label_ids - @new_project.labels - .where(title: @old_issue.labels.pluck(:title)).pluck(:id) + params = { + project_id: @new_project.id, + title: @old_issue.labels.pluck(:title) + } + + LabelsFinder.new(current_user, params).execute.pluck(:id) end def cloneable_milestone_id -- cgit v1.2.1 From 07709c5576a06179c5365b0d7fe154c5f67ca7e5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 29 Sep 2016 00:21:47 -0300 Subject: Unfold references for group labels when moving issue to another project --- app/helpers/labels_helper.rb | 4 +- app/models/group_label.rb | 17 +++++ app/models/label.rb | 24 ------- app/models/project_label.rb | 24 +++++++ lib/banzai/filter/label_reference_filter.rb | 39 +++++++++- lib/gitlab/gfm/reference_rewriter.rb | 18 ++++- .../banzai/filter/label_reference_filter_spec.rb | 82 ++++++++++++++++++++++ spec/lib/gitlab/gfm/reference_rewriter_spec.rb | 26 ++++++- spec/models/group_label_spec.rb | 28 ++++++++ spec/models/label_spec.rb | 46 ------------ spec/models/project_label_spec.rb | 46 ++++++++++++ 11 files changed, 275 insertions(+), 79 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index e26e82c6448..6d0d33b84fb 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -115,8 +115,8 @@ module LabelsHelper span.html_safe end - def render_colored_cross_project_label(label, tooltip: true) - label_suffix = label.project.name_with_namespace + def render_colored_cross_project_label(label, source_project = nil, tooltip: true) + label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace label_suffix = " in #{escape_once(label_suffix)}" render_colored_label(label, label_suffix, tooltip: tooltip) end diff --git a/app/models/group_label.rb b/app/models/group_label.rb index a854d075820..bfcaf3df27e 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -2,4 +2,21 @@ class GroupLabel < Label belongs_to :group validates :group, presence: true + + ## + # Returns the String necessary to reference this GroupLabel in Markdown + # + # format - Symbol format to use (default: :id, optional: :name) + # + # Examples: + # + # GroupLabel.first.to_reference # => "~1" + # GroupLabel.first.to_reference(format: :name) # => "~\"bug\"" + # + # Returns a String + # + def to_reference(from_project = nil, format: :id) + format_reference = label_format_reference(format) + "#{self.class.reference_prefix}#{format_reference}" + end end diff --git a/app/models/label.rb b/app/models/label.rb index f844a3d3378..7dd2d8790b0 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -73,30 +73,6 @@ class Label < ActiveRecord::Base nil end - ## - # Returns the String necessary to reference this Label in Markdown - # - # format - Symbol format to use (default: :id, optional: :name) - # - # Examples: - # - # Label.first.to_reference # => "~1" - # Label.first.to_reference(format: :name) # => "~\"bug\"" - # Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1" - # - # Returns a String - # - def to_reference(from_project = nil, format: :id) - format_reference = label_format_reference(format) - reference = "#{self.class.reference_prefix}#{format_reference}" - - if cross_project_reference?(from_project) - project.to_reference + reference - else - reference - end - end - def open_issues_count(user = nil, project = nil) issues_count(user, project_id: project.try(:id) || project_id, state: 'opened') end diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 1171aa2dbb3..2fc074dc401 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -7,6 +7,30 @@ class ProjectLabel < Label delegate :group, to: :project, allow_nil: true + ## + # Returns the String necessary to reference this ProjectLabel in Markdown + # + # format - Symbol format to use (default: :id, optional: :name) + # + # Examples: + # + # ProjectLabel.first.to_reference # => "~1" + # ProjectLabel.first.to_reference(format: :name) # => "~\"bug\"" + # ProjectLabel.first.to_reference(project) # => "gitlab-org/gitlab-ce~1" + # + # Returns a String + # + def to_reference(from_project = nil, format: :id) + format_reference = label_format_reference(format) + reference = "#{self.class.reference_prefix}#{format_reference}" + + if cross_project_reference?(from_project) + project.to_reference + reference + else + reference + end + end + private def title_must_not_exist_at_group_level diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 4c4784b0052..649c697b415 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -70,13 +70,46 @@ module Banzai end def object_link_text(object, matches) - if object.is_a?(GroupLabel) || object.project == context[:project] - LabelsHelper.render_colored_label(object) + if same_group?(object) && namespace_match?(matches) + render_same_project_label(object) + elsif same_project?(object) + render_same_project_label(object) else - LabelsHelper.render_colored_cross_project_label(object) + render_cross_project_label(object, matches) end end + def same_group?(object) + object.is_a?(GroupLabel) && object.group == project.group + end + + def namespace_match?(matches) + matches[:project].blank? || matches[:project] == project.path_with_namespace + end + + def same_project?(object) + object.is_a?(ProjectLabel) && object.project == project + end + + def project + context[:project] + end + + def render_same_project_label(object) + LabelsHelper.render_colored_label(object) + end + + def render_cross_project_label(object, matches) + source_project = + if matches[:project] + Project.find_with_namespace(matches[:project]) + else + object.project + end + + LabelsHelper.render_colored_cross_project_label(object, source_project) + end + def unescape_html_entities(text) CGI.unescapeHTML(text.to_s) end diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb index 78d7a4f27cf..d0b8cd90e0e 100644 --- a/lib/gitlab/gfm/reference_rewriter.rb +++ b/lib/gitlab/gfm/reference_rewriter.rb @@ -58,7 +58,7 @@ module Gitlab referable = find_referable(reference) return reference unless referable - cross_reference = referable.to_reference(target_project) + cross_reference = build_cross_reference(referable, target_project) return reference if reference == cross_reference new_text = before + cross_reference + after @@ -72,6 +72,22 @@ module Gitlab extractor.all.first end + def build_cross_reference(referable, target_project) + if referable.respond_to?(:project) + referable.to_reference(target_project) + else + to_reference(referable, target_project) + end + end + + def to_reference(referable, target_project) + if @source_project != target_project + @source_project.to_reference + referable.to_reference + else + referable.to_reference + end + end + def substitution_valid?(substituted) @original_html == markdown(substituted) end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 908ccebbf87..9c09f00ae8a 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -305,6 +305,58 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end + describe 'group label references' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:group_label) { create(:group_label, name: 'gfm references', group: group) } + + context 'without project reference' do + let(:reference) { group_label.to_reference(format: :name) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}", project: project) + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: group_label.name) + expect(doc.text).to eq 'See gfm references' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{group_label.name}

      Google

      \.\))) + end + + it 'ignores invalid label names' do + exp = act = %(Label #{Label.reference_prefix}"#{group_label.name.reverse}") + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'with project reference' do + let(:reference) { project.to_reference + group_label.to_reference(format: :name) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}", project: project) + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: group_label.name) + expect(doc.text).to eq 'See gfm references' + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{group_label.name}
      \.\))) + end + + it 'ignores invalid label names' do + exp = act = %(Label #{project.to_reference}#{Label.reference_prefix}"#{group_label.name.reverse}") + + expect(reference_filter(act).to_html).to eq exp + end + end + end + describe 'cross project label references' do context 'valid project referenced' do let(:another_project) { create(:empty_project, :public) } @@ -339,4 +391,34 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end end end + + describe 'cross group label references' do + context 'valid project referenced' do + let(:group) { create(:group) } + let(:project) { create(:empty_project, :public, namespace: group) } + let(:another_group) { create(:group) } + let(:another_project) { create(:empty_project, :public, namespace: another_group) } + let(:project_name) { another_project.name_with_namespace } + let(:group_label) { create(:group_label, group: another_group, color: '#00ff00') } + let(:reference) { another_project.to_reference + group_label.to_reference } + + let!(:result) { reference_filter("See #{reference}", project: project) } + + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(another_project.namespace, + another_project, + label_name: group_label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')) + .to match /background-color: #00ff00/ + end + + it 'contains cross project content' do + expect(result.css('a').first.text).to eq "#{group_label.name} in #{project_name}" + end + end + end end diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb index 0af249d8690..f045463c1cb 100644 --- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb +++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Gitlab::Gfm::ReferenceRewriter do let(:text) { 'some text' } - let(:old_project) { create(:project) } - let(:new_project) { create(:project) } + let(:old_project) { create(:project, name: 'old') } + let(:new_project) { create(:project, name: 'new') } let(:user) { create(:user) } before { old_project.team << [user, :guest] } @@ -62,7 +62,7 @@ describe Gitlab::Gfm::ReferenceRewriter do it { is_expected.to eq "#{ref}, `#1`, #{ref}, `#1`" } end - context 'description with labels' do + context 'description with project labels' do let!(:label) { create(:label, id: 123, name: 'test', project: old_project) } let(:project_ref) { old_project.to_reference } @@ -76,6 +76,26 @@ describe Gitlab::Gfm::ReferenceRewriter do it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~123} } end end + + context 'description with group labels' do + let(:old_group) { create(:group) } + let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) } + let(:project_ref) { old_project.to_reference } + + before do + old_project.update(namespace: old_group) + end + + context 'label referenced by id' do + let(:text) { '#1 and ~321' } + it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~321} } + end + + context 'label referenced by text' do + let(:text) { '#1 and ~"group label"' } + it { is_expected.to eq %Q{#{project_ref}#1 and #{project_ref}~321} } + end + end end context 'reference contains milestone' do diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index a82d23bcc0b..92b07a3cd44 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -8,4 +8,32 @@ describe GroupLabel, models: true do describe 'validations' do it { is_expected.to validate_presence_of(:group) } end + + describe '#to_reference' do + let(:label) { create(:group_label) } + + context 'using id' do + it 'returns a String reference to the object' do + expect(label.to_reference).to eq "~#{label.id}" + end + end + + context 'using name' do + it 'returns a String reference to the object' do + expect(label.to_reference(format: :name)).to eq %(~"#{label.name}") + end + + it 'uses id when name contains double quote' do + label = create(:label, name: %q{"irony"}) + expect(label.to_reference(format: :name)).to eq "~#{label.id}" + end + end + + context 'using invalid format' do + it 'raises error' do + expect { label.to_reference(format: :invalid) } + .to raise_error StandardError, /Unknown format/ + end + end + end end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index ab640e216cf..c6e1ea19987 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -45,50 +45,4 @@ describe Label, models: true do expect(label.title).to eq('foo & bar?') end end - - describe '#to_reference' do - let(:label) { create(:label) } - - context 'using id' do - it 'returns a String reference to the object' do - expect(label.to_reference).to eq "~#{label.id}" - end - end - - context 'using name' do - it 'returns a String reference to the object' do - expect(label.to_reference(format: :name)).to eq %(~"#{label.name}") - end - - it 'uses id when name contains double quote' do - label = create(:label, name: %q{"irony"}) - expect(label.to_reference(format: :name)).to eq "~#{label.id}" - end - end - - context 'using invalid format' do - it 'raises error' do - expect { label.to_reference(format: :invalid) } - .to raise_error StandardError, /Unknown format/ - end - end - - context 'cross project reference' do - let(:project) { create(:project) } - - context 'using name' do - it 'returns cross reference with label name' do - expect(label.to_reference(project, format: :name)) - .to eq %Q(#{label.project.to_reference}~"#{label.name}") - end - end - - context 'using id' do - it 'returns cross reference with label id' do - expect(label.to_reference(project, format: :id)) - .to eq %Q(#{label.project.to_reference}~#{label.id}) - end - end - end - end end diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 355bb2a938c..7966c52c38d 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -42,4 +42,50 @@ describe ProjectLabel, models: true do end end end + + describe '#to_reference' do + let(:label) { create(:label) } + + context 'using id' do + it 'returns a String reference to the object' do + expect(label.to_reference).to eq "~#{label.id}" + end + end + + context 'using name' do + it 'returns a String reference to the object' do + expect(label.to_reference(format: :name)).to eq %(~"#{label.name}") + end + + it 'uses id when name contains double quote' do + label = create(:label, name: %q{"irony"}) + expect(label.to_reference(format: :name)).to eq "~#{label.id}" + end + end + + context 'using invalid format' do + it 'raises error' do + expect { label.to_reference(format: :invalid) } + .to raise_error StandardError, /Unknown format/ + end + end + + context 'cross project reference' do + let(:project) { create(:project) } + + context 'using name' do + it 'returns cross reference with label name' do + expect(label.to_reference(project, format: :name)) + .to eq %Q(#{label.project.to_reference}~"#{label.name}") + end + end + + context 'using id' do + it 'returns cross reference with label id' do + expect(label.to_reference(project, format: :id)) + .to eq %Q(#{label.project.to_reference}~#{label.id}) + end + end + end + end end -- cgit v1.2.1 From 0bfa39d5bdb9f53bfc319b9351230b3eb405b619 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 29 Sep 2016 01:03:28 -0300 Subject: Remove scopes/types for labels --- app/assets/stylesheets/pages/labels.scss | 10 +---- app/controllers/projects/labels_controller.rb | 19 ++++---- app/helpers/labels_helper.rb | 20 --------- app/views/groups/labels/edit.html.haml | 3 +- app/views/groups/labels/index.html.haml | 8 +--- app/views/groups/labels/new.html.haml | 5 +-- app/views/projects/labels/edit.html.haml | 3 +- app/views/projects/labels/index.html.haml | 52 +++++++++------------- app/views/projects/labels/new.html.haml | 3 +- app/views/shared/_label_row.html.haml | 2 - .../controllers/projects/labels_controller_spec.rb | 38 +++------------- .../projects/labels/update_prioritization_spec.rb | 10 ++--- 12 files changed, 50 insertions(+), 123 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index cbd009ccd07..9bac6d46355 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -140,10 +140,6 @@ color: $gray-light; } - .label-type { - opacity: 0.3; - } - li:hover { .draggable-handler { display: inline-block; @@ -152,11 +148,7 @@ } } -.group-labels + .project-labels { - margin-top: 30px; -} - -.group-labels, .project-labels { +.other-labels { .remove-priority { display: none; } diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 919c6f239cb..3154a4435f6 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -3,7 +3,7 @@ class Projects::LabelsController < Projects::ApplicationController before_action :module_enabled before_action :label, only: [:edit, :update, :destroy] - before_action :labels, only: [:index] + before_action :find_labels, only: [:index, :set_priorities, :remove_priority] before_action :authorize_read_label! before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :generate, :destroy, :remove_priority, @@ -12,9 +12,8 @@ class Projects::LabelsController < Projects::ApplicationController respond_to :js, :html def index - @prioritized_labels = @labels.prioritized - @group_labels = @project.group.labels.unprioritized if @project.group.present? - @project_labels = @project.labels.unprioritized.page(params[:page]) + @prioritized_labels = @available_labels.prioritized + @labels = @available_labels.unprioritized.page(params[:page]) respond_to do |format| format.html @@ -70,7 +69,7 @@ class Projects::LabelsController < Projects::ApplicationController def destroy @label.destroy - @labels = labels + @labels = find_labels respond_to do |format| format.html do @@ -83,7 +82,7 @@ class Projects::LabelsController < Projects::ApplicationController def remove_priority respond_to do |format| - label = labels.find(params[:id]) + label = @available_labels.find(params[:id]) if label.update_attribute(:priority, nil) format.json { render json: label } @@ -96,8 +95,10 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do + label_ids = @available_labels.where(id: params[:label_ids]).pluck(:id) + params[:label_ids].each_with_index do |label_id, index| - next unless labels.where(id: label_id).any? + next unless label_ids.include?(label_id.to_i) Label.where(id: label_id).update_all(priority: index) end @@ -125,8 +126,8 @@ class Projects::LabelsController < Projects::ApplicationController end alias_method :subscribable_resource, :label - def labels - @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + def find_labels + @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute end def authorize_admin_labels! diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 6d0d33b84fb..e8992c114b0 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -81,26 +81,6 @@ module LabelsHelper end end - def label_type_icon(label, options = {}) - title, icon = - case label - when GroupLabel then ['Group', 'folder-open'] - when ProjectLabel then ['Project', 'bookmark'] - end - - options[:class] ||= '' - options[:class] << ' has-tooltip js-label-type' - options[:class] << ' hidden' if options.fetch(:hidden, false) - - content_tag :span, - class: options[:class], - data: { 'placement' => 'top' }, - title: title, - aria: { label: title } do - icon(icon, base: true) - end - end - def render_colored_label(label, label_suffix = '', tooltip: true) label_color = label.color || Label::DEFAULT_COLOR text_color = text_color_for_bg(label_color) diff --git a/app/views/groups/labels/edit.html.haml b/app/views/groups/labels/edit.html.haml index e247393abd5..28471f407ad 100644 --- a/app/views/groups/labels/edit.html.haml +++ b/app/views/groups/labels/edit.html.haml @@ -1,8 +1,7 @@ - page_title 'Edit', @label.name, 'Labels' %h3.page-title - = icon('folder-open') - Edit Group Label + Edit Label %hr = render 'form', url: group_label_path(@group, @label) diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 8e93ea4f625..6c69e3465f4 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -10,13 +10,9 @@ New label .labels - - hide = @group.labels.empty? || (params[:page].present? && params[:page] != '1') - .group-labels - %h5{ class: ('hide' if hide) } - = icon('folder-open') - Group Labels + .other-labels - if @labels.present? - %ul.content-list.manage-labels-list.js-group-labels + %ul.content-list.manage-labels-list.js-other-labels = render partial: 'shared/label', collection: @labels, as: :label = paginate @labels, theme: 'gitlab' - else diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml index 01a50607db4..257ae97de03 100644 --- a/app/views/groups/labels/new.html.haml +++ b/app/views/groups/labels/new.html.haml @@ -1,9 +1,8 @@ -- page_title 'New Group Label' +- page_title 'New Label' - header_title group_title(@group, 'Labels', group_labels_path(@group)) %h3.page-title - = icon('folder-open') - New Group Label + New Label %hr = render 'form', url: group_labels_path diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 372abcb8773..49adb593559 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -4,7 +4,6 @@ %div{ class: container_class } %h3.page-title - = icon('bookmark') - Edit Project Label + Edit Label %hr = render 'form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 99f8e8095ad..c1ec9cabc40 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -14,36 +14,26 @@ New label .labels - - unless @labels.empty? + - if can?(current_user, :admin_label, @project) -# Only show it in the first page - - hide = params[:page].present? && params[:page] != '1' - - if can?(current_user, :admin_label, @project) - .prioritized-labels{ class: ('hidden' if hide) } - %h5 Prioritized Labels - %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } - %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - - if @prioritized_labels.present? - = render partial: 'shared/label', collection: @prioritized_labels, as: :label - - .group-labels{ class: ('hidden' if hide || @project.group.blank? || @group_labels.empty?) } - %h5 - = icon('folder-open') - Group Labels - %ul.content-list.manage-labels-list.js-group-labels - - if @group_labels.present? - = render partial: 'shared/label', collection: @group_labels, as: :label + - hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1') + .prioritized-labels{ class: ('hide' if hide) } + %h5 Prioritized Labels + %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } + %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet + - if @prioritized_labels.present? + = render partial: 'shared/label', collection: @prioritized_labels, as: :label - .project-labels{ class: ('hidden' if @project_labels.empty?) } - %h5{ class: ('hidden' if hide) } - = icon('bookmark') - Project Labels - %ul.content-list.manage-labels-list.js-project-labels - - if @project_labels.present? - = render partial: 'shared/label', collection: @project_labels, as: :label - = paginate @project_labels, theme: 'gitlab' - - else - .nothing-here-block - - if can?(current_user, :admin_label, @project) - Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. - - else - No labels created yet. + .other-labels + - if can?(current_user, :admin_label, @project) + %h5{ class: ('hide' if hide) } Other Labels + %ul.content-list.manage-labels-list.js-other-labels + - if @labels.present? + = render partial: 'shared/label', collection: @labels, as: :label + = paginate @labels, theme: 'gitlab' + - if @labels.blank? + .nothing-here-block + - if can?(current_user, :admin_label, @project) + Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. + - else + No labels created diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index f170c41bfc4..0c177feb43c 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -4,7 +4,6 @@ %div{ class: container_class } %h3.page-title - = icon('bookmark') - New Project Label + New Label %hr = render 'form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project) diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index a623bbc6b11..813ce4f1405 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -10,8 +10,6 @@ = icon('star') %span.label-name = link_to_label(label, subject: @project, tooltip: false) - - if can?(current_user, :admin_label, @project) - = label_type_icon(label, hidden: label.priority.blank?) - if label.description %span.label-description = markdown_field(label, :description) diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 2b39f9cf0d1..29251f49810 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -33,55 +33,29 @@ describe Projects::LabelsController do end it 'is sorted by priority, then label title' do - expect(assigns(:prioritized_labels)).to match_array [group_label_2, label_1, label_3, group_label_1, label_2] + expect(assigns(:prioritized_labels)).to eq [group_label_2, label_1, label_3, group_label_1, label_2] end end - context '@group_labels' do - it 'contains only group labels' do - list_labels - - expect(assigns(:group_labels)).to all(have_attributes(group_id: a_value > 0)) - end - + context '@labels' do it 'contains only unprioritized labels' do list_labels - expect(assigns(:group_labels)).to all(have_attributes(priority: nil)) + expect(assigns(:labels)).to all(have_attributes(priority: nil)) end it 'is sorted by label title' do list_labels - expect(assigns(:group_labels)).to match_array [group_label_3, group_label_4] + expect(assigns(:labels)).to eq [group_label_3, group_label_4, label_4, label_5] end - it 'is nil when project does not belong to a group' do + it 'does not include group labels when project does not belong to a group' do project.update(namespace: create(:namespace)) list_labels - expect(assigns(:group_labels)).to be_nil - end - end - - context '@project_labels' do - before do - list_labels - end - - it 'contains only project labels' do - list_labels - - expect(assigns(:project_labels)).to all(have_attributes(project_id: a_value > 0)) - end - - it 'contains only unprioritized labels' do - expect(assigns(:project_labels)).to all(have_attributes(priority: nil)) - end - - it 'is sorted by label title' do - expect(assigns(:project_labels)).to match_array [label_4, label_5] + expect(assigns(:labels)).not_to include(group_label_3, group_label_4) end end diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 21896f0a787..84a12a38c26 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -22,8 +22,8 @@ feature 'Prioritize labels', feature: true do expect(page).to have_content('No prioritized labels yet') - page.within('.group-labels') do - first('.js-toggle-priority').click + page.within('.other-labels') do + all('.js-toggle-priority')[1].click wait_for_ajax expect(page).not_to have_content('feature') end @@ -47,7 +47,7 @@ feature 'Prioritize labels', feature: true do expect(page).not_to have_content('bug') end - page.within('.group-labels') do + page.within('.other-labels') do expect(page).to have_content('feature') end end @@ -57,7 +57,7 @@ feature 'Prioritize labels', feature: true do expect(page).to have_content('No prioritized labels yet') - page.within('.project-labels') do + page.within('.other-labels') do first('.js-toggle-priority').click wait_for_ajax expect(page).not_to have_content('bug') @@ -82,7 +82,7 @@ feature 'Prioritize labels', feature: true do expect(page).not_to have_content('bug') end - page.within('.project-labels') do + page.within('.other-labels') do expect(page).to have_content('bug') expect(page).to have_content('wontfix') end -- cgit v1.2.1 From 848a146fc3bd34ec94a206f2ed6ef33d539bfce5 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 29 Sep 2016 09:51:12 +0200 Subject: Fix import test --- lib/gitlab/import_export/import_export.yml | 4 +++- lib/gitlab/import_export/relation_factory.rb | 5 +++-- .../import_export/test_project_export.tar.gz | Bin 680875 -> 680856 bytes 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 4204a13dd63..59abca04b35 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -71,7 +71,9 @@ excluded_attributes: - :awardable_id methods: - labels: + project_labels: + - :type + label: - :type statuses: - :type diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 9300f789e1b..5a84bc97226 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -9,7 +9,8 @@ module Gitlab builds: 'Ci::Build', hooks: 'ProjectHook', merge_access_levels: 'ProtectedBranch::MergeAccessLevel', - push_access_levels: 'ProtectedBranch::PushAccessLevel' }.freeze + push_access_levels: 'ProtectedBranch::PushAccessLevel', + labels: :project_labels }.freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze @@ -19,7 +20,7 @@ module Gitlab IMPORTED_OBJECT_MAX_RETRIES = 5.freeze - EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze + EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels].freeze FINDER_ATTRIBUTES = %w[title project_id].freeze diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz index 50b42bcec13..8f683cf89aa 100644 Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ -- cgit v1.2.1 From 77b7bfd463bf57d38cb6aa30f277cd19ffbb6504 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 29 Sep 2016 13:15:18 +0200 Subject: Fix import/export labels to cope with project and group labels. Added relevant specs. --- doc/user/project/settings/import_export.md | 3 ++- lib/gitlab/import_export.rb | 2 +- lib/gitlab/import_export/import_export.yml | 2 +- lib/gitlab/import_export/project_tree_restorer.rb | 7 +++++- lib/gitlab/import_export/relation_factory.rb | 20 +++++++++++++-- spec/lib/gitlab/import_export/project.json | 27 ++++++++++++++++++-- .../import_export/project_tree_restorer_spec.rb | 29 +++++++++++++++++++++- .../import_export/project_tree_saver_spec.rb | 16 +++++++++--- 8 files changed, 94 insertions(+), 12 deletions(-) diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index 65ed9fae4ec..dfc762fe1d3 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -22,7 +22,8 @@ with all their related data and be moved into a new GitLab instance. | GitLab version | Import/Export version | | -------- | -------- | -| 8.12.0 to current | 0.1.4 | +| 8.13.0 to current | 0.1.5 | +| 8.12.0 | 0.1.4 | | 8.10.3 | 0.1.3 | | 8.10.0 | 0.1.2 | | 8.9.5 | 0.1.1 | diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 181e288a014..eb667a85b78 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -3,7 +3,7 @@ module Gitlab extend self # For every version update, the version history in import_export.md has to be kept up to date. - VERSION = '0.1.4' + VERSION = '0.1.5' FILENAME_LIMIT = 50 def export_path(relative_path:) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 59abca04b35..8882f146632 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -71,7 +71,7 @@ excluded_attributes: - :awardable_id methods: - project_labels: + labels: - :type label: - :type diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index 5a109f24f9f..accac5325f8 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -110,7 +110,7 @@ module Gitlab def create_relation(relation, relation_hash_list) relation_array = [relation_hash_list].flatten.map do |relation_hash| Gitlab::ImportExport::RelationFactory.create(relation_sym: relation.to_sym, - relation_hash: relation_hash, + relation_hash: parsed_relation_hash(relation_hash), members_mapper: members_mapper, user: @user, project_id: restored_project.id) @@ -118,6 +118,11 @@ module Gitlab relation_hash_list.is_a?(Array) ? relation_array : relation_array.first end + + def parsed_relation_hash(relation_hash) + group_id = restored_project.group ? restored_project.group.id : nil + relation_hash.merge!('group_id' => group_id, 'project_id' => restored_project.id) + end end end end diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 5a84bc97226..8bc4ab85c18 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -10,7 +10,8 @@ module Gitlab hooks: 'ProjectHook', merge_access_levels: 'ProtectedBranch::MergeAccessLevel', push_access_levels: 'ProtectedBranch::PushAccessLevel', - labels: :project_labels }.freeze + labels: :project_labels, + label: :project_label }.freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze @@ -20,7 +21,7 @@ module Gitlab IMPORTED_OBJECT_MAX_RETRIES = 5.freeze - EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels].freeze + EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels project_label group_label].freeze FINDER_ATTRIBUTES = %w[title project_id].freeze @@ -57,6 +58,8 @@ module Gitlab update_user_references update_project_references + + handle_group_label if group_label? reset_ci_tokens if @relation_name == 'Ci::Trigger' @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data'] set_st_diffs if @relation_name == :merge_request_diff @@ -124,6 +127,19 @@ module Gitlab @relation_hash['target_project_id'] && @relation_hash['target_project_id'] == @relation_hash['source_project_id'] end + def group_label? + @relation_hash['type'] == 'GroupLabel' + end + + def handle_group_label + # If there's no group, move the label to a project label + if @relation_hash['group_id'] + @relation_name = :group_label + else + @relation_hash['type'] = 'ProjectLabel' + end + end + def reset_ci_tokens return unless Gitlab::ImportExport.reset_tokens? diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 98323fe6be4..bf9dc279f7d 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -64,7 +64,29 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null + "priority": null, + "type": "ProjectLabel" + } + }, + { + "id": 3, + "label_id": 3, + "target_id": 40, + "target_type": "Issue", + "created_at": "2016-07-22T08:57:02.841Z", + "updated_at": "2016-07-22T08:57:02.841Z", + "label": { + "id": 3, + "title": "test3", + "color": "#428bca", + "group_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "priority": null, + "project_id": null, + "type": "GroupLabel" } } ], @@ -536,7 +558,8 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null + "priority": null, + "type": "ProjectLabel" } } ], diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 7582a732cdf..365d08940ba 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do it 'has the same label associated to two issues' do restored_project_json - expect(Label.first.issues.count).to eq(2) + expect(ProjectLabel.find_by_title('test2').issues.count).to eq(2) end it 'has milestones associated to two separate issues' do @@ -107,6 +107,33 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(Label.first.label_links.first.target).not_to be_nil end + it 'has project labels' do + restored_project_json + + expect(ProjectLabel.count).to eq(2) + end + + it 'has no group labels' do + restored_project_json + + expect(GroupLabel.count).to eq(0) + end + + context 'with group' do + let!(:project) { create(:empty_project, + name: 'project', + path: 'project', + builds_access_level: ProjectFeature::DISABLED, + issues_access_level: ProjectFeature::DISABLED, + group: create(:group)) } + + it 'has group labels' do + restored_project_json + + expect(GroupLabel.count).to eq(1) + end + end + it 'has a project feature' do restored_project_json diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index cf8f2200c57..9a8ba61559b 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -111,6 +111,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty end + it 'has project and group labels' do + label_types = saved_project_json['issues'].first['label_links'].map { |link| link['label']['type']} + + expect(label_types).to match(['ProjectLabel', 'GroupLabel']) + end + it 'saves the correct service type' do expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService') end @@ -135,15 +141,19 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do issue = create(:issue, assignee: user) snippet = create(:project_snippet) release = create(:release) + group = create(:group) project = create(:project, :public, issues: [issue], snippets: [snippet], - releases: [release] + releases: [release], + group: group ) - label = create(:label, project: project) - create(:label_link, label: label, target: issue) + project_label = create(:label, project: project) + group_label = create(:group_label, group: group) + create(:label_link, label: project_label, target: issue) + create(:label_link, label: group_label, target: issue) milestone = create(:milestone, project: project) merge_request = create(:merge_request, source_project: project, milestone: milestone) commit_status = create(:commit_status, project: project) -- cgit v1.2.1 From 723e576782aefa339a4db8916908c7ebe5a92f48 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 29 Sep 2016 15:06:10 +0200 Subject: fix rubocop warning --- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 365d08940ba..6312b990a66 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -120,12 +120,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do end context 'with group' do - let!(:project) { create(:empty_project, + let!(:project) do + create(:empty_project, name: 'project', path: 'project', builds_access_level: ProjectFeature::DISABLED, issues_access_level: ProjectFeature::DISABLED, - group: create(:group)) } + group: create(:group)) + end it 'has group labels' do restored_project_json -- cgit v1.2.1 From 3db2261005c438faad8bf4a339d46eb7798f05b5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 29 Sep 2016 15:50:14 -0300 Subject: Reuse LabelsFinder on Banzai::Filter::LabelReferenceFilter --- app/finders/issuable_finder.rb | 5 ++-- app/finders/labels_finder.rb | 38 +++++++++++++++++++++++++---- app/models/project.rb | 4 +++ lib/banzai/filter/label_reference_filter.rb | 12 ++++----- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 6f2adf47c3a..41ea8f801c1 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -128,8 +128,7 @@ class IssuableFinder @labels = Label.where(title: label_names) if projects - label_ids = LabelsFinder.new(current_user, project_id: projects).execute.select(:id) - @labels = @labels.where(labels: { id: label_ids }) + @labels = LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute end else @labels = Label.none @@ -277,7 +276,7 @@ class IssuableFinder items = items.with_label(label_names, params[:sort]) if projects - label_ids = LabelsFinder.new(current_user, project_id: projects).execute.select(:id) + label_ids = LabelsFinder.new(current_user, project_ids: projects).execute.select(:id) items = items.where(labels: { id: label_ids }) end end diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index b8828bcdd32..28110be7097 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -16,8 +16,16 @@ class LabelsFinder < UnionFinder def label_ids label_ids = [] - label_ids << Label.where(group_id: projects.joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)).select(:id) - label_ids << Label.where(project_id: projects.select(:id)).select(:id) + + if project + label_ids << project.group.labels if project.group.present? + label_ids << project.labels + else + label_ids << Label.where(group_id: projects.group_ids) + label_ids << Label.where(project_id: projects.select(:id)) + end + + label_ids end def sort(items) @@ -37,18 +45,38 @@ class LabelsFinder < UnionFinder params[:project_id].presence end + def project_ids + params[:project_ids].presence + end + def title params[:title].presence end + def project + return @project if defined?(@project) + + if project_id + @project = available_projects.find(project_id) rescue nil + else + @project = nil + end + + @project + end + def projects return @projects if defined?(@projects) - @projects = ProjectsFinder.new.execute(current_user) - @projects = @projects.joins(:namespace).where(namespaces: { id: group_id, type: 'Group' }) if group_id - @projects = @projects.where(id: project_id) if project_id + @projects = available_projects + @projects = @projects.in_namespace(group_id) if group_id + @projects = @projects.where(id: project_ids) if project_ids @projects = @projects.reorder(nil) @projects end + + def available_projects + @available_projects ||= ProjectsFinder.new.execute(current_user) + end end diff --git a/app/models/project.rb b/app/models/project.rb index 41125223044..a04817987b4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -388,6 +388,10 @@ class Project < ActiveRecord::Base Project.count end end + + def group_ids + joins(:namespace).where(namespaces: { type: 'Group' }).pluck(:namespace_id) + end end def lfs_enabled? diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 649c697b415..2ce33aa1d15 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -39,13 +39,7 @@ module Banzai end def find_labels(project) - label_ids = [] - label_ids << project.group.labels.select(:id) if project.group.present? - label_ids << project.labels.select(:id) - - union = Gitlab::SQL::Union.new(label_ids) - - object_class.where("labels.id IN (#{union.to_sql})") + LabelsFinder.new(user, project_id: project.id).execute end # Parameters to pass to `Label.find_by` based on the given arguments @@ -91,6 +85,10 @@ module Banzai object.is_a?(ProjectLabel) && object.project == project end + def user + context[:current_user] || context[:author] + end + def project context[:project] end -- cgit v1.2.1 From 7e11ca86fdb23c967c25b19735770f99f936b32c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 3 Oct 2016 18:41:46 -0300 Subject: Reuse LabelsFinder on Issueable#add_labels_by_names --- app/models/concerns/issuable.rb | 12 +++--------- lib/api/merge_requests.rb | 4 ++-- lib/gitlab/fogbugz_import/importer.rb | 2 +- lib/gitlab/google_code_import/importer.rb | 2 +- spec/lib/gitlab/google_code_import/importer_spec.rb | 7 ++++--- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 1647d693a9d..fee68d9cc8f 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -234,19 +234,13 @@ module Issuable labels.delete_all end - def add_labels_by_names(label_names) - label_ids = [] - label_ids << project.group.labels.select(:id) if project.group.present? - label_ids << project.labels.select(:id) - - union = Gitlab::SQL::Union.new(label_ids) - - available_labels = Label.where("labels.id IN (#{union.to_sql})") + def add_labels_by_names(label_names, current_user) + available_labels = LabelsFinder.new(current_user, project_id: project.id).execute label_names.each do |label_name| title = label_name.strip label = available_labels.find_by(title: title) - label = project.labels.build(title: title, color: Label::DEFAULT_COLOR) if label.nil? + label ||= project.labels.build(title: title, color: Label::DEFAULT_COLOR) self.labels << label end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 2b685621da9..67fdd0be927 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -91,7 +91,7 @@ module API if merge_request.valid? # Find or create labels and attach to issue if params[:labels].present? - merge_request.add_labels_by_names(params[:labels].split(",")) + merge_request.add_labels_by_names(params[:labels].split(","), current_user) end present merge_request, with: Entities::MergeRequest, current_user: current_user @@ -201,7 +201,7 @@ module API # Find or create labels and attach to issue unless params[:labels].nil? merge_request.remove_labels - merge_request.add_labels_by_names(params[:labels].split(",")) + merge_request.add_labels_by_names(params[:labels].split(","), current_user) end present merge_request, with: Entities::MergeRequest, current_user: current_user diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index 501d5a95547..1d6f97b99c7 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -129,7 +129,7 @@ module Gitlab assignee_id: assignee_id, state: bug['fOpen'] == 'true' ? 'opened' : 'closed' ) - issue.add_labels_by_names(labels) + issue.add_labels_by_names(labels, project.creator) if issue.iid != bug['ixBug'] issue.update_attribute(:iid, bug['ixBug']) diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index ef8c3e35619..8d757da2264 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -100,7 +100,7 @@ module Gitlab state: raw_issue["state"] == "closed" ? "closed" : "opened" ) - issue.add_labels_by_names(labels) + issue.add_labels_by_names(labels, project.creator) if issue.iid != raw_issue["id"] issue.update_attribute(:iid, raw_issue["id"]) diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb index 54f85f8cffc..097861fd34d 100644 --- a/spec/lib/gitlab/google_code_import/importer_spec.rb +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -15,6 +15,7 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do subject { described_class.new(project) } before do + project.team << [project.creator, :master] project.create_import_data(data: import_data) end @@ -31,9 +32,9 @@ describe Gitlab::GoogleCodeImport::Importer, lib: true do subject.execute %w( - Type-Defect Type-Enhancement Type-Task Type-Review Type-Other Milestone-0.12 Priority-Critical - Priority-High Priority-Medium Priority-Low OpSys-All OpSys-Windows OpSys-Linux OpSys-OSX Security - Performance Usability Maintainability Component-Panel Component-Taskbar Component-Battery + Type-Defect Type-Enhancement Type-Task Type-Review Type-Other Milestone-0.12 Priority-Critical + Priority-High Priority-Medium Priority-Low OpSys-All OpSys-Windows OpSys-Linux OpSys-OSX Security + Performance Usability Maintainability Component-Panel Component-Taskbar Component-Battery Component-Systray Component-Clock Component-Launcher Component-Tint2conf Component-Docs Component-New ).each do |label| label.sub!("-", ": ") -- cgit v1.2.1 From 9629bb962cd3666ac58cfbaba9d79df011221f41 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 3 Oct 2016 18:43:39 -0300 Subject: Add column type to labels and do the batch update in the same migration --- db/migrate/20160919144305_add_type_to_labels.rb | 7 ++++++- .../20160920191518_set_project_label_type_on_labels.rb | 17 ----------------- 2 files changed, 6 insertions(+), 18 deletions(-) delete mode 100644 db/migrate/20160920191518_set_project_label_type_on_labels.rb diff --git a/db/migrate/20160919144305_add_type_to_labels.rb b/db/migrate/20160919144305_add_type_to_labels.rb index 43aac7846d3..66172bda6ff 100644 --- a/db/migrate/20160919144305_add_type_to_labels.rb +++ b/db/migrate/20160919144305_add_type_to_labels.rb @@ -1,9 +1,14 @@ class AddTypeToLabels < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers - DOWNTIME = false + DOWNTIME = true + DOWNTIME_REASON = 'Labels will not work as expected until this migration is complete.' def change add_column :labels, :type, :string + + update_column_in_batches(:labels, :type, 'ProjectLabel') do |table, query| + query.where(table[:project_id].not_eq(nil)) + end end end diff --git a/db/migrate/20160920191518_set_project_label_type_on_labels.rb b/db/migrate/20160920191518_set_project_label_type_on_labels.rb deleted file mode 100644 index af47d0320e2..00000000000 --- a/db/migrate/20160920191518_set_project_label_type_on_labels.rb +++ /dev/null @@ -1,17 +0,0 @@ -class SetProjectLabelTypeOnLabels < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - def up - update_column_in_batches(:labels, :type, 'ProjectLabel') do |table, query| - query.where(table[:project_id].not_eq(nil)) - end - end - - def down - update_column_in_batches(:labels, :type, nil) do |table, query| - query.where(table[:project_id].not_eq(nil)) - end - end -end -- cgit v1.2.1 From 11d786e7da9f767a877bbdd4fd482c0d0c4cd747 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 3 Oct 2016 18:59:04 -0300 Subject: Use try instead of ternary operator on Gitlab::ImportExport::ProjectTreeRestorer --- lib/gitlab/import_export/project_tree_restorer.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index accac5325f8..7cdba880a93 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -120,8 +120,7 @@ module Gitlab end def parsed_relation_hash(relation_hash) - group_id = restored_project.group ? restored_project.group.id : nil - relation_hash.merge!('group_id' => group_id, 'project_id' => restored_project.id) + relation_hash.merge!('group_id' => restored_project.group.try(:id), 'project_id' => restored_project.id) end end end -- cgit v1.2.1 From 1fd84700874ed29a7852b0f090dbaffd86cb8d00 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 22:30:18 -0300 Subject: Hide prioritized labels only when no labels are available to project --- app/views/projects/labels/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index c1ec9cabc40..f135bf6f6b4 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -16,7 +16,7 @@ .labels - if can?(current_user, :admin_label, @project) -# Only show it in the first page - - hide = @project.labels.empty? || (params[:page].present? && params[:page] != '1') + - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') .prioritized-labels{ class: ('hide' if hide) } %h5 Prioritized Labels %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } -- cgit v1.2.1 From 00e3c2e00f1c81aa2f7a76e4d93c8a1fb2074d6e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 23:37:07 -0300 Subject: Fix replace label references with links for group labels --- lib/banzai/filter/label_reference_filter.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 2ce33aa1d15..21085ae6d49 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -39,7 +39,13 @@ module Banzai end def find_labels(project) - LabelsFinder.new(user, project_id: project.id).execute + label_ids = [] + label_ids << project.group.labels.select(:id) if project.group.present? + label_ids << project.labels.select(:id) + + union = Gitlab::SQL::Union.new(label_ids) + + Label.where("labels.id IN (#{union.to_sql})") end # Parameters to pass to `Label.find_by` based on the given arguments -- cgit v1.2.1 From f98e97fe78dce11a1f88c3961be402d179e63927 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 00:04:15 -0300 Subject: Reduce duplication between the project and group label forms --- app/controllers/groups/labels_controller.rb | 1 + app/views/groups/labels/_form.html.haml | 33 ----------------------------- app/views/groups/labels/edit.html.haml | 2 +- app/views/groups/labels/new.html.haml | 2 +- app/views/projects/labels/_form.html.haml | 33 ----------------------------- app/views/projects/labels/edit.html.haml | 2 +- app/views/projects/labels/new.html.haml | 2 +- app/views/shared/labels/_form.html.haml | 33 +++++++++++++++++++++++++++++ 8 files changed, 38 insertions(+), 70 deletions(-) delete mode 100644 app/views/groups/labels/_form.html.haml delete mode 100644 app/views/projects/labels/_form.html.haml create mode 100644 app/views/shared/labels/_form.html.haml diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 0ebdee55c79..483a5aedf12 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -21,6 +21,7 @@ class Groups::LabelsController < Groups::ApplicationController def new @label = @group.labels.new + @previous_labels_path = previous_labels_path end def create diff --git a/app/views/groups/labels/_form.html.haml b/app/views/groups/labels/_form.html.haml deleted file mode 100644 index a0b44b0dcfb..00000000000 --- a/app/views/groups/labels/_form.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -= form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| - = form_errors(@label) - - .form-group - = f.label :title, class: 'control-label' - .col-sm-10 - = f.text_field :title, class: "form-control", required: true, autofocus: true - .form-group - = f.label :description, class: 'control-label' - .col-sm-10 - = f.text_field :description, class: "form-control js-quick-submit" - .form-group - = f.label :color, "Background color", class: 'control-label' - .col-sm-10 - .input-group - .input-group-addon.label-color-preview   - = f.color_field :color, class: "form-control" - .help-block - Choose any color. - %br - Or you can choose one of suggested colors below - - .suggest-colors - - suggested_colors.each do |color| - = link_to '#', style: "background-color: #{color}", data: { color: color } do -   - - .form-actions - - if @label.persisted? - = f.submit 'Save changes', class: 'btn btn-save js-save-button' - - else - = f.submit 'Create Label', class: 'btn btn-create js-save-button' - = link_to 'Cancel', @previous_labels_path, class: 'btn btn-cancel' diff --git a/app/views/groups/labels/edit.html.haml b/app/views/groups/labels/edit.html.haml index 28471f407ad..836981fc6fd 100644 --- a/app/views/groups/labels/edit.html.haml +++ b/app/views/groups/labels/edit.html.haml @@ -4,4 +4,4 @@ Edit Label %hr -= render 'form', url: group_label_path(@group, @label) += render 'shared/labels/form', url: group_label_path(@group, @label), back_path: @previous_labels_path diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml index 257ae97de03..2be87460b1d 100644 --- a/app/views/groups/labels/new.html.haml +++ b/app/views/groups/labels/new.html.haml @@ -5,4 +5,4 @@ New Label %hr -= render 'form', url: group_labels_path += render 'shared/labels/form', url: group_labels_path, back_path: @previous_labels_path diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml deleted file mode 100644 index 28a062c7eb5..00000000000 --- a/app/views/projects/labels/_form.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -= form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| - = form_errors(@label) - - .form-group - = f.label :title, class: 'control-label' - .col-sm-10 - = f.text_field :title, class: "form-control", required: true, autofocus: true - .form-group - = f.label :description, class: 'control-label' - .col-sm-10 - = f.text_field :description, class: "form-control js-quick-submit" - .form-group - = f.label :color, "Background color", class: 'control-label' - .col-sm-10 - .input-group - .input-group-addon.label-color-preview   - = f.text_field :color, class: "form-control" - .help-block - Choose any color. - %br - Or you can choose one of suggested colors below - - .suggest-colors - - suggested_colors.each do |color| - = link_to '#', style: "background-color: #{color}", data: { color: color } do -   - - .form-actions - - if @label.persisted? - = f.submit 'Save changes', class: 'btn btn-save js-save-button' - - else - = f.submit 'Create Label', class: 'btn btn-create js-save-button' - = link_to 'Cancel', namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 49adb593559..a80a07b52e6 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -6,4 +6,4 @@ %h3.page-title Edit Label %hr - = render 'form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label) + = render 'shared/labels/form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label), back_path: namespace_project_labels_path(@project.namespace, @project) diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index 0c177feb43c..f0d9be744d1 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -6,4 +6,4 @@ %h3.page-title New Label %hr - = render 'form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project) + = render 'shared/labels/form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project), back_path: namespace_project_labels_path(@project.namespace, @project) diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml new file mode 100644 index 00000000000..647e05e5ff7 --- /dev/null +++ b/app/views/shared/labels/_form.html.haml @@ -0,0 +1,33 @@ += form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| + = form_errors(@label) + + .form-group + = f.label :title, class: 'control-label' + .col-sm-10 + = f.text_field :title, class: "form-control", required: true, autofocus: true + .form-group + = f.label :description, class: 'control-label' + .col-sm-10 + = f.text_field :description, class: "form-control js-quick-submit" + .form-group + = f.label :color, "Background color", class: 'control-label' + .col-sm-10 + .input-group + .input-group-addon.label-color-preview   + = f.text_field :color, class: "form-control" + .help-block + Choose any color. + %br + Or you can choose one of suggested colors below + + .suggest-colors + - suggested_colors.each do |color| + = link_to '#', style: "background-color: #{color}", data: { color: color } do +   + + .form-actions + - if @label.persisted? + = f.submit 'Save changes', class: 'btn btn-save js-save-button' + - else + = f.submit 'Create Label', class: 'btn btn-create js-save-button' + = link_to 'Cancel', back_path, class: 'btn btn-cancel' -- cgit v1.2.1 From bdf365e64f35e76ba6d9a372111ce502db11827e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 13:57:52 -0300 Subject: Use delegate! on group and project labels policies --- app/policies/group_label_policy.rb | 2 +- app/policies/project_label_policy.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/policies/group_label_policy.rb b/app/policies/group_label_policy.rb index 4d4052c5800..7b34aa182eb 100644 --- a/app/policies/group_label_policy.rb +++ b/app/policies/group_label_policy.rb @@ -1,5 +1,5 @@ class GroupLabelPolicy < BasePolicy def rules - can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.group) + delegate! @subject.group end end diff --git a/app/policies/project_label_policy.rb b/app/policies/project_label_policy.rb index e7bd58372a6..b12b4c5166b 100644 --- a/app/policies/project_label_policy.rb +++ b/app/policies/project_label_policy.rb @@ -1,5 +1,5 @@ class ProjectLabelPolicy < BasePolicy def rules - can! :admin_label if Ability.allowed?(@user, :admin_label, @subject.project) + delegate! @subject.project end end -- cgit v1.2.1 From 2deef25eb0c87db2fddf8b25c95891650d9c43a7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:13:26 -0300 Subject: Use Label.attributes instead of Label.dup when creating label templates --- app/models/project.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index a04817987b4..7ab624eafdf 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -733,12 +733,7 @@ class Project < ActiveRecord::Base def create_labels Label.templates.each do |label| - label = label.dup - label.template = false - label.project_id = self.id - label.type = 'ProjectLabel' - - label.save + self.labels.create!(label.attributes.symbolize_keys.except(:id, :template)) end end -- cgit v1.2.1 From 73bfc15cd4fc681015e84f7db8507e38e4ca8b59 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:18:21 -0300 Subject: Always use symbols for type on LabelsHelper#link_to_label --- app/helpers/labels_helper.rb | 2 +- app/views/projects/merge_requests/_merge_request.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index e8992c114b0..af8741f5e06 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -46,7 +46,7 @@ module LabelsHelper end end - def label_filter_path(subject, label, type: issue) + def label_filter_path(subject, label, type: :issue) case subject when Group send("#{type.to_s.pluralize}_group_path", diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index ad62bf50b57..12408068834 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -62,7 +62,7 @@ - if merge_request.labels.any?   - merge_request.labels.each do |label| - = link_to_label(label, subject: merge_request.project, type: 'merge_request') + = link_to_label(label, subject: merge_request.project, type: :merge_request) - if merge_request.tasks?   %span.task-status -- cgit v1.2.1 From 933ebb8f9b289cc077e4d16fd62e1e7b04bc10de Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:35:50 -0300 Subject: Use present? instead of presence on Projects::MergeRequestsController --- app/controllers/projects/merge_requests_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9171b47cda1..0f593d1a936 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -40,7 +40,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_requests = @merge_requests.page(params[:page]) @merge_requests = @merge_requests.preload(:target_project) - @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute if params[:label_name].presence + if params[:label_name].present? + labels_params = { project_id: @project.id, title: params[:label_name] } + @labels = LabelsFinder.new(current_user, labels_params).execute + end respond_to do |format| format.html -- cgit v1.2.1 From 504682db9e2dd99fe827940ac18d5ea8030ae49c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:39:32 -0300 Subject: Limit what label fields we expose on Dashboard::LabelsController#index --- app/controllers/dashboard/labels_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index 05f7bc37952..d5031da867a 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -1,7 +1,9 @@ class Dashboard::LabelsController < Dashboard::ApplicationController def index + labels = LabelsFinder.new(current_user).execute + respond_to do |format| - format.json { render json: LabelsFinder.new(current_user).execute } + format.json { render json: labels.as_json(only: [:id, :title, :color]) } end end end -- cgit v1.2.1 From 36fee24c80afa1e1c5f55cd5f5e5f45a60531d8e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:46:07 -0300 Subject: Limit what label fields we expose on Groups::LabelsController#index --- app/controllers/groups/labels_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 483a5aedf12..0a3dee5ce39 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -14,7 +14,8 @@ class Groups::LabelsController < Groups::ApplicationController end format.json do - render json: LabelsFinder.new(current_user, group_id: @group.id).execute + available_labels = LabelsFinder.new(current_user, group_id: @group.id).execute + render json: available_labels.as_json(only: [:id, :title, :color]) end end end -- cgit v1.2.1 From bde992a83a9e0655dd9c2f59d7314c14b714bd31 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 14:47:02 -0300 Subject: Limit what label fields we expose on Projects::LabelsController#index --- app/controllers/projects/labels_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 3154a4435f6..87c9101551b 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -18,7 +18,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @labels + render json: @labels.as_json(only: [:id, :title, :color]) end end end -- cgit v1.2.1 From f74d5f2750c638f30020ba727e2947b7207bf0e2 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 15:50:17 -0300 Subject: Fix shared labels filter --- app/views/shared/issuable/_filter.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 8c2036a1cde..f8018fc3de0 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -27,7 +27,7 @@ = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } + = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title, :type).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } .filter-item.inline.reset-filters %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters -- cgit v1.2.1 From 1e6d136af31b0b91a03881a0c20b9bfa448201ee Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 16:30:40 -0300 Subject: Keep cross project reference logic in GroupLabel#to_reference --- app/models/group_label.rb | 16 ++++++++++++++-- lib/gitlab/gfm/reference_rewriter.rb | 10 +--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/models/group_label.rb b/app/models/group_label.rb index bfcaf3df27e..c7efa29a5f6 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -15,8 +15,20 @@ class GroupLabel < Label # # Returns a String # - def to_reference(from_project = nil, format: :id) + def to_reference(source_project = nil, target_project = nil, format: :id) format_reference = label_format_reference(format) - "#{self.class.reference_prefix}#{format_reference}" + reference = "#{self.class.reference_prefix}#{format_reference}" + + if cross_project_reference?(source_project, target_project) + source_project.to_reference + reference + else + reference + end + end + + private + + def cross_project_reference?(source_project, target_project) + source_project && target_project && source_project != target_project end end diff --git a/lib/gitlab/gfm/reference_rewriter.rb b/lib/gitlab/gfm/reference_rewriter.rb index d0b8cd90e0e..a7c596dced0 100644 --- a/lib/gitlab/gfm/reference_rewriter.rb +++ b/lib/gitlab/gfm/reference_rewriter.rb @@ -76,15 +76,7 @@ module Gitlab if referable.respond_to?(:project) referable.to_reference(target_project) else - to_reference(referable, target_project) - end - end - - def to_reference(referable, target_project) - if @source_project != target_project - @source_project.to_reference + referable.to_reference - else - referable.to_reference + referable.to_reference(@source_project, target_project) end end -- cgit v1.2.1 From 247859c82915a0ee88944c1fcda3f6faf49e54c0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 16:33:55 -0300 Subject: Render all available labels to project on project labels dropdown --- app/controllers/projects/labels_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 87c9101551b..f4ad503c9a6 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -18,7 +18,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @labels.as_json(only: [:id, :title, :color]) + render json: @available_labels.as_json(only: [:id, :title, :color]) end end end -- cgit v1.2.1 From 0e8dd599134f17e58cf533ab21cf3c4a5b50c353 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 11 Oct 2016 16:51:11 -0300 Subject: Move common logic to reference group/project label to Label#to_reference --- app/models/group_label.rb | 27 +-------------------------- app/models/label.rb | 28 ++++++++++++++++++++++++++++ app/models/project_label.rb | 24 ++---------------------- 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/app/models/group_label.rb b/app/models/group_label.rb index c7efa29a5f6..a1d8d087726 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -3,32 +3,7 @@ class GroupLabel < Label validates :group, presence: true - ## - # Returns the String necessary to reference this GroupLabel in Markdown - # - # format - Symbol format to use (default: :id, optional: :name) - # - # Examples: - # - # GroupLabel.first.to_reference # => "~1" - # GroupLabel.first.to_reference(format: :name) # => "~\"bug\"" - # - # Returns a String - # def to_reference(source_project = nil, target_project = nil, format: :id) - format_reference = label_format_reference(format) - reference = "#{self.class.reference_prefix}#{format_reference}" - - if cross_project_reference?(source_project, target_project) - source_project.to_reference + reference - else - reference - end - end - - private - - def cross_project_reference?(source_project, target_project) - source_project && target_project && source_project != target_project + super(source_project, target_project, format: format) end end diff --git a/app/models/label.rb b/app/models/label.rb index 7dd2d8790b0..444f45fa09e 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -97,8 +97,36 @@ class Label < ActiveRecord::Base write_attribute(:title, sanitize_title(value)) if value.present? end + ## + # Returns the String necessary to reference this Label in Markdown + # + # format - Symbol format to use (default: :id, optional: :name) + # + # Examples: + # + # Label.first.to_reference # => "~1" + # Label.first.to_reference(format: :name) # => "~\"bug\"" + # Label.first.to_reference(project1, project2) # => "gitlab-org/gitlab-ce~1" + # + # Returns a String + # + def to_reference(source_project = nil, target_project = nil, format: :id) + format_reference = label_format_reference(format) + reference = "#{self.class.reference_prefix}#{format_reference}" + + if cross_project_reference?(source_project, target_project) + source_project.to_reference + reference + else + reference + end + end + private + def cross_project_reference?(source_project, target_project) + source_project && target_project && source_project != target_project + end + def issues_count(user, params = {}) IssuesFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) .execute diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 2fc074dc401..a246a90435d 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -7,28 +7,8 @@ class ProjectLabel < Label delegate :group, to: :project, allow_nil: true - ## - # Returns the String necessary to reference this ProjectLabel in Markdown - # - # format - Symbol format to use (default: :id, optional: :name) - # - # Examples: - # - # ProjectLabel.first.to_reference # => "~1" - # ProjectLabel.first.to_reference(format: :name) # => "~\"bug\"" - # ProjectLabel.first.to_reference(project) # => "gitlab-org/gitlab-ce~1" - # - # Returns a String - # - def to_reference(from_project = nil, format: :id) - format_reference = label_format_reference(format) - reference = "#{self.class.reference_prefix}#{format_reference}" - - if cross_project_reference?(from_project) - project.to_reference + reference - else - reference - end + def to_reference(target_project = nil, format: :id) + super(project, target_project, format: format) end private -- cgit v1.2.1 From 6792644ae77476af88343d4a5b9e370326d331ea Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:14:05 -0300 Subject: Use present? instead of presence on Projects::IssuesController#index --- app/controllers/projects/issues_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 9f18c8c03df..cb649264146 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -26,7 +26,7 @@ class Projects::IssuesController < Projects::ApplicationController @issues = issues_collection @issues = @issues.page(params[:page]) - if params[:label_name].presence + if params[:label_name].present? @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute end -- cgit v1.2.1 From da9407927be1dd4a6b0b00be360e61d6bb507da8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:16:51 -0300 Subject: Remove unnecessary `title.present?` on LabelsFinder --- app/finders/labels_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 28110be7097..5ee2e1ee6e8 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -33,7 +33,7 @@ class LabelsFinder < UnionFinder end def with_title(items) - items = items.where(title: title) if title.present? + items = items.where(title: title) if title items end -- cgit v1.2.1 From 5912251e614421b4798ac235c7f408a1b45a2b4c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:22:11 -0300 Subject: Use reverse_merge on Label#issues_count and Label#merge_requests_count --- app/models/label.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/label.rb b/app/models/label.rb index 444f45fa09e..112d9f3fbe5 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -128,13 +128,13 @@ class Label < ActiveRecord::Base end def issues_count(user, params = {}) - IssuesFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) + IssuesFinder.new(user, params.reverse_merge(label_name: title, scope: 'all')) .execute .count end def merge_requests_count(user, params = {}) - MergeRequestsFinder.new(user, { label_name: title, scope: 'all' }.merge(params)) + MergeRequestsFinder.new(user, params.reverse_merge(label_name: title, scope: 'all')) .execute .count end -- cgit v1.2.1 From fc59d357201a907e6079f6f0a7fc9a31f2957f88 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:25:25 -0300 Subject: Skip update query if label have the same id on Labels::TransferService --- app/services/labels/transfer_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index 81897c62c0f..04312c114ef 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -19,6 +19,8 @@ module Labels labels_to_transfer.find_each do |label| new_label_id = find_or_create_label!(label) + next if new_label_id == label.id + LabelLink.where(label_id: label.id).update_all(label_id: new_label_id) end end -- cgit v1.2.1 From cece77f273407da4a9ed66acda53e9ac4117dbaf Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 17:45:39 -0300 Subject: Fix validation to allow updates to description/color of project label --- app/models/project_label.rb | 2 +- spec/models/project_label_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/project_label.rb b/app/models/project_label.rb index a246a90435d..5b739bcdadf 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -14,7 +14,7 @@ class ProjectLabel < Label private def title_must_not_exist_at_group_level - return unless group.present? + return unless group.present? && title_changed? if group.labels.with_title(self.title).exists? errors.add(:title, :label_already_exists_at_group_level, group: group.name) diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 7966c52c38d..c861d4b73bb 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -40,6 +40,16 @@ describe ProjectLabel, models: true do expect(label.errors[:title]).to be_empty end + + it 'does not returns error when title does not change' do + project_label = create(:label, project: project, name: 'Security') + create(:group_label, group: group, name: 'Security') + project_label.description = 'Security related stuff.' + + project_label.valid? + + expect(project_label .errors[:title]).to be_empty + end end end -- cgit v1.2.1 From 0dbb47f00ee7333bbe76ddda853511c9120e2ad6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:03:52 -0300 Subject: Fix IssuableBaseService#find_or_create_label_ids --- app/services/issuable_base_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index b3e4f8dcf27..deadf1fe283 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -88,7 +88,7 @@ class IssuableBaseService < BaseService return unless labels params[:label_ids] = labels.split(',').map do |label_name| - label = available_labels.find_by(title: title).select(:id) + label = available_labels.find_by(title: label_name) label ||= project.labels.create(title: label_name.strip, color: Label::DEFAULT_COLOR) label.id -- cgit v1.2.1 From 033ea9d1e80544df5236ca045c88f649e41afbc7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:04:55 -0300 Subject: Move label management to services on merge requests API --- lib/api/merge_requests.rb | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 67fdd0be927..bf8504e1101 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -86,14 +86,11 @@ module API render_api_error!({ labels: errors }, 400) end + attrs[:labels] = params[:labels] if params[:labels] + merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute if merge_request.valid? - # Find or create labels and attach to issue - if params[:labels].present? - merge_request.add_labels_by_names(params[:labels].split(","), current_user) - end - present merge_request, with: Entities::MergeRequest, current_user: current_user else handle_merge_request_errors! merge_request.errors @@ -195,15 +192,11 @@ module API render_api_error!({ labels: errors }, 400) end + attrs[:labels] = params[:labels] if params[:labels] + merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request) if merge_request.valid? - # Find or create labels and attach to issue - unless params[:labels].nil? - merge_request.remove_labels - merge_request.add_labels_by_names(params[:labels].split(","), current_user) - end - present merge_request, with: Entities::MergeRequest, current_user: current_user else handle_merge_request_errors! merge_request.errors -- cgit v1.2.1 From bf710b5119dce329a1ffd43b01b3a4dbb3e94b09 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:34:44 -0300 Subject: Validate label params against all labels available to project on the API --- lib/api/helpers.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 67473f300c9..45120898b76 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -71,6 +71,10 @@ module API @project ||= find_project(params[:id]) end + def available_labels + @available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute + end + def find_project(id) project = Project.find_with_namespace(id) || Project.find_by(id: id) @@ -118,7 +122,7 @@ module API end def find_project_label(id) - label = user_project.labels.find_by_id(id) || user_project.labels.find_by_title(id) + label = available_labels.find_by_id(id) || available_labels.find_by_title(id) label || not_found!('Label') end @@ -197,16 +201,11 @@ module API def validate_label_params(params) errors = {} - if params[:labels].present? - params[:labels].split(',').each do |label_name| - label = user_project.labels.create_with( - color: Label::DEFAULT_COLOR).find_or_initialize_by( - title: label_name.strip) + params[:labels].to_s.split(',').each do |label_name| + label = available_labels.find_or_initialize_by(title: label_name.strip) + next if label.valid? - if label.invalid? - errors[label.title] = label.errors - end - end + errors[label.title] = label.errors end errors -- cgit v1.2.1 From 68f30b2fff362805568588f416709e7000d75ce3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:48:59 -0300 Subject: Add support to group labels on issues board API --- lib/api/boards.rb | 4 ++-- spec/requests/api/boards_spec.rb | 25 +++++++++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/api/boards.rb b/lib/api/boards.rb index b14dd4f6e83..4ac491edc1b 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -65,8 +65,8 @@ module API requires :label_id, type: Integer, desc: 'The ID of an existing label' end post '/lists' do - unless user_project.labels.exists?(params[:label_id]) - render_api_error!({ error: "Label not found!" }, 400) + unless available_labels.exists?(params[:label_id]) + render_api_error!({ error: 'Label not found!' }, 400) end authorize!(:admin_list, user_project) diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index f4b04445c6c..4f5c09a3029 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -106,9 +106,20 @@ describe API::API, api: true do describe "POST /projects/:id/board/lists" do let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } - it 'creates a new issue board list' do - post api(base_url, user), - label_id: ux_label.id + it 'creates a new issue board list for group labels' do + group = create(:group) + group_label = create(:group_label, group: group) + project.update(group: group) + + post api(base_url, user), label_id: group_label.id + + expect(response).to have_http_status(201) + expect(json_response['label']['name']).to eq(group_label.title) + expect(json_response['position']).to eq(3) + end + + it 'creates a new issue board list for project labels' do + post api(base_url, user), label_id: ux_label.id expect(response).to have_http_status(201) expect(json_response['label']['name']).to eq(ux_label.title) @@ -116,15 +127,13 @@ describe API::API, api: true do end it 'returns 400 when creating a new list if label_id is invalid' do - post api(base_url, user), - label_id: 23423 + post api(base_url, user), label_id: 23423 expect(response).to have_http_status(400) end - it "returns 403 for project members with guest role" do - put api("#{base_url}/#{test_list.id}", guest), - position: 1 + it 'returns 403 for project members with guest role' do + put api("#{base_url}/#{test_list.id}", guest), position: 1 expect(response).to have_http_status(403) end -- cgit v1.2.1 From 9b288238549dac5b59fd467f6ee1fdc53b6c783e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 18:53:06 -0300 Subject: List all available labels to the project on the labels API --- lib/api/labels.rb | 2 +- spec/requests/api/labels_spec.rb | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/api/labels.rb b/lib/api/labels.rb index c806829d69e..642e6345b9e 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -11,7 +11,7 @@ module API # Example Request: # GET /projects/:id/labels get ':id/labels' do - present user_project.labels, with: Entities::Label, current_user: current_user + present available_labels, with: Entities::Label, current_user: current_user end # Creates a new label diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 83789223019..1da9988978b 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -12,12 +12,18 @@ describe API::API, api: true do end describe 'GET /projects/:id/labels' do - it 'returns project labels' do + it 'returns all available labels to the project' do + group = create(:group) + group_label = create(:group_label, group: group) + project.update(group: group) + get api("/projects/#{project.id}/labels", user) + expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.size).to eq(1) - expect(json_response.first['name']).to eq(label1.name) + expect(json_response.size).to eq(2) + expect(json_response.first['name']).to eq(group_label.name) + expect(json_response.second['name']).to eq(label1.name) end end -- cgit v1.2.1 From ed7591f7b8c55498e3e38be41cc049a92b9fe4a6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 19:05:07 -0300 Subject: Remove unused method Issuable#remove_labels --- app/models/concerns/issuable.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index fee68d9cc8f..0b57e318662 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -230,10 +230,6 @@ module Issuable labels.order('title ASC').pluck(:title) end - def remove_labels - labels.delete_all - end - def add_labels_by_names(label_names, current_user) available_labels = LabelsFinder.new(current_user, project_id: project.id).execute -- cgit v1.2.1 From f008cdf218952556a0dc93962f55c0ff50733594 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 19:30:02 -0300 Subject: Remove Issuable#add_labels_by_names --- app/models/concerns/issuable.rb | 12 ------------ lib/gitlab/fogbugz_import/importer.rb | 25 ++++++++++--------------- lib/gitlab/google_code_import/importer.rb | 19 ++++++++----------- 3 files changed, 18 insertions(+), 38 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 0b57e318662..76de927ceab 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -230,18 +230,6 @@ module Issuable labels.order('title ASC').pluck(:title) end - def add_labels_by_names(label_names, current_user) - available_labels = LabelsFinder.new(current_user, project_id: project.id).execute - - label_names.each do |label_name| - title = label_name.strip - label = available_labels.find_by(title: title) - label ||= project.labels.build(title: title, color: Label::DEFAULT_COLOR) - - self.labels << label - end - end - # Convert this Issuable class name to a format usable by Ability definitions # # Examples: diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index 1d6f97b99c7..9e926e2681a 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -122,25 +122,20 @@ module Gitlab author_id = user_info(bug['ixPersonOpenedBy'])[:gitlab_id] || project.creator_id issue = Issue.create!( - project_id: project.id, - title: bug['sTitle'], - description: body, - author_id: author_id, - assignee_id: assignee_id, - state: bug['fOpen'] == 'true' ? 'opened' : 'closed' + iid: bug['ixBug'], + project_id: project.id, + title: bug['sTitle'], + description: body, + author_id: author_id, + assignee_id: assignee_id, + state: bug['fOpen'] == 'true' ? 'opened' : 'closed', + created_at: date, + updated_at: DateTime.parse(bug['dtLastUpdated']) ) - issue.add_labels_by_names(labels, project.creator) - if issue.iid != bug['ixBug'] - issue.update_attribute(:iid, bug['ixBug']) - end + issue.update_attribute(:label_ids, project.labels.where(title: labels).pluck(:id)) import_issue_comments(issue, comments) - - issue.update_attribute(:created_at, date) - - last_update = DateTime.parse(bug['dtLastUpdated']) - issue.update_attribute(:updated_at, last_update) end end diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 8d757da2264..79a0eaba1e8 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -92,19 +92,16 @@ module Gitlab end issue = Issue.create!( - project_id: project.id, - title: raw_issue["title"], - description: body, - author_id: project.creator_id, - assignee_id: assignee_id, - state: raw_issue["state"] == "closed" ? "closed" : "opened" + iid: raw_issue['id'], + project_id: project.id, + title: raw_issue['title'], + description: body, + author_id: project.creator_id, + assignee_id: assignee_id, + state: raw_issue['state'] == 'closed' ? 'closed' : 'opened' ) - issue.add_labels_by_names(labels, project.creator) - - if issue.iid != raw_issue["id"] - issue.update_attribute(:iid, raw_issue["id"]) - end + issue.update_attribute(:label_ids, project.labels.where(title: labels).pluck(:id)) import_issue_comments(issue, comments) end -- cgit v1.2.1 From de9d3915d249a59e65226f6dd2ebe1f50a47306e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 13 Oct 2016 19:38:22 -0300 Subject: Remove `::` for method call on Label#text_color --- app/models/label.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/label.rb b/app/models/label.rb index 112d9f3fbe5..8b775e81db7 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -90,7 +90,7 @@ class Label < ActiveRecord::Base end def text_color - LabelsHelper::text_color_for_bg(self.color) + LabelsHelper.text_color_for_bg(self.color) end def title=(value) -- cgit v1.2.1 From 297892011330ecdd2fa7cbe47fbc6fd4f3b62171 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 16:12:07 -0300 Subject: Add LabelPriority model --- app/models/label.rb | 7 +-- app/models/label_priority.rb | 8 ++++ .../20161014173530_create_label_priorities.rb | 55 ++++++++++++++++++++++ db/schema.rb | 15 +++++- spec/factories/label_priorities.rb | 7 +++ spec/models/label_priority_spec.rb | 21 +++++++++ spec/models/label_spec.rb | 1 + 7 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 app/models/label_priority.rb create mode 100644 db/migrate/20161014173530_create_label_priorities.rb create mode 100644 spec/factories/label_priorities.rb create mode 100644 spec/models/label_priority_spec.rb diff --git a/app/models/label.rb b/app/models/label.rb index 8b775e81db7..3ce4e253035 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -16,6 +16,7 @@ class Label < ActiveRecord::Base default_value_for :color, DEFAULT_COLOR has_many :lists, dependent: :destroy + has_many :priorities, class_name: 'LabelPriority' has_many :label_links, dependent: :destroy has_many :issues, through: :label_links, source: :target, source_type: 'Issue' has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest' @@ -26,8 +27,6 @@ class Label < ActiveRecord::Base validates :title, presence: true, format: { with: /\A[^,]+\z/ } validates :title, uniqueness: { scope: [:group_id, :project_id] } - before_save :nullify_priority - default_scope { order(title: :asc) } scope :templates, -> { where(template: true) } @@ -149,10 +148,6 @@ class Label < ActiveRecord::Base end end - def nullify_priority - self.priority = nil if priority.blank? - end - def sanitize_title(value) CGI.unescapeHTML(Sanitize.clean(value.to_s)) end diff --git a/app/models/label_priority.rb b/app/models/label_priority.rb new file mode 100644 index 00000000000..5b85e0b6533 --- /dev/null +++ b/app/models/label_priority.rb @@ -0,0 +1,8 @@ +class LabelPriority < ActiveRecord::Base + belongs_to :project + belongs_to :label + + validates :project, :label, :priority, presence: true + validates :label_id, uniqueness: { scope: :project_id } + validates :priority, numericality: { only_integer: true, greater_than_or_equal_to: 0 } +end diff --git a/db/migrate/20161014173530_create_label_priorities.rb b/db/migrate/20161014173530_create_label_priorities.rb new file mode 100644 index 00000000000..f9d94ebdc70 --- /dev/null +++ b/db/migrate/20161014173530_create_label_priorities.rb @@ -0,0 +1,55 @@ +class CreateLabelPriorities < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'Prioritezed labels will not work as expected until this migration is complete.' + + disable_ddl_transaction! + + def up + create_table :label_priorities do |t| + t.references :project, foreign_key: { on_delete: :cascade }, null: false + t.references :label, foreign_key: { on_delete: :cascade }, null: false + t.integer :priority, null: false + + t.timestamps null: false + end + + execute <<-EOF.strip_heredoc + INSERT INTO label_priorities (project_id, label_id, priority, created_at, updated_at) + SELECT labels.project_id, labels.id, labels.priority, NOW(), NOW() + FROM labels + WHERE labels.project_id IS NOT NULL + AND labels.priority IS NOT NULL; + EOF + + add_concurrent_index :label_priorities, [:project_id, :label_id], unique: true + add_concurrent_index :label_priorities, :priority + + remove_column :labels, :priority + end + + def down + add_column :labels, :priority, :integer + + if Gitlab::Database.mysql? + execute <<-EOF.strip_heredoc + UPDATE labels + INNER JOIN label_priorities ON labels.id = label_priorities.label_id AND labels.project_id = label_priorities.project_id + SET labels.priority = label_priorities.priority; + EOF + else + execute <<-EOF.strip_heredoc + UPDATE labels + SET priority = label_priorities.priority + FROM label_priorities + WHERE labels.id = label_priorities.label_id + AND labels.project_id = label_priorities.project_id; + EOF + end + + add_concurrent_index :labels, :priority + + drop_table :label_priorities + end +end diff --git a/db/schema.rb b/db/schema.rb index 37f0be0e834..e17fdf75f88 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -519,6 +519,17 @@ ActiveRecord::Schema.define(version: 20161017095000) do add_index "label_links", ["label_id"], name: "index_label_links_on_label_id", using: :btree add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree + create_table "label_priorities", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "label_id", null: false + t.integer "priority", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "label_priorities", ["priority"], name: "index_label_priorities_on_priority", using: :btree + add_index "label_priorities", ["project_id", "label_id"], name: "index_label_priorities_on_project_id_and_label_id", unique: true, using: :btree + create_table "labels", force: :cascade do |t| t.string "title" t.string "color" @@ -527,14 +538,12 @@ ActiveRecord::Schema.define(version: 20161017095000) do t.datetime "updated_at" t.boolean "template", default: false t.string "description" - t.integer "priority" t.text "description_html" t.string "type" t.integer "group_id" end add_index "labels", ["group_id"], name: "index_labels_on_group_id", using: :btree - add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree add_index "labels", ["title"], name: "index_labels_on_title", using: :btree @@ -1216,6 +1225,8 @@ ActiveRecord::Schema.define(version: 20161017095000) do add_foreign_key "boards", "projects" add_foreign_key "issue_metrics", "issues", on_delete: :cascade + add_foreign_key "label_priorities", "labels", on_delete: :cascade + add_foreign_key "label_priorities", "projects", on_delete: :cascade add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "lists", "boards" add_foreign_key "lists", "labels" diff --git a/spec/factories/label_priorities.rb b/spec/factories/label_priorities.rb new file mode 100644 index 00000000000..f25939d2d3e --- /dev/null +++ b/spec/factories/label_priorities.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :label_priority do + project factory: :empty_project + label + sequence(:priority) + end +end diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb new file mode 100644 index 00000000000..5f7fa3aa047 --- /dev/null +++ b/spec/models/label_priority_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe LabelPriority, models: true do + describe 'relationships' do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:label) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:label) } + it { is_expected.to validate_presence_of(:priority) } + it { is_expected.to validate_numericality_of(:priority).only_integer.is_greater_than_or_equal_to(0) } + + it 'validates uniqueness of label_id scoped to project_id' do + create(:label_priority) + + expect(subject).to validate_uniqueness_of(:label_id).scoped_to(:project_id) + end + end +end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index c6e1ea19987..4af0fb6afa9 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -10,6 +10,7 @@ describe Label, models: true do it { is_expected.to have_many(:issues).through(:label_links).source(:target) } it { is_expected.to have_many(:label_links).dependent(:destroy) } it { is_expected.to have_many(:lists).dependent(:destroy) } + it { is_expected.to have_many(:priorities).class_name('LabelPriority') } end describe 'validation' do -- cgit v1.2.1 From 67314e95ae836365fa1989439a6379aac781a0b4 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 18:32:44 -0300 Subject: Add support to group labels prioritization on project level --- app/controllers/projects/labels_controller.rb | 13 +++++++------ app/models/label.rb | 10 ++++++---- spec/features/projects/labels/update_prioritization_spec.rb | 8 ++++---- spec/models/label_priority_spec.rb | 1 - 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index f4ad503c9a6..f453b70fb5d 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -12,8 +12,8 @@ class Projects::LabelsController < Projects::ApplicationController respond_to :js, :html def index - @prioritized_labels = @available_labels.prioritized - @labels = @available_labels.unprioritized.page(params[:page]) + @prioritized_labels = @available_labels.prioritized(@project) + @labels = @available_labels.unprioritized(@project).page(params[:page]) respond_to do |format| format.html @@ -84,11 +84,10 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| label = @available_labels.find(params[:id]) - if label.update_attribute(:priority, nil) + if label.priorities.where(project_id: project).delete_all format.json { render json: label } else - message = label.errors.full_messages.uniq.join('. ') - format.json { render json: { message: message }, status: :unprocessable_entity } + format.json { head :unprocessable_entity } end end end @@ -100,7 +99,9 @@ class Projects::LabelsController < Projects::ApplicationController params[:label_ids].each_with_index do |label_id, index| next unless label_ids.include?(label_id.to_i) - Label.where(id: label_id).update_all(priority: index) + label_priority = LabelPriority.find_or_initialize_by(project_id: @project.id, label_id: label_id) + label_priority.priority = index + label_priority.save! end end diff --git a/app/models/label.rb b/app/models/label.rb index 3ce4e253035..ea11d9d7864 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -32,12 +32,14 @@ class Label < ActiveRecord::Base scope :templates, -> { where(template: true) } scope :with_title, ->(title) { where(title: title) } - def self.prioritized - where.not(priority: nil).reorder(:priority, :title) + def self.prioritized(project) + joins(:priorities) + .where(label_priorities: { project_id: project }) + .reorder('label_priorities.priority ASC, labels.title ASC') end - def self.unprioritized - where(priority: nil) + def self.unprioritized(project) + where.not(id: prioritized(project).select(:id)) end alias_attribute :name, :title diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index 84a12a38c26..c9fa8315e79 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -35,7 +35,7 @@ feature 'Prioritize labels', feature: true do end scenario 'user can unprioritize a group label', js: true do - feature.update(priority: 1) + create(:label_priority, project: project, label: feature, priority: 1) visit namespace_project_labels_path(project.namespace, project) @@ -70,7 +70,7 @@ feature 'Prioritize labels', feature: true do end scenario 'user can unprioritize a project label', js: true do - bug.update(priority: 1) + create(:label_priority, project: project, label: bug, priority: 1) visit namespace_project_labels_path(project.namespace, project) @@ -89,8 +89,8 @@ feature 'Prioritize labels', feature: true do end scenario 'user can sort prioritized labels and persist across reloads', js: true do - bug.update(priority: 1) - feature.update(priority: 2) + create(:label_priority, project: project, label: bug, priority: 1) + create(:label_priority, project: project, label: feature, priority: 2) visit namespace_project_labels_path(project.namespace, project) diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb index 5f7fa3aa047..d18c2f7949a 100644 --- a/spec/models/label_priority_spec.rb +++ b/spec/models/label_priority_spec.rb @@ -9,7 +9,6 @@ describe LabelPriority, models: true do describe 'validations' do it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:label) } - it { is_expected.to validate_presence_of(:priority) } it { is_expected.to validate_numericality_of(:priority).only_integer.is_greater_than_or_equal_to(0) } it 'validates uniqueness of label_id scoped to project_id' do -- cgit v1.2.1 From 99e928f103182b58156edb107b55344eaafc6772 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 18:51:34 -0300 Subject: Add restriction to number of permitted priorities per project label --- app/models/project_label.rb | 9 +++++++++ spec/models/project_label_spec.rb | 13 ++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 5b739bcdadf..f863d442c21 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -1,8 +1,11 @@ class ProjectLabel < Label + NUMBER_OF_PRIORITIES = 1 + belongs_to :project validates :project, presence: true + validate :permitted_numbers_of_priorities validate :title_must_not_exist_at_group_level delegate :group, to: :project, allow_nil: true @@ -20,4 +23,10 @@ class ProjectLabel < Label errors.add(:title, :label_already_exists_at_group_level, group: group.name) end end + + def permitted_numbers_of_priorities + if priorities && priorities.size >= NUMBER_OF_PRIORITIES + errors.add(:priorities, 'Number of permitted priorities exceeded') + end + end end diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index c861d4b73bb..cd4732fb737 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -48,7 +48,18 @@ describe ProjectLabel, models: true do project_label.valid? - expect(project_label .errors[:title]).to be_empty + expect(project_label.errors[:title]).to be_empty + end + end + + context 'when attempting to add more than one priority to the project label' do + it 'returns error' do + subject.priorities.build + subject.priorities.build + + subject.valid? + + expect(subject.errors[:priorities]).to include 'Number of permitted priorities exceeded' end end end -- cgit v1.2.1 From 3c2aaec1f2624ad4817e7ac52120985682afa448 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 20:06:26 -0300 Subject: Fix sorting by label priorities --- app/models/concerns/issuable.rb | 3 ++- app/models/concerns/sortable.rb | 6 +++-- app/models/label.rb | 11 ++++++++ app/models/todo.rb | 2 +- .../controllers/projects/labels_controller_spec.rb | 31 +++++++++++++--------- spec/factories/labels.rb | 10 +++++++ 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 76de927ceab..d726cb6b7aa 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -146,7 +146,8 @@ module Issuable def order_labels_priority(excluded_labels: []) condition_field = "#{table_name}.id" - highest_priority = highest_label_priority(name, condition_field, excluded_labels: excluded_labels).to_sql + project_field = "#{table_name}.project_id" + highest_priority = highest_label_priority(name, project_field, condition_field, excluded_labels: excluded_labels).to_sql select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). group(arel_table[:id]). diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index 1ebecd86af9..83e551fd152 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -38,10 +38,12 @@ module Sortable private - def highest_label_priority(object_types, condition_field, excluded_labels: []) - query = Label.select(Label.arel_table[:priority].minimum). + def highest_label_priority(object_types, project_field, condition_field, excluded_labels: []) + query = Label.select(LabelPriority.arel_table[:priority].minimum). + left_join_priorities. joins(:label_links). where(label_links: { target_type: object_types }). + where("label_priorities.project_id = #{project_field}"). where("label_links.target_id = #{condition_field}"). reorder(nil) diff --git a/app/models/label.rb b/app/models/label.rb index ea11d9d7864..1d775a83f96 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -42,6 +42,17 @@ class Label < ActiveRecord::Base where.not(id: prioritized(project).select(:id)) end + def self.left_join_priorities + labels = Label.arel_table + priorities = LabelPriority.arel_table + + label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin). + on(labels[:id].eq(priorities[:label_id])). + join_sources + + joins(label_priorities) + end + alias_attribute :name, :title def self.reference_prefix diff --git a/app/models/todo.rb b/app/models/todo.rb index 6ae9956ade5..fd90a893d2e 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -52,7 +52,7 @@ class Todo < ActiveRecord::Base # Todos with highest priority first then oldest todos # Need to order by created_at last because of differences on Mysql and Postgres when joining by type "Merge_request/Issue" def order_by_labels_priority - highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.target_id").to_sql + highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.project_id", "todos.target_id").to_sql select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')). diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 29251f49810..622ab154493 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -15,21 +15,28 @@ describe Projects::LabelsController do let!(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') } let!(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') } let!(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') } - let!(:label_4) { create(:label, project: project, priority: nil, title: 'Label 4') } - let!(:label_5) { create(:label, project: project, priority: nil, title: 'Label 5') } + let!(:label_4) { create(:label, project: project, title: 'Label 4') } + let!(:label_5) { create(:label, project: project, title: 'Label 5') } - let!(:group_label_1) { create(:group_label, group: group, priority: 3, title: 'Group Label 1') } - let!(:group_label_2) { create(:group_label, group: group, priority: 1, title: 'Group Label 2') } - let!(:group_label_3) { create(:group_label, group: group, priority: nil, title: 'Group Label 3') } - let!(:group_label_4) { create(:group_label, group: group, priority: nil, title: 'Group Label 4') } + let!(:group_label_1) { create(:group_label, group: group, title: 'Group Label 1') } + let!(:group_label_2) { create(:group_label, group: group, title: 'Group Label 2') } + let!(:group_label_3) { create(:group_label, group: group, title: 'Group Label 3') } + let!(:group_label_4) { create(:group_label, group: group, title: 'Group Label 4') } + + before do + create(:label_priority, project: project, label: group_label_1, priority: 3) + create(:label_priority, project: project, label: group_label_2, priority: 1) + end context '@prioritized_labels' do before do list_labels end - it 'contains only prioritized labels' do - expect(assigns(:prioritized_labels)).to all(have_attributes(priority: a_value > 0)) + it 'does not include labels without priority' do + list_labels + + expect(assigns(:prioritized_labels)).not_to include(group_label_3, group_label_4, label_4, label_5) end it 'is sorted by priority, then label title' do @@ -38,16 +45,16 @@ describe Projects::LabelsController do end context '@labels' do - it 'contains only unprioritized labels' do + it 'is sorted by label title' do list_labels - expect(assigns(:labels)).to all(have_attributes(priority: nil)) + expect(assigns(:labels)).to eq [group_label_3, group_label_4, label_4, label_5] end - it 'is sorted by label title' do + it 'does not include labels with priority' do list_labels - expect(assigns(:labels)).to eq [group_label_3, group_label_4, label_4, label_5] + expect(assigns(:labels)).not_to include(group_label_2, label_1, label_3, group_label_1, label_2) end it 'does not include group labels when project does not belong to a group' do diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 5c789d72bac..3e8822faf97 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -3,6 +3,16 @@ FactoryGirl.define do sequence(:title) { |n| "label#{n}" } color "#990000" project + + transient do + priority nil + end + + after(:create) do |label, evaluator| + if evaluator.priority + label.priorities.create(project: label.project, priority: evaluator.priority) + end + end end factory :group_label, class: GroupLabel do -- cgit v1.2.1 From 86e0b5d643df21503281115774da550e06a4e878 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 14 Oct 2016 20:51:41 -0300 Subject: Fix issue board related controllers to expose label priority per project --- app/controllers/projects/boards/issues_controller.rb | 4 ++-- app/controllers/projects/boards/lists_controller.rb | 5 ++--- app/models/issue.rb | 12 ++++++++++++ app/models/label.rb | 6 ++++++ app/models/list.rb | 11 +++++++++++ spec/fixtures/api/schemas/list.json | 2 +- 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 71eb56aed0b..a2b01ff43dc 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -72,10 +72,10 @@ module Projects def serialize_as_json(resource) resource.as_json( + labels: true, only: [:iid, :title, :confidential], include: { - assignee: { only: [:id, :name, :username], methods: [:avatar_url] }, - labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] } + assignee: { only: [:id, :name, :username], methods: [:avatar_url] } }) end end diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb index 76ae41319c4..67e3c9add81 100644 --- a/app/controllers/projects/boards/lists_controller.rb +++ b/app/controllers/projects/boards/lists_controller.rb @@ -76,9 +76,8 @@ module Projects resource.as_json( only: [:id, :list_type, :position], methods: [:title], - include: { - label: { only: [:id, :title, :description, :color, :priority] } - }) + label: true + ) end end end diff --git a/app/models/issue.rb b/app/models/issue.rb index abd58e0454a..f7ccce2924a 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -274,4 +274,16 @@ class Issue < ActiveRecord::Base def check_for_spam? project.public? end + + def as_json(options = {}) + super(options).tap do |json| + if options.has_key?(:labels) + json[:labels] = labels.as_json( + project: project, + only: [:id, :title, :description, :color], + methods: [:text_color] + ) + end + end + end end diff --git a/app/models/label.rb b/app/models/label.rb index 1d775a83f96..6fd45d251a8 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -133,6 +133,12 @@ class Label < ActiveRecord::Base end end + def as_json(options = {}) + super(options).tap do |json| + json[:priority] = priorities.find_by(project: options[:project]).try(:priority) if options.has_key?(:project) + end + end + private def cross_project_reference?(source_project, target_project) diff --git a/app/models/list.rb b/app/models/list.rb index eb87decdbc8..065d75bd1dc 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -26,6 +26,17 @@ class List < ActiveRecord::Base label? ? label.name : list_type.humanize end + def as_json(options = {}) + super(options).tap do |json| + if options.has_key?(:label) + json[:label] = label.as_json( + project: board.project, + only: [:id, :title, :description, :color] + ) + end + end + end + private def can_be_destroyed diff --git a/spec/fixtures/api/schemas/list.json b/spec/fixtures/api/schemas/list.json index f070fa3b254..8d94cf26ecb 100644 --- a/spec/fixtures/api/schemas/list.json +++ b/spec/fixtures/api/schemas/list.json @@ -13,7 +13,7 @@ "enum": ["backlog", "label", "done"] }, "label": { - "type": ["object"], + "type": ["object", "null"], "required": [ "id", "color", -- cgit v1.2.1 From 9cb123843474d60c452c02bf5701f11769661829 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Sat, 15 Oct 2016 12:23:16 -0300 Subject: Fix project issues labels feature spec --- features/steps/project/issues/labels.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb index 2937d5d7ca8..f74a9b5df47 100644 --- a/features/steps/project/issues/labels.rb +++ b/features/steps/project/issues/labels.rb @@ -8,7 +8,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps end step 'I remove label \'bug\'' do - page.within "#label_#{bug_label.id}" do + page.within "#project_label_#{bug_label.id}" do first(:link, 'Delete').click end end -- cgit v1.2.1 From 074c964913218e99c42f0d8b5855c4ad2ad93267 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Sat, 15 Oct 2016 12:46:20 -0300 Subject: Add label type to group and project labels lists --- app/assets/stylesheets/pages/labels.scss | 16 ++++++++++- app/views/groups/labels/index.html.haml | 2 +- app/views/shared/_label_row.html.haml | 2 ++ .../20161017125927_add_unique_index_to_labels.rb | 32 ++++++++++++++++++++++ db/schema.rb | 5 ++-- 5 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20161017125927_add_unique_index_to_labels.rb diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 9bac6d46355..2f7f7325877 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -66,7 +66,21 @@ text-overflow: ellipsis; vertical-align: middle; max-width: 100%; - } + } + } + + .label-type { + display: block; + margin-bottom: 10px; + margin-left: 50px; + + @media (min-width: $screen-sm-min) { + display: inline-block; + width: 100px; + margin-left: 10px; + margin-bottom: 0; + vertical-align: middle; + } } .label-description { diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 6c69e3465f4..70783a63409 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -2,7 +2,7 @@ .top-area.adjust .nav-text - Labels can be applied to issues and merge requests. + Labels can be applied to issues and merge requests. Group labels are available for any project within the group. .nav-controls - if can?(current_user, :admin_label, @group) diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 813ce4f1405..76c3327fefc 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -10,6 +10,8 @@ = icon('star') %span.label-name = link_to_label(label, subject: @project, tooltip: false) + %span.label-type + = label.model_name.human.titleize - if label.description %span.label-description = markdown_field(label, :description) diff --git a/db/migrate/20161017125927_add_unique_index_to_labels.rb b/db/migrate/20161017125927_add_unique_index_to_labels.rb new file mode 100644 index 00000000000..16ae38612de --- /dev/null +++ b/db/migrate/20161017125927_add_unique_index_to_labels.rb @@ -0,0 +1,32 @@ +class AddUniqueIndexToLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'This migration removes duplicated labels.' + + disable_ddl_transaction! + + def up + select_all('SELECT title, COUNT(id) as cnt FROM labels GROUP BY project_id, title HAVING COUNT(id) > 1').each do |label| + label_title = quote_string(label['title']) + duplicated_ids = select_all("SELECT id FROM labels WHERE title = '#{label_title}' ORDER BY id ASC").map{ |label| label['id'] } + label_id = duplicated_ids.first + duplicated_ids.delete(label_id) + + execute("UPDATE label_links SET label_id = #{label_id} WHERE label_id IN(#{duplicated_ids.join(",")})") + execute("DELETE FROM labels WHERE id IN(#{duplicated_ids.join(",")})") + end + + remove_index :labels, column: :project_id if index_exists?(:labels, :project_id) + remove_index :labels, column: :title if index_exists?(:labels, :title) + + add_concurrent_index :labels, [:group_id, :project_id, :title], unique: true + end + + def down + remove_index :labels, column: [:group_id, :project_id, :title] if index_exists?(:labels, [:group_id, :project_id, :title], unique: true) + + add_concurrent_index :labels, :project_id + add_concurrent_index :labels, :title + end +end diff --git a/db/schema.rb b/db/schema.rb index e17fdf75f88..f9353290abf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161017095000) do +ActiveRecord::Schema.define(version: 20161017125927) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -543,9 +543,8 @@ ActiveRecord::Schema.define(version: 20161017095000) do t.integer "group_id" end + add_index "labels", ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true, using: :btree add_index "labels", ["group_id"], name: "index_labels_on_group_id", using: :btree - add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree - add_index "labels", ["title"], name: "index_labels_on_title", using: :btree create_table "lfs_objects", force: :cascade do |t| t.string "oid", null: false -- cgit v1.2.1 From 530aae9080942646b130510e970d9d82c009d8e5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 16:34:22 -0200 Subject: Abstract LabelPriority away into methods on Label model --- app/controllers/projects/labels_controller.rb | 14 +++---- app/models/label.rb | 16 +++++++- spec/models/label_spec.rb | 58 +++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index f453b70fb5d..42fd09e9b7e 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -84,7 +84,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| label = @available_labels.find(params[:id]) - if label.priorities.where(project_id: project).delete_all + if label.unprioritize!(project) format.json { render json: label } else format.json { head :unprocessable_entity } @@ -94,14 +94,12 @@ class Projects::LabelsController < Projects::ApplicationController def set_priorities Label.transaction do - label_ids = @available_labels.where(id: params[:label_ids]).pluck(:id) + available_labels_ids = @available_labels.where(id: params[:label_ids]).pluck(:id) + label_ids = params[:label_ids].select { |id| available_labels_ids.include?(id.to_i) } - params[:label_ids].each_with_index do |label_id, index| - next unless label_ids.include?(label_id.to_i) - - label_priority = LabelPriority.find_or_initialize_by(project_id: @project.id, label_id: label_id) - label_priority.priority = index - label_priority.save! + label_ids.each_with_index do |label_id, index| + label = @available_labels.find(label_id) + label.prioritize!(project, index) end end diff --git a/app/models/label.rb b/app/models/label.rb index 6fd45d251a8..ae07e8f60e1 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -97,6 +97,20 @@ class Label < ActiveRecord::Base merge_requests_count(user, project_id: project.try(:id) || project_id, state: 'opened') end + def prioritize!(project, value) + label_priority = priorities.find_or_initialize_by(project_id: project.id) + label_priority.priority = value + label_priority.save! + end + + def unprioritize!(project) + priorities.where(project: project).delete_all + end + + def priority(project) + priorities.find_by(project: project).try(:priority) + end + def template? template end @@ -135,7 +149,7 @@ class Label < ActiveRecord::Base def as_json(options = {}) super(options).tap do |json| - json[:priority] = priorities.find_by(project: options[:project]).try(:priority) if options.has_key?(:project) + json[:priority] = priority(options[:project]) if options.has_key?(:project) end end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 4af0fb6afa9..0c163659a71 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -46,4 +46,62 @@ describe Label, models: true do expect(label.title).to eq('foo & bar?') end end + + describe 'priorization' do + subject(:label) { create(:label) } + + let(:project) { label.project } + + describe '#prioritize!' do + context 'when label is not prioritized' do + it 'creates a label priority' do + expect { label.prioritize!(project, 1) }.to change(label.priorities, :count).by(1) + end + + it 'sets label priority' do + label.prioritize!(project, 1) + + expect(label.priorities.first.priority).to eq 1 + end + end + + context 'when label is prioritized' do + let!(:priority) { create(:label_priority, project: project, label: label, priority: 0) } + + it 'does not create a label priority' do + expect { label.prioritize!(project, 1) }.not_to change(label.priorities, :count) + end + + it 'updates label priority' do + label.prioritize!(project, 1) + + expect(priority.reload.priority).to eq 1 + end + end + end + + describe '#unprioritize!' do + it 'removes label priority' do + create(:label_priority, project: project, label: label, priority: 0) + + expect { label.unprioritize!(project) }.to change(label.priorities, :count).by(-1) + end + end + + describe '#priority' do + context 'when label is not prioritized' do + it 'returns nil' do + expect(label.priority(project)).to be_nil + end + end + + context 'when label is prioritized' do + it 'returns label priority' do + create(:label_priority, project: project, label: label, priority: 1) + + expect(label.priority(project)).to eq 1 + end + end + end + end end -- cgit v1.2.1 From 1e5ea6e7e05cd45fc56d0341ac8b5c32e57779b5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 16:55:46 -0200 Subject: Return only labels that user have access on IssuableFinder#labels --- app/finders/issuable_finder.rb | 13 +++++-------- app/finders/labels_finder.rb | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 41ea8f801c1..e27986ef95b 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -124,15 +124,12 @@ class IssuableFinder def labels return @labels if defined?(@labels) - if labels? && !filter_by_no_label? - @labels = Label.where(title: label_names) - - if projects - @labels = LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute + @labels = + if labels? && !filter_by_no_label? + LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute + else + Label.none end - else - @labels = Label.none - end end def assignee? diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 5ee2e1ee6e8..48fd1280ed2 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -45,7 +45,7 @@ class LabelsFinder < UnionFinder params[:project_id].presence end - def project_ids + def projects_ids params[:project_ids].presence end @@ -70,7 +70,7 @@ class LabelsFinder < UnionFinder @projects = available_projects @projects = @projects.in_namespace(group_id) if group_id - @projects = @projects.where(id: project_ids) if project_ids + @projects = @projects.where(id: projects_ids) if projects_ids @projects = @projects.reorder(nil) @projects -- cgit v1.2.1 From d3b76e832f0afc38e2d0ffdff540c708a74ac26c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 17:30:49 -0200 Subject: Reuse LabelsFinder on Banzai::Filter::LabelReferenceFilter --- app/finders/labels_finder.rb | 18 ++++++++++++++---- lib/banzai/filter/label_reference_filter.rb | 8 +------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 48fd1280ed2..65db4184ecf 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -4,7 +4,9 @@ class LabelsFinder < UnionFinder @params = params end - def execute + def execute(authorized_only: true) + @authorized_only = authorized_only + items = find_union(label_ids, Label) items = with_title(items) sort(items) @@ -12,7 +14,7 @@ class LabelsFinder < UnionFinder private - attr_reader :current_user, :params + attr_reader :current_user, :params, :authorized_only def label_ids label_ids = [] @@ -57,7 +59,7 @@ class LabelsFinder < UnionFinder return @project if defined?(@project) if project_id - @project = available_projects.find(project_id) rescue nil + @project = find_project else @project = nil end @@ -65,10 +67,18 @@ class LabelsFinder < UnionFinder @project end + def find_project + if authorized_only + available_projects.find_by(id: project_id) + else + Project.find_by(id: project_id) + end + end + def projects return @projects if defined?(@projects) - @projects = available_projects + @projects = authorized_only ? available_projects : Project.all @projects = @projects.in_namespace(group_id) if group_id @projects = @projects.where(id: projects_ids) if projects_ids @projects = @projects.reorder(nil) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 21085ae6d49..c24831e68ee 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -39,13 +39,7 @@ module Banzai end def find_labels(project) - label_ids = [] - label_ids << project.group.labels.select(:id) if project.group.present? - label_ids << project.labels.select(:id) - - union = Gitlab::SQL::Union.new(label_ids) - - Label.where("labels.id IN (#{union.to_sql})") + LabelsFinder.new(nil, project_id: project.id).execute(authorized_only: false) end # Parameters to pass to `Label.find_by` based on the given arguments -- cgit v1.2.1 From 8379fbcd47930320bf4dd6a3ac41c6efd427a91a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 17:48:46 -0200 Subject: Add subject to group and projects labels which return group/project --- app/helpers/labels_helper.rb | 10 ++-------- app/models/group_label.rb | 2 ++ app/models/project_label.rb | 2 ++ spec/models/group_label_spec.rb | 8 ++++++++ spec/models/project_label_spec.rb | 8 ++++++++ 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index af8741f5e06..a1d7713b45f 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -14,7 +14,7 @@ module LabelsHelper # # Examples: # - # # Allow the generated link to use the label's own project + # # Allow the generated link to use the label's own subject # link_to_label(label) # # # Force the generated link to use a provided group @@ -31,13 +31,7 @@ module LabelsHelper # # Returns a String def link_to_label(label, subject: nil, type: :issue, tooltip: true, css_class: nil, &block) - subject ||= - case label - when GroupLabel then label.group - when ProjectLabel then label.project - end - - link = label_filter_path(subject, label, type: type) + link = label_filter_path(subject || label.subject, label, type: type) if block_given? link_to link, class: css_class, &block diff --git a/app/models/group_label.rb b/app/models/group_label.rb index a1d8d087726..a698b532d19 100644 --- a/app/models/group_label.rb +++ b/app/models/group_label.rb @@ -3,6 +3,8 @@ class GroupLabel < Label validates :group, presence: true + alias_attribute :subject, :group + def to_reference(source_project = nil, target_project = nil, format: :id) super(source_project, target_project, format: format) end diff --git a/app/models/project_label.rb b/app/models/project_label.rb index f863d442c21..0a79521ed09 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -10,6 +10,8 @@ class ProjectLabel < Label delegate :group, to: :project, allow_nil: true + alias_attribute :subject, :project + def to_reference(target_project = nil, format: :id) super(project, target_project, format: format) end diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index 92b07a3cd44..85eb889225b 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -9,6 +9,14 @@ describe GroupLabel, models: true do it { is_expected.to validate_presence_of(:group) } end + describe '#subject' do + it 'aliases group to subject' do + subject = described_class.new(group: build(:group)) + + expect(subject.subject).to be(subject.group) + end + end + describe '#to_reference' do let(:label) { create(:group_label) } diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index cd4732fb737..18c9d449ee5 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -64,6 +64,14 @@ describe ProjectLabel, models: true do end end + describe '#subject' do + it 'aliases project to subject' do + subject = described_class.new(project: build(:empty_project)) + + expect(subject.subject).to be(subject.project) + end + end + describe '#to_reference' do let(:label) { create(:label) } -- cgit v1.2.1 From 928acba4c0ae31626dac621f0f240f18cbad548a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 18:03:06 -0200 Subject: Use keyword arguments on Sortable#highest_label_priority --- app/models/concerns/issuable.rb | 11 ++++++++--- app/models/concerns/sortable.rb | 8 ++++---- app/models/todo.rb | 8 +++++++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index d726cb6b7aa..245a865bcba 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -145,9 +145,14 @@ module Issuable end def order_labels_priority(excluded_labels: []) - condition_field = "#{table_name}.id" - project_field = "#{table_name}.project_id" - highest_priority = highest_label_priority(name, project_field, condition_field, excluded_labels: excluded_labels).to_sql + params = { + target_type: name, + target_column: "#{table_name}.id", + project_column: "#{table_name}.project_id", + excluded_labels: excluded_labels + } + + highest_priority = highest_label_priority(params).to_sql select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). group(arel_table[:id]). diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index 83e551fd152..12b23f00769 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -38,13 +38,13 @@ module Sortable private - def highest_label_priority(object_types, project_field, condition_field, excluded_labels: []) + def highest_label_priority(target_type:, target_column:, project_column:, excluded_labels: []) query = Label.select(LabelPriority.arel_table[:priority].minimum). left_join_priorities. joins(:label_links). - where(label_links: { target_type: object_types }). - where("label_priorities.project_id = #{project_field}"). - where("label_links.target_id = #{condition_field}"). + where("label_priorities.project_id = #{project_column}"). + where(label_links: { target_type: target_type }). + where("label_links.target_id = #{target_column}"). reorder(nil) query.where.not(title: excluded_labels) if excluded_labels.present? diff --git a/app/models/todo.rb b/app/models/todo.rb index fd90a893d2e..11c072dd000 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -52,7 +52,13 @@ class Todo < ActiveRecord::Base # Todos with highest priority first then oldest todos # Need to order by created_at last because of differences on Mysql and Postgres when joining by type "Merge_request/Issue" def order_by_labels_priority - highest_priority = highest_label_priority(["Issue", "MergeRequest"], "todos.project_id", "todos.target_id").to_sql + params = { + target_type: ['Issue', 'MergeRequest'], + target_column: "todos.target_id", + project_column: "todos.project_id" + } + + highest_priority = highest_label_priority(params).to_sql select("#{table_name}.*, (#{highest_priority}) AS highest_priority"). order(Gitlab::Database.nulls_last_order('highest_priority', 'ASC')). -- cgit v1.2.1 From 301264beada6aaf7dd0e4244c3f41131f9b4f359 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 18:50:49 -0200 Subject: Fix sorting merge requests by priority --- app/models/concerns/issuable.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 245a865bcba..9a9b562af02 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -145,10 +145,16 @@ module Issuable end def order_labels_priority(excluded_labels: []) + project_column = + case table_name + when Issue.table_name then "#{table_name}.project_id" + when MergeRequest.table_name then "#{table_name}.target_project_id" + end + params = { target_type: name, target_column: "#{table_name}.id", - project_column: "#{table_name}.project_id", + project_column: project_column, excluded_labels: excluded_labels } -- cgit v1.2.1 From 49ec98d1b2ca6f57f3f9434a0be0018fa5a53681 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 17 Oct 2016 22:39:21 -0200 Subject: Recreates the label priorities when moving project to another group --- app/services/labels/transfer_service.rb | 3 ++- spec/services/labels/transfer_service_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index 04312c114ef..559d2860d97 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -14,7 +14,7 @@ module Labels return unless group.present? Label.transaction do - labels_to_transfer = Label.where(id: label_links.select(:label_id).uniq) + labels_to_transfer = Label.where(id: label_links.select(:label_id)) labels_to_transfer.find_each do |label| new_label_id = find_or_create_label!(label) @@ -22,6 +22,7 @@ module Labels next if new_label_id == label.id LabelLink.where(label_id: label.id).update_all(label_id: new_label_id) + LabelPriority.where(project_id: project.id, label_id: label.id).update_all(label_id: new_label_id) end end end diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb index a72a05f6c99..cb09c16698a 100644 --- a/spec/services/labels/transfer_service_spec.rb +++ b/spec/services/labels/transfer_service_spec.rb @@ -26,6 +26,16 @@ describe Labels::TransferService, services: true do expect { service.execute }.to change(project.labels, :count).by(2) end + it 'recreates label priorities related to the missing group labels' do + create(:label_priority, project: project, label: group_label_1, priority: 1) + + service.execute + + new_project_label = project.labels.find_by(title: group_label_1.title) + expect(new_project_label.id).not_to eq group_label_1.id + expect(new_project_label.priorities).not_to be_empty + end + it 'does not recreate missing group labels that are not applied to issues or merge requests' do service.execute -- cgit v1.2.1 From 6c189dcc8e76d5ddb348832500b003bf0d1b49a6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 00:11:33 -0200 Subject: Add service to create project labels --- app/services/labels/create_service.rb | 33 +++++++++++++++++++ spec/services/labels/create_service_spec.rb | 51 +++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 app/services/labels/create_service.rb create mode 100644 spec/services/labels/create_service_spec.rb diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb new file mode 100644 index 00000000000..bb475ce741d --- /dev/null +++ b/app/services/labels/create_service.rb @@ -0,0 +1,33 @@ +module Labels + class CreateService + def initialize(current_user, project, params = {}) + @current_user = current_user + @group = project.group + @project = project + @params = params.dup + end + + def execute + find_or_create_label + end + + private + + attr_reader :current_user, :group, :project, :params + + def available_labels + @available_labels ||= LabelsFinder.new(current_user, project_id: project.id).execute + end + + def find_or_create_label + new_label = available_labels.find_by(title: title) + new_label ||= project.labels.create(params) + + new_label + end + + def title + params[:title] || params[:name] + end + end +end diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb new file mode 100644 index 00000000000..1e4bc294b46 --- /dev/null +++ b/spec/services/labels/create_service_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Labels::CreateService, services: true do + describe '#execute' do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + + let(:params) do + { + title: 'Security', + description: 'Security related stuff.', + color: '#FF0000' + } + end + + subject(:service) { described_class.new(user, project, params) } + + before do + project.team << [user, :developer] + end + + context 'when label does not exist at group level' do + it 'creates a new label at project level' do + expect { service.execute }.to change(project.labels, :count).by(1) + end + end + + context 'when label exists at group level' do + it 'returns the group label' do + group_label = create(:group_label, group: group, title: 'Security') + + expect(service.execute).to eq group_label + end + end + + context 'when label does not exist at group level' do + it 'creates a new label at project leve' do + expect { service.execute }.to change(project.labels, :count).by(1) + end + end + + context 'when label exists at project level' do + it 'returns the project label' do + project_label = create(:label, project: project, title: 'Security') + + expect(service.execute).to eq project_label + end + end + end +end -- cgit v1.2.1 From d009d38ed6ab5459f596194ce808c304e6379161 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 00:27:10 -0200 Subject: User Labes::CreateService to create labels --- app/models/project.rb | 3 ++- app/services/boards/lists/generate_service.rb | 3 +-- app/services/issuable_base_service.rb | 4 ++-- app/services/labels/transfer_service.rb | 8 ++------ lib/gitlab/fogbugz_import/importer.rb | 4 ++-- lib/gitlab/github_import/label_formatter.rb | 7 ++++--- lib/gitlab/google_code_import/importer.rb | 4 ++-- lib/gitlab/issues_labels.rb | 4 ++-- 8 files changed, 17 insertions(+), 20 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 7ab624eafdf..bc15ca3fc2e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -733,7 +733,8 @@ class Project < ActiveRecord::Base def create_labels Label.templates.each do |label| - self.labels.create!(label.attributes.symbolize_keys.except(:id, :template)) + params = label.attributes.except('id', 'template', 'created_at', 'updated_at') + Labels::CreateService.new(owner, self, params).execute end end diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index d8048f1c67e..1d3c7f2071b 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -19,8 +19,7 @@ module Boards end def find_or_create_label(params) - project.labels.create_with(color: params[:color]) - .find_or_create_by(name: params[:name]) + ::Labels::CreateService.new(current_user, project, params).execute end def label_params diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index deadf1fe283..4554963370f 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -88,8 +88,8 @@ class IssuableBaseService < BaseService return unless labels params[:label_ids] = labels.split(',').map do |label_name| - label = available_labels.find_by(title: label_name) - label ||= project.labels.create(title: label_name.strip, color: Label::DEFAULT_COLOR) + service = Labels::CreateService.new(current_user, project, title: label_name.strip) + label = service.execute label.id end diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index 559d2860d97..65b4bdbaff9 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -41,13 +41,9 @@ module Labels LabelLink.where("label_links.id IN (#{union.to_sql})") end - def labels - @labels ||= LabelsFinder.new(current_user, project_id: project.id).execute - end - def find_or_create_label!(label) - new_label = labels.find_by(title: label.title) - new_label ||= project.labels.create!(label.attributes.slice("title", "description", "color")) + params = label.attributes.slice('title', 'description', 'color') + new_label = CreateService.new(current_user, project, params).execute new_label.id end diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index 9e926e2681a..f154ee689cf 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -74,8 +74,8 @@ module Gitlab end def create_label(name) - color = nice_label_color(name) - Label.create!(project_id: project.id, title: name, color: color) + params = { title: name, color: nice_label_color(name) } + ::Labels::CreateService.new(project.owner, project, params).execute end def user_info(person_id) diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb index 2cad7fca88e..3101116a614 100644 --- a/lib/gitlab/github_import/label_formatter.rb +++ b/lib/gitlab/github_import/label_formatter.rb @@ -14,9 +14,10 @@ module Gitlab end def create! - project.labels.find_or_create_by!(title: title) do |label| - label.color = color - end + params = attributes.except(:project) + service = ::Labels::CreateService.new(project.owner, project, params) + + service.execute end private diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 79a0eaba1e8..904a228aeef 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -233,8 +233,8 @@ module Gitlab end def create_label(name) - color = nice_label_color(name) - project.labels.create!(name: name, color: color) + params = { name: name, color: nice_label_color(name) } + ::Labels::CreateService.new(project.owner, project, params).execute end def format_content(raw_content) diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index 1bec6088292..6788eca7146 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -18,8 +18,8 @@ module Gitlab { title: "enhancement", color: green } ] - labels.each do |label| - project.labels.create(label) + labels.each do |params| + ::Labels::CreateService.new(project.owner, project).execute(params) end end end -- cgit v1.2.1 From 771d3fc3cbaa2b473c87d23fc1c067fb9bad6206 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 00:56:05 -0200 Subject: Split migration to create label priorities --- .../20161014173530_create_label_priorities.rb | 32 +------------------ .../20161018024215_migrate_labels_priority.rb | 36 ++++++++++++++++++++++ .../20161018024550_remove_priority_from_labels.rb | 17 ++++++++++ db/schema.rb | 2 +- 4 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 db/migrate/20161018024215_migrate_labels_priority.rb create mode 100644 db/migrate/20161018024550_remove_priority_from_labels.rb diff --git a/db/migrate/20161014173530_create_label_priorities.rb b/db/migrate/20161014173530_create_label_priorities.rb index f9d94ebdc70..2c22841c28a 100644 --- a/db/migrate/20161014173530_create_label_priorities.rb +++ b/db/migrate/20161014173530_create_label_priorities.rb @@ -2,7 +2,7 @@ class CreateLabelPriorities < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers DOWNTIME = true - DOWNTIME_REASON = 'Prioritezed labels will not work as expected until this migration is complete.' + DOWNTIME_REASON = 'This migration adds foreign keys' disable_ddl_transaction! @@ -15,41 +15,11 @@ class CreateLabelPriorities < ActiveRecord::Migration t.timestamps null: false end - execute <<-EOF.strip_heredoc - INSERT INTO label_priorities (project_id, label_id, priority, created_at, updated_at) - SELECT labels.project_id, labels.id, labels.priority, NOW(), NOW() - FROM labels - WHERE labels.project_id IS NOT NULL - AND labels.priority IS NOT NULL; - EOF - add_concurrent_index :label_priorities, [:project_id, :label_id], unique: true add_concurrent_index :label_priorities, :priority - - remove_column :labels, :priority end def down - add_column :labels, :priority, :integer - - if Gitlab::Database.mysql? - execute <<-EOF.strip_heredoc - UPDATE labels - INNER JOIN label_priorities ON labels.id = label_priorities.label_id AND labels.project_id = label_priorities.project_id - SET labels.priority = label_priorities.priority; - EOF - else - execute <<-EOF.strip_heredoc - UPDATE labels - SET priority = label_priorities.priority - FROM label_priorities - WHERE labels.id = label_priorities.label_id - AND labels.project_id = label_priorities.project_id; - EOF - end - - add_concurrent_index :labels, :priority - drop_table :label_priorities end end diff --git a/db/migrate/20161018024215_migrate_labels_priority.rb b/db/migrate/20161018024215_migrate_labels_priority.rb new file mode 100644 index 00000000000..22bec2382f4 --- /dev/null +++ b/db/migrate/20161018024215_migrate_labels_priority.rb @@ -0,0 +1,36 @@ +class MigrateLabelsPriority < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'Prioritized labels will not work as expected until this migration is complete.' + + disable_ddl_transaction! + + def up + execute <<-EOF.strip_heredoc + INSERT INTO label_priorities (project_id, label_id, priority, created_at, updated_at) + SELECT labels.project_id, labels.id, labels.priority, NOW(), NOW() + FROM labels + WHERE labels.project_id IS NOT NULL + AND labels.priority IS NOT NULL; + EOF + end + + def down + if Gitlab::Database.mysql? + execute <<-EOF.strip_heredoc + UPDATE labels + INNER JOIN label_priorities ON labels.id = label_priorities.label_id AND labels.project_id = label_priorities.project_id + SET labels.priority = label_priorities.priority; + EOF + else + execute <<-EOF.strip_heredoc + UPDATE labels + SET priority = label_priorities.priority + FROM label_priorities + WHERE labels.id = label_priorities.label_id + AND labels.project_id = label_priorities.project_id; + EOF + end + end +end diff --git a/db/migrate/20161018024550_remove_priority_from_labels.rb b/db/migrate/20161018024550_remove_priority_from_labels.rb new file mode 100644 index 00000000000..b7416cca664 --- /dev/null +++ b/db/migrate/20161018024550_remove_priority_from_labels.rb @@ -0,0 +1,17 @@ +class RemovePriorityFromLabels < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'This migration removes an existing column' + + disable_ddl_transaction! + + def up + remove_column :labels, :priority, :integer, index: true + end + + def down + add_column :labels, :priority, :integer + add_concurrent_index :labels, :priority + end +end diff --git a/db/schema.rb b/db/schema.rb index f9353290abf..65f55aa109b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161017125927) do +ActiveRecord::Schema.define(version: 20161018024550) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 007267ef8b927646bab0932b5704ff0f3720253f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 01:00:45 -0200 Subject: Use `includes(:priorities)` on Projects::LabelsController --- app/controllers/projects/labels_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 42fd09e9b7e..4f855134368 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -126,7 +126,7 @@ class Projects::LabelsController < Projects::ApplicationController alias_method :subscribable_resource, :label def find_labels - @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute + @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute.includes(:priorities) end def authorize_admin_labels! -- cgit v1.2.1 From 4c9241075dc1b2f1cda5648cf9ad1f553db3d03b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 01:32:00 -0200 Subject: Warn user deleting a group label affect all projects within the group --- app/helpers/labels_helper.rb | 7 +++++++ app/views/shared/_label.html.haml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index a1d7713b45f..d7cfd24c918 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -155,6 +155,13 @@ module LabelsHelper label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' end + def label_deletion_confirm_text(label) + case label + when GroupLabel then 'Remove this label? This will affect all projects within the group. Are you sure?' + when ProjectLabel then 'Remove this label? Are you sure?' + end + end + # Required for Banzai::Filter::LabelReferenceFilter module_function :render_colored_label, :render_colored_cross_project_label, :text_color_for_bg, :escape_once diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index ba8a3efccda..5cdd18d24f0 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -44,7 +44,7 @@ = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do %span.sr-only Edit = icon('pencil-square-o') - = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?", toggle: "tooltip"} do + = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do %span.sr-only Delete = icon('trash-o') -- cgit v1.2.1 From f99744d00de10094d6e483776313c52d86437a9d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 04:00:33 -0200 Subject: Use join instead of subquery on Label.unprioritized scope --- app/models/label.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/label.rb b/app/models/label.rb index ae07e8f60e1..149fd98ecb3 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -39,7 +39,14 @@ class Label < ActiveRecord::Base end def self.unprioritized(project) - where.not(id: prioritized(project).select(:id)) + labels = Label.arel_table + priorities = LabelPriority.arel_table + + label_priorities = labels.join(priorities, Arel::Nodes::OuterJoin). + on(labels[:id].eq(priorities[:label_id]).and(priorities[:project_id].eq(project.id))). + join_sources + + joins(label_priorities).where(priorities[:priority].eq(nil)) end def self.left_join_priorities -- cgit v1.2.1 From a9938e227bfb0ee1908beb9238bb95fece72805e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 04:30:43 -0200 Subject: Add support to group labels to SlashCommands::InterpretService --- app/finders/labels_finder.rb | 2 +- app/services/slash_commands/interpret_service.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 65db4184ecf..a5802171b6c 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -52,7 +52,7 @@ class LabelsFinder < UnionFinder end def title - params[:title].presence + params[:title].presence || params[:name].presence end def project diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index e4ae3dec8aa..5a81194a5f4 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -116,8 +116,10 @@ module SlashCommands desc 'Add label(s)' params '~label1 ~"label 2"' condition do + available_labels = LabelsFinder.new(current_user, project_id: project.id).execute + current_user.can?(:"admin_#{issuable.to_ability_name}", project) && - project.labels.any? + available_labels.any? end command :label do |labels_param| label_ids = find_label_ids(labels_param) @@ -248,7 +250,7 @@ module SlashCommands def find_label_ids(labels_param) label_ids_by_reference = extract_references(labels_param, :label).map(&:id) - labels_ids_by_name = @project.labels.where(name: labels_param.split).select(:id) + labels_ids_by_name = LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute.select(:id) label_ids_by_reference | labels_ids_by_name end -- cgit v1.2.1 From 1a41a89cb383d286e21a125e8a643eb0fbb2442b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 04:18:54 -0200 Subject: Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c866696889e..dd37b0a6a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup + - Add group level labels. (!6425) - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - Cancelled pipelines could be retried. !6927 - Updating verbiage on git basics to be more intuitive -- cgit v1.2.1 From f0ad0ceff5236f3ee5babee47bfec217a54c3b07 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 06:26:16 -0200 Subject: Fix GitHub importer spec --- lib/gitlab/github_import/label_formatter.rb | 5 ++++- spec/lib/gitlab/github_import/importer_spec.rb | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb index 3101116a614..8ed1574c4fc 100644 --- a/lib/gitlab/github_import/label_formatter.rb +++ b/lib/gitlab/github_import/label_formatter.rb @@ -16,8 +16,11 @@ module Gitlab def create! params = attributes.except(:project) service = ::Labels::CreateService.new(project.owner, project, params) + label = service.execute - service.execute + raise ActiveRecord::RecordInvalid.new(label) unless label.persisted? + + label end private diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb index 8854c8431b5..1af553f8f03 100644 --- a/spec/lib/gitlab/github_import/importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -157,7 +157,7 @@ describe Gitlab::GithubImport::Importer, lib: true do { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" }, { type: :wiki, errors: "Gitlab::Shell::Error" }, { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" } - ] + ] } described_class.new(project).execute -- cgit v1.2.1 From 2f7260b460f2a893241039115a201c2522fb47ca Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 09:46:11 -0200 Subject: Fix max number of permitted priorities per project label --- app/models/project_label.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project_label.rb b/app/models/project_label.rb index 0a79521ed09..33c2b617715 100644 --- a/app/models/project_label.rb +++ b/app/models/project_label.rb @@ -1,5 +1,5 @@ class ProjectLabel < Label - NUMBER_OF_PRIORITIES = 1 + MAX_NUMBER_OF_PRIORITIES = 1 belongs_to :project @@ -27,7 +27,7 @@ class ProjectLabel < Label end def permitted_numbers_of_priorities - if priorities && priorities.size >= NUMBER_OF_PRIORITIES + if priorities && priorities.size > MAX_NUMBER_OF_PRIORITIES errors.add(:priorities, 'Number of permitted priorities exceeded') end end -- cgit v1.2.1 From 891e5f4851c2067daba12a1750651170a1583481 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 18 Oct 2016 19:31:10 +0200 Subject: Update specs to cope with new label types and priorities Fixed all related specs and also changed the logic to handle edge cases. This includes exporting and exporting of group labels, which will get associated with the new group (if any) or they will become normal project labels otherwise. Found other issues to do with not being able to import all labels at once in the beginning of the JSON - code was much simpler when we import all labels and milestones associated to a project first, then the associations will find the already created labels instead of creating them from the associations themselves. --- app/models/project.rb | 4 +++ lib/gitlab/import_export/attribute_cleaner.rb | 2 +- lib/gitlab/import_export/import_export.yml | 9 ++++-- lib/gitlab/import_export/json_hash_builder.rb | 6 ++++ lib/gitlab/import_export/relation_factory.rb | 22 +++++++++----- .../import_export/test_project_export.tar.gz | Bin 680856 -> 681774 bytes spec/lib/gitlab/import_export/all_models.yml | 3 ++ spec/lib/gitlab/import_export/project.json | 33 ++++++++++++++++----- .../import_export/project_tree_restorer_spec.rb | 6 ++++ .../import_export/project_tree_saver_spec.rb | 9 +++++- .../gitlab/import_export/safe_model_attributes.yml | 9 +++++- 11 files changed, 82 insertions(+), 21 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index bc15ca3fc2e..94105a8ea79 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -738,6 +738,10 @@ class Project < ActiveRecord::Base end end + def all_labels + Label.find_by_project_id(self.id) + end + def find_service(list, name) list.find { |service| service.to_param == name } end diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb index b9e4042220a..f755a404693 100644 --- a/lib/gitlab/import_export/attribute_cleaner.rb +++ b/lib/gitlab/import_export/attribute_cleaner.rb @@ -1,7 +1,7 @@ module Gitlab module ImportExport class AttributeCleaner - ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id'] def self.clean!(relation_hash:) relation_hash.reject! do |key, _value| diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 8882f146632..e6ecd118609 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -1,6 +1,7 @@ # Model relationships to be included in the project import/export project_tree: - - :labels + - labels: + :priorities - milestones: - :events - issues: @@ -9,7 +10,8 @@ project_tree: - :author - :events - label_links: - - :label + - label: + :priorities - milestone: - :events - snippets: @@ -26,7 +28,8 @@ project_tree: - :merge_request_diff - :events - label_links: - - :label + - label: + :priorities - milestone: - :events - pipelines: diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb index 0cc10f40087..48c09dafcb6 100644 --- a/lib/gitlab/import_export/json_hash_builder.rb +++ b/lib/gitlab/import_export/json_hash_builder.rb @@ -65,11 +65,17 @@ module Gitlab # +value+ existing model to be included in the hash # +parsed_hash+ the original hash def parse_hash(value) + return nil if already_contains_methods?(value) + @attributes_finder.parse(value) do |hash| { include: hash_or_merge(value, hash) } end end + def already_contains_methods?(value) + value.is_a?(Hash) && value.values.detect { |val| val[:methods]} + end + # Adds new model configuration to an existing hash with key +current_key+ # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+ # diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 8bc4ab85c18..dc630e76411 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -11,6 +11,7 @@ module Gitlab merge_access_levels: 'ProtectedBranch::MergeAccessLevel', push_access_levels: 'ProtectedBranch::PushAccessLevel', labels: :project_labels, + priorities: :label_priorities, label: :project_label }.freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze @@ -23,8 +24,6 @@ module Gitlab EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels project_label group_label].freeze - FINDER_ATTRIBUTES = %w[title project_id].freeze - def self.create(*args) new(*args).create end @@ -134,6 +133,7 @@ module Gitlab def handle_group_label # If there's no group, move the label to a project label if @relation_hash['group_id'] + @relation_hash['project_id'] = nil @relation_name = :group_label else @relation_hash['type'] = 'ProjectLabel' @@ -188,11 +188,9 @@ module Gitlab # Otherwise always create the record, skipping the extra SELECT clause. @existing_or_new_object ||= begin if EXISTING_OBJECT_CHECK.include?(@relation_name) - events = parsed_relation_hash.delete('events') + attribute_hash = attribute_hash_for(['events', 'priorities']) - unless events.blank? - existing_object.assign_attributes(events: events) - end + existing_object.assign_attributes(attribute_hash) if attribute_hash.any? existing_object else @@ -201,14 +199,22 @@ module Gitlab end end + def attribute_hash_for(attributes) + attributes.inject({}) do |hash, value| + hash[value] = parsed_relation_hash.delete(value) if parsed_relation_hash[value] + hash + end + end + def existing_object @existing_object ||= begin - finder_hash = parsed_relation_hash.slice(*FINDER_ATTRIBUTES) + finder_attributes = @relation_name == :group_label ? %w[title group_id] : %w[title project_id] + finder_hash = parsed_relation_hash.slice(*finder_attributes) existing_object = relation_class.find_or_create_by(finder_hash) # Done in two steps, as MySQL behaves differently than PostgreSQL using # the +find_or_create_by+ method and does not return the ID the second time. - existing_object.update(parsed_relation_hash) + existing_object.update!(parsed_relation_hash) existing_object end end diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz index 8f683cf89aa..bfe59bdb90e 100644 Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8fcbf12eab8..02b11bd999a 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -38,6 +38,7 @@ label: - label_links - issues - merge_requests +- priorities milestone: - project - issues @@ -186,3 +187,5 @@ project: award_emoji: - awardable - user +priorities: +- label \ No newline at end of file diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index bf9dc279f7d..ed9df468ced 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -2,6 +2,21 @@ "description": "Nisi et repellendus ut enim quo accusamus vel magnam.", "visibility_level": 10, "archived": false, + "labels": [ + { + "id": 2, + "title": "test2", + "color": "#428bca", + "project_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "type": "ProjectLabel", + "priorities": [ + ] + } + ], "issues": [ { "id": 40, @@ -64,7 +79,6 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null, "type": "ProjectLabel" } }, @@ -84,9 +98,18 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null, "project_id": null, - "type": "GroupLabel" + "type": "GroupLabel", + "priorities": [ + { + "id": 1, + "project_id": 5, + "label_id": 1, + "priority": 1, + "created_at": "2016-10-18T09:35:43.338Z", + "updated_at": "2016-10-18T09:35:43.338Z" + } + ] } } ], @@ -558,7 +581,6 @@ "updated_at": "2016-07-22T08:55:44.161Z", "template": false, "description": "", - "priority": null, "type": "ProjectLabel" } } @@ -2249,9 +2271,6 @@ } ] } - ], - "labels": [ - ], "milestones": [ { diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 6312b990a66..069ea960321 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -134,6 +134,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(GroupLabel.count).to eq(1) end + + it 'has label priorities' do + restored_project_json + + expect(GroupLabel.first.priorities).not_to be_empty + end end it 'has a project feature' do diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 9a8ba61559b..c8bba553558 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -114,7 +114,13 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do it 'has project and group labels' do label_types = saved_project_json['issues'].first['label_links'].map { |link| link['label']['type']} - expect(label_types).to match(['ProjectLabel', 'GroupLabel']) + expect(label_types).to match_array(['ProjectLabel', 'GroupLabel']) + end + + it 'has priorities associated to labels' do + priorities = saved_project_json['issues'].first['label_links'].map { |link| link['label']['priorities']} + + expect(priorities.flatten).not_to be_empty end it 'saves the correct service type' do @@ -154,6 +160,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do group_label = create(:group_label, group: group) create(:label_link, label: project_label, target: issue) create(:label_link, label: group_label, target: issue) + create(:label_priority, label: group_label, priority: 1) milestone = create(:milestone, project: project) merge_request = create(:merge_request, source_project: project, milestone: milestone) commit_status = create(:commit_status, project: project) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 26049914bac..feee0f025d8 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -60,7 +60,7 @@ LabelLink: - target_type - created_at - updated_at -Label: +ProjectLabel: - id - title - color @@ -331,3 +331,10 @@ AwardEmoji: - awardable_type - created_at - updated_at +LabelPriority: +- id +- project_id +- label_id +- priority +- created_at +- updated_at \ No newline at end of file -- cgit v1.2.1 From aa78148901cd3877936bc2afcea9c329077bf951 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 18 Oct 2016 17:00:43 -0200 Subject: Remove unused method Project#all_labels --- app/models/project.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 94105a8ea79..bc15ca3fc2e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -738,10 +738,6 @@ class Project < ActiveRecord::Base end end - def all_labels - Label.find_by_project_id(self.id) - end - def find_service(list, name) list.find { |service| service.to_param == name } end -- cgit v1.2.1 From 355389d065216739a2b8e8150a1a569c410f4ff6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 11:31:08 -0200 Subject: Disable subscribing to group-level labels --- app/assets/stylesheets/pages/labels.scss | 7 +++++++ app/controllers/groups/labels_controller.rb | 5 +---- app/helpers/labels_helper.rb | 21 ++++++++++++++------- app/views/shared/_label.html.haml | 8 ++++---- config/routes/group.rb | 6 +----- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 2f7f7325877..397f89f501a 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -223,6 +223,13 @@ } .label-subscribe-button { + .label-subscribe-button-icon { + &[disabled] { + opacity: 0.5; + pointer-events: none; + } + } + .label-subscribe-button-loading { display: none; } diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 0a3dee5ce39..29528b2cfaa 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -1,7 +1,5 @@ class Groups::LabelsController < Groups::ApplicationController - include ToggleSubscriptionAction - - before_action :label, only: [:edit, :update, :destroy, :toggle_subscription] + before_action :label, only: [:edit, :update, :destroy] before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] before_action :save_previous_label_path, only: [:edit] @@ -71,7 +69,6 @@ class Groups::LabelsController < Groups::ApplicationController def label @label ||= @group.labels.find(params[:id]) end - alias_method :subscribable_resource, :label def label_params params.require(:label).permit(:title, :description, :color) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index d7cfd24c918..221a84b042f 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -68,11 +68,12 @@ module LabelsHelper end end - def toggle_subscription_label_path(label) - case label - when GroupLabel then toggle_subscription_group_label_path(label.group, label) - when ProjectLabel then toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) - end + def toggle_subscription_data(label) + return unless label.is_a?(ProjectLabel) + + { + url: toggle_subscription_namespace_project_label_path(label.project.namespace, label.project, label) + } end def render_colored_label(label, label_suffix = '', tooltip: true) @@ -148,11 +149,17 @@ module LabelsHelper end def label_subscription_status(label) - label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed' + case label + when GroupLabel then 'Subscribing to group labels is currently not supported.' + when ProjectLabel then label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed' + end end def label_subscription_toggle_button_text(label) - label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' + case label + when GroupLabel then 'Subscribing to group labels is currently not supported.' + when ProjectLabel then label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' + end end def label_deletion_confirm_text(label) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 5cdd18d24f0..40c8d2af226 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -18,7 +18,7 @@ = link_to_label(label, subject: @project) do = pluralize open_issues_count, 'open issue' - if current_user - %li.label-subscription{ data: { url: toggle_subscription_label_path(label) } } + %li.label-subscription{ data: toggle_subscription_data(label) } %a.js-subscribe-button.label-subscribe-button.subscription-status{ role: "button", href: "#", data: { toggle: "tooltip", status: label_subscription_status(label) } } %span= label_subscription_toggle_button_text(label) - if can?(current_user, :admin_label, label) @@ -34,10 +34,10 @@ = pluralize open_issues_count, 'open issue' - if current_user - .label-subscription.inline{ data: { url: toggle_subscription_label_path(label) } } + .label-subscription.inline{ data: toggle_subscription_data(label) } %button.js-subscribe-button.label-subscribe-button.btn.btn-transparent.btn-action.subscription-status{ type: "button", title: label_subscription_toggle_button_text(label), data: { toggle: "tooltip", status: label_subscription_status(label) } } %span.sr-only= label_subscription_toggle_button_text(label) - = icon('eye', class: 'label-subscribe-button-icon') + = icon('eye', class: 'label-subscribe-button-icon', disabled: label.is_a?(GroupLabel)) = icon('spinner spin', class: 'label-subscribe-button-loading') - if can?(current_user, :admin_label, label) @@ -48,6 +48,6 @@ %span.sr-only Delete = icon('trash-o') - - if current_user + - if current_user && label.is_a?(ProjectLabel) :javascript new Subscription('##{dom_id(label)} .label-subscription'); diff --git a/config/routes/group.rb b/config/routes/group.rb index 7bb9aa50875..4838c9d91c6 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -29,10 +29,6 @@ resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(? Date: Wed, 19 Oct 2016 11:37:10 -0200 Subject: Only show label type for projects that belong to a group --- app/views/shared/_label_row.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index 76c3327fefc..d28f9421ecf 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -10,8 +10,9 @@ = icon('star') %span.label-name = link_to_label(label, subject: @project, tooltip: false) - %span.label-type - = label.model_name.human.titleize + - if defined?(@project) && @project.group.present? + %span.label-type + = label.model_name.human.titleize - if label.description %span.label-description = markdown_field(label, :description) -- cgit v1.2.1 From fc2c64fcdf913a37f987ab5e5626ef9bb9e8b854 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 11:49:07 -0200 Subject: Add self.project_foreign_key on both Issue and MergeRequest --- app/models/concerns/issuable.rb | 8 +------- app/models/issue.rb | 4 ++++ app/models/merge_request.rb | 4 ++++ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 9a9b562af02..17c3b526c97 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -145,16 +145,10 @@ module Issuable end def order_labels_priority(excluded_labels: []) - project_column = - case table_name - when Issue.table_name then "#{table_name}.project_id" - when MergeRequest.table_name then "#{table_name}.target_project_id" - end - params = { target_type: name, target_column: "#{table_name}.id", - project_column: project_column, + project_column: "#{table_name}.#{project_foreign_key}", excluded_labels: excluded_labels } diff --git a/app/models/issue.rb b/app/models/issue.rb index f7ccce2924a..133a5993815 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -138,6 +138,10 @@ class Issue < ActiveRecord::Base reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE end + def self.project_foreign_key + 'project_id' + end + def self.sort(method, excluded_labels: []) case method.to_s when 'due_date_asc' then order_due_date_asc diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index fedc35102ef..0cc0b3c2a0e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -137,6 +137,10 @@ class MergeRequest < ActiveRecord::Base reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE end + def self.project_foreign_key + 'target_project_id' + end + # Returns all the merge requests from an ActiveRecord:Relation. # # This method uses a UNION as it usually operates on the result of -- cgit v1.2.1 From 4f6d1c1d70d744ff599ae9c51e1cbc3a3c23e13e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 11:53:31 -0200 Subject: Rename Labels::CreateService to Labels::FindOrCreateService --- app/models/project.rb | 2 +- app/services/boards/lists/generate_service.rb | 2 +- app/services/issuable_base_service.rb | 2 +- app/services/labels/create_service.rb | 33 -------------- app/services/labels/find_or_create_service.rb | 33 ++++++++++++++ app/services/labels/transfer_service.rb | 2 +- lib/gitlab/fogbugz_import/importer.rb | 2 +- lib/gitlab/github_import/label_formatter.rb | 2 +- lib/gitlab/google_code_import/importer.rb | 2 +- lib/gitlab/issues_labels.rb | 2 +- spec/services/labels/create_service_spec.rb | 51 ---------------------- .../services/labels/find_or_create_service_spec.rb | 51 ++++++++++++++++++++++ 12 files changed, 92 insertions(+), 92 deletions(-) delete mode 100644 app/services/labels/create_service.rb create mode 100644 app/services/labels/find_or_create_service.rb delete mode 100644 spec/services/labels/create_service_spec.rb create mode 100644 spec/services/labels/find_or_create_service_spec.rb diff --git a/app/models/project.rb b/app/models/project.rb index bc15ca3fc2e..a6039bb8cc4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -734,7 +734,7 @@ class Project < ActiveRecord::Base def create_labels Label.templates.each do |label| params = label.attributes.except('id', 'template', 'created_at', 'updated_at') - Labels::CreateService.new(owner, self, params).execute + Labels::FindOrCreateService.new(owner, self, params).execute end end diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 1d3c7f2071b..939f9bfd068 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -19,7 +19,7 @@ module Boards end def find_or_create_label(params) - ::Labels::CreateService.new(current_user, project, params).execute + ::Labels::FindOrCreateService.new(current_user, project, params).execute end def label_params diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 4554963370f..bb92cd80cc9 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -88,7 +88,7 @@ class IssuableBaseService < BaseService return unless labels params[:label_ids] = labels.split(',').map do |label_name| - service = Labels::CreateService.new(current_user, project, title: label_name.strip) + service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip) label = service.execute label.id diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb deleted file mode 100644 index bb475ce741d..00000000000 --- a/app/services/labels/create_service.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Labels - class CreateService - def initialize(current_user, project, params = {}) - @current_user = current_user - @group = project.group - @project = project - @params = params.dup - end - - def execute - find_or_create_label - end - - private - - attr_reader :current_user, :group, :project, :params - - def available_labels - @available_labels ||= LabelsFinder.new(current_user, project_id: project.id).execute - end - - def find_or_create_label - new_label = available_labels.find_by(title: title) - new_label ||= project.labels.create(params) - - new_label - end - - def title - params[:title] || params[:name] - end - end -end diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb new file mode 100644 index 00000000000..74291312c4e --- /dev/null +++ b/app/services/labels/find_or_create_service.rb @@ -0,0 +1,33 @@ +module Labels + class FindOrCreateService + def initialize(current_user, project, params = {}) + @current_user = current_user + @group = project.group + @project = project + @params = params.dup + end + + def execute + find_or_create_label + end + + private + + attr_reader :current_user, :group, :project, :params + + def available_labels + @available_labels ||= LabelsFinder.new(current_user, project_id: project.id).execute + end + + def find_or_create_label + new_label = available_labels.find_by(title: title) + new_label ||= project.labels.create(params) + + new_label + end + + def title + params[:title] || params[:name] + end + end +end diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index 65b4bdbaff9..f91b3724aef 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -43,7 +43,7 @@ module Labels def find_or_create_label!(label) params = label.attributes.slice('title', 'description', 'color') - new_label = CreateService.new(current_user, project, params).execute + new_label = FindOrCreateService.new(current_user, project, params).execute new_label.id end diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index f154ee689cf..ce4d87a0741 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -75,7 +75,7 @@ module Gitlab def create_label(name) params = { title: name, color: nice_label_color(name) } - ::Labels::CreateService.new(project.owner, project, params).execute + ::Labels::FindOrCreateService.new(project.owner, project, params).execute end def user_info(person_id) diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb index 8ed1574c4fc..942dfb3312b 100644 --- a/lib/gitlab/github_import/label_formatter.rb +++ b/lib/gitlab/github_import/label_formatter.rb @@ -15,7 +15,7 @@ module Gitlab def create! params = attributes.except(:project) - service = ::Labels::CreateService.new(project.owner, project, params) + service = ::Labels::FindOrCreateService.new(project.owner, project, params) label = service.execute raise ActiveRecord::RecordInvalid.new(label) unless label.persisted? diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 904a228aeef..b16a5654096 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -234,7 +234,7 @@ module Gitlab def create_label(name) params = { name: name, color: nice_label_color(name) } - ::Labels::CreateService.new(project.owner, project, params).execute + ::Labels::FindOrCreateService.new(project.owner, project, params).execute end def format_content(raw_content) diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index 6788eca7146..01a2c19ab23 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -19,7 +19,7 @@ module Gitlab ] labels.each do |params| - ::Labels::CreateService.new(project.owner, project).execute(params) + ::Labels::FindOrCreateService.new(project.owner, project).execute(params) end end end diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb deleted file mode 100644 index 1e4bc294b46..00000000000 --- a/spec/services/labels/create_service_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'spec_helper' - -describe Labels::CreateService, services: true do - describe '#execute' do - let(:user) { create(:user) } - let(:group) { create(:group) } - let(:project) { create(:project, namespace: group) } - - let(:params) do - { - title: 'Security', - description: 'Security related stuff.', - color: '#FF0000' - } - end - - subject(:service) { described_class.new(user, project, params) } - - before do - project.team << [user, :developer] - end - - context 'when label does not exist at group level' do - it 'creates a new label at project level' do - expect { service.execute }.to change(project.labels, :count).by(1) - end - end - - context 'when label exists at group level' do - it 'returns the group label' do - group_label = create(:group_label, group: group, title: 'Security') - - expect(service.execute).to eq group_label - end - end - - context 'when label does not exist at group level' do - it 'creates a new label at project leve' do - expect { service.execute }.to change(project.labels, :count).by(1) - end - end - - context 'when label exists at project level' do - it 'returns the project label' do - project_label = create(:label, project: project, title: 'Security') - - expect(service.execute).to eq project_label - end - end - end -end diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb new file mode 100644 index 00000000000..cbfc63de811 --- /dev/null +++ b/spec/services/labels/find_or_create_service_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Labels::FindOrCreateService, services: true do + describe '#execute' do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + + let(:params) do + { + title: 'Security', + description: 'Security related stuff.', + color: '#FF0000' + } + end + + subject(:service) { described_class.new(user, project, params) } + + before do + project.team << [user, :developer] + end + + context 'when label does not exist at group level' do + it 'creates a new label at project level' do + expect { service.execute }.to change(project.labels, :count).by(1) + end + end + + context 'when label exists at group level' do + it 'returns the group label' do + group_label = create(:group_label, group: group, title: 'Security') + + expect(service.execute).to eq group_label + end + end + + context 'when label does not exist at group level' do + it 'creates a new label at project leve' do + expect { service.execute }.to change(project.labels, :count).by(1) + end + end + + context 'when label exists at project level' do + it 'returns the project label' do + project_label = create(:label, project: project, title: 'Security') + + expect(service.execute).to eq project_label + end + end + end +end -- cgit v1.2.1 From e6957a6b4776c47e7f21bd7494e4efafa63501ca Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 11:59:31 -0200 Subject: Remove order by label type on LabelsFinder --- app/finders/labels_finder.rb | 2 +- app/views/shared/issuable/_filter.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index a5802171b6c..6ace14a4bb5 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -31,7 +31,7 @@ class LabelsFinder < UnionFinder end def sort(items) - items.reorder(title: :asc, type: :desc) + items.reorder(title: :asc) end def with_title(items) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index f8018fc3de0..8c2036a1cde 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -27,7 +27,7 @@ = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true .filter-item.inline.labels-filter - = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title, :type).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } + = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } .filter-item.inline.reset-filters %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters -- cgit v1.2.1 From 1d8b74fee34af0f13e69a3363417493746279488 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 14:47:17 -0200 Subject: Avoid touch label links that does not belongs to project when moving it --- app/services/labels/transfer_service.rb | 57 ++++++++++++++++++++------- spec/services/labels/transfer_service_spec.rb | 29 ++++++++------ 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb index f91b3724aef..514679ed29d 100644 --- a/app/services/labels/transfer_service.rb +++ b/app/services/labels/transfer_service.rb @@ -4,48 +4,75 @@ # module Labels class TransferService - def initialize(current_user, group, project) + def initialize(current_user, old_group, project) @current_user = current_user - @group = group + @old_group = old_group @project = project end def execute - return unless group.present? + return unless old_group.present? Label.transaction do - labels_to_transfer = Label.where(id: label_links.select(:label_id)) - labels_to_transfer.find_each do |label| new_label_id = find_or_create_label!(label) next if new_label_id == label.id - LabelLink.where(label_id: label.id).update_all(label_id: new_label_id) - LabelPriority.where(project_id: project.id, label_id: label.id).update_all(label_id: new_label_id) + update_label_links(group_labels_applied_to_issues, old_label_id: label.id, new_label_id: new_label_id) + update_label_links(group_labels_applied_to_merge_requests, old_label_id: label.id, new_label_id: new_label_id) + update_label_priorities(old_label_id: label.id, new_label_id: new_label_id) end end end private - attr_reader :current_user, :group, :project + attr_reader :current_user, :old_group, :project + + def labels_to_transfer + label_ids = [] + label_ids << group_labels_applied_to_issues.select(:id) + label_ids << group_labels_applied_to_merge_requests.select(:id) - def label_links - label_link_ids = [] - label_link_ids << LabelLink.where(target: project.issues, label: group.labels).select(:id) - label_link_ids << LabelLink.where(target: project.merge_requests, label: group.labels).select(:id) + union = Gitlab::SQL::Union.new(label_ids) - union = Gitlab::SQL::Union.new(label_link_ids) + Label.where("labels.id IN (#{union.to_sql})").reorder(nil).uniq + end + + def group_labels_applied_to_issues + Label.joins(:issues). + where( + issues: { project_id: project.id }, + labels: { type: 'GroupLabel', group_id: old_group.id } + ) + end - LabelLink.where("label_links.id IN (#{union.to_sql})") + def group_labels_applied_to_merge_requests + Label.joins(:merge_requests). + where( + merge_requests: { target_project_id: project.id }, + labels: { type: 'GroupLabel', group_id: old_group.id } + ) end def find_or_create_label!(label) - params = label.attributes.slice('title', 'description', 'color') + params = label.attributes.slice('title', 'description', 'color') new_label = FindOrCreateService.new(current_user, project, params).execute new_label.id end + + def update_label_links(labels, old_label_id:, new_label_id:) + LabelLink.joins(:label). + merge(labels). + where(label_id: old_label_id). + update_all(label_id: new_label_id) + end + + def update_label_priorities(old_label_id:, new_label_id:) + LabelPriority.where(project_id: project.id, label_id: old_label_id). + update_all(label_id: new_label_id) + end end end diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb index cb09c16698a..ddf3527dc0f 100644 --- a/spec/services/labels/transfer_service_spec.rb +++ b/spec/services/labels/transfer_service_spec.rb @@ -5,33 +5,38 @@ describe Labels::TransferService, services: true do let(:user) { create(:user) } let(:group_1) { create(:group) } let(:group_2) { create(:group) } - let(:project) { create(:project, namespace: group_2) } + let(:group_3) { create(:group) } + let(:project_1) { create(:project, namespace: group_2) } + let(:project_2) { create(:project, namespace: group_3) } let(:group_label_1) { create(:group_label, group: group_1, name: 'Group Label 1') } let(:group_label_2) { create(:group_label, group: group_1, name: 'Group Label 2') } let(:group_label_3) { create(:group_label, group: group_1, name: 'Group Label 3') } let(:group_label_4) { create(:group_label, group: group_2, name: 'Group Label 4') } - let(:project_label_1) { create(:label, project: project, name: 'Project Label 1') } + let(:group_label_5) { create(:group_label, group: group_3, name: 'Group Label 5') } + let(:project_label_1) { create(:label, project: project_1, name: 'Project Label 1') } - subject(:service) { described_class.new(user, group_1, project) } + subject(:service) { described_class.new(user, group_1, project_1) } before do - create(:labeled_issue, project: project, labels: [group_label_1]) - create(:labeled_issue, project: project, labels: [group_label_4]) - create(:labeled_issue, project: project, labels: [project_label_1]) - create(:labeled_merge_request, source_project: project, labels: [group_label_1, group_label_2]) + create(:labeled_issue, project: project_1, labels: [group_label_1]) + create(:labeled_issue, project: project_1, labels: [group_label_4]) + create(:labeled_issue, project: project_1, labels: [project_label_1]) + create(:labeled_issue, project: project_2, labels: [group_label_5]) + create(:labeled_merge_request, source_project: project_1, labels: [group_label_1, group_label_2]) + create(:labeled_merge_request, source_project: project_2, labels: [group_label_5]) end it 'recreates the missing group labels at project level' do - expect { service.execute }.to change(project.labels, :count).by(2) + expect { service.execute }.to change(project_1.labels, :count).by(2) end it 'recreates label priorities related to the missing group labels' do - create(:label_priority, project: project, label: group_label_1, priority: 1) + create(:label_priority, project: project_1, label: group_label_1, priority: 1) service.execute - new_project_label = project.labels.find_by(title: group_label_1.title) + new_project_label = project_1.labels.find_by(title: group_label_1.title) expect(new_project_label.id).not_to eq group_label_1.id expect(new_project_label.priorities).not_to be_empty end @@ -39,13 +44,13 @@ describe Labels::TransferService, services: true do it 'does not recreate missing group labels that are not applied to issues or merge requests' do service.execute - expect(project.labels.where(title: group_label_3.title)).to be_empty + expect(project_1.labels.where(title: group_label_3.title)).to be_empty end it 'does not recreate missing group labels that already exist in the project group' do service.execute - expect(project.labels.where(title: group_label_4.title)).to be_empty + expect(project_1.labels.where(title: group_label_4.title)).to be_empty end end end -- cgit v1.2.1 From 2fad811d0d028a7bb663ae9c2ff1ed3b251a82a0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 19 Oct 2016 17:27:10 +0000 Subject: Spaces before `}`! --- app/views/projects/cycle_analytics/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index ef24ec0cf29..b647882efa0 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -2,7 +2,7 @@ - page_title "Cycle Analytics" = render "projects/pipelines/head" -#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project)}} +#cycle-analytics{class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) }} .bordered-box.landing.content-block{"v-if" => "!isHelpDismissed"} = icon('times', class: 'dismiss-icon', "@click" => "dismissLanding()") -- cgit v1.2.1 From c90a31ebcf56aa095e471edb2cd21df887318d1a Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Wed, 19 Oct 2016 22:09:18 +0300 Subject: Remove show_menu_above attribute from issuable dropdowns. --- app/views/shared/issuable/_form.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index a7944a60130..ee5c21f0762 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -88,19 +88,19 @@ - if issuable.assignee_id = f.hidden_field :assignee_id = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } }) + placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - has_labels = issuable.project.labels.any? = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label" + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" - if has_due_date .col-lg-6 .form-group -- cgit v1.2.1 From 2fabd1a123e2bcbf8f372679cacd50fc1da11252 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 19 Oct 2016 22:14:57 +0300 Subject: Change the order of tested methods in project_members_controller_spec Signed-off-by: Dmitriy Zaporozhets --- .../projects/project_members_controller_spec.rb | 88 +++++++++++----------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index ea56bd72912..8519ebc1d5f 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -4,50 +4,6 @@ describe Projects::ProjectMembersController do let(:user) { create(:user) } let(:project) { create(:project, :public) } - describe 'POST apply_import' do - let(:another_project) { create(:project, :private) } - let(:member) { create(:user) } - - before do - project.team << [user, :master] - another_project.team << [member, :guest] - sign_in(user) - end - - shared_context 'import applied' do - before do - post(:apply_import, namespace_id: project.namespace, - project_id: project, - source_project_id: another_project.id) - end - end - - context 'when user can access source project members' do - before { another_project.team << [user, :guest] } - include_context 'import applied' - - it 'imports source project members' do - expect(project.team_members).to include member - expect(response).to set_flash.to 'Successfully imported' - expect(response).to redirect_to( - namespace_project_project_members_path(project.namespace, project) - ) - end - end - - context 'when user is not member of a source project' do - include_context 'import applied' - - it 'does not import team members' do - expect(project.team_members).not_to include member - end - - it 'responds with not found' do - expect(response.status).to eq 404 - end - end - end - describe 'GET index' do it 'renders index with 200 status code' do get :index, namespace_id: project.namespace, project_id: project @@ -228,4 +184,48 @@ describe Projects::ProjectMembersController do end end end + + describe 'POST apply_import' do + let(:another_project) { create(:project, :private) } + let(:member) { create(:user) } + + before do + project.team << [user, :master] + another_project.team << [member, :guest] + sign_in(user) + end + + shared_context 'import applied' do + before do + post(:apply_import, namespace_id: project.namespace, + project_id: project, + source_project_id: another_project.id) + end + end + + context 'when user can access source project members' do + before { another_project.team << [user, :guest] } + include_context 'import applied' + + it 'imports source project members' do + expect(project.team_members).to include member + expect(response).to set_flash.to 'Successfully imported' + expect(response).to redirect_to( + namespace_project_project_members_path(project.namespace, project) + ) + end + end + + context 'when user is not member of a source project' do + include_context 'import applied' + + it 'does not import team members' do + expect(project.team_members).not_to include member + end + + it 'responds with not found' do + expect(response.status).to eq 404 + end + end + end end -- cgit v1.2.1 From 97762a5858864dd39aa744e19bc16b555832ebba Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 17:27:58 -0200 Subject: Use LabelsFinder on Google Code importer --- lib/gitlab/google_code_import/importer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index b16a5654096..6a68e786b4f 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -101,7 +101,8 @@ module Gitlab state: raw_issue['state'] == 'closed' ? 'closed' : 'opened' ) - issue.update_attribute(:label_ids, project.labels.where(title: labels).pluck(:id)) + issue_labels = ::LabelsFinder.new(project.owner, project_id: project.id, title: labels).execute + issue.update_attribute(:label_ids, issue_labels.pluck(:id)) import_issue_comments(issue, comments) end -- cgit v1.2.1 From edef2d15f9f3b357074b73db3b26acc5b7ab2d5a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 19 Oct 2016 17:28:14 -0200 Subject: Use LabelsFinder on Fogbuz importer --- lib/gitlab/fogbugz_import/importer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb index ce4d87a0741..65ee85ca5a9 100644 --- a/lib/gitlab/fogbugz_import/importer.rb +++ b/lib/gitlab/fogbugz_import/importer.rb @@ -133,7 +133,8 @@ module Gitlab updated_at: DateTime.parse(bug['dtLastUpdated']) ) - issue.update_attribute(:label_ids, project.labels.where(title: labels).pluck(:id)) + issue_labels = ::LabelsFinder.new(project.owner, project_id: project.id, title: labels).execute + issue.update_attribute(:label_ids, issue_labels.pluck(:id)) import_issue_comments(issue, comments) end -- cgit v1.2.1 From 325a5bafcc8a37b7d2ed1416b2952862066a914f Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Wed, 19 Oct 2016 21:58:12 +0200 Subject: Simpler arguments passed to named_route on toggle_award_url helper method --- CHANGELOG.md | 1 + app/helpers/award_emoji_helper.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e185d030ed5..421f0496025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) + - Simpler arguments passed to named_route on toggle_award_url helper method ## 8.13.0 (2016-10-22) diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb index 592ffe7b89f..167b09e678f 100644 --- a/app/helpers/award_emoji_helper.rb +++ b/app/helpers/award_emoji_helper.rb @@ -3,8 +3,8 @@ module AwardEmojiHelper return url_for([:toggle_award_emoji, awardable]) unless @project if awardable.is_a?(Note) - # We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (6.5x) - toggle_award_emoji_namespace_project_note_url(namespace_id: @project.namespace, project_id: @project, id: awardable.id) + # We render a list of notes very frequently and calling the specific method is a lot faster than the generic one (4.5x) + toggle_award_emoji_namespace_project_note_url(@project.namespace, @project, awardable.id) else url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable]) end -- cgit v1.2.1 From ed9838cd292ce78ae98079f513d0d1648b7f49f0 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 14 Oct 2016 19:38:41 -0300 Subject: Create project feature when project is created --- CHANGELOG.md | 1 + app/models/project.rb | 7 +----- ...213545_generate_project_feature_for_projects.rb | 28 ++++++++++++++++++++++ db/schema.rb | 2 +- spec/models/project_spec.rb | 14 ++++++++--- 5 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20161019213545_generate_project_feature_for_projects.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b018fc0d57..e9d07ed0927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - Cancelled pipelines could be retried. !6927 - Updating verbiage on git basics to be more intuitive + - Fix project_feature record not generated on project creation - Clarify documentation for Runners API (Gennady Trafimenkov) - The instrumentation for Banzai::Renderer has been restored - Change user & group landing page routing from /u/:username to /:username diff --git a/app/models/project.rb b/app/models/project.rb index 653c38322c5..6685baab699 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -32,8 +32,8 @@ class Project < ActiveRecord::Base default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } after_create :ensure_dir_exist + after_create :create_project_feature, unless: :project_feature after_save :ensure_dir_exist, if: :namespace_id_changed? - after_initialize :setup_project_feature # set last_activity_at to the same as created_at after_create :set_last_activity_at @@ -1310,11 +1310,6 @@ class Project < ActiveRecord::Base "projects/#{id}/pushes_since_gc" end - # Prevents the creation of project_feature record for every project - def setup_project_feature - build_project_feature unless project_feature - end - def default_branch_protected? current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE diff --git a/db/migrate/20161019213545_generate_project_feature_for_projects.rb b/db/migrate/20161019213545_generate_project_feature_for_projects.rb new file mode 100644 index 00000000000..4554e14b0df --- /dev/null +++ b/db/migrate/20161019213545_generate_project_feature_for_projects.rb @@ -0,0 +1,28 @@ +class GenerateProjectFeatureForProjects < ActiveRecord::Migration + DOWNTIME = true + + DOWNTIME_REASON = <<-HEREDOC + Application was eager loading project_feature for all projects generating an extra query + everytime a project was fetched. We removed that behavior to avoid the extra query, this migration + makes sure all projects have a project_feature record associated. + HEREDOC + + def up + # Generate enabled values for each project feature 20, 20, 20, 20, 20 + # All features are enabled by default + enabled_values = [ProjectFeature::ENABLED] * 5 + + execute <<-EOF.strip_heredoc + INSERT INTO project_features + (project_id, merge_requests_access_level, builds_access_level, + issues_access_level, snippets_access_level, wiki_access_level) + (SELECT projects.id, #{enabled_values.join(',')} FROM projects LEFT OUTER JOIN project_features + ON project_features.project_id = projects.id + WHERE project_features.id IS NULL) + EOF + end + + def down + "Not needed" + end +end diff --git a/db/schema.rb b/db/schema.rb index 65f55aa109b..a3c7fc2fd57 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161018024550) do +ActiveRecord::Schema.define(version: 20161019213545) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e6d98e25d0b..f4dda1ee558 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -67,6 +67,14 @@ describe Project, models: true do it { is_expected.to have_many(:notification_settings).dependent(:destroy) } it { is_expected.to have_many(:forks).through(:forked_project_links) } + context 'after create' do + it "creates project feature" do + project = FactoryGirl.build(:project) + + expect { project.save }.to change{ project.project_feature.present? }.from(false).to(true) + end + end + describe '#members & #requesters' do let(:project) { create(:project, :public) } let(:requester) { create(:user) } @@ -531,9 +539,9 @@ describe Project, models: true do end describe '#has_wiki?' do - let(:no_wiki_project) { build(:project, wiki_enabled: false, has_external_wiki: false) } - let(:wiki_enabled_project) { build(:project) } - let(:external_wiki_project) { build(:project, has_external_wiki: true) } + let(:no_wiki_project) { create(:project, wiki_access_level: ProjectFeature::DISABLED, has_external_wiki: false) } + let(:wiki_enabled_project) { create(:project) } + let(:external_wiki_project) { create(:project, has_external_wiki: true) } it 'returns true if project is wiki enabled or has external wiki' do expect(wiki_enabled_project).to have_wiki -- cgit v1.2.1 From 373c232c05dce8b720017265763ed34f36f1aeaf Mon Sep 17 00:00:00 2001 From: evhoffmann Date: Wed, 19 Oct 2016 18:30:06 -0400 Subject: a round of terms and edits --- doc/university/glossary/README.md | 367 ++++++++++++++++++++++++-------------- 1 file changed, 230 insertions(+), 137 deletions(-) diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md index a86ff165f2e..2cca5439a5a 100644 --- a/doc/university/glossary/README.md +++ b/doc/university/glossary/README.md @@ -6,83 +6,87 @@ Please add any terms that you discover that you think would be useful for others ### 2FA -User authentication by combination of 2 different steps during login. This allows for more security. +User authentication by combination of 2 different steps during login. This allows for [more security](https://about.gitlab.com/handbook/security/). ### Access Levels -Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions. -See, [GitLab's Permission Guidelines](http://doc.gitlab.com/ce/permissions/permissions.html) +Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions. See [GitLab's Permission Guidelines](http://doc.gitlab.com/ce/permissions/permissions.html) ### Active Directory (AD) -A Microsoft based directory service for windows domain networks. It uses LDAP technology under the hood +A Microsoft-based [directory service](https://msdn.microsoft.com/en-us/library/bb742424.aspx) for windows domain networks. It uses LDAP technology under the hood. ### Agile -Building and delivering software in phases/parts rather than trying to build everything at once then delivering to the user/client. The later is known as a WaterFall model +Building and [delivering software](http://agilemethodology.org/) in phases/parts rather than trying to build everything at once then delivering to the user/client. The latter is known as the WaterFall model. ### Application Lifecycle Management (ALM) -Entire product lifecycle management process for an application. From requirements management, development and testing until deployment. +The entire product lifecycle management process for an application, from requirements management, development, and testing until deployment. GitLab has [advantages](https://docs.google.com/presentation/d/1vCU-NbZWz8NTNK8Vu3y4zGMAHb5DpC8PE5mHtw1PWfI/edit#slide=id.g72f2e4906_2_288) over both legacy and modern ALM tools. ### Artifactory -Version control for binaries. +A version control [system](https://www.jfrog.com/open-source/#os-arti) for non-text files. ### Artifacts -objects (usually binary and large) created by a build process +Objects (usually binary and large) created by a build process. These can include use cases, class diagrams, requirements and design documents. ### Atlassian -A company that develops software products for developers and project managers including Bitbucket, Jira, Hipchat, Confluence, Bamboo. See [Atlassian] (https://www.atlassian.com) +A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Hipchat, Confluence, Bamboo. ### Audit Log -*** Needs definition here +Also called an [audit trail](https://en.wikipedia.org/wiki/Audit_trail), an audit log is a document that records an event in an IT system. ### Auto Defined User Group -User groups are a way of centralizing control over important management tasks, particularly access control and password policies. -A simple example of such groups are the users and the admins groups. -In most of the cases these groups are auto defined in terms of access, rules of usage, conditions to be part of, etc... +User groups are a way of centralizing control over important management tasks, particularly access control and password policies. A simple example of such groups are the users and the admins groups. +In most of the cases these groups are auto defined in terms of access, rules of usage, conditions to be part of, etc. ### Bamboo -Atlassian's CI tool similar to GitLab CI and Jenkins +Atlassian's CI tool similar to GitLab CI and Jenkins. ### Basic Subscription -Entry level subscription for GitLab EE currently available in packs of 10 see [Basic subscription](https://about.gitlab.com/pricing/) +Entry level [subscription](https://about.gitlab.com/pricing/) for GitLab EE currently available in packs of 10. ### Bitbucket -Atlassian's web hosting service for Git and Mercurial Projects i.e. GitLab.com competitor +Atlassian's web hosting service for Git and Mercurial Projects i.e. GitLab.com competitor. ### Branch -A branch is a parallel version of a repository. Allows you to work on the repository without you affecting the "master" branch. Allows you to make changes without affecting the current "live" version. When you have made all your changes to your branch you can then merge to the master and to make the changes fo "live". +A branch is a parallel version of a repository. This allows you to work on the repository without affecting the "master" branch, and without affecting the current "live" version. When you have made all your changes to your branch you can then merge to the master. When your merge request is accepted your changes will be "live." ### Branded Login -Having your own logo on your GitLab instance login page instead of the GitLab logo. +Having your own logo on [your GitLab instance login page](https://docs.gitlab.com/ee/customization/branded_login_page.html) instead of the GitLab logo. + +### Build triggers +These protect your code base against breaks, for instance when a team is working on the same project. Learn about [setting up](https://docs.gitlab.com/ce/ci/triggers/README.html) build triggers. ### CEPH -is a distributed object store and file system designed to provide excellent performance, reliability and scalability. + A distributed object store and file [system](http://ceph.com/) designed to provide excellent performance, reliability and scalability. + +### ChatOps + +The ability to [initiate an action](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/1412) from chat. ChatBots run in your chat application and give you the ability to do "anything" from chat. ### Clone -A copy of a repository stored on your machine that allows you to use your own editor without being online, but still tracks the changes made remotely. +A [copy](https://git-scm.com/docs/git-clone) of a repository stored on your machine that allows you to use your own editor without being online, but still tracks the changes made remotely. ### Code Review -Examination of a progam's code. The main aim is to maintain high standards quality of code that is being shipped. +Examination of a progam's code. The main aim is to maintain high quality standards of code that is being shipped. Merge requests [serve as a code review tool](https://about.gitlab.com/2014/09/29/gitlab-flow/) in GitLab. ### Code Snippet -A small amount of code. Usually for the purpose of showing other developers how -to do something specific or reproduce a problem. +A small amount of code, usually selected for the purpose of showing other developers how to do something specific or reproduce a problem. ### Collaborator @@ -90,31 +94,39 @@ Person with read and write access to a repository who has been invited by reposi ### Commit -Is a change (revision) to a file, and also creates an ID that allows you to see revision history and who made the changes. +A [change](https://git-scm.com/docs/git-commit) (revision) to a file that also creates an ID, allowing you to see revision history and the author of the changes. ### Community -Everyone who is using GitLab +[Everyone](https://about.gitlab.com/community/) who uses GitLab. ### Confluence -Atlassian's product for collaboration of documents and projects. +Atlassian's product for collaboration on documents and projects. -### Continuous Deivery +### Continuous Delivery -Continuous delivery is a series of practices designed to ensure that code can be rapidly and safely deployed to production by delivering every change to a production-like environment and ensuring business applications and services function as expected through rigorous automated testing. +A [software engineering approach](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) in which continuous integration, automated testing, and automated deployment capabilities allow software to be developed and deployed rapidly, reliably and repeatedly with minimal human intervention. Still, the deployment to production is defined strategically and triggered manually. ### Continuous Deployment -Continuous deployment is the next step of continuous delivery: Every change that passes the automated tests is deployed to production automatically. +A [software development practice](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) in which every code change goes through the entire pipeline and is put into production automatically, resulting in many production deployments every day. It does everything that Continuous Delivery does, but the process is fully automated, there's no human intervention at all. ### Continuous Integration -A process that involves adding new code commits to source code with the combined code being run on an automated test to ensure that the changes do not break the software. +A [software development practice](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) in which you build and test software every time a developer pushes code to the application, and it happens several times a day. ### Contributor -Term used to a person contributing to an Open Source Project. +Term used for a person contributing to an open source project. + +### Conversational Development (ConvDev) + +A [natural evolution](https://about.gitlab.com/2016/09/14/gitlab-live-event-recap/) of software development that carries a conversation across functional groups throughout the development process, enabling developers to track the full path of development in a cohesive and intuitive way. ConvDev accelerates the development lifecycle by fostering collaboration and knowledge sharing from idea to production. + +### Cycle Time + +The time it takes to move from [idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab). ### Data Centre @@ -122,37 +134,47 @@ Atlassian product for High Availability. ### Deploy Keys -An SSH key stored on the your server that grants access to a single GitLab repository. This is used by a GitLab runner to clone a project's code so that tests can be run against the checked out code. +A [SSH key](https://docs.gitlab.com/ce/gitlab-basics/create-your-ssh-keys.html)stored on your server that grants access to a single GitLab repository. This is used by a GitLab runner to clone a project's code so that tests can be run against the checked out code. ### Developer -For us (GitLab) this means a software developer, i.e. someone who makes software. It is also one of the levels of access in our multi level approval system. +For us at GitLab, this means a software developer, or someone who makes software. It is also one of the levels of access in our multi-level approval system. + +### DevOps + +The intersection of software engineering, quality assurance, and technology operations. Explore more DevOps topics in the [glossary by XebiaLabs](https://xebialabs.com/glossary/) ### Diff -Is the difference between two commits, or saved changes. This will also be shown visually after the changes. +The difference between two commits, or saved changes. This will also be shown visually after the changes. + +#### Directory + +A folder used for storing multiple files. ### Docker -Containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. -This guarantees that it will always run the same, regardless of the environment it is running in. +Containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in. + +### Dynamic Environment + +### Emacs ### Fork -Your own copy of a repository that allows you to make changes to the repository without affecting the original. +Your [own copy](https://docs.gitlab.com/ce/workflow/forking_workflow.html) of a repository that allows you to make changes to the repository without affecting the original. ### Gerrit -A code review tool built on top of Git. +A code review [tool](https://www.gerritcodereview.com/) built on top of Git. ### Git Hooks -Are scripts you can use to trigger actions at certain points. +[Scripts](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) you can use to trigger actions at certain points. ### GitHost.io -Is a single-tenant solution that provides GitLab CE or EE as a managed service. GitLab Inc. is responsible for -installing, updating, hosting, and backing up customers own private and secure GitLab instance. +A single-tenant solution that provides GitLab CE or EE as a managed service. GitLab Inc. is responsible for installing, updating, hosting, and backing up customers' own private and secure GitLab instance. ### GitHub @@ -164,51 +186,78 @@ Our free on Premise solution with >100,000 users ### GitLab CI -Our own Continuos Integration feature that is shipped with each instance +Our own Continuos Integration [feature](https://about.gitlab.com/gitlab-ci/) that is shipped with each instance ### GitLab EE -Our premium on premise solution that currently has Basic, Standard and Plus subscription packages with additional features and support. +Our premium on premise [solution](https://about.gitlab.com/features/#enterprise) that currently has Basic, Standard and Plus subscription packages with additional features and support. ### GitLab.com Our free SaaS for public and private repositories. +### GitLab Geo + +Allows you to replicate your GitLab instance to other geographical locations as a read-only fully operational version. It [can be used](https://docs.gitlab.com/ee/gitlab-geo/README.html) for cloning and fetching projects, in addition to reading any data. This will make working with large repositories over large distances much faster. + +### GitLab Pages +These allow you to [create websites](https://pages.gitlab.io/) for your GitLab projects, groups, or user account. + ### Gitolite -Is basically an access layer that sits on top of Git. Users are granted access to repos via a simple config file and you as an admin only needs the users public SSH key and a username from the user. +An [access layer](https://git-scm.com/book/en/v1/Git-on-the-Server-Gitolite) that sits on top of Git. Users are granted access to repos via a simple config file. As an admin, you only need the users' public SSH key and a username. ### Gitorious -A web based hosting service for projects using Git. It was acquired by GitLab and we discontinued the service. [Gitorious Acquisition Blog Post](https://about.gitlab.com/2015/03/03/gitlab-acquires-gitorious/) +A web-based hosting service for projects using Git. It was acquired by GitLab and we discontinued the service. Read the[Gitorious Acquisition Blog Post](https://about.gitlab.com/2015/03/03/gitlab-acquires-gitorious/). -### HADR +### Go -Sometimes written HA/DR. High Availability for Disaster Recovery. Usually refers to a strategy having a failover server in place in case the main server fails. +An open source programming [language](https://golang.org/). + +### GUI/ Git GUI + +A portable [graphical interface](https://git-scm.com/docs/git-gui) to Git that allows users to make changes to their repository by making new commits, amending existing ones, creating branches, performing local merges, and fetching/pushing to remote repositories. + +### High Availability for Disaster Recovery (HADR) + +Sometimes written HA/DR, this usually refers to a strategy for having a failover server in place in case the main server fails. ### Hip Chat -Atlassian's real time chat application for teams. Competitor to Slack, RocketChat and MatterMost. +Atlassian's real time chat application for teams, Hip Chat is a competitor to Slack, RocketChat and MatterMost. ### High Availability -Refers to a system or component that is continuously operational for a desirably long length of time. Availability can be measured relative to "100% operational" or "never failing." +Refers to a [system or component](https://about.gitlab.com/high-availability/) that is continuously operational for a desirably long length of time. Availability can be measured relative to "100% operational" or "never failing." + +### Inner-sourcing + +The [use of](https://about.gitlab.com/2014/09/05/innersourcing-using-the-open-source-workflow-to-improve-collaboration-within-an-organization/) open source development techniques within the corporation. + +### Internet Relay Chat (IRC) + +An [application layer protocol](http://www.irchelp.org/) that facilitates communication in the form of text. ### Issue Tracker -A tool used to manage, organize, and maintain a list of issues, making it easier for an organization to manage. +A [tool](https://docs.gitlab.com/ee/integration/external-issue-tracker.html) used to manage, organize, and maintain a list of issues, making it easier for an organization to manage. ### Jenkins -An Open Source CI tool written using the Java programming language. Does the same job as GitLab CI, Bamboo, Travis CI. It is extremely popular. see [Jenkins](https://jenkins-ci.org/) +An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins-ci.org/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. ### Jira -Atlassian's project management software. i.e. a complex issue tracker. See[Jira](https://www.atlassian.com/software/jira) +Atlassian's [project management software](https://www.atlassian.com/software/jira), i.e. a complex issue tracker. GitLab [can be configured](https://docs.gitlab.com/ee/project_services/jira.html) to interact with JIRA Core either using an on-premises instance or the SaaS solution that Atlassian offers. + +### JUnit + +A testing framework for the Java programming language, [JUnit](http://junit.org/junit4/) has been important in the evolution of test-driven development. ### Kerberos -A network authentication protocol that uses secret-key cryptography for security. +A network authentication [protocol](http://web.mit.edu/kerberos/) that uses secret-key cryptography for security. ### Kubernetes @@ -216,23 +265,27 @@ An open source container cluster manager originally designed by Google. It's bas ### Labels -An identifier to describe a group of one or more specific file revisions +An [identifier](https://docs.gitlab.com/ce/user/project/labels.html) to describe a group of one or more specific file revisions. -### LDAP +### Lightweight Directory Access Protocol (LDAP) -Lightweight Directory Access Protocol - basically its a directory (electronic address book) with user information e.g. name, phone_number etc + A directory (electronic address book) with user information (e.g. name, phone_number etc.) ### LDAP User Authentication -Allowing GitLab to sign in people from an LDAP server i.e. Allow people whose names are on the electronic user directory server) to be able to use their LDAP accounts to login. +GitLab [integrates](https://docs.gitlab.com/ce/administration/auth/ldap.html) with LDAP to support user authentication. This enables GitLab to sign in people from an LDAP server (i.e., allowing people whose names are on the electronic user directory server to be able to use their LDAP accounts to login.) ### LDAP Group Sync Allows you to synchronize the members of a GitLab group with one or more LDAP groups. -### Git LFS +### Load Balancer + +A [device](https://en.wikipedia.org/wiki/Load_balancing_(computing)) that distributes network or application traffic across multiple servers. + +### Git Large File Storage (LFS) -Git Large File Storage. A way to enable git to handle large binary files by using reference pointers within small text files to point to the large files. +A way [to enable](https://about.gitlab.com/2015/11/23/announcing-git-lfs-support-in-gitlab/) git to handle large binary files by using reference pointers within small text files to point to the large files. Large files such as high resolution images and videos, audio files, and assets can be called from a remote server. ### Linux @@ -240,8 +293,7 @@ An operating system like Windows or OS X. It is mostly used by software develope ### Markdown -Is a lightweight markup language with plain text formatting syntax designed so that it can be converted to HTML and many other formats using a tool by the same name. -Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor. +A lightweight markup language with plain text formatting syntax designed so that it can be converted to HTML and many other formats using a tool by the same name. Markdown is often used to format readme files, for writing messages in online discussion forums, and to create rich text using a plain text editor. Checkout GitLab's [Markdown guide](https://gitlab.com/help/user/markdown.md). ### Maria DB @@ -249,193 +301,211 @@ A community developed fork/variation of MySQL. MySQL is owned by Oracle. ### Master -Name of the default branch in every git repository. +Name of the [default branch](https://git-scm.com/book/en/v1/Git-Branching-What-a-Branch-Is) in every git repository. + +### Mattermost + +An open source, self-hosted messaging alternative to Slack. View GitLab's Mattermost [feature](https://gitlab.com/gitlab-org/gitlab-mattermost). ### Mercurial -A free distributed version control system like Git. Think of it as a competitor to Git. +A free distributed version control system similar to and a competitor with Git. ### Merge -Takes changes from one branch, and applies them into another branch. +Takes changes from one branch, and [applies them](https://git-scm.com/docs/git-merge) into another branch. + +### Merge Conflict + +[Arises](https://about.gitlab.com/2016/09/06/resolving-merge-conflicts-from-the-gitlab-ui/) when a merge can't be performed cleanly between two versions of the same file. ### Meteor -A hip platform for building javascript apps.[Meteor] (https://www.meteor.com) +A [platform](https://www.meteor.com) for building javascript apps. ### Milestones -Allows you to track the progress on issues, and merge requests, which allows you to get a snapshot of the progress made. +Allow you to [organize issues](https://docs.gitlab.com/ce/workflow/milestones.html) and merge requests in GitLab into a cohesive group, optionally setting a due date. A common use is keeping track of an upcoming software version. Milestones are created per-project. ### Mirror Repositories -You can set up a project to automatically have its branches, tags, and commits updated from an upstream repository. This is useful when a repository you're interested in is located on a different server, and you want to be able to browse its content and its activity using the familiar GitLab interface. +A project that is setup to automatically have its branches, tags, and commits [updated from an upstream repository](https://docs.gitlab.com/ee/workflow/repository_mirroring.html). This is useful when a repository you're interested in is located on a different server, and you want to be able to browse its content and activity using the familiar GitLab interface. ### MIT License -A type of software license. It lets people do anything with your code with proper attribution and without warranty. It is the most common license for open source applications written in Ruby on Rails. GitLab CE is issued under this license. -This means, you can download the code, modify it as you want even build a new commercial product using the underlying code and its not illegal. The only condition is that there is no form of waranty provided by GitLab so whatever happens if you use the code is your own problem. - -### Mondo +A type of software license. It lets people do anything with your code with proper attribution and without warranty. It is the most common license for open source applications written in Ruby on Rails. GitLab CE is issued under this [license](https://docs.gitlab.com/ce/development/licensing.html). This means you can download the code, modify it as you want, and even build a new commercial product using the underlying code and it's not illegal. The only condition is that there is no form of warranty provided by GitLab so whatever happens when you use the code is your own problem. -*** Needs definition here +### Mondo Rescue -### Multi LDAP Server +A free disaster recovery [software](https://help.ubuntu.com/community/MondoMindi). -*** Needs definition here +### MySQL -### My SQL - -A relational database. Currently only supported if you are using EE. It is owned by Oracle. +A relational [database](http://www.mysql.com/) owned by Oracle. Currently only supported if you are using EE. ### Namespace -In computing, a namespace is a set of symbols that are used to organize objects of various kinds, so that these objects may be referred to by name. - -Prominent examples include: -- file systems are namespaces that assign names to files; -- programming languages organize their variables and subroutines in namespaces; -- computer networks and distributed systems assign names to resources, such as computers, printers, websites, (remote) files, etc. +A set of symbols that are used to organize objects of various kinds so that these objects may be referred to by name. Examples of namespaces in action include file systems that assign names to files; programming languages that organize their variables and subroutines in namespaces; and computer networks and distributed systems that assign names to resources, such as computers, printers, websites, (remote) files, etc. ### Nginx -(pronounced "engine x") is a web server. It can act as a reverse proxy server for HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer and an HTTP cache. +A web [server](https://www.nginx.com/resources/wiki/) (pronounced "engine x"). It can act as a reverse proxy server for HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer and an HTTP cache. -### oAuth +### OAuth -Is an open standard for authorization, commonly used as a way for Internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password. +An open [standard](https://oauth.net/) for authorization, commonly used as a way for internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password. ### Omnibus Packages -Omnibus is a way to package the different services and tools required to run GitLab, so that users can install it without as much work. +A way to [package different services and tools](https://docs.gitlab.com/omnibus/) required to run GitLab, so that most developers can install it without laborious configuration. ### On Premise -On your own server. In GitLab, this refers to the ability to download GitLab EE/GitLab CE and host it on your own server rather than using GitLab.com which is hosted by GitLab Inc's servers. +On your own server. In GitLab, this [refers](https://about.gitlab.com/2015/02/12/why-ship-on-premises-in-the-saas-era/) to the ability to download GitLab EE/GitLab CE and host it on your own server rather than using GitLab.com, which is hosted by GitLab Inc's servers. + +### Open Core + +GitLab's [business model](https://about.gitlab.com/2016/07/20/gitlab-is-open-core-github-is-closed-source/). Coined by Andrew Lampitt in 2008, the [open core model](https://en.wikipedia.org/wiki/Open_core) primarily involves offering a "core" or feature-limited version of a software product as free and open-source software, while offering "commercial" versions or add-ons as proprietary software. ### Open Source Software -Software for which the original source code is freely available and may be redistributed and modified. +Software for which the original source code is freely [available](https://opensource.org/docs/osd) and may be redistributed and modified. GitLab prioritizes open source [stewardship](https://about.gitlab.com/2016/01/11/being-a-good-open-source-steward/). ### Owner -This is the most powerful person on a GitLab project. He has the permissions of all the other users plus the additional permission of being able to destroy i.e. delete the project +The most powerful person on a GitLab project. They have the permissions of all the other users plus the additional permission of being able to destroy (i.e. delete) the project. -### PaaS +### Platform as a Service (PaaS) -Typically referred to in regards to application development, it is a model in which a cloud provider delivers hardware and software tools to its users as a service +Typically referred to in regards to application development, PaaS is a model in which a cloud provider delivers hardware and software tools to its users as a service. ### Perforce -The company that produces Helix. A commercial, proprietary, centralised VCS well known for it's ability to version files of any size and type. They OEM a re-branded version of GitLab called "GitSwarm" that is tightly integrated with their "GitFusion" product, which in turn represents a portion of a Helix repository (called a depot) as a git repo +The company that produces Helix. A commercial, proprietary, centralised VCS well known for its ability to version files of any size and type. They OEM a re-branded version of GitLab called "GitSwarm" that is tightly integrated with their "GitFusion" product, which in turn represents a portion of a Helix repository (called a depot) as a git repo. ### Phabricator -Is a suite of web-based software development collaboration tools, including the Differential code review tool, the Diffusion repository browser, the Herald change monitoring tool, the Maniphest bug tracker and the Phriction wiki. Phabricator integrates with Git, Mercurial, and Subversion. +A suite of web-based software development collaboration tools, including the Differential code review tool, the Diffusion repository browser, the Herald change monitoring tool, the Maniphest bug tracker and the Phriction wiki. Phabricator integrates with Git, Mercurial, and Subversion. ### Piwik Analytics -An open source analytics software to help you analyze web traffic. It is similar to google analytics only that google analytics is not open source and information is stored by google while in Piwik the information is stored in your own server hence fully private. +An open source analytics software to help you analyze web traffic. It is similar to Google Analytics, except that the latter is not open source and information is stored by Google. In Piwik, the information is stored on your own server and hence is fully private. ### Plus Subscription -GitLab Premium EE subscription that includes training and dedicated Account Management and Service Engineer and complete support package [Plus subscription](https://about.gitlab.com/pricing/) +GitLab Premium EE [subscription](https://about.gitlab.com/pricing/) that includes training and dedicated Account Management and Service Engineer and complete support package. ### PostgreSQL -A relational database. Touted as the most advanced open source database. +An [object-relational](https://en.wikipedia.org/wiki/PostgreSQL) database. Touted as the most advanced open source database, it is one of two database management systems [supported by](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/database.md) GitLab, the other being MySQL. ### Protected Branches -A feature that protects branches from unauthorized pushes, force pushing or deletion. +A [feature](https://docs.gitlab.com/ce/user/project/protected_branches.html) that protects branches from unauthorized pushes, force pushing or deletion. ### Pull -Git command to synchronize the local repository with the remote repository, by fetching all remote changes and merging them into the local repository. +Git command to [synchronize](https://git-scm.com/docs/git-pull) the local repository with the remote repository, by fetching all remote changes and merging them into the local repository. ### Puppet -A popular devops automation tool +A popular DevOps [automation tool](https://puppet.com/product/how-puppet-works). ### Push -Git command to send commits from the local repository to the remote repository. +Git [command](https://git-scm.com/docs/git-push) to send commits from the local repository to the remote repository. ### RE Read Only -Permissions to see a file and it's contents, but not change it +Permissions to see a file and its contents, but not change it. ### Rebase -Moves a branch from one commit to another. This allows you to re-write your project's history. +In addition to the merge, the [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) is a main way to integrate changes from one branch into another. -### Git Repository +### (Git) Repository -Storage location of all files which are tracked by git. +A directory where Git [has been initiatlized](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository) to start version controlling your files. The history of your work is stored here. A remote repository is not on your machine, but usually online (like on GitLab.com, for instance). The main remote repository is usually called "Origin." ### Requirements management -*** Needs definition here - -### Revision - -*** Needs definition here +Gives your distributed teams a single shared repository to collaborate and share requirements, understand their relationship to tests, and evaluate linked defects. It includes multiple, preconfigured requirement types. ### Revision Control -Also known as version control or source control, is the management of changes to documents, computer programs, large web sites, and other collections of information. Changes are usually identified by a number or letter code, termed the "revision number", "revision level", or simply "revision". +Also known as version control or source control, this is the management of changes to documents, computer programs, large web sites, and other collections of information. Changes are usually identified by a number or letter code, termed the "revision number," "revision level," or simply "revision." ### RocketChat -An open source chat application for teams. Very similar to Slack only that is is open-source. +An open source chat application for teams, RocketChat is very similar to Slack but it is also open-source. + +### Route Table + +A route table contains rules (called routes) that determine where network traffic is directed. Each [subnet in a VPC](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Route_Tables.html) must be associated with a route table. ### Runners -Actual build machines/containers that run/execute tests you have specified to be run on GitLab CI +Actual build machines/containers that [run and execute tests](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner) you have specified to be run on GitLab CI. -### SaaS +### Software as a service (SaaS) -Software as a service. Software is hosted centrally and accessed on-demand i.e. when you want to. This refers to GitLab.com in our scenario +Software that is hosted centrally and accessed on-demand (i.e. whenever you want to). This applies to GitLab.com. -### SCM +### Software Configuration Management (SCM) -Software Configuration Management. Often used by people when they mean Version Control +This term is often used by people when they mean "Version Control." ## Scrum -An Agile framework designed to help complete complex (typically) software projects. It's made up of several parts: product requirments backlog, sprint plannnig, sprint (development), sprint review, retrospec (analyzing the sprint). The goal is to end up with potentially shippable products. +An Agile [framework](https://www.scrum.org/Resources/What-is-Scrum) designed to typically help complete complex software projects. It's made up of several parts: product requirements backlog, sprint planning, sprint (development), sprint review, and retrospec (analyzing the sprint). The goal is to end up with potentially shippable products. ### Scrum Board The board used to track the status and progress of each of the sprint backlog items. +### Shell + +[Terminal](https://docs.gitlab.com/ce/gitlab-basics/start-using-git.html) on Mac OSX, GitBash on Windows, or Linux Terminal on Linux. + +### Single-tenant + +The tenant purchases their own copy of the software and the software can be customized to meet the specific and needs of that customer. [GitHost.io](https://about.gitlab.com/handbook/positioning-faq/) is our provider of single-tenant 'managed cloud' GitLab instances. + ### Slack -Real time messaging app for teams. Used internally by GitLab +Real time messaging app for teams that is used internally by GitLab team members. GitLab users can enable [Slack integration](https://docs.gitlab.com/ce/project_services/slack.html) to trigger push, issue, and merge request events among others. ### Slave Servers -Also known as secondary servers. They help to spread the load over multiple machines, they also provide backups when the master/primary server crashes. +Also known as secondary servers, these help to spread the load over multiple machines. They also provide backups when the master/primary server crashes. ### Source Code -Program code as typed by a computer programmer. i.e. it has not yet been compiled/translated by the computer to machine language. +Program code as typed by a computer programmer (i.e. it has not yet been compiled/translated by the computer to machine language). ### SSH Key -A unique identifier of a computer. It is used to identify computers without the need for a password. e.g. On GitLab I have added the ssh key of all my work machines so that the GitLab instance knows that it can accept code pushes and pulls from this trusted machines whose keys are I have added. +A unique identifier of a computer. It is used to identify computers without the need for a password (e.g., On GitLab I have [added the ssh key](https://docs.gitlab.com/ce/gitlab-basics/create-your-ssh-keys.html) of all my work machines so that the GitLab instance knows that it can accept code pushes and pulls from this trusted machines whose keys are I have added.) + +### Single Sign On (SSO) -### SSO +An authentication process that allows you enter one username and password to access multiple applications. -Single Sign On. An authentication process that allows you enter one username and password to access multiple applications. +### Staging Area + +[Staging occurs](https://git-scm.com/book/en/v2/Getting-Started-Git-Basics) before the commit process in git. The staging area is a file, generally contained in your Git directory, that stores information about what will go into your next commit. It’s sometimes referred to as the “index."" ### Standard Subscription -Our mid range EE subscription that includes 24/7 support, support for High Availability [Standard Subscription](https://about.gitlab.com/pricing/) +Our mid range EE subscription that includes 24/7 support and support for High Availability [Standard Subscription](https://about.gitlab.com/pricing/). ### Stash -Atlassian's Git On-Premises solution. Think of it as Atlassian's GitLab EE. It is now known as BitBucket Server. +Atlassian's Git on-premise solution. Think of it as Atlassian's GitLab EE, now known as BitBucket Server. + +### Static Site Generators (SSGs) + +A [software](https://wiki.python.org/moin/StaticSiteGenerator) that takes some text and templates as input and produces html files on the output. ### Subversion @@ -443,40 +513,63 @@ Non-proprietary, centralized version control system. ### Sudo -A program that allows you to perform superuser/administrator actions on Unix Operating Systems e.g. Linux, OS X. It actually stands for 'superuser do' +A program that allows you to perform superuser/administrator actions on Unix Operating Systems (e.g., Linux, OS X.) It actually stands for 'superuser do.' -### SVN +### Subversion (SVN) -Abbreviation for Subversion. +An open source version control system. ### Tag -Represents a version of a particular branch at a moment in time. +[Represents](https://docs.gitlab.com/ce/api/tags.html) a version of a particular branch at a moment in time. ### Tool Stack -Set of tools used in a process to achieve a common outcome. E.g. set of tools used in Application Lifecycle Management. +The set of tools used in a process to achieve a common outcome (e.g. set of tools used in Application Lifecycle Management). ### Trac -An Open Source project management and bug tracking web application. +An open source project management and bug tracking web [application](https://trac.edgewall.org/). + +### Untracked files + +New files that Git has not [been told](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository) to track previously. ### User Anyone interacting with the software. -### VCS +### Version Control Software (VCS) + +Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. VCS [has evolved](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit#slide=id.gd69537a19_0_32) from local version control systems, to centralized version control systems, to the present distributed version control systems like Git, Mercurial, Bazaar, and Darcs. + +### Virtual Private Cloud (VPC) + +An on demand configurable pool of shared computing resources allocated within a public cloud environment, providing some isolation between the different users using the resources. GitLab users need to create a new Amazon VPC in order to [setup High Availability](https://docs.gitlab.com/ce/university/high-availability/aws/). -Version Control Software +### Virtual private server (VPS) + +A [virtual machine](https://en.wikipedia.org/wiki/Virtual_private_server) sold as a service by an Internet hosting service. A VPS runs its own copy of an operating system, and customers have superuser-level access to that operating system instance, so they can install almost any software that runs on that OS. + +### VM Instance ### Waterfall -A model of building software that involves collecting all requirements from the customer, then building and refining all the requirements and finally delivering the COMPLETE software to the customer that meets all the requirements specified by the customer +A [model](http://www.umsl.edu/~hugheyd/is6840/waterfall.html) of building software that involves collecting all requirements from the customer, then building and refining all the requirements and finally delivering the complete software to the customer that meets all the requirements they specified. ### Webhooks -A way for for an app to provide other applications with real-time information. e.g. send a message to a slack channel when a commit is pushed +A way for for an app to [provide](https://docs.gitlab.com/ce/web_hooks/web_hooks.html) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) ### Wiki -A website/system that allows for collaborative editing of its content by the users. In programming, they usually contain documentation of how to use the software +A [website/system](http://www.wiki.com/) that allows for collaborative editing of its content by the users. In programming, wikis usually contain documentation of how to use the software. + +### Working Tree + +[Consists of files](http://stackoverflow.com/questions/3689838/difference-between-head-working-tree-index-in-git) that you are currently working on. + +### YAML + +A human-readable data serialization [language](http://www.yaml.org/about.html) that takes concepts from programming languages such as C, Perl, and Python, and ideas from XML and the data format of electronic mail. + -- cgit v1.2.1 From af8e06ee4c1ca54e7e05a40433d09253ba55fbbb Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Thu, 20 Oct 2016 08:57:23 +0900 Subject: Fix a broken table in Project API doc --- CHANGELOG.md | 1 + doc/api/projects.md | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b018fc0d57..a0e7dff7e1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Delete dynamic environments - Fix buggy iOS tooltip layering behavior. - Make guests unable to view MRs on private projects + - Fix broken Project API docs (Takuya Noguchi) ## 8.12.7 diff --git a/doc/api/projects.md b/doc/api/projects.md index b7791b4748a..77d6bd6b5c2 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1333,8 +1333,8 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `query` (required) - A string contained in the project name -| `per_page` (optional) - number of projects to return per page -| `page` (optional) - the page to retrieve -| `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields +| `query` | string | yes | A string contained in the project name | +| `per_page` | integer | no | number of projects to return per page | +| `page` | integer | no | the page to retrieve | +| `order_by` | string | no | Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields | | `sort` | string | no | Return requests sorted in `asc` or `desc` order | -- cgit v1.2.1 From a8bbe53c0c15c0924bce971e1ed853630d2b6d56 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Thu, 20 Oct 2016 08:58:35 +0900 Subject: Remove pagination description from individual doc --- doc/api/projects.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 77d6bd6b5c2..b69db90e70d 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1334,7 +1334,5 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `query` | string | yes | A string contained in the project name | -| `per_page` | integer | no | number of projects to return per page | -| `page` | integer | no | the page to retrieve | | `order_by` | string | no | Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields | | `sort` | string | no | Return requests sorted in `asc` or `desc` order | -- cgit v1.2.1 From 9124310f2871117acaac98781be84c9fc016e2ad Mon Sep 17 00:00:00 2001 From: Callum Dryden Date: Thu, 6 Oct 2016 15:19:27 +0000 Subject: Differentiate the expire from leave event At the moment we cannot see weather a user left a project due to their membership expiring of if they themselves opted to leave the project. This adds a new event type that allows us to make this differentiation. Note that is not really feasable to go back and reliably fix up the previous events. As a result the events for previous expire removals will remain the same however events of this nature going forward will be correctly represented. --- CHANGELOG.md | 1 + app/models/concerns/expirable.rb | 6 +++- app/models/event.rb | 9 ++++- app/models/members/project_member.rb | 6 +++- app/services/event_create_service.rb | 4 +++ features/dashboard/dashboard.feature | 13 ------- features/steps/dashboard/dashboard.rb | 27 -------------- lib/event_filter.rb | 11 ++++-- .../project_member_activity_index_spec.rb | 41 ++++++++++++++++++++++ spec/models/concerns/expirable_spec.rb | 31 ++++++++++++++++ spec/models/event_spec.rb | 27 ++++++++++++++ spec/models/members/project_member_spec.rb | 11 ++++++ spec/services/event_create_service_spec.rb | 19 ++++++++++ 13 files changed, 161 insertions(+), 45 deletions(-) create mode 100644 spec/features/dashboard/project_member_activity_index_spec.rb create mode 100644 spec/models/concerns/expirable_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b018fc0d57..8cb848c3957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) + - Adds user project membership expired event to clarify why user was removed (Callum Dryden) ## 8.13.0 (2016-10-22) diff --git a/app/models/concerns/expirable.rb b/app/models/concerns/expirable.rb index be93435453b..b66ba08dc59 100644 --- a/app/models/concerns/expirable.rb +++ b/app/models/concerns/expirable.rb @@ -5,11 +5,15 @@ module Expirable scope :expired, -> { where('expires_at <= ?', Time.current) } end + def expired? + expires? && expires_at <= Time.current + end + def expires? expires_at.present? end def expires_soon? - expires_at < 7.days.from_now + expires? && expires_at < 7.days.from_now end end diff --git a/app/models/event.rb b/app/models/event.rb index 0764cb8cabd..3993b35f96d 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -12,6 +12,7 @@ class Event < ActiveRecord::Base JOINED = 8 # User joined project LEFT = 9 # User left project DESTROYED = 10 + EXPIRED = 11 # User left project due to expiry RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour @@ -115,6 +116,10 @@ class Event < ActiveRecord::Base action == LEFT end + def expired? + action == EXPIRED + end + def destroyed? action == DESTROYED end @@ -124,7 +129,7 @@ class Event < ActiveRecord::Base end def membership_changed? - joined? || left? + joined? || left? || expired? end def created_project? @@ -184,6 +189,8 @@ class Event < ActiveRecord::Base 'joined' elsif left? 'left' + elsif expired? + 'removed due to membership expiration from' elsif destroyed? 'destroyed' elsif commented? diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 125f26369d7..e4880973117 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -121,7 +121,11 @@ class ProjectMember < Member end def post_destroy_hook - event_service.leave_project(self.project, self.user) + if expired? + event_service.expired_leave_project(self.project, self.user) + else + event_service.leave_project(self.project, self.user) + end super end diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index 07fc77001a5..e24cc66e0fe 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -62,6 +62,10 @@ class EventCreateService create_event(project, current_user, Event::LEFT) end + def expired_leave_project(project, current_user) + create_event(project, current_user, Event::EXPIRED) + end + def create_project(project, current_user) create_event(project, current_user, Event::CREATED) end diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index 1f4c9020731..b1d5e4a7acb 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -31,19 +31,6 @@ Feature: Dashboard And I click "Create Merge Request" link Then I see prefilled new Merge Request page - @javascript - Scenario: I should see User joined Project event - Given user with name "John Doe" joined project "Shop" - When I visit dashboard activity page - Then I should see "John Doe joined project Shop" event - - @javascript - Scenario: I should see User left Project event - Given user with name "John Doe" joined project "Shop" - And user with name "John Doe" left project "Shop" - When I visit dashboard activity page - Then I should see "John Doe left project Shop" event - @javascript Scenario: Sorting Issues Given I visit dashboard issues page diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index a7d61bc28e0..b2bec369e0f 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -33,33 +33,6 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps expect(find("input#merge_request_target_branch").value).to eq "master" end - step 'user with name "John Doe" joined project "Shop"' do - user = create(:user, { name: "John Doe" }) - project.team << [user, :master] - Event.create( - project: project, - author_id: user.id, - action: Event::JOINED - ) - end - - step 'I should see "John Doe joined project Shop" event' do - expect(page).to have_content "John Doe joined project #{project.name_with_namespace}" - end - - step 'user with name "John Doe" left project "Shop"' do - user = User.find_by(name: "John Doe") - Event.create( - project: project, - author_id: user.id, - action: Event::LEFT - ) - end - - step 'I should see "John Doe left project Shop" event' do - expect(page).to have_content "John Doe left project #{project.name_with_namespace}" - end - step 'I have group with projects' do @group = create(:group) @project = create(:project, namespace: @group) diff --git a/lib/event_filter.rb b/lib/event_filter.rb index 96e70e37e8f..21f6a9a762b 100644 --- a/lib/event_filter.rb +++ b/lib/event_filter.rb @@ -45,9 +45,16 @@ class EventFilter when EventFilter.comments actions = [Event::COMMENTED] when EventFilter.team - actions = [Event::JOINED, Event::LEFT] + actions = [Event::JOINED, Event::LEFT, Event::EXPIRED] when EventFilter.all - actions = [Event::PUSHED, Event::MERGED, Event::COMMENTED, Event::JOINED, Event::LEFT] + actions = [ + Event::PUSHED, + Event::MERGED, + Event::COMMENTED, + Event::JOINED, + Event::LEFT, + Event::EXPIRED + ] end events.where(action: actions) diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb new file mode 100644 index 00000000000..ba77093a6d4 --- /dev/null +++ b/spec/features/dashboard/project_member_activity_index_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +feature 'Project member activity', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:project) { create(:empty_project, :public, name: 'x', namespace: user.namespace) } + + before do + project.team << [user, :master] + end + + def visit_activities_and_wait_with_event(event_type) + Event.create(project: project, author_id: user.id, action: event_type) + visit activity_namespace_project_path(project.namespace.path, project.path) + wait_for_ajax + end + + subject { page.find(".event-title").text } + + context 'when a user joins the project' do + before { visit_activities_and_wait_with_event(Event::JOINED) } + + it { is_expected.to eq("#{user.name} joined project") } + end + + context 'when a user leaves the project' do + before { visit_activities_and_wait_with_event(Event::LEFT) } + + it { is_expected.to eq("#{user.name} left project") } + end + + context 'when a users membership expires for the project' do + before { visit_activities_and_wait_with_event(Event::EXPIRED) } + + it "presents the correct message" do + message = "#{user.name} removed due to membership expiration from project" + is_expected.to eq(message) + end + end +end diff --git a/spec/models/concerns/expirable_spec.rb b/spec/models/concerns/expirable_spec.rb new file mode 100644 index 00000000000..f7b436f32e6 --- /dev/null +++ b/spec/models/concerns/expirable_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Expirable do + describe 'ProjectMember' do + let(:no_expire) { create(:project_member) } + let(:expire_later) { create(:project_member, expires_at: Time.current + 6.days) } + let(:expired) { create(:project_member, expires_at: Time.current - 6.days) } + + describe '.expired' do + it { expect(ProjectMember.expired).to match_array([expired]) } + end + + describe '#expired?' do + it { expect(no_expire.expired?).to eq(false) } + it { expect(expire_later.expired?).to eq(false) } + it { expect(expired.expired?).to eq(true) } + end + + describe '#expires?' do + it { expect(no_expire.expires?).to eq(false) } + it { expect(expire_later.expires?).to eq(true) } + it { expect(expired.expires?).to eq(true) } + end + + describe '#expires_soon?' do + it { expect(no_expire.expires_soon?).to eq(false) } + it { expect(expire_later.expires_soon?).to eq(true) } + it { expect(expired.expires_soon?).to eq(true) } + end + end +end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 733b79079ed..aca49be2942 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -40,6 +40,33 @@ describe Event, models: true do end end + describe '#membership_changed?' do + context "created" do + subject { build(:event, action: Event::CREATED).membership_changed? } + it { is_expected.to be_falsey } + end + + context "updated" do + subject { build(:event, action: Event::UPDATED).membership_changed? } + it { is_expected.to be_falsey } + end + + context "expired" do + subject { build(:event, action: Event::EXPIRED).membership_changed? } + it { is_expected.to be_truthy } + end + + context "left" do + subject { build(:event, action: Event::LEFT).membership_changed? } + it { is_expected.to be_truthy } + end + + context "joined" do + subject { build(:event, action: Event::JOINED).membership_changed? } + it { is_expected.to be_truthy } + end + end + describe '#note?' do subject { Event.new(project: target.project, target: target) } diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index d85a1c1e3b2..b2fe96e2e02 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -54,6 +54,17 @@ describe ProjectMember, models: true do master_todos end + it "creates an expired event when left due to expiry" do + expired = create(:project_member, project: project, expires_at: Time.now - 6.days) + expired.destroy + expect(Event.first.action).to eq(Event::EXPIRED) + end + + it "creates a left event when left due to leave" do + master.destroy + expect(Event.first.action).to eq(Event::LEFT) + end + it "destroys itself and delete associated todos" do expect(owner.user.todos.size).to eq(2) expect(master.user.todos.size).to eq(3) diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index 16a9956fe7f..b7dc99ed887 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -110,4 +110,23 @@ describe EventCreateService, services: true do end end end + + describe 'Project' do + let(:user) { create :user } + let(:project) { create(:empty_project) } + + describe '#join_project' do + subject { service.join_project(project, user) } + + it { is_expected.to be_truthy } + it { expect { subject }.to change { Event.count }.from(0).to(1) } + end + + describe '#expired_leave_project' do + subject { service.expired_leave_project(project, user) } + + it { is_expected.to be_truthy } + it { expect { subject }.to change { Event.count }.from(0).to(1) } + end + end end -- cgit v1.2.1 From e6f515ecbed85b204e8f7bc9934b0bf208a0ea4c Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 19 Oct 2016 16:53:12 +0100 Subject: Revert "Add #closed_without_source_project?" This reverts commit 31c37c6c38258684fc92e0d91119c33872e39034. See #23341 --- app/models/merge_request.rb | 10 ++----- app/views/projects/merge_requests/_show.html.haml | 31 ++++++++++------------ spec/models/merge_request_spec.rb | 32 ----------------------- 3 files changed, 16 insertions(+), 57 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 0cc0b3c2a0e..d32bc9f882f 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -333,11 +333,7 @@ class MergeRequest < ActiveRecord::Base end def closed_without_fork? - closed? && forked_source_project_missing? - end - - def closed_without_source_project? - closed? && !source_project + closed? && (forked_source_project_missing? || !source_project) end def forked_source_project_missing? @@ -348,9 +344,7 @@ class MergeRequest < ActiveRecord::Base end def reopenable? - return false if closed_without_fork? || closed_without_source_project? || merged? - - closed? + source_branch_exists? && closed? end def ensure_merge_request_diff diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index cd98aaf8d75..fe90383b00f 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -26,19 +26,17 @@ %ul.dropdown-menu.dropdown-menu-align-right %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) - - unless @merge_request.closed_without_fork? - .normal - %span Request to merge - %span.label-branch= source_branch_with_namespace(@merge_request) - %span into - %span.label-branch - = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) - - if @merge_request.open? && @merge_request.diverged_from_target_branch? - %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) + .normal + %span Request to merge + %span.label-branch= source_branch_with_namespace(@merge_request) + %span into + %span.label-branch + = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) + - if @merge_request.open? && @merge_request.diverged_from_target_branch? + %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) - - unless @merge_request.closed_without_source_project? - = render "projects/merge_requests/show/how_to_merge" - = render "projects/merge_requests/widget/show.html.haml" + = render "projects/merge_requests/show/how_to_merge" + = render "projects/merge_requests/widget/show.html.haml" - if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user) .light.prepend-top-default.append-bottom-default @@ -52,11 +50,10 @@ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion %span.badge= @merge_request.mr_and_commit_notes.user.count - - unless @merge_request.closed_without_source_project? - %li.commits-tab - = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do - Commits - %span.badge= @commits_count + %li.commits-tab + = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do + Commits + %span.badge= @commits_count - if @pipeline %li.pipelines-tab = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 6db5e7f7d80..ee003a9d18f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1274,38 +1274,6 @@ describe MergeRequest, models: true do end end - describe '#closed_without_source_project?' do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } - let(:destroy_service) { Projects::DestroyService.new(fork_project, user) } - - context 'when the merge request is closed' do - let(:closed_merge_request) do - create(:closed_merge_request, - source_project: fork_project, - target_project: project) - end - - it 'returns false if the source project exists' do - expect(closed_merge_request.closed_without_source_project?).to be_falsey - end - - it 'returns true if the source project does not exist' do - destroy_service.execute - closed_merge_request.reload - - expect(closed_merge_request.closed_without_source_project?).to be_truthy - end - end - - context 'when the merge request is open' do - it 'returns false' do - expect(subject.closed_without_source_project?).to be_falsey - end - end - end - describe '#reopenable?' do context 'when the merge request is closed' do it 'returns true' do -- cgit v1.2.1 From a4ef4244d4b9b54246f53bba5c02746542034da5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 20 Oct 2016 16:16:57 +0800 Subject: Preserve note_type and position for notes from emails Closes #23208 --- lib/gitlab/email/handler/create_note_handler.rb | 4 +++- spec/fixtures/emails/commands_in_reply.eml | 2 -- spec/fixtures/emails/commands_only_reply.eml | 2 -- .../email/handler/create_note_handler_spec.rb | 24 ++++++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index 06dae31cc27..447c7a6a6b9 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -46,7 +46,9 @@ module Gitlab noteable_type: sent_notification.noteable_type, noteable_id: sent_notification.noteable_id, commit_id: sent_notification.commit_id, - line_code: sent_notification.line_code + line_code: sent_notification.line_code, + position: sent_notification.position, + type: sent_notification.note_type ).execute end end diff --git a/spec/fixtures/emails/commands_in_reply.eml b/spec/fixtures/emails/commands_in_reply.eml index 06bf60ab734..712f6f797b4 100644 --- a/spec/fixtures/emails/commands_in_reply.eml +++ b/spec/fixtures/emails/commands_in_reply.eml @@ -23,8 +23,6 @@ Cool! /close /todo -/due tomorrow - On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta wrote: diff --git a/spec/fixtures/emails/commands_only_reply.eml b/spec/fixtures/emails/commands_only_reply.eml index aed64224b06..2d2e2f94290 100644 --- a/spec/fixtures/emails/commands_only_reply.eml +++ b/spec/fixtures/emails/commands_only_reply.eml @@ -21,8 +21,6 @@ X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 /close /todo -/due tomorrow - On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta wrote: diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index 4909fed6b77..48660d1dd1b 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -12,10 +12,13 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do let(:email_raw) { fixture_file('emails/valid_reply.eml') } let(:project) { create(:project, :public) } - let(:noteable) { create(:issue, project: project) } let(:user) { create(:user) } + let(:note) { create(:diff_note_on_merge_request, project: project) } + let(:noteable) { note.noteable } - let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) } + let!(:sent_notification) do + SentNotification.record_note(note, user.id, mail_key) + end context "when the recipient address doesn't include a mail key" do let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") } @@ -82,7 +85,6 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do expect { receiver.execute }.to change { noteable.notes.count }.by(1) expect(noteable.reload).to be_closed - expect(noteable.due_date).to eq(Date.tomorrow) expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy end end @@ -100,7 +102,6 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do expect { receiver.execute }.to change { noteable.notes.count }.by(1) expect(noteable.reload).to be_open - expect(noteable.due_date).to be_nil expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy end end @@ -117,7 +118,6 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do expect { receiver.execute }.to change { noteable.notes.count }.by(2) expect(noteable.reload).to be_closed - expect(noteable.due_date).to eq(Date.tomorrow) expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy end end @@ -138,10 +138,11 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do it "creates a comment" do expect { receiver.execute }.to change { noteable.notes.count }.by(1) - note = noteable.notes.last + new_note = noteable.notes.last - expect(note.author).to eq(sent_notification.recipient) - expect(note.note).to include("I could not disagree more.") + expect(new_note.author).to eq(sent_notification.recipient) + expect(new_note.position).to eq(note.position) + expect(new_note.note).to include("I could not disagree more.") end it "adds all attachments" do @@ -160,10 +161,11 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do shared_examples 'an email that contains a mail key' do |header| it "fetches the mail key from the #{header} header and creates a comment" do expect { receiver.execute }.to change { noteable.notes.count }.by(1) - note = noteable.notes.last + new_note = noteable.notes.last - expect(note.author).to eq(sent_notification.recipient) - expect(note.note).to include('I could not disagree more.') + expect(new_note.author).to eq(sent_notification.recipient) + expect(new_note.position).to eq(note.position) + expect(new_note.note).to include('I could not disagree more.') end end -- cgit v1.2.1 From 80ca78fa47068d71e866b58d546d0cd5059d96d9 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 20 Oct 2016 16:24:37 +0800 Subject: Add CHANGELOG entry [ci skip] --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b018fc0d57..d6e6690ced1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) + - Fix discussion thread from emails for merge requests. !7010 + ## 8.13.0 (2016-10-22) - Fix save button on project pipeline settings page. (!6955) -- cgit v1.2.1 From fd2c3a3da0302a474d7c1adbd409aedea2a41053 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 19 Oct 2016 19:43:04 +0300 Subject: Refactoring find_commits functionality --- app/controllers/projects/commits_controller.rb | 2 +- app/models/repository.rb | 9 ++++++--- lib/gitlab/project_search_results.rb | 6 +----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index a52c614b259..c2e7bf1ffec 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -13,7 +13,7 @@ class Projects::CommitsController < Projects::ApplicationController @commits = if search.present? - @repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact + @repository.find_commits_by_message(search, @ref, @path, @limit, @offset) else @repository.commits(@ref, path: @path, limit: @limit, offset: @offset) end diff --git a/app/models/repository.rb b/app/models/repository.rb index 72e473871fa..1b7f20a2134 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -109,6 +109,10 @@ class Repository end def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0) + unless exists? && has_visible_content? && query.present? + return [] + end + ref ||= root_ref args = %W( @@ -117,9 +121,8 @@ class Repository ) args = args.concat(%W(-- #{path})) if path.present? - git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp) - commits = git_log_results.map { |c| commit(c) } - commits + git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines + git_log_results.map { |c| commit(c.chomp) }.compact end def find_branch(name, fresh_repo: true) diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 5b9cfaeb2f8..24733435a5a 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -73,11 +73,7 @@ module Gitlab end def commits - if project.empty_repo? || query.blank? - [] - else - project.repository.find_commits_by_message(query).compact - end + project.repository.find_commits_by_message(query) end def project_ids_relation -- cgit v1.2.1 From c9108442b5ac33514801c38412bed5e7b13677b8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 19 Oct 2016 14:48:37 +0200 Subject: Update duration at the end of pipeline --- CHANGELOG.md | 1 + app/models/ci/pipeline.rb | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfde2cc81e5..cb43cb4b307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - Keep around commits only pipeline creation as pipeline data doesn't change over time + - Update duration at the end of pipeline - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - Add group level labels. (!6425) - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index e84c91b417d..d5c1e03b461 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -59,9 +59,6 @@ module Ci before_transition any => [:success, :failed, :canceled] do |pipeline| pipeline.finished_at = Time.now - end - - before_transition do |pipeline| pipeline.update_duration end -- cgit v1.2.1 From d9d0b81bc6117f9c01c28c1fab597f556b56f369 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 20 Oct 2016 12:10:27 +0200 Subject: Make label API spec independent of order --- spec/requests/api/labels_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 1da9988978b..867bc615b97 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -22,8 +22,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.size).to eq(2) - expect(json_response.first['name']).to eq(group_label.name) - expect(json_response.second['name']).to eq(label1.name) + expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, label1.name]) end end -- cgit v1.2.1 From 50288a6ace0e75aa3e6c09c534f0be9800c95836 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 20 Oct 2016 10:27:30 +0100 Subject: Removed code from project members controller This code was meant to be added to another branch as an expirement, but instead was commited to wrong branch --- app/controllers/projects/project_members_controller.rb | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 37a86ed0523..2a07d154853 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -32,21 +32,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController current_user: current_user ) - if params[:group_ids].present? - group_ids = params[:group_ids].split(',') - groups = Group.where(id: group_ids) - - groups.each do |group| - next unless can?(current_user, :read_group, group) - - project.project_group_links.create( - group: group, - group_access: params[:access_level], - expires_at: params[:expires_at] - ) - end - end - redirect_to namespace_project_project_members_path(@project.namespace, @project) end -- cgit v1.2.1 From 493367108eef14c8517c6d023ec46267c1e706cf Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Wed, 19 Oct 2016 14:30:17 +0200 Subject: Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method --- CHANGELOG.md | 2 ++ app/models/issue.rb | 8 +++++++- spec/models/issue_spec.rb | 8 +++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b018fc0d57..6c0fd97b070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) + - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method + ## 8.13.0 (2016-10-22) - Fix save button on project pipeline settings page. (!6955) diff --git a/app/models/issue.rb b/app/models/issue.rb index 133a5993815..89158a50353 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -211,7 +211,13 @@ class Issue < ActiveRecord::Base note.all_references(current_user, extractor: ext) end - ext.merge_requests.select { |mr| mr.open? && mr.closes_issue?(self) } + merge_requests = ext.merge_requests.select(&:open?) + if merge_requests.any? + ids = MergeRequestsClosingIssues.where(merge_request_id: merge_requests.map(&:id), issue_id: id).pluck(:merge_request_id) + merge_requests.select { |mr| mr.id.in?(ids) } + else + [] + end end def moved? diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 3b8b743af2d..60d30eb7418 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -100,11 +100,17 @@ describe Issue, models: true do end it 'returns the merge request to close this issue' do - allow(mr).to receive(:closes_issue?).with(issue).and_return(true) + mr expect(issue.closed_by_merge_requests).to eq([mr]) end + it "returns an empty array when the merge request is closed already" do + closed_mr + + expect(issue.closed_by_merge_requests).to eq([]) + end + it "returns an empty array when the current issue is closed already" do expect(closed_issue.closed_by_merge_requests).to eq([]) end -- cgit v1.2.1 From bc31a489dd8c329cf011ac898498735809c319dd Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 20 Oct 2016 12:59:39 +0200 Subject: Restrict ProjectCacheWorker jobs to one per 15 min This ensures ProjectCacheWorker jobs for a given project are performed at most once per 15 minutes. This should reduce disk load a bit in cases where there are multiple pushes happening (which should schedule multiple ProjectCacheWorker jobs). --- CHANGELOG.md | 3 ++- app/workers/project_cache_worker.rb | 27 ++++++++++++++++++++++ spec/workers/project_cache_worker_spec.rb | 38 +++++++++++++++++++++++-------- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 399a7f65267..ba5d72d3ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) - Adds user project membership expired event to clarify why user was removed (Callum Dryden) - - Simpler arguments passed to named_route on toggle_award_url helper method + - Simpler arguments passed to named_route on toggle_award_url helper method ## 8.13.0 (2016-10-22) @@ -35,6 +35,7 @@ Please view this file on the master branch, on stable branches it's out of date. - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - ProjectCacheWorker updates caches at most once per 15 minutes per project - Fix Error 500 when viewing old merge requests with bad diff data - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - Speed-up group milestones show page diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb index ccefd0f71a0..0d524e88dc3 100644 --- a/app/workers/project_cache_worker.rb +++ b/app/workers/project_cache_worker.rb @@ -1,9 +1,30 @@ +# Worker for updating any project specific caches. +# +# This worker runs at most once every 15 minutes per project. This is to ensure +# that multiple instances of jobs for this worker don't hammer the underlying +# storage engine as much. class ProjectCacheWorker include Sidekiq::Worker sidekiq_options queue: :default + LEASE_TIMEOUT = 15.minutes.to_i + def perform(project_id) + if try_obtain_lease_for(project_id) + Rails.logger. + info("Obtained ProjectCacheWorker lease for project #{project_id}") + else + Rails.logger. + info("Could not obtain ProjectCacheWorker lease for project #{project_id}") + + return + end + + update_caches(project_id) + end + + def update_caches(project_id) project = Project.find(project_id) return unless project.repository.exists? @@ -15,4 +36,10 @@ class ProjectCacheWorker project.repository.build_cache end end + + def try_obtain_lease_for(project_id) + Gitlab::ExclusiveLease. + new("project_cache_worker:#{project_id}", timeout: LEASE_TIMEOUT). + try_obtain + end end diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index 5785a6a06ff..f5b60b90d11 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -6,21 +6,39 @@ describe ProjectCacheWorker do subject { described_class.new } describe '#perform' do - it 'updates project cache data' do - expect_any_instance_of(Repository).to receive(:size) - expect_any_instance_of(Repository).to receive(:commit_count) + context 'when an exclusive lease can be obtained' do + before do + allow(subject).to receive(:try_obtain_lease_for).with(project.id). + and_return(true) + end - expect_any_instance_of(Project).to receive(:update_repository_size) - expect_any_instance_of(Project).to receive(:update_commit_count) + it 'updates project cache data' do + expect_any_instance_of(Repository).to receive(:size) + expect_any_instance_of(Repository).to receive(:commit_count) - subject.perform(project.id) + expect_any_instance_of(Project).to receive(:update_repository_size) + expect_any_instance_of(Project).to receive(:update_commit_count) + + subject.perform(project.id) + end + + it 'handles missing repository data' do + expect_any_instance_of(Repository).to receive(:exists?).and_return(false) + expect_any_instance_of(Repository).not_to receive(:size) + + subject.perform(project.id) + end end - it 'handles missing repository data' do - expect_any_instance_of(Repository).to receive(:exists?).and_return(false) - expect_any_instance_of(Repository).not_to receive(:size) + context 'when an exclusive lease can not be obtained' do + it 'does nothing' do + allow(subject).to receive(:try_obtain_lease_for).with(project.id). + and_return(false) + + expect(subject).not_to receive(:update_caches) - subject.perform(project.id) + subject.perform(project.id) + end end end end -- cgit v1.2.1 From 374071321d0cfb7a161ec38e85e27e1d46ae7c9a Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 19 Oct 2016 17:13:04 +0100 Subject: Fix the merge request view when source projects or branches are removed --- app/controllers/projects/merge_requests_controller.rb | 2 +- app/helpers/merge_requests_helper.rb | 10 +++++++--- app/models/merge_request.rb | 15 ++++++--------- app/views/projects/merge_requests/_show.html.haml | 13 ++++++++----- spec/features/merge_requests/created_from_fork_spec.rb | 14 ++++++++++++++ 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 0f593d1a936..55ea31e48a0 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -554,7 +554,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_pipelines_vars @pipelines = @merge_request.all_pipelines - if @pipelines.any? + if @pipelines.present? @pipeline = @pipelines.first @statuses = @pipeline.statuses.relevant end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 249cb44e9d5..a6659ea2fd1 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -86,11 +86,15 @@ module MergeRequestsHelper end def source_branch_with_namespace(merge_request) - branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch)) + namespace = merge_request.source_project_namespace + branch = merge_request.source_branch + + if merge_request.source_branch_exists? + namespace = link_to(namespace, project_path(merge_request.source_project)) + branch = link_to(branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch)) + end if merge_request.for_fork? - namespace = link_to(merge_request.source_project_namespace, - project_path(merge_request.source_project)) namespace + ":" + branch else branch diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index d32bc9f882f..45fa8af60e5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -333,7 +333,7 @@ class MergeRequest < ActiveRecord::Base end def closed_without_fork? - closed? && (forked_source_project_missing? || !source_project) + closed? && forked_source_project_missing? end def forked_source_project_missing? @@ -344,7 +344,7 @@ class MergeRequest < ActiveRecord::Base end def reopenable? - source_branch_exists? && closed? + closed? && !source_project_missing? && source_branch_exists? end def ensure_merge_request_diff @@ -656,7 +656,7 @@ class MergeRequest < ActiveRecord::Base end def has_ci? - source_project.ci_service && commits.any? + source_project.try(:ci_service) && commits.any? end def branch_missing? @@ -688,12 +688,9 @@ class MergeRequest < ActiveRecord::Base @environments ||= begin - environments = source_project.environments_for( - source_branch, diff_head_commit) - environments += target_project.environments_for( - target_branch, diff_head_commit, with_tags: true) - - environments.uniq + envs = target_project.environments_for(target_branch, diff_head_commit, with_tags: true) + envs.concat(source_project.environments_for(source_branch, diff_head_commit)) if source_project + envs.uniq end end diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index fe90383b00f..0e19d224fcd 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -35,7 +35,9 @@ - if @merge_request.open? && @merge_request.diverged_from_target_branch? %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) - = render "projects/merge_requests/show/how_to_merge" + - if @merge_request.source_branch_exists? + = render "projects/merge_requests/show/how_to_merge" + = render "projects/merge_requests/widget/show.html.haml" - if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user) @@ -50,10 +52,11 @@ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion %span.badge= @merge_request.mr_and_commit_notes.user.count - %li.commits-tab - = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do - Commits - %span.badge= @commits_count + - if @merge_request.source_project + %li.commits-tab + = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do + Commits + %span.badge= @commits_count - if @pipeline %li.pipelines-tab = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb index a506624b30d..cfc1244429f 100644 --- a/spec/features/merge_requests/created_from_fork_spec.rb +++ b/spec/features/merge_requests/created_from_fork_spec.rb @@ -25,6 +25,20 @@ feature 'Merge request created from fork' do expect(page).to have_content 'Test merge request' end + context 'source project is deleted' do + background do + MergeRequests::MergeService.new(project, user).execute(merge_request) + fork_project.destroy! + end + + scenario 'user can access merge request' do + visit_merge_request(merge_request) + + expect(page).to have_content 'Test merge request' + expect(page).to have_content "(removed):#{merge_request.source_branch}" + end + end + context 'pipeline present in source project' do include WaitForAjax -- cgit v1.2.1 From 6f846fcbe8fa129e240ae58c8d4bbb1e1a82bbef Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 19 Oct 2016 17:48:30 +0100 Subject: Fix two CI endpoints for MRs where the source project is deleted --- app/controllers/projects/merge_requests_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 55ea31e48a0..2ee53f7ceda 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -398,7 +398,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController status ||= "preparing" else - ci_service = @merge_request.source_project.ci_service + ci_service = @merge_request.source_project.try(:ci_service) status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service if ci_service.respond_to?(:commit_coverage) -- cgit v1.2.1 From 61536ed2cff454bb2e3db8b7ca9ee09cc407461f Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 19 Oct 2016 19:33:51 +0100 Subject: Rename forked_source_project_missing? to source_project_missing? --- app/models/merge_request.rb | 6 +++--- spec/models/merge_request_spec.rb | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 45fa8af60e5..c476a3bb14e 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -326,17 +326,17 @@ class MergeRequest < ActiveRecord::Base def validate_fork return true unless target_project && source_project return true if target_project == source_project - return true unless forked_source_project_missing? + return true unless source_project_missing? errors.add :validate_fork, 'Source project is not a fork of the target project' end def closed_without_fork? - closed? && forked_source_project_missing? + closed? && source_project_missing? end - def forked_source_project_missing? + def source_project_missing? return false unless for_fork? return true unless source_project diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index ee003a9d18f..6e5137602aa 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1198,7 +1198,7 @@ describe MergeRequest, models: true do end end - describe "#forked_source_project_missing?" do + describe "#source_project_missing?" do let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } let(:user) { create(:user) } @@ -1211,13 +1211,13 @@ describe MergeRequest, models: true do target_project: project) end - it { expect(merge_request.forked_source_project_missing?).to be_falsey } + it { expect(merge_request.source_project_missing?).to be_falsey } end context "when the source project is the same as the target project" do let(:merge_request) { create(:merge_request, source_project: project) } - it { expect(merge_request.forked_source_project_missing?).to be_falsey } + it { expect(merge_request.source_project_missing?).to be_falsey } end context "when the fork does not exist" do @@ -1231,7 +1231,7 @@ describe MergeRequest, models: true do unlink_project.execute merge_request.reload - expect(merge_request.forked_source_project_missing?).to be_truthy + expect(merge_request.source_project_missing?).to be_truthy end end end -- cgit v1.2.1 From 0bbfdde64d3de61df1f87e2394c0f459512c7f51 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 19 Oct 2016 18:09:33 +0100 Subject: Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b018fc0d57..edaa1432757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Fix Error 500 when viewing old merge requests with bad diff data - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) + - Fix viewing merged MRs when the source project has been removed !6991 - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService -- cgit v1.2.1 From 8815748feb0c9bbef9b04f07f09645ba94ea4d63 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 13 Oct 2016 13:58:31 -0500 Subject: Change input order on Sign In form for better tabbing. This *unreverts* 8751491b, which was mistakenly reverted in !6328. It also changes the implementation of the original commit to work with the new login styling and markup. cc: @ClemMakesApps --- app/assets/stylesheets/pages/login.scss | 17 +++++++++++++++++ app/views/devise/sessions/_new_base.html.haml | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index e6d9be5185d..bdb13bee178 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -53,6 +53,7 @@ margin: 0 0 10px; } + .login-footer { margin-top: 10px; @@ -246,3 +247,19 @@ padding: 65px; // height of footer + bottom padding of email confirmation link } } + +// For sign in pane only, to improve tab order, the following removes the submit button from +// normal document flow and pins it to the bottom of the form. For context, see !6867 & !6928 + +.login-box { + .new_user { + position: relative; + padding-bottom: 35px; + } + + .move-submit-down { + position: absolute; + width: 100%; + bottom: 0; + } +} diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index a96b579c593..525e7d99d71 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -5,6 +5,8 @@ %div.form-group = f.label :password = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required." + %div.submit-container.move-submit-down + = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "user_remember_me"} @@ -12,5 +14,3 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) - %div.submit-container - = f.submit "Sign in", class: "btn btn-save" -- cgit v1.2.1 From c1212beaa495b5b0c3b805bb1b2a7bdc6d1a941b Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 19 Oct 2016 14:49:09 +0200 Subject: Use deployment IID when saving refs --- app/models/deployment.rb | 2 +- app/models/environment.rb | 4 ++-- spec/controllers/projects/merge_requests_controller_spec.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 1f8c5fb3d85..c843903877b 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -102,6 +102,6 @@ class Deployment < ActiveRecord::Base private def ref_path - File.join(environment.ref_path, 'deployments', id.to_s) + File.join(environment.ref_path, 'deployments', iid.to_s) end end diff --git a/app/models/environment.rb b/app/models/environment.rb index d575f1dc73a..73f415c0ef0 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -71,8 +71,8 @@ class Environment < ActiveRecord::Base return nil unless ref - deployment_id = ref.split('/').last - deployments.find(deployment_id) + deployment_iid = ref.split('/').last + deployments.find_by(iid: deployment_iid) end def ref_path diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index d6980471ea4..940d54f8686 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -913,7 +913,7 @@ describe Projects::MergeRequestsController do end describe 'GET ci_environments_status' do - context 'when the environment is from a forked project' do + context 'the environment is from a forked project' do let!(:forked) { create(:project) } let!(:environment) { create(:environment, project: forked) } let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } -- cgit v1.2.1 From 55365d368a069d6598f58d71d8891d6c8f06ea66 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 20 Oct 2016 14:21:40 +0200 Subject: Only create refs for new deployments This patch makes sure GitLab does not save the refs to the filesystem each time the deployment is updated. This will save some IO although I expect the impact to be minimal. --- app/models/deployment.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index c843903877b..91d85c2279b 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -11,7 +11,7 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true - after_save :create_ref + after_create :create_ref def commit project.commit(sha) -- cgit v1.2.1 From b5d210c9d6d44fc664b006567b8f488e198ad04f Mon Sep 17 00:00:00 2001 From: Airat Shigapov Date: Thu, 15 Sep 2016 18:45:22 +0300 Subject: Render hipchat notification descriptions as HTML instead of raw markdown --- app/models/project_services/hipchat_service.rb | 9 +++------ spec/models/project_services/hipchat_service_spec.rb | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index afebd3b6a12..98f0312d84e 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -144,8 +144,7 @@ class HipchatService < Service message = "#{user_name} #{state} #{issue_link} in #{project_link}: #{title}" if description - description = format_body(description) - message << description + message << format_body(Banzai::Filter::MarkdownFilter.renderer.render(description)) end message @@ -167,8 +166,7 @@ class HipchatService < Service "#{project_link}: #{title}" if description - description = format_body(description) - message << description + message << format_body(Banzai::Filter::MarkdownFilter.renderer.render(description)) end message @@ -219,8 +217,7 @@ class HipchatService < Service message << title if note - note = format_body(note) - message << note + message << format_body(Banzai::Filter::MarkdownFilter.renderer.render(note)) end message diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 26dd95bdfec..90066f01f6f 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -117,7 +117,7 @@ describe HipchatService, models: true do end context 'issue events' do - let(:issue) { create(:issue, title: 'Awesome issue', description: 'please fix') } + let(:issue) { create(:issue, title: 'Awesome issue', description: '**please** fix') } let(:issue_service) { Issues::CreateService.new(project, user) } let(:issues_sample_data) { issue_service.hook_data(issue, 'open') } @@ -135,12 +135,12 @@ describe HipchatService, models: true do "issue ##{obj_attr["iid"]} in " \ "#{project_name}: " \ "Awesome issue" \ - "
      please fix
      ") + "

      please fix

      \n
      ") end end context 'merge request events' do - let(:merge_request) { create(:merge_request, description: 'please fix', title: 'Awesome merge request', target_project: project, source_project: project) } + let(:merge_request) { create(:merge_request, description: '**please** fix', title: 'Awesome merge request', target_project: project, source_project: project) } let(:merge_service) { MergeRequests::CreateService.new(project, user) } let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') } @@ -159,7 +159,7 @@ describe HipchatService, models: true do "merge request !#{obj_attr["iid"]} in " \ "#{project_name}: " \ "Awesome merge request" \ - "
      please fix
      ") + "

      please fix

      \n
      ") end end @@ -190,7 +190,7 @@ describe HipchatService, models: true do "commit #{commit_id} in " \ "#{project_name}: " \ "#{title}" \ - "
      a comment on a commit
      ") + "

      a comment on a commit

      \n
      ") end end @@ -203,7 +203,7 @@ describe HipchatService, models: true do let(:merge_request_note) do create(:note_on_merge_request, noteable: merge_request, project: project, - note: "merge request note") + note: "merge request **note**") end it "calls Hipchat API for merge request comment events" do @@ -222,7 +222,7 @@ describe HipchatService, models: true do "merge request !#{merge_id} in " \ "#{project_name}: " \ "#{title}" \ - "
      merge request note
      ") + "

      merge request note

      \n
      ") end end @@ -230,7 +230,7 @@ describe HipchatService, models: true do let(:issue) { create(:issue, project: project) } let(:issue_note) do create(:note_on_issue, noteable: issue, project: project, - note: "issue note") + note: "issue **note**") end it "calls Hipchat API for issue comment events" do @@ -247,7 +247,7 @@ describe HipchatService, models: true do "issue ##{issue_id} in " \ "#{project_name}: " \ "#{title}" \ - "
      issue note
      ") + "

      issue note

      \n
      ") end end @@ -275,7 +275,7 @@ describe HipchatService, models: true do "snippet ##{snippet_id} in " \ "#{project_name}: " \ "#{title}" \ - "
      snippet note
      ") + "

      snippet note

      \n
      ") end end end -- cgit v1.2.1 From 3d034c15017b45611cb3e52744f09cc45c1c7bbe Mon Sep 17 00:00:00 2001 From: David Eisner Date: Tue, 4 Oct 2016 12:28:42 +0100 Subject: Full Banzai rendering for HipChat notifications --- app/models/project_services/hipchat_service.rb | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 98f0312d84e..f4edbbbceb2 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -121,14 +121,6 @@ class HipchatService < Service message end - def format_body(body) - if body - body = body.truncate(200, separator: ' ', omission: '...') - end - - "
      #{body}
      " - end - def create_issue_message(data) user_name = data[:user][:name] @@ -144,7 +136,7 @@ class HipchatService < Service message = "#{user_name} #{state} #{issue_link} in #{project_link}: #{title}" if description - message << format_body(Banzai::Filter::MarkdownFilter.renderer.render(description)) + message << Banzai.render(note, project: project) end message @@ -166,7 +158,7 @@ class HipchatService < Service "#{project_link}: #{title}" if description - message << format_body(Banzai::Filter::MarkdownFilter.renderer.render(description)) + message << Banzai.render(note, project: project) end message @@ -217,7 +209,7 @@ class HipchatService < Service message << title if note - message << format_body(Banzai::Filter::MarkdownFilter.renderer.render(note)) + message << Banzai.render(note, project: project) end message -- cgit v1.2.1 From 8e0c868436db61be8020356f4792cc79c2964363 Mon Sep 17 00:00:00 2001 From: David Eisner Date: Tue, 4 Oct 2016 12:38:31 +0100 Subject: Also render commit titles in HipChat notifications --- app/models/project_services/hipchat_service.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index f4edbbbceb2..7d70452a70b 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -88,6 +88,10 @@ class HipchatService < Service end end + def render_line(text) + Banzai.render(text.lines.first.chomp, project: project, pipeline: :single_line) if text + end + def create_push_message(push) ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch' ref = Gitlab::Git.ref_name(push[:ref]) @@ -110,7 +114,7 @@ class HipchatService < Service message << "(Compare changes)" push[:commits].take(MAX_COMMITS).each do |commit| - message << "
      - #{commit[:message].lines.first} (#{commit[:id][0..5]})" + message << "
      - #{render_line(commit[:message])} (#{commit[:id][0..5]})" end if push[:commits].count > MAX_COMMITS @@ -126,7 +130,7 @@ class HipchatService < Service obj_attr = data[:object_attributes] obj_attr = HashWithIndifferentAccess.new(obj_attr) - title = obj_attr[:title] + title = render_line(obj_attr[:title]) state = obj_attr[:state] issue_iid = obj_attr[:iid] issue_url = obj_attr[:url] @@ -150,7 +154,7 @@ class HipchatService < Service merge_request_id = obj_attr[:iid] state = obj_attr[:state] description = obj_attr[:description] - title = obj_attr[:title] + title = render_line(obj_attr[:title]) merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}" merge_request_link = "merge request !#{merge_request_id}" @@ -165,7 +169,7 @@ class HipchatService < Service end def format_title(title) - "" + title.lines.first.chomp + "" + "#{render_line(title)}" end def create_note_message(data) -- cgit v1.2.1 From eb074021b0bfeed139e098d06d45b562b98f6214 Mon Sep 17 00:00:00 2001 From: David Eisner Date: Tue, 4 Oct 2016 16:23:16 +0100 Subject: Absolute URLs for Banzai HTML for HipChat Using "pipeline: :email" gets "only_path: false" into the context to produce full URLs instead of /namespace/project/... --- app/models/project_services/hipchat_service.rb | 32 +++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 7d70452a70b..9d52a1423b7 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -89,7 +89,7 @@ class HipchatService < Service end def render_line(text) - Banzai.render(text.lines.first.chomp, project: project, pipeline: :single_line) if text + markdown(text.lines.first.chomp, pipeline: :single_line) if text end def create_push_message(push) @@ -125,6 +125,20 @@ class HipchatService < Service message end + def markdown(text, context = {}) + if text + context = ({ + project: project, + pipeline: :email + }).merge(context) + + html = Banzai.render(text, context) + html = Banzai.post_process(html, context) + else + "" + end + end + def create_issue_message(data) user_name = data[:user][:name] @@ -139,9 +153,7 @@ class HipchatService < Service issue_link = "issue ##{issue_iid}" message = "#{user_name} #{state} #{issue_link} in #{project_link}: #{title}" - if description - message << Banzai.render(note, project: project) - end + message << markdown(description) message end @@ -161,9 +173,7 @@ class HipchatService < Service message = "#{user_name} #{state} #{merge_request_link} in " \ "#{project_link}: #{title}" - if description - message << Banzai.render(note, project: project) - end + message << markdown(description) message end @@ -180,11 +190,13 @@ class HipchatService < Service note = obj_attr[:note] note_url = obj_attr[:url] noteable_type = obj_attr[:noteable_type] + commit_id = nil case noteable_type when "Commit" commit_attr = HashWithIndifferentAccess.new(data[:commit]) - subject_desc = commit_attr[:id] + commit_id = commit_attr[:id] + subject_desc = commit_id subject_desc = Commit.truncate_sha(subject_desc) subject_type = "commit" title = format_title(commit_attr[:message]) @@ -212,9 +224,7 @@ class HipchatService < Service message = "#{user_name} commented on #{subject_html} in #{project_link}: " message << title - if note - message << Banzai.render(note, project: project) - end + message << markdown(note, ref: commit_id) message end -- cgit v1.2.1 From b434b75fd0a5486325dabcf0a2edf652c959675b Mon Sep 17 00:00:00 2001 From: David Eisner Date: Tue, 4 Oct 2016 16:25:45 +0100 Subject: Ensure absolute URLs for single lines from Banzai for HipChat "pipeline: :single_line" leaves the protocol/host part out of the URLs and caches them that way. To avoid giving those out to HipChat, markdown is always rendered with "pipeline: :email" first. There must be a better way to do this, but I can't see how to avoid the link caching. --- app/models/project_services/hipchat_service.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 9d52a1423b7..ce4a2a96015 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -125,12 +125,16 @@ class HipchatService < Service message end - def markdown(text, context = {}) + def markdown(text, options = {}) if text - context = ({ + context = { project: project, pipeline: :email - }).merge(context) + } + + Banzai.render(text, context) + + context.merge!(options) html = Banzai.render(text, context) html = Banzai.post_process(html, context) -- cgit v1.2.1 From aa2406e0f821e217ed5e0c59a212cecd73227509 Mon Sep 17 00:00:00 2001 From: David Eisner Date: Tue, 4 Oct 2016 16:27:40 +0100 Subject: Clean up Banzai HTML for HipChat The `class` and `data-*` attributes are meaningless in HipChat, and it would probably be better to limit the tags, too. For example, we could avoid block-level elements in `render_line`. --- app/models/project_services/hipchat_service.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index ce4a2a96015..8988a7b905e 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -1,4 +1,6 @@ class HipchatService < Service + include ActionView::Helpers::SanitizeHelper + MAX_COMMITS = 3 prop_accessor :token, :room, :server, :notify, :color, :api_version @@ -138,6 +140,7 @@ class HipchatService < Service html = Banzai.render(text, context) html = Banzai.post_process(html, context) + sanitize html, attributes: %w(href title alt) else "" end -- cgit v1.2.1 From 32cf2e5f77c24c27782adc37d4635b06dfada060 Mon Sep 17 00:00:00 2001 From: David Eisner Date: Wed, 5 Oct 2016 13:38:08 +0100 Subject: Tests for markdown HipChat notifications --- spec/models/project_services/hipchat_service_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 90066f01f6f..1029b6d2459 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -135,7 +135,7 @@ describe HipchatService, models: true do "issue ##{obj_attr["iid"]} in " \ "#{project_name}: " \ "Awesome issue" \ - "

      please fix

      \n
      ") + "

      please fix

      ") end end @@ -159,7 +159,7 @@ describe HipchatService, models: true do "merge request !#{obj_attr["iid"]} in " \ "#{project_name}: " \ "Awesome merge request" \ - "

      please fix

      \n
      ") + "

      please fix

      ") end end @@ -190,7 +190,7 @@ describe HipchatService, models: true do "commit #{commit_id} in " \ "#{project_name}: " \ "#{title}" \ - "

      a comment on a commit

      \n
      ") + "

      a comment on a commit

      ") end end @@ -222,7 +222,7 @@ describe HipchatService, models: true do "merge request !#{merge_id} in " \ "#{project_name}: " \ "#{title}" \ - "

      merge request note

      \n
      ") + "

      merge request note

      ") end end @@ -247,7 +247,7 @@ describe HipchatService, models: true do "issue ##{issue_id} in " \ "#{project_name}: " \ "#{title}" \ - "

      issue note

      \n
      ") + "

      issue note

      ") end end @@ -275,7 +275,7 @@ describe HipchatService, models: true do "snippet ##{snippet_id} in " \ "#{project_name}: " \ "#{title}" \ - "

      snippet note

      \n
      ") + "

      snippet note

      ") end end end -- cgit v1.2.1 From 7b90680c2dd78d88d747fff545d69b1908ced7ae Mon Sep 17 00:00:00 2001 From: Airat Shigapov Date: Wed, 12 Oct 2016 09:51:02 +0300 Subject: Use guard clause instead of if-else statement --- app/models/project_services/hipchat_service.rb | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 8988a7b905e..e7a77070b9f 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -128,22 +128,21 @@ class HipchatService < Service end def markdown(text, options = {}) - if text - context = { - project: project, - pipeline: :email - } + return "" unless text - Banzai.render(text, context) + context = { + project: project, + pipeline: :email + } - context.merge!(options) + Banzai.render(text, context) - html = Banzai.render(text, context) - html = Banzai.post_process(html, context) - sanitize html, attributes: %w(href title alt) - else - "" - end + context.merge!(options) + + html = Banzai.render(text, context) + html = Banzai.post_process(html, context) + + sanitize html, attributes: %w(href title alt) end def create_issue_message(data) -- cgit v1.2.1 From 257d15a67007f7c8750ca6d00a7c13f8e8a9f974 Mon Sep 17 00:00:00 2001 From: Airat Shigapov Date: Wed, 19 Oct 2016 22:51:15 +0300 Subject: Return truncation for notification descriptions, fix minor bugs with rendering --- app/models/project_services/hipchat_service.rb | 17 +++++++++++------ spec/models/project_services/hipchat_service_spec.rb | 12 ++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index e7a77070b9f..660a8ae3421 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -2,6 +2,11 @@ class HipchatService < Service include ActionView::Helpers::SanitizeHelper MAX_COMMITS = 3 + HIPCHAT_ALLOWED_TAGS = %w[ + a b i strong em br img pre code + table th tr td caption colgroup col thead tbody tfoot + ul ol li dl dt dd + ] prop_accessor :token, :room, :server, :notify, :color, :api_version boolean_accessor :notify_only_broken_builds @@ -139,10 +144,10 @@ class HipchatService < Service context.merge!(options) - html = Banzai.render(text, context) - html = Banzai.post_process(html, context) + html = Banzai.post_process(Banzai.render(text, context), context) + sanitized_html = sanitize(html, tags: HIPCHAT_ALLOWED_TAGS, attributes: %w[href title alt]) - sanitize html, attributes: %w(href title alt) + sanitized_html.truncate(200, separator: ' ', omission: '...') end def create_issue_message(data) @@ -159,7 +164,7 @@ class HipchatService < Service issue_link = "issue ##{issue_iid}" message = "#{user_name} #{state} #{issue_link} in #{project_link}: #{title}" - message << markdown(description) + message << "
      #{markdown(description)}
      " message end @@ -179,7 +184,7 @@ class HipchatService < Service message = "#{user_name} #{state} #{merge_request_link} in " \ "#{project_link}: #{title}" - message << markdown(description) + message << "
      #{markdown(description)}
      " message end @@ -230,7 +235,7 @@ class HipchatService < Service message = "#{user_name} commented on #{subject_html} in #{project_link}: " message << title - message << markdown(note, ref: commit_id) + message << "
      #{markdown(note, ref: commit_id)}
      " message end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 1029b6d2459..2da3a9cb09f 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -135,7 +135,7 @@ describe HipchatService, models: true do "issue ##{obj_attr["iid"]} in " \ "#{project_name}: " \ "Awesome issue" \ - "

      please fix

      ") + "
      please fix
      ") end end @@ -159,7 +159,7 @@ describe HipchatService, models: true do "merge request !#{obj_attr["iid"]} in " \ "#{project_name}: " \ "Awesome merge request" \ - "

      please fix

      ") + "
      please fix
      ") end end @@ -190,7 +190,7 @@ describe HipchatService, models: true do "commit #{commit_id} in " \ "#{project_name}: " \ "#{title}" \ - "

      a comment on a commit

      ") + "
      a comment on a commit
      ") end end @@ -222,7 +222,7 @@ describe HipchatService, models: true do "merge request !#{merge_id} in " \ "#{project_name}: " \ "#{title}" \ - "

      merge request note

      ") + "
      merge request note
      ") end end @@ -247,7 +247,7 @@ describe HipchatService, models: true do "issue ##{issue_id} in " \ "#{project_name}: " \ "#{title}" \ - "

      issue note

      ") + "
      issue note
      ") end end @@ -275,7 +275,7 @@ describe HipchatService, models: true do "snippet ##{snippet_id} in " \ "#{project_name}: " \ "#{title}" \ - "

      snippet note

      ") + "
      snippet note
      ") end end end -- cgit v1.2.1 From db7c227fc64de0221d061d58387a93c6925cded7 Mon Sep 17 00:00:00 2001 From: Airat Shigapov Date: Thu, 20 Oct 2016 15:12:39 +0300 Subject: Add CHANGELOG.md entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98cffab8c03..9135b851f96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) - Adds user project membership expired event to clarify why user was removed (Callum Dryden) + - Fix HipChat notifications rendering (airatshigapov, eisnerd) - Simpler arguments passed to named_route on toggle_award_url helper method ## 8.13.0 (2016-10-22) -- cgit v1.2.1 From d1f6dfc863f1560f7e6e1a30e846304979679a61 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 20 Oct 2016 20:47:21 +0800 Subject: We want to release this in 8.13.0 --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6e6690ced1..4fd0b763e48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) - - Fix discussion thread from emails for merge requests. !7010 - ## 8.13.0 (2016-10-22) - Fix save button on project pipeline settings page. (!6955) @@ -40,6 +38,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService + - Fix discussion thread from emails for merge requests. !7010 - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment -- cgit v1.2.1 From 2e411b5ea0faf733aa457ecc28064bb48f07deed Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Wed, 19 Oct 2016 15:35:38 +0200 Subject: Test GitLab project import for a user with only their default namespace. Refactor the spec file: - remove hardcoded record IDs - avoid top-level let if not used in all scenarios - prefer expect { ... }.to change { ... }.from(0).to(1) over checking that there are no records at the beginning of the test --- .../projects/import_export/import_file_spec.rb | 53 ++++++++++++---------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index f32834801a0..3015576f6f8 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' feature 'Import/Export - project import integration test', feature: true, js: true do include Select2Helper - let(:admin) { create(:admin) } - let(:normal_user) { create(:user) } - let!(:namespace) { create(:namespace, name: "asd", owner: admin) } let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } let(:export_path) { "#{Dir::tmpdir}/import_file_spec" } - let(:project) { Project.last } - let(:project_hook) { Gitlab::Git::Hook.new('post-receive', project.repository.path) } background do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) @@ -19,41 +14,43 @@ feature 'Import/Export - project import integration test', feature: true, js: tr FileUtils.rm_rf(export_path, secure: true) end - context 'admin user' do + context 'when selecting the namespace' do + let(:user) { create(:admin) } + let!(:namespace) { create(:namespace, name: "asd", owner: user) } + before do - login_as(admin) + login_as(user) end scenario 'user imports an exported project successfully' do - expect(Project.all.count).to be_zero - visit new_project_path - select2('2', from: '#project_namespace_id') + select2(namespace.id, from: '#project_namespace_id') fill_in :project_path, with: 'test-project-path', visible: true click_link 'GitLab export' expect(page).to have_content('GitLab project export') - expect(URI.parse(current_url).query).to eq('namespace_id=2&path=test-project-path') + expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=test-project-path") attach_file('file', file) - click_on 'Import project' # import starts + expect { click_on 'Import project' }.to change { Project.count }.from(0).to(1) + project = Project.last expect(project).not_to be_nil expect(project.issues).not_to be_empty expect(project.merge_requests).not_to be_empty - expect(project_hook).to exist - expect(wiki_exists?).to be true + expect(project_hook_exists?(project)).to be true + expect(wiki_exists?(project)).to be true expect(project.import_status).to eq('finished') end scenario 'invalid project' do - project = create(:project, namespace_id: 2) + project = create(:project, namespace: namespace) visit new_project_path - select2('2', from: '#project_namespace_id') + select2(namespace.id, from: '#project_namespace_id') fill_in :project_path, with: project.name, visible: true click_link 'GitLab export' @@ -66,11 +63,11 @@ feature 'Import/Export - project import integration test', feature: true, js: tr end scenario 'project with no name' do - create(:project, namespace_id: 2) + create(:project, namespace: namespace) visit new_project_path - select2('2', from: '#project_namespace_id') + select2(namespace.id, from: '#project_namespace_id') # click on disabled element find(:link, 'GitLab export').trigger('click') @@ -81,24 +78,30 @@ feature 'Import/Export - project import integration test', feature: true, js: tr end end - context 'normal user' do + context 'when limited to the default user namespace' do + let(:user) { create(:user) } before do - login_as(normal_user) + login_as(user) end - scenario 'non-admin user is allowed to import a project' do - expect(Project.all.count).to be_zero - + scenario 'passes correct namespace ID in the URL' do visit new_project_path fill_in :project_path, with: 'test-project-path', visible: true - expect(page).to have_content('GitLab export') + click_link 'GitLab export' + + expect(page).to have_content('GitLab project export') + expect(URI.parse(current_url).query).to eq("namespace_id=#{user.namespace.id}&path=test-project-path") end end - def wiki_exists? + def wiki_exists?(project) wiki = ProjectWiki.new(project) File.exist?(wiki.repository.path_to_repo) && !wiki.repository.empty? end + + def project_hook_exists?(project) + Gitlab::Git::Hook.new('post-receive', project.repository.path).exists? + end end -- cgit v1.2.1 From 474313e44034882122c60329b7c72b0f31cac45b Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Wed, 19 Oct 2016 20:42:35 +0200 Subject: Fix GitLab project import when a user has access only to their default namespace. Render a hidden field with namespace ID so it can be read by JavaScript and passed to "/import/gitlab_project/new" screen. --- app/views/projects/new.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index cc8cb134fb8..399ccf15b7f 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -27,6 +27,7 @@ - else .input-group-addon.static-namespace #{root_url}#{current_user.username}/ + = f.hidden_field :namespace_id, value: current_user.namespace_id .form-group.col-xs-12.col-sm-6.project-path = f.label :namespace_id, class: 'label-light' do %span -- cgit v1.2.1 From e9218c7e4e6c46d72d0860ba1107643a9f15f542 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Wed, 19 Oct 2016 10:04:05 +0200 Subject: Change target Ruby version for Rubocop to 2.1. We have to use the lowest common denominator to check the supported syntax and in our case it is Ruby 2.1. Please note that it will not help with unsupported syntax in HAML files because they are not checked by Rubocop. --- .rubocop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index bec2464c740..13df3f99613 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,7 +5,7 @@ require: inherit_from: .rubocop_todo.yml AllCops: - TargetRubyVersion: 2.3 + TargetRubyVersion: 2.1 # Cop names are not d§splayed in offense messages by default. Change behavior # by overriding DisplayCopNames, or by giving the -D/--display-cop-names # option. -- cgit v1.2.1 From 0c0caede85b0ea6082799ae9fa6afd74a1186006 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 20 Oct 2016 17:33:12 +0300 Subject: Fix: Backup restore doesn't clear cache --- CHANGELOG.md | 1 + lib/tasks/gitlab/backup.rake | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98cffab8c03..20aef16b105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) - Adds user project membership expired event to clarify why user was removed (Callum Dryden) - Simpler arguments passed to named_route on toggle_award_url helper method + - Fix: Backup restore doesn't clear cache ## 8.13.0 (2016-10-22) diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index b43ee5b3383..a9f1255e8cf 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -51,6 +51,7 @@ namespace :gitlab do $progress.puts 'done'.color(:green) Rake::Task['gitlab:backup:db:restore'].invoke end + Rake::Task['gitlab:backup:repo:restore'].invoke unless backup.skipped?('repositories') Rake::Task['gitlab:backup:uploads:restore'].invoke unless backup.skipped?('uploads') Rake::Task['gitlab:backup:builds:restore'].invoke unless backup.skipped?('builds') @@ -58,6 +59,7 @@ namespace :gitlab do Rake::Task['gitlab:backup:lfs:restore'].invoke unless backup.skipped?('lfs') Rake::Task['gitlab:backup:registry:restore'].invoke unless backup.skipped?('registry') Rake::Task['gitlab:shell:setup'].invoke + Rake::Task['cache:clear'].invoke backup.cleanup end -- cgit v1.2.1 From 23e81bfde879dfd116cb70cb8034d0fce21fffb6 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Thu, 20 Oct 2016 16:40:24 +0200 Subject: Ignore external issues when bulk assigning issues to author of merge request. Fixes #23552 --- app/services/merge_requests/assign_issues_service.rb | 2 +- spec/services/merge_requests/assign_issues_service_spec.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb index f636e5fec4f..066efa1acc3 100644 --- a/app/services/merge_requests/assign_issues_service.rb +++ b/app/services/merge_requests/assign_issues_service.rb @@ -4,7 +4,7 @@ module MergeRequests @assignable_issues ||= begin if current_user == merge_request.author closes_issues.select do |issue| - !issue.assignee_id? && can?(current_user, :admin_issue, issue) + !issue.is_a?(ExternalIssue) && !issue.assignee_id? && can?(current_user, :admin_issue, issue) end else [] diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb index 7aeb95a15ea..5034b6ef33f 100644 --- a/spec/services/merge_requests/assign_issues_service_spec.rb +++ b/spec/services/merge_requests/assign_issues_service_spec.rb @@ -46,4 +46,16 @@ describe MergeRequests::AssignIssuesService, services: true do it 'assigns these to the merge request owner' do expect { service.execute }.to change { issue.reload.assignee }.to(user) end + + it 'ignores external issues' do + external_issue = ExternalIssue.new('JIRA-123', project) + service = described_class.new( + project, + user, + merge_request: merge_request, + closes_issues: [external_issue] + ) + + expect(service.assignable_issues.count).to eq 0 + end end -- cgit v1.2.1 From fab393984a4685164886c974747d312da21e1798 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 20 Oct 2016 14:15:39 -0200 Subject: [ci skip] Add a comment explaining validate_board_limit callback Callback associations are not common to see around. We want to make clear that the `before_add` callback uses the number before the addition, in this particular case 1. --- app/models/project.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 6685baab699..af117f0acb0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1339,6 +1339,13 @@ class Project < ActiveRecord::Base shared_projects.any? end + # Similar to the normal callbacks that hook into the life cycle of an + # Active Record object, you can also define callbacks that get triggered + # when you add an object to an association collection. If any of these + # callbacks throw an exception, the object will not be added to the + # collection. Before you add a new board to the boards collection if you + # already have 1, 2, or n it will fail, but it if you have 0 that is lower + # than the number of permitted boards per project it won't fail. def validate_board_limit(board) raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS end -- cgit v1.2.1 From 3bace66970ff5cd71107f24511447d8c5a72715f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 14 Oct 2016 17:25:12 -0500 Subject: Create protected branches bundle --- .../protected_branch_access_dropdown.js.es6 | 28 -------- .../javascripts/protected_branch_create.js.es6 | 54 --------------- .../javascripts/protected_branch_dropdown.js.es6 | 76 ---------------------- .../javascripts/protected_branch_edit.js.es6 | 65 ------------------ .../javascripts/protected_branch_edit_list.js.es6 | 17 ----- .../protected_branch_access_dropdown.js.es6 | 28 ++++++++ .../protected_branch_create.js.es6 | 54 +++++++++++++++ .../protected_branch_dropdown.js.es6 | 76 ++++++++++++++++++++++ .../protected_branch_edit.js.es6 | 65 ++++++++++++++++++ .../protected_branch_edit_list.js.es6 | 17 +++++ .../protected_branches_bundle.js | 1 + .../projects/protected_branches/index.html.haml | 2 + config/application.rb | 1 + 13 files changed, 244 insertions(+), 240 deletions(-) delete mode 100644 app/assets/javascripts/protected_branch_access_dropdown.js.es6 delete mode 100644 app/assets/javascripts/protected_branch_create.js.es6 delete mode 100644 app/assets/javascripts/protected_branch_dropdown.js.es6 delete mode 100644 app/assets/javascripts/protected_branch_edit.js.es6 delete mode 100644 app/assets/javascripts/protected_branch_edit_list.js.es6 create mode 100644 app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 create mode 100644 app/assets/javascripts/protected_branches/protected_branch_create.js.es6 create mode 100644 app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 create mode 100644 app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 create mode 100644 app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 create mode 100644 app/assets/javascripts/protected_branches/protected_branches_bundle.js diff --git a/app/assets/javascripts/protected_branch_access_dropdown.js.es6 b/app/assets/javascripts/protected_branch_access_dropdown.js.es6 deleted file mode 100644 index 7aeb5f92514..00000000000 --- a/app/assets/javascripts/protected_branch_access_dropdown.js.es6 +++ /dev/null @@ -1,28 +0,0 @@ -(global => { - global.gl = global.gl || {}; - - gl.ProtectedBranchAccessDropdown = class { - constructor(options) { - const { $dropdown, data, onSelect } = options; - - $dropdown.glDropdown({ - data: data, - selectable: true, - inputId: $dropdown.data('input-id'), - fieldName: $dropdown.data('field-name'), - toggleLabel(item, el) { - if (el.is('.is-active')) { - return item.text; - } else { - return 'Select'; - } - }, - clicked(item, $el, e) { - e.preventDefault(); - onSelect(); - } - }); - } - } - -})(window); diff --git a/app/assets/javascripts/protected_branch_create.js.es6 b/app/assets/javascripts/protected_branch_create.js.es6 deleted file mode 100644 index 46beca469b9..00000000000 --- a/app/assets/javascripts/protected_branch_create.js.es6 +++ /dev/null @@ -1,54 +0,0 @@ -(global => { - global.gl = global.gl || {}; - - gl.ProtectedBranchCreate = class { - constructor() { - this.$wrap = this.$form = $('#new_protected_branch'); - this.buildDropdowns(); - } - - buildDropdowns() { - const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge'); - const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push'); - - // Cache callback - this.onSelectCallback = this.onSelect.bind(this); - - // Allowed to Merge dropdown - new gl.ProtectedBranchAccessDropdown({ - $dropdown: $allowedToMergeDropdown, - data: gon.merge_access_levels, - onSelect: this.onSelectCallback - }); - - // Allowed to Push dropdown - new gl.ProtectedBranchAccessDropdown({ - $dropdown: $allowedToPushDropdown, - data: gon.push_access_levels, - onSelect: this.onSelectCallback - }); - - // Select default - $allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0); - $allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0); - - // Protected branch dropdown - new ProtectedBranchDropdown({ - $dropdown: this.$wrap.find('.js-protected-branch-select'), - onSelect: this.onSelectCallback - }); - } - - // This will run after clicked callback - onSelect() { - - // Enable submit button - const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]'); - const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]'); - const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]'); - - this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length)); - } - } - -})(window); diff --git a/app/assets/javascripts/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branch_dropdown.js.es6 deleted file mode 100644 index 983322cbecc..00000000000 --- a/app/assets/javascripts/protected_branch_dropdown.js.es6 +++ /dev/null @@ -1,76 +0,0 @@ -class ProtectedBranchDropdown { - constructor(options) { - this.onSelect = options.onSelect; - this.$dropdown = options.$dropdown; - this.$dropdownContainer = this.$dropdown.parent(); - this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer'); - this.$protectedBranch = this.$dropdownContainer.find('.create-new-protected-branch'); - - this.buildDropdown(); - this.bindEvents(); - - // Hide footer - this.$dropdownFooter.addClass('hidden'); - } - - buildDropdown() { - this.$dropdown.glDropdown({ - data: this.getProtectedBranches.bind(this), - filterable: true, - remote: false, - search: { - fields: ['title'] - }, - selectable: true, - toggleLabel(selected) { - return (selected && 'id' in selected) ? selected.title : 'Protected Branch'; - }, - fieldName: 'protected_branch[name]', - text(protectedBranch) { - return _.escape(protectedBranch.title); - }, - id(protectedBranch) { - return _.escape(protectedBranch.id); - }, - onFilter: this.toggleCreateNewButton.bind(this), - clicked: (item, $el, e) => { - e.preventDefault(); - this.onSelect(); - } - }); - } - - bindEvents() { - this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this)); - } - - onClickCreateWildcard() { - // Refresh the dropdown's data, which ends up calling `getProtectedBranches` - this.$dropdown.data('glDropdown').remote.execute(); - this.$dropdown.data('glDropdown').selectRowAtIndex(0); - } - - getProtectedBranches(term, callback) { - if (this.selectedBranch) { - callback(gon.open_branches.concat(this.selectedBranch)); - } else { - callback(gon.open_branches); - } - } - - toggleCreateNewButton(branchName) { - this.selectedBranch = { - title: branchName, - id: branchName, - text: branchName - }; - - if (branchName) { - this.$dropdownContainer - .find('.create-new-protected-branch code') - .text(branchName); - } - - this.$dropdownFooter.toggleClass('hidden', !branchName); - } -} diff --git a/app/assets/javascripts/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branch_edit.js.es6 deleted file mode 100644 index 15a6dca2875..00000000000 --- a/app/assets/javascripts/protected_branch_edit.js.es6 +++ /dev/null @@ -1,65 +0,0 @@ -(global => { - global.gl = global.gl || {}; - - gl.ProtectedBranchEdit = class { - constructor(options) { - this.$wrap = options.$wrap; - this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge'); - this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push'); - - this.buildDropdowns(); - } - - buildDropdowns() { - - // Allowed to merge dropdown - new gl.ProtectedBranchAccessDropdown({ - $dropdown: this.$allowedToMergeDropdown, - data: gon.merge_access_levels, - onSelect: this.onSelect.bind(this) - }); - - // Allowed to push dropdown - new gl.ProtectedBranchAccessDropdown({ - $dropdown: this.$allowedToPushDropdown, - data: gon.push_access_levels, - onSelect: this.onSelect.bind(this) - }); - } - - onSelect() { - const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`); - const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`); - - // Do not update if one dropdown has not selected any option - if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return; - - $.ajax({ - type: 'POST', - url: this.$wrap.data('url'), - dataType: 'json', - data: { - _method: 'PATCH', - protected_branch: { - merge_access_levels_attributes: [{ - id: this.$allowedToMergeDropdown.data('access-level-id'), - access_level: $allowedToMergeInput.val() - }], - push_access_levels_attributes: [{ - id: this.$allowedToPushDropdown.data('access-level-id'), - access_level: $allowedToPushInput.val() - }] - } - }, - success: () => { - this.$wrap.effect('highlight'); - }, - error() { - $.scrollTo(0); - new Flash('Failed to update branch!'); - } - }); - } - } - -})(window); diff --git a/app/assets/javascripts/protected_branch_edit_list.js.es6 b/app/assets/javascripts/protected_branch_edit_list.js.es6 deleted file mode 100644 index 9ff0fd12c76..00000000000 --- a/app/assets/javascripts/protected_branch_edit_list.js.es6 +++ /dev/null @@ -1,17 +0,0 @@ -(global => { - global.gl = global.gl || {}; - - gl.ProtectedBranchEditList = class { - constructor() { - this.$wrap = $('.protected-branches-list'); - - // Build edit forms - this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => { - new gl.ProtectedBranchEdit({ - $wrap: $(el) - }); - }); - } - } - -})(window); diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 new file mode 100644 index 00000000000..7aeb5f92514 --- /dev/null +++ b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 @@ -0,0 +1,28 @@ +(global => { + global.gl = global.gl || {}; + + gl.ProtectedBranchAccessDropdown = class { + constructor(options) { + const { $dropdown, data, onSelect } = options; + + $dropdown.glDropdown({ + data: data, + selectable: true, + inputId: $dropdown.data('input-id'), + fieldName: $dropdown.data('field-name'), + toggleLabel(item, el) { + if (el.is('.is-active')) { + return item.text; + } else { + return 'Select'; + } + }, + clicked(item, $el, e) { + e.preventDefault(); + onSelect(); + } + }); + } + } + +})(window); diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 new file mode 100644 index 00000000000..46beca469b9 --- /dev/null +++ b/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 @@ -0,0 +1,54 @@ +(global => { + global.gl = global.gl || {}; + + gl.ProtectedBranchCreate = class { + constructor() { + this.$wrap = this.$form = $('#new_protected_branch'); + this.buildDropdowns(); + } + + buildDropdowns() { + const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge'); + const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push'); + + // Cache callback + this.onSelectCallback = this.onSelect.bind(this); + + // Allowed to Merge dropdown + new gl.ProtectedBranchAccessDropdown({ + $dropdown: $allowedToMergeDropdown, + data: gon.merge_access_levels, + onSelect: this.onSelectCallback + }); + + // Allowed to Push dropdown + new gl.ProtectedBranchAccessDropdown({ + $dropdown: $allowedToPushDropdown, + data: gon.push_access_levels, + onSelect: this.onSelectCallback + }); + + // Select default + $allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0); + $allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0); + + // Protected branch dropdown + new ProtectedBranchDropdown({ + $dropdown: this.$wrap.find('.js-protected-branch-select'), + onSelect: this.onSelectCallback + }); + } + + // This will run after clicked callback + onSelect() { + + // Enable submit button + const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]'); + const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]'); + const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]'); + + this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length)); + } + } + +})(window); diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 new file mode 100644 index 00000000000..983322cbecc --- /dev/null +++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 @@ -0,0 +1,76 @@ +class ProtectedBranchDropdown { + constructor(options) { + this.onSelect = options.onSelect; + this.$dropdown = options.$dropdown; + this.$dropdownContainer = this.$dropdown.parent(); + this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer'); + this.$protectedBranch = this.$dropdownContainer.find('.create-new-protected-branch'); + + this.buildDropdown(); + this.bindEvents(); + + // Hide footer + this.$dropdownFooter.addClass('hidden'); + } + + buildDropdown() { + this.$dropdown.glDropdown({ + data: this.getProtectedBranches.bind(this), + filterable: true, + remote: false, + search: { + fields: ['title'] + }, + selectable: true, + toggleLabel(selected) { + return (selected && 'id' in selected) ? selected.title : 'Protected Branch'; + }, + fieldName: 'protected_branch[name]', + text(protectedBranch) { + return _.escape(protectedBranch.title); + }, + id(protectedBranch) { + return _.escape(protectedBranch.id); + }, + onFilter: this.toggleCreateNewButton.bind(this), + clicked: (item, $el, e) => { + e.preventDefault(); + this.onSelect(); + } + }); + } + + bindEvents() { + this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this)); + } + + onClickCreateWildcard() { + // Refresh the dropdown's data, which ends up calling `getProtectedBranches` + this.$dropdown.data('glDropdown').remote.execute(); + this.$dropdown.data('glDropdown').selectRowAtIndex(0); + } + + getProtectedBranches(term, callback) { + if (this.selectedBranch) { + callback(gon.open_branches.concat(this.selectedBranch)); + } else { + callback(gon.open_branches); + } + } + + toggleCreateNewButton(branchName) { + this.selectedBranch = { + title: branchName, + id: branchName, + text: branchName + }; + + if (branchName) { + this.$dropdownContainer + .find('.create-new-protected-branch code') + .text(branchName); + } + + this.$dropdownFooter.toggleClass('hidden', !branchName); + } +} diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 new file mode 100644 index 00000000000..15a6dca2875 --- /dev/null +++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 @@ -0,0 +1,65 @@ +(global => { + global.gl = global.gl || {}; + + gl.ProtectedBranchEdit = class { + constructor(options) { + this.$wrap = options.$wrap; + this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge'); + this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push'); + + this.buildDropdowns(); + } + + buildDropdowns() { + + // Allowed to merge dropdown + new gl.ProtectedBranchAccessDropdown({ + $dropdown: this.$allowedToMergeDropdown, + data: gon.merge_access_levels, + onSelect: this.onSelect.bind(this) + }); + + // Allowed to push dropdown + new gl.ProtectedBranchAccessDropdown({ + $dropdown: this.$allowedToPushDropdown, + data: gon.push_access_levels, + onSelect: this.onSelect.bind(this) + }); + } + + onSelect() { + const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`); + const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`); + + // Do not update if one dropdown has not selected any option + if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return; + + $.ajax({ + type: 'POST', + url: this.$wrap.data('url'), + dataType: 'json', + data: { + _method: 'PATCH', + protected_branch: { + merge_access_levels_attributes: [{ + id: this.$allowedToMergeDropdown.data('access-level-id'), + access_level: $allowedToMergeInput.val() + }], + push_access_levels_attributes: [{ + id: this.$allowedToPushDropdown.data('access-level-id'), + access_level: $allowedToPushInput.val() + }] + } + }, + success: () => { + this.$wrap.effect('highlight'); + }, + error() { + $.scrollTo(0); + new Flash('Failed to update branch!'); + } + }); + } + } + +})(window); diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 new file mode 100644 index 00000000000..9ff0fd12c76 --- /dev/null +++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 @@ -0,0 +1,17 @@ +(global => { + global.gl = global.gl || {}; + + gl.ProtectedBranchEditList = class { + constructor() { + this.$wrap = $('.protected-branches-list'); + + // Build edit forms + this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => { + new gl.ProtectedBranchEdit({ + $wrap: $(el) + }); + }); + } + } + +})(window); diff --git a/app/assets/javascripts/protected_branches/protected_branches_bundle.js b/app/assets/javascripts/protected_branches/protected_branches_bundle.js new file mode 100644 index 00000000000..15b3affd469 --- /dev/null +++ b/app/assets/javascripts/protected_branches/protected_branches_bundle.js @@ -0,0 +1 @@ +/*= require_tree . */ diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 49dcc9a6ba4..42e9bdbd30e 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -1,4 +1,6 @@ - page_title "Protected branches" +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('protected_branches/protected_branches_bundle.js') .row.prepend-top-default.append-bottom-default .col-lg-3 diff --git a/config/application.rb b/config/application.rb index 8a9c539cb43..f3337b00dc6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -87,6 +87,7 @@ module Gitlab config.assets.precompile << "users/users_bundle.js" config.assets.precompile << "network/network_bundle.js" config.assets.precompile << "profile/profile_bundle.js" + config.assets.precompile << "protected_branches/protected_branches_bundle.js" config.assets.precompile << "diff_notes/diff_notes_bundle.js" config.assets.precompile << "boards/boards_bundle.js" config.assets.precompile << "merge_conflicts/merge_conflicts_bundle.js" -- cgit v1.2.1 From b4359fb24e599c278edeae843bd6a25c980b1243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 20 Oct 2016 18:51:03 +0200 Subject: Don't use Hash#slice since it's not supported in Ruby 2.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/commit_statuses.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index f282a3b9cd6..f54d4f06627 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -67,9 +67,14 @@ module API pipeline = @project.ensure_pipeline(ref, commit.sha, current_user) status = GenericCommitStatus.running_or_pending.find_or_initialize_by( - project: @project, pipeline: pipeline, - user: current_user, name: name, ref: ref) - status.attributes = declared(params).slice(:target_url, :description) + project: @project, + pipeline: pipeline, + user: current_user, + name: name, + ref: ref, + target_url: params[:target_url], + description: params[:description] + ) begin case params[:state].to_s -- cgit v1.2.1 From f956de7c3991f229967339ad04aa5464c02b3ac6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 20 Oct 2016 20:16:35 +0200 Subject: Refactor and add new functionality to CI yaml reference [ci ski] --- doc/ci/yaml/README.md | 160 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 135 insertions(+), 25 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 84ea59ab687..5c0e1c44e3f 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -146,13 +146,17 @@ variables: ``` These variables can be later used in all executed commands and scripts. - The YAML-defined variables are also set to all created service containers, -thus allowing to fine tune them. +thus allowing to fine tune them. Variables can be also defined on a +[job level](#job-variables). -Variables can be also defined on [job level](#job-variables). +Except for the user defined variables, there are also the ones set up by the +Runner itself. One example would be `CI_BUILD_REF_NAME` which has the value of +the branch or tag name for which project is built. Apart from the variables +you can set in `.gitlab-ci.yml`, there are also the so called secret variables +which can be set in GitLab's UI. -[Learn more about variables.](../variables/README.md) +[Learn more about variables.][variables] ### cache @@ -541,20 +545,29 @@ An example usage of manual actions is deployment to production. > Introduced in GitLab 8.9. -`environment` is used to define that a job deploys to a specific [environment]. -This allows easy tracking of all deployments to your environments straight from -GitLab. +> You can read more about environments and find more examples in the +[documentation about environments][environment]. +`environment` is used to define that a job deploys to a specific environment. If `environment` is specified and no environment under that name exists, a new one will be created automatically. -The `environment` name must contain only letters, digits, '-', '_', '/', '$', '{', '}' and spaces. Common -names are `qa`, `staging`, and `production`, but you can use whatever name works -with your workflow. +The `environment` name can contain: ---- +- letters +- digits +- spaces +- `-` +- `_` +- `/` +- `$` +- `{` +- `}` -**Example configurations** +Common names are `qa`, `staging`, and `production`, but you can use whatever +name works with your workflow. + +In its simplest form, the `environment` keyword can be defined like: ``` deploy to production: @@ -563,39 +576,134 @@ deploy to production: environment: production ``` -The `deploy to production` job will be marked as doing deployment to -`production` environment. +In the above example, the `deploy to production` job will be marked as doing a +deployment to the `production` environment. + +#### environment:name + +> Introduced in GitLab 8.11. + +>**Note:** +Before GitLab 8.11, the name of an environment could be defined as a string like +`environment: production`. The recommended way now is to define it under the +`name` keyword. + +Instead of defining the name of the environment right after the `environment` +keyword, it is also possible to define it as a separate value. For that, use +the `name` keyword under `environment`: + +``` +deploy to production: + stage: deploy + script: git push production HEAD:master + environment: + name: production +``` + +#### environment:url + +> Introduced in GitLab 8.11. + +>**Note:** +Before GitLab 8.11, the URL could be added only in GitLab's UI. The +recommended way now is to define it in `.gitlab-ci.yml`. + +This is an optional value that when set, it exposes buttons in various places +in GitLab which when clicked take you to the defined URL. + +In the example below, if the job finishes successfully, it will create buttons +in the merge requests and in the environments/deployments pages which will point +to `https://prod.example.com`. + +``` +deploy to production: + stage: deploy + script: git push production HEAD:master + environment: + name: production + url: https://prod.example.com +``` + +#### environment:on_stop + +> [Introduced][ce-6669] in GitLab 8.13. + +Closing (stoping) environments can be achieved with the `on_stop` keyword defined under +`environment`. It declares a different job that runs in order to close +the environment. + +Read the `environment:action` section for an example. + +#### environment:action + +> [Introduced][ce-6669] in GitLab 8.13. + +The `action` keyword is to be used in conjunction with `on_stop` and is defined +in the job that is called to close the environment. + +Take for instance: + +```yaml +review_app: + stage: deploy + script: make deploy-app + environment: + name: review + on_stop: stop_review_app + +stop_review_app: + stage: deploy + script: make delete-app + when: manual + environment: + name: review + action: stop +``` + +In the above example we set up the `review_app` job to deploy to the `review` +environment, and we also defined a new `stop_review_app` job under `on_stop`. +Once the `review_app` job is successfully finished, it will trigger the +`stop_review_app` job based on what is defined under `when`. In this case we +set it up to `manual` so it will need a [manual action](#manual-actions) via +GitLab's web interface in order to run. + +The `stop_review_app` job is **required** to have the following keywords defined: + +- `when` - [reference](#when) +- `environment:name` +- `environment:action` #### dynamic environments > [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6. `environment` can also represent a configuration hash with `name` and `url`. -These parameters can use any of the defined CI [variables](#variables) +These parameters can use any of the defined [CI variables](#variables) (including predefined, secure variables and `.gitlab-ci.yml` variables). -The common use case is to create dynamic environments for branches and use them -as review apps. - ---- - -**Example configurations** +For example: ``` deploy as review app: stage: deploy - script: ... + script: make deploy environment: name: review-apps/$CI_BUILD_REF_NAME url: https://$CI_BUILD_REF_NAME.review.example.com/ ``` The `deploy as review app` job will be marked as deployment to dynamically -create the `review-apps/branch-name` environment. +create the `review-apps/$CI_BUILD_REF_NAME` environment, which `$CI_BUILD_REF_NAME` +is an [environment variable][variables] set by the Runner. If for example the +`deploy as review app` job was run in a branch named `pow`, this environment +should be accessible under `https://pow.review.example.com/`. -This environment should be accessible under `https://branch-name.review.example.com/`. +This of course implies that the underlying server which hosts the application +is properly configured. -You can see a simple example at https://gitlab.com/gitlab-examples/review-apps-nginx/. +The common use case is to create dynamic environments for branches and use them +as Review Apps. You can see a simple example using Review Apps at +https://gitlab.com/gitlab-examples/review-apps-nginx/. ### artifacts @@ -1105,3 +1213,5 @@ CI with various languages. [examples]: ../examples/README.md [ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323 [environment]: ../environments.md +[ce-6669]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6669 +[variables]: ../variables/README.md -- cgit v1.2.1 From 4e03f4c40602b568cffd591dcd5af6bd4b9a281e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 19 Oct 2016 14:57:42 +0100 Subject: Fixed issues with sticky mr tabs & sidebar Closes #23504 --- app/assets/javascripts/merge_request_tabs.js | 11 +--- app/assets/stylesheets/framework/sidebar.scss | 8 +++ app/assets/stylesheets/pages/merge_requests.scss | 13 ++++- app/views/projects/merge_requests/_show.html.haml | 68 ++++++++++++----------- 4 files changed, 54 insertions(+), 46 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index fd21aa1fefa..1a04a037210 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -388,8 +388,7 @@ // So we dont affix the tabs on these if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; - var tabsWidth = $tabs.outerWidth(), - $diffTabs = $('#diff-notes-app'), + var $diffTabs = $('#diff-notes-app'), offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); $tabs.off('affix.bs.affix affix-top.bs.affix') @@ -398,18 +397,10 @@ top: offsetTop } }).on('affix.bs.affix', function () { - $tabs.css({ - left: $tabs.offset().left, - width: tabsWidth - }); $diffTabs.css({ marginTop: $tabs.height() }); }).on('affix-top.bs.affix', function () { - $tabs.css({ - left: '', - width: '' - }); $diffTabs.css({ marginTop: '' }); diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index ec52f326eb9..1d8e64a0e4b 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -185,6 +185,10 @@ header.header-sidebar-pinned { @media (min-width: $screen-sm-min) { padding-right: $sidebar_collapsed_width; + + .merge-request-tabs-holder.affix { + right: $sidebar_collapsed_width; + } } .sidebar-collapsed-icon { @@ -207,6 +211,10 @@ header.header-sidebar-pinned { @media (min-width: $screen-md-min) { padding-right: $gutter_width; + + .merge-request-tabs-holder.affix { + right: $gutter_width; + } } &.with-overlay { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 35a1877df95..70afa568554 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -183,11 +183,11 @@ .ci-coverage { float: right; } - + .stop-env-container { color: $gl-text-color; float: right; - + a { color: $gl-text-color; } @@ -438,11 +438,18 @@ } } -.merge-request-tabs { +.merge-request-tabs-holder { background-color: #fff; &.affix { top: 100px; + left: 0; z-index: 9; + transition: right .15s; + } + + &:not(.affix) .container-fluid { + padding-left: 0; + padding-right: 0; } } diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 0e19d224fcd..f57abe73977 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -47,39 +47,41 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits_count.nonzero? - %ul.merge-request-tabs.nav-links.no-top.no-bottom{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } - %li.notes-tab - = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do - Discussion - %span.badge= @merge_request.mr_and_commit_notes.user.count - - if @merge_request.source_project - %li.commits-tab - = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do - Commits - %span.badge= @commits_count - - if @pipeline - %li.pipelines-tab - = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do - Pipelines - %span.badge= @pipelines.size - %li.builds-tab - = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do - Builds - %span.badge= @statuses.size - %li.diffs-tab - = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do - Changes - %span.badge= @merge_request.diff_size - %li#resolve-count-app.line-resolve-all-container.pull-right.prepend-top-10.hidden-xs{ "v-cloak" => true } - %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" } - .line-resolve-all{ "v-show" => "discussionCount > 0", - ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } - %span.line-resolve-btn.is-disabled{ type: "button", - ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" } - = render "shared/icons/icon_status_success.svg" - %span.line-resolve-text - {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ discussionCount | pluralize 'discussion' }} resolved - = render "discussions/jump_to_next" + .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } + %div{ class: container_class } + %ul.merge-request-tabs.nav-links.no-top.no-bottom + %li.notes-tab + = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do + Discussion + %span.badge= @merge_request.mr_and_commit_notes.user.count + - if @merge_request.source_project + %li.commits-tab + = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do + Commits + %span.badge= @commits_count + - if @pipeline + %li.pipelines-tab + = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do + Pipelines + %span.badge= @pipelines.size + %li.builds-tab + = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do + Builds + %span.badge= @statuses.size + %li.diffs-tab + = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do + Changes + %span.badge= @merge_request.diff_size + %li#resolve-count-app.line-resolve-all-container.pull-right.prepend-top-10.hidden-xs{ "v-cloak" => true } + %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" } + .line-resolve-all{ "v-show" => "discussionCount > 0", + ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } + %span.line-resolve-btn.is-disabled{ type: "button", + ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" } + = render "shared/icons/icon_status_success.svg" + %span.line-resolve-text + {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ discussionCount | pluralize 'discussion' }} resolved + = render "discussions/jump_to_next" .tab-content#diff-notes-app #notes.notes.tab-pane.voting_notes -- cgit v1.2.1 From a28371dbe33c568c970c704b90760d2b540256af Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 19 Oct 2016 17:24:24 +0100 Subject: Fixed issue when images are loading it would push off the tabs --- app/assets/javascripts/merge_request_tabs.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 1a04a037210..9f28738e06b 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -389,12 +389,18 @@ if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; var $diffTabs = $('#diff-notes-app'), - offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); + $fixedNav = $('.navbar-fixed-top'), + $layoutNav = $('.layout-nav'); $tabs.off('affix.bs.affix affix-top.bs.affix') .affix({ offset: { - top: offsetTop + top: function () { + var tabsTop = $diffTabs.offset().top - $tabs.height(); + tabsTop = tabsTop - ($fixedNav.height() + $layoutNav.height()); + + return tabsTop; + } } }).on('affix.bs.affix', function () { $diffTabs.css({ -- cgit v1.2.1 From 8c4576418be18dc6143d029f8d51645fef951655 Mon Sep 17 00:00:00 2001 From: evhoffmann Date: Thu, 20 Oct 2016 15:00:02 -0400 Subject: updated some links in definitions --- doc/university/glossary/README.md | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md index 2cca5439a5a..c7cfebb7c7c 100644 --- a/doc/university/glossary/README.md +++ b/doc/university/glossary/README.md @@ -55,7 +55,7 @@ Entry level [subscription](https://about.gitlab.com/pricing/) for GitLab EE curr ### Bitbucket -Atlassian's web hosting service for Git and Mercurial Projects i.e. GitLab.com competitor. +Atlassian's web hosting service for Git and Mercurial Projects. Read about [migrating](https://docs.gitlab.com/ce/workflow/importing/import_projects_from_bitbucket.html) from BitBucket to a GitLab instance. ### Branch @@ -152,12 +152,16 @@ The difference between two commits, or saved changes. This will also be shown vi A folder used for storing multiple files. -### Docker +### Docker Container Registry -Containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in. +A [feature](https://docs.gitlab.com/ce/user/project/container_registry.html) of GitLab projects. Containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in. ### Dynamic Environment +### ElasticSearch + +Elasticsearch is a flexible, scalable and powerful search service. When [enabled](https://gitlab.com/help/integration/elasticsearch.md), it helps keep GitLab's search fast when dealing with a huge amount of data. + ### Emacs ### Fork @@ -168,6 +172,10 @@ Your [own copy](https://docs.gitlab.com/ce/workflow/forking_workflow.html) of a A code review [tool](https://www.gerritcodereview.com/) built on top of Git. +### Git Attributes + +A [git attributes file](https://git-scm.com/docs/gitattributes) is a simple text file that gives attributes to pathnames. + ### Git Hooks [Scripts](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) you can use to trigger actions at certain points. @@ -178,7 +186,7 @@ A single-tenant solution that provides GitLab CE or EE as a managed service. Git ### GitHub -A web-based Git repository hosting service with an enterprise offering. Its main features are: issue tracking, pull request with code review, abundancy of integrations and wiki. As of April 2016, the service has over 14 million users. It offers free public repos, private repos and enterprise services are paid. +A web-based Git repository hosting service with an enterprise offering. Its main features are: issue tracking, pull request with code review, abundancy of integrations and wiki. It offers free public repos, private repos and enterprise services are paid. Read about [importing a project](https://docs.gitlab.com/ce/workflow/importing/import_projects_from_github.html) from GitHub to GitLab. ### GitLab CE @@ -201,7 +209,7 @@ Our free SaaS for public and private repositories. Allows you to replicate your GitLab instance to other geographical locations as a read-only fully operational version. It [can be used](https://docs.gitlab.com/ee/gitlab-geo/README.html) for cloning and fetching projects, in addition to reading any data. This will make working with large repositories over large distances much faster. ### GitLab Pages -These allow you to [create websites](https://pages.gitlab.io/) for your GitLab projects, groups, or user account. +These allow you to [create websites](https://gitlab.com/help/pages/README.md) for your GitLab projects, groups, or user account. ### Gitolite @@ -249,7 +257,7 @@ An Open Source CI tool written using the Java programming language. [Jenkins](ht ### Jira -Atlassian's [project management software](https://www.atlassian.com/software/jira), i.e. a complex issue tracker. GitLab [can be configured](https://docs.gitlab.com/ee/project_services/jira.html) to interact with JIRA Core either using an on-premises instance or the SaaS solution that Atlassian offers. +Atlassian's [project management software](https://www.atlassian.com/software/jira), i.e. a complex issue tracker. GitLab [can be configured](https://docs.gitlab.com/ee/project_services/jira.html) to interact with JIRA Core either using an on-premise instance or the SaaS solution that Atlassian offers. ### JUnit @@ -353,7 +361,7 @@ A web [server](https://www.nginx.com/resources/wiki/) (pronounced "engine x"). I ### OAuth -An open [standard](https://oauth.net/) for authorization, commonly used as a way for internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password. +An open standard for authorization, commonly used as a way for internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password. GitLab [is](https://docs.gitlab.com/ce/integration/oauth_provider.html) an OAuth2 authentication service provider. ### Omnibus Packages @@ -413,7 +421,7 @@ A popular DevOps [automation tool](https://puppet.com/product/how-puppet-works). ### Push -Git [command](https://git-scm.com/docs/git-push) to send commits from the local repository to the remote repository. +Git [command](https://git-scm.com/docs/git-push) to send commits from the local repository to the remote repository. Read about [advanced push rules](https://gitlab.com/help/pages/README.md) in GitLab. ### RE Read Only @@ -447,6 +455,10 @@ A route table contains rules (called routes) that determine where network traffi Actual build machines/containers that [run and execute tests](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner) you have specified to be run on GitLab CI. +### Sidekiq + +The background job processor GitLab [uses](https://docs.gitlab.com/ce/administration/troubleshooting/sidekiq.html) to asynchronously run tasks. + ### Software as a service (SaaS) Software that is hosted centrally and accessed on-demand (i.e. whenever you want to). This applies to GitLab.com. @@ -465,7 +477,7 @@ The board used to track the status and progress of each of the sprint backlog it ### Shell -[Terminal](https://docs.gitlab.com/ce/gitlab-basics/start-using-git.html) on Mac OSX, GitBash on Windows, or Linux Terminal on Linux. +Terminal on Mac OSX, GitBash on Windows, or Linux Terminal on Linux. You [use git]() and make changes to GitLab projects in your shell. You [use git](https://docs.gitlab.com/ce/gitlab-basics/start-using-git.html) and make changes to GitLab projects in your shell. ### Single-tenant @@ -517,7 +529,7 @@ A program that allows you to perform superuser/administrator actions on Unix Ope ### Subversion (SVN) -An open source version control system. +An open source version control system. Read about [migrating from SVN](https://docs.gitlab.com/ce/workflow/importing/migrating_from_svn.html) to GitLab using SubGit. ### Tag @@ -559,7 +571,7 @@ A [model](http://www.umsl.edu/~hugheyd/is6840/waterfall.html) of building softwa ### Webhooks -A way for for an app to [provide](https://docs.gitlab.com/ce/web_hooks/web_hooks.html) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) +A way for for an app to [provide](https://docs.gitlab.com/ce/web_hooks/web_hooks.html) other applications with real-time information (e.g., send a message to a slack channel when a commit is pushed.) Read about setting up [custom git hooks](https://gitlab.com/help/administration/custom_hooks.md) for when webhooks are insufficient. ### Wiki -- cgit v1.2.1 From 12991f84a2ceb157b7ddec94858ccc395c767006 Mon Sep 17 00:00:00 2001 From: evhoffmann Date: Thu, 20 Oct 2016 15:47:57 -0400 Subject: added skipped definition --- doc/university/glossary/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md index c7cfebb7c7c..cf836667fac 100644 --- a/doc/university/glossary/README.md +++ b/doc/university/glossary/README.md @@ -565,6 +565,8 @@ A [virtual machine](https://en.wikipedia.org/wiki/Virtual_private_server) sold a ### VM Instance +In object-oriented programming, an [instance](http://stackoverflow.com/questions/20461907/what-is-meaning-of-instance-in-programming) is a specific realization of any object. An object may be varied in a number of ways. Each realized variation of that object is an instance. Therefore, a VM instance is an instance of a virtual machine, which is an emulation of a computer system. + ### Waterfall A [model](http://www.umsl.edu/~hugheyd/is6840/waterfall.html) of building software that involves collecting all requirements from the customer, then building and refining all the requirements and finally delivering the complete software to the customer that meets all the requirements they specified. -- cgit v1.2.1 From 57046eb0abf280594d6625db3429f13d45499c83 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 19 Oct 2016 14:40:58 +0200 Subject: Ensure custom provider tab labels don't break layout. (Also fix some issues for session views on small screens.) --- app/assets/stylesheets/pages/login.scss | 34 +++++++++++++++++++++++++- app/views/devise/sessions/two_factor.html.haml | 2 +- app/views/devise/shared/_tabs_ldap.html.haml | 2 +- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index bdb13bee178..9496234c773 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -143,6 +143,7 @@ &:not(.active) { background-color: $gray-light; + border-left: 1px solid $border-color; } a { @@ -170,6 +171,31 @@ } } + // Ldap configurations may need more tabs & the tab labels are user generated (arbitrarily long). + // These styles prevent this from breaking the layout, and only applied when providers are configured. + + .new-session-tabs.custom-provider-tabs { + flex-wrap: wrap; + + li { + min-width: 85px; + flex-basis: auto; + + // This styles tab elements that have wrapped to a second line. We cannot easily predict when this will happen. + // We are making somewhat of an assumption about the configuration here: that users do not have more than + // 3 LDAP servers configured (in addition to standard login) and they are not using especially long names for any + // of them. If either condition is false, this will work as expected. If both are true, there may be a missing border + // above one of the bottom row elements. If you know a better way, please implement it! + &:nth-child(n+5) { + border-top: 1px solid $border-color; + } + } + + a { + font-size: 16px; + } + } + .form-control { &:active, &:focus { @@ -203,6 +229,7 @@ .login-page { .col-sm-5.pull-right { float: none !important; + margin-bottom: 45px; } } } @@ -244,7 +271,11 @@ } .navless-container { - padding: 65px; // height of footer + bottom padding of email confirmation link + padding: 65px 15px; // height of footer + bottom padding of email confirmation link + + @media (max-width: $screen-xs-max) { + padding: 0 15px 65px; + } } } @@ -263,3 +294,4 @@ bottom: 0; } } + diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 0e865b807c1..fd77cdbee2e 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -10,7 +10,7 @@ = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f| - resource_params = params[resource_name].presence || params = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) - .form-group + %div = f.label 'Two-Factor Authentication code', name: :otp_attempt = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.' %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index a057f126c45..1e957f0935f 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -1,4 +1,4 @@ -%ul.new-session-tabs.nav-links.nav-tabs +%ul.new-session-tabs.nav-links.nav-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) } - if crowd_enabled? %li.active = link_to "Crowd", "#crowd", 'data-toggle' => 'tab' -- cgit v1.2.1 From a82a80aa4e7841f8474cc25c21bb6d93b0eaee92 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Thu, 6 Oct 2016 11:31:01 +0200 Subject: Trim project_path whitespace on form submit When the form is submitted, any leading and/or trailing whitespace is trimmed. --- CHANGELOG.md | 1 + app/views/projects/new.html.haml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73dc323e02c..25f2a3777e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) - Adds user project membership expired event to clarify why user was removed (Callum Dryden) + - Trim leading and trailing whitespace on project_path (Linus Thiel) - Fix HipChat notifications rendering (airatshigapov, eisnerd) - Simpler arguments passed to named_route on toggle_award_url helper method diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 399ccf15b7f..932603f03b0 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -127,6 +127,11 @@ } }); + $('#new_project').submit(function(){ + var $path = $('#project_path'); + $path.val($path.val().trim()); + }); + $('#project_path').keyup(function(){ if($(this).val().length !=0) { $('.btn_import_gitlab_project').attr('disabled', false); -- cgit v1.2.1 From 1c668b125e8fd3e2959d0a2bd83447f09ea7fee4 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Fri, 21 Oct 2016 09:35:17 +1100 Subject: Add hover to trash icon in notes --- CHANGELOG.md | 1 + app/views/projects/notes/_note.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73dc323e02c..128f5dec039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.14.0 (2016-11-22) - Adds user project membership expired event to clarify why user was removed (Callum Dryden) - Fix HipChat notifications rendering (airatshigapov, eisnerd) + - Add hover to trash icon in notes !7008 (blackst0ne) - Simpler arguments passed to named_route on toggle_award_url helper method ## 8.13.0 (2016-10-22) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 73fe6a715fa..ab719e38904 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -57,7 +57,7 @@ = link_to '#', title: 'Edit comment', class: 'note-action-button js-note-edit' do = icon('pencil', class: 'link-highlight') = 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 hidden-xs js-note-delete danger' do - = icon('trash-o') + = icon('trash-o', class: 'danger-highlight') .note-body{class: note_editable ? 'js-task-list-container' : ''} .note-text.md = preserve do -- cgit v1.2.1 From f87124da1f3cf48415457b2c8bdae7ce4cb573ea Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Fri, 14 Oct 2016 11:42:08 -0700 Subject: fix font weight of project feature settings --- app/assets/stylesheets/pages/projects.scss | 16 +++- app/views/projects/edit.html.haml | 116 ++++++++++++++--------------- 2 files changed, 72 insertions(+), 60 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 1062d7effb0..fe7cf3c87e3 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -13,9 +13,18 @@ .new_project, .edit-project { + fieldset { - &.features .control-label { - font-weight: normal; + + &.features { + + .label-light { + margin-bottom: 0; + } + + .help-block { + margin-top: 0; + } } .form-group { @@ -40,6 +49,7 @@ } .input-group > div { + &:last-child { padding-right: 0; } @@ -47,6 +57,7 @@ @media (max-width: $screen-xs-max) { .input-group > div { + margin-bottom: 14px; &:last-child { @@ -60,6 +71,7 @@ } .input-group-addon { + &.static-namespace { height: 35px; border-radius: 3px; diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index fb776e3a3e7..55b6580e640 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -46,70 +46,70 @@ %h5.prepend-top-0 Feature Visibility - = f.fields_for :project_feature do |feature_fields| - .form_group.prepend-top-20 - .row - .col-md-9 - = feature_fields.label :repository_access_level, "Repository", class: 'label-light' - %span.help-block Push files to be stored in this project - .col-md-3.js-repo-access-level - = project_feature_access_select(:repository_access_level) + = f.fields_for :project_feature do |feature_fields| + .form_group.prepend-top-20 + .row + .col-md-9 + = feature_fields.label :repository_access_level, "Repository", class: 'label-light' + %span.help-block Push files to be stored in this project + .col-md-3.js-repo-access-level + = project_feature_access_select(:repository_access_level) - .col-sm-12 - .row - .col-md-9.project-feature-nested - = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light' - %span.help-block Submit changes to be merged upstream - .col-md-3 - = project_feature_access_select(:merge_requests_access_level) + .col-sm-12 + .row + .col-md-9.project-feature-nested + = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light' + %span.help-block Submit changes to be merged upstream + .col-md-3 + = project_feature_access_select(:merge_requests_access_level) - .row - .col-md-9.project-feature-nested - = feature_fields.label :builds_access_level, "Builds", class: 'label-light' - %span.help-block Submit, test and deploy your changes before merge - .col-md-3 - = project_feature_access_select(:builds_access_level) + .row + .col-md-9.project-feature-nested + = feature_fields.label :builds_access_level, "Builds", class: 'label-light' + %span.help-block Submit, test and deploy your changes before merge + .col-md-3 + = project_feature_access_select(:builds_access_level) - .row - .col-md-9 - = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light' - %span.help-block Share code pastes with others out of Git repository - .col-md-3 - = project_feature_access_select(:snippets_access_level) + .row + .col-md-9 + = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light' + %span.help-block Share code pastes with others out of Git repository + .col-md-3 + = project_feature_access_select(:snippets_access_level) - .row - .col-md-9 - = feature_fields.label :issues_access_level, "Issues", class: 'label-light' - %span.help-block Lightweight issue tracking system for this project - .col-md-3 - = project_feature_access_select(:issues_access_level) + .row + .col-md-9 + = feature_fields.label :issues_access_level, "Issues", class: 'label-light' + %span.help-block Lightweight issue tracking system for this project + .col-md-3 + = project_feature_access_select(:issues_access_level) - .row - .col-md-9 - = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light' - %span.help-block Pages for project documentation - .col-md-3 - = project_feature_access_select(:wiki_access_level) - - - if Gitlab.config.lfs.enabled && current_user.admin? - .checkbox - = f.label :lfs_enabled do - = f.check_box :lfs_enabled - %strong LFS - %br - %span.descr - Git Large File Storage - = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + .row + .col-md-9 + = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light' + %span.help-block Pages for project documentation + .col-md-3 + = project_feature_access_select(:wiki_access_level) - if Gitlab.config.lfs.enabled && current_user.admin? - .form-group - .checkbox - = f.label :container_registry_enabled do - = f.check_box :container_registry_enabled - %strong Container Registry - %br - %span.descr Enable Container Registry for this project - = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank' + .checkbox + = f.label :lfs_enabled do + = f.check_box :lfs_enabled + %strong LFS + %br + %span.descr + Git Large File Storage + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + + - if Gitlab.config.lfs.enabled && current_user.admin? + .form-group + .checkbox + = f.label :container_registry_enabled do + = f.check_box :container_registry_enabled + %strong Container Registry + %br + %span.descr Enable Container Registry for this project + = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank' = render 'merge_request_settings', f: f %hr @@ -288,4 +288,4 @@ Saving project. %p Please wait a moment, this page will automatically refresh when ready. -= render 'shared/confirm_modal', phrase: @project.path += render 'shared/confirm_modal', phrase: @project.path \ No newline at end of file -- cgit v1.2.1 From cf31a0f0b2b5ba6d4445e3e5c767119f9cf5953a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 21 Oct 2016 01:03:16 -0700 Subject: Disable warming of the asset cache in Spinach tests under CI I suspect some combination of Knapsack tests cause no regular Rack tests to be loaded (i.e. all JavaScript tests), which leads to the error: ArgumentError: rack-test requires a rack application, but none was given In CI, we precompile all the assets so there is no need to warm the asset cache in any case. Closes #23613 --- features/support/capybara.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/support/capybara.rb b/features/support/capybara.rb index fe9e39cf509..dae0d0f918c 100644 --- a/features/support/capybara.rb +++ b/features/support/capybara.rb @@ -20,5 +20,5 @@ unless ENV['CI'] || ENV['CI_SERVER'] end Spinach.hooks.before_run do - TestEnv.warm_asset_cache + TestEnv.warm_asset_cache unless ENV['CI'] || ENV['CI_SERVER'] end -- cgit v1.2.1 From 168197cd5a179c961301225626ac1a175f892782 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 18 Oct 2016 16:49:19 +0300 Subject: Fix project member access levels --- CHANGELOG.md | 1 + .../20161018124658_make_project_owners_masters.rb | 15 +++++++++ db/schema.rb | 2 +- .../projects/project_members_controller_spec.rb | 36 ++++++++++++++++++++++ spec/requests/api/members_spec.rb | 11 +++++++ 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20161018124658_make_project_owners_masters.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 670404e4fce..16ca2ff93e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -137,6 +137,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix buggy iOS tooltip layering behavior. - Make guests unable to view MRs on private projects - Fix broken Project API docs (Takuya Noguchi) + - Migrate invalid project members (owner -> master) ## 8.12.7 diff --git a/db/migrate/20161018124658_make_project_owners_masters.rb b/db/migrate/20161018124658_make_project_owners_masters.rb new file mode 100644 index 00000000000..a576bb7b622 --- /dev/null +++ b/db/migrate/20161018124658_make_project_owners_masters.rb @@ -0,0 +1,15 @@ +class MakeProjectOwnersMasters < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + update_column_in_batches(:members, :access_level, 40) do |table, query| + query.where(table[:access_level].eq(50).and(table[:source_type].eq('Project'))) + end + end + + def down + # do nothing + end +end diff --git a/db/schema.rb b/db/schema.rb index a3c7fc2fd57..f5c01511195 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -843,7 +843,7 @@ ActiveRecord::Schema.define(version: 20161019213545) do t.integer "builds_access_level" t.datetime "created_at" t.datetime "updated_at" - t.integer "repository_access_level", default: 20, null: false + t.integer "repository_access_level", default: 20, null: false end add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 8519ebc1d5f..5e487241d07 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -228,4 +228,40 @@ describe Projects::ProjectMembersController do end end end + + describe 'POST create' do + let(:stranger) { create(:user) } + + context 'when creating owner' do + before do + project.team << [user, :master] + sign_in(user) + end + + it 'does not create a member' do + expect do + post :create, user_ids: stranger.id, + namespace_id: project.namespace, + access_level: Member::OWNER, + project_id: project + end.to change { project.members.count }.by(0) + end + end + + context 'when create master' do + before do + project.team << [user, :master] + sign_in(user) + end + + it 'creates a member' do + expect do + post :create, user_ids: stranger.id, + namespace_id: project.namespace, + access_level: Member::MASTER, + project_id: project + end.to change { project.members.count }.by(1) + end + end + end end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index d22e0595788..493c0a893d1 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -328,4 +328,15 @@ describe API::Members, api: true do it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'group' do let(:source) { group } end + + context 'Adding owner to project' do + it 'returns 403' do + expect do + post api("/projects/#{project.id}/members", master), + user_id: stranger.id, access_level: Member::OWNER + + expect(response).to have_http_status(422) + end.to change { project.members.count }.by(0) + end + end end -- cgit v1.2.1 From b332931af375a29fa0ff41d45315400beca44c7a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 20 Oct 2016 21:43:24 -0700 Subject: Fix broken label uniqueness label migration The previous implementation of the migration failed on staging because the migration was attempted to remove labels from projects that did not actually have duplicates. This occurred because the SQL query did not account for the project ID when selecting the labels. To replicate the problem: 1. Disable the uniqueness validation in app/models/label.rb. 2. Create a duplicate label "bug" in project A. 3. Create the same label in project B with label "bug". The migration will attempt to remove the label in B even if there are no duplicates. Closes #23609 --- db/migrate/20161017125927_add_unique_index_to_labels.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20161017125927_add_unique_index_to_labels.rb b/db/migrate/20161017125927_add_unique_index_to_labels.rb index 16ae38612de..f2b56ebfb7b 100644 --- a/db/migrate/20161017125927_add_unique_index_to_labels.rb +++ b/db/migrate/20161017125927_add_unique_index_to_labels.rb @@ -7,9 +7,9 @@ class AddUniqueIndexToLabels < ActiveRecord::Migration disable_ddl_transaction! def up - select_all('SELECT title, COUNT(id) as cnt FROM labels GROUP BY project_id, title HAVING COUNT(id) > 1').each do |label| + select_all('SELECT title, project_id, COUNT(id) as cnt FROM labels GROUP BY project_id, title HAVING COUNT(id) > 1').each do |label| label_title = quote_string(label['title']) - duplicated_ids = select_all("SELECT id FROM labels WHERE title = '#{label_title}' ORDER BY id ASC").map{ |label| label['id'] } + duplicated_ids = select_all("SELECT id FROM labels WHERE project_id = #{label['project_id']} AND title = '#{label_title}' ORDER BY id ASC").map{ |label| label['id'] } label_id = duplicated_ids.first duplicated_ids.delete(label_id) -- cgit v1.2.1 From c81ff152e08d58c13efbd50c40dd2e083ac65083 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Fri, 21 Oct 2016 13:53:38 +0200 Subject: Change "Group#web_url" to return "/groups/twitter" rather than "/twitter". Bring back the old behaviour which was changed by 6b90ccb9. Fixes #23527. --- app/models/group.rb | 2 +- config/routes/group.rb | 33 ++++++++++++++++++--------------- spec/models/group_spec.rb | 6 ++++++ 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/app/models/group.rb b/app/models/group.rb index 00a595d2705..6865e610718 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -68,7 +68,7 @@ class Group < Namespace end def web_url - Gitlab::Routing.url_helpers.group_url(self) + Gitlab::Routing.url_helpers.group_canonical_url(self) end def human_name diff --git a/config/routes/group.rb b/config/routes/group.rb index 4838c9d91c6..826048ba196 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -12,23 +12,26 @@ constraints(GroupUrlConstrainer.new) do end end -resources :groups, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'groups#show', as: :group_canonical end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index ac862055ebc..47f89f744cb 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -265,4 +265,10 @@ describe Group, models: true do members end + + describe '#web_url' do + it 'returns the canonical URL' do + expect(group.web_url).to include("groups/#{group.name}") + end + end end -- cgit v1.2.1 From a74dfa301e77752e333467892073811677e82c89 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 21 Oct 2016 13:43:52 +0100 Subject: Fixed compare ellipsis messing with layout --- app/views/projects/compare/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 76b68c544aa..7bde20c3286 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -10,7 +10,7 @@ = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do .dropdown-toggle-text= params[:from] || 'Select branch/tag' = render "ref_dropdown" - .compare-ellipsis ... + .compare-ellipsis.inline ... .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown .input-group.inline-input-group %span.input-group-addon to -- cgit v1.2.1 From 1a53511a3454bf70786d72e59530bff42ae160e4 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 14 Oct 2016 16:29:54 -0500 Subject: Fix object data to be sent to fetch analytics data --- app/assets/javascripts/cycle_analytics.js.es6 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/cycle_analytics.js.es6 b/app/assets/javascripts/cycle_analytics.js.es6 index bd9accacb8c..20791bab942 100644 --- a/app/assets/javascripts/cycle_analytics.js.es6 +++ b/app/assets/javascripts/cycle_analytics.js.es6 @@ -36,7 +36,11 @@ method: 'GET', dataType: 'json', contentType: 'application/json', - data: { start_date: options.startDate } + data: { + cycle_analytics: { + start_date: options.startDate + } + } }).done((data) => { this.decorateData(data); this.initDropdown(); -- cgit v1.2.1 From 6c2ab27aeaf0cd59d87e14876492a4162d48e2d7 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 19 Oct 2016 10:27:24 -0500 Subject: Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c5c96c4528..42e3df435bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Update duration at the end of pipeline - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - Add group level labels. (!6425) + - Fix Cycle analytics not showing correct data when filtering by date. !6906 - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - Cancelled pipelines could be retried. !6927 - Updating verbiage on git basics to be more intuitive -- cgit v1.2.1 From b939529c2a2c724f1471ab3b0ec2a5dac10c913c Mon Sep 17 00:00:00 2001 From: Airat Shigapov Date: Fri, 21 Oct 2016 18:05:36 +0300 Subject: Fix wrong endpoint in api/users documentation, fix same typo in spec describe blocks --- doc/api/users.md | 2 +- spec/requests/api/users_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api/users.md b/doc/api/users.md index 2b12770d5a5..a50ba5432fe 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -643,7 +643,7 @@ Parameters: | `id` | integer | yes | The ID of the user | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/user/:id/events +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/users/:id/events ``` Example response: diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f83f4d2c9b1..d48752473f3 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -846,7 +846,7 @@ describe API::API, api: true do end end - describe 'PUT /user/:id/block' do + describe 'PUT /users/:id/block' do before { admin } it 'blocks existing user' do put api("/users/#{user.id}/block", admin) @@ -873,7 +873,7 @@ describe API::API, api: true do end end - describe 'PUT /user/:id/unblock' do + describe 'PUT /users/:id/unblock' do let(:blocked_user) { create(:user, state: 'blocked') } before { admin } @@ -914,7 +914,7 @@ describe API::API, api: true do end end - describe 'GET /user/:id/events' do + describe 'GET /users/:id/events' do let(:user) { create(:user) } let(:project) { create(:empty_project) } let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) } -- cgit v1.2.1 From 97731760d7252acf8ee94c707c0e107492b1ef24 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 21 Oct 2016 18:13:41 +0200 Subject: Re-organize queues to use for Sidekiq Dumping too many jobs in the same queue (e.g. the "default" queue) is a dangerous setup. Jobs that take a long time to process can effectively block any other work from being performed given there are enough of these jobs. Furthermore it becomes harder to monitor the jobs as a single queue could contain jobs for different workers. In such a setup the only reliable way of getting counts per job is to iterate over all jobs in a queue, which is a rather time consuming process. By using separate queues for various workers we have better control over throughput, we can add weight to queues, and we can monitor queues better. Some workers still use the same queue whenever their work is related. For example, the various CI pipeline workers use the same "pipeline" queue. This commit includes a Rails migration that moves Sidekiq jobs from the old queues to the new ones. This migration also takes care of doing the inverse if ever needed. This does require downtime as otherwise new jobs could be scheduled in the old queues after this migration completes. This commit also includes an RSpec test that blacklists the use of the "default" queue and ensures cron workers use the "cronjob" queue. Fixes gitlab-org/gitlab-ce#23370 --- CHANGELOG.md | 1 + app/workers/admin_email_worker.rb | 3 +- app/workers/build_coverage_worker.rb | 2 +- app/workers/build_email_worker.rb | 1 + app/workers/build_finished_worker.rb | 1 + app/workers/build_hooks_worker.rb | 2 +- app/workers/build_success_worker.rb | 2 +- app/workers/clear_database_cache_worker.rb | 1 + app/workers/concerns/build_queue.rb | 8 ++ app/workers/concerns/cronjob_queue.rb | 9 ++ app/workers/concerns/dedicated_sidekiq_queue.rb | 9 ++ app/workers/concerns/pipeline_queue.rb | 8 ++ app/workers/concerns/repository_check_queue.rb | 8 ++ app/workers/delete_user_worker.rb | 1 + app/workers/email_receiver_worker.rb | 3 +- app/workers/emails_on_push_worker.rb | 2 +- app/workers/expire_build_artifacts_worker.rb | 1 + .../expire_build_instance_artifacts_worker.rb | 1 + app/workers/git_garbage_collect_worker.rb | 3 +- app/workers/gitlab_shell_worker.rb | 3 +- app/workers/group_destroy_worker.rb | 3 +- .../import_export_project_cleanup_worker.rb | 3 +- app/workers/irker_worker.rb | 1 + app/workers/merge_worker.rb | 3 +- app/workers/new_note_worker.rb | 3 +- app/workers/pipeline_hooks_worker.rb | 2 +- app/workers/pipeline_metrics_worker.rb | 3 +- app/workers/pipeline_process_worker.rb | 3 +- app/workers/pipeline_success_worker.rb | 2 +- app/workers/pipeline_update_worker.rb | 3 +- app/workers/post_receive.rb | 3 +- app/workers/project_cache_worker.rb | 3 +- app/workers/project_destroy_worker.rb | 3 +- app/workers/project_export_worker.rb | 3 +- app/workers/project_service_worker.rb | 3 +- app/workers/project_web_hook_worker.rb | 3 +- app/workers/prune_old_events_worker.rb | 1 + app/workers/remove_expired_group_links_worker.rb | 1 + app/workers/remove_expired_members_worker.rb | 1 + app/workers/repository_archive_cache_worker.rb | 3 +- app/workers/repository_check/batch_worker.rb | 21 ++-- app/workers/repository_check/clear_worker.rb | 3 +- .../repository_check/single_repository_worker.rb | 3 +- app/workers/repository_fork_worker.rb | 3 +- app/workers/repository_import_worker.rb | 3 +- app/workers/requests_profiles_worker.rb | 3 +- app/workers/stuck_ci_builds_worker.rb | 1 + app/workers/system_hook_worker.rb | 3 +- app/workers/trending_projects_worker.rb | 3 +- app/workers/update_merge_requests_worker.rb | 1 + bin/background_jobs | 3 +- config/application.rb | 3 +- config/sidekiq_queues.yml | 46 +++++++++ ...19190736_migrate_sidekiq_queues_from_default.rb | 109 +++++++++++++++++++++ doc/development/README.md | 3 +- doc/development/sidekiq_style_guide.md | 38 +++++++ spec/workers/concerns/build_queue_spec.rb | 14 +++ spec/workers/concerns/cronjob_queue_spec.rb | 18 ++++ .../concerns/dedicated_sidekiq_queue_spec.rb | 20 ++++ spec/workers/concerns/pipeline_queue_spec.rb | 14 +++ .../concerns/repository_check_queue_spec.rb | 18 ++++ spec/workers/every_sidekiq_worker_spec.rb | 44 +++++++++ 62 files changed, 425 insertions(+), 68 deletions(-) create mode 100644 app/workers/concerns/build_queue.rb create mode 100644 app/workers/concerns/cronjob_queue.rb create mode 100644 app/workers/concerns/dedicated_sidekiq_queue.rb create mode 100644 app/workers/concerns/pipeline_queue.rb create mode 100644 app/workers/concerns/repository_check_queue.rb create mode 100644 config/sidekiq_queues.yml create mode 100644 db/migrate/20161019190736_migrate_sidekiq_queues_from_default.rb create mode 100644 doc/development/sidekiq_style_guide.md create mode 100644 spec/workers/concerns/build_queue_spec.rb create mode 100644 spec/workers/concerns/cronjob_queue_spec.rb create mode 100644 spec/workers/concerns/dedicated_sidekiq_queue_spec.rb create mode 100644 spec/workers/concerns/pipeline_queue_spec.rb create mode 100644 spec/workers/concerns/repository_check_queue_spec.rb create mode 100644 spec/workers/every_sidekiq_worker_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 518d0362d07..52d435df8f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.13.0 (2016-10-22) - Fix save button on project pipeline settings page. (!6955) + - All Sidekiq workers now use their own queue - Avoid race condition when asynchronously removing expired artifacts. (!6881) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb index 667fff031dd..c2dc955b27c 100644 --- a/app/workers/admin_email_worker.rb +++ b/app/workers/admin_email_worker.rb @@ -1,7 +1,6 @@ class AdminEmailWorker include Sidekiq::Worker - - sidekiq_options retry: false # this job auto-repeats via sidekiq-cron + include CronjobQueue def perform repository_check_failed_count = Project.where(last_repository_check_failed: true).count diff --git a/app/workers/build_coverage_worker.rb b/app/workers/build_coverage_worker.rb index 0680645a8db..def0ab1dde1 100644 --- a/app/workers/build_coverage_worker.rb +++ b/app/workers/build_coverage_worker.rb @@ -1,6 +1,6 @@ class BuildCoverageWorker include Sidekiq::Worker - sidekiq_options queue: :default + include BuildQueue def perform(build_id) Ci::Build.find_by(id: build_id) diff --git a/app/workers/build_email_worker.rb b/app/workers/build_email_worker.rb index 1c7a04a66a8..5fdb1f2baa0 100644 --- a/app/workers/build_email_worker.rb +++ b/app/workers/build_email_worker.rb @@ -1,5 +1,6 @@ class BuildEmailWorker include Sidekiq::Worker + include BuildQueue def perform(build_id, recipients, push_data) recipients.each do |recipient| diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb index e7286b77ac5..466410bf08c 100644 --- a/app/workers/build_finished_worker.rb +++ b/app/workers/build_finished_worker.rb @@ -1,5 +1,6 @@ class BuildFinishedWorker include Sidekiq::Worker + include BuildQueue def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb index e22ececb3fd..9965af935d4 100644 --- a/app/workers/build_hooks_worker.rb +++ b/app/workers/build_hooks_worker.rb @@ -1,6 +1,6 @@ class BuildHooksWorker include Sidekiq::Worker - sidekiq_options queue: :default + include BuildQueue def perform(build_id) Ci::Build.find_by(id: build_id) diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index 500d357ce31..e0ad5268664 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -1,6 +1,6 @@ class BuildSuccessWorker include Sidekiq::Worker - sidekiq_options queue: :default + include BuildQueue def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| diff --git a/app/workers/clear_database_cache_worker.rb b/app/workers/clear_database_cache_worker.rb index c541daba50e..c4cb4733482 100644 --- a/app/workers/clear_database_cache_worker.rb +++ b/app/workers/clear_database_cache_worker.rb @@ -1,6 +1,7 @@ # This worker clears all cache fields in the database, working in batches. class ClearDatabaseCacheWorker include Sidekiq::Worker + include DedicatedSidekiqQueue BATCH_SIZE = 1000 diff --git a/app/workers/concerns/build_queue.rb b/app/workers/concerns/build_queue.rb new file mode 100644 index 00000000000..cf0ead40a8b --- /dev/null +++ b/app/workers/concerns/build_queue.rb @@ -0,0 +1,8 @@ +# Concern for setting Sidekiq settings for the various CI build workers. +module BuildQueue + extend ActiveSupport::Concern + + included do + sidekiq_options queue: :build + end +end diff --git a/app/workers/concerns/cronjob_queue.rb b/app/workers/concerns/cronjob_queue.rb new file mode 100644 index 00000000000..e918bb011e0 --- /dev/null +++ b/app/workers/concerns/cronjob_queue.rb @@ -0,0 +1,9 @@ +# Concern that sets various Sidekiq settings for workers executed using a +# cronjob. +module CronjobQueue + extend ActiveSupport::Concern + + included do + sidekiq_options queue: :cronjob, retry: false + end +end diff --git a/app/workers/concerns/dedicated_sidekiq_queue.rb b/app/workers/concerns/dedicated_sidekiq_queue.rb new file mode 100644 index 00000000000..132bae6022b --- /dev/null +++ b/app/workers/concerns/dedicated_sidekiq_queue.rb @@ -0,0 +1,9 @@ +# Concern that sets the queue of a Sidekiq worker based on the worker's class +# name/namespace. +module DedicatedSidekiqQueue + extend ActiveSupport::Concern + + included do + sidekiq_options queue: name.sub(/Worker\z/, '').underscore.tr('/', '_') + end +end diff --git a/app/workers/concerns/pipeline_queue.rb b/app/workers/concerns/pipeline_queue.rb new file mode 100644 index 00000000000..ca3860e1d38 --- /dev/null +++ b/app/workers/concerns/pipeline_queue.rb @@ -0,0 +1,8 @@ +# Concern for setting Sidekiq settings for the various CI pipeline workers. +module PipelineQueue + extend ActiveSupport::Concern + + included do + sidekiq_options queue: :pipeline + end +end diff --git a/app/workers/concerns/repository_check_queue.rb b/app/workers/concerns/repository_check_queue.rb new file mode 100644 index 00000000000..a597321ccf4 --- /dev/null +++ b/app/workers/concerns/repository_check_queue.rb @@ -0,0 +1,8 @@ +# Concern for setting Sidekiq settings for the various repository check workers. +module RepositoryCheckQueue + extend ActiveSupport::Concern + + included do + sidekiq_options queue: :repository_check, retry: false + end +end diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb index 6ff361e4d80..3194c389b3d 100644 --- a/app/workers/delete_user_worker.rb +++ b/app/workers/delete_user_worker.rb @@ -1,5 +1,6 @@ class DeleteUserWorker include Sidekiq::Worker + include DedicatedSidekiqQueue def perform(current_user_id, delete_user_id, options = {}) delete_user = User.find(delete_user_id) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 842eebdea9e..d3f7e479a8d 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -1,7 +1,6 @@ class EmailReceiverWorker include Sidekiq::Worker - - sidekiq_options queue: :incoming_email + include DedicatedSidekiqQueue def perform(raw) return unless Gitlab::IncomingEmail.enabled? diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 1dc7e0adef7..b9cd49985dc 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -1,7 +1,7 @@ class EmailsOnPushWorker include Sidekiq::Worker + include DedicatedSidekiqQueue - sidekiq_options queue: :mailers attr_reader :email, :skip_premailer def perform(project_id, recipients, push_data, options = {}) diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb index 174eabff9fd..a27585fd389 100644 --- a/app/workers/expire_build_artifacts_worker.rb +++ b/app/workers/expire_build_artifacts_worker.rb @@ -1,5 +1,6 @@ class ExpireBuildArtifactsWorker include Sidekiq::Worker + include CronjobQueue def perform Rails.logger.info 'Scheduling removal of build artifacts' diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb index d9e2cc37bb3..eb403c134d1 100644 --- a/app/workers/expire_build_instance_artifacts_worker.rb +++ b/app/workers/expire_build_instance_artifacts_worker.rb @@ -1,5 +1,6 @@ class ExpireBuildInstanceArtifactsWorker include Sidekiq::Worker + include DedicatedSidekiqQueue def perform(build_id) build = Ci::Build diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb index a6cefd4d601..65f8093b5b0 100644 --- a/app/workers/git_garbage_collect_worker.rb +++ b/app/workers/git_garbage_collect_worker.rb @@ -1,8 +1,9 @@ class GitGarbageCollectWorker include Sidekiq::Worker include Gitlab::ShellAdapter + include DedicatedSidekiqQueue - sidekiq_options queue: :gitlab_shell, retry: false + sidekiq_options retry: false def perform(project_id) project = Project.find(project_id) diff --git a/app/workers/gitlab_shell_worker.rb b/app/workers/gitlab_shell_worker.rb index cfeda88bbc5..964287a1793 100644 --- a/app/workers/gitlab_shell_worker.rb +++ b/app/workers/gitlab_shell_worker.rb @@ -1,8 +1,7 @@ class GitlabShellWorker include Sidekiq::Worker include Gitlab::ShellAdapter - - sidekiq_options queue: :gitlab_shell + include DedicatedSidekiqQueue def perform(action, *arg) gitlab_shell.send(action, *arg) diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb index 5048746f09b..a49a5fd0855 100644 --- a/app/workers/group_destroy_worker.rb +++ b/app/workers/group_destroy_worker.rb @@ -1,7 +1,6 @@ class GroupDestroyWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include DedicatedSidekiqQueue def perform(group_id, user_id) begin diff --git a/app/workers/import_export_project_cleanup_worker.rb b/app/workers/import_export_project_cleanup_worker.rb index 72e3a9ae734..7957ed807ab 100644 --- a/app/workers/import_export_project_cleanup_worker.rb +++ b/app/workers/import_export_project_cleanup_worker.rb @@ -1,7 +1,6 @@ class ImportExportProjectCleanupWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include CronjobQueue def perform ImportExportCleanUpService.new.execute diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 19f38358eb5..7e44b241743 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -3,6 +3,7 @@ require 'socket' class IrkerWorker include Sidekiq::Worker + include DedicatedSidekiqQueue def perform(project_id, chans, colors, push_data, settings) project = Project.find(project_id) diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb index c87c0a252b1..79efca4f2f9 100644 --- a/app/workers/merge_worker.rb +++ b/app/workers/merge_worker.rb @@ -1,7 +1,6 @@ class MergeWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include DedicatedSidekiqQueue def perform(merge_request_id, current_user_id, params) params = params.with_indifferent_access diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb index 1b3232cd365..c3e62bb88c0 100644 --- a/app/workers/new_note_worker.rb +++ b/app/workers/new_note_worker.rb @@ -1,7 +1,6 @@ class NewNoteWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include DedicatedSidekiqQueue def perform(note_id, note_params) note = Note.find(note_id) diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb index ab5e9f6daad..7e36eacebf8 100644 --- a/app/workers/pipeline_hooks_worker.rb +++ b/app/workers/pipeline_hooks_worker.rb @@ -1,6 +1,6 @@ class PipelineHooksWorker include Sidekiq::Worker - sidekiq_options queue: :default + include PipelineQueue def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id) diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb index 7bb92df3bbd..34f6ef161fb 100644 --- a/app/workers/pipeline_metrics_worker.rb +++ b/app/workers/pipeline_metrics_worker.rb @@ -1,7 +1,6 @@ class PipelineMetricsWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include PipelineQueue def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb index f44227d7086..357e4a9a1c3 100644 --- a/app/workers/pipeline_process_worker.rb +++ b/app/workers/pipeline_process_worker.rb @@ -1,7 +1,6 @@ class PipelineProcessWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include PipelineQueue def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id) diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb index 5dd443fea59..2aa6fff24da 100644 --- a/app/workers/pipeline_success_worker.rb +++ b/app/workers/pipeline_success_worker.rb @@ -1,6 +1,6 @@ class PipelineSuccessWorker include Sidekiq::Worker - sidekiq_options queue: :default + include PipelineQueue def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb index 44a7f24e401..96c4152c674 100644 --- a/app/workers/pipeline_update_worker.rb +++ b/app/workers/pipeline_update_worker.rb @@ -1,7 +1,6 @@ class PipelineUpdateWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include PipelineQueue def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id) diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index a9a2b716005..eee0ca12af9 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -1,7 +1,6 @@ class PostReceive include Sidekiq::Worker - - sidekiq_options queue: :post_receive + include DedicatedSidekiqQueue def perform(repo_path, identifier, changes) if path = Gitlab.config.repositories.storages.find { |p| repo_path.start_with?(p[1].to_s) } diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb index 0d524e88dc3..71b274e0c99 100644 --- a/app/workers/project_cache_worker.rb +++ b/app/workers/project_cache_worker.rb @@ -5,8 +5,7 @@ # storage engine as much. class ProjectCacheWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include DedicatedSidekiqQueue LEASE_TIMEOUT = 15.minutes.to_i diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb index 3062301a9b1..b462327490e 100644 --- a/app/workers/project_destroy_worker.rb +++ b/app/workers/project_destroy_worker.rb @@ -1,7 +1,6 @@ class ProjectDestroyWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include DedicatedSidekiqQueue def perform(project_id, user_id, params) begin diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb index 615311e63f5..6009aa1b191 100644 --- a/app/workers/project_export_worker.rb +++ b/app/workers/project_export_worker.rb @@ -1,7 +1,8 @@ class ProjectExportWorker include Sidekiq::Worker + include DedicatedSidekiqQueue - sidekiq_options queue: :gitlab_shell, retry: 3 + sidekiq_options retry: 3 def perform(current_user_id, project_id) current_user = User.find(current_user_id) diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb index 64d39c4d3f7..fdfdeab7b41 100644 --- a/app/workers/project_service_worker.rb +++ b/app/workers/project_service_worker.rb @@ -1,7 +1,6 @@ class ProjectServiceWorker include Sidekiq::Worker - - sidekiq_options queue: :project_web_hook + include DedicatedSidekiqQueue def perform(hook_id, data) data = data.with_indifferent_access diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb index fb878965288..efb85eafd15 100644 --- a/app/workers/project_web_hook_worker.rb +++ b/app/workers/project_web_hook_worker.rb @@ -1,7 +1,6 @@ class ProjectWebHookWorker include Sidekiq::Worker - - sidekiq_options queue: :project_web_hook + include DedicatedSidekiqQueue def perform(hook_id, data, hook_name) data = data.with_indifferent_access diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb index 5883cafe1d1..392abb9c21b 100644 --- a/app/workers/prune_old_events_worker.rb +++ b/app/workers/prune_old_events_worker.rb @@ -1,5 +1,6 @@ class PruneOldEventsWorker include Sidekiq::Worker + include CronjobQueue def perform # Contribution calendar shows maximum 12 months of events. diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb index 246c8b6650a..2a619f83410 100644 --- a/app/workers/remove_expired_group_links_worker.rb +++ b/app/workers/remove_expired_group_links_worker.rb @@ -1,5 +1,6 @@ class RemoveExpiredGroupLinksWorker include Sidekiq::Worker + include CronjobQueue def perform ProjectGroupLink.expired.destroy_all diff --git a/app/workers/remove_expired_members_worker.rb b/app/workers/remove_expired_members_worker.rb index cf765af97ce..31f652e5f9b 100644 --- a/app/workers/remove_expired_members_worker.rb +++ b/app/workers/remove_expired_members_worker.rb @@ -1,5 +1,6 @@ class RemoveExpiredMembersWorker include Sidekiq::Worker + include CronjobQueue def perform Member.expired.find_each do |member| diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb index a2e49c61f59..e47069df189 100644 --- a/app/workers/repository_archive_cache_worker.rb +++ b/app/workers/repository_archive_cache_worker.rb @@ -1,7 +1,6 @@ class RepositoryArchiveCacheWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include CronjobQueue def perform RepositoryArchiveCleanUpService.new.execute diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb index a3e16fa5212..c3e7491ec4e 100644 --- a/app/workers/repository_check/batch_worker.rb +++ b/app/workers/repository_check/batch_worker.rb @@ -1,14 +1,13 @@ module RepositoryCheck class BatchWorker include Sidekiq::Worker - + include CronjobQueue + RUN_TIME = 3600 - - sidekiq_options retry: false - + def perform start = Time.now - + # This loop will break after a little more than one hour ('a little # more' because `git fsck` may take a few minutes), or if it runs out of # projects to check. By default sidekiq-cron will start a new @@ -17,15 +16,15 @@ module RepositoryCheck project_ids.each do |project_id| break if Time.now - start >= RUN_TIME break unless current_settings.repository_checks_enabled - + next unless try_obtain_lease(project_id) - + SingleRepositoryWorker.new.perform(project_id) end end - + private - + # Project.find_each does not support WHERE clauses and # Project.find_in_batches does not support ordering. So we just build an # array of ID's. This is OK because we do it only once an hour, because @@ -39,7 +38,7 @@ module RepositoryCheck reorder('last_repository_check_at ASC').limit(limit).pluck(:id) never_checked_projects + old_check_projects end - + def try_obtain_lease(id) # Use a 24-hour timeout because on servers/projects where 'git fsck' is # super slow we definitely do not want to run it twice in parallel. @@ -48,7 +47,7 @@ module RepositoryCheck timeout: 24.hours ).try_obtain end - + def current_settings # No caching of the settings! If we cache them and an admin disables # this feature, an active RepositoryCheckWorker would keep going for up diff --git a/app/workers/repository_check/clear_worker.rb b/app/workers/repository_check/clear_worker.rb index b7202ddff34..1f1b38540ee 100644 --- a/app/workers/repository_check/clear_worker.rb +++ b/app/workers/repository_check/clear_worker.rb @@ -1,8 +1,7 @@ module RepositoryCheck class ClearWorker include Sidekiq::Worker - - sidekiq_options retry: false + include RepositoryCheckQueue def perform # Do small batched updates because these updates will be slow and locking diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb index 98ddf5d0688..3d8bfc6fc6c 100644 --- a/app/workers/repository_check/single_repository_worker.rb +++ b/app/workers/repository_check/single_repository_worker.rb @@ -1,8 +1,7 @@ module RepositoryCheck class SingleRepositoryWorker include Sidekiq::Worker - - sidekiq_options retry: false + include RepositoryCheckQueue def perform(project_id) project = Project.find(project_id) diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index 61ed1c38ac4..efc99ec962a 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -1,8 +1,7 @@ class RepositoryForkWorker include Sidekiq::Worker include Gitlab::ShellAdapter - - sidekiq_options queue: :gitlab_shell + include DedicatedSidekiqQueue def perform(project_id, forked_from_repository_storage_path, source_path, target_path) Gitlab::Metrics.add_event(:fork_repository, diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index d2ca8813ab9..c8a77e21c12 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -1,8 +1,7 @@ class RepositoryImportWorker include Sidekiq::Worker include Gitlab::ShellAdapter - - sidekiq_options queue: :gitlab_shell + include DedicatedSidekiqQueue attr_accessor :project, :current_user diff --git a/app/workers/requests_profiles_worker.rb b/app/workers/requests_profiles_worker.rb index 9dd228a2483..703b025d76e 100644 --- a/app/workers/requests_profiles_worker.rb +++ b/app/workers/requests_profiles_worker.rb @@ -1,7 +1,6 @@ class RequestsProfilesWorker include Sidekiq::Worker - - sidekiq_options queue: :default + include CronjobQueue def perform Gitlab::RequestProfiler.remove_all_profiles diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb index 6828013b377..b70df5a1afa 100644 --- a/app/workers/stuck_ci_builds_worker.rb +++ b/app/workers/stuck_ci_builds_worker.rb @@ -1,5 +1,6 @@ class StuckCiBuildsWorker include Sidekiq::Worker + include CronjobQueue BUILD_STUCK_TIMEOUT = 1.day diff --git a/app/workers/system_hook_worker.rb b/app/workers/system_hook_worker.rb index a122c274763..baf2f12eeac 100644 --- a/app/workers/system_hook_worker.rb +++ b/app/workers/system_hook_worker.rb @@ -1,7 +1,6 @@ class SystemHookWorker include Sidekiq::Worker - - sidekiq_options queue: :system_hook + include DedicatedSidekiqQueue def perform(hook_id, data, hook_name) SystemHook.find(hook_id).execute(data, hook_name) diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb index df4c4a6628b..0531630d13a 100644 --- a/app/workers/trending_projects_worker.rb +++ b/app/workers/trending_projects_worker.rb @@ -1,7 +1,6 @@ class TrendingProjectsWorker include Sidekiq::Worker - - sidekiq_options queue: :trending_projects + include CronjobQueue def perform Rails.logger.info('Refreshing trending projects') diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb index 03f0528cdae..acc4d858136 100644 --- a/app/workers/update_merge_requests_worker.rb +++ b/app/workers/update_merge_requests_worker.rb @@ -1,5 +1,6 @@ class UpdateMergeRequestsWorker include Sidekiq::Worker + include DedicatedSidekiqQueue def perform(project_id, user_id, oldrev, newrev, ref) project = Project.find_by(id: project_id) diff --git a/bin/background_jobs b/bin/background_jobs index 25a578a1c49..f28e2f722dc 100755 --- a/bin/background_jobs +++ b/bin/background_jobs @@ -4,6 +4,7 @@ cd $(dirname $0)/.. app_root=$(pwd) sidekiq_pidfile="$app_root/tmp/pids/sidekiq.pid" sidekiq_logfile="$app_root/log/sidekiq.log" +sidekiq_config="$app_root/config/sidekiq_queues.yml" gitlab_user=$(ls -l config.ru | awk '{print $3}') warn() @@ -37,7 +38,7 @@ start_no_deamonize() start_sidekiq() { - exec bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@" + exec bundle exec sidekiq -C "${sidekiq_config}" -e $RAILS_ENV -P $sidekiq_pidfile "$@" } load_ok() diff --git a/config/application.rb b/config/application.rb index f3337b00dc6..92c8467e7f4 100644 --- a/config/application.rb +++ b/config/application.rb @@ -24,7 +24,8 @@ module Gitlab #{config.root}/app/models/ci #{config.root}/app/models/hooks #{config.root}/app/models/members - #{config.root}/app/models/project_services)) + #{config.root}/app/models/project_services + #{config.root}/app/workers/concerns)) config.generators.templates.push("#{config.root}/generator_templates") diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml new file mode 100644 index 00000000000..c2e880e891f --- /dev/null +++ b/config/sidekiq_queues.yml @@ -0,0 +1,46 @@ +# This configuration file should be exclusively used to set queue settings for +# Sidekiq. Any other setting should be specified using the Sidekiq CLI or the +# Sidekiq Ruby API (see config/initializers/sidekiq.rb). +--- +# All the queues to process and their weights. Every queue _must_ have a weight +# defined. +# +# The available weights are as follows +# +# 1: low priority +# 2: medium priority +# 3: high priority +# 5: _super_ high priority, this should only be used for _very_ important queues +# +# As per http://stackoverflow.com/a/21241357/290102 the formula for calculating +# the likelihood of a job being popped off a queue (given all queues have work +# to perform) is: +# +# chance = (queue weight / total weight of all queues) * 100 +:queues: + - [post_receive, 5] + - [merge, 5] + - [update_merge_requests, 3] + - [new_note, 2] + - [build, 2] + - [pipeline, 2] + - [gitlab_shell, 2] + - [email_receiver, 2] + - [emails_on_push, 2] + - [repository_fork, 1] + - [repository_import, 1] + - [project_service, 1] + - [clear_database_cache, 1] + - [delete_user, 1] + - [expire_build_instance_artifacts, 1] + - [group_destroy, 1] + - [irker, 1] + - [project_cache, 1] + - [project_destroy, 1] + - [project_export, 1] + - [project_web_hook, 1] + - [repository_check, 1] + - [system_hook, 1] + - [git_garbage_collect, 1] + - [cronjob, 1] + - [default, 1] diff --git a/db/migrate/20161019190736_migrate_sidekiq_queues_from_default.rb b/db/migrate/20161019190736_migrate_sidekiq_queues_from_default.rb new file mode 100644 index 00000000000..e875213ab96 --- /dev/null +++ b/db/migrate/20161019190736_migrate_sidekiq_queues_from_default.rb @@ -0,0 +1,109 @@ +require 'json' + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class MigrateSidekiqQueuesFromDefault < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + + DOWNTIME_REASON = <<-EOF + Moving Sidekiq jobs from queues requires Sidekiq to be stopped. Not stopping + Sidekiq will result in the loss of jobs that are scheduled after this + migration completes. + EOF + + disable_ddl_transaction! + + # Jobs for which the queue names have been changed (e.g. multiple workers + # using the same non-default queue). + # + # The keys are the old queue names, the values the jobs to move and their new + # queue names. + RENAMED_QUEUES = { + gitlab_shell: { + 'GitGarbageCollectorWorker' => :git_garbage_collector, + 'ProjectExportWorker' => :project_export, + 'RepositoryForkWorker' => :repository_fork, + 'RepositoryImportWorker' => :repository_import + }, + project_web_hook: { + 'ProjectServiceWorker' => :project_service + }, + incoming_email: { + 'EmailReceiverWorker' => :email_receiver + }, + mailers: { + 'EmailsOnPushWorker' => :emails_on_push + }, + default: { + 'AdminEmailWorker' => :cronjob, + 'BuildCoverageWorker' => :build, + 'BuildEmailWorker' => :build, + 'BuildFinishedWorker' => :build, + 'BuildHooksWorker' => :build, + 'BuildSuccessWorker' => :build, + 'ClearDatabaseCacheWorker' => :clear_database_cache, + 'DeleteUserWorker' => :delete_user, + 'ExpireBuildArtifactsWorker' => :cronjob, + 'ExpireBuildInstanceArtifactsWorker' => :expire_build_instance_artifacts, + 'GroupDestroyWorker' => :group_destroy, + 'ImportExportProjectCleanupWorker' => :cronjob, + 'IrkerWorker' => :irker, + 'MergeWorker' => :merge, + 'NewNoteWorker' => :new_note, + 'PipelineHooksWorker' => :pipeline, + 'PipelineMetricsWorker' => :pipeline, + 'PipelineProcessWorker' => :pipeline, + 'PipelineSuccessWorker' => :pipeline, + 'PipelineUpdateWorker' => :pipeline, + 'ProjectCacheWorker' => :project_cache, + 'ProjectDestroyWorker' => :project_destroy, + 'PruneOldEventsWorker' => :cronjob, + 'RemoveExpiredGroupLinksWorker' => :cronjob, + 'RemoveExpiredMembersWorker' => :cronjob, + 'RepositoryArchiveCacheWorker' => :cronjob, + 'RepositoryCheck::BatchWorker' => :cronjob, + 'RepositoryCheck::ClearWorker' => :repository_check, + 'RepositoryCheck::SingleRepositoryWorker' => :repository_check, + 'RequestsProfilesWorker' => :cronjob, + 'StuckCiBuildsWorker' => :cronjob, + 'UpdateMergeRequestsWorker' => :update_merge_requests + } + } + + def up + Sidekiq.redis do |redis| + RENAMED_QUEUES.each do |queue, jobs| + migrate_from_queue(redis, queue, jobs) + end + end + end + + def down + Sidekiq.redis do |redis| + RENAMED_QUEUES.each do |dest_queue, jobs| + jobs.each do |worker, from_queue| + migrate_from_queue(redis, from_queue, worker => dest_queue) + end + end + end + end + + def migrate_from_queue(redis, queue, job_mapping) + while job = redis.lpop("queue:#{queue}") + payload = JSON.load(job) + new_queue = job_mapping[payload['class']] + + # If we have no target queue to migrate to we're probably dealing with + # some ancient job for which the worker no longer exists. In that case + # there's no sane option we can take, other than just dropping the job. + next unless new_queue + + payload['queue'] = new_queue + + redis.lpush("queue:#{new_queue}", JSON.dump(payload)) + end + end +end diff --git a/doc/development/README.md b/doc/development/README.md index 9706cb1de7f..fb6a8a5b095 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -14,7 +14,8 @@ - [Testing standards and style guidelines](testing.md) - [UI guide](ui_guide.md) for building GitLab with existing CSS styles and elements - [Frontend guidelines](frontend.md) -- [SQL guidelines](sql.md) for SQL guidelines +- [SQL guidelines](sql.md) for working with SQL queries +- [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers ## Process diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md new file mode 100644 index 00000000000..e3a20f29a09 --- /dev/null +++ b/doc/development/sidekiq_style_guide.md @@ -0,0 +1,38 @@ +# Sidekiq Style Guide + +This document outlines various guidelines that should be followed when adding or +modifying Sidekiq workers. + +## Default Queue + +Use of the "default" queue is not allowed. Every worker should use a queue that +matches the worker's purpose the closest. For example, workers that are to be +executed periodically should use the "cronjob" queue. + +A list of all available queues can be found in `config/sidekiq_queues.yml`. + +## Dedicated Queues + +Most workers should use their own queue. To ease this process a worker can +include the `DedicatedSidekiqQueue` concern as follows: + +```ruby +class ProcessSomethingWorker + include Sidekiq::Worker + include DedicatedSidekiqQueue +end +``` + +This will set the queue name based on the class' name, minus the `Worker` +suffix. In the above example this would lead to the queue being +`process_something`. + +In some cases multiple workers do use the same queue. For example, the various +workers for updating CI pipelines all use the `pipeline` queue. Adding workers +to existing queues should be done with care, as adding more workers can lead to +slow jobs blocking work (even for different jobs) on the shared queue. + +## Tests + +Each Sidekiq worker must be tested using RSpec, just like any other class. These +tests should be placed in `spec/workers`. diff --git a/spec/workers/concerns/build_queue_spec.rb b/spec/workers/concerns/build_queue_spec.rb new file mode 100644 index 00000000000..6bf955e0be2 --- /dev/null +++ b/spec/workers/concerns/build_queue_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe BuildQueue do + let(:worker) do + Class.new do + include Sidekiq::Worker + include BuildQueue + end + end + + it 'sets the queue name of a worker' do + expect(worker.sidekiq_options['queue'].to_s).to eq('build') + end +end diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb new file mode 100644 index 00000000000..5d1336c21a6 --- /dev/null +++ b/spec/workers/concerns/cronjob_queue_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe CronjobQueue do + let(:worker) do + Class.new do + include Sidekiq::Worker + include CronjobQueue + end + end + + it 'sets the queue name of a worker' do + expect(worker.sidekiq_options['queue'].to_s).to eq('cronjob') + end + + it 'disables retrying of failed jobs' do + expect(worker.sidekiq_options['retry']).to eq(false) + end +end diff --git a/spec/workers/concerns/dedicated_sidekiq_queue_spec.rb b/spec/workers/concerns/dedicated_sidekiq_queue_spec.rb new file mode 100644 index 00000000000..512baec8b7e --- /dev/null +++ b/spec/workers/concerns/dedicated_sidekiq_queue_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe DedicatedSidekiqQueue do + let(:worker) do + Class.new do + def self.name + 'Foo::Bar::DummyWorker' + end + + include Sidekiq::Worker + include DedicatedSidekiqQueue + end + end + + describe 'queue names' do + it 'sets the queue name based on the class name' do + expect(worker.sidekiq_options['queue']).to eq('foo_bar_dummy') + end + end +end diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb new file mode 100644 index 00000000000..40794d0e42a --- /dev/null +++ b/spec/workers/concerns/pipeline_queue_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe PipelineQueue do + let(:worker) do + Class.new do + include Sidekiq::Worker + include PipelineQueue + end + end + + it 'sets the queue name of a worker' do + expect(worker.sidekiq_options['queue'].to_s).to eq('pipeline') + end +end diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb new file mode 100644 index 00000000000..8868e969829 --- /dev/null +++ b/spec/workers/concerns/repository_check_queue_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe RepositoryCheckQueue do + let(:worker) do + Class.new do + include Sidekiq::Worker + include RepositoryCheckQueue + end + end + + it 'sets the queue name of a worker' do + expect(worker.sidekiq_options['queue'].to_s).to eq('repository_check') + end + + it 'disables retrying of failed jobs' do + expect(worker.sidekiq_options['retry']).to eq(false) + end +end diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb new file mode 100644 index 00000000000..fc9adf47c1e --- /dev/null +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe 'Every Sidekiq worker' do + let(:workers) do + root = Rails.root.join('app', 'workers') + concerns = root.join('concerns').to_s + + workers = Dir[root.join('**', '*.rb')]. + reject { |path| path.start_with?(concerns) } + + workers.map do |path| + ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '') + + ns.camelize.constantize + end + end + + it 'does not use the default queue' do + workers.each do |worker| + expect(worker.sidekiq_options['queue'].to_s).not_to eq('default') + end + end + + it 'uses the cronjob queue when the worker runs as a cronjob' do + cron_workers = Settings.cron_jobs. + map { |job_name, options| options['job_class'].constantize }. + to_set + + workers.each do |worker| + next unless cron_workers.include?(worker) + + expect(worker.sidekiq_options['queue'].to_s).to eq('cronjob') + end + end + + it 'defines the queue in the Sidekiq configuration file' do + config = YAML.load_file(Rails.root.join('config', 'sidekiq_queues.yml').to_s) + queue_names = config[:queues].map { |(queue, _)| queue }.to_set + + workers.each do |worker| + expect(queue_names).to include(worker.sidekiq_options['queue'].to_s) + end + end +end -- cgit v1.2.1 From e32858e7734a13b820d2f1439e189c1586af29d8 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Fri, 21 Oct 2016 23:08:44 +0600 Subject: removes extra line for empty issue description --- app/assets/stylesheets/framework/common.scss | 2 ++ app/views/projects/issues/show.html.haml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 81e4e264560..0d00b6a0d9c 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -372,3 +372,5 @@ table { margin-right: -$gl-padding; border-top: 1px solid $border-color; } + +.hide-bottom-border { border-bottom: none;} diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 6f3f238a436..6defd7834bf 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -53,7 +53,7 @@ .issue-details.issuable-details - .detail-page-description.content-block + .detail-page-description.content-block{class: ('hide-bottom-border' unless @issue.description.present? )} %h2.title = markdown_field(@issue, :title) - if @issue.description.present? -- cgit v1.2.1 From 0ca0697a9c2501db23458aa8ef35a8b030625f93 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Fri, 21 Oct 2016 23:21:31 +0600 Subject: adds entry in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 518d0362d07..9207d98327c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method ## 8.13.0 (2016-10-22) - + - Removes extra line for empty issue description. (!7045) - Fix save button on project pipeline settings page. (!6955) - Avoid race condition when asynchronously removing expired artifacts. (!6881) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) -- cgit v1.2.1 From 3a29ea9da0abd6cfd0788f6d717a08862ed6b062 Mon Sep 17 00:00:00 2001 From: Lemures Lemniscati Date: Sat, 22 Oct 2016 03:07:26 +0900 Subject: Fix documents and comments on Build API `scope`. #23146 #19131 --- CHANGELOG.md | 1 + doc/api/builds.md | 8 ++++---- lib/api/builds.rb | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc3dd569aea..fa9f4dc6091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix: Backup restore doesn't clear cache - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method + - Fix documents and comments on Build API `scope` ## 8.13.0 (2016-10-22) diff --git a/doc/api/builds.md b/doc/api/builds.md index e40f198696d..0476cac0eda 100644 --- a/doc/api/builds.md +++ b/doc/api/builds.md @@ -11,10 +11,10 @@ GET /projects/:id/builds | Attribute | Type | Required | Description | |-----------|---------|----------|---------------------| | `id` | integer | yes | The ID of a project | -| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided | +| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all builds if none provided | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v3/projects/1/builds?scope%5B0%5D=pending&scope%5B1%5D=running' ``` Example of response @@ -132,10 +132,10 @@ GET /projects/:id/repository/commits/:sha/builds |-----------|---------|----------|---------------------| | `id` | integer | yes | The ID of a project | | `sha` | string | yes | The SHA id of a commit | -| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided | +| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all builds if none provided | ``` -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds" +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds?scope%5B0%5D=pending&scope%5B1%5D=running' ``` Example of response diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 52bdbcae5a8..7b00c5037f1 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -8,7 +8,7 @@ module API # # Parameters: # id (required) - The ID of a project - # scope (optional) - The scope of builds to show (one or array of: pending, running, failed, success, canceled; + # scope (optional) - The scope of builds to show (one or array of: created, pending, running, failed, success, canceled, skipped; # if none provided showing all builds) # Example Request: # GET /projects/:id/builds @@ -25,7 +25,7 @@ module API # Parameters: # id (required) - The ID of a project # sha (required) - The SHA id of a commit - # scope (optional) - The scope of builds to show (one or array of: pending, running, failed, success, canceled; + # scope (optional) - The scope of builds to show (one or array of: created, pending, running, failed, success, canceled, skipped; # if none provided showing all builds) # Example Request: # GET /projects/:id/repository/commits/:sha/builds -- cgit v1.2.1 From 94ceadb4a32a4a5128d43983206518d3159354e0 Mon Sep 17 00:00:00 2001 From: Winnie Date: Fri, 21 Oct 2016 20:18:03 +0000 Subject: Document link syntax introduced by !5586 --- doc/user/markdown.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 56e5b802a52..7a7a0b864bd 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -501,6 +501,10 @@ There are two ways to create links, inline-style and reference-style. [I'm a reference-style link][Arbitrary case-insensitive reference text] [I'm a relative reference to a repository file](LICENSE) + + [I am an absolute reference within the repository](/doc/user/markdown.md) + + [I link to the Milestones page](/../milestones) [You can use numbers for reference-style link definitions][1] @@ -518,6 +522,10 @@ There are two ways to create links, inline-style and reference-style. [I'm a relative reference to a repository file](LICENSE)[^1] +[I am an absolute reference within the repository](/doc/user/markdown.md) + +[I link to the Milestones page](/../milestones) + [You can use numbers for reference-style link definitions][1] Or leave it empty and use the [link text itself][] -- cgit v1.2.1 From fadaba000a2faba191177793ff6aba5a0ecdbe24 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Fri, 21 Oct 2016 11:31:15 -0700 Subject: Add an example of how to run the backups when using docker to the docs --- doc/raketasks/backup_restore.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 26baffdf792..fc0cd1b8af2 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -30,6 +30,10 @@ Use this if you've installed GitLab from source: ``` sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` +If you are running GitLab within a Docker container, you can run the backup from the host: +``` +docker -t exec gitlab-rake gitlab:backup:create +``` You can specify that portions of the application data be skipped using the environment variable `SKIP`. You can skip: -- cgit v1.2.1 From d79c41e7629b6e983c4cf5c0670a1fbab37528ed Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 21 Oct 2016 22:28:39 -0700 Subject: Fix bug where e-mails were not being sent out via Sidekiq By default, ActionMailer uses the "mailers" queue, but this entry was not included in the list of queues for Sidekiq to use. For more details: * https://github.com/plataformatec/devise/wiki/How-To:-Send-devise-emails-in-background-(Resque,-Sidekiq-and-Delayed::Job) * http://guides.rubyonrails.org/active_job_basics.html --- config/sidekiq_queues.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index c2e880e891f..f36fe893fd0 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -27,6 +27,7 @@ - [gitlab_shell, 2] - [email_receiver, 2] - [emails_on_push, 2] + - [mailers, 2] - [repository_fork, 1] - [repository_import, 1] - [project_service, 1] -- cgit v1.2.1 From 0890aeb61a5378ec3bb98511de236ee01eee8711 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 22 Oct 2016 01:59:40 -0700 Subject: Fix error in generating labels Attempting to generate default set of labels would result in an error: ArgumentError: wrong number of arguments (given 1, expected 0) Closes #23649 --- CHANGELOG.md | 3 +++ lib/gitlab/issues_labels.rb | 2 +- spec/controllers/projects/labels_controller_spec.rb | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c39ddca7cf..bfc6a586ade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ Please view this file on the master branch, on stable branches it's out of date. - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method +## 8.13.1 (unreleased) + - Fix error in generating labels + ## 8.13.0 (2016-10-22) - Fix save button on project pipeline settings page. (!6955) diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index 01a2c19ab23..dbc759367eb 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -19,7 +19,7 @@ module Gitlab ] labels.each do |params| - ::Labels::FindOrCreateService.new(project.owner, project).execute(params) + ::Labels::FindOrCreateService.new(project.owner, project, params).execute end end end diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 622ab154493..7ba4406c1f6 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -70,4 +70,19 @@ describe Projects::LabelsController do get :index, namespace_id: project.namespace.to_param, project_id: project.to_param end end + + describe 'POST #generate' do + let(:admin) { create(:admin) } + let(:project) { create(:empty_project) } + + before do + sign_in(admin) + end + + it 'creates labels' do + post :generate, namespace_id: project.namespace.to_param, project_id: project.to_param + + expect(response.code).to eq(302) + end + end end -- cgit v1.2.1 From c0eb2cbbb4b14e3abb843a65d685bee400b5fffe Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Sat, 22 Oct 2016 12:42:19 +0100 Subject: Stop clearing the database cache on rake cache:clear --- lib/tasks/cache.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake index a95a3455a4a..78ae187817a 100644 --- a/lib/tasks/cache.rake +++ b/lib/tasks/cache.rake @@ -29,5 +29,5 @@ namespace :cache do task all: [:db, :redis] end - task clear: 'cache:clear:all' + task clear: 'cache:clear:redis' end -- cgit v1.2.1 From e6968964870286af5ce6a1f7cf1152c057fd5c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Sat, 22 Oct 2016 13:46:23 +0200 Subject: Fix status code expectation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/controllers/projects/labels_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 7ba4406c1f6..41df63d445a 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -82,7 +82,7 @@ describe Projects::LabelsController do it 'creates labels' do post :generate, namespace_id: project.namespace.to_param, project_id: project.to_param - expect(response.code).to eq(302) + expect(response).to have_http_status(302) end end end -- cgit v1.2.1 From 3b7ca69e530d684faf89a8dc1ea48c26eed5ccb7 Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Sat, 22 Oct 2016 14:20:13 -0500 Subject: Fix sign in page Forgot your password link overlap --- CHANGELOG.md | 1 + app/assets/stylesheets/pages/login.scss | 6 ++++++ app/views/devise/sessions/_new_base.html.haml | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c39ddca7cf..d4a975e6c3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Fix: Backup restore doesn't clear cache - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method + - Fix Sign in page 'Forgot your password?' link overlaps on medium-large screens ## 8.13.0 (2016-10-22) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index bdb13bee178..51048649383 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -255,6 +255,12 @@ .new_user { position: relative; padding-bottom: 35px; + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + .forgot-password { + float: none !important; + margin-top: 5px; + } + } } .move-submit-down { diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 525e7d99d71..5fd896f6835 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -12,5 +12,5 @@ %label{for: "user_remember_me"} = f.check_box :remember_me %span Remember me - .pull-right + .pull-right.forgot-password = link_to "Forgot your password?", new_password_path(resource_name) -- cgit v1.2.1 From 2eb452402f6d9ac7dc6a5b083dab824fa9767c4d Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Sat, 22 Oct 2016 14:38:58 -0500 Subject: Add empty line before media query --- app/assets/stylesheets/pages/login.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 51048649383..02c4bbf3710 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -255,6 +255,7 @@ .new_user { position: relative; padding-bottom: 35px; + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { .forgot-password { float: none !important; -- cgit v1.2.1 From 204da00ea0dc52c6746b160fd698e07f55a7fc00 Mon Sep 17 00:00:00 2001 From: Jared Ready Date: Thu, 29 Sep 2016 20:18:46 -0500 Subject: Move spec/mailers/shared/notify.rb to spec/support --- spec/mailers/emails/builds_spec.rb | 1 - spec/mailers/emails/merge_requests_spec.rb | 1 - spec/mailers/emails/profile_spec.rb | 1 - spec/mailers/notify_spec.rb | 1 - spec/mailers/shared/notify.rb | 200 ----------------------------- spec/support/notify_shared_examples.rb | 200 +++++++++++++++++++++++++++++ 6 files changed, 200 insertions(+), 204 deletions(-) delete mode 100644 spec/mailers/shared/notify.rb create mode 100644 spec/support/notify_shared_examples.rb diff --git a/spec/mailers/emails/builds_spec.rb b/spec/mailers/emails/builds_spec.rb index 0df89938e97..d968096783c 100644 --- a/spec/mailers/emails/builds_spec.rb +++ b/spec/mailers/emails/builds_spec.rb @@ -1,6 +1,5 @@ require 'spec_helper' require 'email_spec' -require 'mailers/shared/notify' describe Notify do include EmailSpec::Matchers diff --git a/spec/mailers/emails/merge_requests_spec.rb b/spec/mailers/emails/merge_requests_spec.rb index 4d3811af254..e22858d1d8f 100644 --- a/spec/mailers/emails/merge_requests_spec.rb +++ b/spec/mailers/emails/merge_requests_spec.rb @@ -1,6 +1,5 @@ require 'spec_helper' require 'email_spec' -require 'mailers/shared/notify' describe Notify, "merge request notifications" do include EmailSpec::Matchers diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb index 781472d0c00..14bc062ef12 100644 --- a/spec/mailers/emails/profile_spec.rb +++ b/spec/mailers/emails/profile_spec.rb @@ -1,6 +1,5 @@ require 'spec_helper' require 'email_spec' -require 'mailers/shared/notify' describe Notify do include EmailSpec::Matchers diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index c8207e58e90..f5f3f58613d 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -1,6 +1,5 @@ require 'spec_helper' require 'email_spec' -require 'mailers/shared/notify' describe Notify do include EmailSpec::Helpers diff --git a/spec/mailers/shared/notify.rb b/spec/mailers/shared/notify.rb deleted file mode 100644 index 3956d05060b..00000000000 --- a/spec/mailers/shared/notify.rb +++ /dev/null @@ -1,200 +0,0 @@ -shared_context 'gitlab email notification' do - let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } - let(:gitlab_sender) { Gitlab.config.gitlab.email_from } - let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } - let(:recipient) { create(:user, email: 'recipient@example.com') } - let(:project) { create(:project) } - let(:new_user_address) { 'newguy@example.com' } - - before do - ActionMailer::Base.deliveries.clear - email = recipient.emails.create(email: "notifications@example.com") - recipient.update_attribute(:notification_email, email.email) - stub_incoming_email_setting(enabled: true, address: "reply+%{key}@#{Gitlab.config.gitlab.host}") - end -end - -shared_context 'reply-by-email is enabled with incoming address without %{key}' do - before do - stub_incoming_email_setting(enabled: true, address: "reply@#{Gitlab.config.gitlab.host}") - end -end - -shared_examples 'a multiple recipients email' do - it 'is sent to the given recipient' do - is_expected.to deliver_to recipient.notification_email - end -end - -shared_examples 'an email sent from GitLab' do - it 'is sent from GitLab' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(gitlab_sender_display_name) - expect(sender.address).to eq(gitlab_sender) - end - - it 'has a Reply-To address' do - reply_to = subject.header[:reply_to].addresses - expect(reply_to).to eq([gitlab_sender_reply_to]) - end - - context 'when custom suffix for email subject is set' do - before do - stub_config_setting(email_subject_suffix: 'A Nice Suffix') - end - - it 'ends the subject with the suffix' do - is_expected.to have_subject /\ \| A Nice Suffix$/ - end - end -end - -shared_examples 'an email that contains a header with author username' do - it 'has X-GitLab-Author header containing author\'s username' do - is_expected.to have_header 'X-GitLab-Author', user.username - end -end - -shared_examples 'an email with X-GitLab headers containing project details' do - it 'has X-GitLab-Project* headers' do - is_expected.to have_header 'X-GitLab-Project', /#{project.name}/ - is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/ - is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/ - end -end - -shared_examples 'a new thread email with reply-by-email enabled' do - let(:regex) { /\A\Z/ } - - it 'has a Message-ID header' do - is_expected.to have_header 'Message-ID', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>" - end - - it 'has a References header' do - is_expected.to have_header 'References', regex - end -end - -shared_examples 'a thread answer email with reply-by-email enabled' do - include_examples 'an email with X-GitLab headers containing project details' - let(:regex) { /\A<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}> \Z/ } - - it 'has a Message-ID header' do - is_expected.to have_header 'Message-ID', /\A<(.*)@#{Gitlab.config.gitlab.host}>\Z/ - end - - it 'has a In-Reply-To header' do - is_expected.to have_header 'In-Reply-To', "<#{model.class.model_name.singular_route_key}_#{model.id}@#{Gitlab.config.gitlab.host}>" - end - - it 'has a References header' do - is_expected.to have_header 'References', regex - end - - it 'has a subject that begins with Re: ' do - is_expected.to have_subject /^Re: / - end -end - -shared_examples 'an email starting a new thread with reply-by-email enabled' do - include_examples 'an email with X-GitLab headers containing project details' - include_examples 'a new thread email with reply-by-email enabled' - - context 'when reply-by-email is enabled with incoming address with %{key}' do - it 'has a Reply-To header' do - is_expected.to have_header 'Reply-To', /\Z/ - end - end - - context 'when reply-by-email is enabled with incoming address without %{key}' do - include_context 'reply-by-email is enabled with incoming address without %{key}' - include_examples 'a new thread email with reply-by-email enabled' - - it 'has a Reply-To header' do - is_expected.to have_header 'Reply-To', /\Z/ - end - end -end - -shared_examples 'an answer to an existing thread with reply-by-email enabled' do - include_examples 'an email with X-GitLab headers containing project details' - include_examples 'a thread answer email with reply-by-email enabled' - - context 'when reply-by-email is enabled with incoming address with %{key}' do - it 'has a Reply-To header' do - is_expected.to have_header 'Reply-To', /\Z/ - end - end - - context 'when reply-by-email is enabled with incoming address without %{key}' do - include_context 'reply-by-email is enabled with incoming address without %{key}' - include_examples 'a thread answer email with reply-by-email enabled' - - it 'has a Reply-To header' do - is_expected.to have_header 'Reply-To', /\Z/ - end - end -end - -shared_examples 'a new user email' do - it 'is sent to the new user' do - is_expected.to deliver_to new_user_address - end - - it 'has the correct subject' do - is_expected.to have_subject /^Account was created for you$/i - end - - it 'contains the new user\'s login name' do - is_expected.to have_body_text /#{new_user_address}/ - end -end - -shared_examples 'it should have Gmail Actions links' do - it { is_expected.to have_body_text '