diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue index 072ed2fa663..72dc56facd2 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/alert_management/components/alert_details.vue @@ -1,5 +1,4 @@ diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue b/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue index 429a4e04110..e1652f54982 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/modals/add_image/add_image_modal.vue @@ -32,8 +32,8 @@ export default { uploadImageTab: null, }; }, - modalTitle: __('Image Details'), - okTitle: __('Insert'), + modalTitle: __('Image details'), + okTitle: __('Insert image'), urlTabTitle: __('By URL'), urlLabel: __('Image URL'), descriptionLabel: __('Description'), diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 248238ef445..1064baa9e6d 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -247,6 +247,7 @@ .label-badge { color: $gray-900; + display: inline-block; font-weight: $gl-font-weight-normal; padding: $gl-padding-4 $gl-padding-8; border-radius: $border-radius-default; diff --git a/app/controllers/admin/instance_review_controller.rb b/app/controllers/admin/instance_review_controller.rb new file mode 100644 index 00000000000..db304c82dd6 --- /dev/null +++ b/app/controllers/admin/instance_review_controller.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +class Admin::InstanceReviewController < Admin::ApplicationController + feature_category :instance_statistics + + def index + redirect_to("#{::Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL}/instance_review?#{instance_review_params}") + end + + def instance_review_params + result = { + instance_review: { + email: current_user.email, + last_name: current_user.name, + version: ::Gitlab::VERSION + } + } + + if Gitlab::CurrentSettings.usage_ping_enabled? + data = ::Gitlab::UsageData.data + counts = data[:counts] + + result[:instance_review].merge!( + users_count: data[:active_user_count], + projects_count: counts[:projects], + groups_count: counts[:groups], + issues_count: counts[:issues], + merge_requests_count: counts[:merge_requests], + internal_pipelines_count: counts[:ci_internal_pipelines], + external_pipelines_count: counts[:ci_external_pipelines], + labels_count: counts[:labels], + milestones_count: counts[:milestones], + snippets_count: counts[:snippets], + notes_count: counts[:notes] + ) + end + + result.to_query + end +end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 93be0130e12..5a5200452de 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -80,18 +80,17 @@ class HelpController < ApplicationController def documentation_url return unless documentation_base_url - @documentation_url ||= [ - documentation_base_url.chomp('/'), - version_segment, - 'ee', - "#{@path}.html" - ].compact.join('/') + @documentation_url ||= Gitlab::Utils.append_path(documentation_base_url, documentation_file_path) end def documentation_base_url @documentation_base_url ||= Gitlab::CurrentSettings.current_application_settings.help_page_documentation_base_url.presence end + def documentation_file_path + @documentation_file_path ||= [version_segment, 'ee', "#{@path}.html"].compact.join('/') + end + def version_segment return if Gitlab.pre_release? diff --git a/app/controllers/projects/alert_management_controller.rb b/app/controllers/projects/alert_management_controller.rb index 8ecf8fadefd..0d0ef9b05cb 100644 --- a/app/controllers/projects/alert_management_controller.rb +++ b/app/controllers/projects/alert_management_controller.rb @@ -10,5 +10,6 @@ class Projects::AlertManagementController < Projects::ApplicationController def details @alert_id = params[:id] + push_frontend_feature_flag(:expose_environment_path_in_alert_details, @project) end end diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb index f4103c4d3d7..623762de208 100644 --- a/app/graphql/types/alert_management/alert_type.rb +++ b/app/graphql/types/alert_management/alert_type.rb @@ -68,6 +68,11 @@ module Types null: true, description: 'Timestamp the alert ended' + field :environment, + Types::EnvironmentType, + null: true, + description: 'Environment for the alert' + field :event_count, GraphQL::INT_TYPE, null: true, diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb index 239b26f9c38..e4631a4a903 100644 --- a/app/graphql/types/environment_type.rb +++ b/app/graphql/types/environment_type.rb @@ -5,6 +5,8 @@ module Types graphql_name 'Environment' description 'Describes where code is deployed for a project' + present_using ::EnvironmentPresenter + authorize :read_environment field :name, GraphQL::STRING_TYPE, null: false, @@ -16,6 +18,10 @@ module Types field :state, GraphQL::STRING_TYPE, null: false, description: 'State of the environment, for example: available/stopped' + field :path, GraphQL::STRING_TYPE, null: true, + description: 'The path to the environment. Will always return null ' \ + 'if `expose_environment_path_in_alert_details` feature flag is disabled' + field :metrics_dashboard, Types::Metrics::DashboardType, null: true, description: 'Metrics dashboard schema for the environment', resolver: Resolvers::Metrics::DashboardResolver @@ -23,6 +29,6 @@ module Types field :latest_opened_most_severe_alert, Types::AlertManagement::AlertType, null: true, - description: 'The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned.' + description: 'The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned' end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9fdf0dae6d7..2a6b00c0bd8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -212,6 +212,10 @@ module ApplicationHelper Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/' end + def instance_review_permitted? + ::Gitlab::CurrentSettings.instance_review_permitted? && current_user&.admin? + end + def static_objects_external_storage_enabled? Gitlab::CurrentSettings.static_objects_external_storage_enabled? end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index b0dcb50de21..d034630a085 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -11,6 +11,7 @@ class ApplicationSetting < ApplicationRecord ignore_column :instance_statistics_visibility_private, remove_with: '13.6', remove_after: '2020-10-22' ignore_column :snowplow_iglu_registry_url, remove_with: '13.6', remove_after: '2020-11-22' + INSTANCE_REVIEW_MIN_USERS = 50 GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \ 'Admin Area > Settings > Metrics and profiling > Metrics - Grafana' @@ -437,6 +438,14 @@ class ApplicationSetting < ApplicationRecord !!(sourcegraph_url =~ /\Ahttps:\/\/(www\.)?sourcegraph\.com/) end + def instance_review_permitted? + users_count = Rails.cache.fetch('limited_users_count', expires_in: 1.day) do + ::User.limit(INSTANCE_REVIEW_MIN_USERS + 1).count(:all) + end + + users_count >= INSTANCE_REVIEW_MIN_USERS + end + def self.create_from_defaults check_schema! diff --git a/app/models/environment.rb b/app/models/environment.rb index f64776a6991..8889bd2eda2 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -4,6 +4,7 @@ class Environment < ApplicationRecord include Gitlab::Utils::StrongMemoize include ReactiveCaching include FastDestroyAll::Helpers + include Presentable self.reactive_cache_refresh_interval = 1.minute self.reactive_cache_lifetime = 55.seconds diff --git a/app/models/incident_management/project_incident_management_setting.rb b/app/models/incident_management/project_incident_management_setting.rb index c79acdb685f..4887265be88 100644 --- a/app/models/incident_management/project_incident_management_setting.rb +++ b/app/models/incident_management/project_incident_management_setting.rb @@ -51,3 +51,5 @@ module IncidentManagement end end end + +IncidentManagement::ProjectIncidentManagementSetting.prepend_if_ee('EE::IncidentManagement::ProjectIncidentManagementSetting') diff --git a/app/presenters/environment_presenter.rb b/app/presenters/environment_presenter.rb new file mode 100644 index 00000000000..3fa31eb69a2 --- /dev/null +++ b/app/presenters/environment_presenter.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class EnvironmentPresenter < Gitlab::View::Presenter::Delegated + include ActionView::Helpers::UrlHelper + + presents :environment + + def path + if Feature.enabled?(:expose_environment_path_in_alert_details, project) + project_environment_path(project, self) + end + end +end diff --git a/app/presenters/label_presenter.rb b/app/presenters/label_presenter.rb index 68aa05ada8e..c23d6ce2218 100644 --- a/app/presenters/label_presenter.rb +++ b/app/presenters/label_presenter.rb @@ -2,6 +2,7 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated presents :label + delegate :name, :full_name, to: :label_subject, prefix: :subject def edit_path case label @@ -39,8 +40,8 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated label.is_a?(ProjectLabel) end - def subject_name - label.subject.name + def label_subject + @label_subject ||= label.subject end private diff --git a/app/services/users/block_service.rb b/app/services/users/block_service.rb index 041db731875..8513664ee85 100644 --- a/app/services/users/block_service.rb +++ b/app/services/users/block_service.rb @@ -7,6 +7,8 @@ module Users end def execute(user) + return error('An internal user cannot be blocked', 403) if user.internal? + if user.block after_block_hook(user) success diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 2d1ada78248..e0edf83b04c 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -182,7 +182,7 @@ %li Access Git repositories %br = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn gl-button btn-info", data: { confirm: 'Are you sure?' } - - else + - elsif !@user.internal? .card.border-warning .card-header.bg-warning.text-white Block this user diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 6cc736e7056..9d0c3ad5787 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -69,7 +69,8 @@ = Gon::Base.render_data(nonce: content_security_policy_nonce) = javascript_include_tag locale_path unless I18n.locale == :en - = webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled + -# Temporarily commented out to investigate performance: https://gitlab.com/gitlab-org/gitlab/-/issues/251179 + -# = webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled = webpack_bundle_tag 'performance_bar' if performance_bar_enabled? = yield :page_specific_javascripts diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml index dcc6cba8444..4c6bfc0b33c 100644 --- a/app/views/layouts/header/_current_user_dropdown.html.haml +++ b/app/views/layouts/header/_current_user_dropdown.html.haml @@ -46,7 +46,7 @@ - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile) %li.d-md-none = render 'shared/user_dropdown_contributing_link' - = render_if_exists 'shared/user_dropdown_instance_review' + = render 'shared/user_dropdown_instance_review' - if Gitlab.com_but_not_canary? %li.d-md-none = link_to _("Switch to GitLab Next"), "https://next.gitlab.com/" diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml index 2b6cbc1c0ef..40bf45db80d 100644 --- a/app/views/layouts/header/_help_dropdown.html.haml +++ b/app/views/layouts/header/_help_dropdown.html.haml @@ -17,7 +17,7 @@ - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile) %li = render 'shared/user_dropdown_contributing_link' - = render_if_exists 'shared/user_dropdown_instance_review' + = render 'shared/user_dropdown_instance_review' - if Gitlab.com_but_not_canary? %li = link_to _("Switch to GitLab Next"), "https://next.gitlab.com/" diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 9fcb71ec2b9..1dadb4384b9 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -9,9 +9,6 @@ %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label, force_priority: force_priority %ul.label-actions-list - - if @project - %li.inline - .label-badge.gl-bg-gray-50= label.model_name.human.capitalize - if can?(current_user, :admin_label, @project) %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label), dom_id: dom_id(label), type: label.type } } diff --git a/app/views/shared/_label_full_path.html.haml b/app/views/shared/_label_full_path.html.haml new file mode 100644 index 00000000000..fd67bbbbd10 --- /dev/null +++ b/app/views/shared/_label_full_path.html.haml @@ -0,0 +1,4 @@ +- full_path = label.subject_full_name + +.label-badge.gl-bg-gray-50.gl-max-w-full.gl-text-truncate{ title: full_path } + = full_path diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index f2b257f9776..5aee99d434a 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -3,22 +3,28 @@ - show_label_issues_link = subject_or_group_defined && show_label_issuables_link?(label, :issues) - show_label_merge_requests_link = subject_or_group_defined && show_label_issuables_link?(label, :merge_requests) -.label-name.gl-flex-shrink-0.gl-mr-3 +.label-name.gl-flex-shrink-0.gl-mt-2.gl-mr-3 = render_label(label, tooltip: false) -.label-description.gl-flex-grow-1.gl-mr-3.gl-w-full - - if label.description.present? - .description-text.gl-mb-3 - = markdown_field(label, :description) - %ul.label-links.gl-m-0.gl-p-0.gl-white-space-nowrap - - if show_label_issues_link - %li.inline.gl-text-blue-600 - = link_to_label(label, css_class: 'gl-text-blue-600!') { _('Issues') } - - if show_label_merge_requests_link - · - %li.inline.gl-text-blue-600 - = link_to_label(label, type: :merge_request, css_class: 'gl-text-blue-600!') { _('Merge requests') } - = render_if_exists 'shared/label_row_epics_link', label: label - - if force_priority - · - %li.js-priority-badge.inline.gl-ml-3 - .label-badge.gl-bg-blue-50= _('Prioritized label') +.label-description.gl-flex-grow-1.gl-overflow-hidden + .gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-mt-2 + .description-text.gl-flex-grow-1.gl-overflow-hidden + - if label.description.present? + = markdown_field(label, :description) + - elsif @project + = render 'shared/label_full_path', label: label + %ul.label-links.gl-m-0.gl-p-0.gl-white-space-nowrap + - if show_label_issues_link + %li.inline + = link_to_label(label, css_class: 'gl-text-blue-600!') { _('Issues') } + - if show_label_merge_requests_link + · + %li.inline + = link_to_label(label, type: :merge_request, css_class: 'gl-text-blue-600!') { _('Merge requests') } + = render_if_exists 'shared/label_row_epics_link', label: label + - if force_priority + · + %li.js-priority-badge.inline.gl-ml-3 + .label-badge.gl-bg-blue-50= _('Prioritized label') + - if @project && label.description.present? + .gl-mt-3 + = render 'shared/label_full_path', label: label diff --git a/app/views/shared/_user_dropdown_instance_review.html.haml b/app/views/shared/_user_dropdown_instance_review.html.haml new file mode 100644 index 00000000000..18bfb5d7e3e --- /dev/null +++ b/app/views/shared/_user_dropdown_instance_review.html.haml @@ -0,0 +1,6 @@ +- return unless instance_review_permitted? + +%li.divider +%li + = link_to admin_instance_review_path, target: '_blank', class: 'text-nowrap' do + = _("Get a free instance review") diff --git a/changelogs/unreleased/229918-issue_data_epics.yml b/changelogs/unreleased/229918-issue_data_epics.yml new file mode 100644 index 00000000000..692edbf54d2 --- /dev/null +++ b/changelogs/unreleased/229918-issue_data_epics.yml @@ -0,0 +1,5 @@ +--- +title: Added UsageData metrics for issues added/removed from Epics +merge_request: 44371 +author: +type: added diff --git a/changelogs/unreleased/241990-show-full-path-where-labels-come-from.yml b/changelogs/unreleased/241990-show-full-path-where-labels-come-from.yml new file mode 100644 index 00000000000..3a2ac5a0465 --- /dev/null +++ b/changelogs/unreleased/241990-show-full-path-where-labels-come-from.yml @@ -0,0 +1,5 @@ +--- +title: Show labels origin path on project labels page +merge_request: 43858 +author: +type: added diff --git a/changelogs/unreleased/247634-add-healthcheck-to-ce.yml b/changelogs/unreleased/247634-add-healthcheck-to-ce.yml new file mode 100644 index 00000000000..06e571e8166 --- /dev/null +++ b/changelogs/unreleased/247634-add-healthcheck-to-ce.yml @@ -0,0 +1,6 @@ +--- +title: Back-port free instance review for instances with 50+ users from EE Core to + CE +merge_request: 44770 +author: +type: changed diff --git a/changelogs/unreleased/251179-spike-investigate-value-of-sentry-vs-performance-implications.yml b/changelogs/unreleased/251179-spike-investigate-value-of-sentry-vs-performance-implications.yml new file mode 100644 index 00000000000..876083b0fe1 --- /dev/null +++ b/changelogs/unreleased/251179-spike-investigate-value-of-sentry-vs-performance-implications.yml @@ -0,0 +1,5 @@ +--- +title: Remove Sentry implementation to investigate performance impact +merge_request: 44643 +author: +type: performance diff --git a/changelogs/unreleased/44949-update-copy-insert-modal.yml b/changelogs/unreleased/44949-update-copy-insert-modal.yml new file mode 100644 index 00000000000..6d936b414bc --- /dev/null +++ b/changelogs/unreleased/44949-update-copy-insert-modal.yml @@ -0,0 +1,5 @@ +--- +title: Update the copy in the insert image modal to align with copy guidelines +merge_request: 44949 +author: +type: other diff --git a/changelogs/unreleased/bvl-remove-ff-commits-count.yml b/changelogs/unreleased/bvl-remove-ff-commits-count.yml new file mode 100644 index 00000000000..4077a8bb1c1 --- /dev/null +++ b/changelogs/unreleased/bvl-remove-ff-commits-count.yml @@ -0,0 +1,5 @@ +--- +title: Remove the commit count from the commits API +merge_request: 44934 +author: +type: performance diff --git a/changelogs/unreleased/incident-settings-sla.yml b/changelogs/unreleased/incident-settings-sla.yml new file mode 100644 index 00000000000..a42a96bf4be --- /dev/null +++ b/changelogs/unreleased/incident-settings-sla.yml @@ -0,0 +1,5 @@ +--- +title: Add Incident Sla timer columns to DB +merge_request: 44099 +author: +type: added diff --git a/changelogs/unreleased/sk-250667-fix-block-user-api.yml b/changelogs/unreleased/sk-250667-fix-block-user-api.yml new file mode 100644 index 00000000000..7259027c684 --- /dev/null +++ b/changelogs/unreleased/sk-250667-fix-block-user-api.yml @@ -0,0 +1,5 @@ +--- +title: Fix 500 error in block user API for internal user +merge_request: 43461 +author: Sashi Kumar +type: fixed diff --git a/config/feature_flags/development/expose_environment_path_in_alert_details.yml b/config/feature_flags/development/expose_environment_path_in_alert_details.yml new file mode 100644 index 00000000000..f1e35cffbe0 --- /dev/null +++ b/config/feature_flags/development/expose_environment_path_in_alert_details.yml @@ -0,0 +1,7 @@ +--- +name: expose_environment_path_in_alert_details +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43414 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258638 +type: development +group: group::progressive delivery +default_enabled: false diff --git a/config/feature_flags/development/api_commits_without_count.yml b/config/feature_flags/development/incident_sla_dev.yml similarity index 68% rename from config/feature_flags/development/api_commits_without_count.yml rename to config/feature_flags/development/incident_sla_dev.yml index bd5ff8d5eb3..3d18140db3f 100644 --- a/config/feature_flags/development/api_commits_without_count.yml +++ b/config/feature_flags/development/incident_sla_dev.yml @@ -1,7 +1,7 @@ --- -name: api_commits_without_count -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43159 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/254994 +name: incident_sla_dev +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43648 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/266931 +group: group::health type: development -group: team::Scalability default_enabled: false diff --git a/config/feature_flags/licensed/incident_sla.yml b/config/feature_flags/licensed/incident_sla.yml new file mode 100644 index 00000000000..e59251dd82f --- /dev/null +++ b/config/feature_flags/licensed/incident_sla.yml @@ -0,0 +1,7 @@ +--- +name: incident_sla +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43648 +rollout_issue_url: +group: group::health +type: licensed +default_enabled: true diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 60efd12bb57..1503e6547bf 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -81,6 +81,8 @@ namespace :admin do post :preview, on: :collection end + get :instance_review, to: 'instance_review#index' + resource :health_check, controller: 'health_check', only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show] diff --git a/config/webpack.config.js b/config/webpack.config.js index 1b69eb87be8..147f8c25802 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -79,7 +79,7 @@ function generateEntries() { const manualEntries = { default: defaultEntries, - sentry: './sentry/index.js', + // sentry: './sentry/index.js', Temporarily commented out to investigate performance: https://gitlab.com/gitlab-org/gitlab/-/issues/251179 performance_bar: './performance_bar/index.js', chrome_84_icon_fix: './lib/chrome_84_icon_fix.js', }; diff --git a/db/migrate/20200929032729_add_sla_minutes_to_project_incident_management_settings.rb b/db/migrate/20200929032729_add_sla_minutes_to_project_incident_management_settings.rb new file mode 100644 index 00000000000..2848cdf8fcd --- /dev/null +++ b/db/migrate/20200929032729_add_sla_minutes_to_project_incident_management_settings.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddSlaMinutesToProjectIncidentManagementSettings < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :project_incident_management_settings, :sla_timer, :boolean, default: false + add_column :project_incident_management_settings, :sla_timer_minutes, :integer + end +end diff --git a/db/schema_migrations/20200929032729 b/db/schema_migrations/20200929032729 new file mode 100644 index 00000000000..cd38d67c78a --- /dev/null +++ b/db/schema_migrations/20200929032729 @@ -0,0 +1 @@ +77fa26f97216c1fa3d0b046dfabac92a5afa2a0eaf33439117b29ac81e740e4a \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 3e802ac8415..bd4470faa30 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14800,6 +14800,8 @@ CREATE TABLE project_incident_management_settings ( encrypted_pagerduty_token bytea, encrypted_pagerduty_token_iv bytea, auto_close_incident boolean DEFAULT true NOT NULL, + sla_timer boolean DEFAULT false, + sla_timer_minutes integer, CONSTRAINT pagerduty_token_iv_length_constraint CHECK ((octet_length(encrypted_pagerduty_token_iv) <= 12)), CONSTRAINT pagerduty_token_length_constraint CHECK ((octet_length(encrypted_pagerduty_token) <= 255)) ); diff --git a/doc/administration/instance_review.md b/doc/administration/instance_review.md index 8ea4bff252e..326305f4517 100644 --- a/doc/administration/instance_review.md +++ b/doc/administration/instance_review.md @@ -2,7 +2,7 @@ > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/6995) in [GitLab Core](https://about.gitlab.com/pricing/) 11.3. -If you are running a medium size instance of GitLab Core edition you are qualified for a free Instance Review. You can find the button in the User menu. +If you are running a medium size instance (50+ users) of GitLab Core edition you are qualified for a free Instance Review. You can find the button in the User menu. ![Instance Review button](img/instance_review_button.png) diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 53b18a2d218..5fe7d0f5d6e 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -244,6 +244,11 @@ type AlertManagementAlert implements Noteable { """ endedAt: Time + """ + Environment for the alert + """ + environment: Environment + """ Number of events of this alert """ @@ -5773,7 +5778,7 @@ type Environment { id: ID! """ - The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned. + The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned """ latestOpenedMostSevereAlert: AlertManagementAlert @@ -5792,6 +5797,12 @@ type Environment { """ name: String! + """ + The path to the environment. Will always return null if + `expose_environment_path_in_alert_details` feature flag is disabled + """ + path: String + """ State of the environment, for example: available/stopped """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index e34d22e878a..c0d3befd489 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -666,6 +666,20 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "environment", + "description": "Environment for the alert", + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Environment", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "eventCount", "description": "Number of events of this alert", @@ -15990,7 +16004,7 @@ }, { "name": "latestOpenedMostSevereAlert", - "description": "The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned.", + "description": "The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned", "args": [ ], @@ -16047,6 +16061,20 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "path", + "description": "The path to the environment. Will always return null if `expose_environment_path_in_alert_details` feature flag is disabled", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "state", "description": "State of the environment, for example: available/stopped", diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index e37b4dc16c4..665f64f9469 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -77,6 +77,7 @@ Describes an alert from the project's Alert Management. | `details` | JSON | Alert details | | `detailsUrl` | String! | The URL of the alert detail page | | `endedAt` | Time | Timestamp the alert ended | +| `environment` | Environment | Environment for the alert | | `eventCount` | Int | Number of events of this alert | | `hosts` | String! => Array | List of hosts the alert came from | | `iid` | ID! | Internal ID of the alert | @@ -943,9 +944,10 @@ Describes where code is deployed for a project. | Field | Type | Description | | ----- | ---- | ----------- | | `id` | ID! | ID of the environment | -| `latestOpenedMostSevereAlert` | AlertManagementAlert | The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned. | +| `latestOpenedMostSevereAlert` | AlertManagementAlert | The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned | | `metricsDashboard` | MetricsDashboard | Metrics dashboard schema for the environment | | `name` | String! | Human-readable name of the environment | +| `path` | String | The path to the environment. Will always return null if `expose_environment_path_in_alert_details` feature flag is disabled | | `state` | String! | State of the environment, for example: available/stopped | ### Epic diff --git a/doc/api/users.md b/doc/api/users.md index a8c032e2f65..5a53695b362 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -1210,7 +1210,9 @@ Returns: - `201 OK` on success. - `404 User Not Found` if user cannot be found. -- `403 Forbidden` when trying to block an already blocked user by LDAP synchronization. +- `403 Forbidden` when trying to block: + - A user that is blocked through LDAP. + - An internal user. ## Unblock user diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index df921925066..0ead0695acc 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -2819,6 +2819,8 @@ cache: - binaries/ ``` +You can specify a [fallback cache key](#fallback-cache-key) to use if the specified `cache:key` is not found. + ##### `cache:key:files` > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18986) in GitLab v12.5. @@ -4404,6 +4406,32 @@ variables: You can set them globally or per-job in the [`variables`](#variables) section. +### Fallback cache key + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/1534) in GitLab Runner 13.4. + +You can use the `$CI_COMMIT_REF_SLUG` variable to specify your [`cache:key`](#cachekey). +For example, if your `$CI_COMMIT_REF_SLUG` is `test` you can set a job +to download cache that's tagged with `test`. + +If a cache with this tag is not found, you can use `CACHE_FALLBACK_KEY` to +specify a cache to use when none exists. + +For example: + +```yaml +variables: + CACHE_FALLBACK_KEY: fallback-key + +cache: + key: "$CI_COMMIT_REF_SLUG" + paths: + - binaries/ +``` + +In this example, if the `$CI_COMMIT_REF_SLUG` is not found, the job uses the key defined +by the `CACHE_FALLBACK_KEY` variable. + ### Shallow cloning > Introduced in GitLab 8.9 as an experimental feature. diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md index 8c59a0709e2..e5415c79e1d 100644 --- a/doc/operations/incident_management/alerts.md +++ b/doc/operations/incident_management/alerts.md @@ -308,3 +308,49 @@ Viewing logs from a metrics panel can be useful if you're triaging an application incident and need to [explore logs](../metrics/dashboards/index.md#chart-context-menu) from across your application. These logs help you understand what's affecting your application's performance and how to resolve any problems. + +## View the environment that generated the alert + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232492) in GitLab 13.5. +> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default. +> - It's disabled on GitLab.com. +> - It's not recommended for production use. +> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-environment-link-in-alert-details). **(CORE ONLY)** + +CAUTION: **Warning:** +This feature might not be available to you. Check the **version history** note above for details. + +The environment information and the link are displayed in the Alert Details tab[#alert-details-tab]. + +### Enable or disable Environment Link in Alert Details **(CORE ONLY)** + +Viewing the environment is under development and not ready for production use. It is +deployed behind a feature flag that is **disabled by default**. +[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) +can enable it. + +To enable it: + +```ruby +Feature.enable(:expose_environment_path_in_alert_details) +``` + +To enable for just a particular project: + +```ruby +project = Project.find_by_full_path('your-group/your-project') +Feature.enable(:expose_environment_path_in_alert_details, project) +``` + +To disable it: + +```ruby +Feature.disable(:expose_environment_path_in_alert_details) +``` + +To disable for just a particular project: + +```ruby +project = Project.find_by_full_path('your-group/your-project') +Feature.disable(:expose_environment_path_in_alert_details, project) +``` diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index 1f2504a4f60..dd621873786 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -52,8 +52,9 @@ and edit labels. View the project labels list by going to the project and clicking **Issues > Labels**. The list includes all labels that are defined at the project level, as well as all -labels inherited from the immediate parent group. You can filter the list by entering a search -query at the top and clicking search (**{search}**). +labels inherited from the immediate parent group. +For each label, you can see the project or group path from where it was created. +You can filter the list by entering a search query at the top and clicking search (**{search}**). To create a new project label: diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 3097bcc0ef1..aa7dc1eded1 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -64,27 +64,13 @@ module API serializer = with_stats ? Entities::CommitWithStats : Entities::Commit - if Feature.enabled?(:api_commits_without_count, user_project) - # This tells kaminari that there is 1 more commit after the one we've - # loaded, meaning there will be a next page, if the currently loaded set - # of commits is equal to the requested page size. - commit_count = offset + commits.size + 1 - paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count) + # This tells kaminari that there is 1 more commit after the one we've + # loaded, meaning there will be a next page, if the currently loaded set + # of commits is equal to the requested page size. + commit_count = offset + commits.size + 1 + paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count) - present paginate(paginated_commits, exclude_total_headers: true), with: serializer - else - commit_count = - if all || path || before || after || first_parent - user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all, first_parent: first_parent) - else - # Cacheable commit count. - user_project.repository.commit_count_for_ref(ref) - end - - paginated_commits = Kaminari.paginate_array(commits, total_count: commit_count) - - present paginate(paginated_commits), with: serializer - end + present paginate(paginated_commits, exclude_total_headers: true), with: serializer end desc 'Commit multiple file changes as one commit' do diff --git a/lib/gitlab/subscription_portal.rb b/lib/gitlab/subscription_portal.rb new file mode 100644 index 00000000000..a918e7bec80 --- /dev/null +++ b/lib/gitlab/subscription_portal.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Gitlab + module SubscriptionPortal + def self.default_subscriptions_url + ::Gitlab.dev_or_test_env? ? 'https://customers.stg.gitlab.com' : 'https://customers.gitlab.com' + end + + SUBSCRIPTIONS_URL = ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url).freeze + end +end diff --git a/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb index b2adb989959..5ee51e11299 100644 --- a/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb +++ b/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb @@ -24,6 +24,9 @@ module Gitlab ISSUE_MARKED_AS_DUPLICATE = 'g_project_management_issue_marked_as_duplicate' ISSUE_LOCKED = 'g_project_management_issue_locked' ISSUE_UNLOCKED = 'g_project_management_issue_unlocked' + ISSUE_ADDED_TO_EPIC = 'g_project_management_issue_added_to_epic' + ISSUE_REMOVED_FROM_EPIC = 'g_project_management_issue_removed_from_epic' + ISSUE_CHANGED_EPIC = 'g_project_management_issue_changed_epic' class << self def track_issue_created_action(author:, time: Time.zone.now) @@ -102,6 +105,18 @@ module Gitlab track_unique_action(ISSUE_UNLOCKED, author, time) end + def track_issue_added_to_epic_action(author:, time: Time.zone.now) + track_unique_action(ISSUE_ADDED_TO_EPIC, author, time) + end + + def track_issue_removed_from_epic_action(author:, time: Time.zone.now) + track_unique_action(ISSUE_REMOVED_FROM_EPIC, author, time) + end + + def track_issue_changed_epic_action(author:, time: Time.zone.now) + track_unique_action(ISSUE_CHANGED_EPIC, author, time) + end + private def track_unique_action(action, author, time) diff --git a/lib/gitlab/usage_data_counters/known_events.yml b/lib/gitlab/usage_data_counters/known_events.yml index d91fa6ba926..1e01f00de86 100644 --- a/lib/gitlab/usage_data_counters/known_events.yml +++ b/lib/gitlab/usage_data_counters/known_events.yml @@ -267,3 +267,15 @@ category: issues_edit redis_slot: project_management aggregation: daily +- name: g_project_management_issue_added_to_epic + category: issues_edit + redis_slot: project_management + aggregation: daily +- name: g_project_management_issue_removed_from_epic + category: issues_edit + redis_slot: project_management + aggregation: daily +- name: g_project_management_issue_changed_epic + category: issues_edit + redis_slot: project_management + aggregation: daily diff --git a/locale/gitlab.pot b/locale/gitlab.pot index aa4a1f10a23..008f679e583 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -13451,10 +13451,10 @@ msgstr "" msgid "Ignored" msgstr "" -msgid "Image Details" +msgid "Image URL" msgstr "" -msgid "Image URL" +msgid "Image details" msgstr "" msgid "ImageDiffViewer|2-up" @@ -13728,12 +13728,18 @@ msgstr "" msgid "IncidentManagement|Unpublished" msgstr "" +msgid "IncidentSettings|Activate \"time to SLA\" countdown timer" +msgstr "" + msgid "IncidentSettings|Alert integration" msgstr "" msgid "IncidentSettings|Grafana integration" msgstr "" +msgid "IncidentSettings|Incident settings" +msgstr "" + msgid "IncidentSettings|Incidents" msgstr "" @@ -13743,6 +13749,30 @@ msgstr "" msgid "IncidentSettings|Set up integrations with external tools to help better manage incidents." msgstr "" +msgid "IncidentSettings|Time limit" +msgstr "" + +msgid "IncidentSettings|Time limit must be a multiple of 15 minutes" +msgstr "" + +msgid "IncidentSettings|Time limit must be a valid number" +msgstr "" + +msgid "IncidentSettings|Time limit must be greater than 0" +msgstr "" + +msgid "IncidentSettings|When activated, this will apply to all new incidents within the project" +msgstr "" + +msgid "IncidentSettings|You may choose to introduce a countdown timer in incident issues to better track Service Level Agreements (SLAs). The timer is automatically started when the incident is created, and sets a time limit for the incident to be resolved in. When activated, \"time to SLA\" countdown will appear on all new incidents." +msgstr "" + +msgid "IncidentSettings|hours" +msgstr "" + +msgid "IncidentSettings|minutes" +msgstr "" + msgid "Incidents" msgstr "" @@ -13830,9 +13860,6 @@ msgstr "" msgid "Input your repository URL" msgstr "" -msgid "Insert" -msgstr "" - msgid "Insert a code block" msgstr "" @@ -13845,6 +13872,9 @@ msgstr "" msgid "Insert code" msgstr "" +msgid "Insert image" +msgstr "" + msgid "Insert inline code" msgstr "" diff --git a/package.json b/package.json index ed0d631a036..a5fb7b59a5e 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "^6.0.3-3", "@rails/ujs": "^6.0.3-2", - "@sentry/browser": "^5.22.3", "@sourcegraph/code-host-integration": "0.0.50", "@toast-ui/editor": "^2.4.0", "@toast-ui/vue-editor": "^2.4.0", diff --git a/spec/controllers/admin/instance_review_controller_spec.rb b/spec/controllers/admin/instance_review_controller_spec.rb new file mode 100644 index 00000000000..d15894eeb5d --- /dev/null +++ b/spec/controllers/admin/instance_review_controller_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Admin::InstanceReviewController do + include UsageDataHelpers + + let(:admin) { create(:admin) } + let(:subscriptions_url) { ::Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL } + + before do + sign_in(admin) + end + + context 'GET #index' do + let!(:group) { create(:group) } + let!(:projects) { create_list(:project, 2, group: group) } + + subject { post :index } + + context 'with usage ping enabled' do + before do + stub_application_setting(usage_ping_enabled: true) + stub_usage_data_connections + ::Gitlab::UsageData.data(force_refresh: true) + subject + end + + it 'redirects to the customers app with correct params' do + params = { instance_review: { + email: admin.email, + last_name: admin.name, + version: ::Gitlab::VERSION, + users_count: 5, + projects_count: 2, + groups_count: 1, + issues_count: 0, + merge_requests_count: 0, + internal_pipelines_count: 0, + external_pipelines_count: 0, + labels_count: 0, + milestones_count: 0, + snippets_count: 0, + notes_count: 0 + } }.to_query + + expect(response).to redirect_to("#{subscriptions_url}/instance_review?#{params}") + end + end + + context 'with usage ping disabled' do + before do + stub_application_setting(usage_ping_enabled: false) + subject + end + + it 'redirects to the customers app with correct params' do + params = { instance_review: { + email: admin.email, + last_name: admin.name, + version: ::Gitlab::VERSION + } }.to_query + + expect(response).to redirect_to("#{subscriptions_url}/instance_review?#{params}") + end + end + end +end diff --git a/spec/controllers/projects/alert_management_controller_spec.rb b/spec/controllers/projects/alert_management_controller_spec.rb index 6a1952f949b..d80147b5c59 100644 --- a/spec/controllers/projects/alert_management_controller_spec.rb +++ b/spec/controllers/projects/alert_management_controller_spec.rb @@ -42,7 +42,7 @@ RSpec.describe Projects::AlertManagementController do let(:role) { :reporter } it 'shows 404' do - get :index, params: { namespace_id: project.namespace, project_id: project } + get :details, params: { namespace_id: project.namespace, project_id: project, id: id } expect(response).to have_gitlab_http_status(:not_found) end diff --git a/spec/features/alert_management_spec.rb b/spec/features/alert_management_spec.rb new file mode 100644 index 00000000000..2989f72e356 --- /dev/null +++ b/spec/features/alert_management_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Alert management', :js do + let_it_be(:project) { create(:project) } + let_it_be(:developer) { create(:user) } + + before_all do + project.add_developer(developer) + end + + context 'when visiting the alert details page' do + let!(:alert) { create(:alert_management_alert, :resolved, :with_fingerprint, title: 'dos-test', project: project, **options) } + let(:options) { {} } + + before do + sign_in(user) + end + + context 'when actor has permission to see the alert' do + let(:user) { developer } + + it 'shows the alert details' do + visit(details_project_alert_management_path(project, alert)) + + within('.alert-management-details-table') do + expect(page).to have_content(alert.title) + end + end + + context 'when alert belongs to an environment' do + let(:options) { { environment: environment } } + let!(:environment) { create(:environment, name: 'production', project: project) } + + it 'shows the environment name' do + visit(details_project_alert_management_path(project, alert)) + + expect(page).to have_link(environment.name, href: project_environment_path(project, environment)) + within('.alert-management-details-table') do + expect(page).to have_content(environment.name) + end + end + + context 'when expose_environment_path_in_alert_details feature flag is disabled' do + before do + stub_feature_flags(expose_environment_path_in_alert_details: false) + end + + it 'does not show the environment name' do + visit(details_project_alert_management_path(project, alert)) + + within('.alert-management-details-table') do + expect(page).to have_content(alert.title) + expect(page).not_to have_content(environment.name) + end + end + end + end + end + end +end diff --git a/spec/features/sentry_js_spec.rb b/spec/features/sentry_js_spec.rb index 1d277ba7b3c..aa0ad17340a 100644 --- a/spec/features/sentry_js_spec.rb +++ b/spec/features/sentry_js_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'Sentry' do expect(has_requested_sentry).to eq(false) end - it 'loads sentry if sentry is enabled' do + xit 'loads sentry if sentry is enabled' do stub_sentry_settings visit new_user_session_path diff --git a/spec/frontend/alert_management/components/alert_details_spec.js b/spec/frontend/alert_management/components/alert_details_spec.js index 910bb31b573..f3ebdfc5cc2 100644 --- a/spec/frontend/alert_management/components/alert_details_spec.js +++ b/spec/frontend/alert_management/components/alert_details_spec.js @@ -1,25 +1,32 @@ -import { mount, shallowMount } from '@vue/test-utils'; import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { mount, shallowMount } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; import AlertDetails from '~/alert_management/components/alert_details.vue'; import AlertSummaryRow from '~/alert_management/components/alert_summary_row.vue'; +import { + ALERTS_SEVERITY_LABELS, + trackAlertsDetailsViewsOptions, +} from '~/alert_management/constants'; import createIssueMutation from '~/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql'; import { joinPaths } from '~/lib/utils/url_utility'; -import { - trackAlertsDetailsViewsOptions, - ALERTS_SEVERITY_LABELS, -} from '~/alert_management/constants'; import Tracking from '~/tracking'; +import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; import mockAlerts from '../mocks/alerts.json'; const mockAlert = mockAlerts[0]; +const environmentName = 'Production'; +const environmentPath = '/fake/path'; describe('AlertDetails', () => { - let wrapper; + let environmentData = { + name: environmentName, + path: environmentPath, + }; + let glFeatures = { exposeEnvironmentPathInAlertDetails: false }; let mock; + let wrapper; const projectPath = 'root/alerts'; const projectIssuesPath = 'root/alerts/-/issues'; const projectId = '1'; @@ -33,9 +40,17 @@ describe('AlertDetails', () => { projectPath, projectIssuesPath, projectId, + glFeatures, }, data() { - return { alert: { ...mockAlert }, sidebarStatus: false, ...data }; + return { + alert: { + ...mockAlert, + environment: environmentData, + }, + sidebarStatus: false, + ...data, + }; }, mocks: { $apollo: { @@ -72,7 +87,8 @@ describe('AlertDetails', () => { const findCreateIncidentBtn = () => wrapper.findByTestId('createIncidentBtn'); const findViewIncidentBtn = () => wrapper.findByTestId('viewIncidentBtn'); const findIncidentCreationAlert = () => wrapper.findByTestId('incidentCreationError'); - const findEnvironmentLink = () => wrapper.findByTestId('environmentUrl'); + const findEnvironmentName = () => wrapper.findByTestId('environmentName'); + const findEnvironmentPath = () => wrapper.findByTestId('environmentPath'); const findDetailsTable = () => wrapper.find(AlertDetailsTable); describe('Alert details', () => { @@ -120,8 +136,6 @@ describe('AlertDetails', () => { field | data | isShown ${'eventCount'} | ${1} | ${true} ${'eventCount'} | ${undefined} | ${false} - ${'environment'} | ${undefined} | ${false} - ${'environment'} | ${'Production'} | ${true} ${'monitoringTool'} | ${'New Relic'} | ${true} ${'monitoringTool'} | ${undefined} | ${false} ${'service'} | ${'Prometheus'} | ${true} @@ -144,16 +158,34 @@ describe('AlertDetails', () => { }); }); - describe('environment URL fields', () => { - it('should show the environment URL when available', () => { - const environment = 'Production'; - const environmentUrl = 'fake/url'; - mountComponent({ - data: { alert: { ...mockAlert, environment, environmentUrl } }, + describe('environment fields', () => { + describe('when exposeEnvironmentPathInAlertDetails is disabled', () => { + beforeEach(mountComponent); + + it('should not show the environment', () => { + expect(findEnvironmentName().exists()).toBe(false); + expect(findEnvironmentPath().exists()).toBe(false); + }); + }); + + describe('when exposeEnvironmentPathInAlertDetails is enabled', () => { + beforeEach(() => { + glFeatures = { exposeEnvironmentPathInAlertDetails: true }; + mountComponent(); }); - expect(findEnvironmentLink().text()).toBe(environment); - expect(findEnvironmentLink().attributes('href')).toBe(environmentUrl); + it('should show the environment name with link to path', () => { + expect(findEnvironmentName().exists()).toBe(false); + expect(findEnvironmentPath().text()).toBe(environmentName); + expect(findEnvironmentPath().attributes('href')).toBe(environmentPath); + }); + + it('should only show the environment name if the path is not provided', () => { + environmentData = { name: environmentName, path: null }; + mountComponent(); + expect(findEnvironmentPath().exists()).toBe(false); + expect(findEnvironmentName().text()).toBe(environmentName); + }); }); }); diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js index 34d99473eb7..d61f79071d5 100644 --- a/spec/frontend/clusters_list/components/clusters_spec.js +++ b/spec/frontend/clusters_list/components/clusters_spec.js @@ -6,7 +6,7 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlTable, } from '@gitlab/ui'; -import * as Sentry from '@sentry/browser'; +import * as Sentry from '~/sentry/wrapper'; import axios from '~/lib/utils/axios_utils'; import Clusters from '~/clusters_list/components/clusters.vue'; import ClusterStore from '~/clusters_list/store'; diff --git a/spec/frontend/clusters_list/store/actions_spec.js b/spec/frontend/clusters_list/store/actions_spec.js index 053128a179a..3d4e07d00eb 100644 --- a/spec/frontend/clusters_list/store/actions_spec.js +++ b/spec/frontend/clusters_list/store/actions_spec.js @@ -1,7 +1,7 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import * as Sentry from '@sentry/browser'; +import * as Sentry from '~/sentry/wrapper'; import Poll from '~/lib/utils/poll'; import { deprecatedCreateFlash as flashError } from '~/flash'; import axios from '~/lib/utils/axios_utils'; diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap index 53c3e131466..072e611b9a4 100644 --- a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap +++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap @@ -58,6 +58,8 @@ exports[`IncidentsSettingTabs should render the component 1`] = ` /> + + diff --git a/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js b/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js index c56b9ed2a69..11b9eda2585 100644 --- a/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js +++ b/spec/frontend/incidents_settings/components/incidents_settings_tabs_spec.js @@ -6,7 +6,12 @@ describe('IncidentsSettingTabs', () => { let wrapper; beforeEach(() => { - wrapper = shallowMount(IncidentsSettingTabs); + wrapper = shallowMount(IncidentsSettingTabs, { + provide: { + service: {}, + serviceLevelAgreementSettings: {}, + }, + }); }); afterEach(() => { diff --git a/spec/frontend/sentry/sentry_config_spec.js b/spec/frontend/sentry/sentry_config_spec.js index bcc7f29b98d..ed30e4774d9 100644 --- a/spec/frontend/sentry/sentry_config_spec.js +++ b/spec/frontend/sentry/sentry_config_spec.js @@ -1,4 +1,4 @@ -import * as Sentry from '@sentry/browser'; +import * as Sentry from '~/sentry/wrapper'; import SentryConfig from '~/sentry/sentry_config'; describe('SentryConfig', () => { diff --git a/spec/frontend/vue_shared/components/alert_details_table_spec.js b/spec/frontend/vue_shared/components/alert_details_table_spec.js index 2bc599bcf79..dff307e92c2 100644 --- a/spec/frontend/vue_shared/components/alert_details_table_spec.js +++ b/spec/frontend/vue_shared/components/alert_details_table_spec.js @@ -18,13 +18,24 @@ const mockAlert = { __typename: 'AlertManagementAlert', }; +const environmentName = 'Production'; +const environmentPath = '/fake/path'; + describe('AlertDetails', () => { + let environmentData = { name: environmentName, path: environmentPath }; + let glFeatures = { exposeEnvironmentPathInAlertDetails: false }; let wrapper; function mountComponent(propsData = {}) { wrapper = mount(AlertDetailsTable, { + provide: { + glFeatures, + }, propsData: { - alert: mockAlert, + alert: { + ...mockAlert, + environment: environmentData, + }, loading: false, ...propsData, }, @@ -38,6 +49,12 @@ describe('AlertDetails', () => { const findTableComponent = () => wrapper.find(GlTable); const findTableKeys = () => findTableComponent().findAll('tbody td:first-child'); + const findTableFieldValueByKey = fieldKey => + findTableComponent() + .findAll('tbody tr') + .filter(row => row.text().includes(fieldKey)) + .at(0) + .find('td:nth-child(2)'); const findTableField = (fields, fieldName) => fields.filter(row => row.text() === fieldName); describe('Alert details', () => { @@ -62,11 +79,7 @@ describe('AlertDetails', () => { }); describe('with table data', () => { - const environment = 'myEnvironment'; - const environmentUrl = 'fake/url'; - beforeEach(() => { - mountComponent({ alert: { ...mockAlert, environment, environmentUrl } }); - }); + beforeEach(mountComponent); it('renders a table', () => { expect(findTableComponent().exists()).toBe(true); @@ -83,18 +96,43 @@ describe('AlertDetails', () => { expect(findTableField(fields, 'Title').exists()).toBe(true); expect(findTableField(fields, 'Severity').exists()).toBe(true); expect(findTableField(fields, 'Status').exists()).toBe(true); - expect(findTableField(fields, 'Environment').exists()).toBe(true); expect(findTableField(fields, 'Hosts').exists()).toBe(true); + expect(findTableField(fields, 'Environment').exists()).toBe(false); }); - it('should not show disallowed alert fields', () => { + it('should not show disallowed and flaggedAllowed alert fields', () => { const fields = findTableKeys(); expect(findTableField(fields, 'Typename').exists()).toBe(false); expect(findTableField(fields, 'Todos').exists()).toBe(false); expect(findTableField(fields, 'Notes').exists()).toBe(false); expect(findTableField(fields, 'Assignees').exists()).toBe(false); - expect(findTableField(fields, 'EnvironmentUrl').exists()).toBe(false); + expect(findTableField(fields, 'Environment').exists()).toBe(false); + }); + }); + + describe('when exposeEnvironmentPathInAlertDetails is enabled', () => { + beforeEach(() => { + glFeatures = { exposeEnvironmentPathInAlertDetails: true }; + mountComponent(); + }); + + it('should show flaggedAllowed alert fields', () => { + const fields = findTableKeys(); + + expect(findTableField(fields, 'Environment').exists()).toBe(true); + }); + + it('should display only the name for the environment', () => { + expect(findTableFieldValueByKey('Iid').text()).toBe('1527542'); + expect(findTableFieldValueByKey('Environment').text()).toBe(environmentName); + }); + + it('should not display the environment row if there is not data', () => { + environmentData = { name: null, path: null }; + mountComponent(); + + expect(findTableFieldValueByKey('Environment').text()).toBeFalsy(); }); }); }); diff --git a/spec/graphql/types/alert_management/alert_type_spec.rb b/spec/graphql/types/alert_management/alert_type_spec.rb index e14c189d4b6..82b48a20708 100644 --- a/spec/graphql/types/alert_management/alert_type_spec.rb +++ b/spec/graphql/types/alert_management/alert_type_spec.rb @@ -32,6 +32,7 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do todos details_url prometheus_alert + environment ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb index abeeeba543f..2220f847e4e 100644 --- a/spec/graphql/types/environment_type_spec.rb +++ b/spec/graphql/types/environment_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Environment'] do it 'has the expected fields' do expected_fields = %w[ - name id state metrics_dashboard latest_opened_most_severe_alert + name id state metrics_dashboard latest_opened_most_severe_alert path ] expect(described_class).to have_graphql_fields(*expected_fields) @@ -28,6 +28,7 @@ RSpec.describe GitlabSchema.types['Environment'] do project(fullPath: "#{project.full_path}") { environment(name: "#{environment.name}") { name + path state } } @@ -43,6 +44,18 @@ RSpec.describe GitlabSchema.types['Environment'] do expect(subject['data']['project']['environment']['name']).to eq(environment.name) end + it 'returns the path when the feature is enabled' do + expect(subject['data']['project']['environment']['path']).to eq( + Gitlab::Routing.url_helpers.project_environment_path(project, environment) + ) + end + + it 'does not return the path when the feature is disabled' do + stub_feature_flags(expose_environment_path_in_alert_details: false) + + expect(subject['data']['project']['environment']['path']).to be_nil + end + context 'when query alert data for the environment' do let_it_be(:query) do %( diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index ce4e73bdc55..a557e9e04da 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -222,6 +222,32 @@ RSpec.describe ApplicationHelper do end end + describe '#instance_review_permitted?' do + let_it_be(:non_admin_user) { create :user } + let_it_be(:admin_user) { create :user, :admin } + + before do + allow(::Gitlab::CurrentSettings).to receive(:instance_review_permitted?).and_return(app_setting) + allow(helper).to receive(:current_user).and_return(current_user) + end + + subject { helper.instance_review_permitted? } + + where(app_setting: [true, false], is_admin: [true, false, nil]) + + with_them do + let(:current_user) do + if is_admin.nil? + nil + else + is_admin ? admin_user : non_admin_user + end + end + + it { is_expected.to be(app_setting && is_admin) } + end + end + describe '#locale_path' do it 'returns the locale path with an `_`' do Gitlab::I18n.with_locale('pt-BR') do diff --git a/spec/helpers/operations_helper_spec.rb b/spec/helpers/operations_helper_spec.rb index 3dac2cf54dc..b6d05b1ab17 100644 --- a/spec/helpers/operations_helper_spec.rb +++ b/spec/helpers/operations_helper_spec.rb @@ -145,7 +145,7 @@ RSpec.describe OperationsHelper do subject { helper.operations_settings_data } it 'returns the correct set of data' do - is_expected.to eq( + is_expected.to include( operations_settings_endpoint: project_settings_operations_path(project), templates: '[]', create_issue: 'false', diff --git a/spec/lib/gitlab/subscription_portal_spec.rb b/spec/lib/gitlab/subscription_portal_spec.rb new file mode 100644 index 00000000000..351af3c07d2 --- /dev/null +++ b/spec/lib/gitlab/subscription_portal_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Gitlab::SubscriptionPortal do + describe '.default_subscriptions_url' do + subject { described_class.default_subscriptions_url } + + context 'on non test and non dev environments' do + before do + allow(Rails).to receive_message_chain(:env, :test?).and_return(false) + allow(Rails).to receive_message_chain(:env, :development?).and_return(false) + end + + it 'returns production subscriptions app URL' do + is_expected.to eq('https://customers.gitlab.com') + end + end + + context 'on dev environment' do + before do + allow(Rails).to receive_message_chain(:env, :test?).and_return(false) + allow(Rails).to receive_message_chain(:env, :development?).and_return(true) + end + + it 'returns staging subscriptions app url' do + is_expected.to eq('https://customers.stg.gitlab.com') + end + end + + context 'on test environment' do + before do + allow(Rails).to receive_message_chain(:env, :test?).and_return(true) + allow(Rails).to receive_message_chain(:env, :development?).and_return(false) + end + + it 'returns staging subscriptions app url' do + is_expected.to eq('https://customers.stg.gitlab.com') + end + end + end +end diff --git a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb index bd3c8024215..c2952f8c9c8 100644 --- a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb @@ -202,6 +202,36 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git end end + context 'for Issue added to epic actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_ADDED_TO_EPIC} + + def track_action(params) + described_class.track_issue_added_to_epic_action(**params) + end + end + end + + context 'for Issue removed from epic actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_REMOVED_FROM_EPIC} + + def track_action(params) + described_class.track_issue_removed_from_epic_action(**params) + end + end + end + + context 'for Issue changed epic actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_CHANGED_EPIC} + + def track_action(params) + described_class.track_issue_changed_epic_action(**params) + end + end + end + it 'can return the count of actions per user deduplicated', :aggregate_failures do described_class.track_issue_title_changed_action(author: user1) described_class.track_issue_description_changed_action(author: user1) diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 69ad0eeafa0..fb702d10a42 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -807,6 +807,23 @@ RSpec.describe ApplicationSetting do end end + describe '#instance_review_permitted?', :request_store do + subject { setting.instance_review_permitted? } + + before do + RequestStore.store[:current_license] = nil + expect(Rails.cache).to receive(:fetch).and_return( + ::ApplicationSetting::INSTANCE_REVIEW_MIN_USERS + users_over_minimum + ) + end + + where(users_over_minimum: [-1, 0, 1]) + + with_them do + it { is_expected.to be(users_over_minimum >= 0) } + end + end + describe 'email_restrictions' do context 'when email restrictions are enabled' do before do diff --git a/spec/presenters/label_presenter_spec.rb b/spec/presenters/label_presenter_spec.rb index cb6e991bd8e..44c68a6102f 100644 --- a/spec/presenters/label_presenter_spec.rb +++ b/spec/presenters/label_presenter_spec.rb @@ -91,4 +91,18 @@ RSpec.describe LabelPresenter do it { is_expected.to eq(label.project.name) } end end + + describe '#subject_full_name' do + context 'with group label' do + subject { group_label.subject_full_name } + + it { is_expected.to eq(group_label.group.full_name) } + end + + context 'with project label' do + subject { label.subject_full_name } + + it { is_expected.to eq(label.project.full_name) } + end + end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 98c1e0228d4..d455ed9c194 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -40,17 +40,6 @@ RSpec.describe API::Commits do expect(response).to include_limited_pagination_headers end - - it 'includes the total headers when the count is not disabled' do - stub_feature_flags(api_commits_without_count: false) - commit_count = project.repository.count_commits(ref: 'master').to_s - - get api(route, current_user) - - expect(response).to include_pagination_headers - expect(response.headers['X-Total']).to eq(commit_count) - expect(response.headers['X-Page']).to eql('1') - end end context 'when unauthenticated', 'and project is public' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 72a826d91e6..1ba6b818afa 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -2524,6 +2524,15 @@ RSpec.describe API::Users, :do_not_mock_admin_mode do expect(json_response['message']).to eq('404 User Not Found') end + it 'returns a 403 error if user is internal' do + internal_user = create(:user, :bot) + + post api("/users/#{internal_user.id}/block", admin) + + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to eq('An internal user cannot be blocked') + end + it 'returns a 201 if user is already blocked' do post api("/users/#{blocked_user.id}/block", admin) diff --git a/spec/requests/user_activity_spec.rb b/spec/requests/user_activity_spec.rb index 6f0726dbdc9..148bb2d6fae 100644 --- a/spec/requests/user_activity_spec.rb +++ b/spec/requests/user_activity_spec.rb @@ -3,18 +3,6 @@ require 'spec_helper' RSpec.describe 'Update of user activity' do - let(:user) { create(:user, last_activity_on: nil) } - - before do - group = create(:group, name: 'group') - project = create(:project, :public, namespace: group, name: 'project') - - create(:issue, project: project, iid: 10) - create(:merge_request, source_project: project, iid: 15) - - project.add_maintainer(user) - end - paths_to_visit = [ '/group', '/group/project', @@ -30,85 +18,5 @@ RSpec.describe 'Update of user activity' do '/group/project/-/merge_requests/15' ] - context 'without an authenticated user' do - it 'does not set the last activity cookie' do - get "/group/project" - - expect(response.cookies['user_last_activity_on']).to be_nil - end - end - - context 'with an authenticated user' do - before do - login_as(user) - end - - context 'with a POST request' do - it 'does not set the last activity cookie' do - post "/group/project/archive" - - expect(response.cookies['user_last_activity_on']).to be_nil - end - end - - paths_to_visit.each do |path| - context "on GET to #{path}" do - it 'updates the last activity date' do - expect(Users::ActivityService).to receive(:new).and_call_original - - get path - - expect(user.last_activity_on).to eq(Date.today) - end - - context 'when calling it twice' do - it 'updates last_activity_on just once' do - expect(Users::ActivityService).to receive(:new).once.and_call_original - - 2.times do - get path - end - end - end - - context 'when last_activity_on is nil' do - before do - user.update_attribute(:last_activity_on, nil) - end - - it 'updates the last activity date' do - expect(user.last_activity_on).to be_nil - - get path - - expect(user.last_activity_on).to eq(Date.today) - end - end - - context 'when last_activity_on is stale' do - before do - user.update_attribute(:last_activity_on, 2.days.ago.to_date) - end - - it 'updates the last activity date' do - get path - - expect(user.last_activity_on).to eq(Date.today) - end - end - - context 'when last_activity_on is up to date' do - before do - user.update_attribute(:last_activity_on, Date.today) - end - - it 'does not try to update it' do - expect(Users::ActivityService).not_to receive(:new) - - get path - end - end - end - end - end + it_behaves_like 'updating of user activity', paths_to_visit end diff --git a/spec/services/users/block_service_spec.rb b/spec/services/users/block_service_spec.rb index e170a5494aa..45a5b1e5100 100644 --- a/spec/services/users/block_service_spec.rb +++ b/spec/services/users/block_service_spec.rb @@ -34,5 +34,15 @@ RSpec.describe Users::BlockService do expect { operation }.not_to change { user.state } end end + + context 'when internal user' do + let(:user) { create(:user, :bot) } + + it 'returns error result' do + expect(operation[:status]).to eq(:error) + expect(operation[:message]).to eq('An internal user cannot be blocked') + expect(operation[:http_status]).to eq(403) + end + end end end diff --git a/spec/support/shared_examples/requests/user_activity_shared_examples.rb b/spec/support/shared_examples/requests/user_activity_shared_examples.rb new file mode 100644 index 00000000000..37da1ce5c63 --- /dev/null +++ b/spec/support/shared_examples/requests/user_activity_shared_examples.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'updating of user activity' do |paths_to_visit| + let(:user) { create(:user, last_activity_on: nil) } + + before do + group = create(:group, name: 'group') + project = create(:project, :public, namespace: group, name: 'project') + + create(:issue, project: project, iid: 10) + create(:merge_request, source_project: project, iid: 15) + + project.add_maintainer(user) + end + + context 'without an authenticated user' do + it 'does not set the last activity cookie' do + get "/group/project" + + expect(response.cookies['user_last_activity_on']).to be_nil + end + end + + context 'with an authenticated user' do + before do + login_as(user) + end + + context 'with a POST request' do + it 'does not set the last activity cookie' do + post "/group/project/archive" + + expect(response.cookies['user_last_activity_on']).to be_nil + end + end + + paths_to_visit.each do |path| + context "on GET to #{path}" do + it 'updates the last activity date' do + expect(Users::ActivityService).to receive(:new).and_call_original + + get path + + expect(user.last_activity_on).to eq(Date.today) + end + + context 'when calling it twice' do + it 'updates last_activity_on just once' do + expect(Users::ActivityService).to receive(:new).once.and_call_original + + 2.times do + get path + end + end + end + + context 'when last_activity_on is nil' do + before do + user.update_attribute(:last_activity_on, nil) + end + + it 'updates the last activity date' do + expect(user.last_activity_on).to be_nil + + get path + + expect(user.last_activity_on).to eq(Date.today) + end + end + + context 'when last_activity_on is stale' do + before do + user.update_attribute(:last_activity_on, 2.days.ago.to_date) + end + + it 'updates the last activity date' do + get path + + expect(user.last_activity_on).to eq(Date.today) + end + end + + context 'when last_activity_on is up to date' do + before do + user.update_attribute(:last_activity_on, Date.today) + end + + it 'does not try to update it' do + expect(Users::ActivityService).not_to receive(:new) + + get path + end + end + end + end + end +end diff --git a/spec/views/shared/_label_row.html.haml_spec.rb b/spec/views/shared/_label_row.html.haml_spec.rb index 8f8aa3072e2..1d21574a0c4 100644 --- a/spec/views/shared/_label_row.html.haml_spec.rb +++ b/spec/views/shared/_label_row.html.haml_spec.rb @@ -2,43 +2,83 @@ require 'spec_helper' RSpec.describe 'shared/_label_row.html.haml' do - label_types = { - 'project label': :label, - 'group label': :group_label - } + let_it_be(:group) { create(:group) } + let(:label) { build_stubbed(:group_label, group: group).present(issuable_subject: group) } - label_types.each do |label_type, label_factory| - let!(:label) do - label_record = create(label_factory) # rubocop: disable Rails/SaveBang - label_record.present(issuable_subject: label_record.subject) + before do + allow(view).to receive(:label) { label } + end + + context 'with a project context' do + let_it_be(:project) { create(:project, group: group) } + let(:label) { build_stubbed(:label, project: project).present(issuable_subject: project) } + + before do + assign(:project, label.project) + + render end - context "for a #{label_type}" do - before do - if label.project_label? - @project = label.project - else - @group = label.group - end - end + it 'has a non-linked label title' do + expect(rendered).not_to have_css('a', text: label.title) + end - it 'has a non-linked label title' do - render 'shared/label_row', label: label + it "has Issues link" do + expect(rendered).to have_css('a', text: 'Issues') + end - expect(rendered).not_to have_css('a', text: label.title) - end + it "has Merge request link" do + expect(rendered).to have_css('a', text: 'Merge requests') + end - it "has Issues link for #{label_type}" do - render 'shared/label_row', label: label + it "shows the path from where the label was created" do + expect(rendered).to have_css('.label-badge', text: project.full_name) + end + end - expect(rendered).to have_css('a', text: 'Issues') - end + context 'with a group context' do + before do + assign(:group, label.group) - it "has Merge request link for #{label_type}" do - render 'shared/label_row', label: label + render + end - expect(rendered).to have_css('a', text: 'Merge requests') - end + it 'has a non-linked label title' do + expect(rendered).not_to have_css('a', text: label.title) + end + + it "has Issues link" do + expect(rendered).to have_css('a', text: 'Issues') + end + + it "has Merge request link" do + expect(rendered).to have_css('a', text: 'Merge requests') + end + + it "does not show a path from where the label was created" do + expect(rendered).not_to have_css('.label-badge') + end + end + + context 'with an admin context' do + before do + render + end + + it 'has a non-linked label title' do + expect(rendered).not_to have_css('a', text: label.title) + end + + it "does not show Issues link" do + expect(rendered).not_to have_css('a', text: 'Issues') + end + + it "does not show Merge request link" do + expect(rendered).not_to have_css('a', text: 'Merge requests') + end + + it "does not show a path from where the label was created" do + expect(rendered).not_to have_css('.label-badge') end end end diff --git a/yarn.lock b/yarn.lock index 912ecb18167..0fcd75fc023 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1129,58 +1129,6 @@ resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.0.3-2.tgz#e14c1f29086858215ce7ccd9ad6d8888c458b4a3" integrity sha512-WcpIEftNCfGDEgk6KerOugiet75Mir5q/HT1yt3dDhpBI91BaZ15lfSQIsZwMw2nyeDz9A9QBz8dAFAd4gXIzg== -"@sentry/browser@^5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.22.3.tgz#7a64bd1cf01bf393741a3e4bf35f82aa927f5b4e" - integrity sha512-2TzE/CoBa5ZkvxJizDdi1Iz1ldmXSJpFQ1mL07PIXBjCt0Wxf+WOuFSj5IP4L40XHfJE5gU8wEvSH0VDR8nXtA== - dependencies: - "@sentry/core" "5.22.3" - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" - tslib "^1.9.3" - -"@sentry/core@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.22.3.tgz#030f435f2b518f282ba8bd954dac90cd70888bd7" - integrity sha512-eGL5uUarw3o4i9QUb9JoFHnhriPpWCaqeaIBB06HUpdcvhrjoowcKZj1+WPec5lFg5XusE35vez7z/FPzmJUDw== - dependencies: - "@sentry/hub" "5.22.3" - "@sentry/minimal" "5.22.3" - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" - tslib "^1.9.3" - -"@sentry/hub@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.22.3.tgz#08309a70d2ea8d5e313d05840c1711f34f2fffe5" - integrity sha512-INo47m6N5HFEs/7GMP9cqxOIt7rmRxdERunA3H2L37owjcr77MwHVeeJ9yawRS6FMtbWXplgWTyTIWIYOuqVbw== - dependencies: - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" - tslib "^1.9.3" - -"@sentry/minimal@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.22.3.tgz#706e4029ae5494123d3875c658ba8911aa5cc440" - integrity sha512-HoINpYnVYCpNjn2XIPIlqH5o4BAITpTljXjtAftOx6Hzj+Opjg8tR8PWliyKDvkXPpc4kXK9D6TpEDw8MO0wZA== - dependencies: - "@sentry/hub" "5.22.3" - "@sentry/types" "5.22.3" - tslib "^1.9.3" - -"@sentry/types@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.22.3.tgz#d1d547b30ee8bd7771fa893af74c4f3d71f0fd18" - integrity sha512-cv+VWK0YFgCVDvD1/HrrBWOWYG3MLuCUJRBTkV/Opdy7nkdNjhCAJQrEyMM9zX0sac8FKWKOHT0sykNh8KgmYw== - -"@sentry/utils@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.22.3.tgz#e3bda3e789239eb16d436f768daa12829f33d18f" - integrity sha512-AHNryXMBvIkIE+GQxTlmhBXD0Ksh+5w1SwM5qi6AttH+1qjWLvV6WB4+4pvVvEoS8t5F+WaVUZPQLmCCWp6zKw== - dependencies: - "@sentry/types" "5.22.3" - tslib "^1.9.3" - "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"