summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-15 15:07:43 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-15 15:07:43 +0000
commitf8a5275c45ed2276daf843764113476749e680d2 (patch)
tree4856524dbeebf0280a87ff71bcc274f496975e72
parente6fed37d941271b897d37820fd3b571feab280b0 (diff)
downloadgitlab-ce-f8a5275c45ed2276daf843764113476749e680d2.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml13
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/achievements/components/achievements_app.vue31
-rw-r--r--app/assets/javascripts/achievements/constants.js7
-rw-r--r--app/assets/javascripts/achievements/routes.js16
-rw-r--r--app/assets/javascripts/pages/groups/achievements/index.js43
-rw-r--r--app/assets/javascripts/super_sidebar/components/menu_section.vue13
-rw-r--r--app/assets/javascripts/super_sidebar/components/pinned_section.vue1
-rw-r--r--app/assets/javascripts/super_sidebar/components/sidebar_menu.vue1
-rw-r--r--app/controllers/groups/achievements_controller.rb16
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--app/views/groups/achievements/index.html.haml14
-rw-r--r--app/workers/all_queues.yml9
-rw-r--r--app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb4
-rw-r--r--app/workers/gitlab/github_import/import_release_attachments_worker.rb4
-rw-r--r--app/workers/gitlab/github_import/pull_requests/import_merged_by_worker.rb25
-rw-r--r--app/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker.rb2
-rw-r--r--config/feature_flags/development/namespace_storage_limit_bypass_date_check.yml8
-rw-r--r--config/initializers/doorkeeper_openid_connect_patch.rb23
-rw-r--r--config/routes/group.rb2
-rw-r--r--config/vue3migration/compiler.js113
-rw-r--r--data/removals/16_0/16-0-api-lint-removal.yml41
-rw-r--r--data/removals/16_0/16-0-self-monitor-removal.yml (renamed from data/deprecations/16-0-self-monitor-removal.yml)0
-rw-r--r--doc/administration/auth/ldap/ldap-troubleshooting.md2
-rw-r--r--doc/administration/logs/index.md4
-rw-r--r--doc/ci/examples/authenticating-with-hashicorp-vault/index.md67
-rw-r--r--doc/ci/yaml/yaml_optimization.md4
-rw-r--r--doc/development/database/clickhouse/index.md62
-rw-r--r--doc/development/github_importer.md2
-rw-r--r--doc/update/deprecations.md14
-rw-r--r--doc/update/removals.md16
-rw-r--r--doc/user/admin_area/index.md2
-rw-r--r--doc/user/group/settings/group_access_tokens.md10
-rw-r--r--doc/user/project/settings/project_access_tokens.md10
-rw-r--r--jest.config.base.js1
-rw-r--r--lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb69
-rw-r--r--lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer.rb59
-rw-r--r--lib/gitlab/github_import/importer/pull_requests/merged_by_importer.rb71
-rw-r--r--lib/gitlab/github_import/importer/pull_requests_merged_by_importer.rb57
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/frontend/blob/line_highlighter_spec.js6
-rw-r--r--spec/frontend/diffs/components/compare_versions_spec.js19
-rw-r--r--spec/frontend/prometheus_metrics/custom_metrics_spec.js2
-rw-r--r--spec/frontend/prometheus_metrics/prometheus_metrics_spec.js2
-rw-r--r--spec/frontend/super_sidebar/components/menu_section_spec.js13
-rw-r--r--spec/frontend/super_sidebar/components/sidebar_menu_spec.js9
-rw-r--r--spec/frontend/vue3migration/compiler_spec.js38
-rw-r--r--spec/frontend/vue3migration/components/comments_on_root_level.vue5
-rw-r--r--spec/frontend/vue3migration/components/default_slot_with_comment.vue18
-rw-r--r--spec/frontend/vue3migration/components/key_inside_template.vue7
-rw-r--r--spec/frontend/vue3migration/components/simple.vue10
-rw-r--r--spec/frontend/vue3migration/components/slot_with_comment.vue20
-rw-r--r--spec/frontend/vue3migration/components/slots_with_same_name.vue14
-rw-r--r--spec/frontend/vue3migration/components/v_once_inside_v_if.vue12
-rw-r--r--spec/frontend/vue_compat_test_setup.js2
-rw-r--r--spec/initializers/doorkeeper_openid_connect_patch_spec.rb74
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer_spec.rb (renamed from spec/lib/gitlab/github_import/importer/pull_requests_merged_by_importer_spec.rb)14
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_requests/merged_by_importer_spec.rb (renamed from spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb)14
-rw-r--r--spec/requests/groups/achievements_controller_spec.rb78
-rw-r--r--spec/serializers/import/github_failure_entity_spec.rb2
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb1
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/pull_requests/import_merged_by_worker_spec.rb19
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb2
67 files changed, 820 insertions, 415 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 45a7924fb1e..5f0b59af9d7 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1463,14 +1463,15 @@
.qa:rules:package-and-test-sidebar:
rules:
- - !reference [".qa:rules:package-and-test-common", rules]
- - <<: *if-dot-com-gitlab-org-schedule
+ - <<: *if-merge-request
+ changes: *code-patterns
+ when: manual
+ allow_failure: true
+ - <<: *if-default-branch-schedule-nightly
allow_failure: true
variables:
- SKIP_REPORT_IN_ISSUES: "true"
- PROCESS_TEST_RESULTS: "false"
- KNAPSACK_GENERATE_REPORT: "false"
- UPDATE_QA_CACHE: "false"
+ SKIP_REPORT_IN_ISSUES: "false"
+ PROCESS_TEST_RESULTS: "true"
QA_SAVE_TEST_METRICS: "true"
QA_EXPORT_TEST_METRICS: "false"
diff --git a/Gemfile b/Gemfile
index 7447bc5648e..10c4d6dc8d8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -46,7 +46,7 @@ gem 'devise', '~> 4.8.1'
gem 'devise-pbkdf2-encryptable', '~> 0.0.0', path: 'vendor/gems/devise-pbkdf2-encryptable'
gem 'bcrypt', '~> 3.1', '>= 3.1.14'
gem 'doorkeeper', '~> 5.6', '>= 5.6.6'
-gem 'doorkeeper-openid_connect', '~> 1.8', '>= 1.8.5'
+gem 'doorkeeper-openid_connect', '~> 1.8', '>= 1.8.6'
gem 'rexml', '~> 3.2.5'
gem 'ruby-saml', '~> 1.13.0'
gem 'omniauth', '~> 2.1.0'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index ee74a5c948e..4b24bc11b39 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -123,7 +123,7 @@
{"name":"docile","version":"1.4.0","platform":"ruby","checksum":"5f1734bde23721245c20c3d723e76c104208e1aa01277a69901ce770f0ebb8d3"},
{"name":"domain_name","version":"0.5.20190701","platform":"ruby","checksum":"000a600454cb4a344769b2f10b531765ea7bd3a304fe47ed12e5ca1eab969851"},
{"name":"doorkeeper","version":"5.6.6","platform":"ruby","checksum":"2344e86c77770526efcda893b5217aa13d1c7eb1b40de840b58b19eb1ff757e0"},
-{"name":"doorkeeper-openid_connect","version":"1.8.5","platform":"ruby","checksum":"d4ee57687945402843c948cee399c758cdddf04468c42b1fb02a8800dd0627f6"},
+{"name":"doorkeeper-openid_connect","version":"1.8.6","platform":"ruby","checksum":"8dc46543e697476f441496a5d465bbc68c10d052e54348cec4db06d123b1e003"},
{"name":"dotenv","version":"2.7.6","platform":"ruby","checksum":"2451ed5e8e43776d7a787e51d6f8903b98e446146c7ad143d5678cc2c409d547"},
{"name":"dry-configurable","version":"0.12.0","platform":"ruby","checksum":"87a9579a04dfbae73e401d694282800d64bbdb8631cb3e987bfb79b673df7c67"},
{"name":"dry-container","version":"0.7.2","platform":"ruby","checksum":"a071824ba3451048b23500210f96a2b9facd6e46ac687f65e49c75d18786f6da"},
diff --git a/Gemfile.lock b/Gemfile.lock
index b85499d79ec..199c806a854 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -393,7 +393,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.6.6)
railties (>= 5)
- doorkeeper-openid_connect (1.8.5)
+ doorkeeper-openid_connect (1.8.6)
doorkeeper (>= 5.5, < 5.7)
jwt (>= 2.5)
dotenv (2.7.6)
@@ -1714,7 +1714,7 @@ DEPENDENCIES
diffy (~> 3.4)
discordrb-webhooks (~> 3.4)
doorkeeper (~> 5.6, >= 5.6.6)
- doorkeeper-openid_connect (~> 1.8, >= 1.8.5)
+ doorkeeper-openid_connect (~> 1.8, >= 1.8.6)
duo_api (~> 1.3)
ed25519 (~> 1.3.0)
elasticsearch-api (= 7.13.3)
diff --git a/app/assets/javascripts/achievements/components/achievements_app.vue b/app/assets/javascripts/achievements/components/achievements_app.vue
new file mode 100644
index 00000000000..5f40231856f
--- /dev/null
+++ b/app/assets/javascripts/achievements/components/achievements_app.vue
@@ -0,0 +1,31 @@
+<script>
+export default {
+ inject: {
+ canAdminAchievement: {
+ type: Boolean,
+ required: true,
+ },
+ canAwardAchievement: {
+ type: Boolean,
+ required: true,
+ },
+ groupFullPath: {
+ type: String,
+ required: true,
+ },
+ groupId: {
+ type: Number,
+ required: true,
+ },
+ textQuery: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+};
+</script>
+
+<template>
+ <div></div>
+</template>
diff --git a/app/assets/javascripts/achievements/constants.js b/app/assets/javascripts/achievements/constants.js
new file mode 100644
index 00000000000..82a56588c96
--- /dev/null
+++ b/app/assets/javascripts/achievements/constants.js
@@ -0,0 +1,7 @@
+export const INDEX_ROUTE_NAME = 'index';
+export const NEW_ROUTE_NAME = 'new';
+export const EDIT_ROUTE_NAME = 'edit';
+export const trackViewsOptions = {
+ category: 'Achievements' /* eslint-disable-line @gitlab/require-i18n-strings */,
+ action: 'view_achievements_list',
+};
diff --git a/app/assets/javascripts/achievements/routes.js b/app/assets/javascripts/achievements/routes.js
new file mode 100644
index 00000000000..12aa17d73b6
--- /dev/null
+++ b/app/assets/javascripts/achievements/routes.js
@@ -0,0 +1,16 @@
+import { INDEX_ROUTE_NAME, NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from './constants';
+
+export default [
+ {
+ name: INDEX_ROUTE_NAME,
+ path: '/',
+ },
+ {
+ name: NEW_ROUTE_NAME,
+ path: '/new',
+ },
+ {
+ name: EDIT_ROUTE_NAME,
+ path: '/:id/edit',
+ },
+];
diff --git a/app/assets/javascripts/pages/groups/achievements/index.js b/app/assets/javascripts/pages/groups/achievements/index.js
new file mode 100644
index 00000000000..d964b0feb96
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/achievements/index.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import VueRouter from 'vue-router';
+import createDefaultClient from '~/lib/graphql';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import AchievementsApp from '~/achievements/components/achievements_app.vue';
+import routes from '~/achievements/routes';
+
+Vue.use(VueApollo);
+Vue.use(VueRouter);
+
+const init = () => {
+ const el = document.getElementById('js-achievements-app');
+
+ if (!el) {
+ return false;
+ }
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ const { basePath, viewModel } = el.dataset;
+ const provide = JSON.parse(viewModel);
+
+ const router = new VueRouter({
+ base: basePath,
+ mode: 'history',
+ routes,
+ });
+
+ return new Vue({
+ el,
+ router,
+ apolloProvider,
+ provide: convertObjectPropsToCamelCase(provide),
+ render(createElement) {
+ return createElement(AchievementsApp);
+ },
+ });
+};
+
+init();
diff --git a/app/assets/javascripts/super_sidebar/components/menu_section.vue b/app/assets/javascripts/super_sidebar/components/menu_section.vue
index b57a4e3013c..5de6e04d827 100644
--- a/app/assets/javascripts/super_sidebar/components/menu_section.vue
+++ b/app/assets/javascripts/super_sidebar/components/menu_section.vue
@@ -30,11 +30,6 @@ export default {
required: false,
default: 'div',
},
- collectionStyle: {
- type: Boolean,
- required: false,
- default: false,
- },
},
data() {
return {
@@ -94,13 +89,7 @@ export default {
</slot>
</span>
- <span
- class="gl-pr-3 gl-truncate-end gl-text-gray-900"
- :class="{
- 'gl-font-sm gl-font-weight-semibold': collectionStyle,
- }"
- data-testid="section-title"
- >
+ <span class="gl-pr-3 gl-text-gray-900 gl-truncate-end">
{{ item.title }}
</span>
diff --git a/app/assets/javascripts/super_sidebar/components/pinned_section.vue b/app/assets/javascripts/super_sidebar/components/pinned_section.vue
index 93d9cf71a18..4fc86e41ef2 100644
--- a/app/assets/javascripts/super_sidebar/components/pinned_section.vue
+++ b/app/assets/javascripts/super_sidebar/components/pinned_section.vue
@@ -70,7 +70,6 @@ export default {
:item="sectionItem"
:expanded="expanded"
:separated="true"
- collection-style
@collapse-toggle="expanded = !expanded"
>
<draggable
diff --git a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
index 055886621f1..12abd727ef0 100644
--- a/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
+++ b/app/assets/javascripts/super_sidebar/components/sidebar_menu.vue
@@ -150,7 +150,6 @@ export default {
v-for="item in nonStaticItems"
:key="item.id"
:item="item"
- :collection-style="supportsPins"
tag="li"
@pin-add="createPin"
@pin-remove="destroyPin"
diff --git a/app/controllers/groups/achievements_controller.rb b/app/controllers/groups/achievements_controller.rb
new file mode 100644
index 00000000000..52d63761819
--- /dev/null
+++ b/app/controllers/groups/achievements_controller.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Groups
+ class AchievementsController < Groups::ApplicationController
+ feature_category :user_profile
+ urgency :low
+
+ before_action :authorize_read_achievement!
+
+ private
+
+ def authorize_read_achievement!
+ render_404 unless can?(current_user, :read_achievement, group)
+ end
+ end
+end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 3c4120d899e..ad3b79b604c 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -613,8 +613,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
Feature.enabled?(:summarize_my_code_review, current_user) &&
namespace.group_namespace? &&
namespace.licensed_feature_available?(:summarize_my_mr_code_review) &&
- namespace.experiment_features_enabled &&
- namespace.third_party_ai_features_enabled &&
+ Gitlab::Llm::StageCheck.available?(namespace, :summarize_my_mr_code_review) &&
merge_request.send_to_ai?
end
end
diff --git a/app/views/groups/achievements/index.html.haml b/app/views/groups/achievements/index.html.haml
new file mode 100644
index 00000000000..d93448b430a
--- /dev/null
+++ b/app/views/groups/achievements/index.html.haml
@@ -0,0 +1,14 @@
+- breadcrumb_title _('Achievements')
+- page_title _('Achievements')
+- @content_wrapper_class = "gl-relative"
+
+= content_for :after_content do
+ #js-achievements-form-portal
+
+#js-achievements-app{ data: { base_path: group_achievements_path(@group), view_model: Gitlab::Json.generate({
+ can_admin_achievement: can?(current_user, :admin_achievement, @group),
+ can_award_achievement: can?(current_user, :award_achievement, @group),
+ group_full_path: @group.full_path,
+ group_id: @group.id,
+ text_query: params[:search]
+ }) } }
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 1149f64314e..50b301b2fc3 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1272,6 +1272,15 @@
:weight: 1
:idempotent: false
:tags: []
+- :name: github_importer:github_import_pull_requests_import_merged_by
+ :worker_name: Gitlab::GithubImport::PullRequests::ImportMergedByWorker
+ :feature_category: :importers
+ :has_external_dependencies: true
+ :urgency: :low
+ :resource_boundary: :cpu
+ :weight: 1
+ :idempotent: false
+ :tags: []
- :name: github_importer:github_import_pull_requests_import_review
:worker_name: Gitlab::GithubImport::PullRequests::ImportReviewWorker
:feature_category: :importers
diff --git a/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb b/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb
index ab0cb81249b..94472fdf6db 100644
--- a/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb
+++ b/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+# TODO: remove in 16.1 milestone
+# https://gitlab.com/gitlab-org/gitlab/-/issues/409706
module Gitlab
module GithubImport
class ImportPullRequestMergedByWorker # rubocop:disable Scalability/IdempotentWorker
@@ -12,7 +14,7 @@ module Gitlab
end
def importer_class
- Importer::PullRequestMergedByImporter
+ Importer::PullRequests::MergedByImporter
end
def object_type
diff --git a/app/workers/gitlab/github_import/import_release_attachments_worker.rb b/app/workers/gitlab/github_import/import_release_attachments_worker.rb
index bf901f2f7b8..0d3831789bf 100644
--- a/app/workers/gitlab/github_import/import_release_attachments_worker.rb
+++ b/app/workers/gitlab/github_import/import_release_attachments_worker.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-# TODO: remove in 16.X milestone
-# https://gitlab.com/gitlab-org/gitlab/-/issues/377059
+# TODO: remove in 16.1 milestone
+# https://gitlab.com/gitlab-org/gitlab/-/issues/409706
module Gitlab
module GithubImport
class ImportReleaseAttachmentsWorker # rubocop:disable Scalability/IdempotentWorker
diff --git a/app/workers/gitlab/github_import/pull_requests/import_merged_by_worker.rb b/app/workers/gitlab/github_import/pull_requests/import_merged_by_worker.rb
new file mode 100644
index 00000000000..2c9b2cdffac
--- /dev/null
+++ b/app/workers/gitlab/github_import/pull_requests/import_merged_by_worker.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubImport
+ module PullRequests
+ class ImportMergedByWorker # rubocop:disable Scalability/IdempotentWorker
+ include ObjectImporter
+
+ worker_resource_boundary :cpu
+
+ def representation_class
+ Gitlab::GithubImport::Representation::PullRequest
+ end
+
+ def importer_class
+ Importer::PullRequests::MergedByImporter
+ end
+
+ def object_type
+ :pull_request_merged_by
+ end
+ end
+ end
+ end
+end
diff --git a/app/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker.rb b/app/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker.rb
index 9b123b5776a..329bf8f84b1 100644
--- a/app/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker.rb
+++ b/app/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker.rb
@@ -15,7 +15,7 @@ module Gitlab
# client - An instance of Gitlab::GithubImport::Client.
# project - An instance of Project.
def import(client, project)
- waiter = Importer::PullRequestsMergedByImporter
+ waiter = Importer::PullRequests::AllMergedByImporter
.new(project, client)
.execute
diff --git a/config/feature_flags/development/namespace_storage_limit_bypass_date_check.yml b/config/feature_flags/development/namespace_storage_limit_bypass_date_check.yml
deleted file mode 100644
index f70e60d14b5..00000000000
--- a/config/feature_flags/development/namespace_storage_limit_bypass_date_check.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: namespace_storage_limit_bypass_date_check
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86794
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/361785
-milestone: '15.0'
-type: development
-group: group::utilization
-default_enabled: false
diff --git a/config/initializers/doorkeeper_openid_connect_patch.rb b/config/initializers/doorkeeper_openid_connect_patch.rb
deleted file mode 100644
index d61b70eaa31..00000000000
--- a/config/initializers/doorkeeper_openid_connect_patch.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-# This pulls in
-# https://github.com/doorkeeper-gem/doorkeeper-openid_connect/pull/194
-# to ensure generated `kid` values are RFC 7638-compliant.
-require 'doorkeeper/openid_connect'
-
-raise 'This patch is only needed for doorkeeper_openid_connect v1.8.5' if Doorkeeper::OpenidConnect::VERSION != '1.8.5'
-
-module Doorkeeper
- module OpenidConnect
- def self.signing_key
- key =
- if %i[HS256 HS384 HS512].include?(signing_algorithm)
- configuration.signing_key
- else
- OpenSSL::PKey.read(configuration.signing_key)
- end
-
- ::JWT::JWK.new(key, { kid_generator: JWT::JWK::Thumbprint })
- end
- end
-end
diff --git a/config/routes/group.rb b/config/routes/group.rb
index ef49e53d4d3..9b346867f78 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -158,6 +158,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resources :contacts, only: [:index, :new, :edit]
resources :organizations, only: [:index, :new, :edit]
end
+
+ resources :achievements, only: [:index, :new, :edit]
end
scope(
diff --git a/config/vue3migration/compiler.js b/config/vue3migration/compiler.js
index a2c82584227..d6b6e1e7533 100644
--- a/config/vue3migration/compiler.js
+++ b/config/vue3migration/compiler.js
@@ -2,16 +2,20 @@ const { parse, compile: compilerDomCompile } = require('@vue/compiler-dom');
const COMMENT_NODE_TYPE = 3;
-const getPropIndex = (node, prop) => node.props?.findIndex((p) => p.name === prop) ?? -1;
+const hasProp = (node, prop) => node.props?.some((p) => p.name === prop);
function modifyKeysInsideTemplateTag(templateNode) {
+ if (!templateNode.tag === 'template' || !hasProp(templateNode, 'for')) {
+ return;
+ }
+
let keyCandidate = null;
for (const node of templateNode.children) {
const keyBindingIndex = node.props
? node.props.findIndex((prop) => prop.arg && prop.arg.content === 'key')
: -1;
- if (keyBindingIndex !== -1 && getPropIndex(node, 'for') === -1) {
+ if (keyBindingIndex !== -1 && !hasProp(node, 'for')) {
if (!keyCandidate) {
keyCandidate = node.props[keyBindingIndex];
}
@@ -24,40 +28,97 @@ function modifyKeysInsideTemplateTag(templateNode) {
}
}
+function getSlotName(node) {
+ return node?.props?.find((prop) => prop.name === 'slot')?.arg?.content;
+}
+
+function filterCommentNodeAndTrailingSpace(node, idx, list) {
+ if (node.type === COMMENT_NODE_TYPE) {
+ return false;
+ }
+
+ if (node.content !== ' ') {
+ return true;
+ }
+
+ if (list[idx - 1]?.type === COMMENT_NODE_TYPE) {
+ return false;
+ }
+
+ return true;
+}
+
+function filterCommentNodes(node) {
+ const { length: originalLength } = node.children;
+ // eslint-disable-next-line no-param-reassign
+ node.children = node.children.filter(filterCommentNodeAndTrailingSpace);
+ if (node.children.length !== originalLength) {
+ // trim remaining spaces
+ while (node.children.at(-1)?.content === ' ') {
+ node.children.pop();
+ }
+ }
+}
+
+function dropVOnceForChildrenInsideVIfBecauseOfIssue7725(node) {
+ // See https://github.com/vuejs/core/issues/7725 for details
+ if (!hasProp(node, 'if')) {
+ return;
+ }
+
+ node.children?.forEach((child) => {
+ if (Array.isArray(child.props)) {
+ // eslint-disable-next-line no-param-reassign
+ child.props = child.props.filter((prop) => prop.name !== 'once');
+ }
+ });
+}
+
+function fixSameSlotsInsideTemplateFailingWhenUsingWhitespacePreserveDueToIssue6063(node) {
+ // See https://github.com/vuejs/core/issues/6063 for details
+ // eslint-disable-next-line no-param-reassign
+ node.children = node.children.filter((child, idx) => {
+ if (child.content !== ' ') {
+ // We need to drop only comment nodes
+ return true;
+ }
+
+ const previousNodeSlotName = getSlotName(node.children[idx - 1]);
+ const nextNodeSlotName = getSlotName(node.children[idx + 1]);
+
+ if (previousNodeSlotName && previousNodeSlotName === nextNodeSlotName) {
+ // We have a space beween two slot entries with same slot name, we need to drop it
+ return false;
+ }
+
+ return true;
+ });
+}
+
module.exports = {
parse,
compile(template, options) {
const rootNode = parse(template, options);
- // We do not want to switch to whitespace: collapse mode which is Vue.js 3 default
- // It will be too devastating to codebase
+ const pendingNodes = [rootNode];
+ while (pendingNodes.length) {
+ const currentNode = pendingNodes.pop();
+ if (Array.isArray(currentNode.children)) {
+ // This one will be dropped all together with compiler when we drop Vue.js 2 support
+ modifyKeysInsideTemplateTag(currentNode);
- // However, without `whitespace: condense` Vue will treat spaces between comments
- // and nodes itself as text nodes, resulting in multi-root component
- // For multi-root component passing classes / attributes fallthrough will not work
+ dropVOnceForChildrenInsideVIfBecauseOfIssue7725(currentNode);
- // See https://github.com/vuejs/core/issues/7909 for details
+ // See https://github.com/vuejs/core/issues/7909 for details
+ // However, this issue applies not only to root-level nodes
+ // But on any level comments could change slot emptiness detection
+ // so we simply drop them
+ filterCommentNodes(currentNode);
- // To fix that we simply drop all component comments only on top-level
- rootNode.children = rootNode.children.filter((n) => n.type !== COMMENT_NODE_TYPE);
+ fixSameSlotsInsideTemplateFailingWhenUsingWhitespacePreserveDueToIssue6063(currentNode);
- const pendingNodes = [rootNode];
- while (pendingNodes.length) {
- const currentNode = pendingNodes.pop();
- if (getPropIndex(currentNode, 'for') !== -1) {
- if (currentNode.tag === 'template') {
- // This one will be dropped all together with compiler when we drop Vue.js 2 support
- modifyKeysInsideTemplateTag(currentNode);
- }
-
- // This one will be dropped when https://github.com/vuejs/core/issues/7725 will be fixed
- const vOncePropIndex = getPropIndex(currentNode, 'once');
- if (vOncePropIndex !== -1) {
- currentNode.props.splice(vOncePropIndex, 1);
- }
+ currentNode.children.forEach((child) => pendingNodes.push(child));
}
-
- currentNode.children?.forEach((child) => pendingNodes.push(child));
}
return compilerDomCompile(rootNode, options);
diff --git a/data/removals/16_0/16-0-api-lint-removal.yml b/data/removals/16_0/16-0-api-lint-removal.yml
new file mode 100644
index 00000000000..86e3f70a4bf
--- /dev/null
+++ b/data/removals/16_0/16-0-api-lint-removal.yml
@@ -0,0 +1,41 @@
+# This is a template for a feature deprecation.
+#
+# Please refer to the deprecation guidelines to confirm your understanding of the
+# definitions for "Deprecation", "End of Support", and "Removal":
+# https://docs.gitlab.com/ee/development/deprecation_guidelines/#terminology
+#
+# Deprecations must be announced at least three releases prior to removal.
+# See the OPTIONAL END OF SUPPORT FIELDS section below if an End of Support period also applies.
+#
+# Breaking changes must happen in a major release.
+#
+# For more information please refer to the handbook documentation here:
+# https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations
+#
+# Please delete this line and above before submitting your merge request.
+#
+# REQUIRED FIELDS
+#
+- title: "`POST ci/lint` API endpoint removed" # (required) The name of the feature to be deprecated
+ announcement_milestone: "15.7" # (required) The milestone when this feature was first announced as deprecated.
+ removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
+ breaking_change: true # (required) If this deprecation is a breaking change, set this value to true
+ reporter: dhershkovitch # (required) GitLab username of the person reporting the deprecation
+ stage: verify # (required) String value of the stage that the feature was created in. e.g., Growth
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381669 # (required) Link to the deprecation issue in GitLab
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ The `POST ci/lint` API endpoint was deprecated in 15.7, and removed in 16.0. This endpoint did not validate the full range of CI/CD configuration options. Instead, use [`POST /projects/:id/ci/lint`](https://docs.gitlab.com/ee/api/lint.html#validate-a-ci-yaml-configuration-with-a-namespace), which properly validates CI/CD configuration.
+#
+# OPTIONAL END OF SUPPORT FIELDS
+#
+# If an End of Support period applies, the announcement should be shared with GitLab Support
+# in the `#spt_managers` channel in Slack, and mention `@gitlab-com/support` in this MR.
+#
+ end_of_support_milestone: # (optional) Use "XX.YY" format. The milestone when support for this feature will end.
+ #
+ # OTHER OPTIONAL FIELDS
+ #
+ tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
+ documentation_url: # (optional) This is a link to the current documentation page
+ image_url: # (optional) This is a link to a thumbnail image depicting the feature
+ video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
diff --git a/data/deprecations/16-0-self-monitor-removal.yml b/data/removals/16_0/16-0-self-monitor-removal.yml
index 3d97d1a417d..3d97d1a417d 100644
--- a/data/deprecations/16-0-self-monitor-removal.yml
+++ b/data/removals/16_0/16-0-self-monitor-removal.yml
diff --git a/doc/administration/auth/ldap/ldap-troubleshooting.md b/doc/administration/auth/ldap/ldap-troubleshooting.md
index 9c925e0a40e..909ab5245ca 100644
--- a/doc/administration/auth/ldap/ldap-troubleshooting.md
+++ b/doc/administration/auth/ldap/ldap-troubleshooting.md
@@ -761,7 +761,7 @@ users, [see what to do when no users are found](#no-users-are-found).
### GitLab logs
If a user account is blocked or unblocked due to the LDAP configuration, a
-message is [logged to `application.log`](../../logs/index.md#applicationlog).
+message is [logged to `application_json.log`](../../logs/index.md#application_jsonlog).
If there is an unexpected error during an LDAP lookup (configuration error,
timeout), the sign-in is rejected and a message is [logged to `production.log`](../../logs/index.md#productionlog).
diff --git a/doc/administration/logs/index.md b/doc/administration/logs/index.md
index 82675c4ce39..7fab97f76da 100644
--- a/doc/administration/logs/index.md
+++ b/doc/administration/logs/index.md
@@ -350,7 +350,9 @@ the `view_duration_s` is calculated by [`duration_s - db_duration_s`](https://gi
Therefore, `view_duration_s` can be affected by multiple different factors, like read-write
process on Redis or external HTTP, not only the serialization process.
-## `application.log`
+## `application.log` (deprecated)
+
+> [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111046) in GitLab 15.10.
Depending on your installation method, this file is located at:
diff --git a/doc/ci/examples/authenticating-with-hashicorp-vault/index.md b/doc/ci/examples/authenticating-with-hashicorp-vault/index.md
index a5289ca0a4d..f45c60bdd1f 100644
--- a/doc/ci/examples/authenticating-with-hashicorp-vault/index.md
+++ b/doc/ci/examples/authenticating-with-hashicorp-vault/index.md
@@ -29,7 +29,7 @@ You must replace the `vault.example.com` URL below with the URL of your Vault se
## How it works
-Each job has JSON Web Token (JWT) provided as CI/CD variable named `CI_JOB_JWT`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://developer.hashicorp.com/vault/docs/auth/jwt#jwt-authentication) method.
+ID tokens are JSON Web Tokens (JWTs) used for OIDC authentication with third-party services. If a job has at least one ID token defined, the `secrets` keyword automatically uses that token to authenticate with Vault.
The following fields are included in the JWT:
@@ -256,61 +256,36 @@ $ vault write auth/jwt/config \
For the full list of available configuration options, see Vault's [API documentation](https://developer.hashicorp.com/vault/api-docs/auth/jwt#configure).
-The following job, when run for the default branch, is able to read secrets under `secret/myproject/staging/`, but not the secrets under `secret/myproject/production/`:
+In GitLab, create the following [CI/CD variables](../../variables/index.md#for-a-project) to provide details about your Vault server:
-```yaml
-read_secrets:
- image: vault:latest
- script:
- # Check job's ref name
- - echo $CI_COMMIT_REF_NAME
- # and is this ref protected
- - echo $CI_COMMIT_REF_PROTECTED
- # Vault's address can be provided here or as CI/CD variable
- - export VAULT_ADDR=http://vault.example.com:8200
- # Authenticate and get token. Token expiry time and other properties can be configured
- # when configuring JWT Auth - https://developer.hashicorp.com/vault/api-docs/auth/jwt#parameters-1
- - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-staging jwt=$CI_JOB_JWT)"
- # Now use the VAULT_TOKEN to read the secret and store it in an environment variable
- - export PASSWORD="$(vault kv get -field=password secret/myproject/staging/db)"
- # Use the secret
- - echo $PASSWORD
- # This will fail because the role myproject-staging can not read secrets from secret/myproject/production/*
- - export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)"
-```
+- `VAULT_SERVER_URL` - The URL of your Vault server, for example `https://vault.example.com:8200`.
+- `VAULT_AUTH_ROLE` - Optional. The role to use when attempting to authenticate. If no role is specified, Vault uses the [default role](https://developer.hashicorp.com/vault/api-docs/auth/jwt#default_role) specified when the authentication method was configured.
+- `VAULT_AUTH_PATH` - Optional. The path where the authentication method is mounted. Default is `jwt`.
+- `VAULT_NAMESPACE` - Optional. The [Vault Enterprise namespace](https://developer.hashicorp.com/vault/docs/enterprise/namespaces) to use for reading secrets and authentication. If no namespace is specified, Vault uses the root (`/`) namespace. The setting is ignored by Vault Open Source.
-NOTE:
-If you're using a Vault instance provided by HashiCorp Cloud Platform,
-you need to export the `VAULT_NAMESPACE` variable. Its default value is `admin`.
-
-![read secrets staging example](img/vault-read-secrets-staging.png)
-
-The following job is able to authenticate using the `myproject-production` role and read secrets under `/secret/myproject/production/`:
+The following job, when run for the default branch, can read secrets under `secret/myproject/staging/`, but not the secrets under `secret/myproject/production/`:
```yaml
-read_secrets:
- image: vault:latest
+job_with_secrets:
+ id_tokens:
+ VAULT_ID_TOKEN:
+ aud: https://example.vault.com
+ secrets:
+ STAGING_DB_PASSWORD:
+ vault: secret/myproject/staging/db/password@secrets # authenticates using $VAULT_ID_TOKEN
script:
- # Check job's ref name
- - echo $CI_COMMIT_REF_NAME
- # and is this ref protected
- - echo $CI_COMMIT_REF_PROTECTED
- # Vault's address can be provided here or as CI/CD variable
- - export VAULT_ADDR=http://vault.example.com:8200
- # Authenticate and get token. Token expiry time and other properties can be configured
- # when configuring JWT Auth - https://developer.hashicorp.com/vault/api-docs/auth/jwt#parameters-1
- - export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-production jwt=$CI_JOB_JWT)"
- # Now use the VAULT_TOKEN to read the secret and store it in environment variable
- - export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)"
- # Use the secret
- - echo $PASSWORD
+ - access-staging-db.sh --token $STAGING_DB_PASSWORD
```
-![read secrets production example](img/vault-read-secrets-production.png)
+In this example:
+
+- `@secrets` - The vault name, where your Secrets Engines are enabled.
+- `secret/myproject/staging/db` - The path location of the secret in Vault.
+- `password` The field to be fetched within the referenced secret.
### Limit token access to Vault secrets
-You can control `CI_JOB_JWT` access to Vault secrets by using Vault protections
+You can control ID token access to Vault secrets by using Vault protections
and GitLab features. For example, restrict the token by:
- Using Vault [bound claims](https://developer.hashicorp.com/vault/docs/auth/jwt#bound-claims)
diff --git a/doc/ci/yaml/yaml_optimization.md b/doc/ci/yaml/yaml_optimization.md
index 07019a2776f..2cfda1116fe 100644
--- a/doc/ci/yaml/yaml_optimization.md
+++ b/doc/ci/yaml/yaml_optimization.md
@@ -65,7 +65,7 @@ test2:
`&` sets up the name of the anchor (`job_configuration`), `<<` means "merge the
given hash into the current one," and `*` includes the named anchor
-(`job_configuration` again). The expanded version of this example is:
+(`job_configuration` again). The [expanded](../pipeline_editor/index.md#view-full-configuration) version of this example is:
```yaml
.job_template:
@@ -123,7 +123,7 @@ test:mysql:
services: *mysql_configuration
```
-The expanded version is:
+The [expanded](../pipeline_editor/index.md#view-full-configuration) version is:
```yaml
.job_template:
diff --git a/doc/development/database/clickhouse/index.md b/doc/development/database/clickhouse/index.md
index a26bac261fd..032e4f5f6ee 100644
--- a/doc/development/database/clickhouse/index.md
+++ b/doc/development/database/clickhouse/index.md
@@ -83,3 +83,65 @@ Quoting the [documentation](https://clickhouse.com/docs/en/sql-reference/stateme
> If there's some aggregation in the view query, it's applied only to the batch
> of freshly inserted data. Any changes to existing data of the source table
> (like update, delete, drop a partition, etc.) do not change the materialized view.
+
+## Secure and sensible defaults
+
+ClickHouse instances should follow these security recommendations:
+
+### Users
+
+Files: `users.xml` and `config.xml`.
+
+| Topic | Security Requirement | Reason |
+| ----- | -------------------- | ------ |
+| [`user_name/password`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-namepassword) | Usernames **must not** be blank. Passwords **must** use `password_sha256_hex` and **must not** be blank. | `plaintext` and `password_double_sha1_hex` are insecure. If username isn't specified, [`default` is used with no password](https://clickhouse.com/docs/en/operations/settings/settings-users/). |
+| [`access_management`](https://clickhouse.com/docs/en/operations/settings/settings-users/#access_management-user-setting) | Use Server [configuration files](https://clickhouse.com/docs/en/operations/configuration-files) `users.xml` and `config.xml`. Avoid SQL-driven workflow. | SQL-driven workflow implies that at least one user has `access_management` which can be avoided via configuration files. These files are easier to audit and monitor too, considering that ["You can't manage the same access entity by both configuration methods simultaneously."](https://clickhouse.com/docs/en/operations/access-rights/#access-control). |
+| [`user_name/networks`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-namenetworks) | At least one of `<ip>`, `<host>`, `<host_regexp>` **must** be set. Do not use `<ip>::/0</ip>` to open access for any network. | Network controls. ([Trust cautiously](https://about.gitlab.com/handbook/security/architecture/#trust-cautiously) principle) |
+| [`user_name/profile`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-nameprofile) | Use profiles to set similar properties across multiple users and set limits (from the user interface). | [Least privilege](https://about.gitlab.com/handbook/security/architecture/#assign-the-least-privilege-possible) principle and limits. |
+| [`user_name/quota`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-namequota) | Set quotas for users whenever possible. | Limit resource usage over a period of time or track the use of resources. |
+| [`user_name/databases`](https://clickhouse.com/docs/en/operations/settings/settings-users/#user-namedatabases) | Restrict access to data, and avoid users with full access. | [Least privilege](https://about.gitlab.com/handbook/security/architecture/#assign-the-least-privilege-possible) principle. |
+
+### Network
+
+Files: `config.xml`
+
+| Topic | Security Requirement | Reason |
+| ----- | -------------------- | ------ |
+| [`mysql_port`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#server_configuration_parameters-mysql_port) | Disable MySQL access unless strictly necessary:<br/> `<!-- <mysql_port>9004</mysql_port> -->`. | Close unnecessary ports and features exposure. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth) principle) |
+| [`postgresql_port`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#server_configuration_parameters-postgresql_port) | Disable PostgreSQL access unless strictly necessary:<br/> `<!-- <mysql_port>9005</mysql_port> -->` | Close unnecessary ports and features exposure. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth) principle) |
+| [`http_port/https_port`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#http-porthttps-port) & [`tcp_port/tcp_port_secure`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#http-porthttps-port) | Configure [SSL-TLS](https://clickhouse.com/docs/en/guides/sre/configuring-ssl), and disable non SSL ports:<br/>`<!-- <http_port>8123</http_port> -->`<br/>`<!-- <tcp_port>9000</tcp_port> -->`<br/>and enable secure ports:<br/>`<https_port>8443</https_port>`<br/>`<tcp_port_secure>9440</tcp_port_secure>` | Encrypt data in transit. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth) principle) |
+| [`interserver_http_host`](https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings/#interserver-http-host) | Disable `interserver_http_host` in favor of `interserver_https_host` (`<interserver_https_port>9010</interserver_https_port>`) if ClickHouse is configured as a cluster. | Encrypt data in transit. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth) principle) |
+
+### Storage
+
+| Topic | Security Requirement | Reason |
+| ----- | -------------------- | ------ |
+| Permissions | ClickHouse runs by default with the `clickhouse` user. Running as `root` is never needed. Use the principle of least privileges for the folders: `/etc/clickhouse-server`, `/var/lib/clickhouse`, `/var/log/clickhouse-server`. These folders must belong to the `clickhouse` user and group, and no other system user must have access to them. | Default passwords, ports and rules are "open doors". ([Fail securely & use secure defaults](https://about.gitlab.com/handbook/security/architecture/#fail-securely--use-secure-defaults) principle) |
+| Encryption | Use an encrypted storage for logs and data if RED data is processed. On Kubernetes, the [StorageClass](https://kubernetes.io/docs/concepts/storage/storage-classes/) used must be encrypted. | Encrypt data at rest. ([Defense in depth](https://about.gitlab.com/handbook/security/architecture/#implement-defense-in-depth)) |
+
+### Logging
+
+| Topic | Security Requirement | Reason |
+| ----- | -------------------- | ------ |
+| `logger` | `Log` and `errorlog` **must** be defined and writable by `clickhouse`. | Make sure logs are stored. |
+| SIEM | If hosted on GitLab.com, the ClickHouse instance or cluster **must** report [logs to our SIEM](https://internal-handbook.gitlab.io/handbook/security/infrastructure_security_logging/tooling/devo/) (internal link). | [GitLab logs critical information system activity](https://about.gitlab.com/handbook/security/audit-logging-policy.html). |
+| Log sensitive data | Query masking rules **must** be used if sensitive data can be logged. See [example masking rules](#example-masking-rules). | [Column level encryption](https://clickhouse.com/docs/en/sql-reference/functions/encryption-functions/) can be used and leak sensitive data (keys) in logs. |
+
+#### Example masking rules
+
+```xml
+<query_masking_rules>
+ <rule>
+ <name>hide SSN</name>
+ <regexp>(^|\D)\d{3}-\d{2}-\d{4}($|\D)</regexp>
+ <replace>000-00-0000</replace>
+ </rule>
+ <rule>
+ <name>hide encrypt/decrypt arguments</name>
+ <regexp>
+ ((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\)
+ </regexp>
+ <replace>\1(???)</replace>
+ </rule>
+</query_masking_rules>
+```
diff --git a/doc/development/github_importer.md b/doc/development/github_importer.md
index 7f9dbf94d9b..7fc2ee3a880 100644
--- a/doc/development/github_importer.md
+++ b/doc/development/github_importer.md
@@ -81,7 +81,7 @@ This worker imports the pull requests' _merged-by_ user information. The
[_List pull requests_](https://docs.github.com/en/rest/pulls#list-pull-requests)
API doesn't provide this information. Therefore, this stage must fetch each merged pull request
individually to import this information. A
-`Gitlab::GithubImport::ImportPullRequestMergedByWorker` job is scheduled for each fetched pull
+`Gitlab::GithubImport::PullRequests::ImportMergedByWorker` job is scheduled for each fetched pull
request.
### 7. Stage::ImportPullRequestsReviewRequestsWorker
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index e92bfa96a4b..f563e16b778 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -1989,20 +1989,6 @@ For more information, refer to [security report validation](https://docs.gitlab.
<div class="deprecation breaking-change" data-milestone="16.0">
-### Self-monitoring project is removed
-
-<div class="deprecation-notes">
-- Announced in: GitLab <span class="milestone">14.9</span>
-- This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
-- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/groups/gitlab-org/-/epics/10030).
-</div>
-
-GitLab self-monitoring project was meant to enable self-hosted GitLab administrators to visualize performance metrics of GitLab within GitLab itself. This feature relied on GitLab Metrics dashboards. With metrics dashboard being removed, self-monitoring project is also removed. We recommended that self-hosted users monitor their GitLab instance with alternative visualization tools, such as Grafana.
-
-</div>
-
-<div class="deprecation breaking-change" data-milestone="16.0">
-
### Shimo integration
<div class="deprecation-notes">
diff --git a/doc/update/removals.md b/doc/update/removals.md
index 3e1fb620fcd..abe1ec6e2e6 100644
--- a/doc/update/removals.md
+++ b/doc/update/removals.md
@@ -442,6 +442,14 @@ Review the details carefully before upgrading.
Version 14.x.x [security report schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas) have been removed.
Security reports that use schema version 14.x.x will cause an error in the pipeline's **Security** tab. For more information, refer to [security report validation](https://docs.gitlab.com/ee/user/application_security/#security-report-validation).
+### Self-monitoring project is removed
+
+WARNING:
+This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
+Review the details carefully before upgrading.
+
+GitLab self-monitoring project was meant to enable self-hosted GitLab administrators to visualize performance metrics of GitLab within GitLab itself. This feature relied on GitLab Metrics dashboards. With metrics dashboard being removed, self-monitoring project is also removed. We recommended that self-hosted users monitor their GitLab instance with alternative visualization tools, such as Grafana.
+
### Starboard directive in the config for the GitLab agent for Kubernetes removed
WARNING:
@@ -539,6 +547,14 @@ The predefined CI/CD variables that start with `CI_BUILD_*` were deprecated in G
| `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` |
| `CI_BUILD_TRIGGERED` | `CI_PIPELINE_TRIGGERED` |
+### `POST ci/lint` API endpoint removed
+
+WARNING:
+This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
+Review the details carefully before upgrading.
+
+The `POST ci/lint` API endpoint was deprecated in 15.7, and removed in 16.0. This endpoint did not validate the full range of CI/CD configuration options. Instead, use [`POST /projects/:id/ci/lint`](https://docs.gitlab.com/ee/api/lint.html#validate-a-ci-yaml-configuration-with-a-namespace), which properly validates CI/CD configuration.
+
### vulnerabilityFindingDismiss GraphQL mutation
WARNING:
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index 7227da3ce0d..71c2468c97f 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -458,7 +458,7 @@ For multi-node systems we recommend ingesting the logs into services like Elasti
| Log file | Contents |
|:------------------------|:---------|
-| `application.log` | GitLab user activity |
+| `application_json.log` | GitLab user activity |
| `git_json.log` | Failed GitLab interaction with Git repositories |
| `production.log` | Requests received from Puma, and the actions taken to serve those requests |
| `sidekiq.log` | Background jobs |
diff --git a/doc/user/group/settings/group_access_tokens.md b/doc/user/group/settings/group_access_tokens.md
index be9821e1b68..4a2bfd4c4b4 100644
--- a/doc/user/group/settings/group_access_tokens.md
+++ b/doc/user/group/settings/group_access_tokens.md
@@ -50,6 +50,11 @@ configured for personal access tokens.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days and default role of Guest is populated in the UI.
> - Ability to create non-expiring group access tokens [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0.
+WARNING:
+Project access tokens are treated as [internal users](../../../development/internal_users.md).
+If an internal user creates a project access token, that token is able to access
+all projects that have visibility level set to [Internal](../../public_access.md).
+
To create a group access token:
1. On the top bar, select **Main menu > Groups** and find your group.
@@ -66,11 +71,6 @@ To create a group access token:
A group access token is displayed. Save the group access token somewhere safe. After you leave or refresh the page, you can't view it again.
-WARNING:
-Group access tokens are treated as [internal users](../../../development/internal_users.md).
-If an internal user creates a group access token, that token is able to access all
-groups that have visibility level set to [Internal](../../public_access.md).
-
## Create a group access token using Rails console
GitLab 14.6 and earlier doesn't support creating group access tokens using the UI
diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md
index a9201f57155..178500093e2 100644
--- a/doc/user/project/settings/project_access_tokens.md
+++ b/doc/user/project/settings/project_access_tokens.md
@@ -50,6 +50,11 @@ configured for personal access tokens.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days and default role of Guest is populated in the UI.
> - Ability to create non-expiring project access tokens [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/392855) in GitLab 16.0.
+WARNING:
+Project access tokens are treated as [internal users](../../../development/internal_users.md).
+If an internal user creates a project access token, that token is able to access
+all projects that have visibility level set to [Internal](../../public_access.md).
+
To create a project access token:
1. On the top bar, select **Main menu > Projects** and find your project.
@@ -66,11 +71,6 @@ To create a project access token:
A project access token is displayed. Save the project access token somewhere safe. After you leave or refresh the page, you can't view it again.
-WARNING:
-Project access tokens are treated as [internal users](../../../development/internal_users.md).
-If an internal user creates a project access token, that token is able to access
-all projects that have visibility level set to [Internal](../../public_access.md).
-
## Revoke a project access token
To revoke a project access token:
diff --git a/jest.config.base.js b/jest.config.base.js
index 3cbf2fdd61b..d11b3a5c1e6 100644
--- a/jest.config.base.js
+++ b/jest.config.base.js
@@ -51,6 +51,7 @@ module.exports = (path, options = {}) => {
experimentalCSSCompile: false,
compiler: require.resolve('./config/vue3migration/compiler'),
compilerOptions: {
+ whitespace: 'preserve',
compatConfig: {
MODE: 2,
},
diff --git a/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb b/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb
deleted file mode 100644
index 51a72a80268..00000000000
--- a/lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module GithubImport
- module Importer
- class PullRequestMergedByImporter
- # pull_request - An instance of
- # `Gitlab::GithubImport::Representation::PullRequest`
- # project - An instance of `Project`
- # client - An instance of `Gitlab::GithubImport::Client`
- def initialize(pull_request, project, client)
- @pull_request = pull_request
- @project = project
- @client = client
- end
-
- def execute
- user_finder = GithubImport::UserFinder.new(project, client)
-
- gitlab_user_id = user_finder.user_id_for(pull_request.merged_by)
-
- metrics_upsert(gitlab_user_id)
-
- add_note!
- end
-
- private
-
- attr_reader :project, :pull_request, :client
-
- def metrics_upsert(gitlab_user_id)
- MergeRequest::Metrics.upsert({
- target_project_id: project.id,
- merge_request_id: merge_request.id,
- merged_by_id: gitlab_user_id,
- merged_at: pull_request.merged_at,
- created_at: timestamp,
- updated_at: timestamp
- }, unique_by: :merge_request_id)
- end
-
- def add_note!
- merge_request.notes.create!(
- importing: true,
- note: missing_author_note,
- author_id: project.creator_id,
- project: project,
- created_at: pull_request.merged_at
- )
- end
-
- def merge_request
- @merge_request ||= project.merge_requests.find_by_iid(pull_request.iid)
- end
-
- def timestamp
- @timestamp ||= Time.new.utc
- end
-
- def missing_author_note
- s_("GitHubImporter|*Merged by: %{author} at %{timestamp}*") % {
- author: pull_request.merged_by&.login || 'ghost',
- timestamp: pull_request.merged_at
- }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer.rb b/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer.rb
new file mode 100644
index 00000000000..9aa55fd3eae
--- /dev/null
+++ b/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubImport
+ module Importer
+ module PullRequests
+ class AllMergedByImporter
+ include ParallelScheduling
+
+ def importer_class
+ MergedByImporter
+ end
+
+ def representation_class
+ Gitlab::GithubImport::Representation::PullRequest
+ end
+
+ def sidekiq_worker_class
+ Gitlab::GithubImport::PullRequests::ImportMergedByWorker
+ end
+
+ def collection_method
+ :pull_requests_merged_by
+ end
+
+ def object_type
+ :pull_request_merged_by
+ end
+
+ def id_for_already_imported_cache(merge_request)
+ merge_request.id
+ end
+
+ def each_object_to_import
+ merge_requests_to_import.find_each do |merge_request|
+ Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
+
+ pull_request = client.pull_request(project.import_source, merge_request.iid)
+ yield(pull_request)
+
+ mark_as_imported(merge_request)
+ end
+ end
+
+ private
+
+ # Returns only the merge requests that still have merged_by to be imported.
+ def merge_requests_to_import
+ project.merge_requests.id_not_in(already_imported_objects).with_state(:merged)
+ end
+
+ def already_imported_objects
+ Gitlab::Cache::Import::Caching.values_from_set(already_imported_cache_key)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer/pull_requests/merged_by_importer.rb b/lib/gitlab/github_import/importer/pull_requests/merged_by_importer.rb
new file mode 100644
index 00000000000..19880716832
--- /dev/null
+++ b/lib/gitlab/github_import/importer/pull_requests/merged_by_importer.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubImport
+ module Importer
+ module PullRequests
+ class MergedByImporter
+ # pull_request - An instance of
+ # `Gitlab::GithubImport::Representation::PullRequest`
+ # project - An instance of `Project`
+ # client - An instance of `Gitlab::GithubImport::Client`
+ def initialize(pull_request, project, client)
+ @pull_request = pull_request
+ @project = project
+ @client = client
+ end
+
+ def execute
+ user_finder = GithubImport::UserFinder.new(project, client)
+
+ gitlab_user_id = user_finder.user_id_for(pull_request.merged_by)
+
+ metrics_upsert(gitlab_user_id)
+
+ add_note!
+ end
+
+ private
+
+ attr_reader :project, :pull_request, :client
+
+ def metrics_upsert(gitlab_user_id)
+ MergeRequest::Metrics.upsert({
+ target_project_id: project.id,
+ merge_request_id: merge_request.id,
+ merged_by_id: gitlab_user_id,
+ merged_at: pull_request.merged_at,
+ created_at: timestamp,
+ updated_at: timestamp
+ }, unique_by: :merge_request_id)
+ end
+
+ def add_note!
+ merge_request.notes.create!(
+ importing: true,
+ note: missing_author_note,
+ author_id: project.creator_id,
+ project: project,
+ created_at: pull_request.merged_at
+ )
+ end
+
+ def merge_request
+ @merge_request ||= project.merge_requests.find_by_iid(pull_request.iid)
+ end
+
+ def timestamp
+ @timestamp ||= Time.new.utc
+ end
+
+ def missing_author_note
+ format(s_("GitHubImporter|*Merged by: %{author} at %{timestamp}*"),
+ author: pull_request.merged_by&.login || 'ghost',
+ timestamp: pull_request.merged_at
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer/pull_requests_merged_by_importer.rb b/lib/gitlab/github_import/importer/pull_requests_merged_by_importer.rb
deleted file mode 100644
index c56b391cbec..00000000000
--- a/lib/gitlab/github_import/importer/pull_requests_merged_by_importer.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module GithubImport
- module Importer
- class PullRequestsMergedByImporter
- include ParallelScheduling
-
- def importer_class
- PullRequestMergedByImporter
- end
-
- def representation_class
- Gitlab::GithubImport::Representation::PullRequest
- end
-
- def sidekiq_worker_class
- ImportPullRequestMergedByWorker
- end
-
- def collection_method
- :pull_requests_merged_by
- end
-
- def object_type
- :pull_request_merged_by
- end
-
- def id_for_already_imported_cache(merge_request)
- merge_request.id
- end
-
- def each_object_to_import
- merge_requests_to_import.find_each do |merge_request|
- Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
-
- pull_request = client.pull_request(project.import_source, merge_request.iid)
- yield(pull_request)
-
- mark_as_imported(merge_request)
- end
- end
-
- private
-
- # Returns only the merge requests that still have merged_by to be imported.
- def merge_requests_to_import
- project.merge_requests.id_not_in(already_imported_objects).with_state(:merged)
- end
-
- def already_imported_objects
- Gitlab::Cache::Import::Caching.values_from_set(already_imported_cache_key)
- end
- end
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 99ac0af463e..ca8143c5e3f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2375,6 +2375,9 @@ msgstr ""
msgid "AccountValidation|Verification is required to discourage and reduce the abuse on GitLab infrastructure. If you verify with a credit or debit card, %{strong_start}GitLab will not charge your card, it will only be used for validation.%{strong_end} %{learn_more_link}"
msgstr ""
+msgid "Achievements"
+msgstr ""
+
msgid "Achievements|%{namespace_full_path} awarded you the %{achievement_name} achievement"
msgstr ""
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 2808b6181f3..1f43caf37e7 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -649,6 +649,8 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do
end
it 'loads togglable usage ping payload on click', :js do
+ allow(Gitlab::Usage::ServicePingReport).to receive(:for).and_return({ uuid: '12345678', hostname: '127.0.0.1' })
+
stub_usage_data_connections
stub_database_flavor_check
diff --git a/spec/frontend/blob/line_highlighter_spec.js b/spec/frontend/blob/line_highlighter_spec.js
index 21d4e8db503..b2e1a29b84f 100644
--- a/spec/frontend/blob/line_highlighter_spec.js
+++ b/spec/frontend/blob/line_highlighter_spec.js
@@ -1,7 +1,7 @@
/* eslint-disable no-return-assign, no-new, no-underscore-dangle */
-
import $ from 'jquery';
-import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import htmlStaticLineHighlighter from 'test_fixtures_static/line_highlighter.html';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import LineHighlighter from '~/blob/line_highlighter';
import * as utils from '~/lib/utils/common_utils';
@@ -17,7 +17,7 @@ describe('LineHighlighter', () => {
};
beforeEach(() => {
- loadHTMLFixture('static/line_highlighter.html');
+ setHTMLFixture(htmlStaticLineHighlighter);
testContext.class = new LineHighlighter();
testContext.css = testContext.class.highlightLineClass;
return (testContext.spies = {
diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js
index 23da1a3601b..47a266c2e36 100644
--- a/spec/frontend/diffs/components/compare_versions_spec.js
+++ b/spec/frontend/diffs/components/compare_versions_spec.js
@@ -21,6 +21,7 @@ beforeEach(() => {
describe('CompareVersions', () => {
let wrapper;
let store;
+ let dispatchMock;
const targetBranchName = 'tmp-wine-dev';
const { commit } = getDiffWithCommit;
@@ -29,6 +30,8 @@ describe('CompareVersions', () => {
store.state.diffs.commit = { ...store.state.diffs.commit, ...commitArgs };
}
+ dispatchMock = jest.spyOn(store, 'dispatch');
+
wrapper = mount(CompareVersionsComponent, {
store,
propsData: {
@@ -146,7 +149,7 @@ describe('CompareVersions', () => {
it('renders short commit ID', () => {
expect(wrapper.text()).toContain('Viewing commit');
- expect(wrapper.text()).toContain(wrapper.vm.commit.short_id);
+ expect(wrapper.text()).toContain(commit.short_id);
});
});
@@ -204,10 +207,6 @@ describe('CompareVersions', () => {
setWindowLocation(`?commit_id=${mrCommit.id}`);
});
- beforeEach(() => {
- jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {});
- });
-
it('uses the correct href', () => {
const link = getPrevCommitNavElement();
@@ -219,7 +218,7 @@ describe('CompareVersions', () => {
link.trigger('click');
await nextTick();
- expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({
+ expect(dispatchMock).toHaveBeenCalledWith('diffs/moveToNeighboringCommit', {
direction: 'previous',
});
});
@@ -238,10 +237,6 @@ describe('CompareVersions', () => {
setWindowLocation(`?commit_id=${mrCommit.id}`);
});
- beforeEach(() => {
- jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {});
- });
-
it('uses the correct href', () => {
const link = getNextCommitNavElement();
@@ -253,7 +248,9 @@ describe('CompareVersions', () => {
link.trigger('click');
await nextTick();
- expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({ direction: 'next' });
+ expect(dispatchMock).toHaveBeenCalledWith('diffs/moveToNeighboringCommit', {
+ direction: 'next',
+ });
});
it('renders a disabled button when there is no next commit', () => {
diff --git a/spec/frontend/prometheus_metrics/custom_metrics_spec.js b/spec/frontend/prometheus_metrics/custom_metrics_spec.js
index 823caec0211..706f932aa8d 100644
--- a/spec/frontend/prometheus_metrics/custom_metrics_spec.js
+++ b/spec/frontend/prometheus_metrics/custom_metrics_spec.js
@@ -1,5 +1,5 @@
-import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html';
import MockAdapter from 'axios-mock-adapter';
+import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
diff --git a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
index bc796ac53ca..64cf69b7f5b 100644
--- a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
+++ b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
@@ -1,5 +1,5 @@
-import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html';
import MockAdapter from 'axios-mock-adapter';
+import prometheusIntegration from 'test_fixtures/integrations/prometheus/prometheus_integration.html';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
diff --git a/spec/frontend/super_sidebar/components/menu_section_spec.js b/spec/frontend/super_sidebar/components/menu_section_spec.js
index 203a65d6a0d..556e07a2e31 100644
--- a/spec/frontend/super_sidebar/components/menu_section_spec.js
+++ b/spec/frontend/super_sidebar/components/menu_section_spec.js
@@ -10,8 +10,6 @@ describe('MenuSection component', () => {
const findButton = () => wrapper.find('button');
const findCollapse = () => wrapper.getComponent(GlCollapse);
const findNavItems = () => wrapper.findAllComponents(NavItem);
- const findSectionTitle = () => wrapper.findByTestId('section-title');
-
const createWrapper = (item, otherProps) => {
wrapper = shallowMountExtended(MenuSection, {
propsData: { item, ...otherProps },
@@ -70,17 +68,6 @@ describe('MenuSection component', () => {
});
});
- describe('`collectionStyle` prop', () => {
- const newClasses = 'gl-font-sm gl-font-weight-semibold'.split(' ');
-
- it('applies new classes when using new styles', () => {
- createWrapper({ title: 'Asdf' }, { collectionStyle: true });
- const classes = findSectionTitle().classes();
-
- newClasses.forEach((newClass) => expect(classes).toContain(newClass));
- });
- });
-
describe('`separated` prop', () => {
describe('by default (false)', () => {
it('does not render a separator', () => {
diff --git a/spec/frontend/super_sidebar/components/sidebar_menu_spec.js b/spec/frontend/super_sidebar/components/sidebar_menu_spec.js
index 55483c46732..26b146f0c8b 100644
--- a/spec/frontend/super_sidebar/components/sidebar_menu_spec.js
+++ b/spec/frontend/super_sidebar/components/sidebar_menu_spec.js
@@ -1,6 +1,5 @@
import { mountExtended } from 'helpers/vue_test_utils_helper';
import SidebarMenu from '~/super_sidebar/components/sidebar_menu.vue';
-import MenuSection from '~/super_sidebar/components/menu_section.vue';
import { PANELS_WITH_PINS } from '~/super_sidebar/constants';
import { sidebarData } from '../mock_data';
@@ -102,10 +101,6 @@ describe('SidebarMenu component', () => {
'Also with subitems',
]);
});
-
- it('passes `supportsPin` to menu sections', () => {
- expect(wrapper.findAllComponents(MenuSection).at(1).props('collectionStyle')).toBe(true);
- });
});
describe('when the sidebar does not support pins', () => {
@@ -120,10 +115,6 @@ describe('SidebarMenu component', () => {
it('keeps all items as non-static', () => {
expect(wrapper.vm.nonStaticItems).toEqual(menuItems);
});
-
- it('passes `supportsPin` to menu sections', () => {
- expect(wrapper.findAllComponents(MenuSection).at(1).props('collectionStyle')).toBe(false);
- });
});
});
diff --git a/spec/frontend/vue3migration/compiler_spec.js b/spec/frontend/vue3migration/compiler_spec.js
new file mode 100644
index 00000000000..3623f69fe07
--- /dev/null
+++ b/spec/frontend/vue3migration/compiler_spec.js
@@ -0,0 +1,38 @@
+import { mount } from '@vue/test-utils';
+
+import SlotsWithSameName from './components/slots_with_same_name.vue';
+import VOnceInsideVIf from './components/v_once_inside_v_if.vue';
+import KeyInsideTemplate from './components/key_inside_template.vue';
+import CommentsOnRootLevel from './components/comments_on_root_level.vue';
+import SlotWithComment from './components/slot_with_comment.vue';
+import DefaultSlotWithComment from './components/default_slot_with_comment.vue';
+
+describe('Vue.js 3 compiler edge cases', () => {
+ it('workarounds issue #6063 when same slot is used with whitespace preserve', () => {
+ expect(() => mount(SlotsWithSameName)).not.toThrow();
+ });
+
+ it('workarounds issue #7725 when v-once is used inside v-if', () => {
+ expect(() => mount(VOnceInsideVIf)).not.toThrow();
+ });
+
+ it('renders vue.js 2 component when key is inside template', () => {
+ const wrapper = mount(KeyInsideTemplate);
+ expect(wrapper.text()).toBe('12345');
+ });
+
+ it('passes attributes to component with trailing comments on root level', () => {
+ const wrapper = mount(CommentsOnRootLevel, { propsData: { 'data-testid': 'test' } });
+ expect(wrapper.html()).toBe('<div data-testid="test"></div>');
+ });
+
+ it('treats empty slots with comments as empty', () => {
+ const wrapper = mount(SlotWithComment);
+ expect(wrapper.html()).toBe('<div>Simple</div>');
+ });
+
+ it('treats empty default slot with comments as empty', () => {
+ const wrapper = mount(DefaultSlotWithComment);
+ expect(wrapper.html()).toBe('<div>Simple</div>');
+ });
+});
diff --git a/spec/frontend/vue3migration/components/comments_on_root_level.vue b/spec/frontend/vue3migration/components/comments_on_root_level.vue
new file mode 100644
index 00000000000..78222c059d5
--- /dev/null
+++ b/spec/frontend/vue3migration/components/comments_on_root_level.vue
@@ -0,0 +1,5 @@
+<template>
+ <!-- root level comment -->
+ <div><slot></slot></div>
+ <!-- root level comment -->
+</template>
diff --git a/spec/frontend/vue3migration/components/default_slot_with_comment.vue b/spec/frontend/vue3migration/components/default_slot_with_comment.vue
new file mode 100644
index 00000000000..d2589104a5d
--- /dev/null
+++ b/spec/frontend/vue3migration/components/default_slot_with_comment.vue
@@ -0,0 +1,18 @@
+<script>
+import Simple from './simple.vue';
+
+export default {
+ components: {
+ Simple,
+ },
+};
+</script>
+<template>
+ <simple>
+ <!-- slot comment typical for gitlab-ui, for example -->
+ <!-- slot comment typical for gitlab-ui, for example -->
+ <slot></slot>
+ <!-- slot comment typical for gitlab-ui, for example -->
+ <!-- slot comment typical for gitlab-ui, for example -->
+ </simple>
+</template>
diff --git a/spec/frontend/vue3migration/components/key_inside_template.vue b/spec/frontend/vue3migration/components/key_inside_template.vue
new file mode 100644
index 00000000000..af1f46c44e6
--- /dev/null
+++ b/spec/frontend/vue3migration/components/key_inside_template.vue
@@ -0,0 +1,7 @@
+<template>
+ <div>
+ <template v-for="count in 5"
+ ><span :key="count">{{ count }}</span></template
+ >
+ </div>
+</template>
diff --git a/spec/frontend/vue3migration/components/simple.vue b/spec/frontend/vue3migration/components/simple.vue
new file mode 100644
index 00000000000..1d9854b5b4d
--- /dev/null
+++ b/spec/frontend/vue3migration/components/simple.vue
@@ -0,0 +1,10 @@
+<script>
+export default {
+ name: 'Simple',
+};
+</script>
+<template>
+ <div>
+ <slot>{{ $options.name }}</slot>
+ </div>
+</template>
diff --git a/spec/frontend/vue3migration/components/slot_with_comment.vue b/spec/frontend/vue3migration/components/slot_with_comment.vue
new file mode 100644
index 00000000000..56bb41e432f
--- /dev/null
+++ b/spec/frontend/vue3migration/components/slot_with_comment.vue
@@ -0,0 +1,20 @@
+<script>
+import Simple from './simple.vue';
+
+export default {
+ components: {
+ Simple,
+ },
+};
+</script>
+<template>
+ <simple>
+ <template #default>
+ <!-- slot comment typical for gitlab-ui, for example -->
+ <!-- slot comment typical for gitlab-ui, for example -->
+ <slot></slot>
+ <!-- slot comment typical for gitlab-ui, for example -->
+ <!-- slot comment typical for gitlab-ui, for example -->
+ </template>
+ </simple>
+</template>
diff --git a/spec/frontend/vue3migration/components/slots_with_same_name.vue b/spec/frontend/vue3migration/components/slots_with_same_name.vue
new file mode 100644
index 00000000000..37604cd9f6e
--- /dev/null
+++ b/spec/frontend/vue3migration/components/slots_with_same_name.vue
@@ -0,0 +1,14 @@
+<script>
+import Simple from './simple.vue';
+
+export default {
+ name: 'SlotsWithSameName',
+ components: { Simple },
+};
+</script>
+<template>
+ <simple>
+ <template v-if="true" #default>{{ $options.name }}</template>
+ <template v-else #default>{{ $options.name }}</template>
+ </simple>
+</template>
diff --git a/spec/frontend/vue3migration/components/v_once_inside_v_if.vue b/spec/frontend/vue3migration/components/v_once_inside_v_if.vue
new file mode 100644
index 00000000000..708aa7a96c2
--- /dev/null
+++ b/spec/frontend/vue3migration/components/v_once_inside_v_if.vue
@@ -0,0 +1,12 @@
+<script>
+export default {
+ name: 'VOnceInsideVIf',
+};
+</script>
+<template>
+ <div>
+ <template v-if="true">
+ <div v-once>{{ $options.name }}</div>
+ </template>
+ </div>
+</template>
diff --git a/spec/frontend/vue_compat_test_setup.js b/spec/frontend/vue_compat_test_setup.js
index 8c0346e6198..6eba9465c80 100644
--- a/spec/frontend/vue_compat_test_setup.js
+++ b/spec/frontend/vue_compat_test_setup.js
@@ -63,7 +63,7 @@ if (global.document) {
};
let compatH;
- Vue.config.compilerOptions.whitespace = 'condense';
+ Vue.config.compilerOptions.whitespace = 'preserve';
Vue.createApp({
compatConfig: {
MODE: 3,
diff --git a/spec/initializers/doorkeeper_openid_connect_patch_spec.rb b/spec/initializers/doorkeeper_openid_connect_patch_spec.rb
deleted file mode 100644
index c04d7d95de6..00000000000
--- a/spec/initializers/doorkeeper_openid_connect_patch_spec.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-require_relative '../../config/initializers/doorkeeper_openid_connect_patch'
-
-RSpec.describe 'doorkeeper_openid_connect_patch', feature_category: :integrations do
- describe '.signing_key' do
- let(:config) { Doorkeeper::OpenidConnect::Config.new }
-
- before do
- allow(config).to receive(:signing_key).and_return(key)
- allow(config).to receive(:signing_algorithm).and_return(algorithm)
- allow(Doorkeeper::OpenidConnect).to receive(:configuration).and_return(config)
- end
-
- context 'with RS256 algorithm' do
- let(:algorithm) { :RS256 }
- # Taken from https://github.com/doorkeeper-gem/doorkeeper-openid_connect/blob/01903c81a2b6237a3bf576ed45864f69ef20184e/spec/dummy/config/initializers/doorkeeper_openid_connect.rb#L6-L34
- let(:key) do
- <<~KEY
- -----BEGIN RSA PRIVATE KEY-----
- MIIEpgIBAAKCAQEAsjdnSA6UWUQQHf6BLIkIEUhMRNBJC1NN/pFt1EJmEiI88GS0
- ceROO5B5Ooo9Y3QOWJ/n+u1uwTHBz0HCTN4wgArWd1TcqB5GQzQRP4eYnWyPfi4C
- feqAHzQp+v4VwbcK0LW4FqtW5D0dtrFtI281FDxLhARzkhU2y7fuYhL8fVw5rUhE
- 8uwvHRZ5CEZyxf7BSHxIvOZAAymhuzNLATt2DGkDInU1BmF75tEtBJAVLzWG/j4L
- PZh1EpSdfezqaXQlcy9PJi916UzTl0P7Yy+ulOdUsMlB6yo8qKTY1+AbZ5jzneHb
- GDU/O8QjYvii1WDmJ60t0jXicmOkGrOhruOptwIDAQABAoIBAQChYNwMeu9IugJi
- NsEf4+JDTBWMRpOuRrwcpfIvQAUPrKNEB90COPvCoju0j9OxCDmpdPtq1K/zD6xx
- khlw485FVAsKufSp4+g6GJ75yT6gZtq1JtKo1L06BFFzb7uh069eeP7+wB6JxPHw
- KlAqwxvsfADhxeolQUKCTMb3Vjv/Aw2cO/nn6RAOeftw2aDmFy8Xl+oTUtSxyib0
- YCdU9cK8MxsxDdmowwHp04xRTm/wfG5hLEn7HMz1PP86iP9BiFsCqTId9dxEUTS1
- K+VAt9FbxRAq5JlBocxUMHNxLigb94Ca2FOMR7F6l/tronLfHD801YoObF0fN9qW
- Cgw4aTO5AoGBAOR79hiZVM7/l1cBid7hKSeMWKUZ/nrwJsVfNpu1H9xt9uDu+79U
- mcGfM7pm7L2qCNGg7eeWBHq2CVg/XQacRNtcTlomFrw4tDXUkFN1hE56t1iaTs9m
- dN9IDr6jFgf6UaoOxxoPT9Q1ZtO46l043Nzrkoz8cBEBaBY20bUDwCYjAoGBAMet
- tt1ImGF1cx153KbOfjl8v54VYUVkmRNZTa1E821nL/EMpoONSqJmRVsX7grLyPL1
- QyZe245NOvn63YM0ng0rn2osoKsMVJwYBEYjHL61iF6dPtW5p8FIs7auRnC3NrG0
- XxHATZ4xhHD0iIn14iXh0XIhUVk+nGktHU1gbmVdAoGBANniwKdqqS6RHKBTDkgm
- Dhnxw6MGa+CO3VpA1xGboxuRHeoY3KfzpIC5MhojBsZDvQ8zWUwMio7+w2CNZEfm
- g99wYiOjyPCLXocrAssj+Rzh97AdzuQHf5Jh4/W2Dk9jTbdPSl02ltj2Z+2lnJFz
- pWNjnqimHrSI09rDQi5NulJjAoGBAImquujVpDmNQFCSNA7NTzlTSMk09FtjgCZW
- 67cKUsqa2fLXRfZs84gD+s1TMks/NMxNTH6n57e0h3TSAOb04AM0kDQjkKJdXfhA
- lrHEg4z4m4yf3TJ9Tat09HJ+tRIBPzRFp0YVz23Btg4qifiUDdcQWdbWIb/l6vCY
- qhsu4O4BAoGBANbceYSDYRdT7a5QjJGibkC90Z3vFe4rDTBgZWg7xG0cpSU4JNg7
- SFR3PjWQyCg7aGGXiooCM38YQruACTj0IFub24MFRA4ZTXvrACvpsVokJlQiG0Z4
- tuQKYki41JvYqPobcq/rLE/AM7PKJftW35nqFuj0MrsUwPacaVwKBf5J
- -----END RSA PRIVATE KEY-----
- KEY
- end
-
- it 'returns the private key as JWK instance' do
- expect(Doorkeeper::OpenidConnect.signing_key).to be_a ::JWT::JWK::KeyBase
- expect(Doorkeeper::OpenidConnect.signing_key.kid).to eq 'IqYwZo2cE6hsyhs48cU8QHH4GanKIx0S4Dc99kgTIMA'
- end
-
- it 'matches json-jwt implementation' do
- json_jwt_key = OpenSSL::PKey::RSA.new(key).public_key.to_jwk.slice(:kty, :kid, :e, :n)
- expect(Doorkeeper::OpenidConnect.signing_key.export.sort.to_json).to eq(json_jwt_key.sort.to_json)
- end
- end
-
- context 'with HS512 algorithm' do
- let(:algorithm) { :HS512 }
- let(:key) { 'the_greatest_secret_key' }
-
- it 'returns the HMAC public key parameters' do
- expect(Doorkeeper::OpenidConnect.signing_key_normalized).to eq(
- kty: 'oct',
- kid: 'lyAW7LdxryFWQtLdgxZpOrI87APHrzJKgWLT0BkWVog'
- )
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/github_import/importer/pull_requests_merged_by_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer_spec.rb
index b6c162aafa9..8e13b35eb6b 100644
--- a/spec/lib/gitlab/github_import/importer/pull_requests_merged_by_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_requests/all_merged_by_importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do
+RSpec.describe Gitlab::GithubImport::Importer::PullRequests::AllMergedByImporter, feature_category: :importers do
let(:client) { double }
let_it_be(:project) { create(:project, import_source: 'http://somegithub.com') }
@@ -16,7 +16,11 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do
end
describe '#importer_class' do
- it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequestMergedByImporter) }
+ it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequests::MergedByImporter) }
+ end
+
+ describe '#sidekiq_worker_class' do
+ it { expect(subject.sidekiq_worker_class).to eq(Gitlab::GithubImport::PullRequests::ImportMergedByWorker) }
end
describe '#collection_method' do
@@ -24,7 +28,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do
end
describe '#id_for_already_imported_cache' do
- it { expect(subject.id_for_already_imported_cache(double(id: 1))).to eq(1) }
+ it { expect(subject.id_for_already_imported_cache(instance_double(MergeRequest, id: 1))).to eq(1) }
end
describe '#each_object_to_import', :clean_gitlab_redis_cache do
@@ -44,7 +48,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do
expect { |b| subject.each_object_to_import(&b) }
.to yield_with_args(pull_request)
- subject.each_object_to_import {}
+ subject.each_object_to_import
end
it 'skips cached merge requests' do
@@ -55,7 +59,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do
expect(client).not_to receive(:pull_request)
- subject.each_object_to_import {}
+ subject.each_object_to_import
end
end
end
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_requests/merged_by_importer_spec.rb
index 01d706beea2..25381594632 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_merged_by_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_requests/merged_by_importer_spec.rb
@@ -2,12 +2,16 @@
require 'spec_helper'
-RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::GithubImport::Importer::PullRequests::MergedByImporter,
+ :clean_gitlab_redis_cache, feature_category: :importers do
let_it_be(:merge_request) { create(:merged_merge_request) }
let(:project) { merge_request.project }
- let(:merged_at) { Time.new(2017, 1, 1, 12, 00).utc }
- let(:client_double) { double(user: { id: 999, login: 'merger', email: 'merger@email.com' } ) }
+ let(:merged_at) { Time.utc(2017, 1, 1, 12) }
+ let(:client_double) do
+ instance_double(Gitlab::GithubImport::Client, user: { id: 999, login: 'merger', email: 'merger@email.com' })
+ end
+
let(:merger_user) { { id: 999, login: 'merger' } }
let(:pull_request) do
@@ -25,7 +29,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :cle
shared_examples 'adds a note referencing the merger user' do
it 'adds a note referencing the merger user' do
expect { subject.execute }
- .to change(Note, :count).by(1)
+ .to change { Note.count }.by(1)
.and not_change(merge_request, :updated_at)
metrics = merge_request.metrics.reload
@@ -68,7 +72,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestMergedByImporter, :cle
it 'adds a note referencing the merger user' do
expect { subject.execute }
- .to change(Note, :count).by(1)
+ .to change { Note.count }.by(1)
.and not_change(merge_request, :updated_at)
metrics = merge_request.metrics.reload
diff --git a/spec/requests/groups/achievements_controller_spec.rb b/spec/requests/groups/achievements_controller_spec.rb
new file mode 100644
index 00000000000..26ca0039984
--- /dev/null
+++ b/spec/requests/groups/achievements_controller_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::AchievementsController, feature_category: :user_profile do
+ let_it_be(:user) { create(:user) }
+
+ shared_examples 'response with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'ok response with index template' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ end
+ end
+
+ shared_examples 'ok response with index template if authorized' do
+ context 'with a private group' do
+ let(:group) { create(:group, :private) }
+
+ context 'with authorized user' do
+ before do
+ group.add_guest(user)
+ sign_in(user)
+ end
+
+ it_behaves_like 'ok response with index template'
+
+ context 'when achievements ff is disabled' do
+ before do
+ stub_feature_flags(achievements: false)
+ end
+
+ it_behaves_like 'response with 404 status'
+ end
+ end
+
+ context 'with unauthorized user' do
+ before do
+ sign_in(user)
+ end
+
+ it_behaves_like 'response with 404 status'
+ end
+
+ context 'with anonymous user' do
+ it 'redirects to sign_in page' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context 'with a public group' do
+ let(:group) { create(:group, :public) }
+
+ context 'with anonymous user' do
+ it_behaves_like 'ok response with index template'
+ end
+ end
+ end
+
+ describe 'GET #index' do
+ subject { get group_achievements_path(group) }
+
+ it_behaves_like 'ok response with index template if authorized'
+ end
+end
diff --git a/spec/serializers/import/github_failure_entity_spec.rb b/spec/serializers/import/github_failure_entity_spec.rb
index 357eae91b28..0de710f22cc 100644
--- a/spec/serializers/import/github_failure_entity_spec.rb
+++ b/spec/serializers/import/github_failure_entity_spec.rb
@@ -62,7 +62,7 @@ RSpec.describe Import::GithubFailureEntity, feature_category: :importers do
end
it_behaves_like 'import failure entity' do
- let(:source) { 'Gitlab::GithubImport::Importer::PullRequestMergedByImporter' }
+ let(:source) { 'Gitlab::GithubImport::Importer::PullRequests::MergedByImporter' }
let(:title) { 'Pull request 2 merger' }
let(:provider_url) { 'https://github.com/example/repo/pull/2' }
end
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 2c2cd7e9960..9fada09263c 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -277,6 +277,7 @@ RSpec.describe 'Every Sidekiq worker', feature_category: :shared do
'Gitlab::GithubImport::ImportPullRequestReviewWorker' => 5,
'Gitlab::GithubImport::PullRequests::ImportReviewRequestWorker' => 5,
'Gitlab::GithubImport::PullRequests::ImportReviewWorker' => 5,
+ 'Gitlab::GithubImport::PullRequests::ImportMergedByWorker' => 5,
'Gitlab::GithubImport::ImportPullRequestWorker' => 5,
'Gitlab::GithubImport::RefreshImportJidWorker' => 5,
'Gitlab::GithubImport::Stage::FinishImportWorker' => 5,
diff --git a/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb
index 6f771a1b79d..4fbdfb1903f 100644
--- a/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_pull_request_merged_by_worker_spec.rb
@@ -10,6 +10,6 @@ RSpec.describe Gitlab::GithubImport::ImportPullRequestMergedByWorker, feature_ca
end
describe '#importer_class' do
- it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequestMergedByImporter) }
+ it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequests::MergedByImporter) }
end
end
diff --git a/spec/workers/gitlab/github_import/pull_requests/import_merged_by_worker_spec.rb b/spec/workers/gitlab/github_import/pull_requests/import_merged_by_worker_spec.rb
new file mode 100644
index 00000000000..23631c76a61
--- /dev/null
+++ b/spec/workers/gitlab/github_import/pull_requests/import_merged_by_worker_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::PullRequests::ImportMergedByWorker, feature_category: :importers do
+ it { is_expected.to include_module(Gitlab::GithubImport::ObjectImporter) }
+
+ describe '#representation_class' do
+ it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::PullRequest) }
+ end
+
+ describe '#importer_class' do
+ it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::PullRequests::MergedByImporter) }
+ end
+
+ describe '#object_type' do
+ it { expect(subject.object_type).to eq(:pull_request_merged_by) }
+ end
+end
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb
index 4ffc5a956a3..0debabda0cc 100644
--- a/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_merged_by_worker_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportPullRequestsMergedByWorker, fe
client = double(:client)
waiter = Gitlab::JobWaiter.new(2, '123')
- expect(Gitlab::GithubImport::Importer::PullRequestsMergedByImporter)
+ expect(Gitlab::GithubImport::Importer::PullRequests::AllMergedByImporter)
.to receive(:new)
.with(project, client)
.and_return(importer)