Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-11 00:08:21 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-11 00:08:21 +0300
commite43574ee831197b604c59b80f94e30764223e5ed (patch)
treef467f4c23cd9d151b5d54711451252de94d62879
parentd18b7dc5eea84db5008986c6879a24ad7f6462a6 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/yaml.gitlab-ci.yml2
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/invite_members/components/invite_modal_base.vue185
-rw-r--r--app/assets/javascripts/tracking/tracking.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/content_transition.vue32
-rw-r--r--app/controllers/groups/harbor/repositories_controller.rb24
-rw-r--r--app/controllers/projects/harbor/application_controller.rb22
-rw-r--r--app/controllers/projects/harbor/repositories_controller.rb11
-rw-r--r--app/models/user.rb2
-rw-r--r--app/presenters/ci/build_runner_presenter.rb57
-rw-r--r--app/services/ci/runners/unassign_runner_service.rb9
-rw-r--r--app/services/users/destroy_service.rb2
-rw-r--r--app/services/users/migrate_to_ghost_user_service.rb13
-rw-r--r--app/views/dashboard/todos/_todo.html.haml6
-rw-r--r--app/views/dashboard/todos/index.html.haml4
-rw-r--r--app/views/groups/harbor/repositories/index.html.haml9
-rw-r--r--app/views/projects/blob/_editor.html.haml3
-rw-r--r--app/views/projects/blob/_upload.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml4
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/harbor/repositories/index.html.haml9
-rw-r--r--config/feature_flags/development/harbor_registry_integration.yml (renamed from config/feature_flags/development/read_from_vulnerability_finding_evidence.yml)10
-rw-r--r--config/routes/group.rb1
-rw-r--r--config/routes/project.rb3
-rw-r--r--data/deprecations/14-7-deprecate-merged_by-api-field.yml2
-rw-r--r--data/deprecations/14-9-system_monitoring.yml6
-rw-r--r--data/deprecations/15-0-oauth.yml2
-rw-r--r--data/deprecations/distribution_deprecations_14-4.yml8
-rw-r--r--doc/ci/services/index.md4
-rw-r--r--doc/ci/yaml/artifacts_reports.md31
-rw-r--r--doc/development/performance.md26
-rw-r--r--doc/user/project/merge_requests/test_coverage_visualization.md2
-rw-r--r--doc/user/project/working_with_projects.md16
-rw-r--r--lib/api/entities/ci/job_request/artifacts.rb2
-rw-r--r--lib/atlassian/jira_connect/serializers/build_entity.rb2
-rw-r--r--lib/gitlab/ci/config/entry/reports.rb16
-rw-r--r--lib/gitlab/ci/config/entry/reports/coverage_report.rb31
-rw-r--r--lib/gitlab/config/entry/validators.rb11
-rw-r--r--lib/gitlab/database/migrations/background_migration_helpers.rb2
-rw-r--r--lib/gitlab/usage/service_ping/payload_keys_processor.rb4
-rw-r--r--lib/sidebars/groups/menus/packages_registries_menu.rb13
-rw-r--r--lib/sidebars/projects/menus/packages_registries_menu.rb13
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb13
-rw-r--r--spec/factories/ci/builds.rb16
-rw-r--r--spec/factories/merge_requests.rb4
-rw-r--r--spec/factories/sequences.rb1
-rw-r--r--spec/features/groups/navbar_spec.rb13
-rw-r--r--spec/features/projects/navbar_spec.rb13
-rw-r--r--spec/frontend/invite_members/components/invite_groups_modal_spec.js2
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js2
-rw-r--r--spec/frontend/invite_members/components/invite_modal_base_spec.js2
-rw-r--r--spec/frontend/tracking/tracking_spec.js17
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/content_transition_spec.js.snap41
-rw-r--r--spec/frontend/vue_shared/components/content_transition_spec.js109
-rw-r--r--spec/lib/atlassian/jira_connect/serializers/build_entity_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports/coverage_report_spec.rb57
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb47
-rw-r--r--spec/lib/gitlab/config/entry/validators_spec.rb43
-rw-r--r--spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb12
-rw-r--r--spec/lib/gitlab/usage/service_ping/payload_keys_processor_spec.rb8
-rw-r--r--spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb35
-rw-r--r--spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb21
-rw-r--r--spec/presenters/ci/build_runner_presenter_spec.rb60
-rw-r--r--spec/requests/api/ci/runner/jobs_request_post_spec.rb34
-rw-r--r--spec/requests/groups/harbor/repositories_controller_spec.rb69
-rw-r--r--spec/requests/projects/harbor/repositories_controller_spec.rb69
-rw-r--r--spec/services/ci/create_pipeline_service/artifacts_spec.rb67
-rw-r--r--spec/services/users/destroy_service_spec.rb14
-rw-r--r--spec/services/users/migrate_to_ghost_user_service_spec.rb7
-rw-r--r--spec/support/helpers/navbar_structure_helper.rb8
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb12
73 files changed, 833 insertions, 582 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index daccb29eba1..5ff371fbb68 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -167,6 +167,7 @@
.yaml-lint-patterns: &yaml-lint-patterns
- ".gitlab-ci.yml"
- ".gitlab/ci/**/*.yml"
+ - "data/**/*.yml"
- "lib/gitlab/ci/templates/**/*.yml"
.docs-patterns: &docs-patterns
diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml
index 218dc0a7859..606bb385325 100644
--- a/.gitlab/ci/yaml.gitlab-ci.yml
+++ b/.gitlab/ci/yaml.gitlab-ci.yml
@@ -8,6 +8,6 @@ lint-yaml:
stage: lint
needs: []
variables:
- LINT_PATHS: .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates
+ LINT_PATHS: .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates data/deprecations data/removals data/whats_new
script:
- yamllint --strict -f colored $LINT_PATHS
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 0993e77fdf7..e733be39c52 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0206458c621892e0bc247098eb6f3a21d2424477
+965c2fedd2d11761d3f0c00b13b98e3f7e50b34e
diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
index 69205d7d250..bafbe94b8bd 100644
--- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -11,6 +11,7 @@ import {
GlFormInput,
} from '@gitlab/ui';
import { sprintf } from '~/locale';
+import ContentTransition from '~/vue_shared/components/content_transition.vue';
import {
ACCESS_LEVEL,
ACCESS_EXPIRE_DATE,
@@ -20,6 +21,17 @@ import {
HEADER_CLOSE_LABEL,
} from '../constants';
+const DEFAULT_SLOT = 'default';
+const DEFAULT_SLOTS = [
+ {
+ key: DEFAULT_SLOT,
+ attributes: {
+ class: 'invite-modal-content',
+ 'data-testid': 'invite-modal-initial-content',
+ },
+ },
+];
+
export default {
components: {
GlFormGroup,
@@ -31,6 +43,7 @@ export default {
GlSprintf,
GlButton,
GlFormInput,
+ ContentTransition,
},
inheritAttrs: false,
props: {
@@ -86,6 +99,21 @@ export default {
required: false,
default: '',
},
+ submitButtonText: {
+ type: String,
+ required: false,
+ default: INVITE_BUTTON_TEXT,
+ },
+ currentSlot: {
+ type: String,
+ required: false,
+ default: DEFAULT_SLOT,
+ },
+ extraSlots: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
// Be sure to check out reset!
@@ -110,6 +138,9 @@ export default {
(key) => this.accessLevels[key] === Number(this.selectedAccessLevel),
);
},
+ contentSlots() {
+ return [...DEFAULT_SLOTS, ...(this.extraSlots || [])];
+ },
},
watch: {
selectedAccessLevel: {
@@ -148,6 +179,7 @@ export default {
READ_MORE_TEXT,
INVITE_BUTTON_TEXT,
CANCEL_BUTTON_TEXT,
+ DEFAULT_SLOT,
};
</script>
@@ -164,79 +196,96 @@ export default {
@close="reset"
@hide="reset"
>
- <div class="gl-display-flex" data-testid="modal-base-intro-text">
- <slot name="intro-text-before"></slot>
- <p>
- <gl-sprintf :message="introText">
- <template #strong="{ content }">
- <strong>{{ content }}</strong>
- </template>
- </gl-sprintf>
- </p>
- <slot name="intro-text-after"></slot>
- </div>
-
- <gl-form-group
- :invalid-feedback="invalidFeedbackMessage"
- :state="validationState"
- :description="formGroupDescription"
- data-testid="members-form-group"
+ <content-transition
+ class="gl-display-grid"
+ transition-name="invite-modal-transition"
+ :slots="contentSlots"
+ :current-slot="currentSlot"
>
- <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
- <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
- </gl-form-group>
+ <template #[$options.DEFAULT_SLOT]>
+ <div class="gl-display-flex" data-testid="modal-base-intro-text">
+ <slot name="intro-text-before"></slot>
+ <p>
+ <gl-sprintf :message="introText">
+ <template #strong="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+ <slot name="intro-text-after"></slot>
+ </div>
- <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-dropdown
- class="gl-shadow-none gl-w-full"
- data-qa-selector="access_level_dropdown"
- v-bind="$attrs"
- :text="selectedRoleName"
- >
- <template v-for="(key, item) in accessLevels">
- <gl-dropdown-item
- :key="key"
- active-class="is-active"
- is-check-item
- :is-checked="key === selectedAccessLevel"
- @click="changeSelectedItem(key)"
- >
- <div>{{ item }}</div>
- </gl-dropdown-item>
- </template>
- </gl-dropdown>
- </div>
+ <gl-form-group
+ :invalid-feedback="invalidFeedbackMessage"
+ :state="validationState"
+ :description="formGroupDescription"
+ data-testid="members-form-group"
+ >
+ <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
+ <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
+ </gl-form-group>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-sprintf :message="$options.READ_MORE_TEXT">
- <template #link="{ content }">
- <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </div>
+ <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full">
+ <gl-dropdown
+ class="gl-shadow-none gl-w-full"
+ data-qa-selector="access_level_dropdown"
+ v-bind="$attrs"
+ :text="selectedRoleName"
+ >
+ <template v-for="(key, item) in accessLevels">
+ <gl-dropdown-item
+ :key="key"
+ active-class="is-active"
+ is-check-item
+ :is-checked="key === selectedAccessLevel"
+ @click="changeSelectedItem(key)"
+ >
+ <div>{{ item }}</div>
+ </gl-dropdown-item>
+ </template>
+ </gl-dropdown>
+ </div>
- <label class="gl-mt-5 gl-display-block" for="expires_at">{{
- $options.ACCESS_EXPIRE_DATE
- }}</label>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
- <gl-datepicker
- v-model="selectedDate"
- class="gl-display-inline!"
- :min-date="minDate"
- :target="null"
- >
- <template #default="{ formattedDate }">
- <gl-form-input class="gl-w-full" :value="formattedDate" :placeholder="__(`YYYY-MM-DD`)" />
- </template>
- </gl-datepicker>
- </div>
- <slot name="form-after"></slot>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full">
+ <gl-sprintf :message="$options.READ_MORE_TEXT">
+ <template #link="{ content }">
+ <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ <label class="gl-mt-5 gl-display-block" for="expires_at">{{
+ $options.ACCESS_EXPIRE_DATE
+ }}</label>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
+ <gl-datepicker
+ v-model="selectedDate"
+ class="gl-display-inline!"
+ :min-date="minDate"
+ :target="null"
+ >
+ <template #default="{ formattedDate }">
+ <gl-form-input
+ class="gl-w-full"
+ :value="formattedDate"
+ :placeholder="__(`YYYY-MM-DD`)"
+ />
+ </template>
+ </gl-datepicker>
+ </div>
+ <slot name="form-after"></slot>
+ </template>
+ <template v-for="{ key } in extraSlots" #[key]>
+ <slot :name="key"></slot>
+ </template>
+ </content-transition>
<template #modal-footer>
- <gl-button data-testid="cancel-button" @click="closeModal">
- {{ $options.CANCEL_BUTTON_TEXT }}
- </gl-button>
+ <slot name="cancel-button">
+ <gl-button data-testid="cancel-button" @click="closeModal">
+ {{ $options.CANCEL_BUTTON_TEXT }}
+ </gl-button>
+ </slot>
<gl-button
:disabled="submitDisabled"
:loading="isLoading"
@@ -245,7 +294,7 @@ export default {
data-testid="invite-button"
@click="submit"
>
- {{ $options.INVITE_BUTTON_TEXT }}
+ {{ submitButtonText }}
</gl-button>
</template>
</gl-modal>
diff --git a/app/assets/javascripts/tracking/tracking.js b/app/assets/javascripts/tracking/tracking.js
index c26abc261ed..173eef0646b 100644
--- a/app/assets/javascripts/tracking/tracking.js
+++ b/app/assets/javascripts/tracking/tracking.js
@@ -10,6 +10,8 @@ import {
addReferrersCacheEntry,
} from './utils';
+const ALLOWED_URL_HASHES = ['#diff', '#note'];
+
export default class Tracking {
static queuedEvents = [];
static initialized = false;
@@ -183,7 +185,9 @@ export default class Tracking {
originalUrl: window.location.href,
});
- window.snowplow('setCustomUrl', pageLinks.url);
+ const appendHash = ALLOWED_URL_HASHES.some((prefix) => window.location.hash.startsWith(prefix));
+ const customUrl = `${pageUrl}${appendHash ? window.location.hash : ''}`;
+ window.snowplow('setCustomUrl', customUrl);
if (document.referrer) {
const node = referrers.find((links) => links.originalUrl === document.referrer);
diff --git a/app/assets/javascripts/vue_shared/components/content_transition.vue b/app/assets/javascripts/vue_shared/components/content_transition.vue
new file mode 100644
index 00000000000..446610d6b91
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_transition.vue
@@ -0,0 +1,32 @@
+<script>
+export default {
+ props: {
+ currentSlot: {
+ type: String,
+ required: true,
+ },
+ slots: {
+ type: Array,
+ required: true,
+ },
+ transitionName: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ shouldShow(key) {
+ return this.currentSlot === key;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <transition v-for="{ key, attributes } in slots" :key="key" :name="transitionName">
+ <div v-show="shouldShow(key)" v-bind="attributes">
+ <slot :name="key"></slot>
+ </div>
+ </transition>
+ </div>
+</template>
diff --git a/app/controllers/groups/harbor/repositories_controller.rb b/app/controllers/groups/harbor/repositories_controller.rb
new file mode 100644
index 00000000000..364607f9b20
--- /dev/null
+++ b/app/controllers/groups/harbor/repositories_controller.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Groups
+ module Harbor
+ class RepositoriesController < Groups::ApplicationController
+ feature_category :integrations
+
+ before_action :harbor_registry_enabled!
+ before_action do
+ push_frontend_feature_flag(:harbor_registry_integration)
+ end
+
+ def show
+ render :index
+ end
+
+ private
+
+ def harbor_registry_enabled!
+ render_404 unless Feature.enabled?(:harbor_registry_integration)
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/harbor/application_controller.rb b/app/controllers/projects/harbor/application_controller.rb
new file mode 100644
index 00000000000..e6e694783fa
--- /dev/null
+++ b/app/controllers/projects/harbor/application_controller.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Projects
+ module Harbor
+ class ApplicationController < Projects::ApplicationController
+ layout 'project'
+
+ before_action :harbor_registry_enabled!
+ before_action do
+ push_frontend_feature_flag(:harbor_registry_integration)
+ end
+
+ feature_category :integrations
+
+ private
+
+ def harbor_registry_enabled!
+ render_404 unless Feature.enabled?(:harbor_registry_integration)
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/harbor/repositories_controller.rb b/app/controllers/projects/harbor/repositories_controller.rb
new file mode 100644
index 00000000000..dd3e3dc1978
--- /dev/null
+++ b/app/controllers/projects/harbor/repositories_controller.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Projects
+ module Harbor
+ class RepositoriesController < ::Projects::Harbor::ApplicationController
+ def show
+ render :index
+ end
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index e49a39079ef..fa58455ad35 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -391,7 +391,7 @@ class User < ApplicationRecord
# rubocop: disable CodeReuse/ServiceClass
# Ideally we should not call a service object here but user.block
- # is also bcalled by Users::MigrateToGhostUserService which references
+ # is also called by Users::MigrateToGhostUserService which references
# this state transition object in order to do a rollback.
# For this reason the tradeoff is to disable this cop.
after_transition any => :blocked do |user|
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 015dfc16df0..082993130a1 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -64,50 +64,35 @@ module Ci
def create_archive(artifacts)
return unless artifacts[:untracked] || artifacts[:paths]
- BuildArtifact.for_archive(artifacts).to_h.tap do |artifact|
- artifact.delete(:exclude) unless artifact[:exclude].present?
+ archive = {
+ artifact_type: :archive,
+ artifact_format: :zip,
+ name: artifacts[:name],
+ untracked: artifacts[:untracked],
+ paths: artifacts[:paths],
+ when: artifacts[:when],
+ expire_in: artifacts[:expire_in]
+ }
+
+ if artifacts.dig(:exclude).present?
+ archive.merge(exclude: artifacts[:exclude])
+ else
+ archive
end
end
def create_reports(reports, expire_in:)
return unless reports&.any?
- reports.map { |report| BuildArtifact.for_report(report, expire_in).to_h.compact }
- end
-
- BuildArtifact = Struct.new(:name, :untracked, :paths, :exclude, :when, :expire_in, :artifact_type, :artifact_format, keyword_init: true) do
- def self.for_archive(artifacts)
- self.new(
- artifact_type: :archive,
- artifact_format: :zip,
- name: artifacts[:name],
- untracked: artifacts[:untracked],
- paths: artifacts[:paths],
- when: artifacts[:when],
- expire_in: artifacts[:expire_in],
- exclude: artifacts[:exclude]
- )
- end
-
- def self.for_report(report, expire_in)
- type, params = report
-
- if type == :coverage_report
- artifact_type = params[:coverage_format].to_sym
- paths = [params[:path]]
- else
- artifact_type = type
- paths = params
- end
-
- self.new(
- artifact_type: artifact_type,
- artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(artifact_type),
- name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(artifact_type),
- paths: paths,
+ reports.map do |report_type, report_paths|
+ {
+ artifact_type: report_type.to_sym,
+ artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(report_type.to_sym),
+ name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(report_type.to_sym),
+ paths: report_paths,
when: 'always',
expire_in: expire_in
- )
+ }
end
end
diff --git a/app/services/ci/runners/unassign_runner_service.rb b/app/services/ci/runners/unassign_runner_service.rb
index a38a4196021..1e46cf6add8 100644
--- a/app/services/ci/runners/unassign_runner_service.rb
+++ b/app/services/ci/runners/unassign_runner_service.rb
@@ -6,8 +6,9 @@ module Ci
# @param [Ci::RunnerProject] runner_project the runner/project association to destroy
# @param [User] user the user performing the operation
def initialize(runner_project, user)
- @runner = runner_project.runner
@runner_project = runner_project
+ @runner = runner_project.runner
+ @project = runner_project.project
@user = user
end
@@ -16,6 +17,12 @@ module Ci
@runner_project.destroy
end
+
+ private
+
+ attr_reader :runner, :project, :user
end
end
end
+
+Ci::Runners::UnassignRunnerService.prepend_mod
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index 4ec875098fa..46eec082125 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -54,7 +54,7 @@ module Users
yield(user) if block_given?
- MigrateToGhostUserService.new(user).execute unless options[:hard_delete]
+ MigrateToGhostUserService.new(user).execute(hard_delete: options[:hard_delete])
response = Snippets::BulkDestroyService.new(current_user, user.snippets).execute(options)
raise DestroyError, response.message if response.error?
diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb
index 515d7821416..575614e8743 100644
--- a/app/services/users/migrate_to_ghost_user_service.rb
+++ b/app/services/users/migrate_to_ghost_user_service.rb
@@ -10,14 +10,21 @@ module Users
class MigrateToGhostUserService
extend ActiveSupport::Concern
- attr_reader :ghost_user, :user
+ attr_reader :ghost_user, :user, :hard_delete
def initialize(user)
@user = user
@ghost_user = User.ghost
end
- def execute
+ # If an admin attempts to hard delete a user, in some cases associated
+ # records may have a NOT NULL constraint on the user ID that prevent that record
+ # from being destroyed. In such situations we must assign the record to the ghost user.
+ # Passing in `hard_delete: true` will ensure these records get assigned to
+ # the ghost user before the user is destroyed. Other associated records will be destroyed.
+ # letting the other associated records be destroyed.
+ def execute(hard_delete: false)
+ @hard_delete = hard_delete
transition = user.block_transition
# Block the user before moving records to prevent a data race.
@@ -46,6 +53,8 @@ module Users
private
def migrate_records
+ return if hard_delete
+
migrate_issues
migrate_merge_requests
migrate_notes
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 2c6c721a51c..766900b7a99 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -50,12 +50,12 @@
.todo-actions.gl-ml-3
- if todo.pending?
= link_to dashboard_todo_path(todo), method: :delete, class: 'gl-button btn btn-default btn-loading d-flex align-items-center js-done-todo', data: { href: dashboard_todo_path(todo) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
Done
- %span.gl-spinner.ml-1
= link_to restore_dashboard_todo_path(todo), method: :patch, class: 'gl-button btn btn-default btn-loading d-flex align-items-center js-undo-todo hidden', data: { href: restore_dashboard_todo_path(todo) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
Undo
- %span.gl-spinner.ml-1
- else
= link_to restore_dashboard_todo_path(todo), method: :patch, class: 'gl-button btn btn-default btn-loading d-flex align-items-center js-add-todo', data: { href: restore_dashboard_todo_path(todo) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
Add a to do
- %span.gl-spinner.ml-1
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index f6dc62e1d44..7a329fb34a9 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -22,11 +22,11 @@
- if @allowed_todos.any?(&:pending?)
.gl-mr-3
= link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'gl-button btn btn-default btn-loading align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
= s_("Todos|Mark all as done")
- %span.gl-spinner.ml-1
= link_to bulk_restore_dashboard_todos_path, class: 'gl-button btn btn-default btn-loading align-items-center js-todos-undo-all hidden', method: :patch , data: { href: bulk_restore_dashboard_todos_path(todos_filter_params) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
= s_("Todos|Undo mark all as done")
- %span.gl-spinner.ml-1
.todos-filters
.issues-details-filters.row-content-block.second-block
diff --git a/app/views/groups/harbor/repositories/index.html.haml b/app/views/groups/harbor/repositories/index.html.haml
new file mode 100644
index 00000000000..1ee15557e21
--- /dev/null
+++ b/app/views/groups/harbor/repositories/index.html.haml
@@ -0,0 +1,9 @@
+- page_title _("Harbor Registry")
+- @content_class = "limit-container-width" unless fluid_layout
+
+#js-harbor-registry-list-group{ data: { endpoint: group_harbor_registries_path(@group),
+ "no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
+ "containers_error_image" => image_path('illustrations/docker-error-state.svg'),
+ "help_page_path" => help_page_path('user/packages/container_registry/index'),
+ connection_error: (!!@connection_error).to_s,
+ invalid_path_error: (!!@invalid_path_error).to_s, } }
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 41333c416de..c9303e19d5d 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -45,5 +45,4 @@
- if local_assigns[:path]
.js-edit-mode-pane#preview.hide
.center
- %h2
- %i.icon-spinner.icon-spin
+ = gl_loading_icon(size: 'lg')
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 6d2751bb7d4..1d3bec1ad44 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -20,8 +20,8 @@
= render 'shared/new_commit_form', placeholder: placeholder, ref: local_assigns[:ref]
.form-actions
- = button_tag class: 'btn gl-button btn-confirm btn-upload-file', id: 'submit-all', type: 'button' do
- .gl-spinner.gl-mr-2.js-loading-icon.hidden
+ = button_tag class: 'btn gl-button btn-confirm btn-upload-file gl-mr-2', id: 'submit-all', type: 'button' do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2 js-loading-icon hidden')
= button_title
= link_to _("Cancel"), '#', class: "btn gl-button btn-default btn-cancel", "data-dismiss" => "modal"
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 36d3520cb59..a3343aa4228 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -37,13 +37,13 @@
- @commit.parents.each do |parent|
= link_to parent.short_id, project_commit_path(@project, parent), class: "commit-sha"
.commit-info.branches
- .gl-spinner.vertical-align-middle
+ = gl_loading_icon(inline: true, css_class: 'gl-vertical-align-middle')
.well-segment.merge-request-info
.icon-container
= custom_icon('mr_bold')
%span.commit-info.merge-requests{ 'data-project-commit-path' => merge_requests_project_commit_path(@project, @commit.id, format: :json) }
- .gl-spinner.vertical-align-middle
+ = gl_loading_icon(inline: true, css_class: 'gl-vertical-align-middle')
- if can?(current_user, :read_pipeline, @last_pipeline)
.well-segment.pipeline-info
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f32514141c5..0d1b838906c 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -107,6 +107,6 @@
.save-project-loader.hide
.center
%h2
- .gl-spinner.gl-spinner-md.align-text-bottom
+ = gl_loading_icon(inline: true, size: 'md', css_class: 'gl-vertical-align-middle')
= _('Saving project.')
%p= _('Please wait a moment, this page will automatically refresh when ready.')
diff --git a/app/views/projects/harbor/repositories/index.html.haml b/app/views/projects/harbor/repositories/index.html.haml
new file mode 100644
index 00000000000..b3f5b91596d
--- /dev/null
+++ b/app/views/projects/harbor/repositories/index.html.haml
@@ -0,0 +1,9 @@
+- page_title _("Harbor Registry")
+- @content_class = "limit-container-width" unless fluid_layout
+
+#js-harbor-registry-list-project{ data: { endpoint: project_harbor_registry_index_path(@project),
+ "no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
+ "containers_error_image" => image_path('illustrations/docker-error-state.svg'),
+ "help_page_path" => help_page_path('user/packages/container_registry/index'),
+ connection_error: (!!@connection_error).to_s,
+ invalid_path_error: (!!@invalid_path_error).to_s, } }
diff --git a/config/feature_flags/development/read_from_vulnerability_finding_evidence.yml b/config/feature_flags/development/harbor_registry_integration.yml
index 076339c4f32..84d9709ca30 100644
--- a/config/feature_flags/development/read_from_vulnerability_finding_evidence.yml
+++ b/config/feature_flags/development/harbor_registry_integration.yml
@@ -1,8 +1,8 @@
---
-name: read_from_vulnerability_finding_evidence
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79883
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352632
-milestone: '14.8'
+name: harbor_registry_integration
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81593
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353595
+milestone: '14.9'
type: development
-group: group::threat insights
+group: group::package
default_enabled: false
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 41a165ab6e6..fecd3135cba 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -118,6 +118,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
end
resources :container_registries, only: [:index, :show], controller: 'registry/repositories'
+ resources :harbor_registries, only: [:index, :show], controller: 'harbor/repositories'
resource :dependency_proxy, only: [:show, :update]
resources :email_campaigns, only: :index
diff --git a/config/routes/project.rb b/config/routes/project.rb
index ccead5376fe..eae30d3408a 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -545,6 +545,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :container_registry, only: [:index, :destroy, :show], # rubocop: disable Cop/PutProjectRoutesUnderScope
controller: 'registry/repositories'
+ resources :harbor_registry, only: [:index, :show], # rubocop: disable Cop/PutProjectRoutesUnderScope
+ controller: 'harbor/repositories'
+
namespace :registry do
resources :repository, only: [] do # rubocop: disable Cop/PutProjectRoutesUnderScope
# We default to JSON format in the controller to avoid ambiguity.
diff --git a/data/deprecations/14-7-deprecate-merged_by-api-field.yml b/data/deprecations/14-7-deprecate-merged_by-api-field.yml
index cca006fc564..0a84b118981 100644
--- a/data/deprecations/14-7-deprecate-merged_by-api-field.yml
+++ b/data/deprecations/14-7-deprecate-merged_by-api-field.yml
@@ -19,7 +19,7 @@
body: | # Do not modify this line, instead modify the lines below.
The `merged_by` field in the [merge request API](https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-requests) is being deprecated and will be removed in GitLab 15.0. This field is being replaced with the `merge_user` field (already present in GraphQL) which more correctly identifies who merged a merge request when performing actions (merge when pipeline succeeds, add to merge train) other than a simple merge.
# The following items are not published on the docs page, but may be used in the future.
- stage: create # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
+ stage: create # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
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]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350534 # (optional) This is a link to the deprecation issue in GitLab
documentation_url: # (optional) This is a link to the current documentation page
diff --git a/data/deprecations/14-9-system_monitoring.yml b/data/deprecations/14-9-system_monitoring.yml
index 09901d9f0e8..6c8dc61db4d 100644
--- a/data/deprecations/14-9-system_monitoring.yml
+++ b/data/deprecations/14-9-system_monitoring.yml
@@ -2,13 +2,13 @@
announcement_milestone: "14.9" # The milestone when this feature was first announced as deprecated.
announcement_date: "2022-03-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed
- removal_date: "2022-05-22" # The date of the milestone release when this feature is planned to be removed. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
+ removal_date: "2022-05-22" # The date of the milestone release when this feature is planned to be removed. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
breaking_change: true # If this deprecation is a breaking change, set this value to true
reporter: abellucci # GitLab username of the person reporting the deprecation
body: | # Do not modify this line, instead modify the lines below.
GitLab self-monitoring gives administrators of self-hosted GitLab instances the tools to monitor the health of their instances. This feature is deprecated in GitLab 14.9, and is scheduled for removal in 15.0.
# The following items are not published on the docs page, but may be used in the future.
stage: Monitor # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
- tiers: [Core, Premium, Ultimate] # (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]
- issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348909 # (optional) This is a link to the deprecation issue in GitLab
+ tiers: [Core, Premium, Ultimate] # (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]
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348909 # (optional) This is a link to the deprecation issue in GitLab
documentation_url: https://docs.gitlab.com/ee/administration/monitoring/gitlab_self_monitoring_project/ # (optional) This is a link to the current documentation page
diff --git a/data/deprecations/15-0-oauth.yml b/data/deprecations/15-0-oauth.yml
index 7f22436cfe9..98c7c974808 100644
--- a/data/deprecations/15-0-oauth.yml
+++ b/data/deprecations/15-0-oauth.yml
@@ -3,7 +3,7 @@
announcement_date: "2021-06-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed
removal_date: "2022-05-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
- breaking_change: yes # If this deprecation is a breaking change, set this value to true
+ breaking_change: true # If this deprecation is a breaking change, set this value to true
body: | # Do not modify this line, instead modify the lines below.
The OAuth implicit grant authorization flow will be removed in our next major release, GitLab 15.0. Any applications that use OAuth implicit grant should switch to alternative [supported OAuth flows](https://docs.gitlab.com/ee/api/oauth2.html).
# The following items are not published on the docs page, but may be used in the future.
diff --git a/data/deprecations/distribution_deprecations_14-4.yml b/data/deprecations/distribution_deprecations_14-4.yml
index 2992745e74b..b8c4e0d817c 100644
--- a/data/deprecations/distribution_deprecations_14-4.yml
+++ b/data/deprecations/distribution_deprecations_14-4.yml
@@ -1,6 +1,6 @@
-- name: "Move `custom_hooks_dir` setting from GitLab Shell to Gitaly" # The name of the feature to be deprecated
- announcement_milestone: "14.9" # The milestone when this feature was first announced as deprecated.
+- name: "Move `custom_hooks_dir` setting from GitLab Shell to Gitaly" # The name of the feature to be deprecated
+ announcement_milestone: "14.9" # The milestone when this feature was first announced as deprecated.
announcement_date: "2021-10-22"
- removal_milestone: "15.0" # the milestone when this feature is planned to be removed
- body: | # Do not modify this line, instead modify the lines below.
+ removal_milestone: "15.0" # the milestone when this feature is planned to be removed
+ body: | # Do not modify this line, instead modify the lines below.
The [`custom_hooks_dir`](https://docs.gitlab.com/ee/administration/server_hooks.html#create-a-global-server-hook-for-all-repositories) setting is now configured in Gitaly, and will be removed from GitLab Shell in GitLab 15.0.
diff --git a/doc/ci/services/index.md b/doc/ci/services/index.md
index d14027cb1ef..e6406818b4c 100644
--- a/doc/ci/services/index.md
+++ b/doc/ci/services/index.md
@@ -438,3 +438,7 @@ docker rm -f -v build service-mysql service-postgres
This forcefully (`-f`) removes the `build` container, the two service
containers, and all volumes (`-v`) that were created with the container
creation.
+
+## Security when using services containers
+
+Docker privileged mode applies to services. This means that the service image container can access the host system. You should use container images from trusted sources only.
diff --git a/doc/ci/yaml/artifacts_reports.md b/doc/ci/yaml/artifacts_reports.md
index bd28d917cd7..e010dd21b9e 100644
--- a/doc/ci/yaml/artifacts_reports.md
+++ b/doc/ci/yaml/artifacts_reports.md
@@ -80,14 +80,9 @@ GitLab can display the results of one or more reports in:
- The [security dashboard](../../user/application_security/security_dashboard/index.md).
- The [Project Vulnerability report](../../user/application_security/vulnerability_report/index.md).
-## `artifacts:reports:cobertura` (DEPRECATED)
+## `artifacts:reports:cobertura`
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3708) in GitLab 12.9.
-> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78132) in GitLab 14.9.
-
-WARNING:
-This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78132) for use in GitLab
-14.8 and replaced with `artifacts:reports:coverage_report`.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3708) in GitLab 12.9.
The `cobertura` report collects [Cobertura coverage XML files](../../user/project/merge_requests/test_coverage_visualization.md).
The collected Cobertura coverage reports upload to GitLab as an artifact.
@@ -98,28 +93,6 @@ GitLab can display the results of one or more reports in the merge request
Cobertura was originally developed for Java, but there are many third-party ports for other languages such as
JavaScript, Python, and Ruby.
-## `artifacts:reports:coverage_report`
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344533) in GitLab 14.9.
-
-Use `coverage_report` to collect coverage report in Cobertura format, similar to `artifacts:reports:cobertura`.
-
-NOTE:
-`artifacts:reports:coverage_report` cannot be used at the same time with `artifacts:reports:cobertura`.
-
-```yaml
-artifacts:
- reports:
- coverage_report:
- coverage_format: cobertura
- path: coverage/cobertura-coverage.xml
-```
-
-The collected coverage report is uploaded to GitLab as an artifact.
-
-GitLab can display the results of coverage report in the merge request
-[diff annotations](../../user/project/merge_requests/test_coverage_visualization.md).
-
## `artifacts:reports:codequality`
> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212499) to GitLab Free in 13.2.
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 3be5d6f24c5..ad019afc37d 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -522,6 +522,32 @@ Fragmented Ruby heap snapshot could look like this:
Memory fragmentation could be reduced by tuning GC parameters [as described in this post](https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html). This should be considered as a tradeoff, as it may affect overall performance of memory allocation and GC cycles.
+### Derailed Benchmarks
+
+`derailed_benchmarks` is a [gem](https://github.com/zombocom/derailed_benchmarks)
+described as "A series of things you can use to benchmark a Rails or Ruby app."
+We include `derailed_benchmarks` in our `Gemfile`.
+
+We run `derailed exec perf:mem` in every pipeline with a `test` stage, in a job
+called `memory-on-boot`. ([Read an example job.](https://gitlab.com/gitlab-org/gitlab/-/jobs/2144695684).)
+You may find the results:
+
+- On the merge request **Overview** tab, in the merge request reports area, in the
+ **Metrics Reports** [dropdown list](../ci/metrics_reports.md).
+- In the `memory-on-boot` artifacts for a full report and a dependency breakdown.
+
+`derailed_benchmarks` also provides other methods to investigate memory. To learn more,
+refer to the [gem documentation](https://github.com/zombocom/derailed_benchmarks#running-derailed-exec).
+Most of the methods (`derailed exec perf:*`) attempt to boot your Rails app in a
+`production` environment and run benchmarks against it.
+It is possible both in GDK and GCK:
+
+- For GDK, follow the
+ [the instructions](https://github.com/zombocom/derailed_benchmarks#running-in-production-locally)
+ on the gem page. You must do similar for Redis configurations to avoid errors.
+- GCK includes `production` configuration sections
+ [out of the box](https://gitlab.com/gitlab-org/gitlab-compose-kit#running-production-like).
+
## Importance of Changes
When working on performance improvements, it's important to always ask yourself
diff --git a/doc/user/project/merge_requests/test_coverage_visualization.md b/doc/user/project/merge_requests/test_coverage_visualization.md
index 16c5dbe9199..d7177208a6e 100644
--- a/doc/user/project/merge_requests/test_coverage_visualization.md
+++ b/doc/user/project/merge_requests/test_coverage_visualization.md
@@ -28,7 +28,7 @@ between pipeline completion and the visualization loading on the page.
For the coverage analysis to work, you have to provide a properly formatted
[Cobertura XML](https://cobertura.github.io/cobertura/) report to
-[`artifacts:reports:cobertura`](../../../ci/yaml/artifacts_reports.md#artifactsreportscobertura-deprecated).
+[`artifacts:reports:cobertura`](../../../ci/yaml/artifacts_reports.md#artifactsreportscobertura).
This format was originally developed for Java, but most coverage analysis frameworks
for other languages have plugins to add support for it, like:
diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md
index 86655014ba6..dbd0da54cfb 100644
--- a/doc/user/project/working_with_projects.md
+++ b/doc/user/project/working_with_projects.md
@@ -191,7 +191,7 @@ To create a project from the HIPAA Audit Protocol template:
change the **Visibility Level**.
1. Select **Create project**.
-## Push to create a new project
+## Create a new project with Git push
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/26388) in GitLab 10.5.
@@ -294,7 +294,7 @@ To delete a project:
1. Select **Delete project**.
1. Confirm this action by completing the field.
-## Projects pending deletion **(PREMIUM)**
+## View projects pending deletion **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37014) in GitLab 13.3 for Administrators.
> - [Tab renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/347468) from **Deleted projects** in GitLab 14.6.
@@ -302,12 +302,16 @@ To delete a project:
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/351556) in GitLab 14.9. [Feature flag `project_owners_list_project_pending_deletion`](https://gitlab.com/gitlab-org/gitlab/-/issues/351556) removed.
When delayed project deletion is [enabled for a group](../group/index.md#enable-delayed-project-deletion),
-projects within that group are not deleted immediately, but only after a delay. To access a list of all projects that are pending deletion:
+projects within that group are not deleted immediately, but only after a delay.
+
+To view a list of all projects that are pending deletion:
1. On the top bar, select **Menu > Projects > Explore projects**.
-1. Select the **Pending deletion** tab (in GitLab 14.6 and later) or the **Deleted projects** tab (GitLab 14.5 and earlier).
+1. Based on your GitLab version:
+ - GitLab 14.6 and later: select the **Pending deletion** tab.
+ - GitLab 14.5 and earlier: select the **Deleted projects** tab.
-Listed for each project is:
+Each project in the list shows:
- The time the project was marked for deletion.
- The time the project is scheduled for final deletion.
@@ -325,7 +329,7 @@ To view the activity of a project:
## Leave a project
-If you leave a project you are no longer a project
+If you leave a project, you are no longer a project
member and cannot contribute.
To leave a project:
diff --git a/lib/api/entities/ci/job_request/artifacts.rb b/lib/api/entities/ci/job_request/artifacts.rb
index d1fb7d330b9..4b09db40504 100644
--- a/lib/api/entities/ci/job_request/artifacts.rb
+++ b/lib/api/entities/ci/job_request/artifacts.rb
@@ -6,7 +6,7 @@ module API
module JobRequest
class Artifacts < Grape::Entity
expose :name
- expose :untracked, expose_nil: false
+ expose :untracked
expose :paths
expose :exclude, expose_nil: false
expose :when
diff --git a/lib/atlassian/jira_connect/serializers/build_entity.rb b/lib/atlassian/jira_connect/serializers/build_entity.rb
index a3434c529a4..10e4bb0e709 100644
--- a/lib/atlassian/jira_connect/serializers/build_entity.rb
+++ b/lib/atlassian/jira_connect/serializers/build_entity.rb
@@ -26,7 +26,7 @@ module Atlassian
# merge request title.
@issue_keys ||= begin
pipeline.all_merge_requests.flat_map do |mr|
- src = "#{mr.source_branch} #{mr.title}"
+ src = "#{mr.source_branch} #{mr.title} #{mr.description}"
JiraIssueKeyExtractor.new(src).issue_keys
end.uniq
end
diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb
index f8fce1abc06..e45dbfa243f 100644
--- a/lib/gitlab/ci/config/entry/reports.rb
+++ b/lib/gitlab/ci/config/entry/reports.rb
@@ -8,7 +8,6 @@ module Gitlab
# Entry that represents a configuration of job artifacts.
#
class Reports < ::Gitlab::Config::Entry::Node
- include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
@@ -16,13 +15,10 @@ module Gitlab
%i[junit codequality sast secret_detection dependency_scanning container_scanning
dast performance browser_performance load_performance license_scanning metrics lsif
dotenv cobertura terraform accessibility cluster_applications
- requirements coverage_fuzzing api_fuzzing cluster_image_scanning
- coverage_report].freeze
+ requirements coverage_fuzzing api_fuzzing cluster_image_scanning].freeze
attributes ALLOWED_KEYS
- entry :coverage_report, Reports::CoverageReport, description: 'Coverage report configuration.'
-
validations do
validates :config, type: Hash
validates :config, allowed_keys: ALLOWED_KEYS
@@ -51,18 +47,10 @@ module Gitlab
validates :cluster_applications, array_of_strings_or_string: true # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/333441
validates :requirements, array_of_strings_or_string: true
end
-
- validates :config, mutually_exclusive_keys: [:coverage_report, :cobertura]
end
def value
- @config.transform_values do |value|
- if value.is_a?(Hash)
- value
- else
- Array(value)
- end
- end
+ @config.transform_values { |v| Array(v) }
end
end
end
diff --git a/lib/gitlab/ci/config/entry/reports/coverage_report.rb b/lib/gitlab/ci/config/entry/reports/coverage_report.rb
deleted file mode 100644
index 98119c7fd53..00000000000
--- a/lib/gitlab/ci/config/entry/reports/coverage_report.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- class Config
- module Entry
- class Reports
- class CoverageReport < ::Gitlab::Config::Entry::Node
- include ::Gitlab::Config::Entry::Validatable
- include ::Gitlab::Config::Entry::Attributable
-
- ALLOWED_KEYS = %i[coverage_format path].freeze
- SUPPORTED_COVERAGE = %w[cobertura].freeze
-
- attributes ALLOWED_KEYS
-
- validations do
- validates :config, type: Hash
- validates :config, allowed_keys: ALLOWED_KEYS
-
- with_options(presence: true) do
- validates :coverage_format, inclusion: { in: SUPPORTED_COVERAGE, message: "must be one of supported formats: #{SUPPORTED_COVERAGE.join(', ')}." }
- validates :path, type: String
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/config/entry/validators.rb b/lib/gitlab/config/entry/validators.rb
index cc24ae837f3..6ebcc476e4b 100644
--- a/lib/gitlab/config/entry/validators.rb
+++ b/lib/gitlab/config/entry/validators.rb
@@ -39,17 +39,6 @@ module Gitlab
end
end
- class MutuallyExclusiveKeysValidator < ActiveModel::EachValidator
- def validate_each(record, attribute, value)
- mutually_exclusive_keys = value.try(:keys).to_a & options[:in]
-
- if mutually_exclusive_keys.length > 1
- record.errors.add(attribute, "please use only one the following keys: " +
- mutually_exclusive_keys.join(', '))
- end
- end
- end
-
class AllowedValuesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless options[:in].include?(value.to_s)
diff --git a/lib/gitlab/database/migrations/background_migration_helpers.rb b/lib/gitlab/database/migrations/background_migration_helpers.rb
index 4f1b490cc8f..7e5c002d072 100644
--- a/lib/gitlab/database/migrations/background_migration_helpers.rb
+++ b/lib/gitlab/database/migrations/background_migration_helpers.rb
@@ -42,7 +42,7 @@ module Gitlab
# end
def queue_background_migration_jobs_by_range_at_intervals(model_class, job_class_name, delay_interval, batch_size: BATCH_SIZE, other_job_arguments: [], initial_delay: 0, track_jobs: false, primary_column_name: :id)
raise "#{model_class} does not have an ID column of #{primary_column_name} to use for batch ranges" unless model_class.column_names.include?(primary_column_name.to_s)
- raise "#{primary_column_name} is not an integer column" unless model_class.columns_hash[primary_column_name.to_s].type == :integer
+ raise "#{primary_column_name} is not an integer or string column" unless [:integer, :string].include?(model_class.columns_hash[primary_column_name.to_s].type)
job_coordinator = coordinator_for_tracking_database
diff --git a/lib/gitlab/usage/service_ping/payload_keys_processor.rb b/lib/gitlab/usage/service_ping/payload_keys_processor.rb
index b00aee45e59..ea2043ffb83 100644
--- a/lib/gitlab/usage/service_ping/payload_keys_processor.rb
+++ b/lib/gitlab/usage/service_ping/payload_keys_processor.rb
@@ -17,7 +17,7 @@ module Gitlab
end
def missing_instrumented_metrics_key_paths
- @missing_key_paths ||= metrics_with_instrumentation.map(&:key_path) - key_paths
+ @missing_key_paths ||= metrics_with_instrumentation.map(&:key) - key_paths
end
private
@@ -50,3 +50,5 @@ module Gitlab
end
end
end
+
+Gitlab::Usage::ServicePing::PayloadKeysProcessor.prepend_mod_with('Gitlab::Usage::ServicePing::PayloadKeysProcessor')
diff --git a/lib/sidebars/groups/menus/packages_registries_menu.rb b/lib/sidebars/groups/menus/packages_registries_menu.rb
index 60d91c8fd10..4c21845ef18 100644
--- a/lib/sidebars/groups/menus/packages_registries_menu.rb
+++ b/lib/sidebars/groups/menus/packages_registries_menu.rb
@@ -8,8 +8,8 @@ module Sidebars
def configure_menu_items
add_item(packages_registry_menu_item)
add_item(container_registry_menu_item)
+ add_item(harbor_registry__menu_item)
add_item(dependency_proxy_menu_item)
-
true
end
@@ -49,6 +49,17 @@ module Sidebars
)
end
+ def harbor_registry__menu_item
+ return nil_menu_item(:harbor_registry) if Feature.disabled?(:harbor_registry_integration)
+
+ ::Sidebars::MenuItem.new(
+ title: _('Harbor Registry'),
+ link: group_harbor_registries_path(context.group),
+ active_routes: { controller: 'groups/harbor/repositories' },
+ item_id: :harbor_registry
+ )
+ end
+
def dependency_proxy_menu_item
setting_does_not_exist_or_is_enabled = !context.group.dependency_proxy_setting ||
context.group.dependency_proxy_setting.enabled
diff --git a/lib/sidebars/projects/menus/packages_registries_menu.rb b/lib/sidebars/projects/menus/packages_registries_menu.rb
index f5f0da2992e..77f09986b19 100644
--- a/lib/sidebars/projects/menus/packages_registries_menu.rb
+++ b/lib/sidebars/projects/menus/packages_registries_menu.rb
@@ -9,7 +9,7 @@ module Sidebars
add_item(packages_registry_menu_item)
add_item(container_registry_menu_item)
add_item(infrastructure_registry_menu_item)
-
+ add_item(harbor_registry__menu_item)
true
end
@@ -65,6 +65,17 @@ module Sidebars
)
end
+ def harbor_registry__menu_item
+ return ::Sidebars::NilMenuItem.new(item_id: :harbor_registry) if Feature.disabled?(:harbor_registry_integration)
+
+ ::Sidebars::MenuItem.new(
+ title: _('Harbor Registry'),
+ link: project_harbor_registry_index_path(context.project),
+ active_routes: { controller: :harbor_registry },
+ item_id: :harbor_registry
+ )
+ end
+
def packages_registry_disabled?
!::Gitlab.config.packages.enabled || !can?(context.current_user, :read_package, context.project)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 257a69adfd6..ac015d382a7 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17932,6 +17932,9 @@ msgstr ""
msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}"
msgstr ""
+msgid "Harbor Registry"
+msgstr ""
+
msgid "Hashed Storage must be enabled to use Geo"
msgstr ""
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index b1f77382f6a..bd3135bafdc 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -58,16 +58,6 @@ module QA
artifacts:
paths:
- my-artifacts/
-
- test-coverage-report:
- tags:
- - #{executor}
- script: mkdir coverage; echo "CONTENTS" > coverage/cobertura.xml
- artifacts:
- reports:
- coverage_report:
- coverage_format: cobertura
- path: coverage/cobertura.xml
YAML
}
]
@@ -81,8 +71,7 @@ module QA
'test-success': 'passed',
'test-failure': 'failed',
'test-tags-mismatch': 'pending',
- 'test-artifacts': 'passed',
- 'test-coverage-report': 'passed'
+ 'test-artifacts': 'passed'
}.each do |job, status|
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job(job)
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 9545378780a..011021f6320 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -497,22 +497,6 @@ FactoryBot.define do
options { {} }
end
- trait :coverage_report_cobertura do
- options do
- {
- artifacts: {
- expire_in: '7d',
- reports: {
- coverage_report: {
- coverage_format: 'cobertura',
- path: 'cobertura.xml'
- }
- }
- }
- }
- end
- end
-
# TODO: move Security traits to ee_ci_build
# https://gitlab.com/gitlab-org/gitlab/-/issues/210486
trait :dast do
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 6f706546402..26804b38db8 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -33,6 +33,10 @@ FactoryBot.define do
title { generate(:jira_title) }
end
+ trait :jira_description do
+ description { generate(:jira_description) }
+ end
+
trait :jira_branch do
source_branch { generate(:jira_branch) }
end
diff --git a/spec/factories/sequences.rb b/spec/factories/sequences.rb
index 893865962d8..6b86154aa91 100644
--- a/spec/factories/sequences.rb
+++ b/spec/factories/sequences.rb
@@ -18,6 +18,7 @@ FactoryBot.define do
sequence(:draft_title) { |n| "Draft: #{n}" }
sequence(:wip_title) { |n| "WIP: #{n}" }
sequence(:jira_title) { |n| "[PROJ-#{n}]: fix bug" }
+ sequence(:jira_description) { |n| "This is a description\n here is the description\n Related to: PROJ-#{n}" }
sequence(:jira_branch) { |n| "feature/PROJ-#{n}" }
sequence(:job_name) { |n| "job #{n}" }
sequence(:work_item_type_name) { |n| "bug#{n}" }
diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb
index c5d2f5e6733..42b53d941c2 100644
--- a/spec/features/groups/navbar_spec.rb
+++ b/spec/features/groups/navbar_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe 'Group navbar' do
stub_feature_flags(customer_relations: false)
stub_config(dependency_proxy: { enabled: false })
stub_config(registry: { enabled: false })
+ stub_feature_flags(harbor_registry_integration: false)
stub_group_wikis(false)
group.add_maintainer(user)
sign_in(user)
@@ -70,4 +71,16 @@ RSpec.describe 'Group navbar' do
it_behaves_like 'verified navigation bar'
end
+
+ context 'when harbor registry is available' do
+ before do
+ stub_feature_flags(harbor_registry_integration: true)
+
+ insert_harbor_registry_nav(_('Package Registry'))
+
+ visit group_path(group)
+ end
+
+ it_behaves_like 'verified navigation bar'
+ end
end
diff --git a/spec/features/projects/navbar_spec.rb b/spec/features/projects/navbar_spec.rb
index 91e643ff258..5098908857a 100644
--- a/spec/features/projects/navbar_spec.rb
+++ b/spec/features/projects/navbar_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe 'Project navbar' do
sign_in(user)
stub_config(registry: { enabled: false })
+ stub_feature_flags(harbor_registry_integration: false)
insert_package_nav(_('Infrastructure'))
insert_infrastructure_registry_nav
insert_infrastructure_google_cloud_nav
@@ -76,4 +77,16 @@ RSpec.describe 'Project navbar' do
it_behaves_like 'verified navigation bar'
end
+
+ context 'when harbor registry is available' do
+ before do
+ stub_feature_flags(harbor_registry_integration: true)
+
+ insert_harbor_registry_nav(_('Infrastructure Registry'))
+
+ visit project_path(project)
+ end
+
+ it_behaves_like 'verified navigation bar'
+ end
end
diff --git a/spec/frontend/invite_members/components/invite_groups_modal_spec.js b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
index 5547b1608aa..8085f48f6e2 100644
--- a/spec/frontend/invite_members/components/invite_groups_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
@@ -4,6 +4,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import Api from '~/api';
import InviteGroupsModal from '~/invite_members/components/invite_groups_modal.vue';
import InviteModalBase from '~/invite_members/components/invite_modal_base.vue';
+import ContentTransition from '~/vue_shared/components/content_transition.vue';
import GroupSelect from '~/invite_members/components/group_select.vue';
import { stubComponent } from 'helpers/stub_component';
import { propsData, sharedGroup } from '../mock_data/group_modal';
@@ -19,6 +20,7 @@ describe('InviteGroupsModal', () => {
},
stubs: {
InviteModalBase,
+ ContentTransition,
GlSprintf,
GlModal: stubComponent(GlModal, {
template: '<div><slot></slot><slot name="modal-footer"></slot></div>',
diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js
index 15a366474e4..dd16bb48cb8 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -19,6 +19,7 @@ import {
LEARN_GITLAB,
} from '~/invite_members/constants';
import eventHub from '~/invite_members/event_hub';
+import ContentTransition from '~/vue_shared/components/content_transition.vue';
import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
import { getParameterValues } from '~/lib/utils/url_utility';
@@ -55,6 +56,7 @@ describe('InviteMembersModal', () => {
},
stubs: {
InviteModalBase,
+ ContentTransition,
GlSprintf,
GlModal: stubComponent(GlModal, {
template: '<div><slot></slot><slot name="modal-footer"></slot></div>',
diff --git a/spec/frontend/invite_members/components/invite_modal_base_spec.js b/spec/frontend/invite_members/components/invite_modal_base_spec.js
index 3687754acb9..9e17112fb15 100644
--- a/spec/frontend/invite_members/components/invite_modal_base_spec.js
+++ b/spec/frontend/invite_members/components/invite_modal_base_spec.js
@@ -10,6 +10,7 @@ import {
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import InviteModalBase from '~/invite_members/components/invite_modal_base.vue';
+import ContentTransition from '~/vue_shared/components/content_transition.vue';
import { CANCEL_BUTTON_TEXT, INVITE_BUTTON_TEXT } from '~/invite_members/constants';
import { propsData } from '../mock_data/modal_base';
@@ -23,6 +24,7 @@ describe('InviteModalBase', () => {
...props,
},
stubs: {
+ ContentTransition,
GlModal: stubComponent(GlModal, {
template:
'<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
diff --git a/spec/frontend/tracking/tracking_spec.js b/spec/frontend/tracking/tracking_spec.js
index b7a2e4f4f51..10380e6f4c5 100644
--- a/spec/frontend/tracking/tracking_spec.js
+++ b/spec/frontend/tracking/tracking_spec.js
@@ -255,6 +255,23 @@ describe('Tracking', () => {
expect(snowplowSpy).toHaveBeenCalledWith('setCustomUrl', TEST_HOST);
});
+ describe('allowed hashes/fragments', () => {
+ it.each`
+ hash | appends | description
+ ${'note_abc_123'} | ${true} | ${'appends'}
+ ${'diff-content-819'} | ${true} | ${'appends'}
+ ${'first_heading'} | ${false} | ${'does not append'}
+ `('$description `$hash` hash', ({ hash, appends }) => {
+ window.gl.snowplowPseudonymizedPageUrl = TEST_HOST;
+ window.location.hash = hash;
+
+ Tracking.setAnonymousUrls();
+
+ const url = appends ? `${TEST_HOST}#${hash}` : TEST_HOST;
+ expect(snowplowSpy).toHaveBeenCalledWith('setCustomUrl', url);
+ });
+ });
+
it('does not set the referrer URL by default', () => {
window.gl.snowplowPseudonymizedPageUrl = TEST_HOST;
diff --git a/spec/frontend/vue_shared/components/__snapshots__/content_transition_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/content_transition_spec.js.snap
new file mode 100644
index 00000000000..fd804990b5e
--- /dev/null
+++ b/spec/frontend/vue_shared/components/__snapshots__/content_transition_spec.js.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`~/vue_shared/components/content_transition.vue default shows all transitions and only default is visible 1`] = `
+<div>
+ <transition-stub
+ name="test_transition_name"
+ >
+ <div
+ data-testval="default"
+ >
+ <p>
+ Default
+ </p>
+ </div>
+ </transition-stub>
+ <transition-stub
+ name="test_transition_name"
+ >
+ <div
+ data-testval="foo"
+ style="display: none;"
+ >
+ <p>
+ Foo
+ </p>
+ </div>
+ </transition-stub>
+ <transition-stub
+ name="test_transition_name"
+ >
+ <div
+ data-testval="bar"
+ style="display: none;"
+ >
+ <p>
+ Bar
+ </p>
+ </div>
+ </transition-stub>
+</div>
+`;
diff --git a/spec/frontend/vue_shared/components/content_transition_spec.js b/spec/frontend/vue_shared/components/content_transition_spec.js
new file mode 100644
index 00000000000..8bb6d31cce7
--- /dev/null
+++ b/spec/frontend/vue_shared/components/content_transition_spec.js
@@ -0,0 +1,109 @@
+import { groupBy, mapValues } from 'lodash';
+import { shallowMount } from '@vue/test-utils';
+import ContentTransition from '~/vue_shared/components/content_transition.vue';
+
+const TEST_CURRENT_SLOT = 'default';
+const TEST_TRANSITION_NAME = 'test_transition_name';
+const TEST_SLOTS = [
+ { key: 'default', attributes: { 'data-testval': 'default' } },
+ { key: 'foo', attributes: { 'data-testval': 'foo' } },
+ { key: 'bar', attributes: { 'data-testval': 'bar' } },
+];
+
+describe('~/vue_shared/components/content_transition.vue', () => {
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const createComponent = (props = {}, slots = {}) => {
+ wrapper = shallowMount(ContentTransition, {
+ propsData: {
+ transitionName: TEST_TRANSITION_NAME,
+ currentSlot: TEST_CURRENT_SLOT,
+ slots: TEST_SLOTS,
+ ...props,
+ },
+ slots: {
+ default: '<p>Default</p>',
+ foo: '<p>Foo</p>',
+ bar: '<p>Bar</p>',
+ dne: '<p>DOES NOT EXIST</p>',
+ ...slots,
+ },
+ });
+ };
+
+ const findTransitionsData = () =>
+ wrapper.findAll('transition-stub').wrappers.map((transition) => {
+ const child = transition.find('[data-testval]');
+ const { style, ...attributes } = child.attributes();
+
+ return {
+ transitionName: transition.attributes('name'),
+ isVisible: child.isVisible(),
+ attributes,
+ text: transition.text(),
+ };
+ });
+ const findVisibleData = () => {
+ const group = groupBy(findTransitionsData(), (x) => x.attributes['data-testval']);
+
+ return mapValues(group, (x) => x[0].isVisible);
+ };
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows all transitions and only default is visible', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('render transitions for each slot', () => {
+ expect(findTransitionsData()).toEqual([
+ {
+ attributes: {
+ 'data-testval': 'default',
+ },
+ isVisible: true,
+ text: 'Default',
+ transitionName: 'test_transition_name',
+ },
+ {
+ attributes: {
+ 'data-testval': 'foo',
+ },
+ isVisible: false,
+ text: 'Foo',
+ transitionName: 'test_transition_name',
+ },
+ {
+ attributes: {
+ 'data-testval': 'bar',
+ },
+ isVisible: false,
+ text: 'Bar',
+ transitionName: 'test_transition_name',
+ },
+ ]);
+ });
+ });
+
+ describe('with currentSlot=foo', () => {
+ beforeEach(() => {
+ createComponent({ currentSlot: 'foo' });
+ });
+
+ it('should only show the foo slot', () => {
+ expect(findVisibleData()).toEqual({
+ default: false,
+ foo: true,
+ bar: false,
+ });
+ });
+ });
+});
diff --git a/spec/lib/atlassian/jira_connect/serializers/build_entity_spec.rb b/spec/lib/atlassian/jira_connect/serializers/build_entity_spec.rb
index 4bbd654655d..a29f32d35b8 100644
--- a/spec/lib/atlassian/jira_connect/serializers/build_entity_spec.rb
+++ b/spec/lib/atlassian/jira_connect/serializers/build_entity_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Atlassian::JiraConnect::Serializers::BuildEntity do
context 'when the pipeline does belong to a Jira issue' do
let(:pipeline) { create(:ci_pipeline, merge_request: merge_request) }
- %i[jira_branch jira_title].each do |trait|
+ %i[jira_branch jira_title jira_description].each do |trait|
context "because it belongs to an MR with a #{trait}" do
let(:merge_request) { create(:merge_request, trait) }
diff --git a/spec/lib/gitlab/ci/config/entry/reports/coverage_report_spec.rb b/spec/lib/gitlab/ci/config/entry/reports/coverage_report_spec.rb
deleted file mode 100644
index 588f53150ff..00000000000
--- a/spec/lib/gitlab/ci/config/entry/reports/coverage_report_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-
-RSpec.describe Gitlab::Ci::Config::Entry::Reports::CoverageReport do
- let(:entry) { described_class.new(config) }
-
- describe 'validations' do
- context 'when it is valid' do
- let(:config) { { coverage_format: 'cobertura', path: 'cobertura-coverage.xml' } }
-
- it { expect(entry).to be_valid }
-
- it { expect(entry.value).to eq(config) }
- end
-
- context 'with unsupported coverage format' do
- let(:config) { { coverage_format: 'jacoco', path: 'jacoco.xml' } }
-
- it { expect(entry).not_to be_valid }
-
- it { expect(entry.errors).to include /format must be one of supported formats/ }
- end
-
- context 'without coverage format' do
- let(:config) { { path: 'cobertura-coverage.xml' } }
-
- it { expect(entry).not_to be_valid }
-
- it { expect(entry.errors).to include /format can't be blank/ }
- end
-
- context 'without path' do
- let(:config) { { coverage_format: 'cobertura' } }
-
- it { expect(entry).not_to be_valid }
-
- it { expect(entry.errors).to include /path can't be blank/ }
- end
-
- context 'with invalid path' do
- let(:config) { { coverage_format: 'cobertura', path: 123 } }
-
- it { expect(entry).not_to be_valid }
-
- it { expect(entry.errors).to include /path should be a string/ }
- end
-
- context 'with unknown keys' do
- let(:config) { { coverage_format: 'cobertura', path: 'cobertura-coverage.xml', foo: :bar } }
-
- it { expect(entry).not_to be_valid }
-
- it { expect(entry.errors).to include /contains unknown keys/ }
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 061d8f34c8d..12b8960eb32 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -6,8 +6,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do
let(:entry) { described_class.new(config) }
describe 'validates ALLOWED_KEYS' do
- it "expects ALLOWED_KEYS to be an artifact file_type or coverage_report" do
- expect(Ci::JobArtifact.file_types.keys.map(&:to_sym) + [:coverage_report]).to include(*described_class::ALLOWED_KEYS)
+ let(:artifact_file_types) { Ci::JobArtifact.file_types }
+
+ described_class::ALLOWED_KEYS.each do |keyword, _|
+ it "expects #{keyword} to be an artifact file_type" do
+ expect(artifact_file_types).to include(keyword)
+ end
end
end
@@ -64,45 +68,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do
it_behaves_like 'a valid entry', params[:keyword], params[:file]
end
end
-
- context 'when coverage_report is specified' do
- let(:coverage_format) { :cobertura }
- let(:filename) { 'cobertura-coverage.xml' }
- let(:coverage_report) { { path: filename, coverage_format: coverage_format } }
- let(:config) { { coverage_report: coverage_report } }
-
- it 'is valid' do
- expect(entry).to be_valid
- end
-
- it 'returns artifacts configuration' do
- expect(entry.value).to eq(config)
- end
-
- context 'and another report is specified' do
- let(:config) { { coverage_report: coverage_report, dast: 'gl-dast-report.json' } }
-
- it 'is valid' do
- expect(entry).to be_valid
- end
-
- it 'returns artifacts configuration' do
- expect(entry.value).to eq({ coverage_report: coverage_report, dast: ['gl-dast-report.json'] })
- end
- end
-
- context 'and a direct coverage report format is specified' do
- let(:config) { { coverage_report: coverage_report, cobertura: 'cobertura-coverage.xml' } }
-
- it 'is not valid' do
- expect(entry).not_to be_valid
- end
-
- it 'reports error' do
- expect(entry.errors).to include /please use only one the following keys: coverage_report, cobertura/
- end
- end
- end
end
context 'when entry value is not correct' do
diff --git a/spec/lib/gitlab/config/entry/validators_spec.rb b/spec/lib/gitlab/config/entry/validators_spec.rb
deleted file mode 100644
index cbc09aac586..00000000000
--- a/spec/lib/gitlab/config/entry/validators_spec.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Config::Entry::Validators do
- let(:klass) do
- Class.new do
- include ActiveModel::Validations
- include Gitlab::Config::Entry::Validators
- end
- end
-
- let(:instance) { klass.new }
-
- describe described_class::MutuallyExclusiveKeysValidator do
- using RSpec::Parameterized::TableSyntax
-
- before do
- klass.instance_eval do
- validates :config, mutually_exclusive_keys: [:foo, :bar]
- end
-
- allow(instance).to receive(:config).and_return(config)
- end
-
- where(:context, :config, :valid_result) do
- 'with mutually exclusive keys' | { foo: 1, bar: 2 } | false
- 'without mutually exclusive keys' | { foo: 1 } | true
- 'without mutually exclusive keys' | { bar: 1 } | true
- 'with other keys' | { foo: 1, baz: 2 } | true
- end
-
- with_them do
- it 'validates the instance' do
- expect(instance.valid?).to be(valid_result)
-
- unless valid_result
- expect(instance.errors.messages_for(:config)).to include /please use only one the following keys: foo, bar/
- end
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
index 96dc3a0fc28..e64f5807385 100644
--- a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb
@@ -164,11 +164,19 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do
end
end
- context "when the primary_column_name is not an integer" do
+ context 'when the primary_column_name is a string' do
+ it 'does not raise error' do
+ expect do
+ model.queue_background_migration_jobs_by_range_at_intervals(ContainerExpirationPolicy, 'FooJob', 10.minutes, primary_column_name: :name_regex)
+ end.not_to raise_error
+ end
+ end
+
+ context "when the primary_column_name is not an integer or a string" do
it 'raises error' do
expect do
model.queue_background_migration_jobs_by_range_at_intervals(ContainerExpirationPolicy, 'FooJob', 10.minutes, primary_column_name: :enabled)
- end.to raise_error(StandardError, /is not an integer column/)
+ end.to raise_error(StandardError, /is not an integer or string column/)
end
end
diff --git a/spec/lib/gitlab/usage/service_ping/payload_keys_processor_spec.rb b/spec/lib/gitlab/usage/service_ping/payload_keys_processor_spec.rb
index 2dc95294d03..dd4349b99df 100644
--- a/spec/lib/gitlab/usage/service_ping/payload_keys_processor_spec.rb
+++ b/spec/lib/gitlab/usage/service_ping/payload_keys_processor_spec.rb
@@ -38,10 +38,10 @@ RSpec.describe Gitlab::Usage::ServicePing::PayloadKeysProcessor do
let(:metrics_definitions) do
[
- double(:issues, key_path: 'counts.issues'),
- double(:topology, key_path: 'topology'),
- double(:i_search_total_monthly, key_path: 'redis_hll_counters.search.i_search_total_monthly'),
- double(:collected_data_categories, key_path: 'settings.collected_data_categories')
+ instance_double(::Gitlab::Usage::MetricDefinition, key: 'counts.issues'),
+ instance_double(::Gitlab::Usage::MetricDefinition, key: 'topology'),
+ instance_double(::Gitlab::Usage::MetricDefinition, key: 'redis_hll_counters.search.i_search_total_monthly'),
+ instance_double(::Gitlab::Usage::MetricDefinition, key: 'settings.collected_data_categories')
]
end
diff --git a/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb b/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb
index bc1fa3e88ff..d3cb18222b5 100644
--- a/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb
+++ b/spec/lib/sidebars/groups/menus/packages_registries_menu_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe Sidebars::Groups::Menus::PackagesRegistriesMenu do
context 'when menu does not have any menu item to show' do
it 'returns false' do
+ stub_feature_flags(harbor_registry_integration: false)
stub_container_registry_config(enabled: false)
stub_config(packages: { enabled: false })
stub_config(dependency_proxy: { enabled: false })
@@ -35,11 +36,13 @@ RSpec.describe Sidebars::Groups::Menus::PackagesRegistriesMenu do
describe '#link' do
let(:registry_enabled) { true }
let(:packages_enabled) { true }
+ let(:harbor_registry_integration) { true }
before do
stub_container_registry_config(enabled: registry_enabled)
stub_config(packages: { enabled: packages_enabled })
stub_config(dependency_proxy: { enabled: true })
+ stub_feature_flags(harbor_registry_integration: harbor_registry_integration)
end
subject { menu.link }
@@ -60,8 +63,16 @@ RSpec.describe Sidebars::Groups::Menus::PackagesRegistriesMenu do
context 'when Container Registry is not visible' do
let(:registry_enabled) { false }
- it 'menu link points to Dependency Proxy page' do
- expect(subject).to eq find_menu(menu, :dependency_proxy).link
+ it 'menu link points to Harbor Registry page' do
+ expect(subject).to eq find_menu(menu, :harbor_registry).link
+ end
+
+ context 'when Harbor Registry is not visible' do
+ let(:harbor_registry_integration) { false }
+
+ it 'menu link points to Dependency Proxy page' do
+ expect(subject).to eq find_menu(menu, :dependency_proxy).link
+ end
end
end
end
@@ -175,6 +186,26 @@ RSpec.describe Sidebars::Groups::Menus::PackagesRegistriesMenu do
it_behaves_like 'the menu entry is not available'
end
end
+
+ describe 'Harbor Registry' do
+ let(:item_id) { :harbor_registry }
+
+ before do
+ stub_feature_flags(harbor_registry_integration: harbor_registry_enabled)
+ end
+
+ context 'when config harbor registry setting is disabled' do
+ let(:harbor_registry_enabled) { false }
+
+ it_behaves_like 'the menu entry is not available'
+ end
+
+ context 'when config harbor registry setting is enabled' do
+ let(:harbor_registry_enabled) { true }
+
+ it_behaves_like 'the menu entry is available'
+ end
+ end
end
private
diff --git a/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb b/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb
index afe0b2a8951..9b78fc807bf 100644
--- a/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb
@@ -33,6 +33,7 @@ RSpec.describe Sidebars::Projects::Menus::PackagesRegistriesMenu do
before do
stub_container_registry_config(enabled: registry_enabled)
stub_config(packages: { enabled: packages_enabled })
+ stub_feature_flags(harbor_registry_integration: false)
end
context 'when Packages Registry is visible' do
@@ -144,5 +145,25 @@ RSpec.describe Sidebars::Projects::Menus::PackagesRegistriesMenu do
end
end
end
+
+ describe 'Harbor Registry' do
+ let(:item_id) { :harbor_registry }
+
+ context 'when config harbor registry setting is disabled' do
+ it 'does not add the menu item to the list' do
+ stub_feature_flags(harbor_registry_integration: false)
+
+ is_expected.to be_nil
+ end
+ end
+
+ context 'when config harbor registry setting is enabled' do
+ it 'the menu item is added to list of menu items' do
+ stub_feature_flags(harbor_registry_integration: true)
+
+ is_expected.not_to be_nil
+ end
+ end
+ end
end
end
diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb
index ace65307321..d25102532a7 100644
--- a/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -78,72 +78,16 @@ RSpec.describe Ci::BuildRunnerPresenter do
artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(file_type),
paths: [filename],
when: 'always'
- }.compact
+ }
end
it 'presents correct hash' do
- expect(presenter.artifacts).to contain_exactly(report_expectation)
+ expect(presenter.artifacts.first).to include(report_expectation)
end
end
end
end
- context 'when a specific coverage_report type is given' do
- let(:coverage_format) { :cobertura }
- let(:filename) { 'cobertura-coverage.xml' }
- let(:coverage_report) { { path: filename, coverage_format: coverage_format } }
- let(:report) { { coverage_report: coverage_report } }
- let(:build) { create(:ci_build, options: { artifacts: { reports: report } }) }
-
- let(:expected_coverage_report) do
- {
- name: filename,
- artifact_type: coverage_format,
- artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(coverage_format),
- paths: [filename],
- when: 'always'
- }
- end
-
- it 'presents the coverage report hash with the coverage format' do
- expect(presenter.artifacts).to contain_exactly(expected_coverage_report)
- end
- end
-
- context 'when a specific coverage_report type is given with another report type' do
- let(:coverage_format) { :cobertura }
- let(:coverage_filename) { 'cobertura-coverage.xml' }
- let(:coverage_report) { { path: coverage_filename, coverage_format: coverage_format } }
- let(:ds_filename) { 'gl-dependency-scanning-report.json' }
-
- let(:report) { { coverage_report: coverage_report, dependency_scanning: [ds_filename] } }
- let(:build) { create(:ci_build, options: { artifacts: { reports: report } }) }
-
- let(:expected_coverage_report) do
- {
- name: coverage_filename,
- artifact_type: coverage_format,
- artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(coverage_format),
- paths: [coverage_filename],
- when: 'always'
- }
- end
-
- let(:expected_ds_report) do
- {
- name: ds_filename,
- artifact_type: :dependency_scanning,
- artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(:dependency_scanning),
- paths: [ds_filename],
- when: 'always'
- }
- end
-
- it 'presents both reports' do
- expect(presenter.artifacts).to contain_exactly(expected_coverage_report, expected_ds_report)
- end
- end
-
context "when option has both archive and reports specification" do
let(:report) { { junit: ['junit.xml'] } }
let(:build) { create(:ci_build, options: { script: 'echo', artifacts: { **archive, reports: report } }) }
diff --git a/spec/requests/api/ci/runner/jobs_request_post_spec.rb b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
index 93c566abc75..d317386dc73 100644
--- a/spec/requests/api/ci/runner/jobs_request_post_spec.rb
+++ b/spec/requests/api/ci/runner/jobs_request_post_spec.rb
@@ -611,40 +611,6 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
end
- context 'when job has code coverage report' do
- let(:job) do
- create(:ci_build, :pending, :queued, :coverage_report_cobertura,
- pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
- end
-
- let(:expected_artifacts) do
- [
- {
- 'name' => 'cobertura-coverage.xml',
- 'paths' => ['cobertura.xml'],
- 'when' => 'always',
- 'expire_in' => '7d',
- "artifact_type" => "cobertura",
- "artifact_format" => "gzip"
- }
- ]
- end
-
- it 'returns job with the correct artifact specification' do
- request_job info: { platform: :darwin, features: { upload_multiple_artifacts: true } }
-
- expect(response).to have_gitlab_http_status(:created)
- expect(response.headers['Content-Type']).to eq('application/json')
- expect(response.headers).not_to have_key('X-GitLab-Last-Update')
- expect(runner.reload.platform).to eq('darwin')
- expect(json_response['id']).to eq(job.id)
- expect(json_response['token']).to eq(job.token)
- expect(json_response['job_info']).to eq(expected_job_info)
- expect(json_response['git_info']).to eq(expected_git_info)
- expect(json_response['artifacts']).to eq(expected_artifacts)
- end
- end
-
context 'when triggered job is available' do
let(:expected_variables) do
[{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
diff --git a/spec/requests/groups/harbor/repositories_controller_spec.rb b/spec/requests/groups/harbor/repositories_controller_spec.rb
new file mode 100644
index 00000000000..3e475dc410e
--- /dev/null
+++ b/spec/requests/groups/harbor/repositories_controller_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::Harbor::RepositoriesController do
+ let_it_be(:group, reload: true) { create(:group) }
+ let_it_be(:user) { create(:user) }
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ end
+ end
+
+ before do
+ stub_feature_flags(harbor_registry_integration: true)
+ group.add_reporter(user)
+ login_as(user)
+ end
+
+ describe 'GET #index' do
+ subject do
+ get group_harbor_registries_path(group)
+ response
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+
+ describe 'GET #show' do
+ subject do
+ get group_harbor_registry_path(group, 1)
+ response
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+end
diff --git a/spec/requests/projects/harbor/repositories_controller_spec.rb b/spec/requests/projects/harbor/repositories_controller_spec.rb
new file mode 100644
index 00000000000..cdb5a696d7e
--- /dev/null
+++ b/spec/requests/projects/harbor/repositories_controller_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::Harbor::RepositoriesController do
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ shared_examples 'responds with 404 status' do
+ it 'returns 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ shared_examples 'responds with 200 status' do
+ it 'renders the index template' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:index)
+ end
+ end
+
+ before do
+ stub_feature_flags(harbor_registry_integration: true)
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ describe 'GET #index' do
+ subject do
+ get project_harbor_registry_index_path(project)
+ response
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+
+ describe 'GET #show' do
+ subject do
+ get project_harbor_registry_path(project, 1)
+ response
+ end
+
+ context 'with harbor registry feature flag enabled' do
+ it_behaves_like 'responds with 200 status'
+ end
+
+ context 'with harbor registry feature flag disabled' do
+ before do
+ stub_feature_flags(harbor_registry_integration: false)
+ end
+
+ it_behaves_like 'responds with 404 status'
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service/artifacts_spec.rb b/spec/services/ci/create_pipeline_service/artifacts_spec.rb
deleted file mode 100644
index 1ec30d68666..00000000000
--- a/spec/services/ci/create_pipeline_service/artifacts_spec.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-require 'spec_helper'
-
-RSpec.describe Ci::CreatePipelineService do
- let_it_be(:project) { create(:project, :repository) }
- let_it_be(:user) { project.first_owner }
-
- let(:ref) { 'refs/heads/master' }
- let(:source) { :push }
-
- let(:service) { described_class.new(project, user, { ref: ref }) }
- let(:pipeline) { service.execute(source).payload }
-
- describe 'artifacts:' do
- before do
- stub_ci_pipeline_yaml_file(config)
- allow_next_instance_of(Ci::BuildScheduleWorker) do |instance|
- allow(instance).to receive(:perform).and_return(true)
- end
- end
-
- describe 'reports:' do
- context 'with valid config' do
- let(:config) do
- <<~YAML
- test-job:
- script: "echo 'hello world' > cobertura.xml"
- artifacts:
- reports:
- coverage_report:
- coverage_format: 'cobertura'
- path: 'cobertura.xml'
-
- dependency-scanning-job:
- script: "echo 'hello world' > gl-dependency-scanning-report.json"
- artifacts:
- reports:
- dependency_scanning: 'gl-dependency-scanning-report.json'
- YAML
- end
-
- it 'creates pipeline with builds' do
- expect(pipeline).to be_persisted
- expect(pipeline).not_to have_yaml_errors
- expect(pipeline.builds.pluck(:name)).to contain_exactly('test-job', 'dependency-scanning-job')
- end
- end
-
- context 'with invalid config' do
- let(:config) do
- <<~YAML
- test-job:
- script: "echo 'hello world' > cobertura.xml"
- artifacts:
- reports:
- foo: 'bar'
- YAML
- end
-
- it 'creates pipeline with yaml errors' do
- expect(pipeline).to be_persisted
- expect(pipeline).to have_yaml_errors
- end
- end
- end
- end
-end
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index 76b84e3b4ab..602db66dba1 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -215,8 +215,8 @@ RSpec.describe Users::DestroyService do
end
end
- context "migrating associated records" do
- let!(:issue) { create(:issue, author: user) }
+ context 'migrating associated records' do
+ let!(:issue) { create(:issue, author: user) }
it 'delegates to the `MigrateToGhostUser` service to move associated records to the ghost user' do
expect_any_instance_of(Users::MigrateToGhostUserService).to receive(:execute).once.and_call_original
@@ -226,12 +226,14 @@ RSpec.describe Users::DestroyService do
expect(issue.reload.author).to be_ghost
end
- it 'does not run `MigrateToGhostUser` if hard_delete option is given' do
- expect_any_instance_of(Users::MigrateToGhostUserService).not_to receive(:execute)
+ context 'when hard_delete option is given' do
+ it 'will not ghost certain records' do
+ expect_any_instance_of(Users::MigrateToGhostUserService).to receive(:execute).once.and_call_original
- service.execute(user, hard_delete: true)
+ service.execute(user, hard_delete: true)
- expect(Issue.exists?(issue.id)).to be_falsy
+ expect(Issue.exists?(issue.id)).to be_falsy
+ end
end
end
diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb
index c36889f20ec..073ebaae5b0 100644
--- a/spec/services/users/migrate_to_ghost_user_service_spec.rb
+++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb
@@ -3,9 +3,10 @@
require 'spec_helper'
RSpec.describe Users::MigrateToGhostUserService do
- let!(:user) { create(:user) }
- let!(:project) { create(:project, :repository) }
- let(:service) { described_class.new(user) }
+ let!(:user) { create(:user) }
+ let!(:project) { create(:project, :repository) }
+ let(:service) { described_class.new(user) }
+ let(:always_ghost) { false }
context "migrating a user's associated records to the ghost user" do
context 'issues' do
diff --git a/spec/support/helpers/navbar_structure_helper.rb b/spec/support/helpers/navbar_structure_helper.rb
index 6fa69cbd6ad..fb06ebfdae2 100644
--- a/spec/support/helpers/navbar_structure_helper.rb
+++ b/spec/support/helpers/navbar_structure_helper.rb
@@ -77,6 +77,14 @@ module NavbarStructureHelper
)
end
+ def insert_harbor_registry_nav(within)
+ insert_after_sub_nav_item(
+ within,
+ within: _('Packages & Registries'),
+ new_sub_nav_item_name: _('Harbor Registry')
+ )
+ end
+
def insert_infrastructure_google_cloud_nav
insert_after_sub_nav_item(
_('Terraform'),
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index 2fbc01a9195..1e291a90163 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -42,6 +42,18 @@ RSpec.shared_examples "migrating a deleted user's associated records to the ghos
end
end
+ it 'will only migrate specific records during a hard_delete' do
+ service.execute(hard_delete: true)
+
+ migrated_record = record_class.find_by_id(record.id)
+
+ check_user = always_ghost ? User.ghost : user
+
+ migrated_fields.each do |field|
+ expect(migrated_record.public_send(field)).to eq(check_user)
+ end
+ end
+
context "race conditions" do
context "when #{record_class_name} migration fails and is rolled back" do
before do