summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/google_tag_manager/index.js47
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/delete_button.vue33
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue19
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js4
-rw-r--r--app/assets/stylesheets/page_bundles/issues_show.scss30
-rw-r--r--app/assets/stylesheets/pages/issues.scss29
-rw-r--r--app/graphql/queries/container_registry/get_container_repositories.query.graphql2
-rw-r--r--app/graphql/types/base_object.rb7
-rw-r--r--app/graphql/types/container_repository_type.rb1
-rw-r--r--app/helpers/namespaces_helper.rb9
-rw-r--r--app/views/projects/issues/show.html.haml1
-rw-r--r--app/workers/bulk_imports/entity_worker.rb29
-rw-r--r--app/workers/bulk_imports/export_request_worker.rb10
-rw-r--r--app/workers/bulk_imports/pipeline_worker.rb34
-rw-r--r--config/application.rb1
-rw-r--r--config/initializers/graphql.rb5
-rw-r--r--config/routes.rb2
-rw-r--r--db/docs/ci_namespace_mirrors.yml2
-rw-r--r--db/docs/ci_project_mirrors.yml2
-rw-r--r--db/docs/loose_foreign_keys_deleted_records.yml2
-rw-r--r--db/docs/namespaces_sync_events.yml2
-rw-r--r--db/docs/projects_sync_events.yml2
-rw-r--r--db/post_migrate/20220412044906_finalize_traversal_ids_background_migrations.rb12
-rw-r--r--db/schema_migrations/202204120449061
-rw-r--r--doc/administration/gitaly/recovery.md12
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/development/rake_tasks.md4
-rw-r--r--doc/user/application_security/api_fuzzing/index.md60
-rw-r--r--doc/user/application_security/dast_api/index.md60
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js67
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js10
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js2
-rw-r--r--spec/graphql/types/base_object_spec.rb20
-rw-r--r--spec/graphql/types/container_repository_details_type_spec.rb4
-rw-r--r--spec/graphql/types/container_repository_type_spec.rb4
-rw-r--r--spec/helpers/namespaces_helper_spec.rb11
-rw-r--r--spec/migrations/finalize_traversal_ids_background_migrations_spec.rb60
-rw-r--r--spec/workers/bulk_imports/entity_worker_spec.rb45
-rw-r--r--spec/workers/bulk_imports/export_request_worker_spec.rb18
-rw-r--r--spec/workers/bulk_imports/pipeline_worker_spec.rb65
41 files changed, 543 insertions, 190 deletions
diff --git a/app/assets/javascripts/google_tag_manager/index.js b/app/assets/javascripts/google_tag_manager/index.js
index f42152006d2..a44a5b30e1e 100644
--- a/app/assets/javascripts/google_tag_manager/index.js
+++ b/app/assets/javascripts/google_tag_manager/index.js
@@ -232,35 +232,40 @@ export const trackTransaction = (transactionDetails) => {
pushEnhancedEcommerceEvent('EECtransactionSuccess', eventData);
};
-export const trackAddToCartUsageTab = () => {
+export const pushEECproductAddToCartEvent = () => {
if (!isSupported()) {
return;
}
- const getStartedButton = document.querySelector('.js-buy-additional-minutes');
- getStartedButton.addEventListener('click', () => {
- window.dataLayer.push({
- event: 'EECproductAddToCart',
- ecommerce: {
- currencyCode: 'USD',
- add: {
- products: [
- {
- name: 'CI/CD Minutes',
- id: '0003',
- price: '10',
- brand: 'GitLab',
- category: 'DevOps',
- variant: 'add-on',
- quantity: 1,
- },
- ],
- },
+ window.dataLayer.push({
+ event: 'EECproductAddToCart',
+ ecommerce: {
+ currencyCode: 'USD',
+ add: {
+ products: [
+ {
+ name: 'CI/CD Minutes',
+ id: '0003',
+ price: '10',
+ brand: 'GitLab',
+ category: 'DevOps',
+ variant: 'add-on',
+ quantity: 1,
+ },
+ ],
},
- });
+ },
});
};
+export const trackAddToCartUsageTab = () => {
+ const getStartedButton = document.querySelector('.js-buy-additional-minutes');
+ if (!getStartedButton) {
+ return;
+ }
+ getStartedButton.addEventListener('click', pushEECproductAddToCartEvent);
+};
+
export const trackCombinedGroupProjectForm = () => {
if (!isSupported()) {
return;
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/delete_button.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/delete_button.vue
index e4a1a1a8266..bb1dac40b92 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/delete_button.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/delete_button.vue
@@ -1,13 +1,13 @@
<script>
-import { GlTooltipDirective, GlButton } from '@gitlab/ui';
+import { GlButton, GlLink, GlTooltip, GlSprintf } from '@gitlab/ui';
export default {
name: 'DeleteButton',
components: {
GlButton,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
+ GlLink,
+ GlTooltip,
+ GlSprintf,
},
props: {
title: {
@@ -18,6 +18,11 @@ export default {
type: String,
required: true,
},
+ tooltipLink: {
+ type: String,
+ default: '',
+ required: false,
+ },
disabled: {
type: Boolean,
default: false,
@@ -29,21 +34,12 @@ export default {
required: false,
},
},
- computed: {
- tooltipConfiguration() {
- return {
- disabled: this.tooltipDisabled,
- title: this.tooltipTitle,
- };
- },
- },
};
</script>
<template>
- <div v-gl-tooltip="tooltipConfiguration">
+ <div ref="deleteImageButton">
<gl-button
- v-gl-tooltip
:disabled="disabled"
:title="title"
:aria-label="title"
@@ -52,5 +48,14 @@ export default {
icon="remove"
@click="$emit('delete')"
/>
+ <gl-tooltip :target="() => $refs.deleteImageButton" :disabled="tooltipDisabled" placement="top">
+ <gl-sprintf :message="tooltipTitle">
+ <template #docLink="{ content }">
+ <gl-link v-if="tooltipLink" :href="tooltipLink" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-tooltip>
</div>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
index c1ec523574a..484903354e8 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
@@ -8,11 +8,13 @@ import ListItem from '~/vue_shared/components/registry/list_item.vue';
import {
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
LIST_DELETE_BUTTON_DISABLED,
+ LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION,
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_FAILED_DELETED_STATUS,
+ IMAGE_MIGRATING_STATE,
ROOT_IMAGE_TEXT,
} from '../../constants/index';
import DeleteButton from '../delete_button.vue';
@@ -32,6 +34,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ inject: ['config'],
props: {
item: {
type: Object,
@@ -44,13 +47,12 @@ export default {
},
},
i18n: {
- LIST_DELETE_BUTTON_DISABLED,
REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION,
},
computed: {
disabledDelete() {
- return !this.item.canDelete || this.deleting;
+ return !this.item.canDelete || this.deleting || this.migrating;
},
id() {
return getIdFromGraphQLId(this.item.id);
@@ -58,6 +60,9 @@ export default {
deleting() {
return this.item.status === IMAGE_DELETE_SCHEDULED_STATUS;
},
+ migrating() {
+ return this.item.migrationState === IMAGE_MIGRATING_STATE;
+ },
failedDelete() {
return this.item.status === IMAGE_FAILED_DELETED_STATUS;
},
@@ -83,6 +88,11 @@ export default {
routerLinkEvent() {
return this.deleting ? '' : 'click';
},
+ deleteButtonTooltipTitle() {
+ return this.migrating
+ ? LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION
+ : LIST_DELETE_BUTTON_DISABLED;
+ },
},
};
</script>
@@ -144,8 +154,9 @@ export default {
<delete-button
:title="$options.i18n.REMOVE_REPOSITORY_LABEL"
:disabled="disabledDelete"
- :tooltip-disabled="item.canDelete"
- :tooltip-title="$options.i18n.LIST_DELETE_BUTTON_DISABLED"
+ :tooltip-disabled="!disabledDelete"
+ :tooltip-link="config.containerRegistryImportingHelpPagePath"
+ :tooltip-title="deleteButtonTooltipTitle"
@delete="$emit('delete', item)"
/>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
index 7fa950ccfd0..c7022d6070f 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
@@ -14,6 +14,9 @@ export const LIST_INTRO_TEXT = s__(
export const LIST_DELETE_BUTTON_DISABLED = s__(
'ContainerRegistry|Missing or insufficient permission, delete button disabled',
);
+export const LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION = s__(
+ `ContainerRegistry|Image repository temporarily cannot be marked for deletion. Please try again in a few minutes. %{docLinkStart}More details%{docLinkEnd}`,
+);
export const REMOVE_REPOSITORY_LABEL = s__('ContainerRegistry|Remove repository');
export const REMOVE_REPOSITORY_MODAL_TEXT = s__(
'ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted.',
@@ -45,6 +48,7 @@ export const EMPTY_RESULT_MESSAGE = s__(
export const IMAGE_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED';
export const IMAGE_FAILED_DELETED_STATUS = 'DELETE_FAILED';
+export const IMAGE_MIGRATING_STATE = 'importing';
export const GRAPHQL_PAGE_SIZE = 10;
export const SORT_FIELDS = [
diff --git a/app/assets/stylesheets/page_bundles/issues_show.scss b/app/assets/stylesheets/page_bundles/issues_show.scss
new file mode 100644
index 00000000000..1642e6a39f3
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/issues_show.scss
@@ -0,0 +1,30 @@
+@import 'mixins_and_variables_and_functions';
+
+.description.work-items-enabled {
+ ul.task-list {
+ > li.task-list-item {
+ padding-inline-start: 2.5rem;
+
+ .js-add-task {
+ svg {
+ visibility: hidden;
+ }
+
+ &:focus svg {
+ visibility: visible;
+ }
+ }
+
+ > input.task-list-item-checkbox {
+ left: 1.25rem;
+ }
+
+ &:hover,
+ &:focus-within {
+ .js-add-task svg {
+ visibility: visible;
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index f127b0dc66c..04e0ef6631e 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -307,32 +307,3 @@ ul.related-merge-requests > li gl-emoji {
.issuable-header-slide-leave-to {
transform: translateY(-100%);
}
-
-.description.work-items-enabled {
- ul.task-list {
- > li.task-list-item {
- padding-inline-start: 2.5rem;
-
- .js-add-task {
- svg {
- visibility: hidden;
- }
-
- &:focus svg {
- visibility: visible;
- }
- }
-
- > input.task-list-item-checkbox {
- left: 1.25rem;
- }
-
- &:hover,
- &:focus-within {
- .js-add-task svg {
- visibility: visible;
- }
- }
- }
- }
-}
diff --git a/app/graphql/queries/container_registry/get_container_repositories.query.graphql b/app/graphql/queries/container_registry/get_container_repositories.query.graphql
index 40e2934a038..264878ccaa2 100644
--- a/app/graphql/queries/container_registry/get_container_repositories.query.graphql
+++ b/app/graphql/queries/container_registry/get_container_repositories.query.graphql
@@ -23,6 +23,7 @@ query getProjectContainerRepositories(
__typename
nodes {
id
+ migrationState
name
path
status
@@ -57,6 +58,7 @@ query getProjectContainerRepositories(
__typename
nodes {
id
+ migrationState
name
path
status
diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb
index b5797f07aa6..4ad88e43f52 100644
--- a/app/graphql/types/base_object.rb
+++ b/app/graphql/types/base_object.rb
@@ -9,6 +9,13 @@ module Types
field_class Types::BaseField
edge_type_class Types::BaseEdge
+ def self.authorize(*args)
+ raise 'Cannot redefine authorize' if @authorize_args && args.any?
+
+ @authorize_args = args.freeze if args.any?
+ @authorize_args || (superclass.respond_to?(:authorize) ? superclass.authorize : nil)
+ end
+
def self.accepts(*types)
@accepts ||= []
@accepts += types
diff --git a/app/graphql/types/container_repository_type.rb b/app/graphql/types/container_repository_type.rb
index 3cd3730010b..dddf9a3ee97 100644
--- a/app/graphql/types/container_repository_type.rb
+++ b/app/graphql/types/container_repository_type.rb
@@ -14,6 +14,7 @@ module Types
field :expiration_policy_started_at, Types::TimeType, null: true, description: 'Timestamp when the cleanup done by the expiration policy was started on the container repository.'
field :id, GraphQL::Types::ID, null: false, description: 'ID of the container repository.'
field :location, GraphQL::Types::String, null: false, description: 'URL of the container repository.'
+ field :migration_state, GraphQL::Types::String, null: false, description: 'Migration state of the container repository.'
field :name, GraphQL::Types::String, null: false, description: 'Name of the container repository.'
field :path, GraphQL::Types::String, null: false, description: 'Path of the container repository.'
field :project, Types::ProjectType, null: false, description: 'Project of the container registry.'
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 64b58d28fc9..cf386ee398a 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -88,6 +88,15 @@ module NamespacesHelper
}.to_json
end
+ def pipeline_usage_quota_app_data(namespace)
+ {
+ namespace_actual_plan_name: namespace.actual_plan_name,
+ namespace_path: namespace.full_path,
+ namespace_id: namespace.id,
+ page_size: page_size
+ }
+ end
+
private
# Many importers create a temporary Group, so use the real
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 6c6f98e0b20..3572d1d6556 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -2,6 +2,7 @@
- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues")
+- add_page_specific_style 'page_bundles/issues_show'
= render 'projects/issuable/show', issuable: @issue, api_awards_path: award_emoji_issue_api_path(@issue)
= render 'projects/invite_members_modal', project: @project
diff --git a/app/workers/bulk_imports/entity_worker.rb b/app/workers/bulk_imports/entity_worker.rb
index 902762eda2f..f6b1c693fe4 100644
--- a/app/workers/bulk_imports/entity_worker.rb
+++ b/app/workers/bulk_imports/entity_worker.rb
@@ -12,12 +12,24 @@ module BulkImports
worker_has_external_dependencies!
def perform(entity_id, current_stage = nil)
- return if stage_running?(entity_id, current_stage)
+ if stage_running?(entity_id, current_stage)
+ logger.info(
+ structured_payload(
+ entity_id: entity_id,
+ current_stage: current_stage,
+ message: 'Stage running'
+ )
+ )
+
+ return
+ end
logger.info(
- worker: self.class.name,
- entity_id: entity_id,
- current_stage: current_stage
+ structured_payload(
+ entity_id: entity_id,
+ current_stage: current_stage,
+ message: 'Stage starting'
+ )
)
next_pipeline_trackers_for(entity_id).each do |pipeline_tracker|
@@ -29,10 +41,11 @@ module BulkImports
end
rescue StandardError => e
logger.error(
- worker: self.class.name,
- entity_id: entity_id,
- current_stage: current_stage,
- error_message: e.message
+ structured_payload(
+ entity_id: entity_id,
+ current_stage: current_stage,
+ message: e.message
+ )
)
Gitlab::ErrorTracking.track_exception(e, entity_id: entity_id)
diff --git a/app/workers/bulk_imports/export_request_worker.rb b/app/workers/bulk_imports/export_request_worker.rb
index 21040178cee..0d3e4f013dd 100644
--- a/app/workers/bulk_imports/export_request_worker.rb
+++ b/app/workers/bulk_imports/export_request_worker.rb
@@ -42,10 +42,12 @@ module BulkImports
correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id
}
- Gitlab::Import::Logger.warn(
- attributes.merge(
- bulk_import_id: entity.bulk_import.id,
- bulk_import_entity_type: entity.source_type
+ Gitlab::Import::Logger.error(
+ structured_payload(
+ attributes.merge(
+ bulk_import_id: entity.bulk_import.id,
+ bulk_import_entity_type: entity.source_type
+ )
)
)
diff --git a/app/workers/bulk_imports/pipeline_worker.rb b/app/workers/bulk_imports/pipeline_worker.rb
index aad93eef753..1a98705c151 100644
--- a/app/workers/bulk_imports/pipeline_worker.rb
+++ b/app/workers/bulk_imports/pipeline_worker.rb
@@ -18,18 +18,20 @@ module BulkImports
if pipeline_tracker.present?
logger.info(
- worker: self.class.name,
- entity_id: pipeline_tracker.entity.id,
- pipeline_name: pipeline_tracker.pipeline_name
+ structured_payload(
+ entity_id: pipeline_tracker.entity.id,
+ pipeline_name: pipeline_tracker.pipeline_name
+ )
)
run(pipeline_tracker)
else
logger.error(
- worker: self.class.name,
- entity_id: entity_id,
- pipeline_tracker_id: pipeline_tracker_id,
- message: 'Unstarted pipeline not found'
+ structured_payload(
+ entity_id: entity_id,
+ pipeline_tracker_id: pipeline_tracker_id,
+ message: 'Unstarted pipeline not found'
+ )
)
end
@@ -63,10 +65,11 @@ module BulkImports
rescue BulkImports::NetworkError => e
if e.retriable?(pipeline_tracker)
logger.error(
- worker: self.class.name,
- entity_id: pipeline_tracker.entity.id,
- pipeline_name: pipeline_tracker.pipeline_name,
- message: "Retrying error: #{e.message}"
+ structured_payload(
+ entity_id: pipeline_tracker.entity.id,
+ pipeline_name: pipeline_tracker.pipeline_name,
+ message: "Retrying error: #{e.message}"
+ )
)
pipeline_tracker.update!(status_event: 'retry', jid: jid)
@@ -83,10 +86,11 @@ module BulkImports
pipeline_tracker.update!(status_event: 'fail_op', jid: jid)
logger.error(
- worker: self.class.name,
- entity_id: pipeline_tracker.entity.id,
- pipeline_name: pipeline_tracker.pipeline_name,
- message: exception.message
+ structured_payload(
+ entity_id: pipeline_tracker.entity.id,
+ pipeline_name: pipeline_tracker.pipeline_name,
+ message: exception.message
+ )
)
Gitlab::ErrorTracking.track_exception(
diff --git a/config/application.rb b/config/application.rb
index aa1a0b26d60..703b829edbb 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -272,6 +272,7 @@ module Gitlab
config.assets.precompile << "page_bundles/import.css"
config.assets.precompile << "page_bundles/incident_management_list.css"
config.assets.precompile << "page_bundles/issues_list.css"
+ config.assets.precompile << "page_bundles/issues_show.css"
config.assets.precompile << "page_bundles/jira_connect.css"
config.assets.precompile << "page_bundles/jira_connect_users.css"
config.assets.precompile << "page_bundles/learn_gitlab.css"
diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb
deleted file mode 100644
index 52c26e756a5..00000000000
--- a/config/initializers/graphql.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-GraphQL::ObjectType.accepts_definitions(authorize: GraphQL::Define.assign_metadata_key(:authorize))
-
-GraphQL::Schema::Object.accepts_definition(:authorize)
diff --git a/config/routes.rb b/config/routes.rb
index 972773467e6..41f06a412a3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -66,7 +66,7 @@ Rails.application.routes.draw do
end
Gitlab.ee do
- resources :company, only: [:new]
+ resource :company, only: [:new, :create], controller: 'company'
resources :groups, only: [:new, :create]
resources :projects, only: [:new, :create]
resources :groups_projects, only: [:new, :create] do
diff --git a/db/docs/ci_namespace_mirrors.yml b/db/docs/ci_namespace_mirrors.yml
index f3f831602b8..31349cfa94c 100644
--- a/db/docs/ci_namespace_mirrors.yml
+++ b/db/docs/ci_namespace_mirrors.yml
@@ -4,6 +4,6 @@ classes:
- Ci::NamespaceMirror
feature_categories:
- sharding
-description: TODO
+description: Mirrors some data from the `main` database into the `ci` database so that we can join directly in a single query
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75621
milestone: '14.6'
diff --git a/db/docs/ci_project_mirrors.yml b/db/docs/ci_project_mirrors.yml
index 93233edc3b3..f04fbf80596 100644
--- a/db/docs/ci_project_mirrors.yml
+++ b/db/docs/ci_project_mirrors.yml
@@ -4,6 +4,6 @@ classes:
- Ci::ProjectMirror
feature_categories:
- sharding
-description: TODO
+description: Mirrors some data from the `main` database into the `ci` database so that we can join directly in a single query
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75621
milestone: '14.6'
diff --git a/db/docs/loose_foreign_keys_deleted_records.yml b/db/docs/loose_foreign_keys_deleted_records.yml
index d099acd5f94..df26ffaefd1 100644
--- a/db/docs/loose_foreign_keys_deleted_records.yml
+++ b/db/docs/loose_foreign_keys_deleted_records.yml
@@ -4,6 +4,6 @@ classes:
- LooseForeignKeys::DeletedRecord
feature_categories:
- sharding
-description: TODO
+description: Used by the loose foreign keys feature as a queue of parent records whose child records (via foreign keys) need to be deleted/nullified
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70152
milestone: '14.3'
diff --git a/db/docs/namespaces_sync_events.yml b/db/docs/namespaces_sync_events.yml
index efc5c69c346..f674bfcf622 100644
--- a/db/docs/namespaces_sync_events.yml
+++ b/db/docs/namespaces_sync_events.yml
@@ -4,6 +4,6 @@ classes:
- Namespaces::SyncEvent
feature_categories:
- sharding
-description: TODO
+description: Used as a queue of data that needs to be synchronized between the `ci` and `main` database
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75517
milestone: '14.6'
diff --git a/db/docs/projects_sync_events.yml b/db/docs/projects_sync_events.yml
index 4b0d13839b7..cdc27423778 100644
--- a/db/docs/projects_sync_events.yml
+++ b/db/docs/projects_sync_events.yml
@@ -4,6 +4,6 @@ classes:
- Projects::SyncEvent
feature_categories:
- sharding
-description: TODO
+description: Used as a queue of data that needs to be synchronized between the `ci` and `main` database
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75517
milestone: '14.6'
diff --git a/db/post_migrate/20220412044906_finalize_traversal_ids_background_migrations.rb b/db/post_migrate/20220412044906_finalize_traversal_ids_background_migrations.rb
new file mode 100644
index 00000000000..f2aa9c0b717
--- /dev/null
+++ b/db/post_migrate/20220412044906_finalize_traversal_ids_background_migrations.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class FinalizeTraversalIdsBackgroundMigrations < Gitlab::Database::Migration[1.0]
+ def up
+ finalize_background_migration('BackfillNamespaceTraversalIdsRoots')
+ finalize_background_migration('BackfillNamespaceTraversalIdsChildren')
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/schema_migrations/20220412044906 b/db/schema_migrations/20220412044906
new file mode 100644
index 00000000000..a86fcfead61
--- /dev/null
+++ b/db/schema_migrations/20220412044906
@@ -0,0 +1 @@
+1d6ed98ad2da7be75e09d853db86905ed1fb1847d387cc6d1980ff5516db06d9 \ No newline at end of file
diff --git a/doc/administration/gitaly/recovery.md b/doc/administration/gitaly/recovery.md
index a7166f7e62e..4e3031ddf15 100644
--- a/doc/administration/gitaly/recovery.md
+++ b/doc/administration/gitaly/recovery.md
@@ -348,13 +348,23 @@ If this occurs, run `remove-repository` again.
### Manually list untracked repositories
-> [Introduced](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/3926) in GitLab 14.4.
+> - [Introduced](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/3926) in GitLab 14.4.
+> - `older-than` option added in GitLab 15.0.
The `list-untracked-repositories` Praefect sub-command lists repositories of the Gitaly Cluster that both:
- Exist for at least one Gitaly storage.
- Aren't tracked in the Praefect database.
+Add the `-older-than` option to avoid showing repositories that are the process of being created and for which a record doesn't yet exist in the
+Praefect database. Replace <duration> with a time duration (for example, `5s`, `10m`, or `1h`). Defaults to `6h`.
+
+```shell
+sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml list-untracked-repositories -older-than <duration>
+```
+
+Only repositories with a creation time before the specified duration are considered.
+
The command outputs:
- Result to `STDOUT` and the command's logs.
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d110e1994aa..7ef9897a170 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -9773,6 +9773,7 @@ A container repository.
| <a id="containerrepositoryexpirationpolicystartedat"></a>`expirationPolicyStartedAt` | [`Time`](#time) | Timestamp when the cleanup done by the expiration policy was started on the container repository. |
| <a id="containerrepositoryid"></a>`id` | [`ID!`](#id) | ID of the container repository. |
| <a id="containerrepositorylocation"></a>`location` | [`String!`](#string) | URL of the container repository. |
+| <a id="containerrepositorymigrationstate"></a>`migrationState` | [`String!`](#string) | Migration state of the container repository. |
| <a id="containerrepositoryname"></a>`name` | [`String!`](#string) | Name of the container repository. |
| <a id="containerrepositorypath"></a>`path` | [`String!`](#string) | Path of the container repository. |
| <a id="containerrepositoryproject"></a>`project` | [`Project!`](#project) | Project of the container registry. |
@@ -9794,6 +9795,7 @@ Details of a container repository.
| <a id="containerrepositorydetailsexpirationpolicystartedat"></a>`expirationPolicyStartedAt` | [`Time`](#time) | Timestamp when the cleanup done by the expiration policy was started on the container repository. |
| <a id="containerrepositorydetailsid"></a>`id` | [`ID!`](#id) | ID of the container repository. |
| <a id="containerrepositorydetailslocation"></a>`location` | [`String!`](#string) | URL of the container repository. |
+| <a id="containerrepositorydetailsmigrationstate"></a>`migrationState` | [`String!`](#string) | Migration state of the container repository. |
| <a id="containerrepositorydetailsname"></a>`name` | [`String!`](#string) | Name of the container repository. |
| <a id="containerrepositorydetailspath"></a>`path` | [`String!`](#string) | Path of the container repository. |
| <a id="containerrepositorydetailsproject"></a>`project` | [`Project!`](#project) | Project of the container registry. |
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 1e9367ecee4..0538add59b5 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -366,8 +366,8 @@ The docs generator code comes from our side giving us more flexibility, like usi
To edit the content, you may need to edit the following:
-- The template. You can edit the template at `lib/gitlab/graphql/docs/templates/default.md.haml`.
- The actual renderer is at `Gitlab::Graphql::Docs::Renderer`.
+- The template. You can edit the template at `tooling/graphql/docs/templates/default.md.haml`.
+ The actual renderer is at `Tooling::Graphql::Docs::Renderer`.
- The applicable `description` field in the code, which
[Updates machine-readable schema files](#update-machine-readable-schema-files),
which is then used by the `rake` task described earlier.
diff --git a/doc/user/application_security/api_fuzzing/index.md b/doc/user/application_security/api_fuzzing/index.md
index ea3701b78c2..ed94686b7a3 100644
--- a/doc/user/application_security/api_fuzzing/index.md
+++ b/doc/user/application_security/api_fuzzing/index.md
@@ -592,6 +592,7 @@ profile increases as the number of tests increases.
| `FUZZAPI_CONFIG` | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/276395) in GitLab 13.12, replaced with default `.gitlab/gitlab-api-fuzzing-config.yml`. API Fuzzing configuration file. |
|[`FUZZAPI_PROFILE`](#api-fuzzing-profiles) | Configuration profile to use during testing. Defaults to `Quick-10`. |
|[`FUZZAPI_EXCLUDE_PATHS`](#exclude-paths) | Exclude API URL paths from testing. |
+|[`FUZZAPI_EXCLUDE_URLS`](#exclude-urls) | Exclude API URL from testing. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357195) in GitLab 14.10. |
|[`FUZZAPI_EXCLUDE_PARAMETER_ENV`](#exclude-parameters) | JSON string containing excluded parameters. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292196) in GitLab 14.10. |
|[`FUZZAPI_EXCLUDE_PARAMETER_FILE`](#exclude-parameters) | Path to a JSON file containing excluded parameters. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292196) in GitLab 14.10. |
|[`FUZZAPI_OPENAPI`](#openapi-specification) | OpenAPI Specification file or URL. |
@@ -1295,6 +1296,65 @@ variables:
The `api-fuzzing-exclude-parameters.json` is a JSON document that follows the structure of [exclude parameters document](#exclude-parameters-using-a-json-document).
+### Exclude URLS
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357195) in GitLab 14.10.
+
+As an alternative to excluding by paths, you can filter by any other component in the URL by using the `FUZZAPI_EXCLUDE_URLS` CI/CD variable. This variable can be set in your `.gitlab-ci.yml` file. The variable can store multiple values, separated by commas (`,`). Each value is a regular expression. Because each entry is a regular expression, an entry such as `.*` excludes all URLs because it is a regular expression that matches everything.
+
+In your job output you can check if any URLs matched any provided regular expression from `FUZZAPI_EXCLUDE_URLS`. Matching operations are listed in the **Excluded Operations** section. Operations listed in the **Excluded Operations** should not be listed in the **Tested Operations** section. For example the following portion of a job output:
+
+```plaintext
+2021-05-27 21:51:08 [INF] API Security: --[ Tested Operations ]-------------------------
+2021-05-27 21:51:08 [INF] API Security: 201 POST http://target:7777/api/users CREATED
+2021-05-27 21:51:08 [INF] API Security: ------------------------------------------------
+2021-05-27 21:51:08 [INF] API Security: --[ Excluded Operations ]-----------------------
+2021-05-27 21:51:08 [INF] API Security: GET http://target:7777/api/messages
+2021-05-27 21:51:08 [INF] API Security: POST http://target:7777/api/messages
+2021-05-27 21:51:08 [INF] API Security: ------------------------------------------------
+```
+
+NOTE:
+Each value in `FUZZAPI_EXCLUDE_URLS` is a regular expression. Characters such as `.` , `*` and `$` among many others have special meanings in [regular expressions](https://en.wikipedia.org/wiki/Regular_expression#Standards).
+
+#### Examples
+
+##### Excluding a URL and child resources
+
+The following example excludes the URL `http://target/api/auth` and its child resources.
+
+```yaml
+variables:
+ FUZZAPI_EXCLUDE_URLS: http://target/api/auth
+```
+
+##### Excluding two URLs and allow their child resources
+
+To exclude the URLs `http://target/api/buy` and `http://target/api/sell` but allowing to scan their child resources, for instance: `http://target/api/buy/toy` or `http://target/api/sell/chair`. You could use the value `http://target/api/buy/$,http://target/api/sell/$`. This value is using two regular expressions, each of them separated by a `,` character. Hence, it contains `http://target/api/buy$` and `http://target/api/sell$`. In each regular expression, the trailing `$` character points out where the matching URL should end.
+
+```yaml
+variables:
+ FUZZAPI_EXCLUDE_URLS: http://target/api/buy/$,http://target/api/sell/$
+```
+
+##### Excluding two URLs and their child resources
+
+In order to exclude the URLs: `http://target/api/buy` and `http://target/api/sell`, and their child resources. To provide multiple URLs we use the `,` character as follows:
+
+```yaml
+variables:
+ FUZZAPI_EXCLUDE_URLS: http://target/api/buy,http://target/api/sell
+```
+
+##### Excluding URL using regular expressions
+
+In order to exclude exactly `https://target/api/v1/user/create` and `https://target/api/v2/user/create` or any other version (`v3`,`v4`, and more). We could use `https://target/api/v.*/user/create$`, in the previous regular expression `.` indicates any character and `*` indicates zero or more times, additionally `$` indicates that the URL should end there.
+
+```yaml
+variables:
+ FUZZAPI_EXCLUDE_URLS: https://target/api/v.*/user/create$
+```
+
### Header Fuzzing
Header fuzzing is disabled by default due to the high number of false positives that occur with many
diff --git a/doc/user/application_security/dast_api/index.md b/doc/user/application_security/dast_api/index.md
index c60f6f92c74..a4908204b60 100644
--- a/doc/user/application_security/dast_api/index.md
+++ b/doc/user/application_security/dast_api/index.md
@@ -544,6 +544,7 @@ can be added, removed, and modified by creating a custom configuration.
|[`DAST_API_CONFIG`](#configuration-files) | DAST API configuration file. Defaults to `.gitlab-dast-api.yml`. |
|[`DAST_API_PROFILE`](#configuration-files) | Configuration profile to use during testing. Defaults to `Quick`. |
|[`DAST_API_EXCLUDE_PATHS`](#exclude-paths) | Exclude API URL paths from testing. |
+|[`DAST_API_EXCLUDE_URLS`](#exclude-urls) | Exclude API URL from testing. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357195) in GitLab 14.10. |
|[`DAST_API_EXCLUDE_PARAMETER_ENV`](#exclude-parameters) | JSON string containing excluded parameters. |
|[`DAST_API_EXCLUDE_PARAMETER_FILE`](#exclude-parameters) | Path to a JSON file containing excluded parameters. |
|[`DAST_API_OPENAPI`](#openapi-specification) | OpenAPI specification file or URL. |
@@ -1249,6 +1250,65 @@ variables:
The `dast-api-exclude-parameters.json` is a JSON document that follows the structure of [exclude parameters document](#exclude-parameters-using-a-json-document).
+### Exclude URLS
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357195) in GitLab 14.10.
+
+As an alternative to excluding by paths, you can filter by any other component in the URL by using the `DAST_API_EXCLUDE_URLS` CI/CD variable. This variable can be set in your `.gitlab-ci.yml` file. The variable can store multiple values, separated by commas (`,`). Each value is a regular expression. Because each entry is a regular expression, an entry like `.*` will exclude all URLs because it is a regular expression that matches everything.
+
+In your job output you can check if any URLs matched any provided regular expression from `DAST_API_EXCLUDE_URLS`. Matching operations are listed in the **Excluded Operations** section. Operations listed in the **Excluded Operations** should not be listed in the **Tested Operations** section. For example the following portion of a job output:
+
+```plaintext
+2021-05-27 21:51:08 [INF] API Security: --[ Tested Operations ]-------------------------
+2021-05-27 21:51:08 [INF] API Security: 201 POST http://target:7777/api/users CREATED
+2021-05-27 21:51:08 [INF] API Security: ------------------------------------------------
+2021-05-27 21:51:08 [INF] API Security: --[ Excluded Operations ]-----------------------
+2021-05-27 21:51:08 [INF] API Security: GET http://target:7777/api/messages
+2021-05-27 21:51:08 [INF] API Security: POST http://target:7777/api/messages
+2021-05-27 21:51:08 [INF] API Security: ------------------------------------------------
+```
+
+NOTE:
+Each value in `DAST_API_EXCLUDE_URLS` is a regular expression. Characters such as `.` , `*` and `$` among many others have special meanings in [regular expressions](https://en.wikipedia.org/wiki/Regular_expression#Standards).
+
+#### Examples
+
+##### Excluding a URL and child resources
+
+The following example excludes the URL `http://target/api/auth` and its child resources.
+
+```yaml
+variables:
+ DAST_API_EXCLUDE_URLS: http://target/api/auth
+```
+
+##### Excluding two URLs and allow their child resources
+
+To exclude the URLs `http://target/api/buy` and `http://target/api/sell` but allowing to scan their child resources, for instance: `http://target/api/buy/toy` or `http://target/api/sell/chair`. You could use the value `http://target/api/buy/$,http://target/api/sell/$`. This value is using two regular expressions, each of them separated by a `,` character. Hence, it contains `http://target/api/buy$` and `http://target/api/sell$`. In each regular expression, the trailing `$` character points out where the matching URL should end.
+
+```yaml
+variables:
+ DAST_API_EXCLUDE_URLS: http://target/api/buy/$,http://target/api/sell/$
+```
+
+##### Excluding two URLs and their child resources
+
+In order to exclude the URLs: `http://target/api/buy` and `http://target/api/sell`, and their child resources. To provide multiple URLs we use the `,` character as follows:
+
+```yaml
+variables:
+ DAST_API_EXCLUDE_URLS: http://target/api/buy,http://target/api/sell
+```
+
+##### Excluding URL using regular expressions
+
+In order to exclude exactly `https://target/api/v1/user/create` and `https://target/api/v2/user/create` or any other version (`v3`,`v4`, and more). We could use `https://target/api/v.*/user/create$`, in the previous regular expression `.` indicates any character and `*` indicates zero or more times, additionally `$` indicates that the URL should end there.
+
+```yaml
+variables:
+ DAST_API_EXCLUDE_URLS: https://target/api/v.*/user/create$
+```
+
## Running your first scan
When configured correctly, a CI/CD pipeline contains a `dast` stage and an `dast_api` job. The job only fails when an invalid configuration is provided. During normal operation, the job always succeeds even if vulnerabilities are identified during testing.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f6e44bc8de3..b7c3378db63 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9773,6 +9773,9 @@ msgstr ""
msgid "ContainerRegistry|Image repository not found"
msgstr ""
+msgid "ContainerRegistry|Image repository temporarily cannot be marked for deletion. Please try again in a few minutes. %{docLinkStart}More details%{docLinkEnd}"
+msgstr ""
+
msgid "ContainerRegistry|Image repository will be deleted"
msgstr ""
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js
index 6d7bf528495..ad67128502a 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js
@@ -1,7 +1,7 @@
-import { GlButton } from '@gitlab/ui';
+import { GlButton, GlTooltip, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import component from '~/packages_and_registries/container_registry/explorer/components/delete_button.vue';
+import { LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION } from '~/packages_and_registries/container_registry/explorer/constants/list';
describe('delete_button', () => {
let wrapper;
@@ -12,6 +12,7 @@ describe('delete_button', () => {
};
const findButton = () => wrapper.find(GlButton);
+ const findTooltip = () => wrapper.find(GlTooltip);
const mountComponent = (props) => {
wrapper = shallowMount(component, {
@@ -19,8 +20,9 @@ describe('delete_button', () => {
...defaultProps,
...props,
},
- directives: {
- GlTooltip: createMockDirective(),
+ stubs: {
+ GlTooltip,
+ GlSprintf,
},
});
};
@@ -33,41 +35,50 @@ describe('delete_button', () => {
describe('tooltip', () => {
it('the title is controlled by tooltipTitle prop', () => {
mountComponent();
- const tooltip = getBinding(wrapper.element, 'gl-tooltip');
+ const tooltip = findTooltip();
expect(tooltip).toBeDefined();
- expect(tooltip.value.title).toBe(defaultProps.tooltipTitle);
+ expect(tooltip.text()).toBe(defaultProps.tooltipTitle);
});
it('is disabled when tooltipTitle is disabled', () => {
mountComponent({ tooltipDisabled: true });
- const tooltip = getBinding(wrapper.element, 'gl-tooltip');
- expect(tooltip.value.disabled).toBe(true);
+ expect(findTooltip().props('disabled')).toBe(true);
});
- describe('button', () => {
- it('exists', () => {
- mountComponent();
- expect(findButton().exists()).toBe(true);
+ it('works with a link', () => {
+ mountComponent({
+ tooltipTitle: LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
+ tooltipLink: 'foo',
});
+ expect(findTooltip().text()).toMatchInterpolatedText(
+ LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
+ );
+ });
+ });
- it('has the correct props/attributes bound', () => {
- mountComponent({ disabled: true });
- expect(findButton().attributes()).toMatchObject({
- 'aria-label': 'Foo title',
- icon: 'remove',
- title: 'Foo title',
- variant: 'danger',
- disabled: 'true',
- category: 'secondary',
- });
- });
+ describe('button', () => {
+ it('exists', () => {
+ mountComponent();
+ expect(findButton().exists()).toBe(true);
+ });
- it('emits a delete event', () => {
- mountComponent();
- expect(wrapper.emitted('delete')).toEqual(undefined);
- findButton().vm.$emit('click');
- expect(wrapper.emitted('delete')).toEqual([[]]);
+ it('has the correct props/attributes bound', () => {
+ mountComponent({ disabled: true });
+ expect(findButton().attributes()).toMatchObject({
+ 'aria-label': 'Foo title',
+ icon: 'remove',
+ title: 'Foo title',
+ variant: 'danger',
+ disabled: 'true',
+ category: 'secondary',
});
});
+
+ it('emits a delete event', () => {
+ mountComponent();
+ expect(wrapper.emitted('delete')).toEqual(undefined);
+ findButton().vm.$emit('click');
+ expect(wrapper.emitted('delete')).toEqual([[]]);
+ });
});
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
index 411bef54e40..690d827ec67 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
@@ -10,6 +10,7 @@ import {
LIST_DELETE_BUTTON_DISABLED,
REMOVE_REPOSITORY_LABEL,
IMAGE_DELETE_SCHEDULED_STATUS,
+ IMAGE_MIGRATING_STATE,
SCHEDULED_STATUS,
ROOT_IMAGE_TEXT,
} from '~/packages_and_registries/container_registry/explorer/constants';
@@ -41,6 +42,9 @@ describe('Image List Row', () => {
item,
...props,
},
+ provide: {
+ config: {},
+ },
directives: {
GlTooltip: createMockDirective(),
},
@@ -178,6 +182,12 @@ describe('Image List Row', () => {
expect(findDeleteBtn().props('disabled')).toBe(state);
},
);
+
+ it('is disabled when migrationState is importing', () => {
+ mountComponent({ item: { ...item, migrationState: IMAGE_MIGRATING_STATE } });
+
+ expect(findDeleteBtn().props('disabled')).toBe(true);
+ });
});
describe('tags count', () => {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js
index fda1db4b7e1..7e6f88fe5bc 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js
@@ -5,6 +5,7 @@ export const imagesListResponse = [
name: 'rails-12009',
path: 'gitlab-org/gitlab-test/rails-12009',
status: null,
+ migrationState: 'default',
location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-12009',
canDelete: true,
createdAt: '2020-11-03T13:29:21Z',
@@ -17,6 +18,7 @@ export const imagesListResponse = [
name: 'rails-20572',
path: 'gitlab-org/gitlab-test/rails-20572',
status: null,
+ migrationState: 'default',
location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-20572',
canDelete: true,
createdAt: '2020-09-21T06:57:43Z',
diff --git a/spec/graphql/types/base_object_spec.rb b/spec/graphql/types/base_object_spec.rb
index d8f2ef58ea5..45dc885ecba 100644
--- a/spec/graphql/types/base_object_spec.rb
+++ b/spec/graphql/types/base_object_spec.rb
@@ -428,5 +428,25 @@ RSpec.describe Types::BaseObject do
expect(result.dig('data', 'users', 'nodes'))
.to contain_exactly({ 'name' => active_users.first.name })
end
+
+ describe '.authorize' do
+ let_it_be(:read_only_type) do
+ Class.new(described_class) do
+ authorize :read_only
+ end
+ end
+
+ let_it_be(:inherited_read_only_type) { Class.new(read_only_type) }
+
+ it 'keeps track of the specified value' do
+ expect(described_class.authorize).to be_nil
+ expect(read_only_type.authorize).to match_array [:read_only]
+ expect(inherited_read_only_type.authorize).to match_array [:read_only]
+ end
+
+ it 'can not redefine the authorize value' do
+ expect { read_only_type.authorize(:write_only) }.to raise_error('Cannot redefine authorize')
+ end
+ end
end
end
diff --git a/spec/graphql/types/container_repository_details_type_spec.rb b/spec/graphql/types/container_repository_details_type_spec.rb
index aa770284f89..d94516c6fce 100644
--- a/spec/graphql/types/container_repository_details_type_spec.rb
+++ b/spec/graphql/types/container_repository_details_type_spec.rb
@@ -3,7 +3,9 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do
- fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status tags size project]
+ fields = %i[id name path location created_at updated_at expiration_policy_started_at
+ status tags_count can_delete expiration_policy_cleanup_status tags size
+ project migration_state]
it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') }
diff --git a/spec/graphql/types/container_repository_type_spec.rb b/spec/graphql/types/container_repository_type_spec.rb
index 87e1c11ce19..9815449dd68 100644
--- a/spec/graphql/types/container_repository_type_spec.rb
+++ b/spec/graphql/types/container_repository_type_spec.rb
@@ -3,7 +3,9 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['ContainerRepository'] do
- fields = %i[id name path location created_at updated_at expiration_policy_started_at status tags_count can_delete expiration_policy_cleanup_status project]
+ fields = %i[id name path location created_at updated_at expiration_policy_started_at
+ status tags_count can_delete expiration_policy_cleanup_status project
+ migration_state]
it { expect(described_class.graphql_name).to eq('ContainerRepository') }
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index 00aa0fd1cba..52c1130e818 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -268,4 +268,15 @@ RSpec.describe NamespacesHelper do
end
end
end
+
+ describe '#pipeline_usage_quota_app_data' do
+ it 'returns a hash with necessary data for the frontend' do
+ expect(helper.pipeline_usage_quota_app_data(user_group)).to eql({
+ namespace_actual_plan_name: user_group.actual_plan_name,
+ namespace_path: user_group.full_path,
+ namespace_id: user_group.id,
+ page_size: Kaminari.config.default_per_page
+ })
+ end
+ end
end
diff --git a/spec/migrations/finalize_traversal_ids_background_migrations_spec.rb b/spec/migrations/finalize_traversal_ids_background_migrations_spec.rb
new file mode 100644
index 00000000000..74d6447e6a7
--- /dev/null
+++ b/spec/migrations/finalize_traversal_ids_background_migrations_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!('finalize_traversal_ids_background_migrations')
+
+RSpec.describe FinalizeTraversalIdsBackgroundMigrations, :migration do
+ shared_context 'incomplete background migration' do
+ before do
+ # Jobs enqueued in Sidekiq.
+ Sidekiq::Testing.disable! do
+ BackgroundMigrationWorker.perform_in(10, job_class_name, [1, 2, 100])
+ BackgroundMigrationWorker.perform_in(20, job_class_name, [3, 4, 100])
+ end
+
+ # Jobs tracked in the database.
+ # table(:background_migration_jobs).create!(
+ Gitlab::Database::BackgroundMigrationJob.create!(
+ class_name: job_class_name,
+ arguments: [5, 6, 100],
+ status: Gitlab::Database::BackgroundMigrationJob.statuses['pending']
+ )
+ # table(:background_migration_jobs).create!(
+ Gitlab::Database::BackgroundMigrationJob.create!(
+ class_name: job_class_name,
+ arguments: [7, 8, 100],
+ status: Gitlab::Database::BackgroundMigrationJob.statuses['succeeded']
+ )
+ end
+ end
+
+ context 'BackfillNamespaceTraversalIdsRoots background migration' do
+ let(:job_class_name) { 'BackfillNamespaceTraversalIdsRoots' }
+
+ include_context 'incomplete background migration'
+
+ before do
+ migrate!
+ end
+
+ it_behaves_like(
+ 'finalized tracked background migration',
+ Gitlab::BackgroundMigration::BackfillNamespaceTraversalIdsRoots
+ )
+ end
+
+ context 'BackfillNamespaceTraversalIdsChildren background migration' do
+ let(:job_class_name) { 'BackfillNamespaceTraversalIdsChildren' }
+
+ include_context 'incomplete background migration'
+
+ before do
+ migrate!
+ end
+
+ it_behaves_like(
+ 'finalized tracked background migration',
+ Gitlab::BackgroundMigration::BackfillNamespaceTraversalIdsChildren
+ )
+ end
+end
diff --git a/spec/workers/bulk_imports/entity_worker_spec.rb b/spec/workers/bulk_imports/entity_worker_spec.rb
index ce45299c7f7..ab85b587975 100644
--- a/spec/workers/bulk_imports/entity_worker_spec.rb
+++ b/spec/workers/bulk_imports/entity_worker_spec.rb
@@ -36,9 +36,11 @@ RSpec.describe BulkImports::EntityWorker do
expect(logger)
.to receive(:info).twice
.with(
- worker: described_class.name,
- entity_id: entity.id,
- current_stage: nil
+ hash_including(
+ 'entity_id' => entity.id,
+ 'current_stage' => nil,
+ 'message' => 'Stage starting'
+ )
)
end
@@ -58,24 +60,26 @@ RSpec.describe BulkImports::EntityWorker do
expect(BulkImports::PipelineWorker)
.to receive(:perform_async)
- .and_raise(exception)
+ .and_raise(exception)
expect_next_instance_of(Gitlab::Import::Logger) do |logger|
expect(logger)
.to receive(:info).twice
.with(
- worker: described_class.name,
- entity_id: entity.id,
- current_stage: nil
+ hash_including(
+ 'entity_id' => entity.id,
+ 'current_stage' => nil
+ )
)
expect(logger)
.to receive(:error)
.with(
- worker: described_class.name,
- entity_id: entity.id,
- current_stage: nil,
- error_message: 'Error!'
+ hash_including(
+ 'entity_id' => entity.id,
+ 'current_stage' => nil,
+ 'message' => 'Error!'
+ )
)
end
@@ -90,6 +94,18 @@ RSpec.describe BulkImports::EntityWorker do
let(:job_args) { [entity.id, 0] }
it 'do not enqueue a new pipeline job if the current stage still running' do
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info).twice
+ .with(
+ hash_including(
+ 'entity_id' => entity.id,
+ 'current_stage' => 0,
+ 'message' => 'Stage running'
+ )
+ )
+ end
+
expect(BulkImports::PipelineWorker)
.not_to receive(:perform_async)
@@ -110,9 +126,10 @@ RSpec.describe BulkImports::EntityWorker do
expect(logger)
.to receive(:info).twice
.with(
- worker: described_class.name,
- entity_id: entity.id,
- current_stage: 0
+ hash_including(
+ 'entity_id' => entity.id,
+ 'current_stage' => 0
+ )
)
end
diff --git a/spec/workers/bulk_imports/export_request_worker_spec.rb b/spec/workers/bulk_imports/export_request_worker_spec.rb
index 4f452e3dd60..846df63a4d7 100644
--- a/spec/workers/bulk_imports/export_request_worker_spec.rb
+++ b/spec/workers/bulk_imports/export_request_worker_spec.rb
@@ -35,14 +35,16 @@ RSpec.describe BulkImports::ExportRequestWorker do
expect(client).to receive(:post).and_raise(BulkImports::NetworkError, 'Export error').twice
end
- expect(Gitlab::Import::Logger).to receive(:warn).with(
- bulk_import_entity_id: entity.id,
- pipeline_class: 'ExportRequestWorker',
- exception_class: 'BulkImports::NetworkError',
- exception_message: 'Export error',
- correlation_id_value: anything,
- bulk_import_id: bulk_import.id,
- bulk_import_entity_type: entity.source_type
+ expect(Gitlab::Import::Logger).to receive(:error).with(
+ hash_including(
+ 'bulk_import_entity_id' => entity.id,
+ 'pipeline_class' => 'ExportRequestWorker',
+ 'exception_class' => 'BulkImports::NetworkError',
+ 'exception_message' => 'Export error',
+ 'correlation_id_value' => anything,
+ 'bulk_import_id' => bulk_import.id,
+ 'bulk_import_entity_type' => entity.source_type
+ )
).twice
perform_multiple(job_args)
diff --git a/spec/workers/bulk_imports/pipeline_worker_spec.rb b/spec/workers/bulk_imports/pipeline_worker_spec.rb
index cb7e70a6749..3578fec5bc0 100644
--- a/spec/workers/bulk_imports/pipeline_worker_spec.rb
+++ b/spec/workers/bulk_imports/pipeline_worker_spec.rb
@@ -34,9 +34,10 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:info)
.with(
- worker: described_class.name,
- pipeline_name: 'FakePipeline',
- entity_id: entity.id
+ hash_including(
+ 'pipeline_name' => 'FakePipeline',
+ 'entity_id' => entity.id
+ )
)
end
@@ -44,7 +45,7 @@ RSpec.describe BulkImports::PipelineWorker do
.to receive(:perform_async)
.with(entity.id, pipeline_tracker.stage)
- expect(subject).to receive(:jid).and_return('jid')
+ allow(subject).to receive(:jid).and_return('jid')
subject.perform(pipeline_tracker.id, pipeline_tracker.stage, entity.id)
@@ -79,10 +80,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
- worker: described_class.name,
- pipeline_tracker_id: pipeline_tracker.id,
- entity_id: entity.id,
- message: 'Unstarted pipeline not found'
+ hash_including(
+ 'pipeline_tracker_id' => pipeline_tracker.id,
+ 'entity_id' => entity.id,
+ 'message' => 'Unstarted pipeline not found'
+ )
)
end
@@ -107,10 +109,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
- worker: described_class.name,
- pipeline_name: 'InexistentPipeline',
- entity_id: entity.id,
- message: "'InexistentPipeline' is not a valid BulkImport Pipeline"
+ hash_including(
+ 'pipeline_name' => 'InexistentPipeline',
+ 'entity_id' => entity.id,
+ 'message' => "'InexistentPipeline' is not a valid BulkImport Pipeline"
+ )
)
end
@@ -126,7 +129,7 @@ RSpec.describe BulkImports::PipelineWorker do
.to receive(:perform_async)
.with(entity.id, pipeline_tracker.stage)
- expect(subject).to receive(:jid).and_return('jid')
+ allow(subject).to receive(:jid).and_return('jid')
subject.perform(pipeline_tracker.id, pipeline_tracker.stage, entity.id)
@@ -151,10 +154,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
- worker: described_class.name,
- pipeline_name: 'Pipeline',
- entity_id: entity.id,
- message: 'Failed entity status'
+ hash_including(
+ 'pipeline_name' => 'Pipeline',
+ 'entity_id' => entity.id,
+ 'message' => 'Failed entity status'
+ )
)
end
@@ -183,7 +187,7 @@ RSpec.describe BulkImports::PipelineWorker do
.and_raise(exception)
end
- expect(subject).to receive(:jid).and_return('jid').twice
+ allow(subject).to receive(:jid).and_return('jid')
expect_any_instance_of(BulkImports::Tracker) do |tracker|
expect(tracker).to receive(:retry).and_call_original
@@ -193,9 +197,10 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:info)
.with(
- worker: described_class.name,
- pipeline_name: 'FakePipeline',
- entity_id: entity.id
+ hash_including(
+ 'pipeline_name' => 'FakePipeline',
+ 'entity_id' => entity.id
+ )
)
end
@@ -292,10 +297,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
- worker: described_class.name,
- pipeline_name: 'NdjsonPipeline',
- entity_id: entity.id,
- message: 'Pipeline timeout'
+ hash_including(
+ 'pipeline_name' => 'NdjsonPipeline',
+ 'entity_id' => entity.id,
+ 'message' => 'Pipeline timeout'
+ )
)
end
@@ -318,10 +324,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger)
.to receive(:error)
.with(
- worker: described_class.name,
- pipeline_name: 'NdjsonPipeline',
- entity_id: entity.id,
- message: 'Error!'
+ hash_including(
+ 'pipeline_name' => 'NdjsonPipeline',
+ 'entity_id' => entity.id,
+ 'message' => 'Error!'
+ )
)
end