Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-04-20 00:09:27 +00:00
parent 3007cf75a9
commit afbf001676
41 changed files with 543 additions and 190 deletions

View File

@ -232,13 +232,11 @@ export const trackTransaction = (transactionDetails) => {
pushEnhancedEcommerceEvent('EECtransactionSuccess', eventData); pushEnhancedEcommerceEvent('EECtransactionSuccess', eventData);
}; };
export const trackAddToCartUsageTab = () => { export const pushEECproductAddToCartEvent = () => {
if (!isSupported()) { if (!isSupported()) {
return; return;
} }
const getStartedButton = document.querySelector('.js-buy-additional-minutes');
getStartedButton.addEventListener('click', () => {
window.dataLayer.push({ window.dataLayer.push({
event: 'EECproductAddToCart', event: 'EECproductAddToCart',
ecommerce: { ecommerce: {
@ -258,7 +256,14 @@ export const trackAddToCartUsageTab = () => {
}, },
}, },
}); });
}); };
export const trackAddToCartUsageTab = () => {
const getStartedButton = document.querySelector('.js-buy-additional-minutes');
if (!getStartedButton) {
return;
}
getStartedButton.addEventListener('click', pushEECproductAddToCartEvent);
}; };
export const trackCombinedGroupProjectForm = () => { export const trackCombinedGroupProjectForm = () => {

View File

@ -1,13 +1,13 @@
<script> <script>
import { GlTooltipDirective, GlButton } from '@gitlab/ui'; import { GlButton, GlLink, GlTooltip, GlSprintf } from '@gitlab/ui';
export default { export default {
name: 'DeleteButton', name: 'DeleteButton',
components: { components: {
GlButton, GlButton,
}, GlLink,
directives: { GlTooltip,
GlTooltip: GlTooltipDirective, GlSprintf,
}, },
props: { props: {
title: { title: {
@ -18,6 +18,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
tooltipLink: {
type: String,
default: '',
required: false,
},
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false, default: false,
@ -29,21 +34,12 @@ export default {
required: false, required: false,
}, },
}, },
computed: {
tooltipConfiguration() {
return {
disabled: this.tooltipDisabled,
title: this.tooltipTitle,
};
},
},
}; };
</script> </script>
<template> <template>
<div v-gl-tooltip="tooltipConfiguration"> <div ref="deleteImageButton">
<gl-button <gl-button
v-gl-tooltip
:disabled="disabled" :disabled="disabled"
:title="title" :title="title"
:aria-label="title" :aria-label="title"
@ -52,5 +48,14 @@ export default {
icon="remove" icon="remove"
@click="$emit('delete')" @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> </div>
</template> </template>

View File

@ -8,11 +8,13 @@ import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { import {
ASYNC_DELETE_IMAGE_ERROR_MESSAGE, ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
LIST_DELETE_BUTTON_DISABLED, LIST_DELETE_BUTTON_DISABLED,
LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
REMOVE_REPOSITORY_LABEL, REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION, ROW_SCHEDULED_FOR_DELETION,
CLEANUP_TIMED_OUT_ERROR_MESSAGE, CLEANUP_TIMED_OUT_ERROR_MESSAGE,
IMAGE_DELETE_SCHEDULED_STATUS, IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_FAILED_DELETED_STATUS, IMAGE_FAILED_DELETED_STATUS,
IMAGE_MIGRATING_STATE,
ROOT_IMAGE_TEXT, ROOT_IMAGE_TEXT,
} from '../../constants/index'; } from '../../constants/index';
import DeleteButton from '../delete_button.vue'; import DeleteButton from '../delete_button.vue';
@ -32,6 +34,7 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
inject: ['config'],
props: { props: {
item: { item: {
type: Object, type: Object,
@ -44,13 +47,12 @@ export default {
}, },
}, },
i18n: { i18n: {
LIST_DELETE_BUTTON_DISABLED,
REMOVE_REPOSITORY_LABEL, REMOVE_REPOSITORY_LABEL,
ROW_SCHEDULED_FOR_DELETION, ROW_SCHEDULED_FOR_DELETION,
}, },
computed: { computed: {
disabledDelete() { disabledDelete() {
return !this.item.canDelete || this.deleting; return !this.item.canDelete || this.deleting || this.migrating;
}, },
id() { id() {
return getIdFromGraphQLId(this.item.id); return getIdFromGraphQLId(this.item.id);
@ -58,6 +60,9 @@ export default {
deleting() { deleting() {
return this.item.status === IMAGE_DELETE_SCHEDULED_STATUS; return this.item.status === IMAGE_DELETE_SCHEDULED_STATUS;
}, },
migrating() {
return this.item.migrationState === IMAGE_MIGRATING_STATE;
},
failedDelete() { failedDelete() {
return this.item.status === IMAGE_FAILED_DELETED_STATUS; return this.item.status === IMAGE_FAILED_DELETED_STATUS;
}, },
@ -83,6 +88,11 @@ export default {
routerLinkEvent() { routerLinkEvent() {
return this.deleting ? '' : 'click'; return this.deleting ? '' : 'click';
}, },
deleteButtonTooltipTitle() {
return this.migrating
? LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION
: LIST_DELETE_BUTTON_DISABLED;
},
}, },
}; };
</script> </script>
@ -144,8 +154,9 @@ export default {
<delete-button <delete-button
:title="$options.i18n.REMOVE_REPOSITORY_LABEL" :title="$options.i18n.REMOVE_REPOSITORY_LABEL"
:disabled="disabledDelete" :disabled="disabledDelete"
:tooltip-disabled="item.canDelete" :tooltip-disabled="!disabledDelete"
:tooltip-title="$options.i18n.LIST_DELETE_BUTTON_DISABLED" :tooltip-link="config.containerRegistryImportingHelpPagePath"
:tooltip-title="deleteButtonTooltipTitle"
@delete="$emit('delete', item)" @delete="$emit('delete', item)"
/> />
</template> </template>

View File

@ -14,6 +14,9 @@ export const LIST_INTRO_TEXT = s__(
export const LIST_DELETE_BUTTON_DISABLED = s__( export const LIST_DELETE_BUTTON_DISABLED = s__(
'ContainerRegistry|Missing or insufficient permission, delete button disabled', '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_LABEL = s__('ContainerRegistry|Remove repository');
export const REMOVE_REPOSITORY_MODAL_TEXT = s__( export const REMOVE_REPOSITORY_MODAL_TEXT = s__(
'ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted.', '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_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED';
export const IMAGE_FAILED_DELETED_STATUS = 'DELETE_FAILED'; export const IMAGE_FAILED_DELETED_STATUS = 'DELETE_FAILED';
export const IMAGE_MIGRATING_STATE = 'importing';
export const GRAPHQL_PAGE_SIZE = 10; export const GRAPHQL_PAGE_SIZE = 10;
export const SORT_FIELDS = [ export const SORT_FIELDS = [

View File

@ -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;
}
}
}
}
}

View File

@ -307,32 +307,3 @@ ul.related-merge-requests > li gl-emoji {
.issuable-header-slide-leave-to { .issuable-header-slide-leave-to {
transform: translateY(-100%); 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;
}
}
}
}
}

View File

@ -23,6 +23,7 @@ query getProjectContainerRepositories(
__typename __typename
nodes { nodes {
id id
migrationState
name name
path path
status status
@ -57,6 +58,7 @@ query getProjectContainerRepositories(
__typename __typename
nodes { nodes {
id id
migrationState
name name
path path
status status

View File

@ -9,6 +9,13 @@ module Types
field_class Types::BaseField field_class Types::BaseField
edge_type_class Types::BaseEdge 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) def self.accepts(*types)
@accepts ||= [] @accepts ||= []
@accepts += types @accepts += types

View File

@ -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 :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 :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 :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 :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 :path, GraphQL::Types::String, null: false, description: 'Path of the container repository.'
field :project, Types::ProjectType, null: false, description: 'Project of the container registry.' field :project, Types::ProjectType, null: false, description: 'Project of the container registry.'

View File

@ -88,6 +88,15 @@ module NamespacesHelper
}.to_json }.to_json
end 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 private
# Many importers create a temporary Group, so use the real # Many importers create a temporary Group, so use the real

View File

@ -2,6 +2,7 @@
- add_to_breadcrumbs _("Issues"), project_issues_path(@project) - add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- breadcrumb_title @issue.to_reference - breadcrumb_title @issue.to_reference
- page_title "#{@issue.title} (#{@issue.to_reference})", _("Issues") - 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/issuable/show', issuable: @issue, api_awards_path: award_emoji_issue_api_path(@issue)
= render 'projects/invite_members_modal', project: @project = render 'projects/invite_members_modal', project: @project

View File

@ -12,12 +12,24 @@ module BulkImports
worker_has_external_dependencies! worker_has_external_dependencies!
def perform(entity_id, current_stage = nil) 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( logger.info(
worker: self.class.name, structured_payload(
entity_id: entity_id, entity_id: entity_id,
current_stage: current_stage current_stage: current_stage,
message: 'Stage starting'
)
) )
next_pipeline_trackers_for(entity_id).each do |pipeline_tracker| next_pipeline_trackers_for(entity_id).each do |pipeline_tracker|
@ -29,10 +41,11 @@ module BulkImports
end end
rescue StandardError => e rescue StandardError => e
logger.error( logger.error(
worker: self.class.name, structured_payload(
entity_id: entity_id, entity_id: entity_id,
current_stage: current_stage, current_stage: current_stage,
error_message: e.message message: e.message
)
) )
Gitlab::ErrorTracking.track_exception(e, entity_id: entity_id) Gitlab::ErrorTracking.track_exception(e, entity_id: entity_id)

View File

@ -42,12 +42,14 @@ module BulkImports
correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id
} }
Gitlab::Import::Logger.warn( Gitlab::Import::Logger.error(
structured_payload(
attributes.merge( attributes.merge(
bulk_import_id: entity.bulk_import.id, bulk_import_id: entity.bulk_import.id,
bulk_import_entity_type: entity.source_type bulk_import_entity_type: entity.source_type
) )
) )
)
BulkImports::Failure.create(attributes) BulkImports::Failure.create(attributes)
end end

View File

@ -18,19 +18,21 @@ module BulkImports
if pipeline_tracker.present? if pipeline_tracker.present?
logger.info( logger.info(
worker: self.class.name, structured_payload(
entity_id: pipeline_tracker.entity.id, entity_id: pipeline_tracker.entity.id,
pipeline_name: pipeline_tracker.pipeline_name pipeline_name: pipeline_tracker.pipeline_name
) )
)
run(pipeline_tracker) run(pipeline_tracker)
else else
logger.error( logger.error(
worker: self.class.name, structured_payload(
entity_id: entity_id, entity_id: entity_id,
pipeline_tracker_id: pipeline_tracker_id, pipeline_tracker_id: pipeline_tracker_id,
message: 'Unstarted pipeline not found' message: 'Unstarted pipeline not found'
) )
)
end end
ensure ensure
@ -63,11 +65,12 @@ module BulkImports
rescue BulkImports::NetworkError => e rescue BulkImports::NetworkError => e
if e.retriable?(pipeline_tracker) if e.retriable?(pipeline_tracker)
logger.error( logger.error(
worker: self.class.name, structured_payload(
entity_id: pipeline_tracker.entity.id, entity_id: pipeline_tracker.entity.id,
pipeline_name: pipeline_tracker.pipeline_name, pipeline_name: pipeline_tracker.pipeline_name,
message: "Retrying error: #{e.message}" message: "Retrying error: #{e.message}"
) )
)
pipeline_tracker.update!(status_event: 'retry', jid: jid) pipeline_tracker.update!(status_event: 'retry', jid: jid)
@ -83,11 +86,12 @@ module BulkImports
pipeline_tracker.update!(status_event: 'fail_op', jid: jid) pipeline_tracker.update!(status_event: 'fail_op', jid: jid)
logger.error( logger.error(
worker: self.class.name, structured_payload(
entity_id: pipeline_tracker.entity.id, entity_id: pipeline_tracker.entity.id,
pipeline_name: pipeline_tracker.pipeline_name, pipeline_name: pipeline_tracker.pipeline_name,
message: exception.message message: exception.message
) )
)
Gitlab::ErrorTracking.track_exception( Gitlab::ErrorTracking.track_exception(
exception, exception,

View File

@ -272,6 +272,7 @@ module Gitlab
config.assets.precompile << "page_bundles/import.css" config.assets.precompile << "page_bundles/import.css"
config.assets.precompile << "page_bundles/incident_management_list.css" config.assets.precompile << "page_bundles/incident_management_list.css"
config.assets.precompile << "page_bundles/issues_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.css"
config.assets.precompile << "page_bundles/jira_connect_users.css" config.assets.precompile << "page_bundles/jira_connect_users.css"
config.assets.precompile << "page_bundles/learn_gitlab.css" config.assets.precompile << "page_bundles/learn_gitlab.css"

View File

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

View File

@ -66,7 +66,7 @@ Rails.application.routes.draw do
end end
Gitlab.ee do Gitlab.ee do
resources :company, only: [:new] resource :company, only: [:new, :create], controller: 'company'
resources :groups, only: [:new, :create] resources :groups, only: [:new, :create]
resources :projects, only: [:new, :create] resources :projects, only: [:new, :create]
resources :groups_projects, only: [:new, :create] do resources :groups_projects, only: [:new, :create] do

View File

@ -4,6 +4,6 @@ classes:
- Ci::NamespaceMirror - Ci::NamespaceMirror
feature_categories: feature_categories:
- sharding - 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 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75621
milestone: '14.6' milestone: '14.6'

View File

@ -4,6 +4,6 @@ classes:
- Ci::ProjectMirror - Ci::ProjectMirror
feature_categories: feature_categories:
- sharding - 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 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75621
milestone: '14.6' milestone: '14.6'

View File

@ -4,6 +4,6 @@ classes:
- LooseForeignKeys::DeletedRecord - LooseForeignKeys::DeletedRecord
feature_categories: feature_categories:
- sharding - 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 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70152
milestone: '14.3' milestone: '14.3'

View File

@ -4,6 +4,6 @@ classes:
- Namespaces::SyncEvent - Namespaces::SyncEvent
feature_categories: feature_categories:
- sharding - 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 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75517
milestone: '14.6' milestone: '14.6'

View File

@ -4,6 +4,6 @@ classes:
- Projects::SyncEvent - Projects::SyncEvent
feature_categories: feature_categories:
- sharding - 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 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75517
milestone: '14.6' milestone: '14.6'

View File

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

View File

@ -0,0 +1 @@
1d6ed98ad2da7be75e09d853db86905ed1fb1847d387cc6d1980ff5516db06d9

View File

@ -348,13 +348,23 @@ If this occurs, run `remove-repository` again.
### Manually list untracked repositories ### 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: The `list-untracked-repositories` Praefect sub-command lists repositories of the Gitaly Cluster that both:
- Exist for at least one Gitaly storage. - Exist for at least one Gitaly storage.
- Aren't tracked in the Praefect database. - 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: The command outputs:
- Result to `STDOUT` and the command's logs. - Result to `STDOUT` and the command's logs.

View File

@ -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="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="containerrepositoryid"></a>`id` | [`ID!`](#id) | ID of the container repository. |
| <a id="containerrepositorylocation"></a>`location` | [`String!`](#string) | URL 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="containerrepositoryname"></a>`name` | [`String!`](#string) | Name of the container repository. |
| <a id="containerrepositorypath"></a>`path` | [`String!`](#string) | Path 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. | | <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="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="containerrepositorydetailsid"></a>`id` | [`ID!`](#id) | ID of the container repository. |
| <a id="containerrepositorydetailslocation"></a>`location` | [`String!`](#string) | URL 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="containerrepositorydetailsname"></a>`name` | [`String!`](#string) | Name of the container repository. |
| <a id="containerrepositorydetailspath"></a>`path` | [`String!`](#string) | Path 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. | | <a id="containerrepositorydetailsproject"></a>`project` | [`Project!`](#project) | Project of the container registry. |

View File

@ -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: 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 template. You can edit the template at `tooling/graphql/docs/templates/default.md.haml`.
The actual renderer is at `Gitlab::Graphql::Docs::Renderer`. The actual renderer is at `Tooling::Graphql::Docs::Renderer`.
- The applicable `description` field in the code, which - The applicable `description` field in the code, which
[Updates machine-readable schema files](#update-machine-readable-schema-files), [Updates machine-readable schema files](#update-machine-readable-schema-files),
which is then used by the `rake` task described earlier. which is then used by the `rake` task described earlier.

View File

@ -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_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_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_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_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_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. | |[`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). 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
Header fuzzing is disabled by default due to the high number of false positives that occur with many Header fuzzing is disabled by default due to the high number of false positives that occur with many

View File

@ -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_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_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_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_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_EXCLUDE_PARAMETER_FILE`](#exclude-parameters) | Path to a JSON file containing excluded parameters. |
|[`DAST_API_OPENAPI`](#openapi-specification) | OpenAPI specification file or URL. | |[`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). 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 ## 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. 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.

View File

@ -9773,6 +9773,9 @@ msgstr ""
msgid "ContainerRegistry|Image repository not found" msgid "ContainerRegistry|Image repository not found"
msgstr "" 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" msgid "ContainerRegistry|Image repository will be deleted"
msgstr "" msgstr ""

View File

@ -1,7 +1,7 @@
import { GlButton } from '@gitlab/ui'; import { GlButton, GlTooltip, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; 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 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', () => { describe('delete_button', () => {
let wrapper; let wrapper;
@ -12,6 +12,7 @@ describe('delete_button', () => {
}; };
const findButton = () => wrapper.find(GlButton); const findButton = () => wrapper.find(GlButton);
const findTooltip = () => wrapper.find(GlTooltip);
const mountComponent = (props) => { const mountComponent = (props) => {
wrapper = shallowMount(component, { wrapper = shallowMount(component, {
@ -19,8 +20,9 @@ describe('delete_button', () => {
...defaultProps, ...defaultProps,
...props, ...props,
}, },
directives: { stubs: {
GlTooltip: createMockDirective(), GlTooltip,
GlSprintf,
}, },
}); });
}; };
@ -33,15 +35,25 @@ describe('delete_button', () => {
describe('tooltip', () => { describe('tooltip', () => {
it('the title is controlled by tooltipTitle prop', () => { it('the title is controlled by tooltipTitle prop', () => {
mountComponent(); mountComponent();
const tooltip = getBinding(wrapper.element, 'gl-tooltip'); const tooltip = findTooltip();
expect(tooltip).toBeDefined(); expect(tooltip).toBeDefined();
expect(tooltip.value.title).toBe(defaultProps.tooltipTitle); expect(tooltip.text()).toBe(defaultProps.tooltipTitle);
}); });
it('is disabled when tooltipTitle is disabled', () => { it('is disabled when tooltipTitle is disabled', () => {
mountComponent({ tooltipDisabled: true }); mountComponent({ tooltipDisabled: true });
const tooltip = getBinding(wrapper.element, 'gl-tooltip'); expect(findTooltip().props('disabled')).toBe(true);
expect(tooltip.value.disabled).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,
);
});
}); });
describe('button', () => { describe('button', () => {
@ -70,4 +82,3 @@ describe('delete_button', () => {
}); });
}); });
}); });
});

View File

@ -10,6 +10,7 @@ import {
LIST_DELETE_BUTTON_DISABLED, LIST_DELETE_BUTTON_DISABLED,
REMOVE_REPOSITORY_LABEL, REMOVE_REPOSITORY_LABEL,
IMAGE_DELETE_SCHEDULED_STATUS, IMAGE_DELETE_SCHEDULED_STATUS,
IMAGE_MIGRATING_STATE,
SCHEDULED_STATUS, SCHEDULED_STATUS,
ROOT_IMAGE_TEXT, ROOT_IMAGE_TEXT,
} from '~/packages_and_registries/container_registry/explorer/constants'; } from '~/packages_and_registries/container_registry/explorer/constants';
@ -41,6 +42,9 @@ describe('Image List Row', () => {
item, item,
...props, ...props,
}, },
provide: {
config: {},
},
directives: { directives: {
GlTooltip: createMockDirective(), GlTooltip: createMockDirective(),
}, },
@ -178,6 +182,12 @@ describe('Image List Row', () => {
expect(findDeleteBtn().props('disabled')).toBe(state); 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', () => { describe('tags count', () => {

View File

@ -5,6 +5,7 @@ export const imagesListResponse = [
name: 'rails-12009', name: 'rails-12009',
path: 'gitlab-org/gitlab-test/rails-12009', path: 'gitlab-org/gitlab-test/rails-12009',
status: null, status: null,
migrationState: 'default',
location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-12009', location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-12009',
canDelete: true, canDelete: true,
createdAt: '2020-11-03T13:29:21Z', createdAt: '2020-11-03T13:29:21Z',
@ -17,6 +18,7 @@ export const imagesListResponse = [
name: 'rails-20572', name: 'rails-20572',
path: 'gitlab-org/gitlab-test/rails-20572', path: 'gitlab-org/gitlab-test/rails-20572',
status: null, status: null,
migrationState: 'default',
location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-20572', location: '0.0.0.0:5000/gitlab-org/gitlab-test/rails-20572',
canDelete: true, canDelete: true,
createdAt: '2020-09-21T06:57:43Z', createdAt: '2020-09-21T06:57:43Z',

View File

@ -428,5 +428,25 @@ RSpec.describe Types::BaseObject do
expect(result.dig('data', 'users', 'nodes')) expect(result.dig('data', 'users', 'nodes'))
.to contain_exactly({ 'name' => active_users.first.name }) .to contain_exactly({ 'name' => active_users.first.name })
end 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
end end

View File

@ -3,7 +3,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['ContainerRepositoryDetails'] do 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') } it { expect(described_class.graphql_name).to eq('ContainerRepositoryDetails') }

View File

@ -3,7 +3,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['ContainerRepository'] do 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') } it { expect(described_class.graphql_name).to eq('ContainerRepository') }

View File

@ -268,4 +268,15 @@ RSpec.describe NamespacesHelper do
end end
end 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 end

View File

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

View File

@ -36,9 +36,11 @@ RSpec.describe BulkImports::EntityWorker do
expect(logger) expect(logger)
.to receive(:info).twice .to receive(:info).twice
.with( .with(
worker: described_class.name, hash_including(
entity_id: entity.id, 'entity_id' => entity.id,
current_stage: nil 'current_stage' => nil,
'message' => 'Stage starting'
)
) )
end end
@ -64,18 +66,20 @@ RSpec.describe BulkImports::EntityWorker do
expect(logger) expect(logger)
.to receive(:info).twice .to receive(:info).twice
.with( .with(
worker: described_class.name, hash_including(
entity_id: entity.id, 'entity_id' => entity.id,
current_stage: nil 'current_stage' => nil
)
) )
expect(logger) expect(logger)
.to receive(:error) .to receive(:error)
.with( .with(
worker: described_class.name, hash_including(
entity_id: entity.id, 'entity_id' => entity.id,
current_stage: nil, 'current_stage' => nil,
error_message: 'Error!' 'message' => 'Error!'
)
) )
end end
@ -90,6 +94,18 @@ RSpec.describe BulkImports::EntityWorker do
let(:job_args) { [entity.id, 0] } let(:job_args) { [entity.id, 0] }
it 'do not enqueue a new pipeline job if the current stage still running' do 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) expect(BulkImports::PipelineWorker)
.not_to receive(:perform_async) .not_to receive(:perform_async)
@ -110,9 +126,10 @@ RSpec.describe BulkImports::EntityWorker do
expect(logger) expect(logger)
.to receive(:info).twice .to receive(:info).twice
.with( .with(
worker: described_class.name, hash_including(
entity_id: entity.id, 'entity_id' => entity.id,
current_stage: 0 'current_stage' => 0
)
) )
end end

View File

@ -35,14 +35,16 @@ RSpec.describe BulkImports::ExportRequestWorker do
expect(client).to receive(:post).and_raise(BulkImports::NetworkError, 'Export error').twice expect(client).to receive(:post).and_raise(BulkImports::NetworkError, 'Export error').twice
end end
expect(Gitlab::Import::Logger).to receive(:warn).with( expect(Gitlab::Import::Logger).to receive(:error).with(
bulk_import_entity_id: entity.id, hash_including(
pipeline_class: 'ExportRequestWorker', 'bulk_import_entity_id' => entity.id,
exception_class: 'BulkImports::NetworkError', 'pipeline_class' => 'ExportRequestWorker',
exception_message: 'Export error', 'exception_class' => 'BulkImports::NetworkError',
correlation_id_value: anything, 'exception_message' => 'Export error',
bulk_import_id: bulk_import.id, 'correlation_id_value' => anything,
bulk_import_entity_type: entity.source_type 'bulk_import_id' => bulk_import.id,
'bulk_import_entity_type' => entity.source_type
)
).twice ).twice
perform_multiple(job_args) perform_multiple(job_args)

View File

@ -34,9 +34,10 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger) expect(logger)
.to receive(:info) .to receive(:info)
.with( .with(
worker: described_class.name, hash_including(
pipeline_name: 'FakePipeline', 'pipeline_name' => 'FakePipeline',
entity_id: entity.id 'entity_id' => entity.id
)
) )
end end
@ -44,7 +45,7 @@ RSpec.describe BulkImports::PipelineWorker do
.to receive(:perform_async) .to receive(:perform_async)
.with(entity.id, pipeline_tracker.stage) .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) subject.perform(pipeline_tracker.id, pipeline_tracker.stage, entity.id)
@ -79,10 +80,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger) expect(logger)
.to receive(:error) .to receive(:error)
.with( .with(
worker: described_class.name, hash_including(
pipeline_tracker_id: pipeline_tracker.id, 'pipeline_tracker_id' => pipeline_tracker.id,
entity_id: entity.id, 'entity_id' => entity.id,
message: 'Unstarted pipeline not found' 'message' => 'Unstarted pipeline not found'
)
) )
end end
@ -107,10 +109,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger) expect(logger)
.to receive(:error) .to receive(:error)
.with( .with(
worker: described_class.name, hash_including(
pipeline_name: 'InexistentPipeline', 'pipeline_name' => 'InexistentPipeline',
entity_id: entity.id, 'entity_id' => entity.id,
message: "'InexistentPipeline' is not a valid BulkImport Pipeline" 'message' => "'InexistentPipeline' is not a valid BulkImport Pipeline"
)
) )
end end
@ -126,7 +129,7 @@ RSpec.describe BulkImports::PipelineWorker do
.to receive(:perform_async) .to receive(:perform_async)
.with(entity.id, pipeline_tracker.stage) .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) subject.perform(pipeline_tracker.id, pipeline_tracker.stage, entity.id)
@ -151,10 +154,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger) expect(logger)
.to receive(:error) .to receive(:error)
.with( .with(
worker: described_class.name, hash_including(
pipeline_name: 'Pipeline', 'pipeline_name' => 'Pipeline',
entity_id: entity.id, 'entity_id' => entity.id,
message: 'Failed entity status' 'message' => 'Failed entity status'
)
) )
end end
@ -183,7 +187,7 @@ RSpec.describe BulkImports::PipelineWorker do
.and_raise(exception) .and_raise(exception)
end 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_any_instance_of(BulkImports::Tracker) do |tracker|
expect(tracker).to receive(:retry).and_call_original expect(tracker).to receive(:retry).and_call_original
@ -193,9 +197,10 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger) expect(logger)
.to receive(:info) .to receive(:info)
.with( .with(
worker: described_class.name, hash_including(
pipeline_name: 'FakePipeline', 'pipeline_name' => 'FakePipeline',
entity_id: entity.id 'entity_id' => entity.id
)
) )
end end
@ -292,10 +297,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger) expect(logger)
.to receive(:error) .to receive(:error)
.with( .with(
worker: described_class.name, hash_including(
pipeline_name: 'NdjsonPipeline', 'pipeline_name' => 'NdjsonPipeline',
entity_id: entity.id, 'entity_id' => entity.id,
message: 'Pipeline timeout' 'message' => 'Pipeline timeout'
)
) )
end end
@ -318,10 +324,11 @@ RSpec.describe BulkImports::PipelineWorker do
expect(logger) expect(logger)
.to receive(:error) .to receive(:error)
.with( .with(
worker: described_class.name, hash_including(
pipeline_name: 'NdjsonPipeline', 'pipeline_name' => 'NdjsonPipeline',
entity_id: entity.id, 'entity_id' => entity.id,
message: 'Error!' 'message' => 'Error!'
)
) )
end end