Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5aaced570d
commit
3f11364a27
|
|
@ -147,7 +147,7 @@ db:backup_and_restore:
|
|||
script:
|
||||
- . scripts/prepare_build.sh
|
||||
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
|
||||
- mkdir -p tmp/tests/public/uploads tmp/tests/{artifacts,pages,lfs-objects,terraform_state,registry,packages,ci_secure_files}
|
||||
- mkdir -p tmp/tests/public/uploads tmp/tests/{artifacts,pages,lfs-objects,terraform_state,registry,packages,ci_secure_files,external-diffs}
|
||||
- bundle exec rake gitlab:backup:create
|
||||
- date
|
||||
- bundle exec rake gitlab:backup:restore
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@
|
|||
|
||||
.ai-gateway-services:
|
||||
services:
|
||||
- name: registry.gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/model-gateway:v1.6.0
|
||||
- name: registry.gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/model-gateway:v1.6.1
|
||||
alias: ai-gateway
|
||||
|
||||
.use-pg13:
|
||||
|
|
|
|||
|
|
@ -3923,7 +3923,6 @@ Gitlab/BoundedContexts:
|
|||
- 'ee/lib/compliance_management/standards_adherence_checks_tracker.rb'
|
||||
- 'ee/lib/delay.rb'
|
||||
- 'ee/lib/duo_pro/bulk_user_assignment.rb'
|
||||
- 'ee/lib/ee/backup/targets/repositories.rb'
|
||||
- 'ee/lib/ee/bulk_imports/groups/stage.rb'
|
||||
- 'ee/lib/ee/bulk_imports/projects/pipelines/issues_pipeline.rb'
|
||||
- 'ee/lib/ee/bulk_imports/projects/stage.rb'
|
||||
|
|
@ -4103,35 +4102,6 @@ Gitlab/BoundedContexts:
|
|||
- 'lib/atlassian/jira_issue_key_extractor.rb'
|
||||
- 'lib/atlassian/jira_issue_key_extractors/branch.rb'
|
||||
- 'lib/aws/s3_client.rb'
|
||||
- 'lib/backup.rb'
|
||||
- 'lib/backup/database_configuration.rb'
|
||||
- 'lib/backup/database_connection.rb'
|
||||
- 'lib/backup/dump/postgres.rb'
|
||||
- 'lib/backup/gitaly_backup.rb'
|
||||
- 'lib/backup/helper.rb'
|
||||
- 'lib/backup/manager.rb'
|
||||
- 'lib/backup/metadata.rb'
|
||||
- 'lib/backup/options.rb'
|
||||
- 'lib/backup/remote_storage.rb'
|
||||
- 'lib/backup/restore/preconditions.rb'
|
||||
- 'lib/backup/restore/process.rb'
|
||||
- 'lib/backup/restore/unpack.rb'
|
||||
- 'lib/backup/targets/database.rb'
|
||||
- 'lib/backup/targets/files.rb'
|
||||
- 'lib/backup/targets/repositories.rb'
|
||||
- 'lib/backup/targets/target.rb'
|
||||
- 'lib/backup/tasks/artifacts.rb'
|
||||
- 'lib/backup/tasks/builds.rb'
|
||||
- 'lib/backup/tasks/ci_secure_files.rb'
|
||||
- 'lib/backup/tasks/database.rb'
|
||||
- 'lib/backup/tasks/lfs.rb'
|
||||
- 'lib/backup/tasks/packages.rb'
|
||||
- 'lib/backup/tasks/pages.rb'
|
||||
- 'lib/backup/tasks/registry.rb'
|
||||
- 'lib/backup/tasks/repositories.rb'
|
||||
- 'lib/backup/tasks/task.rb'
|
||||
- 'lib/backup/tasks/terraform_state.rb'
|
||||
- 'lib/backup/tasks/uploads.rb'
|
||||
- 'lib/bitbucket/app_password_connection.rb'
|
||||
- 'lib/bitbucket/client.rb'
|
||||
- 'lib/bitbucket/collection.rb'
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@
|
|||
{"name":"openssl-signature_algorithm","version":"1.3.0","platform":"ruby","checksum":"a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80"},
|
||||
{"name":"opentelemetry-api","version":"1.2.5","platform":"ruby","checksum":"ab3d9a0566cd2ee068ade40e840bc973383ab8568e693c0c5712f0c789122cc9"},
|
||||
{"name":"opentelemetry-common","version":"0.21.0","platform":"ruby","checksum":"fe891a44583a20bc3217b324aec76d066504494951682d391cfd57d40cd01c98"},
|
||||
{"name":"opentelemetry-exporter-otlp","version":"0.26.3","platform":"ruby","checksum":"fc1deea7924c74e3536983b02684d1bc7e9737baa5c38f3aff9809a7fd330399"},
|
||||
{"name":"opentelemetry-exporter-otlp","version":"0.27.0","platform":"ruby","checksum":"0050cf6ade97186ee3176cd8c44087b70cb739c3c624dbfc7c33097a3a189e4c"},
|
||||
{"name":"opentelemetry-helpers-sql-obfuscation","version":"0.1.0","platform":"ruby","checksum":"bc6ef1373dbcf979647091b3bfc99d7b6fb9669f74c3ae184f58b48adfc8d432"},
|
||||
{"name":"opentelemetry-instrumentation-action_pack","version":"0.9.0","platform":"ruby","checksum":"c5df8472afc9cdbfc1425d9af7816b9cfc1a1a69b86621f1fc624974bd9acb9a"},
|
||||
{"name":"opentelemetry-instrumentation-action_view","version":"0.7.0","platform":"ruby","checksum":"bc7c714be0b4bb76843085c29ecc9465e65cb7fe6722e34c71629e44f8c3cb75"},
|
||||
|
|
|
|||
|
|
@ -1226,7 +1226,7 @@ GEM
|
|||
opentelemetry-api (1.2.5)
|
||||
opentelemetry-common (0.21.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-exporter-otlp (0.26.3)
|
||||
opentelemetry-exporter-otlp (0.27.0)
|
||||
google-protobuf (~> 3.14)
|
||||
googleapis-common-protos-types (~> 1.3)
|
||||
opentelemetry-api (~> 1.1)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ query getLinkedPipelines($fullPath: ID!, $iid: ID!) {
|
|||
}
|
||||
detailedStatus {
|
||||
id
|
||||
detailsPath
|
||||
group
|
||||
icon
|
||||
label
|
||||
|
|
@ -33,6 +34,7 @@ query getLinkedPipelines($fullPath: ID!, $iid: ID!) {
|
|||
}
|
||||
detailedStatus {
|
||||
id
|
||||
detailsPath
|
||||
group
|
||||
icon
|
||||
label
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
|
||||
/**
|
||||
* Renders the downstream portion of the pipeline mini graph.
|
||||
*/
|
||||
export default {
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
components: {
|
||||
CiIcon,
|
||||
},
|
||||
props: {
|
||||
pipelines: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
pipelinePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
maxRenderedPipelines: 3,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
totalPipelineCount() {
|
||||
return this.pipelines.length;
|
||||
},
|
||||
pipelinesTrimmed() {
|
||||
return this.totalPipelineCount > this.maxRenderedPipelines
|
||||
? this.pipelines.slice(0, this.maxRenderedPipelines)
|
||||
: this.pipelines;
|
||||
},
|
||||
shouldRenderCounter() {
|
||||
return this.pipelines.length > this.maxRenderedPipelines;
|
||||
},
|
||||
counterLabel() {
|
||||
return `+${this.pipelines.length - this.maxRenderedPipelines}`;
|
||||
},
|
||||
counterTooltipText() {
|
||||
return sprintf(s__('Pipelines|%{counterLabel} more downstream pipelines'), {
|
||||
counterLabel: this.counterLabel,
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pipelineTooltipText(pipeline) {
|
||||
return `${pipeline?.project?.name} - ${pipeline?.detailedStatus?.label}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="pipelines" class="gl-display-inline-flex gl-gap-2 gl-align-middle">
|
||||
<ci-icon
|
||||
v-for="pipeline in pipelinesTrimmed"
|
||||
:key="pipeline.id"
|
||||
v-gl-tooltip.hover
|
||||
:title="pipelineTooltipText(pipeline)"
|
||||
:status="pipeline.detailedStatus"
|
||||
:show-tooltip="false"
|
||||
data-testid="downstream-pipelines"
|
||||
/>
|
||||
|
||||
<a
|
||||
v-if="shouldRenderCounter"
|
||||
v-gl-tooltip="{ title: counterTooltipText }"
|
||||
:title="counterTooltipText"
|
||||
:href="pipelinePath"
|
||||
class="gl-align-items-center gl-bg-gray-50 gl-display-inline-flex gl-font-sm gl-h-6 gl-justify-content-center gl-rounded-pill gl-text-decoration-none gl-text-gray-500 gl-w-7"
|
||||
data-testid="downstream-pipeline-counter"
|
||||
>
|
||||
{{ counterLabel }}
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -11,20 +11,19 @@ query getPipelineMiniGraph($fullPath: ID!, $iid: ID!) {
|
|||
detailedStatus {
|
||||
id
|
||||
icon
|
||||
group
|
||||
label
|
||||
}
|
||||
}
|
||||
}
|
||||
upstream {
|
||||
id
|
||||
path
|
||||
project {
|
||||
id
|
||||
name
|
||||
}
|
||||
detailedStatus {
|
||||
id
|
||||
group
|
||||
detailsPath
|
||||
icon
|
||||
label
|
||||
}
|
||||
|
|
@ -39,7 +38,7 @@ query getPipelineMiniGraph($fullPath: ID!, $iid: ID!) {
|
|||
}
|
||||
detailedStatus {
|
||||
id
|
||||
group
|
||||
detailsPath
|
||||
icon
|
||||
label
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export default {
|
|||
return `+${this.linkedPipelines.length - this.maxRenderedPipelines}`;
|
||||
},
|
||||
counterTooltipText() {
|
||||
return sprintf(s__('LinkedPipelines|%{counterLabel} more downstream pipelines'), {
|
||||
return sprintf(s__('Pipelines|%{counterLabel} more downstream pipelines'), {
|
||||
counterLabel: this.counterLabel,
|
||||
});
|
||||
},
|
||||
|
|
@ -76,6 +76,7 @@ export default {
|
|||
const { label } = accessValue(pipeline, this.dataMethod, 'detailedStatus');
|
||||
|
||||
return `${pipeline.project.name} - ${label}`;
|
||||
// return `${pipeline?.project?.name} - ${pipeline?.details?.status?.label}`;
|
||||
},
|
||||
pipelineStatus(pipeline) {
|
||||
// detailedStatus is graphQL, details.status is REST
|
||||
|
|
@ -88,10 +89,6 @@ export default {
|
|||
<template>
|
||||
<span
|
||||
v-if="linkedPipelines"
|
||||
:class="{
|
||||
'is-upstream': isUpstream,
|
||||
'is-downstream': isDownstream,
|
||||
}"
|
||||
class="linked-pipeline-mini-list gl-display-inline-flex gl-gap-2 gl-align-middle"
|
||||
>
|
||||
<ci-icon
|
||||
|
|
|
|||
|
|
@ -1,118 +0,0 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
|
||||
import { accessValue } from './accessors/linked_pipelines_accessors';
|
||||
/**
|
||||
* Renders the upstream/downstream portions of the pipeline mini graph.
|
||||
*/
|
||||
export default {
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
components: {
|
||||
CiIcon,
|
||||
},
|
||||
inject: {
|
||||
dataMethod: {
|
||||
default: 'rest',
|
||||
},
|
||||
},
|
||||
props: {
|
||||
triggeredBy: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
triggered: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
pipelinePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
maxRenderedPipelines: 3,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// Exactly one of these (triggeredBy and triggered) must be truthy. Never both. Never neither.
|
||||
isUpstream() {
|
||||
return Boolean(this.triggeredBy.length) && !this.triggered.length;
|
||||
},
|
||||
isDownstream() {
|
||||
return !this.triggeredBy.length && Boolean(this.triggered.length);
|
||||
},
|
||||
linkedPipelines() {
|
||||
return this.isUpstream ? this.triggeredBy : this.triggered;
|
||||
},
|
||||
totalPipelineCount() {
|
||||
return this.linkedPipelines.length;
|
||||
},
|
||||
linkedPipelinesTrimmed() {
|
||||
return this.totalPipelineCount > this.maxRenderedPipelines
|
||||
? this.linkedPipelines.slice(0, this.maxRenderedPipelines)
|
||||
: this.linkedPipelines;
|
||||
},
|
||||
shouldRenderCounter() {
|
||||
return this.isDownstream && this.linkedPipelines.length > this.maxRenderedPipelines;
|
||||
},
|
||||
counterLabel() {
|
||||
return `+${this.linkedPipelines.length - this.maxRenderedPipelines}`;
|
||||
},
|
||||
counterTooltipText() {
|
||||
return sprintf(s__('LinkedPipelines|%{counterLabel} more downstream pipelines'), {
|
||||
counterLabel: this.counterLabel,
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pipelineTooltipText(pipeline) {
|
||||
const { label } = accessValue(pipeline, this.dataMethod, 'detailedStatus');
|
||||
|
||||
return `${pipeline.project.name} - ${label}`;
|
||||
},
|
||||
pipelineStatus(pipeline) {
|
||||
// detailedStatus is graphQL, details.status is REST
|
||||
return pipeline?.detailedStatus || pipeline?.details?.status;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
v-if="linkedPipelines"
|
||||
:class="{
|
||||
'is-upstream': isUpstream,
|
||||
'is-downstream': isDownstream,
|
||||
}"
|
||||
class="linked-pipeline-mini-list gl-display-inline-flex gl-gap-2 gl-align-middle"
|
||||
>
|
||||
<ci-icon
|
||||
v-for="pipeline in linkedPipelinesTrimmed"
|
||||
:key="pipeline.id"
|
||||
v-gl-tooltip="{ title: pipelineTooltipText(pipeline) }"
|
||||
:status="pipelineStatus(pipeline)"
|
||||
:show-tooltip="false"
|
||||
class="linked-pipeline-mini-item gl-mb-0!"
|
||||
data-testid="linked-pipeline-mini-item"
|
||||
/>
|
||||
|
||||
<a
|
||||
v-if="shouldRenderCounter"
|
||||
v-gl-tooltip="{ title: counterTooltipText }"
|
||||
:title="counterTooltipText"
|
||||
:href="pipelinePath"
|
||||
class="gl-align-items-center gl-bg-gray-50 gl-display-inline-flex gl-font-sm gl-h-6 gl-justify-content-center gl-rounded-pill gl-text-decoration-none gl-text-gray-500 gl-w-7 linked-pipelines-counter linked-pipeline-mini-item"
|
||||
data-testid="linked-pipeline-counter"
|
||||
>
|
||||
{{ counterLabel }}
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
<script>
|
||||
import { GlIcon, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __ } from '~/locale';
|
||||
import { keepLatestDownstreamPipelines } from '~/ci/pipeline_details/utils/parsing_utils';
|
||||
import { getQueryHeaders, toggleQueryPollingByVisibility } from '~/ci/pipeline_details/graph/utils';
|
||||
import { PIPELINE_MINI_GRAPH_POLL_INTERVAL } from '~/ci/pipeline_details/constants';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
|
||||
import getPipelineMiniGraphQuery from './graphql/queries/get_pipeline_mini_graph.query.graphql';
|
||||
import LinkedPipelinesMiniList from './linked_pipelines_mini_list.vue';
|
||||
import DownstreamPipelines from './downstream_pipelines.vue';
|
||||
import PipelineStages from './pipeline_stages.vue';
|
||||
/**
|
||||
* Renders the GraphQL instance of the pipeline mini graph.
|
||||
|
|
@ -16,10 +17,14 @@ export default {
|
|||
pipelineMiniGraphFetchError: __('There was a problem fetching the pipeline mini graph.'),
|
||||
},
|
||||
arrowStyles: ['arrow-icon gl-display-inline-block gl-mx-1 gl-text-gray-500 !gl-align-middle'],
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
components: {
|
||||
CiIcon,
|
||||
DownstreamPipelines,
|
||||
GlIcon,
|
||||
GlLoadingIcon,
|
||||
LinkedPipelinesMiniList,
|
||||
PipelineStages,
|
||||
},
|
||||
props: {
|
||||
|
|
@ -90,6 +95,9 @@ export default {
|
|||
upstreamPipeline() {
|
||||
return this.pipeline?.upstream;
|
||||
},
|
||||
upstreamTooltipText() {
|
||||
return `${this.upstreamPipeline.project.name} - ${this.upstreamPipeline.detailedStatus.label}`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
toggleQueryPollingByVisibility(this.$apollo.queries.pipeline);
|
||||
|
|
@ -101,11 +109,14 @@ export default {
|
|||
<div>
|
||||
<gl-loading-icon v-if="$apollo.queries.pipeline.loading" />
|
||||
<div v-else data-testid="pipeline-mini-graph">
|
||||
<linked-pipelines-mini-list
|
||||
<ci-icon
|
||||
v-if="upstreamPipeline"
|
||||
:triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [
|
||||
upstreamPipeline,
|
||||
] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
|
||||
v-gl-tooltip.hover
|
||||
:title="upstreamTooltipText"
|
||||
:aria-label="upstreamTooltipText"
|
||||
:status="upstreamPipeline.detailedStatus"
|
||||
:show-tooltip="false"
|
||||
class="gl-align-middle"
|
||||
data-testid="pipeline-mini-graph-upstream"
|
||||
/>
|
||||
<gl-icon
|
||||
|
|
@ -125,9 +136,9 @@ export default {
|
|||
name="long-arrow"
|
||||
data-testid="downstream-arrow-icon"
|
||||
/>
|
||||
<linked-pipelines-mini-list
|
||||
<downstream-pipelines
|
||||
v-if="hasDownstreamPipelines"
|
||||
:triggered="downstreamPipelines"
|
||||
:pipelines="downstreamPipelines"
|
||||
:pipeline-path="pipelinePath"
|
||||
data-testid="pipeline-mini-graph-downstream"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -75,12 +75,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div data-testid="pipeline-stage">
|
||||
<ci-icon
|
||||
:status="stage.detailedStatus"
|
||||
:show-tooltip="false"
|
||||
:use-link="false"
|
||||
class="gl-mb-0!"
|
||||
/>
|
||||
<ci-icon :status="stage.detailedStatus" :show-tooltip="false" :use-link="false" />
|
||||
<!-- <ul v-for="job in jobs" :key="job.id">
|
||||
<job-item :job="job" />
|
||||
</ul> -->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ProtectedRefAccess
|
||||
include Importable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProtectedBranch::MergeAccessLevel < ApplicationRecord
|
||||
include Importable
|
||||
include ProtectedBranchAccess
|
||||
# default value for the access_level column
|
||||
GITLAB_DEFAULT_ACCESS_LEVEL = Gitlab::Access::MAINTAINER
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProtectedBranch::PushAccessLevel < ApplicationRecord
|
||||
include Importable
|
||||
include ProtectedBranchAccess
|
||||
include ProtectedRefDeployKeyAccess
|
||||
# default value for the access_level column
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProtectedTag::CreateAccessLevel < ApplicationRecord
|
||||
include Importable
|
||||
include ProtectedTagAccess
|
||||
include ProtectedRefDeployKeyAccess
|
||||
end
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@ domains:
|
|||
- permissions
|
||||
- system_access
|
||||
|
||||
Backup:
|
||||
description: Backup and restore
|
||||
feature_categories:
|
||||
- backup_restore
|
||||
|
||||
Boards:
|
||||
description:
|
||||
feature_categories:
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ including:
|
|||
- Snippets
|
||||
- [Group wikis](../../user/project/wiki/group.md)
|
||||
- Project-level Secure Files ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121142) in GitLab 16.1)
|
||||
- External merge request diffs (GitLab Helm chart only. [Issue 438777](https://gitlab.com/gitlab-org/gitlab/-/issues/438777) proposes to backup local external MR diffs with the Linux package)
|
||||
- External merge request diffs ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154914) in GitLab 17.1)
|
||||
|
||||
Backups do not include:
|
||||
|
||||
|
|
@ -443,7 +443,7 @@ Depending on your installation type, slightly different components can be skippe
|
|||
|
||||
:::TabTitle Linux package (Omnibus) / Docker / Self-compiled
|
||||
|
||||
<!-- source: https://gitlab.com/gitlab-org/gitlab/-/blob/31c3df7ebb65768208772da3e20d32688a6c90ef/lib/backup/manager.rb#L126 -->
|
||||
<!-- source: https://gitlab.com/gitlab-org/gitlab/-/blob/d693aa7f894c7306a0d20ab6d138a7b95785f2ff/lib/backup/manager.rb#L117-133 -->
|
||||
|
||||
- `db` (database)
|
||||
- `repositories` (Git repositories data, including wikis)
|
||||
|
|
@ -456,6 +456,7 @@ Depending on your installation type, slightly different components can be skippe
|
|||
- `registry` (Container registry images)
|
||||
- `packages` (Packages)
|
||||
- `ci_secure_files` (Project-level Secure Files)
|
||||
- `external_diffs` (External Merge Request diffs)
|
||||
|
||||
:::TabTitle Helm chart (Kubernetes)
|
||||
|
||||
|
|
|
|||
|
|
@ -1055,7 +1055,7 @@ This file is located at:
|
|||
|
||||
DETAILS:
|
||||
**Tier:** Ultimate
|
||||
**Offering:** GitLab Dedicated
|
||||
**Offering:** GitLab.com, GitLab Dedicated
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137812) in GitLab 16.7.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
stage: Secure
|
||||
group: Secret Detection
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Detected secrets
|
||||
|
||||
This table lists the secrets detected by [pipeline secret detection](index.md).
|
||||
|
||||
<!-- markdownlint-disable MD034 -->
|
||||
<!-- markdownlint-disable MD037 -->
|
||||
<!-- markdownlint-disable MD044 -->
|
||||
<!-- vale gitlab.SentenceSpacing = NO -->
|
||||
|
||||
| Description | ID | Keywords |
|
||||
|:-------------------------------------------------------------------|:----------------------------------------------|:--------------------------------------|
|
||||
| AWS Access Token | AWS | AKIA |
|
||||
| Adobe Client ID (Oauth Web) | Adobe Client ID (Oauth Web) | adobe |
|
||||
| Adobe Client Secret | Adobe Client Secret | adobe, p8e- |
|
||||
| Age secret key | Age secret key | AGE-SECRET-KEY-1 |
|
||||
| Alibaba AccessKey ID | Alibaba AccessKey ID | LTAI |
|
||||
| Alibaba Secret Key | Alibaba Secret Key | alibaba |
|
||||
| Asana Client ID | Asana Client ID | asana |
|
||||
| Asana Client Secret | Asana Client Secret | asana |
|
||||
| Atlassian API token | Atlassian API token | atlassian |
|
||||
| Beamer API token | Beamer API token | beamer |
|
||||
| Bitbucket client ID | Bitbucket client ID | bitbucket |
|
||||
| Bitbucket client secret | Bitbucket client secret | bitbucket |
|
||||
| CircleCI access tokens | CircleCI access tokens | CircleCI |
|
||||
| Clojars API token | Clojars API token | CLOJARS_ |
|
||||
| Contentful delivery API token | Contentful delivery API token | contentful |
|
||||
| Contentful preview API token | Contentful preview API token | contentful |
|
||||
| Databricks API token | Databricks API token | dapi, databricks |
|
||||
| DigitalOcean OAuth Access Token | digitalocean-access-token | doo_v1_ |
|
||||
| DigitalOcean OAuth Refresh Token | digitalocean-refresh-token | dor_v1_ |
|
||||
| DigitalOcean Personal Access Token | digitalocean-pat | dop_v1_ |
|
||||
| Discord API key | Discord API key | discord |
|
||||
| Discord client ID | Discord client ID | discord |
|
||||
| Discord client secret | Discord client secret | discord |
|
||||
| Doppler API token | Doppler API token | doppler |
|
||||
| Dropbox API secret/key | Dropbox API secret/key | dropbox |
|
||||
| Dropbox long lived API token | Dropbox long lived API token | dropbox |
|
||||
| Dropbox short lived API token | Dropbox short lived API token | dropbox |
|
||||
| Duffel API token | Duffel API token | duffel |
|
||||
| Dynatrace API token | Dynatrace API token | dt0c01 |
|
||||
| EasyPost API token | EasyPost API token | EZAK |
|
||||
| EasyPost test API token | EasyPost test API token | EZTK |
|
||||
| Facebook token | Facebook token | facebook |
|
||||
| Fastly API token | Fastly API token | fastly |
|
||||
| Finicity API token | Finicity API token | finicity |
|
||||
| Finicity client secret | Finicity client secret | finicity |
|
||||
| Flutterwave encrypted key | Flutterwave encrypted key | FLWSECK_TEST |
|
||||
| Flutterwave public key | Flutterwave public key | FLWPUBK_TEST |
|
||||
| Flutterwave secret key | Flutterwave secret key | FLWSECK_TEST |
|
||||
| Frame.io API token | Frame.io API token | fio-u- |
|
||||
| GCP API keys can be misused to gain API quota from billed projects | GCP API key | AIza |
|
||||
| GCP OAuth client secrets can be misused to spoof your application | GCP OAuth client secret | GOCSPX- |
|
||||
| GitLab Agent for Kubernetes token | gitlab_kubernetes_agent_token | glagent |
|
||||
| GitLab CI Build (Job) token | gitlab_ci_build_token | glcbt |
|
||||
| GitLab Deploy Token | gitlab_deploy_token | gldt |
|
||||
| GitLab Feed Token | gitlab_feed_token | feed_token |
|
||||
| GitLab Feed token | gitlab_feed_token_v2 | glft |
|
||||
| GitLab Incoming email token | gitlab_incoming_email_token | glimt |
|
||||
| GitLab OAuth Application Secrets | gitlab_oauth_app_secret | gloas |
|
||||
| GitLab Personal Access Token | gitlab_personal_access_token | glpat |
|
||||
| GitLab Pipeline Trigger Token | gitlab_pipeline_trigger_token | glptt |
|
||||
| GitLab Runner Authentication Token | gitlab_runner_auth_token | glrt |
|
||||
| GitLab Runner Registration Token | gitlab_runner_registration_token | GR1348941 |
|
||||
| GitLab SCIM token | gitlab_scim_oauth_token | glsoat |
|
||||
| GitHub App Token | GitHub App Token | ghu_, ghs_ |
|
||||
| GitHub OAuth Access Token | GitHub OAuth Access Token | gho_ |
|
||||
| GitHub Personal Access Token | GitHub Personal Access Token | ghp_ |
|
||||
| GitHub Refresh Token | GitHub Refresh Token | ghr_ |
|
||||
| GoCardless API token | GoCardless API token | gocardless |
|
||||
| Google (GCP) Service-account | Google (GCP) Service-account | service_account |
|
||||
| Grafana API token | Grafana API token | grafana |
|
||||
| Hashicorp Terraform user/org API token | Hashicorp Terraform user/org API token | atlasv1, hashicorp, terraform |
|
||||
| Hashicorp Vault batch token | Hashicorp Vault batch token | hashicorp, AAAAAQ, vault |
|
||||
| Heroku API Key | Heroku API Key | heroku |
|
||||
| Hubspot API token | Hubspot API token | hubspot |
|
||||
| Instagram access token | Instagram access token | IG |
|
||||
| Intercom API token | Intercom API token | intercom |
|
||||
| Intercom client secret/ID | Intercom client secret/ID | intercom |
|
||||
| Ionic API token | Ionic API token | ion_ |
|
||||
| Linear API token | Linear API token | lin_api_ |
|
||||
| Linear client secret/ID | Linear client secret/ID | linear |
|
||||
| Linkedin Client ID | Linkedin Client ID | linkedin |
|
||||
| Linkedin Client secret | Linkedin Client secret | linkedin |
|
||||
| Lob API Key | Lob API Key | lob |
|
||||
| Lob Publishable API Key | Lob Publishable API Key | lob |
|
||||
| Mailchimp API key | Mailchimp API key | mailchimp |
|
||||
| Mailgun private API token | Mailgun private API token | mailgun |
|
||||
| Mailgun public validation key | Mailgun public validation key | mailgun |
|
||||
| Mailgun webhook signing key | Mailgun webhook signing key | mailgun |
|
||||
| Mapbox API token | Mapbox API token | mapbox |
|
||||
| MessageBird API client ID | MessageBird API client ID | messagebird |
|
||||
| MessageBird API token | messagebird-api-token | messagebird |
|
||||
| Meta access token | Meta access token | EA |
|
||||
| New Relic ingest browser API token | New Relic ingest browser API token | NRJS |
|
||||
| New Relic user API ID | New Relic user API ID | newrelic |
|
||||
| New Relic user API Key | New Relic user API Key | NRAK |
|
||||
| npm access token | npm access token | npm_ |
|
||||
| Oculus access token | Oculus access token | OC |
|
||||
| Open AI API key | open ai token | sk- |
|
||||
| PGP private key | PGP private key | -----BEGIN PGP PRIVATE KEY BLOCK----- |
|
||||
| PKCS8 private key | PKCS8 private key | -----BEGIN PRIVATE KEY----- |
|
||||
| Password in URL | Password in URL | Not applicable |
|
||||
| Planetscale API token | Planetscale API token | pscale_tkn_ |
|
||||
| Planetscale password | Planetscale password | pscale_pw_ |
|
||||
| Postman API token | Postman API token | PMAK- |
|
||||
| Pulumi API token | Pulumi API token | pul- |
|
||||
| PyPI upload token | PyPI upload token | pypi-AgEIcHlwaS5vcmc |
|
||||
| RSA private key | RSA private key | -----BEGIN RSA PRIVATE KEY----- |
|
||||
| Rubygem API token | Rubygem API token | rubygems_ |
|
||||
| SSH (DSA) private key | SSH (DSA) private key | -----BEGIN DSA PRIVATE KEY----- |
|
||||
| SSH (EC) private key | SSH (EC) private key | -----BEGIN EC PRIVATE KEY----- |
|
||||
| SSH private key | SSH private key | -----BEGIN OPENSSH PRIVATE KEY----- |
|
||||
| Segment Public API token | Segment Public API token | sgp_ |
|
||||
| Sendgrid API token | Sendgrid API token | sendgrid |
|
||||
| Sendinblue API token | Sendinblue API token | xkeysib- |
|
||||
| Sendinblue SMTP token | Sendinblue SMTP token | xsmtpsib- |
|
||||
| Shippo API token | Shippo API token | shippo_ |
|
||||
| Shopify access token | Shopify access token | shpat_ |
|
||||
| Shopify custom app access token | Shopify custom app access token | shpca_ |
|
||||
| Shopify private app access token | Shopify private app access token | shppa_ |
|
||||
| Shopify shared secret | Shopify shared secret | shpss_ |
|
||||
| Slack Webhook | Slack Webhook | https://hooks.slack.com/services |
|
||||
| Slack token | Slack token | xoxb, xoxa, xoxp, xoxr, xoxs |
|
||||
| Stripe | Stripe | sk_test, pk_test, sk_live, pk_live |
|
||||
| systemd machine-id | systemd-machine-id | Not applicable |
|
||||
| Tailscale keys | Tailscale key | tskey- |
|
||||
| Twilio API Key | Twilio API Key | SK, twilio |
|
||||
| Twitch API token | Twitch API token | twitch |
|
||||
| Twitter token | Twitter token | twitter |
|
||||
| Typeform API token | Typeform API token | typeform |
|
||||
| Yandex.Cloud AWS API compatible Access Secret | Yandex.Cloud AWS API compatible Access Secret | yandex |
|
||||
| Yandex.Cloud IAM API key v1 | Yandex.Cloud IAM Cookie v1 - 3 | yandex |
|
||||
| Yandex.Cloud IAM Cookie v1 | Yandex.Cloud IAM Cookie v1 - 1 | yandex |
|
||||
| Yandex.Cloud IAM Token v1 | Yandex.Cloud IAM Cookie v1 - 2 | yandex |
|
||||
|
||||
<!-- vale gitlab.SentenceSpacing = NO -->
|
||||
<!-- markdownlint-disable MD044 -->
|
||||
<!-- markdownlint-disable MD037 -->
|
||||
<!-- markdownlint-disable MD034 -->
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
stage: Secure
|
||||
group: Secret Detection
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Detected secrets
|
||||
|
||||
This table lists the secrets detected by [secret push protection](index.md).
|
||||
|
||||
<!-- markdownlint-disable MD044 -->
|
||||
<!-- markdownlint-disable MD037 -->
|
||||
|
||||
| Description | ID | Keywords |
|
||||
|:-------------------------------------------------------------------|:---------------------------------------|:-----------------------------------|
|
||||
| AWS Access Token | AWS | AKIA |
|
||||
| GCP API keys can be misused to gain API quota from billed projects | GCP API key | AIza |
|
||||
| GCP OAuth client secrets can be misused to spoof your application | GCP OAuth client secret | GOCSPX- |
|
||||
| GitLab Agent for Kubernetes token | gitlab_kubernetes_agent_token | glagent |
|
||||
| GitLab Feed Token | gitlab_feed_token_v2 | glft |
|
||||
| GitLab Incoming email token | gitlab_incoming_email_token | glimt |
|
||||
| GitLab OAuth Application Secrets | gitlab_oauth_app_secret | gloas |
|
||||
| GitLab Personal Access Token | gitlab_personal_access_token | glpat |
|
||||
| GitLab Pipeline Trigger Token | gitlab_pipeline_trigger_token | glptt |
|
||||
| GitLab Runner Authentication Token | gitlab_runner_auth_token | glrt |
|
||||
| GitLab Runner Registration Token | gitlab_runner_registration_token | GR1348941 |
|
||||
| GitHub App Token | GitHub App Token | ghu_, ghs_ |
|
||||
| GitHub OAuth Access Token | GitHub OAuth Access Token | gho_ |
|
||||
| GitHub Personal Access Token | GitHub Personal Access Token | ghp_ |
|
||||
| GitHub Refresh Token | GitHub Refresh Token | ghr_ |
|
||||
| Google (GCP) Service-account | Google (GCP) Service-account | service_account |
|
||||
| Grafana API token | Grafana API token | grafana |
|
||||
| Hashicorp Terraform user/org API token | Hashicorp Terraform user/org API token | atlasv1, hashicorp, terraform |
|
||||
| Hashicorp Vault batch token | Hashicorp Vault batch token | hashicorp, AAAAAQ, vault |
|
||||
| Mailchimp API key | Mailchimp API key | mailchimp |
|
||||
| Mailgun private API token | Mailgun private API token | mailgun |
|
||||
| Mailgun webhook signing key | Mailgun webhook signing key | mailgun |
|
||||
| New Relic user API ID | New Relic user API ID | newrelic |
|
||||
| New Relic user API Key | New Relic user API Key | NRAK |
|
||||
| npm access token | npm access token | npm_ |
|
||||
| PyPI upload token | PyPI upload token | pypi-AgEIcHlwaS5vcmc |
|
||||
| Rubygem API token | Rubygem API token | rubygems_ |
|
||||
| Segment Public API token | Segment Public API token | sgp_ |
|
||||
| Sendgrid API token | Sendgrid API token | sendgrid |
|
||||
| Shopify access token | Shopify access token | shpat_ |
|
||||
| Shopify custom app access token | Shopify custom app access token | shpca_ |
|
||||
| Shopify private app access token | Shopify private app access token | shppa_ |
|
||||
| Shopify shared secret | Shopify shared secret | shpss_ |
|
||||
| Slack token | Slack token | xoxb, xoxa, xoxp, xoxr, xoxs |
|
||||
| Stripe | Stripe | sk_test, pk_test, sk_live, pk_live |
|
||||
|
||||
<!-- markdownlint-disable MD037 -->
|
||||
<!-- markdownlint-disable MD044 -->
|
||||
|
|
@ -8,20 +8,22 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
DETAILS:
|
||||
**Tier:** Ultimate
|
||||
**Offering:** GitLab Dedicated
|
||||
**Status:** Experiment
|
||||
**Offering:** GitLab.com, GitLab Dedicated
|
||||
**Status:** Beta
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/11439) in GitLab 16.7 as an [experiment](../../../../policy/experiment-beta-support.md) for GitLab Dedicated customers.
|
||||
|
||||
NOTE:
|
||||
This feature is an [experiment](../../../../policy/experiment-beta-support.md), available only on
|
||||
GitLab Dedicated, and is subject to the
|
||||
[GitLab Testing Agreement](https://handbook.gitlab.com/handbook/legal/testing-agreement/).
|
||||
On GitLab.com and GitLab self-managed, use [pipeline secret detection](../index.md) instead.
|
||||
> - [Changed](https://gitlab.com/groups/gitlab-org/-/epics/12729) to Beta in GitLab 17.1.
|
||||
|
||||
Secret push protection blocks secrets such as keys and API tokens from being pushed to GitLab.
|
||||
The content of each commit is checked for secrets when pushed to GitLab. If any secrets are
|
||||
detected, the push is blocked. Regardless of the Git client, GitLab prompts a message when a push is
|
||||
detected, the push is blocked.
|
||||
|
||||
Secret push protection is available on GitLab.com and GitLab Dedicated. To scan for secrets
|
||||
in your GitLab self-managed instance, use [pipeline secret detection](../index.md)
|
||||
instead. Pipeline secret detection can be used together with secret push protection to
|
||||
further secure your GitLab.com or Dedicated instance.
|
||||
|
||||
Regardless of the Git client, GitLab prompts a message when a push is
|
||||
blocked, including details of:
|
||||
|
||||
- Commit ID containing the secret.
|
||||
|
|
@ -41,8 +43,20 @@ remote:
|
|||
remote: To push your changes you must remove the identified secrets.
|
||||
```
|
||||
|
||||
If secret push protection does not detect any secrets in your commits, no message is displayed.
|
||||
|
||||
## Enable secret push protection
|
||||
|
||||
On GitLab Dedicated, you must allow the use of secret push protection in your instance and then enable it per project.
|
||||
On GitLab.com, you only need to enable it per project.
|
||||
|
||||
### Allow the use of secret push protection in your GitLab Dedicated instance
|
||||
|
||||
NOTE:
|
||||
Setting this option gives permission for projects in your GitLab instance to turn on secret push protection.
|
||||
To use secret push protection, you must also enable it in each project. For details,
|
||||
see [enable secret push protection in a project](#enable-secret-push-protection-in-a-project).
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must be an administrator for your GitLab Dedicated instance.
|
||||
|
|
@ -53,6 +67,18 @@ Prerequisites:
|
|||
1. Expand **Secret Detection**.
|
||||
1. Select the **Allow secret push protection** checkbox.
|
||||
|
||||
### Enable secret push protection in a project
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Maintainer role for the project.
|
||||
|
||||
To enable secret push protection in a project:
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. On the left sidebar, select **Secure > Security configuration**.
|
||||
1. Turn on the **Secret push protection** toggle.
|
||||
|
||||
## Coverage
|
||||
|
||||
Secret push protection checks the content of each commit when it is pushed to GitLab.
|
||||
|
|
|
|||
|
|
@ -127,7 +127,8 @@ module Backup
|
|||
Backup::Tasks::TerraformState.id => Backup::Tasks::TerraformState.new(progress: progress, options: options),
|
||||
Backup::Tasks::Registry.id => Backup::Tasks::Registry.new(progress: progress, options: options),
|
||||
Backup::Tasks::Packages.id => Backup::Tasks::Packages.new(progress: progress, options: options),
|
||||
Backup::Tasks::CiSecureFiles.id => Backup::Tasks::CiSecureFiles.new(progress: progress, options: options)
|
||||
Backup::Tasks::CiSecureFiles.id => Backup::Tasks::CiSecureFiles.new(progress: progress, options: options),
|
||||
Backup::Tasks::ExternalDiffs.id => Backup::Tasks::ExternalDiffs.new(progress: progress, options: options)
|
||||
}.freeze
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ module Backup
|
|||
:repositories, # Repositories
|
||||
:packages, # Packages
|
||||
:ci_secure_files, # Project-level Secure Files
|
||||
:external_diffs, # External Merge Request diffs
|
||||
keyword_init: true
|
||||
)
|
||||
|
||||
|
|
@ -219,7 +220,7 @@ module Backup
|
|||
extract_skippables!(backup_information[:skipped]) if backup_information[:skipped]
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/CyclomaticComplexity -- TODO: Complexity will be solved in the Unified Backup implementation (https://gitlab.com/groups/gitlab-org/-/epics/11635)
|
||||
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- TODO: Complexity will be solved in the Unified Backup implementation (https://gitlab.com/groups/gitlab-org/-/epics/11635)
|
||||
# Return a String with a list of skippables, separated by commas
|
||||
#
|
||||
# @return [String] a list of skippables
|
||||
|
|
@ -238,9 +239,10 @@ module Backup
|
|||
list << 'repositories' if skippable_tasks.repositories
|
||||
list << 'packages' if skippable_tasks.packages
|
||||
list << 'ci_secure_files' if skippable_tasks.ci_secure_files
|
||||
list << 'external_diffs' if skippable_tasks.external_diffs
|
||||
list.join(',')
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
||||
|
||||
# Extract skippables from provided data field
|
||||
# Current callers will provide either ENV['SKIP'] or backup_information[:skipped] content
|
||||
|
|
@ -279,6 +281,7 @@ module Backup
|
|||
skippable_tasks.repositories ||= list.include?('repositories') # SKIP=repositories
|
||||
skippable_tasks.packages ||= list.include?('packages') # SKIP=packages
|
||||
skippable_tasks.ci_secure_files ||= list.include?('ci_secure_files') # SKIP=ci_secure_files
|
||||
skippable_tasks.external_diffs ||= list.include?('external_diffs') # SKIP=external_diffs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Backup
|
||||
module Tasks
|
||||
class ExternalDiffs < Task
|
||||
def self.id = 'external_diffs'
|
||||
|
||||
def human_name = _('external diffs')
|
||||
|
||||
def destination_path = 'external_diffs.tar.gz'
|
||||
|
||||
private
|
||||
|
||||
def target
|
||||
@target ||= ::Backup::Targets::Files.new(progress, storage_path, options: options, excludes: ['tmp'])
|
||||
end
|
||||
|
||||
def storage_path
|
||||
Settings.external_diffs.storage_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -11,14 +11,17 @@ module Gitlab
|
|||
# sanitize 6.0 requires only a context argument. Do not add any default
|
||||
# arguments to this method.
|
||||
def sanitize_unsafe_links(env)
|
||||
remove_unsafe_links(env)
|
||||
# sanitize calls this with every node, so no need to check child nodes
|
||||
remove_unsafe_links(env, sanitize_children: false)
|
||||
end
|
||||
|
||||
def remove_unsafe_links(env, remove_invalid_links: true)
|
||||
def remove_unsafe_links(env, remove_invalid_links: true, sanitize_children: true)
|
||||
node = env[:node]
|
||||
|
||||
sanitize_node(node: node, remove_invalid_links: remove_invalid_links)
|
||||
|
||||
return unless sanitize_children
|
||||
|
||||
# HTML entities such as <video></video> have scannable attrs in
|
||||
# children elements, which also need to be sanitized.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -234,6 +234,16 @@ namespace :gitlab do
|
|||
Tasks::Gitlab::Backup.restore_task('ci_secure_files')
|
||||
end
|
||||
end
|
||||
|
||||
namespace :external_diffs do
|
||||
task create: :gitlab_environment do
|
||||
Tasks::Gitlab::Backup.create_task('external_diffs')
|
||||
end
|
||||
|
||||
task restore: :gitlab_environment do
|
||||
Tasks::Gitlab::Backup.restore_task('external_diffs')
|
||||
end
|
||||
end
|
||||
end
|
||||
# namespace end: backup
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15691,6 +15691,9 @@ msgstr ""
|
|||
msgid "Created on %{created_at}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Created on %{date}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Created on:"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -30967,9 +30970,6 @@ msgstr ""
|
|||
msgid "LinkedIn:"
|
||||
msgstr ""
|
||||
|
||||
msgid "LinkedPipelines|%{counterLabel} more downstream pipelines"
|
||||
msgstr ""
|
||||
|
||||
msgid "LinkedResources|Add"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -38436,6 +38436,9 @@ msgstr ""
|
|||
msgid "Pipelines|\"Hello world\" with GitLab CI"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|%{counterLabel} more downstream pipelines"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|Active train"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -46850,6 +46853,9 @@ msgstr ""
|
|||
msgid "Secrets|Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Edit %{id}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -46859,6 +46865,9 @@ msgstr ""
|
|||
msgid "Secrets|Enter a key name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Failed to load secret. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Intervals"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -46880,9 +46889,6 @@ msgstr ""
|
|||
msgid "Secrets|Rotation period"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Secret details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Secrets|Secret key"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -62221,6 +62227,9 @@ msgstr ""
|
|||
msgid "entries cannot contain HTML tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "env"
|
||||
msgstr ""
|
||||
|
||||
msgid "epic"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -62257,6 +62266,9 @@ msgstr ""
|
|||
msgid "expires on %{timebox_due_date}"
|
||||
msgstr ""
|
||||
|
||||
msgid "external diffs"
|
||||
msgstr ""
|
||||
|
||||
msgid "external link"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -10,25 +10,6 @@ module Gitlab
|
|||
link :upgrade_to_premium
|
||||
link :upgrade_to_ultimate
|
||||
|
||||
# Subscription details
|
||||
h5 :subscription_header
|
||||
button :refresh_seats
|
||||
|
||||
# Usage
|
||||
p :seats_in_subscription
|
||||
p :seats_currently_in_use
|
||||
p :max_seats_used
|
||||
p :seats_owed
|
||||
|
||||
# Billing
|
||||
p :subscription_start_date
|
||||
p :subscription_end_date
|
||||
|
||||
def refresh_subscription_seats
|
||||
refresh_seats
|
||||
::QA::Support::WaitForRequests.wait_for_requests
|
||||
end
|
||||
|
||||
# Waits for subscription to be synced and UI to be updated
|
||||
#
|
||||
# @param subscription_plan [String]
|
||||
|
|
|
|||
|
|
@ -100,198 +100,6 @@ module Gitlab
|
|||
def upgrade_to_ultimate?
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @note Defined as +strong :subscription_header+
|
||||
# @return [String] The text content or value of +subscription_header+
|
||||
def subscription_header
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing.subscription_header_element).to exist
|
||||
# end
|
||||
# @return [Watir::Strong] The raw +Strong+ element
|
||||
def subscription_header_element
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing).to be_subscription_header
|
||||
# end
|
||||
# @return [Boolean] true if the +subscription_header+ element is present on the page
|
||||
def subscription_header?
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @note Defined as +button :refresh_seats+
|
||||
# Clicks +refresh_seats+
|
||||
def refresh_seats
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing.refresh_seats_element).to exist
|
||||
# end
|
||||
# @return [Watir::Button] The raw +Button+ element
|
||||
def refresh_seats_element
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing).to be_refresh_seats
|
||||
# end
|
||||
# @return [Boolean] true if the +refresh_seats+ element is present on the page
|
||||
def refresh_seats?
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @note Defined as +p :seats_in_subscription+
|
||||
# @return [String] The text content or value of +seats_in_subscription+
|
||||
def seats_in_subscription
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing.seats_in_subscription_element).to exist
|
||||
# end
|
||||
# @return [Watir::P] The raw +P+ element
|
||||
def seats_in_subscription_element
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing).to be_seats_in_subscription
|
||||
# end
|
||||
# @return [Boolean] true if the +seats_in_subscription+ element is present on the page
|
||||
def seats_in_subscription?
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @note Defined as +p :seats_currently_in_use+
|
||||
# @return [String] The text content or value of +seats_currently_in_use+
|
||||
def seats_currently_in_use
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing.seats_currently_in_use_element).to exist
|
||||
# end
|
||||
# @return [Watir::P] The raw +P+ element
|
||||
def seats_currently_in_use_element
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing).to be_seats_currently_in_use
|
||||
# end
|
||||
# @return [Boolean] true if the +seats_currently_in_use+ element is present on the page
|
||||
def seats_currently_in_use?
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @note Defined as +p :max_seats_used+
|
||||
# @return [String] The text content or value of +max_seats_used+
|
||||
def max_seats_used
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing.max_seats_used_element).to exist
|
||||
# end
|
||||
# @return [Watir::P] The raw +P+ element
|
||||
def max_seats_used_element
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing).to be_max_seats_used
|
||||
# end
|
||||
# @return [Boolean] true if the +max_seats_used+ element is present on the page
|
||||
def max_seats_used?
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @note Defined as +p :seats_owed+
|
||||
# @return [String] The text content or value of +seats_owed+
|
||||
def seats_owed
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing.seats_owed_element).to exist
|
||||
# end
|
||||
# @return [Watir::P] The raw +P+ element
|
||||
def seats_owed_element
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing).to be_seats_owed
|
||||
# end
|
||||
# @return [Boolean] true if the +seats_owed+ element is present on the page
|
||||
def seats_owed?
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @note Defined as +p :subscription_start_date+
|
||||
# @return [String] The text content or value of +subscription_start_date+
|
||||
def subscription_start_date
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing.subscription_start_date_element).to exist
|
||||
# end
|
||||
# @return [Watir::P] The raw +P+ element
|
||||
def subscription_start_date_element
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing).to be_subscription_start_date
|
||||
# end
|
||||
# @return [Boolean] true if the +subscription_start_date+ element is present on the page
|
||||
def subscription_start_date?
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @note Defined as +p :subscription_end_date+
|
||||
# @return [String] The text content or value of +subscription_end_date+
|
||||
def subscription_end_date
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing.subscription_end_date_element).to exist
|
||||
# end
|
||||
# @return [Watir::P] The raw +P+ element
|
||||
def subscription_end_date_element
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
|
||||
# @example
|
||||
# Gitlab::Page::Group::Settings::Billing.perform do |billing|
|
||||
# expect(billing).to be_subscription_end_date
|
||||
# end
|
||||
# @return [Boolean] true if the +subscription_end_date+ element is present on the page
|
||||
def subscription_end_date?
|
||||
# This is a stub, used for indexing. The method is dynamically generated.
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ module QA
|
|||
# It's built on every merge to main branch in the repository.
|
||||
# @name should not contain _ (underscores) as it is used to generate host_name
|
||||
# and _ are not allowed for domain names.
|
||||
# Note: set @host_name = 'localhost' here when running locally against GDK.
|
||||
@image = 'registry.gitlab.com/gitlab-org/analytics-section/product-analytics/' \
|
||||
'gl-application-sdk-browser/example-app:main'
|
||||
@name = 'browser-sdk'
|
||||
@sdk_host = URI(sdk_host)
|
||||
@sdk_app_id = sdk_app_id
|
||||
@port = '8081'
|
||||
@host_name = 'localhost' unless Runtime::Env.running_in_ci?
|
||||
|
||||
super()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ module QA
|
|||
# It's built on every merge to main branch in the repository.
|
||||
# @name should not contain _ (underscores) as it is used to generate host_name
|
||||
# and _ are not allowed for domain names.
|
||||
# Note: set @host_name = 'localhost' here when running locally against GDK.
|
||||
@image = 'registry.gitlab.com/gitlab-org/analytics-section/product-analytics/' \
|
||||
'gl-application-sdk-dotnet/example-app:main'
|
||||
@name = 'dotnet-sdk'
|
||||
@sdk_host = URI(sdk_host)
|
||||
@sdk_app_id = sdk_app_id
|
||||
@port = '5171'
|
||||
@host_name = 'localhost' unless Runtime::Env.running_in_ci?
|
||||
|
||||
super()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Service
|
||||
module DockerRun
|
||||
module ProductAnalytics
|
||||
class NodeSdkApp < Base
|
||||
include Support::API
|
||||
|
||||
def initialize(sdk_host)
|
||||
# Below is an image of a sample app that uses Product Analytics node SDK.
|
||||
# The image is created in https://gitlab.com/gitlab-org/analytics-section/product-analytics/gl-application-sdk-node
|
||||
# It's built on every merge to main branch in the repository.
|
||||
# @name should not contain _ (underscores) as it is used to generate host_name
|
||||
# and _ are not allowed for domain names.
|
||||
@image = 'registry.gitlab.com/gitlab-org/analytics-section/product-analytics/' \
|
||||
'gl-application-sdk-node/example-app:main'
|
||||
@name = 'node-sdk'
|
||||
@sdk_host = URI(sdk_host)
|
||||
@port = '5173'
|
||||
@host_name = 'localhost' unless Runtime::Env.running_in_ci?
|
||||
|
||||
super()
|
||||
end
|
||||
|
||||
def register!(sdk_app_id)
|
||||
shell <<~CMD.tr("\n", ' ')
|
||||
docker run -d --rm
|
||||
--name #{@name}
|
||||
--network #{network}
|
||||
--hostname #{host_name}
|
||||
-p #{@port}:#{@port}
|
||||
-e PA_COLLECTOR_URL=#{@sdk_host}
|
||||
-e PA_APPLICATION_ID=#{sdk_app_id}
|
||||
#{@image}
|
||||
-p #{@port}
|
||||
CMD
|
||||
|
||||
wait_for_app_available
|
||||
end
|
||||
|
||||
def trigger_event
|
||||
get "http://#{host_name}:#{@port}/api/v1/send_event"
|
||||
Runtime::Logger.info('Node SDK event is triggered!')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def wait_for_app_available
|
||||
Runtime::Logger.info("Waiting for Node SDK sample app to become available at http://#{host_name}:#{@port}...")
|
||||
Support::Waiter.wait_until(sleep_interval: 1,
|
||||
message: "Wait for Node SDK sample app to become available at http://#{host_name}:#{@port}") { app_available? }
|
||||
Runtime::Logger.info('Node SDK sample app is up!')
|
||||
end
|
||||
|
||||
def app_available?
|
||||
response = get "http://#{host_name}:#{@port}"
|
||||
response.code == 200
|
||||
rescue Errno::ECONNRESET, Errno::ECONNREFUSED, RestClient::ServerBrokeConnection => e
|
||||
Runtime::Logger.debug("Node SDK sample app is not yet available: #{e.inspect}")
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -13,12 +13,12 @@ module QA
|
|||
# It's built on every merge to main branch in the repository.
|
||||
# @name should not contain _ (underscores) as it is used to generate host_name
|
||||
# and _ are not allowed for domain names.
|
||||
# Note: set @host_name = 'localhost' here when running locally against GDK.
|
||||
@image = 'registry.gitlab.com/gitlab-org/analytics-section/product-analytics/' \
|
||||
'gl-application-sdk-rb/example-app:main'
|
||||
@name = 'ruby-sdk'
|
||||
@sdk_host = URI(sdk_host)
|
||||
@port = '5172'
|
||||
@host_name = 'localhost' unless Runtime::Env.running_in_ci?
|
||||
|
||||
super()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -68,33 +68,25 @@ RSpec.describe Ci::PipelineSchedulesFinder do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#sort' do
|
||||
before do
|
||||
travel_to(Time.zone.local(2024, 3, 2, 1, 0))
|
||||
end
|
||||
|
||||
describe '#sort', time_travel_to: Time.zone.local(2024, 3, 2, 12, 0) do
|
||||
let!(:pipeline1) do
|
||||
create(:ci_pipeline_schedule, description: :aab, ref: :masterb, cron: ' 0 5 * * * ',
|
||||
created_at: Time.zone.local(2024, 3, 2, 1, 0), updated_at: Time.zone.local(2024, 1, 2, 1, 0),
|
||||
project: project)
|
||||
create(:ci_pipeline_schedule, description: :aab, ref: :masterb, cron: ' 0 13 * * * ',
|
||||
created_at: Time.zone.now, updated_at: 2.months.ago, project: project)
|
||||
end
|
||||
|
||||
let!(:pipeline2) do
|
||||
create(:ci_pipeline_schedule, description: :aaa, ref: :masterz, cron: ' 0 6 * * * ',
|
||||
created_at: Time.zone.local(2023, 3, 2, 1, 0), updated_at: Time.zone.local(2024, 3, 2, 1, 0),
|
||||
project: project)
|
||||
create(:ci_pipeline_schedule, description: :aaa, ref: :masterz, cron: ' 0 23 * * * ',
|
||||
created_at: 1.year.ago, updated_at: Time.zone.now, project: project)
|
||||
end
|
||||
|
||||
let!(:pipeline3) do
|
||||
create(:ci_pipeline_schedule, description: :zzz, ref: :mastera, cron: ' 0 8 * * * ',
|
||||
created_at: Time.zone.local(2022, 3, 2, 1, 0), updated_at: Time.zone.local(2024, 4, 2, 1, 0),
|
||||
project: project)
|
||||
create(:ci_pipeline_schedule, description: :zzz, ref: :mastera, cron: ' 0 12 * * * ',
|
||||
created_at: 2.years.ago, updated_at: 1.month.from_now, project: project)
|
||||
end
|
||||
|
||||
let!(:pipeline4) do
|
||||
create(:ci_pipeline_schedule, description: :zza, ref: :mastery, cron: ' 0 7 * * * ',
|
||||
created_at: Time.zone.local(2021, 3, 2, 1, 0), updated_at: Time.zone.local(2024, 2, 2, 1, 0),
|
||||
project: project)
|
||||
create(:ci_pipeline_schedule, description: :zza, ref: :mastery, cron: ' 0 1 * * * ',
|
||||
created_at: 3.years.ago, updated_at: 1.month.ago, project: project)
|
||||
end
|
||||
|
||||
subject { described_class.new(project, params).execute }
|
||||
|
|
|
|||
|
|
@ -416,6 +416,7 @@ export const mockLinkedPipelines = ({ hasDownstream = true, hasUpstream = true }
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-612-612',
|
||||
detailsPath: '/root/trigger-downstream/-/pipelines/610',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
|
|
@ -437,6 +438,7 @@ export const mockLinkedPipelines = ({ hasDownstream = true, hasUpstream = true }
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-611-611',
|
||||
detailsPath: '/root/trigger-downstream/-/pipelines/610',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
|
|
@ -458,6 +460,7 @@ export const mockLinkedPipelines = ({ hasDownstream = true, hasUpstream = true }
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-609-609',
|
||||
detailsPath: '/root/trigger-downstream/-/pipelines/610',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
|
|
@ -485,6 +488,7 @@ export const mockLinkedPipelines = ({ hasDownstream = true, hasUpstream = true }
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-610-610',
|
||||
detailsPath: '/root/trigger-downstream/-/pipelines/610',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import DownstreamPipelines from '~/ci/pipeline_mini_graph/downstream_pipelines.vue';
|
||||
import { downstreamPipelines, singlePipeline } from './mock_data';
|
||||
|
||||
describe('Downstream Pipelines', () => {
|
||||
let wrapper;
|
||||
|
||||
const findCiIcons = () => wrapper.findAllComponents(CiIcon);
|
||||
const findPipelineCounter = () => wrapper.findByTestId('downstream-pipeline-counter');
|
||||
const findDownstreamPipeline = () => wrapper.findByTestId('downstream-pipelines');
|
||||
const findDownstreamPipelines = () => wrapper.findAllByTestId('downstream-pipelines');
|
||||
const findDownstreamPipelinesComponent = () => wrapper.findComponent(DownstreamPipelines);
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = mountExtended(DownstreamPipelines, {
|
||||
directives: {
|
||||
GlTooltip: createMockDirective('gl-tooltip'),
|
||||
},
|
||||
propsData: {
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('when passed 1 downstream pipeline as props', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
pipelines: [singlePipeline],
|
||||
pipelinePath: 'my/pipeline/path',
|
||||
});
|
||||
});
|
||||
|
||||
it('should render the correct ci status icon', () => {
|
||||
expect(wrapper.find('[data-testid="status_success_borderless-icon"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have the correct title assigned for the tooltip', () => {
|
||||
expect(findDownstreamPipeline().attributes('title')).toBe('trigger-downstream - passed');
|
||||
});
|
||||
|
||||
it('should not render the pipeline counter', () => {
|
||||
expect(findPipelineCounter().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when passed > 3 downstream pipelines as props', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
pipelines: downstreamPipelines,
|
||||
pipelinePath: 'my/pipeline/path',
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipelines', () => {
|
||||
it('should render three pipeline items', () => {
|
||||
expect(findDownstreamPipelines().exists()).toBe(true);
|
||||
expect(findDownstreamPipelines()).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should render three ci status icons', () => {
|
||||
expect(findCiIcons().exists()).toBe(true);
|
||||
expect(findCiIcons()).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should correctly trim pipelines', () => {
|
||||
expect(findDownstreamPipelinesComponent().props('pipelines')).toHaveLength(4);
|
||||
expect(findDownstreamPipelines()).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipeline counter', () => {
|
||||
it('should render the pipeline counter', () => {
|
||||
expect(findPipelineCounter().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render the correct tooltip text', () => {
|
||||
const tooltip = getBinding(findPipelineCounter().element, 'gl-tooltip');
|
||||
|
||||
expect(tooltip.value.title).toContain('more downstream pipelines');
|
||||
});
|
||||
|
||||
it('should set the correct pipeline path', () => {
|
||||
expect(findPipelineCounter().attributes('href')).toBe('my/pipeline/path');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import LinkedPipelinesMiniList from '~/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue';
|
||||
import mockData from '../linked_pipelines_mock_data';
|
||||
import LinkedPipelinesMiniList from '~/ci/pipeline_mini_graph/legacy_pipeline_mini_graph/legacy_linked_pipelines_mini_list.vue';
|
||||
import mockData from '../legacy_linked_pipelines_mock_data';
|
||||
|
||||
describe('Linked pipeline mini list', () => {
|
||||
let wrapper;
|
||||
|
|
@ -61,12 +61,6 @@ describe('Linked pipeline mini list', () => {
|
|||
expect(tooltip.value.title).toBe('GitLabCE - running');
|
||||
});
|
||||
|
||||
it('should correctly set is-upstream', () => {
|
||||
expect(findLinkedPipelineMiniList().exists()).toBe(true);
|
||||
|
||||
expect(findLinkedPipelineMiniList().classes('is-upstream')).toBe(true);
|
||||
});
|
||||
|
||||
it('should correctly compute shouldRenderCounter', () => {
|
||||
expect(findLinkedPipelineMiniList().vm.shouldRenderCounter).toBe(false);
|
||||
});
|
||||
|
|
@ -105,12 +99,6 @@ describe('Linked pipeline mini list', () => {
|
|||
expect(tooltip.value.title).toBe('GitLabCE - running');
|
||||
});
|
||||
|
||||
it('should correctly set is-downstream', () => {
|
||||
expect(findLinkedPipelineMiniList().exists()).toBe(true);
|
||||
|
||||
expect(findLinkedPipelineMiniList().classes('is-downstream')).toBe(true);
|
||||
});
|
||||
|
||||
it('should render the pipeline counter', () => {
|
||||
expect(findLinkedPipelineCounter().exists()).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils';
|
|||
import { pipelines } from 'test_fixtures/pipelines/pipelines.json';
|
||||
import LegacyPipelineMiniGraph from '~/ci/pipeline_mini_graph/legacy_pipeline_mini_graph/legacy_pipeline_mini_graph.vue';
|
||||
import PipelineStages from '~/ci/pipeline_mini_graph/legacy_pipeline_mini_graph/legacy_pipeline_stages.vue';
|
||||
import mockLinkedPipelines from '../linked_pipelines_mock_data';
|
||||
import mockLinkedPipelines from '../legacy_linked_pipelines_mock_data';
|
||||
|
||||
const mockStages = pipelines[0].details.stages;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,140 +0,0 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import LinkedPipelinesMiniList from '~/ci/pipeline_mini_graph/linked_pipelines_mini_list.vue';
|
||||
import mockData from './linked_pipelines_mock_data';
|
||||
|
||||
describe('Linked pipeline mini list', () => {
|
||||
let wrapper;
|
||||
|
||||
const findCiIcon = () => wrapper.findComponent(CiIcon);
|
||||
const findCiIcons = () => wrapper.findAllComponents(CiIcon);
|
||||
const findLinkedPipelineCounter = () => wrapper.find('[data-testid="linked-pipeline-counter"]');
|
||||
const findLinkedPipelineMiniItem = () =>
|
||||
wrapper.find('[data-testid="linked-pipeline-mini-item"]');
|
||||
const findLinkedPipelineMiniItems = () =>
|
||||
wrapper.findAll('[data-testid="linked-pipeline-mini-item"]');
|
||||
const findLinkedPipelineMiniList = () => wrapper.findComponent(LinkedPipelinesMiniList);
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = mount(LinkedPipelinesMiniList, {
|
||||
directives: {
|
||||
GlTooltip: createMockDirective('gl-tooltip'),
|
||||
},
|
||||
propsData: {
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('when passed an upstream pipeline as prop', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
triggeredBy: [mockData.triggered_by],
|
||||
});
|
||||
});
|
||||
|
||||
it('should render one linked pipeline item', () => {
|
||||
expect(findLinkedPipelineMiniItem().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render a linked pipeline with the correct href', () => {
|
||||
expect(findLinkedPipelineMiniItem().exists()).toBe(true);
|
||||
|
||||
expect(findLinkedPipelineMiniItem().attributes('href')).toBe(
|
||||
'/gitlab-org/gitlab-foss/-/pipelines/129',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render one ci status icon', () => {
|
||||
expect(findCiIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render the correct ci status icon', () => {
|
||||
expect(wrapper.find('[data-testid="status_running_borderless-icon"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have an activated tooltip', () => {
|
||||
expect(findLinkedPipelineMiniItem().exists()).toBe(true);
|
||||
const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip');
|
||||
|
||||
expect(tooltip.value.title).toBe('GitLabCE - running');
|
||||
});
|
||||
|
||||
it('should correctly set is-upstream', () => {
|
||||
expect(findLinkedPipelineMiniList().exists()).toBe(true);
|
||||
|
||||
expect(findLinkedPipelineMiniList().classes('is-upstream')).toBe(true);
|
||||
});
|
||||
|
||||
it('should correctly compute shouldRenderCounter', () => {
|
||||
expect(findLinkedPipelineMiniList().vm.shouldRenderCounter).toBe(false);
|
||||
});
|
||||
|
||||
it('should not render the pipeline counter', () => {
|
||||
expect(findLinkedPipelineCounter().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when passed downstream pipelines as props', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
triggered: mockData.triggered,
|
||||
pipelinePath: 'my/pipeline/path',
|
||||
});
|
||||
});
|
||||
|
||||
it('should render three linked pipeline items', () => {
|
||||
expect(findLinkedPipelineMiniItems().exists()).toBe(true);
|
||||
expect(findLinkedPipelineMiniItems().length).toBe(3);
|
||||
});
|
||||
|
||||
it('should render three ci status icons', () => {
|
||||
expect(findCiIcons().exists()).toBe(true);
|
||||
expect(findCiIcons().length).toBe(3);
|
||||
});
|
||||
|
||||
it('should render the correct ci status icon', () => {
|
||||
expect(wrapper.find('[data-testid="status_running_borderless-icon"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have an activated tooltip', () => {
|
||||
expect(findLinkedPipelineMiniItem().exists()).toBe(true);
|
||||
const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip');
|
||||
|
||||
expect(tooltip.value.title).toBe('GitLabCE - running');
|
||||
});
|
||||
|
||||
it('should correctly set is-downstream', () => {
|
||||
expect(findLinkedPipelineMiniList().exists()).toBe(true);
|
||||
|
||||
expect(findLinkedPipelineMiniList().classes('is-downstream')).toBe(true);
|
||||
});
|
||||
|
||||
it('should render the pipeline counter', () => {
|
||||
expect(findLinkedPipelineCounter().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should correctly compute shouldRenderCounter', () => {
|
||||
expect(findLinkedPipelineMiniList().vm.shouldRenderCounter).toBe(true);
|
||||
});
|
||||
|
||||
it('should correctly trim linkedPipelines', () => {
|
||||
expect(findLinkedPipelineMiniList().props('triggered').length).toBe(6);
|
||||
expect(findLinkedPipelineMiniList().vm.linkedPipelinesTrimmed.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should set the correct pipeline path', () => {
|
||||
expect(findLinkedPipelineCounter().exists()).toBe(true);
|
||||
|
||||
expect(findLinkedPipelineCounter().attributes('href')).toBe('my/pipeline/path');
|
||||
});
|
||||
|
||||
it('should render the correct counterTooltipText', () => {
|
||||
expect(findLinkedPipelineCounter().exists()).toBe(true);
|
||||
const tooltip = getBinding(findLinkedPipelineCounter().element, 'gl-tooltip');
|
||||
|
||||
expect(tooltip.value.title).toBe(findLinkedPipelineMiniList().vm.counterTooltipText);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -10,7 +10,7 @@ export const mockDownstreamPipelinesGraphql = () => ({
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-612-612',
|
||||
group: 'success',
|
||||
detailsPath: '/root/job-log-sections/-/pipelines/612',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
__typename: 'DetailedStatus',
|
||||
|
|
@ -27,7 +27,7 @@ export const mockDownstreamPipelinesGraphql = () => ({
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-611-611',
|
||||
group: 'success',
|
||||
detailsPath: '/root/job-log-sections/-/pipelines/611',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
__typename: 'DetailedStatus',
|
||||
|
|
@ -44,7 +44,7 @@ export const mockDownstreamPipelinesGraphql = () => ({
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-609-609',
|
||||
group: 'success',
|
||||
detailsPath: '/root/job-log-sections/-/pipelines/609',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
__typename: 'DetailedStatus',
|
||||
|
|
@ -63,13 +63,12 @@ export const pipelineStage = {
|
|||
__typename: 'DetailedStatus',
|
||||
id: 'success-409-409',
|
||||
icon: 'status_success',
|
||||
group: 'success',
|
||||
label: 'passed',
|
||||
},
|
||||
};
|
||||
|
||||
const upstream = {
|
||||
export const singlePipeline = {
|
||||
id: 'gid://gitlab/Ci::Pipeline/610',
|
||||
path: '/root/trigger-downstream/-/pipelines/610',
|
||||
project: {
|
||||
id: 'gid://gitlab/Project/21',
|
||||
name: 'trigger-downstream',
|
||||
|
|
@ -77,7 +76,7 @@ const upstream = {
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-610-610',
|
||||
group: 'success',
|
||||
detailsPath: '/root/trigger-downstream/-/pipelines/610',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
__typename: 'DetailedStatus',
|
||||
|
|
@ -90,9 +89,10 @@ export const mockPipelineMiniGraphQueryResponse = {
|
|||
project: {
|
||||
id: 'gid://gitlab/Project/20',
|
||||
pipeline: {
|
||||
id: 'gid://gitlab/Ci::Pipeline/320',
|
||||
id: 'gid://gitlab/Ci::Pipeline/315',
|
||||
path: '/a/path',
|
||||
downstream: mockDownstreamPipelinesGraphql(),
|
||||
upstream,
|
||||
upstream: singlePipeline,
|
||||
stages: {
|
||||
nodes: [pipelineStage],
|
||||
},
|
||||
|
|
@ -101,6 +101,16 @@ export const mockPipelineMiniGraphQueryResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
export const mockPMGQueryNoDownstreamResponse = {
|
||||
...mockPipelineMiniGraphQueryResponse,
|
||||
downstream: { nodes: [] },
|
||||
};
|
||||
|
||||
export const mockPMGQueryNoUpstreamResponse = {
|
||||
...mockPipelineMiniGraphQueryResponse,
|
||||
upstream: null,
|
||||
};
|
||||
|
||||
export const mockPipelineStatusResponse = {
|
||||
data: {
|
||||
project: {
|
||||
|
|
@ -121,22 +131,66 @@ export const mockPipelineStatusResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
export const mockUpstreamDownstreamQueryResponse = {
|
||||
data: {
|
||||
export const pipelineMiniGraphFetchError = 'There was a problem fetching the pipeline mini graph.';
|
||||
|
||||
export const downstreamPipelines = [
|
||||
{
|
||||
id: 'gid://gitlab/Ci::Pipeline/612',
|
||||
path: '/root/job-log-sections/-/pipelines/612',
|
||||
project: {
|
||||
id: '1',
|
||||
pipeline: {
|
||||
id: 'pipeline-1',
|
||||
path: '/root/ci-project/-/pipelines/790',
|
||||
downstream: mockDownstreamPipelinesGraphql(),
|
||||
upstream,
|
||||
},
|
||||
__typename: 'Project',
|
||||
id: 'gid://gitlab/Project/21',
|
||||
name: 'job-log-sections',
|
||||
},
|
||||
detailedStatus: {
|
||||
id: 'success-612-612',
|
||||
detailsPath: '/hello',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const pipelineMiniGraphFetchError = 'There was a problem fetching the pipeline mini graph.';
|
||||
{
|
||||
id: 'gid://gitlab/Ci::Pipeline/611',
|
||||
path: '/root/job-log-sections/-/pipelines/611',
|
||||
project: {
|
||||
id: 'gid://gitlab/Project/21',
|
||||
name: 'job-log-sections',
|
||||
},
|
||||
detailedStatus: {
|
||||
id: 'success-611-611',
|
||||
detailsPath: '/hello',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/Ci::Pipeline/609',
|
||||
path: '/root/job-log-sections/-/pipelines/609',
|
||||
project: {
|
||||
id: 'gid://gitlab/Project/21',
|
||||
name: 'job-log-sections',
|
||||
},
|
||||
detailedStatus: {
|
||||
id: 'success-609-609',
|
||||
detailsPath: '/hello',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/Ci::Pipeline/610',
|
||||
path: '/root/test-project/-/pipelines/610',
|
||||
project: {
|
||||
id: 'gid://gitlab/Project/22',
|
||||
name: 'test-project',
|
||||
},
|
||||
detailedStatus: {
|
||||
id: 'success-609-609',
|
||||
detailsPath: '/hello',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const legacyStageReply = {
|
||||
name: 'deploy',
|
||||
|
|
|
|||
|
|
@ -2,16 +2,24 @@ import Vue from 'vue';
|
|||
import VueApollo from 'vue-apollo';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
|
||||
import { createAlert } from '~/alert';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { createAlert } from '~/alert';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
||||
import getPipelineMiniGraphQuery from '~/ci/pipeline_mini_graph/graphql/queries/get_pipeline_mini_graph.query.graphql';
|
||||
import PipelineMiniGraph from '~/ci/pipeline_mini_graph/pipeline_mini_graph.vue';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
|
||||
import DownstreamPipelines from '~/ci/pipeline_mini_graph/downstream_pipelines.vue';
|
||||
import PipelineStages from '~/ci/pipeline_mini_graph/pipeline_stages.vue';
|
||||
import * as sharedGraphQlUtils from '~/graphql_shared/utils';
|
||||
|
||||
import { pipelineMiniGraphFetchError, mockPipelineMiniGraphQueryResponse } from './mock_data';
|
||||
import {
|
||||
pipelineMiniGraphFetchError,
|
||||
mockPipelineMiniGraphQueryResponse,
|
||||
mockPMGQueryNoUpstreamResponse,
|
||||
mockPMGQueryNoDownstreamResponse,
|
||||
} from './mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
jest.mock('~/alert');
|
||||
|
|
@ -20,31 +28,35 @@ describe('PipelineMiniGraph', () => {
|
|||
let wrapper;
|
||||
let pipelineMiniGraphResponse;
|
||||
|
||||
const fullPath = 'gitlab-org/gitlab';
|
||||
const iid = '315';
|
||||
const pipelineEtag = '/api/graphql:pipelines/id/315';
|
||||
const defaultProps = {
|
||||
fullPath: 'gitlab-org/gitlab',
|
||||
iid: '315',
|
||||
pipelineEtag: '/api/graphql:pipelines/id/315',
|
||||
};
|
||||
|
||||
const createComponent = ({ pipelineMiniGraphHandler = pipelineMiniGraphResponse } = {}) => {
|
||||
const createComponent = async ({ pipelineMiniGraphHandler = pipelineMiniGraphResponse } = {}) => {
|
||||
const handlers = [[getPipelineMiniGraphQuery, pipelineMiniGraphHandler]];
|
||||
const mockApollo = createMockApollo(handlers);
|
||||
|
||||
wrapper = shallowMountExtended(PipelineMiniGraph, {
|
||||
propsData: {
|
||||
fullPath,
|
||||
iid,
|
||||
pipelineEtag,
|
||||
...defaultProps,
|
||||
},
|
||||
apolloProvider: mockApollo,
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
await waitForPromises();
|
||||
};
|
||||
|
||||
const findPipelineMiniGraph = () => wrapper.findComponent('[data-testid="pipeline-mini-graph"]');
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findPipelineMiniGraph = () => wrapper.findComponent('[data-testid="pipeline-mini-graph"]');
|
||||
const findUpstream = () => wrapper.findComponent(CiIcon);
|
||||
const findDownstream = () => wrapper.findComponent(DownstreamPipelines);
|
||||
const findStages = () => wrapper.findComponent(PipelineStages);
|
||||
|
||||
beforeEach(() => {
|
||||
pipelineMiniGraphResponse = jest.fn().mockResolvedValue(mockPipelineMiniGraphQueryResponse);
|
||||
pipelineMiniGraphResponse = jest.fn();
|
||||
pipelineMiniGraphResponse.mockResolvedValue(mockPipelineMiniGraphQueryResponse);
|
||||
});
|
||||
|
||||
describe('when initial query is loading', () => {
|
||||
|
|
@ -52,30 +64,75 @@ describe('PipelineMiniGraph', () => {
|
|||
createComponent();
|
||||
});
|
||||
|
||||
it('shows a loading icon and no mini graph', () => {
|
||||
it('renders a loading icon', () => {
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render the mini graph', () => {
|
||||
expect(findPipelineMiniGraph().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when query has loaded', () => {
|
||||
it('does not show a loading icon', async () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
});
|
||||
|
||||
it('does not show a loading icon', () => {
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders the Pipeline Mini Graph', async () => {
|
||||
await createComponent();
|
||||
|
||||
it('renders the Pipeline Mini Graph', () => {
|
||||
expect(findPipelineMiniGraph().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('fires the query', async () => {
|
||||
await createComponent();
|
||||
it('fires the query', () => {
|
||||
const { iid, fullPath } = defaultProps;
|
||||
|
||||
expect(pipelineMiniGraphResponse).toHaveBeenCalledWith({ iid, fullPath });
|
||||
});
|
||||
|
||||
describe('stages', () => {
|
||||
it('renders stages', () => {
|
||||
expect(findStages().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('sends the necessary props', () => {
|
||||
expect(findStages().props()).toMatchObject({
|
||||
isMergeTrain: expect.any(Boolean),
|
||||
stages: expect.any(Array),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('upstream', () => {
|
||||
it('renders upstream if available', () => {
|
||||
expect(findUpstream().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render upstream if not available', () => {
|
||||
pipelineMiniGraphResponse.mockResolvedValue(mockPMGQueryNoUpstreamResponse);
|
||||
expect(findUpstream().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('downstream', () => {
|
||||
it('renders downstream if available', () => {
|
||||
expect(findDownstream().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('sends the necessary props', () => {
|
||||
expect(findDownstream().props()).toMatchObject({
|
||||
pipelines: expect.any(Array),
|
||||
pipelinePath: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render downstream if not available', () => {
|
||||
pipelineMiniGraphResponse.mockResolvedValue(mockPMGQueryNoDownstreamResponse);
|
||||
expect(findUpstream().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('polling', () => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-612-612',
|
||||
detailsPath: '/root/trigger-downstream/-/pipelines/610',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
|
|
@ -31,6 +32,7 @@ export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-611-611',
|
||||
detailsPath: '/root/trigger-downstream/-/pipelines/610',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
|
|
@ -52,6 +54,7 @@ export const mockDownstreamPipelinesGraphql = ({ includeSourceJobRetried = true
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-609-609',
|
||||
detailsPath: '/root/trigger-downstream/-/pipelines/610',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
|
|
@ -77,6 +80,7 @@ const upstream = {
|
|||
},
|
||||
detailedStatus: {
|
||||
id: 'success-610-610',
|
||||
detailsPath: '/root/trigger-downstream/-/pipelines/610',
|
||||
group: 'success',
|
||||
icon: 'status_success',
|
||||
label: 'passed',
|
||||
|
|
|
|||
|
|
@ -30,33 +30,25 @@ RSpec.describe Resolvers::ProjectPipelineSchedulesResolver do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#sort' do
|
||||
before do
|
||||
travel_to(Time.zone.local(2024, 3, 2, 1, 0))
|
||||
describe '#sort', time_travel_to: Time.zone.local(2024, 3, 2, 12, 0) do
|
||||
let!(:pipeline1) do
|
||||
create(:ci_pipeline_schedule, description: :aab, ref: :masterb, cron: ' 0 13 * * * ',
|
||||
created_at: Time.zone.now, updated_at: 2.months.ago, project: project)
|
||||
end
|
||||
|
||||
let_it_be(:pipeline1) do
|
||||
create(:ci_pipeline_schedule, description: :aab, ref: :masterb, cron: ' 0 5 * * * ',
|
||||
created_at: Time.zone.local(2024, 3, 2, 1, 0), updated_at: Time.zone.local(2024, 1, 2, 1, 0),
|
||||
project: project)
|
||||
let!(:pipeline2) do
|
||||
create(:ci_pipeline_schedule, description: :aaa, ref: :masterz, cron: ' 0 23 * * * ',
|
||||
created_at: 1.year.ago, updated_at: Time.zone.now, project: project)
|
||||
end
|
||||
|
||||
let_it_be(:pipeline2) do
|
||||
create(:ci_pipeline_schedule, description: :aaa, ref: :masterz, cron: ' 0 6 * * * ',
|
||||
created_at: Time.zone.local(2023, 3, 2, 1, 0), updated_at: Time.zone.local(2024, 3, 2, 1, 0),
|
||||
project: project)
|
||||
let!(:pipeline3) do
|
||||
create(:ci_pipeline_schedule, description: :zzz, ref: :mastera, cron: ' 0 12 * * * ',
|
||||
created_at: 2.years.ago, updated_at: 1.month.from_now, project: project)
|
||||
end
|
||||
|
||||
let_it_be(:pipeline3) do
|
||||
create(:ci_pipeline_schedule, description: :zzz, ref: :mastera, cron: ' 0 8 * * * ',
|
||||
created_at: Time.zone.local(2022, 3, 2, 1, 0), updated_at: Time.zone.local(2024, 4, 2, 1, 0),
|
||||
project: project)
|
||||
end
|
||||
|
||||
let_it_be(:pipeline4) do
|
||||
create(:ci_pipeline_schedule, description: :zza, ref: :mastery, cron: ' 0 7 * * * ',
|
||||
created_at: Time.zone.local(2021, 3, 2, 1, 0), updated_at: Time.zone.local(2024, 2, 2, 1, 0),
|
||||
project: project)
|
||||
let!(:pipeline4) do
|
||||
create(:ci_pipeline_schedule, description: :zza, ref: :mastery, cron: ' 0 1 * * * ',
|
||||
created_at: 3.years.ago, updated_at: 1.month.ago, project: project)
|
||||
end
|
||||
|
||||
context "with by id" do
|
||||
|
|
@ -100,12 +92,12 @@ RSpec.describe Resolvers::ProjectPipelineSchedulesResolver do
|
|||
end
|
||||
|
||||
context "with by next_run_at" do
|
||||
it "sorts desc", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/466308' do
|
||||
it "sorts desc" do
|
||||
expect(resolve_pipeline_schedules(args: { sort: :next_run_at_desc }).to_a).to eq([pipeline3, pipeline4,
|
||||
pipeline2, pipeline1])
|
||||
end
|
||||
|
||||
it "sorts asc", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/466309' do
|
||||
it "sorts asc" do
|
||||
expect(resolve_pipeline_schedules(args: { sort: :next_run_at_asc }).to_a).to eq([pipeline1, pipeline2,
|
||||
pipeline4, pipeline3])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ RSpec.describe PreferencesHelper do
|
|||
expect(helper.user_application_theme).to eq 'ui-neutral'
|
||||
end
|
||||
|
||||
it 'returns the default when id is invalid' do
|
||||
it 'returns the default when id is invalid', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/444873' do
|
||||
stub_user(theme_id: Gitlab::Themes.count + 5)
|
||||
|
||||
allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(1)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
|
|||
struct
|
||||
end
|
||||
|
||||
let_it_be(:nodes_with_children) do
|
||||
<<~TEXT
|
||||
<details><summary>test</summary>
|
||||
<a href="javascript://test">Javascript</a>
|
||||
<a href="data://example">Data</a>
|
||||
</details>
|
||||
TEXT
|
||||
end
|
||||
|
||||
subject(:object) { klass.new(:value) }
|
||||
|
||||
invalid_schemes = [
|
||||
|
|
@ -72,6 +81,25 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
|
|||
end
|
||||
end
|
||||
|
||||
context 'handling child nodes' do
|
||||
let(:doc) { HTML::Pipeline.parse(nodes_with_children) }
|
||||
let(:node) { doc.children.first }
|
||||
|
||||
it 'santizes child nodes' do
|
||||
object.remove_unsafe_links(env, sanitize_children: true)
|
||||
|
||||
expect(doc.to_html).to include '<a>Javascript</a>'
|
||||
expect(doc.to_html).to include '<a>Data</a>'
|
||||
end
|
||||
|
||||
it 'does not sanitize child nodes if sanitize_children is false' do
|
||||
object.remove_unsafe_links(env, sanitize_children: false)
|
||||
|
||||
expect(doc.to_html).to include '<a href="javascript://test">Javascript</a>'
|
||||
expect(doc.to_html).to include '<a href="data://example">Data</a>'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when URI is valid' do
|
||||
let(:doc) { HTML::Pipeline.parse("<a href='http://example.com'>foo</a>") }
|
||||
let(:node) { doc.children.first }
|
||||
|
|
@ -121,12 +149,18 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
|
|||
end
|
||||
|
||||
describe '#sanitize_unsafe_links' do
|
||||
let(:env) { { node: 'node' } }
|
||||
it 'sanitizes all nodes without specifically recursing children' do
|
||||
doc = HTML::Pipeline.parse(nodes_with_children)
|
||||
allowlist = { elements: %w[details summary a], attributes: { 'a' => ['href'] },
|
||||
transformers: [object.method(:sanitize_unsafe_links)] }
|
||||
|
||||
it 'makes a call to #remove_unsafe_links_method' do
|
||||
expect(object).to receive(:remove_unsafe_links).with(env)
|
||||
expect(object).to receive(:remove_unsafe_links).with(anything, sanitize_children: false)
|
||||
.at_least(4).times.and_call_original
|
||||
|
||||
object.sanitize_unsafe_links(env)
|
||||
Sanitize.clean_node!(doc, allowlist)
|
||||
|
||||
expect(doc.to_html).to include '<a>Javascript</a>'
|
||||
expect(doc.to_html).to include '<a>Data</a>'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ module TestEnv
|
|||
FileUtils.mkdir_p(terraform_state_path)
|
||||
FileUtils.mkdir_p(packages_path)
|
||||
FileUtils.mkdir_p(ci_secure_files_path)
|
||||
FileUtils.mkdir_p(external_diffs_path)
|
||||
end
|
||||
|
||||
def setup_gitlab_shell
|
||||
|
|
@ -358,6 +359,10 @@ module TestEnv
|
|||
Gitlab.config.ci_secure_files.storage_path
|
||||
end
|
||||
|
||||
def external_diffs_path
|
||||
Gitlab.config.external_diffs.storage_path
|
||||
end
|
||||
|
||||
# When no cached assets exist, manually hit the root path to create them
|
||||
#
|
||||
# Otherwise they'd be created by the first test, often timing out and
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@ RSpec.describe 'gitlab:backup namespace rake tasks', :reestablished_active_recor
|
|||
let(:enable_registry) { true }
|
||||
let(:backup_restore_pid_path) { "#{Rails.application.root}/tmp/backup_restore.pid" }
|
||||
let(:backup_rake_task_names) do
|
||||
%w[db repo uploads builds artifacts pages lfs terraform_state registry packages ci_secure_files]
|
||||
%w[db repo uploads builds artifacts pages lfs terraform_state registry packages ci_secure_files external_diffs]
|
||||
end
|
||||
|
||||
let(:progress) { StringIO.new }
|
||||
|
||||
let(:backup_task_ids) do
|
||||
%w[db repositories uploads builds artifacts pages lfs terraform_state registry packages ci_secure_files]
|
||||
%w[
|
||||
db repositories uploads builds artifacts pages lfs terraform_state registry packages ci_secure_files
|
||||
external_diffs
|
||||
]
|
||||
end
|
||||
|
||||
def tars_glob
|
||||
|
|
@ -312,7 +315,9 @@ RSpec.describe 'gitlab:backup namespace rake tasks', :reestablished_active_recor
|
|||
"Dumping packages ... ",
|
||||
"Dumping packages ... done",
|
||||
"Dumping ci secure files ... ",
|
||||
"Dumping ci secure files ... done"
|
||||
"Dumping ci secure files ... done",
|
||||
"Dumping external diffs ... ",
|
||||
"Dumping external diffs ... done"
|
||||
])
|
||||
|
||||
backup_rake_task_names.each do |task|
|
||||
|
|
@ -399,6 +404,7 @@ RSpec.describe 'gitlab:backup namespace rake tasks', :reestablished_active_recor
|
|||
registry.tar.gz
|
||||
packages.tar.gz
|
||||
ci_secure_files.tar.gz
|
||||
external_diffs.tar.gz
|
||||
]
|
||||
)
|
||||
|
||||
|
|
@ -414,6 +420,7 @@ RSpec.describe 'gitlab:backup namespace rake tasks', :reestablished_active_recor
|
|||
expect(tar_contents).to match('registry.tar.gz')
|
||||
expect(tar_contents).to match('packages.tar.gz')
|
||||
expect(tar_contents).to match('ci_secure_files.tar.gz')
|
||||
expect(tar_contents).to match('external_diffs.tar.gz')
|
||||
expect(tar_contents).not_to match(%r{^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|
|
||||
pages.tar.gz|artifacts.tar.gz|registry.tar.gz)/$})
|
||||
end
|
||||
|
|
@ -674,7 +681,8 @@ RSpec.describe 'gitlab:backup namespace rake tasks', :reestablished_active_recor
|
|||
'registry.tar.gz',
|
||||
'packages.tar.gz',
|
||||
'repositories',
|
||||
'ci_secure_files.tar.gz'
|
||||
'ci_secure_files.tar.gz',
|
||||
'external_diffs.tar.gz'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue