Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-06-11 00:24:51 +00:00
parent 5aaced570d
commit 3f11364a27
49 changed files with 824 additions and 661 deletions

View File

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

View File

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

View File

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

View File

@ -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"},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
module ProtectedRefAccess
include Importable
extend ActiveSupport::Concern
class_methods do

View File

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

View File

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

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
class ProtectedTag::CreateAccessLevel < ApplicationRecord
include Importable
include ProtectedTagAccess
include ProtectedRefDeployKeyAccess
end

View File

@ -62,6 +62,11 @@ domains:
- permissions
- system_access
Backup:
description: Backup and restore
feature_categories:
- backup_restore
Boards:
description:
feature_categories:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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', () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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