diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue index d73e7892f54..cca56bbe763 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/alert_management/components/alert_details.vue @@ -9,6 +9,7 @@ import { GlTab, GlButton, } from '@gitlab/ui'; +import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { s__ } from '~/locale'; import query from '../graphql/queries/details.query.graphql'; import { fetchPolicies } from '~/lib/graphql'; @@ -24,7 +25,7 @@ export default { errorMsg: s__( 'AlertManagement|There was an error displaying the alert. Please refresh the page to try again.', ), - fullAlertDetailsTitle: s__('AlertManagement|Full Alert Details'), + fullAlertDetailsTitle: s__('AlertManagement|Full alert details'), overviewTitle: s__('AlertManagement|Overview'), }, components: { @@ -32,6 +33,7 @@ export default { GlLoadingIcon, GlNewDropdown, GlNewDropdownItem, + timeAgoTooltip, GlTab, GlTabs, GlButton, @@ -108,7 +110,11 @@ export default { {{ s__('AlertManagement|Create issue') }} -
+
+

{{ alert.title }}

-
    -
  • - {{ s__('AlertManagement|Start time:') }} +
      +
    • + {{ s__('AlertManagement|Start time') }}: +
    • -
    • - {{ s__('AlertManagement|End time:') }} +
    • + {{ s__('AlertManagement|Events') }}: + {{ alert.eventCount }}
    • -
    • - {{ s__('AlertManagement|Events:') }} +
    • + {{ s__('AlertManagement|Tool') }}: + {{ alert.monitoringTool }} +
    • +
    • + {{ s__('AlertManagement|Service') }}: + {{ alert.service }}
    diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue index 46ab04c9b19..43d8b582e80 100644 --- a/app/assets/javascripts/snippets/components/snippet_header.vue +++ b/app/assets/javascripts/snippets/components/snippet_header.vue @@ -127,7 +127,7 @@ export default { }, methods: { redirectToSnippets() { - window.location.pathname = 'dashboard/snippets'; + window.location.pathname = this.snippet.project?.fullPath || 'dashboard/snippets'; }, closeDeleteModal() { this.$refs.deleteModal.hide(); diff --git a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue index 5d7e9557aff..bfe46dcda00 100644 --- a/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue +++ b/app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue @@ -54,22 +54,23 @@ export default { 'issuable-info-container': !canReorder, 'card-body': canReorder, }" - class="item-body d-flex align-items-center p-2 p-lg-3 py-xl-2 px-xl-3" + class="item-body d-flex align-items-center py-2 px-3" >
    -
    - +
    +
    - +
    @@ -97,17 +98,6 @@ export default {
    - - - @@ -159,7 +149,7 @@ export default { v-gl-tooltip :disabled="removeDisabled" type="button" - class="btn btn-default btn-svg btn-item-remove js-issue-item-remove-button mr-xl-0 align-self-xl-center" + class="btn btn-default btn-svg btn-item-remove js-issue-item-remove-button" data-qa-selector="remove_related_issue_button" :title="__('Remove')" :aria-label="__('Remove')" diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss index c9a8d5e5975..61f971a3185 100644 --- a/app/assets/stylesheets/components/related_items_list.scss +++ b/app/assets/stylesheets/components/related_items_list.scss @@ -1,9 +1,11 @@ $item-path-max-width: 160px; $item-milestone-max-width: 120px; $item-weight-max-width: 48px; +$item-remove-button-space: 42px; .related-items-list { padding: $gl-padding-4; + padding-right: $gl-padding-6; &, .list-item:last-child { @@ -11,16 +13,16 @@ $item-weight-max-width: 48px; } } -.sortable-link { - max-width: 85%; -} - .related-items-tree { .card-header { .gl-label { line-height: $gl-line-height; } } + + .sortable-link { + white-space: normal; + } } .item-body { @@ -48,17 +50,12 @@ $item-weight-max-width: 48px; cursor: help; } - .issue-token-state-icon-open, - .issue-token-state-icon-closed { - margin-right: $gl-padding-4; - } - .confidential-icon { color: $orange-600; } .item-title-wrapper { - max-width: 100%; + max-width: calc(100% - #{$item-remove-button-space}); } .item-title { @@ -88,13 +85,6 @@ $item-weight-max-width: 48px; .health-label-short { display: none; } - - @include media-breakpoint-down(lg) { - .issue-count-badge { - padding: 0; - padding-right: 8px; - } - } } .item-body, @@ -232,25 +222,28 @@ $item-weight-max-width: 48px; font-weight: $gl-font-weight-bold; max-width: $item-path-max-width; } - - .issue-token-state-icon-open, - .issue-token-state-icon-closed { - display: block; - } } .btn-item-remove { position: absolute; - right: 0; top: $gl-padding-4 / 2; + right: 0; padding: $gl-padding-4; margin-right: $gl-padding-4 / 2; line-height: 0; border-color: transparent; color: $gl-text-color-secondary; + .related-items-tree & { + position: relative; + top: initial; + padding: $btn-sm-side-margin; + margin-right: initial; + } + &:hover { color: $gl-text-color; + border-color: $border-color; } } @@ -283,6 +276,15 @@ $item-weight-max-width: 48px; /* Small devices (landscape phones, 768px and up) */ @include media-breakpoint-up(md) { + .item-body .item-contents { + max-width: 95%; + } + + .related-items-tree .item-contents, + .item-body .item-title { + max-width: 100%; + } + .sortable-link { text-overflow: ellipsis; overflow: hidden; @@ -294,24 +296,6 @@ $item-weight-max-width: 48px; .item-contents { min-width: 0; } - - .item-title { - flex-basis: unset; - // 95% because we compensate - // for remove button which is - // positioned absolutely - width: 95%; - } - - .btn-item-remove { - order: 1; - } - } - - .item-meta { - .item-meta-child { - flex-basis: unset; - } } .card-header { @@ -348,25 +332,10 @@ $item-weight-max-width: 48px; @include media-breakpoint-up(xl) { .item-body { .item-title { - min-width: 0; width: auto; flex-basis: auto; flex-shrink: 1; font-weight: $gl-font-weight-normal; - - .issue-token-state-icon-open, - .issue-token-state-icon-closed { - display: block; - margin-right: $gl-padding-8; - } - } - - .item-title-wrapper { - max-width: calc(100% - 500px); - } - - .item-info-area { - flex-basis: auto; } } @@ -378,16 +347,7 @@ $item-weight-max-width: 48px; overflow: hidden; } - .item-meta { - flex: 1; - } - .item-assignees { - .avatar { - height: $gl-padding-24; - width: $gl-padding-24; - } - .avatar-counter { height: $gl-padding-24; min-width: $gl-padding-24; @@ -399,12 +359,8 @@ $item-weight-max-width: 48px; .btn-item-remove { position: relative; top: initial; - right: 0; padding: $btn-sm-side-margin; - - &:hover { - border-color: $border-color; - } + margin-right: $gl-padding-4 / 2; } .sortable-link { @@ -422,10 +378,4 @@ $item-weight-max-width: 48px; display: initial; } } - - .item-body { - .item-title-wrapper { - max-width: calc(100% - 640px); - } - } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index c23623005b0..38f5ebdc4fa 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -398,6 +398,7 @@ $tooltip-font-size: 12px; * Padding */ $gl-padding-4: 4px; +$gl-padding-6: 6px; $gl-padding-8: 8px; $gl-padding-12: 12px; $gl-padding: 16px; @@ -447,6 +448,7 @@ $breadcrumb-min-height: 48px; $home-panel-title-row-height: 64px; $home-panel-avatar-mobile-size: 24px; $gl-line-height: 16px; +$gl-line-height-18: 18px; $gl-line-height-20: 20px; $gl-line-height-24: 24px; $gl-line-height-14: 14px; diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 85429415457..508b1f5bd0a 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -65,7 +65,7 @@ class Projects::WikisController < Projects::ApplicationController def update return render('empty') unless can?(current_user, :create_wiki, @project) - @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page) + @page = WikiPages::UpdateService.new(container: @project, current_user: current_user, params: wiki_params).execute(@page) if @page.valid? redirect_to( @@ -81,7 +81,7 @@ class Projects::WikisController < Projects::ApplicationController end def create - @page = WikiPages::CreateService.new(@project, current_user, wiki_params).execute + @page = WikiPages::CreateService.new(container: @project, current_user: current_user, params: wiki_params).execute if @page.persisted? redirect_to( @@ -112,7 +112,7 @@ class Projects::WikisController < Projects::ApplicationController end def destroy - WikiPages::DestroyService.new(@project, current_user).execute(@page) + WikiPages::DestroyService.new(container: @project, current_user: current_user).execute(@page) redirect_to project_wiki_path(@project, :home), status: :found, diff --git a/app/models/group.rb b/app/models/group.rb index 37dfe27e834..a1423c79ce3 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -483,6 +483,16 @@ class Group < Namespace false end + def execute_hooks(data, hooks_scope) + # NOOP + # TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904 + end + + def execute_services(data, hooks_scope) + # NOOP + # TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904 + end + private def update_two_factor_requirement diff --git a/app/services/concerns/measurable.rb b/app/services/concerns/measurable.rb new file mode 100644 index 00000000000..5a74f15506e --- /dev/null +++ b/app/services/concerns/measurable.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +# In order to measure and log execution of our service, we just need to 'prepend Measurable' module +# Example: +# ``` +# class DummyService +# prepend Measurable +# +# def execute +# # ... +# end +# end + +# DummyService.prepend(Measurable) +# ``` +# +# In case when we are prepending a module from the `EE` namespace with EE features +# we need to prepend Measurable after prepending `EE` module. +# This way Measurable will be at the bottom of the ancestor chain, +# in order to measure execution of `EE` features as well +# ``` +# class DummyService +# def execute +# # ... +# end +# end +# +# DummyService.prepend_if_ee('EE::DummyService') +# DummyService.prepend(Measurable) +# ``` +# +module Measurable + extend ::Gitlab::Utils::Override + + override :execute + def execute(*args) + measuring? ? ::Gitlab::Utils::Measuring.new(base_log_data).with_measuring { super(*args) } : super(*args) + end + + protected + + # You can set extra attributes for performance measurement log. + def extra_attributes_for_measurement + defined?(super) ? super : {} + end + + private + + def measuring? + Feature.enabled?("gitlab_service_measuring_#{service_class}") + end + + # These attributes are always present in log. + def base_log_data + extra_attributes_for_measurement.merge({ class: self.class.name }) + end + + def service_class + self.class.name.underscore.tr('/', '_') + end +end diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb index 6e82a39ffd8..9848d8ad5c0 100644 --- a/app/services/groups/import_export/export_service.rb +++ b/app/services/groups/import_export/export_service.rb @@ -52,7 +52,7 @@ module Groups end def savers - [tree_exporter, file_saver] + [version_saver, tree_exporter, file_saver] end def tree_exporter @@ -65,13 +65,17 @@ module Groups end def tree_exporter_class - if ::Feature.enabled?(:group_export_ndjson, @group&.parent) + if ::Feature.enabled?(:group_export_ndjson, @group&.parent, default_enabled: true) Gitlab::ImportExport::Group::TreeSaver else Gitlab::ImportExport::Group::LegacyTreeSaver end end + def version_saver + Gitlab::ImportExport::VersionSaver.new(shared: shared) + end + def file_saver Gitlab::ImportExport::Saver.new(exportable: @group, shared: @shared) end diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb index 5e00ce9ccc0..6f692c98c38 100644 --- a/app/services/groups/import_export/import_service.rb +++ b/app/services/groups/import_export/import_service.rb @@ -53,7 +53,7 @@ module Groups end def ndjson? - ::Feature.enabled?(:group_import_ndjson, @group&.parent) && + ::Feature.enabled?(:group_import_ndjson, @group&.parent, default_enabled: true) && File.exist?(File.join(@shared.export_path, 'tree/groups/_all.ndjson')) end diff --git a/app/services/metrics/users_starred_dashboards/delete_service.rb b/app/services/metrics/users_starred_dashboards/delete_service.rb new file mode 100644 index 00000000000..579715bd49f --- /dev/null +++ b/app/services/metrics/users_starred_dashboards/delete_service.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Delete all matching Metrics::UsersStarredDashboard entries for given user based on matched dashboard_path, project +module Metrics + module UsersStarredDashboards + class DeleteService < ::BaseService + def initialize(user, project, dashboard_path = nil) + @user, @project, @dashboard_path = user, project, dashboard_path + end + + def execute + ServiceResponse.success(payload: { deleted_rows: starred_dashboards.delete_all }) + end + + private + + attr_reader :user, :project, :dashboard_path + + def starred_dashboards + # since deleted records are scoped to their owner there is no need to + # check if that user can delete them, also if user lost access to + # project it shouldn't block that user from removing them + dashboards = user.metrics_users_starred_dashboards + + if dashboard_path.present? + dashboards.for_project_dashboard(project, dashboard_path) + else + dashboards.for_project(project) + end + end + end + end +end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 429ae905e3d..57f5acbc337 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -202,8 +202,19 @@ module Projects end end + def extra_attributes_for_measurement + { + current_user: current_user&.name, + project_full_path: "#{project_namespace&.full_path}/#{@params[:path]}" + } + end + private + def project_namespace + @project_namespace ||= Namespace.find_by_id(@params[:namespace_id]) || current_user.namespace + end + def create_from_template? @params[:template_name].present? || @params[:template_project_id].present? end @@ -224,4 +235,9 @@ module Projects end end +# rubocop: disable Cop/InjectEnterpriseEditionModule Projects::CreateService.prepend_if_ee('EE::Projects::CreateService') +# rubocop: enable Cop/InjectEnterpriseEditionModule + +# Measurable should be at the bottom of the ancestor chain, so it will measure execution of EE::Projects::CreateService as well +Projects::CreateService.prepend(Measurable) diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb index cbe46c84d9d..2e192942b9c 100644 --- a/app/services/projects/gitlab_projects_import_service.rb +++ b/app/services/projects/gitlab_projects_import_service.rb @@ -14,36 +14,16 @@ module Projects @current_user, @params, @override_params = user, import_params.dup, override_params end - def execute(options = {}) - measurement_enabled = !!options[:measurement_enabled] - measurement_logger = options[:measurement_logger] + def execute + prepare_template_environment(template_file) - ::Gitlab::Utils::Measuring.execute_with(measurement_enabled, measurement_logger, base_log_data) do - prepare_template_environment(template_file) + prepare_import_params - prepare_import_params - - ::Projects::CreateService.new(current_user, params).execute - end + ::Projects::CreateService.new(current_user, params).execute end private - def base_log_data - base_log_data = { - class: self.class.name, - current_user: current_user.name, - project_full_path: project_path - } - - if template_file - base_log_data[:import_type] = 'gitlab_project' - base_log_data[:file_path] = template_file.path - end - - base_log_data - end - def overwrite_project? overwrite? && project_with_same_full_path? end diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index ce2f9a13107..86cb4f35206 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -3,37 +3,39 @@ module Projects module ImportExport class ExportService < BaseService - def execute(after_export_strategy = nil, options = {}) + prepend Measurable + + def initialize(*args) + super + + @shared = project.import_export_shared + end + + def execute(after_export_strategy = nil) unless project.template_source? || can?(current_user, :admin_project, project) raise ::Gitlab::ImportExport::Error.permission_error(current_user, project) end - @shared = project.import_export_shared - - measurement_enabled = !!options[:measurement_enabled] - measurement_logger = options[:measurement_logger] - - ::Gitlab::Utils::Measuring.execute_with(measurement_enabled, measurement_logger, base_log_data) do - save_all! - execute_after_export_action(after_export_strategy) - end + save_all! + execute_after_export_action(after_export_strategy) ensure cleanup end + protected + + def extra_attributes_for_measurement + { + current_user: current_user&.name, + project_full_path: project&.full_path, + file_path: shared.export_path + } + end + private attr_accessor :shared - def base_log_data - { - class: self.class.name, - current_user: current_user.name, - project_full_path: project.full_path, - file_path: shared.export_path - } - end - def execute_after_export_action(after_export_strategy) return unless after_export_strategy diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index a2167be7949..449c4c3de6b 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -37,6 +37,17 @@ module Projects error(s_("ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}") % { project_safe_import_url: project.safe_import_url, project_full_path: project.full_path, message: message }) end + protected + + def extra_attributes_for_measurement + { + current_user: current_user&.name, + project_full_path: project&.full_path, + import_type: project&.import_type, + file_path: project&.import_source + } + end + private def after_execute_hook @@ -138,4 +149,9 @@ module Projects end end +# rubocop: disable Cop/InjectEnterpriseEditionModule Projects::ImportService.prepend_if_ee('EE::Projects::ImportService') +# rubocop: enable Cop/InjectEnterpriseEditionModule + +# Measurable should be at the bottom of the ancestor chain, so it will measure execution of EE::Projects::ImportService as well +Projects::ImportService.prepend(Measurable) diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb index 2a2cbd7f7be..a0256ea5e69 100644 --- a/app/services/wiki_pages/base_service.rb +++ b/app/services/wiki_pages/base_service.rb @@ -6,13 +6,13 @@ module WikiPages # - external_action: the action we report to external clients with webhooks # - usage_counter_action: the action that we count in out internal counters # - event_action: what we record as the value of `Event#action` - class BaseService < ::BaseService + class BaseService < ::BaseContainerService private def execute_hooks(page) page_data = payload(page) - @project.execute_hooks(page_data, :wiki_page_hooks) - @project.execute_services(page_data, :wiki_page_hooks) + container.execute_hooks(page_data, :wiki_page_hooks) + container.execute_services(page_data, :wiki_page_hooks) increment_usage create_wiki_event(page) end diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb index 811f460e042..4ef19676d82 100644 --- a/app/services/wiki_pages/create_service.rb +++ b/app/services/wiki_pages/create_service.rb @@ -3,8 +3,8 @@ module WikiPages class CreateService < WikiPages::BaseService def execute - project_wiki = ProjectWiki.new(@project, current_user) - page = WikiPage.new(project_wiki) + wiki = Wiki.for_container(container, current_user) + page = WikiPage.new(wiki) if page.create(@params) execute_hooks(page) diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb index fd3408bc13a..a287c511a65 100644 --- a/app/workers/project_export_worker.rb +++ b/app/workers/project_export_worker.rb @@ -9,7 +9,7 @@ class ProjectExportWorker # rubocop:disable Scalability/IdempotentWorker worker_resource_boundary :memory urgency :throttled - def perform(current_user_id, project_id, after_export_strategy = {}, params = {}, options = {}) + def perform(current_user_id, project_id, after_export_strategy = {}, params = {}) current_user = User.find(current_user_id) project = Project.find(project_id) export_job = project.export_jobs.safe_find_or_create_by(jid: self.jid) @@ -17,7 +17,7 @@ class ProjectExportWorker # rubocop:disable Scalability/IdempotentWorker export_job&.start - ::Projects::ImportExport::ExportService.new(project, current_user, params).execute(after_export, options) + ::Projects::ImportExport::ExportService.new(project, current_user, params).execute(after_export) export_job&.finish rescue ActiveRecord::RecordNotFound, Gitlab::ImportExport::AfterExportStrategyBuilder::StrategyNotFoundError => e diff --git a/changelogs/unreleased/213341-project-snippet-delete-redirect.yml b/changelogs/unreleased/213341-project-snippet-delete-redirect.yml new file mode 100644 index 00000000000..0587892a3de --- /dev/null +++ b/changelogs/unreleased/213341-project-snippet-delete-redirect.yml @@ -0,0 +1,5 @@ +--- +title: Fixed redirection when deleting a project snippet +merge_request: 31709 +author: +type: fixed diff --git a/changelogs/unreleased/214322-remove-token-from-runners-api.yml b/changelogs/unreleased/214322-remove-token-from-runners-api.yml new file mode 100644 index 00000000000..8ee10b43e74 --- /dev/null +++ b/changelogs/unreleased/214322-remove-token-from-runners-api.yml @@ -0,0 +1,5 @@ +--- +title: Remove token attribute from Runners API +merge_request: 31448 +author: +type: removed diff --git a/changelogs/unreleased/enable-group-export-ndjson-by-default.yml b/changelogs/unreleased/enable-group-export-ndjson-by-default.yml new file mode 100644 index 00000000000..74229070c33 --- /dev/null +++ b/changelogs/unreleased/enable-group-export-ndjson-by-default.yml @@ -0,0 +1,5 @@ +--- +title: Prepare group export feature to use ndjson +merge_request: 31742 +author: +type: changed diff --git a/changelogs/unreleased/enable-group-import-ndjson-by-default.yml b/changelogs/unreleased/enable-group-import-ndjson-by-default.yml new file mode 100644 index 00000000000..97a43b8e7ff --- /dev/null +++ b/changelogs/unreleased/enable-group-import-ndjson-by-default.yml @@ -0,0 +1,5 @@ +--- +title: Prepare group import feature to use ndjson +merge_request: 31741 +author: +type: changed diff --git a/config/initializers/measuring.rb b/config/initializers/measuring.rb new file mode 100644 index 00000000000..79258cda365 --- /dev/null +++ b/config/initializers/measuring.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +Gitlab::Utils::Measuring.logger = Gitlab::Services::Logger.build diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 1af3d93edb6..d6b4bdeb50e 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -339,6 +339,9 @@ only. For example: ## `audit_json.log` +NOTE: **Note:** +Most log entries only exist in [GitLab Starter](https://about.gitlab.com/pricing), however a few exist in GitLab Core. + This file lives in `/var/log/gitlab/gitlab-rails/audit_json.log` for Omnibus GitLab packages or in `/home/git/gitlab/log/audit_json.log` for installations from source. @@ -584,6 +587,8 @@ This file lives in `/var/log/gitlab/gitlab-rails/importer.log` for Omnibus GitLab packages or in `/home/git/gitlab/log/importer.log` for installations from source. +It logs the progress of the import process. + ## `auth.log` > Introduced in GitLab 12.0. @@ -738,6 +743,23 @@ Each line contains a JSON line that can be ingested by Elasticsearch. For exampl } ``` +## `service_measurement.log` + +> Introduced in GitLab 13.0. + +This file lives in `/var/log/gitlab/gitlab-rails/service_measurement.log` for +Omnibus GitLab packages or in `/home/git/gitlab/log/service_measurement.log` for +installations from source. + +It contain only a single structured log with measurements for each service execution. +It will contain measurement such as: number of sql calls, execution_time, gc_stats, memory usage, etc... + +For example: + +```json +{ "severity":"INFO", "time":"2020-04-22T16:04:50.691Z","correlation_id":"04f1366e-57a1-45b8-88c1-b00b23dc3616","class":"Projects::ImportExport::ExportService","current_user":"John Doe","project_full_path":"group1/test-export","file_path":"/path/to/archive","gc_stats":{"count":{"before":127,"after":127,"diff":0},"heap_allocated_pages":{"before":10369,"after":10369,"diff":0},"heap_sorted_length":{"before":10369,"after":10369,"diff":0},"heap_allocatable_pages":{"before":0,"after":0,"diff":0},"heap_available_slots":{"before":4226409,"after":4226409,"diff":0},"heap_live_slots":{"before":2542709,"after":2641420,"diff":98711},"heap_free_slots":{"before":1683700,"after":1584989,"diff":-98711},"heap_final_slots":{"before":0,"after":0,"diff":0},"heap_marked_slots":{"before":2542704,"after":2542704,"diff":0},"heap_eden_pages":{"before":10369,"after":10369,"diff":0},"heap_tomb_pages":{"before":0,"after":0,"diff":0},"total_allocated_pages":{"before":10369,"after":10369,"diff":0},"total_freed_pages":{"before":0,"after":0,"diff":0},"total_allocated_objects":{"before":24896308,"after":24995019,"diff":98711},"total_freed_objects":{"before":22353599,"after":22353599,"diff":0},"malloc_increase_bytes":{"before":140032,"after":6650240,"diff":6510208},"malloc_increase_bytes_limit":{"before":25804104,"after":25804104,"diff":0},"minor_gc_count":{"before":94,"after":94,"diff":0},"major_gc_count":{"before":33,"after":33,"diff":0},"remembered_wb_unprotected_objects":{"before":34284,"after":34284,"diff":0},"remembered_wb_unprotected_objects_limit":{"before":68568,"after":68568,"diff":0},"old_objects":{"before":2404725,"after":2404725,"diff":0},"old_objects_limit":{"before":4809450,"after":4809450,"diff":0},"oldmalloc_increase_bytes":{"before":140032,"after":6650240,"diff":6510208},"oldmalloc_increase_bytes_limit":{"before":68537556,"after":68537556,"diff":0}},"time_to_finish":0.12298400001600385,"number_of_sql_calls":70,"memory_usage":"0.0 MiB","label":"process_48616"} +``` + ## `geo.log` **(PREMIUM ONLY)** > Introduced in 9.5. diff --git a/doc/api/runners.md b/doc/api/runners.md index 21d768a1605..5db1f116f6c 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -162,9 +162,9 @@ GET /runners/:id curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/runners/6" ``` -CAUTION: **Deprecation** -The `token` attribute in the response is deprecated [since GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320). -It will be removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322). +NOTE: **Note:** +The `token` attribute in the response was deprecated [in GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320). +and removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322). Example response: @@ -190,7 +190,6 @@ Example response: "path_with_namespace": "gitlab-org/gitlab-foss" } ], - "token": "205086a8e3b9a2b818ffac9b89d102", "revision": null, "tag_list": [ "ruby", @@ -225,9 +224,9 @@ PUT /runners/:id curl --request PUT --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2" ``` -CAUTION: **Deprecation** -The `token` attribute in the response is deprecated [since GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320). -It will be removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322). +NOTE: **Note:** +The `token` attribute in the response was deprecated [in GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320). +and removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322). Example response: @@ -253,7 +252,6 @@ Example response: "path_with_namespace": "gitlab-org/gitlab-foss" } ], - "token": "205086a8e3b9a2b818ffac9b89d102", "revision": null, "tag_list": [ "ruby", diff --git a/doc/development/import_project.md b/doc/development/import_project.md index 78efc6ce2ab..f222a6533e8 100644 --- a/doc/development/import_project.md +++ b/doc/development/import_project.md @@ -61,10 +61,9 @@ Parameters: | `namespace_path` | string | yes | Namespace path | | `project_path` | string | yes | Project name | | `archive_path` | string | yes | Path to the exported project tarball you want to import | -| `measurement_enabled` | boolean | no | Measure execution time, number of SQL calls and GC count | ```shell -bundle exec rake "gitlab:import_export:import[root, root, testingprojectimport, /path/to/file.tar.gz, true]" +bundle exec rake "gitlab:import_export:import[root, root, testingprojectimport, /path/to/file.tar.gz]" ``` ### Importing via the Rails console diff --git a/doc/install/installation.md b/doc/install/installation.md index 27616f6c1ff..dd619e9e7b3 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -370,10 +370,10 @@ use of extensions and concurrent index removal, you need at least PostgreSQL 9.2 ## 7. Redis -GitLab requires at least Redis 2.8. +GitLab requires at least Redis 5.0. -If you are using Debian 8 or Ubuntu 14.04 and up, you can simply install -Redis 2.8 with: +If you are using Debian 10 or Ubuntu 20.04 and up, you can install +Redis 5.0 with: ```shell sudo apt-get install redis-server diff --git a/doc/install/requirements.md b/doc/install/requirements.md index e4af18ebf93..7a0b2056a7b 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -72,6 +72,10 @@ a version older than `v10.13.0`, you need to update to a newer version. You can find instructions to install from community maintained packages or compile from source at the [Node.js website](https://nodejs.org/en/download/). +## Redis versions + +GitLab requires Redis 5.0+. Beginning in GitLab 13.0, lower versions are not supported. + ## Hardware requirements ### Storage diff --git a/doc/user/project/status_page/index.md b/doc/user/project/status_page/index.md index 1acf978c81d..52ec7de4632 100644 --- a/doc/user/project/status_page/index.md +++ b/doc/user/project/status_page/index.md @@ -83,6 +83,8 @@ The incident detail page shows detailed information about a particular incident. To publish an Incident, you first need to create an issue in the Project you enabled the Status Page settings in. Once this issue is created, a background worker will publish the issue onto the Status Page using the credentials you provided during setup. +Since all incidents are published publicly, user and group mentions are anonymized with `Incident Responder`, +and titles of non-public [GitLab references](../../markdown.md#special-gitlab-references) are removed. NOTE: **Note:** Confidential issues are not published. If a published issue is made confidential it will be unpublished. diff --git a/lib/api/entities/runner_details.rb b/lib/api/entities/runner_details.rb index 2bb143253fe..1dd8543d595 100644 --- a/lib/api/entities/runner_details.rb +++ b/lib/api/entities/runner_details.rb @@ -11,9 +11,12 @@ module API expose :version, :revision, :platform, :architecture expose :contacted_at - # @deprecated in 12.10 https://gitlab.com/gitlab-org/gitlab/-/issues/214320 - # will be removed by 13.0 https://gitlab.com/gitlab-org/gitlab/-/issues/214322 - expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.instance_type? } + # Will be removed: https://gitlab.com/gitlab-org/gitlab/-/issues/217105 + expose(:token, if: ->(runner, options) do + return false if ::Feature.enabled?(:hide_token_from_runners_api, default_enabled: true) + + options[:current_user].admin? || !runner.instance_type? + end) # rubocop: disable CodeReuse/ActiveRecord expose :projects, with: Entities::BasicProjectDetails do |runner, options| diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index a2146406690..a42cb9b49af 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -70,7 +70,7 @@ module API post ':id/wikis' do authorize! :create_wiki, user_project - page = WikiPages::CreateService.new(user_project, current_user, params).execute + page = WikiPages::CreateService.new(container: user_project, current_user: current_user, params: params).execute if page.valid? present page, with: Entities::WikiPage @@ -91,7 +91,7 @@ module API put ':id/wikis/:slug' do authorize! :create_wiki, user_project - page = WikiPages::UpdateService.new(user_project, current_user, params).execute(wiki_page) + page = WikiPages::UpdateService.new(container: user_project, current_user: current_user, params: params).execute(wiki_page) if page.valid? present page, with: Entities::WikiPage @@ -107,7 +107,7 @@ module API delete ':id/wikis/:slug' do authorize! :admin_wiki, user_project - WikiPages::DestroyService.new(user_project, current_user).execute(wiki_page) + WikiPages::DestroyService.new(container: user_project, current_user: current_user).execute(wiki_page) no_content! end diff --git a/lib/gitlab/code_navigation_path.rb b/lib/gitlab/code_navigation_path.rb index 8dd2e9cb1bb..c4f9407f7f0 100644 --- a/lib/gitlab/code_navigation_path.rb +++ b/lib/gitlab/code_navigation_path.rb @@ -6,6 +6,7 @@ module Gitlab include Gitlab::Routing CODE_NAVIGATION_JOB_NAME = 'code_navigation' + LATEST_COMMITS_LIMIT = 10 def initialize(project, commit_sha) @project = project @@ -25,8 +26,11 @@ module Gitlab def build strong_memoize(:build) do + latest_commits_shas = + project.repository.commits(commit_sha, limit: LATEST_COMMITS_LIMIT).map(&:sha) + artifact = ::Ci::JobArtifact - .for_sha(commit_sha, project.id) + .for_sha(latest_commits_shas, project.id) .for_job_name(CODE_NAVIGATION_JOB_NAME) .last diff --git a/lib/gitlab/data_builder/wiki_page.rb b/lib/gitlab/data_builder/wiki_page.rb index 9368446fa59..8aee25e9fe6 100644 --- a/lib/gitlab/data_builder/wiki_page.rb +++ b/lib/gitlab/data_builder/wiki_page.rb @@ -8,6 +8,9 @@ module Gitlab def build(wiki_page, user, action) wiki = wiki_page.wiki + # TODO: group hooks https://gitlab.com/gitlab-org/gitlab/-/issues/216904 + return {} if wiki.container.is_a?(Group) + { object_kind: wiki_page.class.name.underscore, user: user.hook_attrs, diff --git a/lib/gitlab/import_export/project/base_task.rb b/lib/gitlab/import_export/project/base_task.rb index f6afbfcc50d..356e261e251 100644 --- a/lib/gitlab/import_export/project/base_task.rb +++ b/lib/gitlab/import_export/project/base_task.rb @@ -11,20 +11,12 @@ module Gitlab @file_path = opts.fetch(:file_path) @namespace = Namespace.find_by_full_path(opts.fetch(:namespace_path)) @current_user = User.find_by_username(opts.fetch(:username)) - @measurement_enabled = opts.fetch(:measurement_enabled) @logger = logger end private - attr_reader :project, :namespace, :current_user, :file_path, :project_path, :logger, :measurement_enabled - - def measurement_options - { - measurement_enabled: measurement_enabled, - measurement_logger: logger - } - end + attr_reader :project, :namespace, :current_user, :file_path, :project_path, :logger def disable_upload_object_storage overwrite_uploads_setting('enabled', false) do diff --git a/lib/gitlab/import_export/project/export_task.rb b/lib/gitlab/import_export/project/export_task.rb index 4919ace0742..5e105b4653d 100644 --- a/lib/gitlab/import_export/project/export_task.rb +++ b/lib/gitlab/import_export/project/export_task.rb @@ -16,7 +16,7 @@ module Gitlab with_export do ::Projects::ImportExport::ExportService.new(project, current_user) - .execute(Gitlab::ImportExport::AfterExportStrategies::MoveFileStrategy.new(archive_path: file_path), measurement_options) + .execute(Gitlab::ImportExport::AfterExportStrategies::MoveFileStrategy.new(archive_path: file_path)) end return error(project.import_export_shared.errors.join(', ')) if project.import_export_shared.errors.any? diff --git a/lib/gitlab/import_export/project/import_task.rb b/lib/gitlab/import_export/project/import_task.rb index 20f7ac1eb18..59bb8af750e 100644 --- a/lib/gitlab/import_export/project/import_task.rb +++ b/lib/gitlab/import_export/project/import_task.rb @@ -59,7 +59,7 @@ module Gitlab import_params ) - service.execute(measurement_options) + service.execute end end diff --git a/lib/gitlab/services/logger.rb b/lib/gitlab/services/logger.rb new file mode 100644 index 00000000000..4e7ef73922c --- /dev/null +++ b/lib/gitlab/services/logger.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Gitlab + module Services + class Logger < ::Gitlab::JsonLogger + def self.file_name_noext + 'service_measurement' + end + end + end +end diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 7ddc9ae969a..f1297cccf6f 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -180,7 +180,8 @@ module Gitlab reply_by_email_enabled: alt_usage_data { Gitlab::IncomingEmail.enabled? }, signup_enabled: alt_usage_data { Gitlab::CurrentSettings.allow_signup? }, web_ide_clientside_preview_enabled: alt_usage_data { Gitlab::CurrentSettings.web_ide_clientside_preview_enabled? }, - ingress_modsecurity_enabled: Feature.enabled?(:ingress_modsecurity) + ingress_modsecurity_enabled: Feature.enabled?(:ingress_modsecurity), + grafana_link_enabled: alt_usage_data { Gitlab::CurrentSettings.grafana_enabled? } }.merge(features_usage_data_container_expiration_policies) end diff --git a/lib/gitlab/utils/measuring.rb b/lib/gitlab/utils/measuring.rb index 9c65dc97ede..febe489f1f8 100644 --- a/lib/gitlab/utils/measuring.rb +++ b/lib/gitlab/utils/measuring.rb @@ -6,19 +6,14 @@ module Gitlab module Utils class Measuring class << self - def execute_with(measurement_enabled, logger, base_log_data) - measurement_enabled ? measuring(logger, base_log_data).with_measuring { yield } : yield - end + attr_writer :logger - private - - def measuring(logger, base_log_data) - Gitlab::Utils::Measuring.new(logger: logger, base_log_data: base_log_data) + def logger + @logger ||= Logger.new(STDOUT) end end - def initialize(logger: nil, base_log_data: {}) - @logger = logger || Logger.new($stdout) + def initialize(base_log_data = {}) @base_log_data = base_log_data end @@ -45,7 +40,7 @@ module Gitlab private - attr_reader :gc_stats, :time_to_finish, :sql_calls_count, :logger, :base_log_data + attr_reader :gc_stats, :time_to_finish, :sql_calls_count, :base_log_data def with_count_queries(&block) @sql_calls_count = 0 @@ -76,21 +71,8 @@ module Gitlab def log_info(details) details = base_log_data.merge(details) - details = details.to_yaml if ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT) - logger.info(details) - end - - def duration_in_numbers(duration_in_seconds) - milliseconds = duration_in_seconds.in_milliseconds % 1.second.in_milliseconds - seconds = duration_in_seconds % 1.minute - minutes = (duration_in_seconds / 1.minute) % (1.hour / 1.minute) - hours = duration_in_seconds / 1.hour - - if hours == 0 - "%02d:%02d:%03d" % [minutes, seconds, milliseconds] - else - "%02d:%02d:%02d:%03d" % [hours, minutes, seconds, milliseconds] - end + details = details.to_yaml if ActiveSupport::Logger.logger_outputs_to?(Measuring.logger, STDOUT) + Measuring.logger.info(details) end end end diff --git a/lib/tasks/gitlab/import_export/export.rake b/lib/tasks/gitlab/import_export/export.rake index c9c212fbe4d..4bdc62c9319 100644 --- a/lib/tasks/gitlab/import_export/export.rake +++ b/lib/tasks/gitlab/import_export/export.rake @@ -3,12 +3,12 @@ # Export project to archive # # @example -# bundle exec rake "gitlab:import_export:export[root, root, project_to_export, /path/to/file.tar.gz, true]" +# bundle exec rake "gitlab:import_export:export[root, root, project_to_export, /path/to/file.tar.gz]" # namespace :gitlab do namespace :import_export do desc 'GitLab | Import/Export | EXPERIMENTAL | Export large project archives' - task :export, [:username, :namespace_path, :project_path, :archive_path, :measurement_enabled] => :gitlab_environment do |_t, args| + task :export, [:username, :namespace_path, :project_path, :archive_path] => :gitlab_environment do |_t, args| # Load it here to avoid polluting Rake tasks with Sidekiq test warnings require 'sidekiq/testing' @@ -18,6 +18,7 @@ namespace :gitlab do warn_user_is_not_gitlab if ENV['EXPORT_DEBUG'].present? + Gitlab::Utils::Measuring.logger = logger ActiveRecord::Base.logger = logger logger.level = Logger::DEBUG else @@ -29,7 +30,6 @@ namespace :gitlab do project_path: args.project_path, username: args.username, file_path: args.archive_path, - measurement_enabled: Gitlab::Utils.to_boolean(args.measurement_enabled), logger: logger ) diff --git a/lib/tasks/gitlab/import_export/import.rake b/lib/tasks/gitlab/import_export/import.rake index 7e2162a7774..2702b530334 100644 --- a/lib/tasks/gitlab/import_export/import.rake +++ b/lib/tasks/gitlab/import_export/import.rake @@ -7,12 +7,12 @@ # 2. Performs Sidekiq job synchronously # # @example -# bundle exec rake "gitlab:import_export:import[root, root, imported_project, /path/to/file.tar.gz, true]" +# bundle exec rake "gitlab:import_export:import[root, root, imported_project, /path/to/file.tar.gz]" # namespace :gitlab do namespace :import_export do desc 'GitLab | Import/Export | EXPERIMENTAL | Import large project archives' - task :import, [:username, :namespace_path, :project_path, :archive_path, :measurement_enabled] => :gitlab_environment do |_t, args| + task :import, [:username, :namespace_path, :project_path, :archive_path] => :gitlab_environment do |_t, args| # Load it here to avoid polluting Rake tasks with Sidekiq test warnings require 'sidekiq/testing' @@ -22,6 +22,7 @@ namespace :gitlab do warn_user_is_not_gitlab if ENV['IMPORT_DEBUG'].present? + Gitlab::Utils::Measuring.logger = logger ActiveRecord::Base.logger = logger logger.level = Logger::DEBUG else @@ -33,7 +34,6 @@ namespace :gitlab do project_path: args.project_path, username: args.username, file_path: args.archive_path, - measurement_enabled: Gitlab::Utils.to_boolean(args.measurement_enabled), logger: logger ) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f827255127a..2f69ef5c05c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,6 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-24 17:33-0400\n" -"PO-Revision-Date: 2020-04-24 17:33-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -1738,16 +1736,10 @@ msgstr "" msgid "AlertManagement|End time" msgstr "" -msgid "AlertManagement|End time:" -msgstr "" - msgid "AlertManagement|Events" msgstr "" -msgid "AlertManagement|Events:" -msgstr "" - -msgid "AlertManagement|Full Alert Details" +msgid "AlertManagement|Full alert details" msgstr "" msgid "AlertManagement|High" @@ -1780,10 +1772,10 @@ msgstr "" msgid "AlertManagement|Resolved" msgstr "" -msgid "AlertManagement|Start time" +msgid "AlertManagement|Service" msgstr "" -msgid "AlertManagement|Start time:" +msgid "AlertManagement|Start time" msgstr "" msgid "AlertManagement|Status" @@ -1798,6 +1790,9 @@ msgstr "" msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear." msgstr "" +msgid "AlertManagement|Tool" +msgstr "" + msgid "AlertManagement|Triggered" msgstr "" @@ -23712,9 +23707,6 @@ msgstr "" msgid "Vulnerabilities over time" msgstr "" -msgid "Vulnerability List" -msgstr "" - msgid "Vulnerability remediated. Review before resolving." msgstr "" diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 00db5ea3a65..78e2ba8a248 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -171,7 +171,12 @@ module QA end def runners(tag_list: nil) - response = get Runtime::API::Request.new(api_client, "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}").url + response = if tag_list + get Runtime::API::Request.new(api_client, "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}").url + else + get Runtime::API::Request.new(api_client, "#{api_runners_path}").url + end + parse_body(response) end diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb index f1f72c9cacd..b2a36f92ffe 100644 --- a/qa/qa/resource/runner.rb +++ b/qa/qa/resource/runner.rb @@ -5,8 +5,8 @@ require 'securerandom' module QA module Resource class Runner < Base - attr_writer :name, :tags, :image - attr_accessor :config, :token + attr_writer :name, :tags, :image, :executor, :executor_image + attr_accessor :config, :token, :run_untagged attribute :id attribute :project do @@ -20,35 +20,42 @@ module QA @name || "qa-runner-#{SecureRandom.hex(4)}" end - def tags - @tags || %w[qa e2e] - end - def image @image || 'gitlab/gitlab-runner:alpine' end + def executor + @executor || :shell + end + + def executor_image + @executor_image || 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6' + end + def fabricate_via_api! Service::DockerRun::GitlabRunner.new(name).tap do |runner| runner.pull runner.token = @token ||= project.runners_token runner.address = Runtime::Scenario.gitlab_address - runner.tags = tags + runner.tags = @tags if @tags runner.image = image runner.config = config if config + runner.executor = executor + runner.executor_image = executor_image if executor == :docker + runner.run_untagged = run_untagged if run_untagged runner.register! end end def remove_via_api! - runners = project.runners(tag_list: tags) + runners = project.runners(tag_list: @tags) unless runners && !runners.empty? - raise "Project #{project.path_with_namespace} has no runners with tags #{tags}." + raise "Project #{project.path_with_namespace} has no runners#{" with tags #{@tags}." if @tags&.any?}" end this_runner = runners.find { |runner| runner[:description] == name } unless this_runner - raise "Project #{project.path_with_namespace} does not have a runner with a description matching #{name} and tags #{tags}. Runners available: #{runners}" + raise "Project #{project.path_with_namespace} does not have a runner with a description matching #{name} #{"or tags #{@tags}" if @tags&.any?}. Runners available: #{runners}" end @id = this_runner[:id] diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb index 6856a5a8399..834f6b430ac 100644 --- a/qa/qa/service/docker_run/gitlab_runner.rb +++ b/qa/qa/service/docker_run/gitlab_runner.rb @@ -6,14 +6,21 @@ module QA module Service module DockerRun class GitlabRunner < Base - attr_accessor :token, :address, :tags, :image, :run_untagged - attr_writer :config + attr_reader :tags + attr_accessor :token, :address, :image, :run_untagged + attr_writer :config, :executor, :executor_image + + CONFLICTING_VARIABLES_MESSAGE = <<~MSG + There are conflicting options preventing the runner from starting. + %s cannot be specified if %s is %s + MSG def initialize(name) @image = 'gitlab/gitlab-runner:alpine' @name = name || "qa-runner-#{SecureRandom.hex(4)}" - @tags = %w[qa test] - @run_untagged = false + @run_untagged = true + @executor = :shell + @executor_image = 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6' super() end @@ -32,23 +39,49 @@ module QA shell <<~CMD.tr("\n", ' ') docker run -d --rm --entrypoint=/bin/sh --network #{network} --name #{@name} - -p 8093:8093 - -e CI_SERVER_URL=#{@address} - -e REGISTER_NON_INTERACTIVE=true - -e REGISTRATION_TOKEN=#{@token} - -e RUNNER_EXECUTOR=shell - -e RUNNER_TAG_LIST=#{@tags.join(',')} - -e RUNNER_NAME=#{@name} + #{'-v /var/run/docker.sock:/var/run/docker.sock' if @executor == :docker} + --privileged #{@image} -c "#{register_command}" CMD end + def tags=(tags) + @tags = tags + @run_untagged = false + end + private def register_command - <<~CMD + args = [] + args << '--non-interactive' + args << "--name #{@name}" + args << "--url #{@address}" + args << "--registration-token #{@token}" + + args << if run_untagged + raise CONFLICTING_VARIABLES_MESSAGE % [:tags=, :run_untagged, run_untagged] if @tags&.any? + + '--run-untagged=true' + else + raise 'You must specify tags to run!' unless @tags&.any? + + "--tag-list #{@tags.join(',')}" + end + + args << "--executor #{@executor}" + + if @executor == :docker + args << "--docker-image #{@executor_image}" + args << '--docker-tlsverify=false' + args << '--docker-privileged=true' + args << "--docker-network-mode=#{network}" + end + + <<~CMD.strip printf '#{config.chomp.gsub(/\n/, "\\n").gsub('"', '\"')}' > /etc/gitlab-runner/config.toml && - gitlab-runner register --run-untagged=#{@run_untagged} && + gitlab-runner register \ + #{args.join(' ')} && gitlab-runner run CMD end diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb index bf20c4a70ea..0838883c8b8 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb @@ -8,6 +8,7 @@ module QA let(:runner) do Resource::Runner.fabricate_via_api! do |runner| runner.name = executor + runner.run_untagged = true end end @@ -17,9 +18,6 @@ module QA mr.file_name = '.gitlab-ci.yml' mr.file_content = <<~EOF test: - tags: - - qa - - e2e script: - echo '(66.67%) covered' EOF diff --git a/qa/spec/service/docker_run/gitlab_runner_spec.rb b/qa/spec/service/docker_run/gitlab_runner_spec.rb new file mode 100644 index 00000000000..db1bb74ca8f --- /dev/null +++ b/qa/spec/service/docker_run/gitlab_runner_spec.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +module QA + describe Service::DockerRun::GitlabRunner do + let(:runner_name) { 'test-runner' } + let(:address) { 'gitlab.test' } + let(:token) { 'abc123' } + + let(:tags) { %w[qa test] } + + subject do + described_class.new(runner_name).tap do |runner| + runner.address = address + runner.token = token + end + end + + it 'defaults to run untagged' do + expect(subject.run_untagged).to be(true) + end + + describe '#register!' do + let(:register) { subject.send(:register!) } + + before do + allow(subject).to receive(:shell) + end + + context 'defaults' do + before do + register + end + + it 'runs non-interactively' do + expect(subject).to have_received(:shell).with(/ --non-interactive /) + end + + it 'sets pertinent information' do + expect(subject).to have_received(:shell).with(/--name #{runner_name} /) + expect(subject).to have_received(:shell).with(/--url #{subject.address} /) + expect(subject).to have_received(:shell).with(/--registration-token #{subject.token} /) + end + + it 'runs untagged' do + expect(subject).to have_received(:shell).with(/--run-untagged=true /) + end + + it 'has no tags' do + expect(subject.tags).to be_falsey + end + + it 'runs daemonized' do + expect(subject).to have_received(:shell).with(/ -d /) + end + + it 'cleans itself up' do + expect(subject).to have_received(:shell).with(/ --rm /) + end + end + + context 'running untagged' do + before do + register + end + + it 'passes --run-untagged=true' do + expect(subject).to have_received(:shell).with(/--run-untagged=true /) + end + + it 'does not pass tag list' do + expect(subject).not_to have_received(:shell).with(/--tag-list/) + end + end + + context 'running tagged' do + context 'with only tags set' do + before do + subject.tags = tags + + register + end + + it 'does not pass --run-untagged' do + expect(subject).not_to have_received(:shell).with(/--run-untagged=true/) + end + + it 'passes the tags with comma-separation' do + expect(subject).to have_received(:shell).with(/--tag-list #{tags.join(',')} /) + end + end + + context 'with specifying only run_untagged' do + before do + subject.run_untagged = false + end + + it 'raises an error if tags are not specified' do + expect { register }.to raise_error(/must specify tags/i) + end + end + + context 'when specifying contradicting variables' do + before do + subject.tags = tags + subject.run_untagged = true + end + + it 'raises an error' do + expect { register }.to raise_error(/conflicting options/i) + end + end + end + + context 'executors' do + it 'defaults to the shell executor' do + register + + expect(subject).to have_received(:shell).with(/--executor shell /) + end + + context 'docker' do + before do + subject.executor = :docker + + register + end + + it 'specifies the docker executor' do + expect(subject).to have_received(:shell).with(/--executor docker /) + end + + it 'mounts the docker socket to the host runner' do + expect(subject).to have_received(:shell).with(/-v \/var\/run\/docker.sock:\/var\/run\/docker.sock /) + end + + it 'runs in privileged mode' do + expect(subject).to have_received(:shell).with(/--privileged /) + end + + it 'has a default image' do + expect(subject).to have_received(:shell).with(/--docker-image \b.+\b /) + end + + it 'does not verify TLS' do + expect(subject).to have_received(:shell).with(/--docker-tlsverify=false /) + end + + it 'passes privileged mode' do + expect(subject).to have_received(:shell).with(/--docker-privileged=true /) + end + + it 'passes the host network' do + expect(subject).to have_received(:shell).with(/--docker-network-mode=#{subject.network}/) + end + end + end + end + + describe '#tags=' do + before do + subject.tags = tags + end + + it 'sets the tags' do + expect(subject.tags).to eq(tags) + end + + it 'sets run_untagged' do + expect(subject.run_untagged).to be(false) + end + end + end +end diff --git a/spec/frontend/alert_management/components/alert_management_detail_spec.js b/spec/frontend/alert_management/components/alert_management_detail_spec.js index af524619913..2758014aaa7 100644 --- a/spec/frontend/alert_management/components/alert_management_detail_spec.js +++ b/spec/frontend/alert_management/components/alert_management_detail_spec.js @@ -2,6 +2,10 @@ import { shallowMount } from '@vue/test-utils'; import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import AlertDetails from '~/alert_management/components/alert_details.vue'; +import mockAlerts from '../mocks/alerts.json'; + +const mockAlert = mockAlerts[0]; + describe('AlertDetails', () => { let wrapper; const newIssuePath = 'root/alerts/-/issues/new'; @@ -56,7 +60,7 @@ describe('AlertDetails', () => { describe('when alert is present', () => { beforeEach(() => { - mountComponent(); + mountComponent({ data: { alert: mockAlert } }); }); it('renders a tab with overview information', () => { @@ -67,8 +71,39 @@ describe('AlertDetails', () => { expect(wrapper.find('[data-testid="fullDetailsTab"]').exists()).toBe(true); }); - it('renders alert details', () => { + it('renders a title', () => { + expect(wrapper.find('[data-testid="title"]').text()).toBe(mockAlert.title); + }); + + it('renders a start time', () => { expect(wrapper.find('[data-testid="startTimeItem"]').exists()).toBe(true); + expect(wrapper.find('[data-testid="startTimeItem"]').props().time).toBe( + mockAlert.startedAt, + ); + }); + }); + + describe('individual alert fields', () => { + describe.each` + field | data | isShown + ${'eventCount'} | ${1} | ${true} + ${'eventCount'} | ${undefined} | ${false} + ${'monitoringTool'} | ${'New Relic'} | ${true} + ${'monitoringTool'} | ${undefined} | ${false} + ${'service'} | ${'Prometheus'} | ${true} + ${'service'} | ${undefined} | ${false} + `(`$desc`, ({ field, data, isShown }) => { + beforeEach(() => { + mountComponent({ data: { alert: { ...mockAlert, [field]: data } } }); + }); + + it(`${field} is ${isShown ? 'displayed' : 'hidden'} correctly`, () => { + if (isShown) { + expect(wrapper.find(`[data-testid="${field}"]`).text()).toBe(data.toString()); + } else { + expect(wrapper.find(`[data-testid="${field}"]`).exists()).toBe(false); + } + }); }); }); diff --git a/spec/frontend/helpers/vue_mount_component_helper.js b/spec/frontend/helpers/vue_mount_component_helper.js index 6848c95d95d..615ff69a01c 100644 --- a/spec/frontend/helpers/vue_mount_component_helper.js +++ b/spec/frontend/helpers/vue_mount_component_helper.js @@ -1,22 +1,38 @@ import Vue from 'vue'; +/** + * Deprecated. Please do not use. + * Please see https://gitlab.com/groups/gitlab-org/-/epics/2445 + */ const mountComponent = (Component, props = {}, el = null) => new Component({ propsData: props, }).$mount(el); +/** + * Deprecated. Please do not use. + * Please see https://gitlab.com/groups/gitlab-org/-/epics/2445 + */ export const createComponentWithStore = (Component, store, propsData = {}) => new Component({ store, propsData, }); +/** + * Deprecated. Please do not use. + * Please see https://gitlab.com/groups/gitlab-org/-/epics/2445 + */ export const mountComponentWithStore = (Component, { el, props, store }) => new Component({ store, propsData: props || {}, }).$mount(el); +/** + * Deprecated. Please do not use. + * Please see https://gitlab.com/groups/gitlab-org/-/epics/2445 + */ export const mountComponentWithSlots = (Component, { props, slots }) => { const component = new Component({ propsData: props || {}, @@ -30,9 +46,18 @@ export const mountComponentWithSlots = (Component, { props, slots }) => { /** * Mount a component with the given render method. * + * ----------------------------- + * Deprecated. Please do not use. + * Please see https://gitlab.com/groups/gitlab-org/-/epics/2445 + * ----------------------------- + * * This helps with inserting slots that need to be compiled. */ export const mountComponentWithRender = (render, el = null) => mountComponent(Vue.extend({ render }), {}, el); +/** + * Deprecated. Please do not use. + * Please see https://gitlab.com/groups/gitlab-org/-/epics/2445 + */ export default mountComponent; diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js index fb04959a7bf..9f6888fca11 100644 --- a/spec/frontend/snippets/components/snippet_header_spec.js +++ b/spec/frontend/snippets/components/snippet_header_spec.js @@ -172,14 +172,34 @@ describe('Snippet header component', () => { }); }); - it('closes modal and redirects to snippets listing in case of successful mutation', () => { - createComponent(); - wrapper.vm.closeDeleteModal = jest.fn(); + describe('in case of successful mutation, closes modal and redirects to correct listing', () => { + const createDeleteSnippet = (snippetProps = {}) => { + createComponent({ + snippetProps, + }); + wrapper.vm.closeDeleteModal = jest.fn(); - wrapper.vm.deleteSnippet(); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled(); - expect(window.location.pathname).toEqual('dashboard/snippets'); + wrapper.vm.deleteSnippet(); + return wrapper.vm.$nextTick(); + }; + + it('redirects to dashboard/snippets for personal snippet', () => { + return createDeleteSnippet().then(() => { + expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled(); + expect(window.location.pathname).toBe('dashboard/snippets'); + }); + }); + + it('redirects to project snippets for project snippet', () => { + const fullPath = 'foo/bar'; + return createDeleteSnippet({ + project: { + fullPath, + }, + }).then(() => { + expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled(); + expect(window.location.pathname).toBe(fullPath); + }); }); }); }); diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js index 6848c95d95d..c1857115b61 100644 --- a/spec/javascripts/helpers/vue_mount_component_helper.js +++ b/spec/javascripts/helpers/vue_mount_component_helper.js @@ -1,38 +1,2 @@ -import Vue from 'vue'; - -const mountComponent = (Component, props = {}, el = null) => - new Component({ - propsData: props, - }).$mount(el); - -export const createComponentWithStore = (Component, store, propsData = {}) => - new Component({ - store, - propsData, - }); - -export const mountComponentWithStore = (Component, { el, props, store }) => - new Component({ - store, - propsData: props || {}, - }).$mount(el); - -export const mountComponentWithSlots = (Component, { props, slots }) => { - const component = new Component({ - propsData: props || {}, - }); - - component.$slots = slots; - - return component.$mount(); -}; - -/** - * Mount a component with the given render method. - * - * This helps with inserting slots that need to be compiled. - */ -export const mountComponentWithRender = (render, el = null) => - mountComponent(Vue.extend({ render }), {}, el); - -export default mountComponent; +export { default } from '../../frontend/helpers/vue_mount_component_helper'; +export * from '../../frontend/helpers/vue_mount_component_helper'; diff --git a/spec/lib/gitlab/code_navigation_path_spec.rb b/spec/lib/gitlab/code_navigation_path_spec.rb index cafe362c8c7..f9f335ff48d 100644 --- a/spec/lib/gitlab/code_navigation_path_spec.rb +++ b/spec/lib/gitlab/code_navigation_path_spec.rb @@ -4,18 +4,30 @@ require 'spec_helper' describe Gitlab::CodeNavigationPath do context 'when there is an artifact with code navigation data' do - let(:project) { create(:project, :repository) } - let(:sha) { project.commit.id } - let(:build_name) { Gitlab::CodeNavigationPath::CODE_NAVIGATION_JOB_NAME } + let_it_be(:project) { create(:project, :repository) } + let_it_be(:sha) { project.repository.commits('master', limit: 5).last.id } + let_it_be(:build_name) { Gitlab::CodeNavigationPath::CODE_NAVIGATION_JOB_NAME } + let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: sha) } + let_it_be(:job) { create(:ci_build, pipeline: pipeline, name: build_name) } + let_it_be(:artifact) { create(:ci_job_artifact, :lsif, job: job) } + + let(:commit_sha) { sha } let(:path) { 'lib/app.rb' } - let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha) } - let!(:job) { create(:ci_build, pipeline: pipeline, name: build_name) } - let!(:artifact) { create(:ci_job_artifact, :lsif, job: job) } - subject { described_class.new(project, sha).full_json_path_for(path) } + subject { described_class.new(project, commit_sha).full_json_path_for(path) } - it 'assigns code_navigation_build variable' do - expect(subject).to eq("/#{project.full_path}/-/jobs/#{job.id}/artifacts/raw/lsif/#{path}.json") + context 'when a pipeline exist for a sha' do + it 'returns path to a file in the artifact' do + expect(subject).to eq("/#{project.full_path}/-/jobs/#{job.id}/artifacts/raw/lsif/#{path}.json") + end + end + + context 'when a pipeline exist for the latest commits' do + let(:commit_sha) { project.commit.id } + + it 'returns path to a file in the artifact' do + expect(subject).to eq("/#{project.full_path}/-/jobs/#{job.id}/artifacts/raw/lsif/#{path}.json") + end end context 'when code_navigation feature is disabled' do @@ -23,7 +35,7 @@ describe Gitlab::CodeNavigationPath do stub_feature_flags(code_navigation: false) end - it 'does not assign code_navigation_build variable' do + it 'returns nil' do expect(subject).to be_nil end end diff --git a/spec/lib/gitlab/import_export/project/import_task_spec.rb b/spec/lib/gitlab/import_export/project/import_task_spec.rb index d83cc173388..7c11161aaa7 100644 --- a/spec/lib/gitlab/import_export/project/import_task_spec.rb +++ b/spec/lib/gitlab/import_export/project/import_task_spec.rb @@ -39,8 +39,6 @@ describe Gitlab::ImportExport::Project::ImportTask, :request_store do expect(project.milestones.count).to be > 0 expect(project.import_state.status).to eq('finished') end - - it_behaves_like 'measurable' end context 'when project import is invalid' do diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index bc9a2bed6cd..99bf5ebe771 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -174,6 +174,21 @@ describe Gitlab::UsageData, :aggregate_failures do expect(subject[:dependency_proxy_enabled]).to eq(Gitlab.config.dependency_proxy.enabled) expect(subject[:gitlab_shared_runners_enabled]).to eq(Gitlab.config.gitlab_ci.shared_runners_enabled) expect(subject[:web_ide_clientside_preview_enabled]).to eq(Gitlab::CurrentSettings.web_ide_clientside_preview_enabled?) + expect(subject[:grafana_link_enabled]).to eq(Gitlab::CurrentSettings.grafana_enabled?) + end + + context 'with embedded grafana' do + it 'returns true when embedded grafana is enabled' do + stub_application_setting(grafana_enabled: true) + + expect(subject[:grafana_link_enabled]).to eq(true) + end + + it 'returns false when embedded grafana is disabled' do + stub_application_setting(grafana_enabled: false) + + expect(subject[:grafana_link_enabled]).to eq(false) + end end context 'with existing container expiration policies' do diff --git a/spec/lib/gitlab/utils/measuring_spec.rb b/spec/lib/gitlab/utils/measuring_spec.rb index 7e9c2dff1e4..254f53f7da3 100644 --- a/spec/lib/gitlab/utils/measuring_spec.rb +++ b/spec/lib/gitlab/utils/measuring_spec.rb @@ -3,62 +3,15 @@ require 'fast_spec_helper' describe Gitlab::Utils::Measuring do - describe '.execute_with' do - let(:measurement_logger) { double(:logger) } - let(:base_log_data) do - { - class: described_class.name - } - end - let(:result_block) { 'result' } - - subject { described_class.execute_with(measurement_enabled, measurement_logger, base_log_data) { result_block } } - - context 'when measurement is enabled' do - let(:measurement_enabled) { true } - let!(:measurement) { described_class.new(logger: measurement_logger, base_log_data: base_log_data) } - - before do - allow(measurement_logger).to receive(:info) - end - - it 'measure execution with Gitlab::Utils::Measuring instance', :aggregate_failure do - expect(described_class).to receive(:new).with(logger: measurement_logger, base_log_data: base_log_data) { measurement } - expect(measurement).to receive(:with_measuring) - - subject - end - - it 'returns result from yielded block' do - is_expected.to eq(result_block) - end - end - - context 'when measurement is disabled' do - let(:measurement_enabled) { false } - - it 'does not measure service execution' do - expect(Gitlab::Utils::Measuring).not_to receive(:new) - - subject - end - - it 'returns result from yielded block' do - is_expected.to eq(result_block) - end - end - end - describe '#with_measuring' do - let(:logger) { double(:logger) } let(:base_log_data) { {} } let(:result) { "result" } before do - allow(logger).to receive(:info) + allow(ActiveSupport::Logger).to receive(:logger_outputs_to?).with(Gitlab::Utils::Measuring.logger, STDOUT).and_return(false) end - let(:measurement) { described_class.new(logger: logger, base_log_data: base_log_data) } + let(:measurement) { described_class.new(base_log_data) } subject do measurement.with_measuring { result } @@ -69,7 +22,7 @@ describe Gitlab::Utils::Measuring do expect(measurement).to receive(:with_count_queries).and_call_original expect(measurement).to receive(:with_gc_stats).and_call_original - expect(logger).to receive(:info).with(including(:gc_stats, :time_to_finish, :number_of_sql_calls, :memory_usage, :label)) + expect(described_class.logger).to receive(:info).with(include(:gc_stats, :time_to_finish, :number_of_sql_calls, :memory_usage, :label)) is_expected.to eq(result) end @@ -78,7 +31,7 @@ describe Gitlab::Utils::Measuring do let(:base_log_data) { { test: "data" } } it 'logs includes base data' do - expect(logger).to receive(:info).with(including(:test, :gc_stats, :time_to_finish, :number_of_sql_calls, :memory_usage, :label)) + expect(described_class.logger).to receive(:info).with(include(:test, :gc_stats, :time_to_finish, :number_of_sql_calls, :memory_usage, :label)) subject end diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb index 859a3cca44f..ad872b88664 100644 --- a/spec/requests/api/project_export_spec.rb +++ b/spec/requests/api/project_export_spec.rb @@ -411,7 +411,9 @@ describe API::ProjectExport, :clean_gitlab_redis_cache do it 'starts', :sidekiq_might_not_need_inline do params = { description: "Foo" } - expect_any_instance_of(Projects::ImportExport::ExportService).to receive(:execute) + expect_next_instance_of(Projects::ImportExport::ExportService) do |service| + expect(service).to receive(:execute) + end post api(path, project.owner), params: params expect(response).to have_gitlab_http_status(:accepted) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 67c258260bf..261e54da6a8 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -326,6 +326,32 @@ describe API::Runners do expect(response).to have_gitlab_http_status(:unauthorized) end end + + context 'FF hide_token_from_runners_api is enabled' do + before do + stub_feature_flags(hide_token_from_runners_api: true) + end + + it "does not return runner's token" do + get api("/runners/#{shared_runner.id}", admin) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).not_to have_key('token') + end + end + + context 'FF hide_token_from_runners_api is disabled' do + before do + stub_feature_flags(hide_token_from_runners_api: false) + end + + it "returns runner's token" do + get api("/runners/#{shared_runner.id}", admin) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to have_key('token') + end + end end describe 'PUT /runners/:id' do diff --git a/spec/services/groups/import_export/export_service_spec.rb b/spec/services/groups/import_export/export_service_spec.rb index e9e356ab4f6..b78e2f0bc40 100644 --- a/spec/services/groups/import_export/export_service_spec.rb +++ b/spec/services/groups/import_export/export_service_spec.rb @@ -49,6 +49,12 @@ describe Groups::ImportExport::ExportService do FileUtils.rm_rf(archive_path) end + it 'saves the version' do + expect(Gitlab::ImportExport::VersionSaver).to receive(:new).and_call_original + + service.execute + end + it 'saves the models using ndjson tree saver' do stub_feature_flags(group_export_ndjson: true) diff --git a/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb b/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb new file mode 100644 index 00000000000..68a2fef5931 --- /dev/null +++ b/spec/services/metrics/users_starred_dashboards/delete_service_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::UsersStarredDashboards::DeleteService do + subject(:service_instance) { described_class.new(user, project, dashboard_path) } + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + + describe '#execute' do + let_it_be(:user_starred_dashboard_1) { create(:metrics_users_starred_dashboard, user: user, project: project, dashboard_path: 'dashboard_1') } + let_it_be(:user_starred_dashboard_2) { create(:metrics_users_starred_dashboard, user: user, project: project) } + let_it_be(:other_user_starred_dashboard) { create(:metrics_users_starred_dashboard, project: project) } + let_it_be(:other_project_starred_dashboard) { create(:metrics_users_starred_dashboard, user: user) } + + context 'without dashboard_path' do + let(:dashboard_path) { nil } + + it 'does not scope user starred dashboards by dashboard path' do + result = service_instance.execute + + expect(result.success?).to be_truthy + expect(result.payload[:deleted_rows]).to be(2) + expect(Metrics::UsersStarredDashboard.all).to contain_exactly(other_user_starred_dashboard, other_project_starred_dashboard) + end + end + + context 'with dashboard_path' do + let(:dashboard_path) { 'dashboard_1' } + + it 'does scope user starred dashboards by dashboard path' do + result = service_instance.execute + + expect(result.success?).to be_truthy + expect(result.payload[:deleted_rows]).to be(1) + expect(Metrics::UsersStarredDashboard.all).to contain_exactly(user_starred_dashboard_2, other_user_starred_dashboard, other_project_starred_dashboard) + end + end + end +end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 1feea27eebc..5b0e32dcd67 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -489,6 +489,27 @@ describe Projects::CreateService, '#execute' do end end + it_behaves_like 'measurable service' do + before do + opts.merge!( + current_user: user, + path: 'foo' + ) + end + + let(:base_log_data) do + { + class: Projects::CreateService.name, + current_user: user.name, + project_full_path: "#{user.namespace.full_path}/#{opts[:path]}" + } + end + + after do + create_project(user, opts) + end + end + def create_project(user, opts) Projects::CreateService.new(user, opts).execute end diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb index 32d0b52f096..5f496cb1e56 100644 --- a/spec/services/projects/import_export/export_service_spec.rb +++ b/spec/services/projects/import_export/export_service_spec.rb @@ -7,9 +7,10 @@ describe Projects::ImportExport::ExportService do let!(:user) { create(:user) } let(:project) { create(:project) } let(:shared) { project.import_export_shared } - let(:service) { described_class.new(project, user) } let!(:after_export_strategy) { Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy.new } + subject(:service) { described_class.new(project, user) } + before do project.add_maintainer(user) end @@ -184,7 +185,7 @@ describe Projects::ImportExport::ExportService do end end - context 'when measurable params are provided' do + it_behaves_like 'measurable service' do let(:base_log_data) do { class: described_class.name, @@ -194,44 +195,8 @@ describe Projects::ImportExport::ExportService do } end - subject(:service) { described_class.new(project, user) } - - context 'when measurement is enabled' do - let(:logger) { double(:logger) } - let(:measurable_options) do - { - measurement_enabled: true, - measurement_logger: logger - } - end - - before do - allow(logger).to receive(:info) - end - - it 'measure service execution with Gitlab::Utils::Measuring' do - expect(Gitlab::Utils::Measuring).to receive(:execute_with).with(true, logger, base_log_data).and_call_original - expect_next_instance_of(Gitlab::Utils::Measuring) do |measuring| - expect(measuring).to receive(:with_measuring).and_call_original - end - - service.execute(after_export_strategy, measurable_options) - end - end - - context 'when measurement is disabled' do - let(:measurable_options) do - { - measurement_enabled: false - } - end - - it 'does not measure service execution' do - expect(Gitlab::Utils::Measuring).to receive(:execute_with).with(false, nil, base_log_data).and_call_original - expect(Gitlab::Utils::Measuring).not_to receive(:new) - - service.execute(after_export_strategy, measurable_options) - end + after do + service.execute(after_export_strategy) end end end diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index af8118f9b11..ca6750b373d 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -264,13 +264,33 @@ describe Projects::ImportService do it 'fails with port 25' do project.import_url = "https://github.com:25/vim/vim.git" - result = described_class.new(project, user).execute + result = subject.execute expect(result[:status]).to eq :error expect(result[:message]).to include('Only allowed ports are 80, 443') end end + it_behaves_like 'measurable service' do + let(:base_log_data) do + { + class: described_class.name, + current_user: user.name, + project_full_path: project.full_path, + import_type: project.import_type, + file_path: project.import_source + } + end + + before do + project.import_type = 'github' + end + + after do + subject.execute + end + end + def stub_github_omniauth_provider provider = OpenStruct.new( 'name' => 'github', diff --git a/spec/services/wiki_pages/base_service_spec.rb b/spec/services/wiki_pages/base_service_spec.rb index 4c44c195ac8..fede86a5192 100644 --- a/spec/services/wiki_pages/base_service_spec.rb +++ b/spec/services/wiki_pages/base_service_spec.rb @@ -10,7 +10,7 @@ describe WikiPages::BaseService do counter = Gitlab::UsageDataCounters::WikiPageCounter error = counter::UnknownEvent - let(:subject) { bad_service_class.new(project, user, {}) } + let(:subject) { bad_service_class.new(container: project, current_user: user) } context 'the class implements usage_counter_action incorrectly' do let(:bad_service_class) do diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb index d63d62e9492..2a17805110e 100644 --- a/spec/services/wiki_pages/create_service_spec.rb +++ b/spec/services/wiki_pages/create_service_spec.rb @@ -3,96 +3,5 @@ require 'spec_helper' describe WikiPages::CreateService do - let(:project) { create(:project, :wiki_repo) } - let(:user) { create(:user) } - let(:page_title) { 'Title' } - - let(:opts) do - { - title: page_title, - content: 'Content for wiki page', - format: 'markdown' - } - end - - subject(:service) { described_class.new(project, user, opts) } - - before do - project.add_developer(user) - end - - describe '#execute' do - it 'creates wiki page with valid attributes' do - page = service.execute - - expect(page).to be_valid - expect(page.title).to eq(opts[:title]) - expect(page.content).to eq(opts[:content]) - expect(page.format).to eq(opts[:format].to_sym) - end - - it 'executes webhooks' do - expect(service).to receive(:execute_hooks).once.with(WikiPage) - - service.execute - end - - it 'counts wiki page creation' do - counter = Gitlab::UsageDataCounters::WikiPageCounter - - expect { service.execute }.to change { counter.read(:create) }.by 1 - end - - shared_examples 'correct event created' do - it 'creates appropriate events' do - expect { service.execute }.to change { Event.count }.by 1 - - expect(Event.recent.first).to have_attributes( - action: Event::CREATED, - target: have_attributes(canonical_slug: page_title) - ) - end - end - - context 'the new page is at the top level' do - let(:page_title) { 'root-level-page' } - - include_examples 'correct event created' - end - - context 'the new page is in a subsection' do - let(:page_title) { 'subsection/page' } - - include_examples 'correct event created' - end - - context 'the feature is disabled' do - before do - stub_feature_flags(wiki_events: false) - end - - it 'does not record the activity' do - expect { service.execute }.not_to change(Event, :count) - end - end - - context 'when the options are bad' do - let(:page_title) { '' } - - it 'does not count a creation event' do - counter = Gitlab::UsageDataCounters::WikiPageCounter - - expect { service.execute }.not_to change { counter.read(:create) } - end - - it 'does not record the activity' do - expect { service.execute }.not_to change(Event, :count) - end - - it 'reports the error' do - expect(service.execute).to be_invalid - .and have_attributes(errors: be_present) - end - end - end + it_behaves_like 'WikiPages::CreateService#execute', :project end diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb index e205bedfdb9..b6fee1fd896 100644 --- a/spec/services/wiki_pages/destroy_service_spec.rb +++ b/spec/services/wiki_pages/destroy_service_spec.rb @@ -3,52 +3,5 @@ require 'spec_helper' describe WikiPages::DestroyService do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:page) { create(:wiki_page) } - - subject(:service) { described_class.new(project, user) } - - before do - project.add_developer(user) - end - - describe '#execute' do - it 'executes webhooks' do - expect(service).to receive(:execute_hooks).once.with(page) - - service.execute(page) - end - - it 'increments the delete count' do - counter = Gitlab::UsageDataCounters::WikiPageCounter - - expect { service.execute(page) }.to change { counter.read(:delete) }.by 1 - end - - it 'creates a new wiki page deletion event' do - expect { service.execute(page) }.to change { Event.count }.by 1 - - expect(Event.recent.first).to have_attributes( - action: Event::DESTROYED, - target: have_attributes(canonical_slug: page.slug) - ) - end - - it 'does not increment the delete count if the deletion failed' do - counter = Gitlab::UsageDataCounters::WikiPageCounter - - expect { service.execute(nil) }.not_to change { counter.read(:delete) } - end - end - - context 'the feature is disabled' do - before do - stub_feature_flags(wiki_events: false) - end - - it 'does not record the activity' do - expect { service.execute(page) }.not_to change(Event, :count) - end - end + it_behaves_like 'WikiPages::DestroyService#execute', :project end diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb index ece714ee8e5..ac629a96f9a 100644 --- a/spec/services/wiki_pages/update_service_spec.rb +++ b/spec/services/wiki_pages/update_service_spec.rb @@ -3,100 +3,5 @@ require 'spec_helper' describe WikiPages::UpdateService do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:page) { create(:wiki_page) } - let(:page_title) { 'New Title' } - - let(:opts) do - { - content: 'New content for wiki page', - format: 'markdown', - message: 'New wiki message', - title: page_title - } - end - - subject(:service) { described_class.new(project, user, opts) } - - before do - project.add_developer(user) - end - - describe '#execute' do - it 'updates the wiki page' do - updated_page = service.execute(page) - - expect(updated_page).to be_valid - expect(updated_page.message).to eq(opts[:message]) - expect(updated_page.content).to eq(opts[:content]) - expect(updated_page.format).to eq(opts[:format].to_sym) - expect(updated_page.title).to eq(page_title) - end - - it 'executes webhooks' do - expect(service).to receive(:execute_hooks).once.with(WikiPage) - - service.execute(page) - end - - it 'counts edit events' do - counter = Gitlab::UsageDataCounters::WikiPageCounter - - expect { service.execute page }.to change { counter.read(:update) }.by 1 - end - - shared_examples 'adds activity event' do - it 'adds a new wiki page activity event' do - expect { service.execute(page) }.to change { Event.count }.by 1 - - expect(Event.recent.first).to have_attributes( - action: Event::UPDATED, - wiki_page: page, - target_title: page.title - ) - end - end - - context 'the page is at the top level' do - let(:page_title) { 'Top level page' } - - include_examples 'adds activity event' - end - - context 'the page is in a subsection' do - let(:page_title) { 'Subsection / secondary page' } - - include_examples 'adds activity event' - end - - context 'the feature is disabled' do - before do - stub_feature_flags(wiki_events: false) - end - - it 'does not record the activity' do - expect { service.execute(page) }.not_to change(Event, :count) - end - end - - context 'when the options are bad' do - let(:page_title) { '' } - - it 'does not count an edit event' do - counter = Gitlab::UsageDataCounters::WikiPageCounter - - expect { service.execute page }.not_to change { counter.read(:update) } - end - - it 'does not record the activity' do - expect { service.execute page }.not_to change(Event, :count) - end - - it 'reports the error' do - expect(service.execute(page)).to be_invalid - .and have_attributes(errors: be_present) - end - end - end + it_behaves_like 'WikiPages::UpdateService#execute', :project end diff --git a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb index da3403ef77a..2aac7e328f0 100644 --- a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb +++ b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb @@ -53,56 +53,4 @@ RSpec.shared_examples 'gitlab projects import validations' do end end end - - context 'when measurable params are provided' do - let(:base_log_data) do - base_log_data = { - class: described_class.name, - current_user: namespace.owner.name, - project_full_path: "#{namespace.full_path}/#{path}" - } - base_log_data.merge!({ import_type: 'gitlab_project', file_path: import_params[:file].path }) if import_params[:file] - - base_log_data - end - - subject { described_class.new(namespace.owner, import_params) } - - context 'when measurement is enabled' do - let(:logger) { double(:logger) } - let(:measurable_options) do - { - measurement_enabled: true, - measurement_logger: logger - } - end - - before do - allow(logger).to receive(:info) - end - - it 'measure service execution with Gitlab::Utils::Measuring' do - expect(Gitlab::Utils::Measuring).to receive(:execute_with).with(true, logger, base_log_data).and_call_original - expect_next_instance_of(Gitlab::Utils::Measuring) do |measuring| - expect(measuring).to receive(:with_measuring).and_call_original - end - - subject.execute(measurable_options) - end - end - context 'when measurement is disabled' do - let(:measurable_options) do - { - measurement_enabled: false - } - end - - it 'does not measure service execution' do - expect(Gitlab::Utils::Measuring).to receive(:execute_with).with(false, nil, base_log_data).and_call_original - expect(Gitlab::Utils::Measuring).not_to receive(:new) - - subject.execute(measurable_options) - end - end - end end diff --git a/spec/support/shared_examples/services/measurable_service_shared_examples.rb b/spec/support/shared_examples/services/measurable_service_shared_examples.rb new file mode 100644 index 00000000000..206c25e49af --- /dev/null +++ b/spec/support/shared_examples/services/measurable_service_shared_examples.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'measurable service' do + context 'when measurement is enabled' do + let!(:measuring) { Gitlab::Utils::Measuring.new(base_log_data) } + + before do + stub_feature_flags(feature_flag => true) + end + + it 'measure service execution with Gitlab::Utils::Measuring', :aggregate_failures do + expect(Gitlab::Utils::Measuring).to receive(:new).with(base_log_data).and_return(measuring) + expect(measuring).to receive(:with_measuring).and_call_original + end + end + + context 'when measurement is disabled' do + it 'does not measure service execution' do + stub_feature_flags(feature_flag => false) + + expect(Gitlab::Utils::Measuring).not_to receive(:new) + end + end + + def feature_flag + "gitlab_service_measuring_#{described_class_name}" + end + + def described_class_name + described_class.name.underscore.tr('/', '_') + end +end diff --git a/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb new file mode 100644 index 00000000000..71bdd46572f --- /dev/null +++ b/spec/support/shared_examples/services/wiki_pages/create_service_shared_examples.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'WikiPages::CreateService#execute' do |container_type| + let(:container) { create(container_type, :wiki_repo) } + let(:user) { create(:user) } + let(:page_title) { 'Title' } + + let(:opts) do + { + title: page_title, + content: 'Content for wiki page', + format: 'markdown' + } + end + + subject(:service) { described_class.new(container: container, current_user: user, params: opts) } + + it 'creates wiki page with valid attributes' do + page = service.execute + + expect(page).to be_valid + expect(page).to be_persisted + expect(page.title).to eq(opts[:title]) + expect(page.content).to eq(opts[:content]) + expect(page.format).to eq(opts[:format].to_sym) + end + + it 'executes webhooks' do + expect(service).to receive(:execute_hooks).once.with(WikiPage) + + service.execute + end + + it 'counts wiki page creation' do + counter = Gitlab::UsageDataCounters::WikiPageCounter + + expect { service.execute }.to change { counter.read(:create) }.by 1 + end + + shared_examples 'correct event created' do + it 'creates appropriate events' do + # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/216904 + pending('group wiki support') if container_type == :group + + expect { service.execute }.to change { Event.count }.by 1 + + expect(Event.recent.first).to have_attributes( + action: Event::CREATED, + target: have_attributes(canonical_slug: page_title) + ) + end + end + + context 'the new page is at the top level' do + let(:page_title) { 'root-level-page' } + + include_examples 'correct event created' + end + + context 'the new page is in a subsection' do + let(:page_title) { 'subsection/page' } + + include_examples 'correct event created' + end + + context 'the feature is disabled' do + before do + stub_feature_flags(wiki_events: false) + end + + it 'does not record the activity' do + expect { service.execute }.not_to change(Event, :count) + end + end + + context 'when the options are bad' do + let(:page_title) { '' } + + it 'does not count a creation event' do + counter = Gitlab::UsageDataCounters::WikiPageCounter + + expect { service.execute }.not_to change { counter.read(:create) } + end + + it 'does not record the activity' do + expect { service.execute }.not_to change(Event, :count) + end + + it 'reports the error' do + expect(service.execute).to be_invalid + .and have_attributes(errors: be_present) + end + end +end diff --git a/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb new file mode 100644 index 00000000000..62541eb3da9 --- /dev/null +++ b/spec/support/shared_examples/services/wiki_pages/destroy_service_shared_examples.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'WikiPages::DestroyService#execute' do |container_type| + let(:container) { create(container_type) } + + let(:user) { create(:user) } + let(:page) { create(:wiki_page) } + + subject(:service) { described_class.new(container: container, current_user: user) } + + it 'executes webhooks' do + expect(service).to receive(:execute_hooks).once.with(page) + + service.execute(page) + end + + it 'increments the delete count' do + counter = Gitlab::UsageDataCounters::WikiPageCounter + + expect { service.execute(page) }.to change { counter.read(:delete) }.by 1 + end + + it 'creates a new wiki page deletion event' do + # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/216904 + pending('group wiki support') if container_type == :group + + expect { service.execute(page) }.to change { Event.count }.by 1 + + expect(Event.recent.first).to have_attributes( + action: Event::DESTROYED, + target: have_attributes(canonical_slug: page.slug) + ) + end + + it 'does not increment the delete count if the deletion failed' do + counter = Gitlab::UsageDataCounters::WikiPageCounter + + expect { service.execute(nil) }.not_to change { counter.read(:delete) } + end + + context 'the feature is disabled' do + before do + stub_feature_flags(wiki_events: false) + end + + it 'does not record the activity' do + expect { service.execute(page) }.not_to change(Event, :count) + end + end +end diff --git a/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb new file mode 100644 index 00000000000..0dfc99d043b --- /dev/null +++ b/spec/support/shared_examples/services/wiki_pages/update_service_shared_examples.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'WikiPages::UpdateService#execute' do |container_type| + let(:container) { create(container_type, :wiki_repo) } + + let(:user) { create(:user) } + let(:page) { create(:wiki_page) } + let(:page_title) { 'New Title' } + + let(:opts) do + { + content: 'New content for wiki page', + format: 'markdown', + message: 'New wiki message', + title: page_title + } + end + + subject(:service) { described_class.new(container: container, current_user: user, params: opts) } + + it 'updates the wiki page' do + updated_page = service.execute(page) + + expect(updated_page).to be_valid + expect(updated_page.message).to eq(opts[:message]) + expect(updated_page.content).to eq(opts[:content]) + expect(updated_page.format).to eq(opts[:format].to_sym) + expect(updated_page.title).to eq(page_title) + end + + it 'executes webhooks' do + expect(service).to receive(:execute_hooks).once.with(WikiPage) + + service.execute(page) + end + + it 'counts edit events' do + counter = Gitlab::UsageDataCounters::WikiPageCounter + + expect { service.execute page }.to change { counter.read(:update) }.by 1 + end + + shared_examples 'adds activity event' do + it 'adds a new wiki page activity event' do + # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/216904 + pending('group wiki support') if container_type == :group + + expect { service.execute(page) }.to change { Event.count }.by 1 + + expect(Event.recent.first).to have_attributes( + action: Event::UPDATED, + wiki_page: page, + target_title: page.title + ) + end + end + + context 'the page is at the top level' do + let(:page_title) { 'Top level page' } + + include_examples 'adds activity event' + end + + context 'the page is in a subsection' do + let(:page_title) { 'Subsection / secondary page' } + + include_examples 'adds activity event' + end + + context 'the feature is disabled' do + before do + stub_feature_flags(wiki_events: false) + end + + it 'does not record the activity' do + expect { service.execute(page) }.not_to change(Event, :count) + end + end + + context 'when the options are bad' do + let(:page_title) { '' } + + it 'does not count an edit event' do + counter = Gitlab::UsageDataCounters::WikiPageCounter + + expect { service.execute page }.not_to change { counter.read(:update) } + end + + it 'does not record the activity' do + expect { service.execute page }.not_to change(Event, :count) + end + + it 'reports the error' do + expect(service.execute(page)).to be_invalid + .and have_attributes(errors: be_present) + end + end +end diff --git a/spec/support/shared_examples/tasks/gitlab/import_export/measurable_shared_examples.rb b/spec/support/shared_examples/tasks/gitlab/import_export/measurable_shared_examples.rb deleted file mode 100644 index 7a81811d1f0..00000000000 --- a/spec/support/shared_examples/tasks/gitlab/import_export/measurable_shared_examples.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'measurable' do - context 'when measurement is enabled' do - let(:measurement_enabled) { true } - - it 'prints measurement results' do - expect { subject }.to output(including('time_to_finish')).to_stdout - end - end - - context 'when measurement is not enabled' do - let(:measurement_enabled) { false } - - it 'does not output measurement results' do - expect { subject }.not_to output(/time_to_finish/).to_stdout - end - end - - context 'when measurement is not provided' do - let(:measurement_enabled) { nil } - - it 'does not output measurement results' do - expect { subject }.not_to output(/time_to_finish/).to_stdout - end - - it 'does not raise any exception' do - expect { subject }.not_to raise_error - end - end -end diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb index 6adf9a1335f..4c49939d34e 100644 --- a/spec/workers/project_export_worker_spec.rb +++ b/spec/workers/project_export_worker_spec.rb @@ -24,20 +24,6 @@ describe ProjectExportWorker do subject.perform(user.id, project.id, { 'klass' => 'Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy' }) end - context 'with measurement options provided' do - it 'calls the ExportService with measurement options' do - measurement_options = { measurement_enabled: true } - params = {} - after_export_strategy = { 'klass' => 'Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy' } - - expect_next_instance_of(::Projects::ImportExport::ExportService) do |service| - expect(service).to receive(:execute).with(instance_of(Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy), measurement_options) - end - - subject.perform(user.id, project.id, after_export_strategy, params, measurement_options) - end - end - context 'export job' do before do allow_next_instance_of(::Projects::ImportExport::ExportService) do |service| @@ -69,7 +55,7 @@ describe ProjectExportWorker do context 'when it fails' do it 'does not raise an exception when strategy is invalid' do - expect_any_instance_of(::Projects::ImportExport::ExportService).not_to receive(:execute) + expect(::Projects::ImportExport::ExportService).not_to receive(:new) expect { subject.perform(user.id, project.id, { 'klass' => 'Whatever' }) }.not_to raise_error end