Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3007cf75a9
commit
afbf001676
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
1d6ed98ad2da7be75e09d853db86905ed1fb1847d387cc6d1980ff5516db06d9
|
|
@ -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.
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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([[]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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') }
|
||||
|
||||
|
|
|
@ -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') }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue