+
-
+
@@ -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