diff options
43 files changed, 504 insertions, 67 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 702a4ba89a8..25e02b1ae1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.16.3 (2017-01-27) + +- Add caching of droplab ajax requests. !8725 +- Fix access to the wiki code via HTTP when repository feature disabled. !8758 +- Revert 3f17f29a. !8785 +- Fix race conditions for AuthorizedProjectsWorker. +- Fix autocomplete initial undefined state. +- Fix Error 500 when repositories contain annotated tags pointing to blobs. +- Fix /explore sorting. +- Fixed label dropdown toggle text not correctly updating. + ## 8.16.2 (2017-01-25) - allow issue filter bar to be operated with mouse only. !8681 diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index 868f28cd356..db8d231a82a 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -58,3 +58,9 @@ fill: $gl-text-color; } } + +.icon-link { + &:hover { + text-decoration: none; + } +} diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 401c2d0f6ee..fd081c2d7e1 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -294,16 +294,18 @@ .container-fluid { position: relative; + + .nav-control { + @media (max-width: $screen-sm-max) { + margin-right: 75px; + } + } } .controls { float: right; padding: 7px 0 0; - @media (max-width: $screen-sm-max) { - display: none; - } - i { color: $layout-link-gray; } @@ -361,6 +363,7 @@ .fade-left { @include fade(right, $gray-light); left: -5px; + text-align: center; .fa { left: -7px; diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 44715dd6f7a..1593b5c1afb 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -2,12 +2,13 @@ class Projects::LabelsController < Projects::ApplicationController include ToggleSubscriptionAction before_action :module_enabled - before_action :label, only: [:edit, :update, :destroy] + before_action :label, only: [:edit, :update, :destroy, :promote] before_action :find_labels, only: [:index, :set_priorities, :remove_priority, :toggle_subscription] 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_group!, only: [:promote] respond_to :js, :html @@ -102,6 +103,32 @@ class Projects::LabelsController < Projects::ApplicationController end end + def promote + promote_service = Labels::PromoteService.new(@project, @current_user) + + begin + return render_404 unless promote_service.execute(@label) + respond_to do |format| + format.html do + redirect_to(namespace_project_labels_path(@project.namespace, @project), + notice: 'Label was promoted to a Group Label') + end + format.js + end + rescue ActiveRecord::RecordInvalid => e + Gitlab::AppLogger.error "Failed to promote label \"#{@label.title}\" to group label" + Gitlab::AppLogger.error e + + respond_to do |format| + format.html do + redirect_to(namespace_project_labels_path(@project.namespace, @project), + notice: 'Failed to promote label due to internal error. Please contact administrators.') + end + format.js + end + end + end + protected def module_enabled @@ -129,4 +156,8 @@ class Projects::LabelsController < Projects::ApplicationController def authorize_admin_labels! return render_404 unless can?(current_user, :admin_label, @project) end + + def authorize_admin_group! + return render_404 unless can?(current_user, :admin_group, @project.group) + end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 2ac76e97de0..80d002f9c32 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -60,9 +60,9 @@ class JiraService < IssueTrackerService end def help - 'You need to configure JIRA before enabling this service. For more details + "You need to configure JIRA before enabling this service. For more details read the - [JIRA service documentation](https://docs.gitlab.com/ce/project_services/jira.html).' + [JIRA service documentation](#{help_page_url('project_services/jira')})." end def title diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb new file mode 100644 index 00000000000..76d0ba67b07 --- /dev/null +++ b/app/services/labels/promote_service.rb @@ -0,0 +1,71 @@ +module Labels + class PromoteService < BaseService + BATCH_SIZE = 1000 + + def execute(label) + return unless project.group && + label.is_a?(ProjectLabel) + + Label.transaction do + new_label = clone_label_to_group_label(label) + + label_ids_for_merge(new_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids| + update_issuables(new_label, batched_ids) + update_issue_board_lists(new_label, batched_ids) + update_priorities(new_label, batched_ids) + # Order is important, project labels need to be last + update_project_labels(batched_ids) + end + + # We skipped validations during creation. Let's run them now, after deleting conflicting labels + raise ActiveRecord::RecordInvalid.new(new_label) unless new_label.valid? + new_label + end + end + + private + + def label_ids_for_merge(new_label) + LabelsFinder. + new(current_user, title: new_label.title, group_id: project.group.id). + execute(skip_authorization: true). + where.not(id: new_label). + select(:id) # Can't use pluck() to avoid object-creation because of the batching + end + + def update_issuables(new_label, label_ids) + LabelLink. + where(label: label_ids). + update_all(label_id: new_label) + end + + def update_issue_board_lists(new_label, label_ids) + List. + where(label: label_ids). + update_all(label_id: new_label) + end + + def update_priorities(new_label, label_ids) + LabelPriority. + where(label: label_ids). + update_all(label_id: new_label) + end + + def update_project_labels(label_ids) + Label.where(id: label_ids).delete_all + end + + def clone_label_to_group_label(label) + params = label.attributes.slice('title', 'description', 'color') + # Since the title of the new label has to be the same as the previous labels + # and we're merging old labels in batches we'll skip validation to omit 2-step + # merge process and do it in one batch + # We'll be forcing validation at the end of the transaction to ensure everything + # was merged correctly + new_label = GroupLabel.new(params.merge(group: project.group)) + new_label.save(validate: false) + + new_label + end + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index f74e6cac174..b2cc39763f3 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -365,7 +365,7 @@ class NotificationService users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq) users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users) - users_with_group_setting = select_group_member_setting(project, project_members, users_with_group_level_global, users) + users_with_group_setting = select_group_member_setting(project.group, project_members, users_with_group_level_global, users) User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a end @@ -415,8 +415,8 @@ class NotificationService end # Build a list of users based on group notification settings - def select_group_member_setting(project, project_members, global_setting, users_global_level_watch) - uids = notification_settings_for(project, :watch) + def select_group_member_setting(group, project_members, global_setting, users_global_level_watch) + uids = notification_settings_for(group, :watch) # Group setting is watch, add to users list if user is not project member users = [] @@ -473,7 +473,7 @@ class NotificationService setting = user.notification_settings_for(project) - if !setting && project.group + if project.group && (setting.nil? || setting.global?) setting = user.notification_settings_for(project.group) end diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index ac04f57e217..19a947af4ca 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -1,5 +1,5 @@ += render 'layouts/nav/admin_settings' .scrolling-tabs-container{ class: nav_control_class } - = render 'layouts/nav/admin_settings' .fade-left = icon('angle-left') .fade-right diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 6dba42d5226..4d0b7a5ca85 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -63,9 +63,10 @@ - if @commit.status .well-segment.pipeline-info %div{ class: "icon-container ci-status-icon-#{@commit.status}" } - = ci_icon_for_status(@commit.status) + = link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id) do + = ci_icon_for_status(@commit.status) Pipeline - = link_to "##{@commit.pipelines.last.id}", pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" + = link_to "##{@commit.pipelines.last.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id), class: "monospace" for = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" %span.ci-status-label diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 6e0d9456900..b23ca109746 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -5,7 +5,7 @@ %div{ class: container_class } .top-area.adjust .col-md-9 - %h3.page-title= @environment.name.capitalize + %h3.page-title= @environment.name .col-md-3 .nav-controls = render 'projects/environments/terminal_button', environment: @environment diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 9fa00811af0..11636d7ebc7 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -75,7 +75,7 @@ // This element is filled in using JavaScript. .content-block.content-block-small - = render 'new_branch' + = render 'new_branch' unless @issue.confidential? = render 'award_emoji/awards_block', awardable: @issue, inline: true %section.issuable-discussion diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 5faa6c43f9f..804a4a2473b 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -2,7 +2,8 @@ .mr-widget-heading - %w[success success_with_warnings skipped canceled failed running pending].each do |status| .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) } - = ci_icon_for_status(status) + = link_to namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'icon-link' do + = ci_icon_for_status(status) %span Pipeline = link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline' diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index ca76f13ef5e..6caa5f16dc6 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -23,8 +23,8 @@ .info-well - if @commit.status .well-segment.pipeline-info - %div{ class: "icon-container ci-status-icon-#{@commit.status}" } - = ci_icon_for_status(@commit.status) + .icon-container + = icon('clock-o') = pluralize @pipeline.statuses.count(:id), "build" - if @pipeline.ref from diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index 65a3a6bddab..54b5ae2402e 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -2,7 +2,7 @@ = f.label :import_url, class: 'control-label' do %span Git repository URL .col-sm-10 - = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true + = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true .well.prepend-top-20 %ul @@ -13,4 +13,4 @@ %li The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination. %li - To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}. + To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}. diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 4bfdf94b913..ead9b84b991 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -66,6 +66,10 @@ %a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } } Group level + - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_group, label.project.group) + = link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do + %span.sr-only Promote to Group + = icon('level-up') - 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 diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml index 0eba1fe075f..c06d1ffa59b 100644 --- a/app/views/shared/_outdated_browser.html.haml +++ b/app/views/shared/_outdated_browser.html.haml @@ -1,8 +1,7 @@ - if outdated_browser? - - link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers" .browser-alert GitLab may not work properly because you are using an outdated web browser. %br Please install a - = link_to 'supported web browser', link + = link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers') for a better experience. diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml index b5c0a7fd6d4..a736bfd91e2 100644 --- a/app/views/shared/notifications/_custom_notifications.html.haml +++ b/app/views/shared/notifications/_custom_notifications.html.haml @@ -18,7 +18,7 @@ %p Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out = succeed "." do - %a{ href: "http://docs.gitlab.com/ce/workflow/notifications.html", target: "_blank" } notification emails + %a{ href: help_page_path('workflow/notifications'), target: "_blank" } notification emails .col-lg-8 - NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index| - field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]" diff --git a/changelogs/unreleased/19164-mobile-settings.yml b/changelogs/unreleased/19164-mobile-settings.yml new file mode 100644 index 00000000000..c26a20f87e2 --- /dev/null +++ b/changelogs/unreleased/19164-mobile-settings.yml @@ -0,0 +1,4 @@ +--- +title: 19164 Add settings dropdown to mobile screens +merge_request: +author: diff --git a/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml b/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml new file mode 100644 index 00000000000..587ef4f9a73 --- /dev/null +++ b/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml @@ -0,0 +1,4 @@ +--- +title: Fix disable storing of sensitive information when importing a new repo +merge_request: 8885 +author: Bernard Pietraga diff --git a/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml b/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml deleted file mode 100644 index 2d4ec482ee0..00000000000 --- a/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix autocomplete initial undefined state -merge_request: -author: diff --git a/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml b/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml deleted file mode 100644 index 4678297cfd4..00000000000 --- a/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Add caching of droplab ajax requests -merge_request: 8725 -author: diff --git a/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml b/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml new file mode 100644 index 00000000000..c5c57af5aaf --- /dev/null +++ b/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml @@ -0,0 +1,4 @@ +--- +title: Improve pipeline status icon linking in widgets +merge_request: +author: diff --git a/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml b/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml deleted file mode 100644 index 0f0a8940f72..00000000000 --- a/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix /explore sorting -merge_request: -author: diff --git a/changelogs/unreleased/27484-environment-show-name.yml b/changelogs/unreleased/27484-environment-show-name.yml new file mode 100644 index 00000000000..dc400d65006 --- /dev/null +++ b/changelogs/unreleased/27484-environment-show-name.yml @@ -0,0 +1,4 @@ +--- +title: Don't capitalize environment name in show page +merge_request: +author: diff --git a/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml b/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml new file mode 100644 index 00000000000..11d1f55172b --- /dev/null +++ b/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml @@ -0,0 +1,4 @@ +--- +title: Fix notifications when set at group level +merge_request: 6813 +author: Alexandre Maia diff --git a/changelogs/unreleased/cop-gem-fetcher.yml b/changelogs/unreleased/cop-gem-fetcher.yml new file mode 100644 index 00000000000..506815a5b54 --- /dev/null +++ b/changelogs/unreleased/cop-gem-fetcher.yml @@ -0,0 +1,4 @@ +--- +title: Cop for gem fetched from a git source +merge_request: 8856 +author: Adam Pahlevi diff --git a/changelogs/unreleased/fix-26518.yml b/changelogs/unreleased/fix-26518.yml deleted file mode 100644 index 961ac2642fb..00000000000 --- a/changelogs/unreleased/fix-26518.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix access to the wiki code via HTTP when repository feature disabled -merge_request: 8758 -author: diff --git a/changelogs/unreleased/fix-27479.yml b/changelogs/unreleased/fix-27479.yml new file mode 100644 index 00000000000..cc72a830695 --- /dev/null +++ b/changelogs/unreleased/fix-27479.yml @@ -0,0 +1,4 @@ +--- +title: Remove new branch button for confidential issues +merge_request: +author: diff --git a/changelogs/unreleased/label-promotion.yml b/changelogs/unreleased/label-promotion.yml new file mode 100644 index 00000000000..2ab997bf420 --- /dev/null +++ b/changelogs/unreleased/label-promotion.yml @@ -0,0 +1,4 @@ +--- +title: "Project labels can now be promoted to group labels" +merge_request: 7242 +author: Olaf Tomalka diff --git a/changelogs/unreleased/label-select-toggle.yml b/changelogs/unreleased/label-select-toggle.yml deleted file mode 100644 index af5b4246521..00000000000 --- a/changelogs/unreleased/label-select-toggle.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed label dropdown toggle text not correctly updating -merge_request: -author: diff --git a/changelogs/unreleased/refresh-authorizations-fork-join.yml b/changelogs/unreleased/refresh-authorizations-fork-join.yml deleted file mode 100644 index b1349b9447d..00000000000 --- a/changelogs/unreleased/refresh-authorizations-fork-join.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix race conditions for AuthorizedProjectsWorker -merge_request: -author: diff --git a/changelogs/unreleased/revert-filter-assigned-to-me.yml b/changelogs/unreleased/revert-filter-assigned-to-me.yml deleted file mode 100644 index 37f9d2f5fc4..00000000000 --- a/changelogs/unreleased/revert-filter-assigned-to-me.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Revert 3f17f29a -merge_request: 8785 -author: diff --git a/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml b/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml deleted file mode 100644 index ff2b38f21f2..00000000000 --- a/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix Error 500 when repositories contain annotated tags pointing to blobs -merge_request: -author: diff --git a/config/routes/project.rb b/config/routes/project.rb index 6620b765e02..f36febc6e04 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -220,6 +220,7 @@ constraints(ProjectUrlConstrainer.new) do end member do + post :promote post :toggle_subscription delete :remove_priority end diff --git a/rubocop/cop/gem_fetcher.rb b/rubocop/cop/gem_fetcher.rb new file mode 100644 index 00000000000..4a63c760744 --- /dev/null +++ b/rubocop/cop/gem_fetcher.rb @@ -0,0 +1,28 @@ +module RuboCop + module Cop + # Cop that checks for all gems specified in the Gemfile, and will + # alert if any gem is to be fetched not from the RubyGems index. + # This enforcement is done so as to minimize external build + # dependencies and build times. + class GemFetcher < RuboCop::Cop::Cop + MSG = 'Do not use gems from git repositories, only use gems from RubyGems.' + + GIT_KEYS = [:git, :github] + + def on_send(node) + file_path = node.location.expression.source_buffer.name + return unless file_path.end_with?("Gemfile") + + func_name = node.children[1] + return unless func_name == :gem + + node.children.last.each_node(:pair) do |pair| + key_name = pair.children[0].children[0].to_sym + if GIT_KEYS.include?(key_name) + add_offense(node, :selector) + end + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index 7922e19768b..7f20754ee51 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -1,3 +1,4 @@ require_relative 'migration_helpers' require_relative 'cop/migration/add_index' require_relative 'cop/migration/column_with_default' +require_relative 'cop/gem_fetcher' diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index ec6cea5c0f4..3e0326dd47d 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -112,4 +112,49 @@ describe Projects::LabelsController do post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label.to_param end end + + describe 'POST #promote' do + let!(:promoted_label_name) { "Promoted Label" } + let!(:label_1) { create(:label, title: promoted_label_name, project: project) } + + context 'not group owner' do + it 'denies access' do + post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param + + expect(response).to have_http_status(404) + end + end + + context 'group owner' do + before do + GroupMember.add_users_to_group(group, [user], :owner) + end + + it 'gives access' do + post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param + + expect(response).to redirect_to(namespace_project_labels_path) + end + + it 'promotes the label' do + post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param + + expect(Label.where(id: label_1.id)).to be_empty + expect(GroupLabel.find_by(title: promoted_label_name)).not_to be_nil + end + + context 'service raising InvalidRecord' do + before do + expect_any_instance_of(Labels::PromoteService).to receive(:execute) do |label| + raise ActiveRecord::RecordInvalid.new(label_1) + end + end + + it 'returns to label list' do + post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param + expect(response).to redirect_to(namespace_project_labels_path) + end + end + end + end end diff --git a/spec/features/environment_spec.rb b/spec/features/environment_spec.rb index 56f6cd2e095..511c95b758f 100644 --- a/spec/features/environment_spec.rb +++ b/spec/features/environment_spec.rb @@ -19,6 +19,10 @@ feature 'Environment', :feature do visit_environment(environment) end + scenario 'shows environment name' do + expect(page).to have_content(environment.name) + end + context 'without deployments' do scenario 'does show no deployments' do expect(page).to have_content('You don\'t have any deployments right now.') diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 72b984cfab8..c033b693213 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -194,7 +194,7 @@ feature 'Environments page', :feature, :js do end scenario 'does create a new pipeline' do - expect(page).to have_content('Production') + expect(page).to have_content('production') end end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index a4d3053d10c..c0ab42c6822 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Start new branch from an issue', feature: true do +feature 'Start new branch from an issue', feature: true, js: true do let!(:project) { create(:project) } let!(:issue) { create(:issue, project: project) } let!(:user) { create(:user)} @@ -11,7 +11,7 @@ feature 'Start new branch from an issue', feature: true do login_as(user) end - it 'shows the new branch button', js: true do + it 'shows the new branch button' do visit namespace_project_issue_path(project.namespace, project, issue) expect(page).to have_css('#new-branch .available') @@ -34,16 +34,26 @@ feature 'Start new branch from an issue', feature: true do visit namespace_project_issue_path(project.namespace, project, issue) end - it "hides the new branch button", js: true do + it "hides the new branch button" do expect(page).to have_css('#new-branch .unavailable') expect(page).not_to have_css('#new-branch .available') expect(page).to have_content /1 Related Merge Request/ end end + + context 'when issue is confidential' do + it 'hides the new branch button' do + issue = create(:issue, :confidential, project: project) + + visit namespace_project_issue_path(project.namespace, project, issue) + + expect(page).not_to have_css('#new-branch') + end + end end - context "for visiters" do - it 'shows no buttons', js: true do + context 'for visitors' do + it 'shows no buttons' do visit namespace_project_issue_path(project.namespace, project, issue) expect(page).not_to have_css('#new-branch') diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index abfc46601fb..b56e562b2b6 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -1,11 +1,13 @@ require "spec_helper" feature "New project", feature: true do - context "Visibility level selector" do - let(:user) { create(:admin) } + let(:user) { create(:admin) } - before { login_as(user) } + before do + login_as(user) + end + context "Visibility level selector" do Gitlab::VisibilityLevel.options.each do |key, level| it "sets selector to #{key}" do stub_application_setting(default_project_visibility: level) @@ -16,4 +18,16 @@ feature "New project", feature: true do end end end + + context 'Import project options' do + before do + visit new_project_path + end + + it 'does not autocomplete sensitive git repo URL' do + autocomplete = find('#project_import_url')['autocomplete'] + + expect(autocomplete).to eq('off') + end + end end diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb new file mode 100644 index 00000000000..4b90ad19640 --- /dev/null +++ b/spec/services/labels/promote_service_spec.rb @@ -0,0 +1,187 @@ +require 'spec_helper' + +describe Labels::PromoteService, services: true do + describe '#execute' do + let!(:user) { create(:user) } + + context 'project without group' do + let!(:project_1) { create(:empty_project) } + + let!(:project_label_1_1) { create(:label, project: project_1) } + + subject(:service) { described_class.new(project_1, user) } + + it 'fails on project without group' do + expect(service.execute(project_label_1_1)).to be_falsey + end + end + + context 'project with group' do + let!(:promoted_label_name) { "Promoted Label" } + let!(:untouched_label_name) { "Untouched Label" } + let!(:promoted_description) { "Promoted Description" } + let!(:promoted_color) { "#0000FF" } + let!(:label_2_1_priority) { 1 } + let!(:label_3_1_priority) { 2 } + + let!(:group_1) { create(:group) } + let!(:group_2) { create(:group) } + + let!(:project_1) { create(:empty_project, namespace: group_1) } + let!(:project_2) { create(:empty_project, namespace: group_1) } + let!(:project_3) { create(:empty_project, namespace: group_1) } + let!(:project_4) { create(:empty_project, namespace: group_2) } + + # Labels/issues can't be lazily created so we might as well eager initialize + # all other objects too since we use them inside + let!(:project_label_1_1) { create(:label, project: project_1, name: promoted_label_name, color: promoted_color, description: promoted_description) } + let!(:project_label_1_2) { create(:label, project: project_1, name: untouched_label_name) } + let!(:project_label_2_1) { create(:label, project: project_2, priority: label_2_1_priority, name: promoted_label_name, color: "#FF0000") } + let!(:project_label_3_1) { create(:label, project: project_3, priority: label_3_1_priority, name: promoted_label_name) } + let!(:project_label_3_2) { create(:label, project: project_3, priority: 1, name: untouched_label_name) } + let!(:project_label_4_1) { create(:label, project: project_4, name: promoted_label_name) } + + let!(:issue_1_1) { create(:labeled_issue, project: project_1, labels: [project_label_1_1, project_label_1_2]) } + let!(:issue_1_2) { create(:labeled_issue, project: project_1, labels: [project_label_1_2]) } + let!(:issue_2_1) { create(:labeled_issue, project: project_2, labels: [project_label_2_1]) } + let!(:issue_4_1) { create(:labeled_issue, project: project_4, labels: [project_label_4_1]) } + + let!(:merge_3_1) { create(:labeled_merge_request, source_project: project_3, target_project: project_3, labels: [project_label_3_1, project_label_3_2]) } + + let!(:issue_board_2_1) { create(:board, project: project_2) } + let!(:issue_board_list_2_1) { create(:list, board: issue_board_2_1, label: project_label_2_1) } + + let(:new_label) { group_1.labels.find_by(title: promoted_label_name) } + + subject(:service) { described_class.new(project_1, user) } + + it 'fails on group label' do + group_label = create(:group_label, group: group_1) + + expect(service.execute(group_label)).to be_falsey + end + + it 'is truthy on success' do + expect(service.execute(project_label_1_1)).to be_truthy + end + + it 'recreates the label as a group label' do + expect { service.execute(project_label_1_1) }. + to change(project_1.labels, :count).by(-1). + and change(group_1.labels, :count).by(1) + expect(new_label).not_to be_nil + end + + it 'copies title, description and color' do + service.execute(project_label_1_1) + + expect(new_label.title).to eq(promoted_label_name) + expect(new_label.description).to eq(promoted_description) + expect(new_label.color).to eq(promoted_color) + end + + it 'merges labels with the same name in group' do + expect { service.execute(project_label_1_1) }.to change(project_2.labels, :count).by(-1).and \ + change(project_3.labels, :count).by(-1) + end + + it 'recreates priorities' do + service.execute(project_label_1_1) + + expect(new_label.priority(project_1)).to be_nil + expect(new_label.priority(project_2)).to eq(label_2_1_priority) + expect(new_label.priority(project_3)).to eq(label_3_1_priority) + end + + it 'does not touch project out of promoted group' do + service.execute(project_label_1_1) + project_4_new_label = project_4.labels.find_by(title: promoted_label_name) + + expect(project_4_new_label).not_to be_nil + expect(project_4_new_label.id).to eq(project_label_4_1.id) + end + + it 'does not touch out of group priority' do + service.execute(project_label_1_1) + + expect(new_label.priority(project_4)).to be_nil + end + + it 'relinks issue with the promoted label' do + service.execute(project_label_1_1) + issue_label = issue_1_1.labels.find_by(title: promoted_label_name) + + expect(issue_label).not_to be_nil + expect(issue_label.id).to eq(new_label.id) + end + + it 'does not remove untouched labels from issue' do + expect { service.execute(project_label_1_1) }.not_to change(issue_1_1.labels, :count) + end + + it 'does not relink untouched label in issue' do + service.execute(project_label_1_1) + issue_label = issue_1_2.labels.find_by(title: untouched_label_name) + + expect(issue_label).not_to be_nil + expect(issue_label.id).to eq(project_label_1_2.id) + end + + it 'relinks issues with merged labels' do + service.execute(project_label_1_1) + issue_label = issue_2_1.labels.find_by(title: promoted_label_name) + + expect(issue_label).not_to be_nil + expect(issue_label.id).to eq(new_label.id) + end + + it 'does not relink issues from other group' do + service.execute(project_label_1_1) + issue_label = issue_4_1.labels.find_by(title: promoted_label_name) + + expect(issue_label).not_to be_nil + expect(issue_label.id).to eq(project_label_4_1.id) + end + + it 'updates merge request' do + service.execute(project_label_1_1) + merge_label = merge_3_1.labels.find_by(title: promoted_label_name) + + expect(merge_label).not_to be_nil + expect(merge_label.id).to eq(new_label.id) + end + + it 'updates board lists' do + service.execute(project_label_1_1) + list = issue_board_2_1.lists.find_by(label: new_label) + + expect(list).not_to be_nil + end + + # In case someone adds a new relation to Label.rb and forgets to relink it + # and the database doesn't have foreign key constraints + it 'relinks all relations' do + service.execute(project_label_1_1) + + Label.reflect_on_all_associations.each do |association| + expect(project_label_1_1.send(association.name).any?).to be_falsey + end + end + + context 'with invalid group label' do + before do + allow(service).to receive(:clone_label_to_group_label).and_wrap_original do |m, *args| + label = m.call(*args) + allow(label).to receive(:valid?).and_return(false) + + label + end + end + + it 'raises an exception' do + expect { service.execute(project_label_1_1) }.to raise_error(ActiveRecord::RecordInvalid) + end + end + end + end +end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 309985a5d90..7cf2cd9968f 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -443,6 +443,8 @@ describe NotificationService, services: true do before do build_team(issue.project) + build_group(issue.project) + add_users_with_subscription(issue.project, issue) reset_delivered_emails! update_custom_notification(:new_issue, @u_guest_custom, project) @@ -459,6 +461,8 @@ describe NotificationService, services: true do should_email(@u_guest_custom) should_email(@u_custom_global) should_email(@u_participant_mentioned) + should_email(@g_global_watcher) + should_email(@g_watcher) should_not_email(@u_mentioned) should_not_email(@u_participating) should_not_email(@u_disabled) @@ -1218,6 +1222,22 @@ describe NotificationService, services: true do project.add_master(@u_custom_global) end + # Users in the project's group but not part of project's team + # with different notification settings + def build_group(project) + group = create(:group, :public) + project.group = group + + # Group member: global=disabled, group=watch + @g_watcher = create_user_with_notification(:watch, 'group_watcher', project.group) + @g_watcher.notification_settings_for(nil).disabled! + + # Group member: global=watch, group=global + @g_global_watcher = create_global_setting_for(create(:user), :watch) + group.add_users([@g_watcher, @g_global_watcher], :master) + group + end + def create_global_setting_for(user, level) setting = user.global_notification_setting setting.level = level @@ -1226,9 +1246,9 @@ describe NotificationService, services: true do user end - def create_user_with_notification(level, username) + def create_user_with_notification(level, username, resource = project) user = create(:user, username: username) - setting = user.notification_settings_for(project) + setting = user.notification_settings_for(resource) setting.level = level setting.save |