Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-04-13 21:08:33 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-04-13 21:08:33 +0300
commit953eb09e086c8f2842512a62e56e32223b5bf974 (patch)
tree3915a57e930263b7c31a1b5e819db50f7d3a609f
parent907fd5d94ecec19ff7de4986e83e75e6fa082558 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml12
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/clusters_list/clusters_util.js13
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_token.vue2
-rw-r--r--app/assets/javascripts/clusters_list/constants.js4
-rw-r--r--app/assets/javascripts/incidents/components/incidents_list.vue15
-rw-r--r--app/assets/javascripts/pipeline_editor/constants.js2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue81
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue (renamed from app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue)62
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue32
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss5
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss5
-rw-r--r--app/assets/stylesheets/startup/startup-signin.scss5
-rw-r--r--app/controllers/clusters/clusters_controller.rb12
-rw-r--r--app/controllers/groups_controller.rb5
-rw-r--r--app/controllers/import/gitlab_groups_controller.rb5
-rw-r--r--app/controllers/jira_connect/subscriptions_controller.rb4
-rw-r--r--app/graphql/resolvers/base_resolver.rb2
-rw-r--r--app/helpers/merge_requests_helper.rb2
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/concerns/issuable.rb12
-rw-r--r--app/presenters/README.md8
-rw-r--r--app/presenters/ci/bridge_presenter.rb4
-rw-r--r--app/presenters/ci/build_presenter.rb6
-rw-r--r--app/presenters/commit_status_presenter.rb2
-rw-r--r--app/presenters/dev_ops_report/metric_presenter.rb44
-rw-r--r--app/presenters/event_presenter.rb2
-rw-r--r--app/presenters/gitlab/blame_presenter.rb2
-rw-r--r--app/presenters/label_presenter.rb2
-rw-r--r--app/presenters/pages_domain_presenter.rb2
-rw-r--r--app/services/alert_management/alerts/update_service.rb21
-rw-r--r--app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb13
-rw-r--r--app/services/issues/create_service.rb5
-rw-r--r--app/views/groups/milestones/index.html.haml4
-rw-r--r--app/views/groups/settings/_export.html.haml2
-rw-r--r--config/feature_flags/development/ci_variables_builder_config_variables.yml (renamed from config/feature_flags/development/group_import_export.yml)12
-rw-r--r--config/feature_flags/development/vulnerability_reads_table.yml2
-rw-r--r--db/docs/approval_merge_request_rules_approved_approvers.yml2
-rw-r--r--db/docs/boards.yml2
-rw-r--r--db/docs/ci_build_report_results.yml2
-rw-r--r--db/docs/ci_pipelines.yml2
-rw-r--r--db/docs/ci_pipelines_config.yml2
-rw-r--r--db/docs/ci_runner_namespaces.yml2
-rw-r--r--db/docs/ci_unit_test_failures.yml2
-rw-r--r--db/docs/deployment_merge_requests.yml2
-rw-r--r--db/docs/description_versions.yml2
-rw-r--r--db/docs/dora_daily_metrics.yml2
-rw-r--r--db/docs/experiment_subjects.yml2
-rw-r--r--db/docs/experiment_users.yml2
-rw-r--r--db/docs/fork_network_members.yml2
-rw-r--r--db/docs/in_product_marketing_emails.yml2
-rw-r--r--db/docs/issue_email_participants.yml2
-rw-r--r--db/docs/issue_metrics.yml2
-rw-r--r--db/docs/issue_user_mentions.yml1
-rw-r--r--db/docs/lfs_objects_projects.yml1
-rw-r--r--db/docs/lists.yml2
-rw-r--r--db/docs/members.yml3
-rw-r--r--db/docs/merge_request_assignees.yml2
-rw-r--r--db/docs/merge_request_metrics.yml3
-rw-r--r--db/docs/merge_request_user_mentions.yml1
-rw-r--r--db/docs/merge_requests_closing_issues.yml2
-rw-r--r--db/docs/namespace_root_storage_statistics.yml2
-rw-r--r--db/docs/namespace_statistics.yml2
-rw-r--r--db/docs/notification_settings.yml2
-rw-r--r--db/docs/project_authorizations.yml2
-rw-r--r--db/docs/project_auto_devops.yml2
-rw-r--r--db/docs/project_repositories.yml2
-rw-r--r--db/docs/project_repository_states.yml2
-rw-r--r--db/docs/project_repository_storage_moves.yml2
-rw-r--r--db/docs/project_settings.yml2
-rw-r--r--db/docs/push_event_payloads.yml2
-rw-r--r--db/docs/push_rules.yml2
-rw-r--r--db/docs/resource_iteration_events.yml2
-rw-r--r--db/docs/resource_label_events.yml3
-rw-r--r--db/docs/resource_state_events.yml3
-rw-r--r--db/docs/timelogs.yml2
-rw-r--r--doc/administration/packages/dependency_proxy.md14
-rw-r--r--doc/development/database/index.md1
-rw-r--r--doc/development/database/migrations_for_multiple_databases.md390
-rw-r--r--doc/development/database/multiple_databases.md57
-rw-r--r--doc/development/migration_style_guide.md26
-rw-r--r--doc/development/single_table_inheritance.md4
-rw-r--r--doc/development/sql.md8
-rw-r--r--doc/user/application_security/dependency_scanning/index.md21
-rw-r--r--doc/user/clusters/agent/install/index.md180
-rw-r--r--doc/user/packages/dependency_proxy/index.md8
-rw-r--r--generator_templates/active_record/migration/migration.rb4
-rw-r--r--generator_templates/post_deployment_migration/post_deployment_migration/migration.rb4
-rw-r--r--haml_lint/linter/documentation_links.rb10
-rw-r--r--lib/api/group_export.rb2
-rw-r--r--lib/gitlab/ci/config.rb17
-rw-r--r--lib/gitlab/ci/variables/builder.rb16
-rw-r--r--lib/gitlab/database/migration.rb8
-rw-r--r--lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb18
-rw-r--r--lib/gitlab/tracking.rb17
-rw-r--r--lib/gitlab/view/presenter/base.rb18
-rw-r--r--locale/gitlab.pot11
-rw-r--r--package.json4
-rw-r--r--qa/qa/support/api.rb13
-rw-r--r--qa/qa/tools/reliable_report.rb124
-rw-r--r--qa/spec/tools/reliable_report_spec.rb75
-rw-r--r--spec/controllers/groups_controller_spec.rb26
-rw-r--r--spec/features/clusters/create_agent_spec.rb2
-rw-r--r--spec/features/groups/import_export/export_file_spec.rb16
-rw-r--r--spec/features/jira_connect/subscriptions_spec.rb2
-rw-r--r--spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap2
-rw-r--r--spec/frontend/clusters_list/components/agent_token_spec.js4
-rw-r--r--spec/frontend/incidents/components/incidents_list_spec.js18
-rw-r--r--spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap10
-rw-r--r--spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js8
-rw-r--r--spec/frontend/pipelines/components/pipelines_filtered_search_spec.js2
-rw-r--r--spec/frontend/pipelines/empty_state/ci_templates_spec.js85
-rw-r--r--spec/frontend/pipelines/empty_state/pipelines_ci_templates_spec.js (renamed from spec/frontend/pipelines/pipelines_ci_templates_spec.js)72
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js2
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js2
-rw-r--r--spec/frontend/runner/components/runner_filtered_search_bar_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js32
-rw-r--r--spec/haml_lint/linter/documentation_links_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/variables/builder_spec.rb111
-rw-r--r--spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb6
-rw-r--r--spec/lib/gitlab/database/migration_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking_spec.rb38
-rw-r--r--spec/lib/gitlab/view/presenter/base_spec.rb34
-rw-r--r--spec/models/ci/pipeline_spec.rb22
-rw-r--r--spec/models/concerns/issuable_spec.rb27
-rw-r--r--spec/presenters/ci/bridge_presenter_spec.rb9
-rw-r--r--spec/requests/api/graphql/mutations/boards/create_spec.rb10
-rw-r--r--spec/requests/api/group_export_spec.rb129
-rw-r--r--spec/requests/import/gitlab_groups_controller_spec.rb14
-rw-r--r--spec/services/alert_management/alerts/update_service_spec.rb44
-rw-r--r--spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb23
-rw-r--r--spec/services/issues/create_service_spec.rb19
-rw-r--r--spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb10
-rw-r--r--spec/support/shared_examples/services/incident_management/escalation_status_shared_examples.rb22
-rw-r--r--yarn.lock20
137 files changed, 1493 insertions, 890 deletions
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index d040abfe902..3628013fc9b 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -87,12 +87,6 @@ gemnasium-dependency_scanning:
- apk add git-lfs
rules: !reference [".reports:rules:gemnasium-dependency_scanning", rules]
-bundler-audit-dependency_scanning:
- rules: !reference [".reports:rules:bundler-audit-dependency_scanning", rules]
-
-retire-js-dependency_scanning:
- rules: !reference [".reports:rules:retire-js-dependency_scanning", rules]
-
gemnasium-python-dependency_scanning:
rules: !reference [".reports:rules:gemnasium-python-dependency_scanning", rules]
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index ed60cb9ef17..8824c333f96 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1480,18 +1480,6 @@
when: never
- changes: *dependency-patterns
-.reports:rules:bundler-audit-dependency_scanning:
- rules:
- - if: '$DEPENDENCY_SCANNING_DISABLED || $GITLAB_FEATURES !~ /\bdependency_scanning\b/ || $DS_EXCLUDED_ANALYZERS =~ /bundler-audit/ || $DS_DEFAULT_ANALYZERS !~ /bundler-audit/'
- when: never
- - changes: *bundler-patterns
-
-.reports:rules:retire-js-dependency_scanning:
- rules:
- - if: '$DEPENDENCY_SCANNING_DISABLED || $GITLAB_FEATURES !~ /\bdependency_scanning\b/ || $DS_EXCLUDED_ANALYZERS =~ /retire.js/ || $DS_DEFAULT_ANALYZERS !~ /retire.js/'
- when: never
- - changes: *nodejs-patterns
-
.reports:rules:gemnasium-python-dependency_scanning:
rules:
- if: '$DEPENDENCY_SCANNING_DISABLED || $GITLAB_FEATURES !~ /\bdependency_scanning\b/ || $DS_EXCLUDED_ANALYZERS =~ /gemnasium-python/ || $DS_DEFAULT_ANALYZERS !~ /gemnasium-python/'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 81cd42ad6ea..b3d3729f21a 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-3c00908c2743ef03a2d5b7057bf8f87d5b267a75
+9413ca591ebe30dcb133c86d0ec53f6bc2fc30bb
diff --git a/app/assets/javascripts/clusters_list/clusters_util.js b/app/assets/javascripts/clusters_list/clusters_util.js
index c78c93fe1ba..e7ad2f45c75 100644
--- a/app/assets/javascripts/clusters_list/clusters_util.js
+++ b/app/assets/javascripts/clusters_list/clusters_util.js
@@ -1,10 +1,11 @@
export function generateAgentRegistrationCommand(agentToken, kasAddress) {
- return `docker run --pull=always --rm \\
- registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/cli:stable generate \\
- --agent-token=${agentToken} \\
- --kas-address=${kasAddress} \\
- --agent-version stable \\
- --namespace gitlab-kubernetes-agent | kubectl apply -f -`;
+ return `helm repo add gitlab https://charts.gitlab.io
+helm repo update
+helm upgrade --install gitlab-agent gitlab/gitlab-agent \\
+ --namespace gitlab-agent \\
+ --create-namespace \\
+ --set config.token=${agentToken} \\
+ --set config.kasAddress=${kasAddress}`;
}
export function getAgentConfigPath(clusterAgentName) {
diff --git a/app/assets/javascripts/clusters_list/components/agent_token.vue b/app/assets/javascripts/clusters_list/components/agent_token.vue
index 951cf7926b4..751ad9795dd 100644
--- a/app/assets/javascripts/clusters_list/components/agent_token.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_token.vue
@@ -9,7 +9,7 @@ import { I18N_AGENT_TOKEN } from '../constants';
export default {
i18n: I18N_AGENT_TOKEN,
advancedInstallPath: helpPagePath('user/clusters/agent/install/index', {
- anchor: 'advanced-installation',
+ anchor: 'advanced-installation-method',
}),
components: {
GlAlert,
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index f52bb079d5b..4a168e811aa 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -96,9 +96,9 @@ export const I18N_AGENT_TOKEN = {
),
tokenSubtitle: s__('ClusterAgents|The agent uses the token to connect with GitLab.'),
- basicInstallTitle: s__('ClusterAgents|Recommended installation method'),
+ basicInstallTitle: s__('ClusterAgents|Install using Helm (recommended)'),
basicInstallBody: s__(
- 'ClusterAgents|From a terminal, connect to your cluster and run this command. The token is included.',
+ 'ClusterAgents|From a terminal, connect to your cluster and run this command. The token is included in the command.',
),
advancedInstallTitle: s__('ClusterAgents|Advanced installation methods'),
diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue
index 9e0c31e29e5..bfc5bd823a2 100644
--- a/app/assets/javascripts/incidents/components/incidents_list.vue
+++ b/app/assets/javascripts/incidents/components/incidents_list.vue
@@ -392,19 +392,24 @@ export default {
</template>
<template #cell(title)="{ item }">
- <div :class="{ 'gl-display-flex gl-align-items-center': item.state === 'closed' }">
+ <div
+ :class="{
+ 'gl-display-flex gl-align-items-center gl-max-w-full': item.state === 'closed',
+ }"
+ >
<gl-link
- v-gl-tooltip
- :title="item.title"
data-testid="incident-link"
:href="showIncidentLink(item)"
+ class="gl-min-w-0"
>
- {{ item.title }}
+ <tooltip-on-truncate :title="item.title" class="gl-text-truncate gl-display-block">
+ {{ item.title }}
+ </tooltip-on-truncate>
</gl-link>
<gl-icon
v-if="item.state === 'closed'"
name="issue-close"
- class="gl-mx-1 gl-fill-blue-500 gl-flex-shrink-0"
+ class="gl-ml-2 gl-fill-blue-500 gl-flex-shrink-0"
:size="16"
data-testid="incident-closed"
/>
diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js
index 5d841bb9a87..9b4732b26d2 100644
--- a/app/assets/javascripts/pipeline_editor/constants.js
+++ b/app/assets/javascripts/pipeline_editor/constants.js
@@ -100,7 +100,5 @@ export const I18N = {
subtitle: s__(
"Pipelines|Use a template based on your project's language or framework to get started with GitLab CI/CD.",
),
- description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
- cta: s__('Pipelines|Use template'),
},
};
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
index 0380ba646cc..5a9c85a0f10 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue
@@ -1,7 +1,7 @@
<script>
import { GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
-import PipelinesCiTemplates from './pipelines_ci_templates.vue';
+import PipelinesCiTemplates from './empty_state/pipelines_ci_templates.vue';
export default {
i18n: {
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue
new file mode 100644
index 00000000000..3b312e78d11
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue
@@ -0,0 +1,81 @@
+<script>
+import { GlAvatar, GlButton } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+import { mergeUrlParams } from '~/lib/utils/url_utility';
+import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
+import Tracking from '~/tracking';
+
+export default {
+ components: {
+ GlAvatar,
+ GlButton,
+ },
+ mixins: [Tracking.mixin()],
+ inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
+ data() {
+ const templates = this.suggestedCiTemplates.map(({ name, logo }) => {
+ return {
+ name,
+ logo,
+ link: mergeUrlParams({ template: name }, this.pipelineEditorPath),
+ description: sprintf(this.$options.i18n.description, { name }),
+ };
+ });
+
+ return {
+ templates,
+ };
+ },
+ methods: {
+ trackEvent(template) {
+ this.track('template_clicked', {
+ label: template,
+ });
+ },
+ },
+ i18n: {
+ description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'),
+ cta: s__('Pipelines|Use template'),
+ },
+ AVATAR_SHAPE_OPTION_RECT,
+};
+</script>
+<template>
+ <ul class="gl-list-style-none gl-pl-0">
+ <li v-for="template in templates" :key="template.name">
+ <div
+ class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-pb-3 gl-pt-3"
+ >
+ <div class="gl-display-flex gl-flex-direction-row gl-align-items-center">
+ <gl-avatar
+ :src="template.logo"
+ :size="48"
+ class="gl-mr-5 gl-bg-white dark-mode-override"
+ :shape="$options.AVATAR_SHAPE_OPTION_RECT"
+ :alt="template.name"
+ data-testid="template-logo"
+ />
+ <div class="gl-flex-direction-row">
+ <div class="gl-mb-3">
+ <strong class="gl-text-gray-800" data-testid="template-name">
+ {{ template.name }}
+ </strong>
+ </div>
+ <p class="gl-mb-0 gl-font-sm" data-testid="template-description">
+ {{ template.description }}
+ </p>
+ </div>
+ </div>
+ <gl-button
+ category="primary"
+ variant="confirm"
+ :href="template.link"
+ data-testid="template-link"
+ @click="trackEvent(template.name)"
+ >
+ {{ $options.i18n.cta }}
+ </gl-button>
+ </div>
+ </li>
+ </ul>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue
index 9dc6ae317ea..be46a7f5cec 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue
@@ -1,8 +1,6 @@
<script>
-import { GlAvatar, GlButton, GlCard, GlSprintf, GlIcon, GlLink } from '@gitlab/ui';
+import { GlButton, GlCard, GlSprintf, GlIcon, GlLink } from '@gitlab/ui';
import { mergeUrlParams } from '~/lib/utils/url_utility';
-import { sprintf } from '~/locale';
-import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import {
STARTER_TEMPLATE_NAME,
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
@@ -11,21 +9,22 @@ import {
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
} from '~/pipeline_editor/constants';
+import Tracking from '~/tracking';
import { helpPagePath } from '~/helpers/help_page_helper';
+import { isExperimentVariant } from '~/experimentation/utils';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import ExperimentTracking from '~/experimentation/experiment_tracking';
-import { isExperimentVariant } from '~/experimentation/utils';
-import Tracking from '~/tracking';
+import CiTemplates from './ci_templates.vue';
export default {
components: {
- GlAvatar,
GlButton,
GlCard,
GlSprintf,
GlIcon,
GlLink,
GitlabExperiment,
+ CiTemplates,
},
mixins: [Tracking.mixin()],
STARTER_TEMPLATE_NAME,
@@ -34,8 +33,7 @@ export default {
RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
I18N,
- AVATAR_SHAPE_OPTION_RECT,
- inject: ['pipelineEditorPath', 'suggestedCiTemplates'],
+ inject: ['pipelineEditorPath'],
props: {
ciRunnerSettingsPath: {
type: String,
@@ -49,17 +47,7 @@ export default {
},
},
data() {
- const templates = this.suggestedCiTemplates.map(({ name, logo }) => {
- return {
- name,
- logo,
- link: mergeUrlParams({ template: name }, this.pipelineEditorPath),
- description: sprintf(this.$options.I18N.templates.description, { name }),
- };
- });
-
return {
- templates,
gettingStartedTemplateUrl: mergeUrlParams(
{ template: STARTER_TEMPLATE_NAME },
this.pipelineEditorPath,
@@ -179,43 +167,7 @@ export default {
<h2 class="gl-font-lg gl-text-gray-900">{{ $options.I18N.templates.title }}</h2>
<p class="gl-text-gray-800 gl-mb-6">{{ $options.I18N.templates.subtitle }}</p>
- <ul class="gl-list-style-none gl-pl-0">
- <li v-for="template in templates" :key="template.name">
- <div
- class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-pb-3 gl-pt-3"
- >
- <div class="gl-display-flex gl-flex-direction-row gl-align-items-center">
- <gl-avatar
- :src="template.logo"
- :size="48"
- class="gl-mr-5 gl-bg-white dark-mode-override"
- :shape="$options.AVATAR_SHAPE_OPTION_RECT"
- :alt="template.name"
- data-testid="template-logo"
- />
- <div class="gl-flex-direction-row">
- <div class="gl-mb-3">
- <strong class="gl-text-gray-800" data-testid="template-name">
- {{ template.name }}
- </strong>
- </div>
- <p class="gl-mb-0 gl-font-sm" data-testid="template-description">
- {{ template.description }}
- </p>
- </div>
- </div>
- <gl-button
- category="primary"
- variant="confirm"
- :href="template.link"
- data-testid="template-link"
- @click="trackEvent(template.name)"
- >
- {{ $options.I18N.templates.cta }}
- </gl-button>
- </div>
- </li>
- </ul>
+ <ci-templates />
</template>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index 5cdf7b6a3b2..6a9f62a91c4 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -163,33 +163,6 @@ export default {
return undefined;
},
},
- watch: {
- /**
- * GlFilteredSearch currently doesn't emit any event when
- * tokens are manually removed from search field so we'd
- * never know when user actually clears all the tokens.
- * This watcher listens for updates to `filterValue` on
- * such instances. :(
- */
- filterValue(newValue, oldValue) {
- const [firstVal] = newValue;
- if (
- !this.initialRender &&
- newValue.length === 1 &&
- firstVal.type === 'filtered-search-term' &&
- !firstVal.value.data
- ) {
- const filtersCleared =
- oldValue[0].type !== 'filtered-search-term' || oldValue[0].value.data !== '';
- this.$emit('onFilter', [], filtersCleared);
- }
-
- // Set initial render flag to false
- // as we don't want to emit event
- // on initial load when value is empty already.
- this.initialRender = false;
- },
- },
created() {
if (this.recentSearchesStorageKey) this.setupRecentSearch();
},
@@ -322,6 +295,10 @@ export default {
return tokenOption.title;
},
+ onClear() {
+ const cleared = true;
+ this.$emit('onFilter', [], cleared);
+ },
},
};
</script>
@@ -345,6 +322,7 @@ export default {
:suggestions-list-class="suggestionsListClass"
class="flex-grow-1"
@history-item-selected="handleHistoryItemSelected"
+ @clear="onClear"
@clear-history="handleClearHistory"
@submit="handleFilterSubmit"
>
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index a3b21caa69d..00d516e6779 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -452,9 +452,12 @@ a.gl-badge.badge-warning:active {
.gl-form-input.form-control:disabled,
.gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only {
background-color: #1f1f1f;
- color: #868686;
box-shadow: inset 0 0 0 1px #404040;
+}
+.gl-form-input:disabled,
+.gl-form-input.form-control:disabled {
cursor: not-allowed;
+ color: #868686;
}
.gl-form-input::placeholder,
.gl-form-input.form-control::placeholder {
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index c0a7dbc5deb..271a4dfffc9 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -438,9 +438,12 @@ a.gl-badge.badge-warning:active {
.gl-form-input.form-control:disabled,
.gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only {
background-color: #fafafa;
- color: #868686;
box-shadow: inset 0 0 0 1px #dbdbdb;
+}
+.gl-form-input:disabled,
+.gl-form-input.form-control:disabled {
cursor: not-allowed;
+ color: #868686;
}
.gl-form-input::placeholder,
.gl-form-input.form-control::placeholder {
diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss
index 00913fa1fdc..751ad26ca21 100644
--- a/app/assets/stylesheets/startup/startup-signin.scss
+++ b/app/assets/stylesheets/startup/startup-signin.scss
@@ -301,9 +301,12 @@ fieldset:disabled a.btn {
.gl-form-input.form-control:disabled,
.gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only {
background-color: #fafafa;
- color: #868686;
box-shadow: inset 0 0 0 1px #dbdbdb;
+}
+.gl-form-input:disabled,
+.gl-form-input.form-control:disabled {
cursor: not-allowed;
+ color: #868686;
}
.gl-form-input::placeholder,
.gl-form-input.form-control::placeholder {
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 21868cec313..939c0ef220c 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -184,7 +184,7 @@ class Clusters::ClustersController < Clusters::BaseController
def cluster_list
return [] unless certificate_based_clusters_enabled?
- finder = ClusterAncestorsFinder.new(clusterable.subject, current_user)
+ finder = ClusterAncestorsFinder.new(clusterable.__subject__, current_user)
clusters = finder.execute
@has_ancestor_clusters = finder.has_ancestor_clusters?
@@ -253,7 +253,7 @@ class Clusters::ClustersController < Clusters::BaseController
]).merge(
provider_type: :gcp,
platform_type: :kubernetes,
- clusterable: clusterable.subject
+ clusterable: clusterable.__subject__
)
end
@@ -274,7 +274,7 @@ class Clusters::ClustersController < Clusters::BaseController
]).merge(
provider_type: :aws,
platform_type: :kubernetes,
- clusterable: clusterable.subject
+ clusterable: clusterable.__subject__
)
end
@@ -291,7 +291,7 @@ class Clusters::ClustersController < Clusters::BaseController
]).merge(
provider_type: :user,
platform_type: :kubernetes,
- clusterable: clusterable.subject
+ clusterable: clusterable.__subject__
)
end
@@ -313,7 +313,7 @@ class Clusters::ClustersController < Clusters::BaseController
end
def gcp_cluster
- cluster = Clusters::BuildService.new(clusterable.subject).execute
+ cluster = Clusters::BuildService.new(clusterable.__subject__).execute
cluster.build_provider_gcp
@gcp_cluster = cluster.present(current_user: current_user)
end
@@ -343,7 +343,7 @@ class Clusters::ClustersController < Clusters::BaseController
end
def user_cluster
- cluster = Clusters::BuildService.new(clusterable.subject).execute
+ cluster = Clusters::BuildService.new(clusterable.__subject__).execute
cluster.build_platform_kubernetes
@user_cluster = cluster.present(current_user: current_user)
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index c550a8fd70f..93e56867780 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -15,7 +15,6 @@ class GroupsController < Groups::ApplicationController
prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) }
- prepend_before_action :ensure_export_enabled, only: [:export, :download_export]
prepend_before_action :check_captcha, only: :create, if: -> { captcha_enabled? }
before_action :authenticate_user!, only: [:new, :create]
@@ -339,10 +338,6 @@ class GroupsController < Groups::ApplicationController
check_rate_limit!(prefixed_action, scope: [current_user, scope].compact)
end
- def ensure_export_enabled
- render_404 unless Feature.enabled?(:group_import_export, @group, default_enabled: true)
- end
-
private
def load_recaptcha
diff --git a/app/controllers/import/gitlab_groups_controller.rb b/app/controllers/import/gitlab_groups_controller.rb
index aca71f6d57a..c9d5e9986dc 100644
--- a/app/controllers/import/gitlab_groups_controller.rb
+++ b/app/controllers/import/gitlab_groups_controller.rb
@@ -3,7 +3,6 @@
class Import::GitlabGroupsController < ApplicationController
include WorkhorseAuthorization
- before_action :ensure_group_import_enabled
before_action :check_import_rate_limit!, only: %i[create]
feature_category :importers
@@ -51,10 +50,6 @@ class Import::GitlabGroupsController < ApplicationController
end
end
- def ensure_group_import_enabled
- render_404 unless Feature.enabled?(:group_import_export, @group, default_enabled: true)
- end
-
def check_import_rate_limit!
check_rate_limit!(:group_import, scope: current_user) do
redirect_to new_group_path, alert: _('This endpoint has been requested too many times. Try again later.')
diff --git a/app/controllers/jira_connect/subscriptions_controller.rb b/app/controllers/jira_connect/subscriptions_controller.rb
index ec6ba07a125..d8ce67d6267 100644
--- a/app/controllers/jira_connect/subscriptions_controller.rb
+++ b/app/controllers/jira_connect/subscriptions_controller.rb
@@ -11,7 +11,9 @@ class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController
style_src_values = Array.wrap(p.directives['style-src']) | %w('self' 'unsafe-inline')
# rubocop: enable Lint/PercentStringArray
- p.frame_ancestors :self, 'https://*.atlassian.net'
+ # *.jira.com is needed for some legacy Jira Cloud instances, new ones will use *.atlassian.net
+ # https://support.atlassian.com/organization-administration/docs/ip-addresses-and-domains-for-atlassian-cloud-products/
+ p.frame_ancestors :self, 'https://*.atlassian.net', 'https://*.jira.com'
p.script_src(*script_src_values)
p.style_src(*style_src_values)
end
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 20ed089d159..dbded8f60a0 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -142,7 +142,7 @@ module Resolvers
def object
super.tap do |obj|
# If the field this resolver is used in is wrapped in a presenter, unwrap its subject
- break obj.subject if obj.is_a?(Gitlab::View::Presenter::Base)
+ break obj.__subject__ if obj.is_a?(Gitlab::View::Presenter::Base)
end
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 92855c444b4..2d93813d5ee 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -185,7 +185,7 @@ module MergeRequestsHelper
endpoint_metadata: @endpoint_metadata_url,
endpoint_batch: diffs_batch_project_json_merge_request_path(project, merge_request, 'json', params),
endpoint_coverage: @coverage_path,
- help_page_path: help_page_path('user/discussions/index.md', anchor: 'suggest-changes'),
+ help_page_path: help_page_path('user/project/merge_requests/reviews/suggestions.md'),
current_user_data: @current_user_data,
update_current_user_path: @update_current_user_path,
project_path: project_path(merge_request.project),
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 0703c6d1df3..11814d98b5f 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -838,6 +838,8 @@ module Ci
def predefined_commit_variables
strong_memoize(:predefined_commit_variables) do
Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ next variables unless sha.present?
+
variables.append(key: 'CI_COMMIT_SHA', value: sha)
variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha)
variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index f8ac180d3d7..729170da242 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -200,18 +200,6 @@ module Issuable
incident?
end
- # When :incident_escalations feature flag is disabled, new
- # incidents should not have a status record unless the incident
- # is associated with the alert. However, escalation attributes
- # are synced with Alert/IssuableEscalationStatus, so we want to
- # ensure that parity is kept prior to rollout.
- # Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/345769.
- def sync_escalation_attributes_from_alert?
- incident? &&
- ::Feature.disabled?(:incident_escalations, project) &&
- alert_management_alert.present?
- end
-
def incident?
is_a?(Issue) && super
end
diff --git a/app/presenters/README.md b/app/presenters/README.md
index 31e5c971a88..e2461580107 100644
--- a/app/presenters/README.md
+++ b/app/presenters/README.md
@@ -68,9 +68,11 @@ we gain the following benefits:
If you need a presenter class that has only necessary interfaces for the view-related context,
inherit from `Gitlab::View::Presenter::Simple`.
-It provides a `.presents` the method which allows you to define an accessor for the
-presented object. It also includes common helpers like `Gitlab::Routing` and
-`Gitlab::Allowable`.
+
+It provides a `.presents` class method which allows you to define the class the presenter is wrapping,
+and specify an accessor for the presented object using the `as:` keyword.
+
+It also includes common helpers like `Gitlab::Routing` and `Gitlab::Allowable`.
```ruby
class LabelPresenter < Gitlab::View::Presenter::Simple
diff --git a/app/presenters/ci/bridge_presenter.rb b/app/presenters/ci/bridge_presenter.rb
index a62d7cdbbd4..ded3844ac99 100644
--- a/app/presenters/ci/bridge_presenter.rb
+++ b/app/presenters/ci/bridge_presenter.rb
@@ -2,11 +2,11 @@
module Ci
class BridgePresenter < ProcessablePresenter
- presents ::Ci::Bridge
+ presents ::Ci::Bridge, as: :bridge
delegator_override :detailed_status
def detailed_status
- @detailed_status ||= subject.detailed_status(user)
+ @detailed_status ||= bridge.detailed_status(user)
end
end
end
diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb
index 65e1c80085f..0be684901d5 100644
--- a/app/presenters/ci/build_presenter.rb
+++ b/app/presenters/ci/build_presenter.rb
@@ -2,7 +2,7 @@
module Ci
class BuildPresenter < ProcessablePresenter
- presents ::Ci::Build
+ presents ::Ci::Build, as: :build
def erased_by_user?
# Build can be erased through API, therefore it does not have
@@ -34,7 +34,7 @@ module Ci
end
def tooltip_message
- "#{subject.name} - #{detailed_status.status_tooltip}"
+ "#{build.name} - #{detailed_status.status_tooltip}"
end
def execute_in
@@ -48,7 +48,7 @@ module Ci
end
def detailed_status
- @detailed_status ||= subject.detailed_status(user)
+ @detailed_status ||= build.detailed_status(user)
end
end
end
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index 250715d7c9c..fdfcc896bf8 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -39,7 +39,7 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
private_constant :CALLOUT_FAILURE_MESSAGES
- presents ::CommitStatus, as: :build
+ presents ::CommitStatus
def self.callout_failure_messages
CALLOUT_FAILURE_MESSAGES
diff --git a/app/presenters/dev_ops_report/metric_presenter.rb b/app/presenters/dev_ops_report/metric_presenter.rb
index 55326f8f678..ec85c5d3809 100644
--- a/app/presenters/dev_ops_report/metric_presenter.rb
+++ b/app/presenters/dev_ops_report/metric_presenter.rb
@@ -2,28 +2,28 @@
module DevOpsReport
class MetricPresenter < Gitlab::View::Presenter::Simple
- presents ::DevOpsReport::Metric
+ presents ::DevOpsReport::Metric, as: :metric
- delegate :created_at, to: :subject
+ delegate :created_at, to: :metric
def cards
[
Card.new(
- metric: subject,
+ metric: metric,
title: 'Issues',
description: 'created per active user',
feature: 'issues',
blog: 'https://www2.deloitte.com/content/dam/Deloitte/se/Documents/technology-media-telecommunications/deloitte-digital-collaboration.pdf'
),
Card.new(
- metric: subject,
+ metric: metric,
title: 'Comments',
description: 'created per active user',
feature: 'notes',
blog: 'http://conversationaldevelopment.com/why/'
),
Card.new(
- metric: subject,
+ metric: metric,
title: 'Milestones',
description: 'created per active user',
feature: 'milestones',
@@ -31,7 +31,7 @@ module DevOpsReport
docs: help_page_path('user/project/milestones/index')
),
Card.new(
- metric: subject,
+ metric: metric,
title: 'Boards',
description: 'created per active user',
feature: 'boards',
@@ -39,7 +39,7 @@ module DevOpsReport
docs: help_page_path('user/project/issue_board')
),
Card.new(
- metric: subject,
+ metric: metric,
title: 'Merge requests',
description: 'per active user',
feature: 'merge_requests',
@@ -47,7 +47,7 @@ module DevOpsReport
docs: help_page_path('user/project/merge_requests/index')
),
Card.new(
- metric: subject,
+ metric: metric,
title: 'Pipelines',
description: 'created per active user',
feature: 'ci_pipelines',
@@ -55,7 +55,7 @@ module DevOpsReport
docs: help_page_path('ci/index')
),
Card.new(
- metric: subject,
+ metric: metric,
title: 'Environments',
description: 'created per active user',
feature: 'environments',
@@ -63,14 +63,14 @@ module DevOpsReport
docs: help_page_path('ci/environments')
),
Card.new(
- metric: subject,
+ metric: metric,
title: 'Deployments',
description: 'created per active user',
feature: 'deployments',
blog: 'https://puppet.com/blog/continuous-delivery-vs-continuous-deployment-what-s-diff'
),
Card.new(
- metric: subject,
+ metric: metric,
title: 'Monitoring',
description: 'fraction of all projects',
feature: 'projects_prometheus_active',
@@ -78,7 +78,7 @@ module DevOpsReport
docs: help_page_path('user/project/integrations/prometheus')
),
Card.new(
- metric: subject,
+ metric: metric,
title: 'Service Desk',
description: 'issues created per active user',
feature: 'service_desk_issues',
@@ -91,52 +91,52 @@ module DevOpsReport
def idea_to_production_steps
[
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Idea',
features: %w(issues)
),
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Issue',
features: %w(issues notes)
),
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Plan',
features: %w(milestones boards)
),
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Code',
features: %w(merge_requests)
),
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Commit',
features: %w(merge_requests)
),
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Test',
features: %w(ci_pipelines)
),
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Review',
features: %w(ci_pipelines environments)
),
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Staging',
features: %w(environments deployments)
),
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Production',
features: %w(deployments)
),
IdeaToProductionStep.new(
- metric: subject,
+ metric: metric,
title: 'Feedback',
features: %w(projects_prometheus_active service_desk_issues)
)
diff --git a/app/presenters/event_presenter.rb b/app/presenters/event_presenter.rb
index 4c787b88e20..7fa87d33c0d 100644
--- a/app/presenters/event_presenter.rb
+++ b/app/presenters/event_presenter.rb
@@ -3,7 +3,7 @@
class EventPresenter < Gitlab::View::Presenter::Delegated
presents ::Event, as: :event
- def initialize(subject, **attributes)
+ def initialize(event, **attributes)
super
@visible_to_user_cache = ActiveSupport::Cache::MemoryStore.new
diff --git a/app/presenters/gitlab/blame_presenter.rb b/app/presenters/gitlab/blame_presenter.rb
index 2244bcc1545..2bd1a82b02d 100644
--- a/app/presenters/gitlab/blame_presenter.rb
+++ b/app/presenters/gitlab/blame_presenter.rb
@@ -21,7 +21,7 @@ module Gitlab
:project_blame_link,
:time_ago_tooltip)
- def initialize(subject, **attributes)
+ def initialize(blame, **attributes)
super
@commits = {}
diff --git a/app/presenters/label_presenter.rb b/app/presenters/label_presenter.rb
index 6929bf79fdf..e60cdf4088c 100644
--- a/app/presenters/label_presenter.rb
+++ b/app/presenters/label_presenter.rb
@@ -4,8 +4,6 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated
presents ::Label, as: :label
delegate :name, :full_name, to: :label_subject, prefix: :subject, allow_nil: true
- delegator_override :subject # TODO: Fix `Gitlab::View::Presenter::Delegated#subject` not to override `Label#subject`.
-
def edit_path
case label
when GroupLabel then edit_group_label_path(label.group, label)
diff --git a/app/presenters/pages_domain_presenter.rb b/app/presenters/pages_domain_presenter.rb
index 0523f702416..d730608cc27 100644
--- a/app/presenters/pages_domain_presenter.rb
+++ b/app/presenters/pages_domain_presenter.rb
@@ -3,8 +3,6 @@
class PagesDomainPresenter < Gitlab::View::Presenter::Delegated
presents ::PagesDomain, as: :pages_domain
- delegator_override :subject # TODO: Fix `Gitlab::View::Presenter::Delegated#subject` not to override `PagesDomain#subject`.
-
def needs_verification?
Gitlab::CurrentSettings.pages_domain_verification_enabled? && unverified?
end
diff --git a/app/services/alert_management/alerts/update_service.rb b/app/services/alert_management/alerts/update_service.rb
index 7ca0fb4c6fb..0769adc862e 100644
--- a/app/services/alert_management/alerts/update_service.rb
+++ b/app/services/alert_management/alerts/update_service.rb
@@ -151,25 +151,14 @@ module AlertManagement
status_change_reason: " by changing the status of #{alert.to_reference(project)}"
}
}
- ).execute(issue)
- end
-
- def issue
- strong_memoize(:issue) { alert.issue }
+ ).execute(alert.issue)
end
def should_sync_to_incident?
- return false unless sync_available?
-
- issue.escalation_status&.status != alert.status
- end
-
- def sync_available?
- return false unless issue.present?
-
- # Remove sync check with https://gitlab.com/gitlab-org/gitlab/-/issues/345769
- issue.supports_escalation? ||
- issue.sync_escalation_attributes_from_alert?
+ alert.issue &&
+ alert.issue.supports_escalation? &&
+ alert.issue.escalation_status &&
+ alert.issue.escalation_status.status != alert.status
end
def filter_duplicate
diff --git a/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
index d755cf61207..1d0504a6e80 100644
--- a/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
+++ b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
@@ -31,10 +31,7 @@ module IncidentManagement
attr_reader :issuable, :param_errors
def available?
- (
- issuable.supports_escalation? ||
- issuable.sync_escalation_attributes_from_alert? # Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/345769
- ) && user_has_permissions?
+ issuable.supports_escalation? && user_has_permissions?
end
def user_has_permissions?
@@ -63,14 +60,6 @@ module IncidentManagement
status = params.delete(:status)
return unless status
- # If we're updating the escalation status because the
- # alert was updated & the feature flag is disabled, then
- # we should not allow the status to be different from the alert's.
- # Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/345769
- if issuable.sync_escalation_attributes_from_alert? && status != issuable.alert_management_alert.status_name
- add_param_error(:status) && return
- end
-
status_event = escalation_status.status_event_for(status)
add_param_error(:status) && return unless status_event
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index e7d7b3a70eb..7ab663718db 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -87,10 +87,7 @@ module Issues
attr_reader :spam_params
def create_escalation_status(issue)
- # Remove sync check with https://gitlab.com/gitlab-org/gitlab/-/issues/345769
- return unless issue.supports_escalation? || issue.sync_escalation_attributes_from_alert?
-
- ::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute
+ ::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute if issue.supports_escalation?
end
def user_agent_detail_service
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 048606e2db4..5c0487db0fc 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -12,7 +12,7 @@
= link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
- if @milestones.blank?
- = render 'shared/empty_states/milestones_tab', learn_more_path: help_page_path('user/group/milestones') do
+ = render 'shared/empty_states/milestones_tab', learn_more_path: help_page_path('user/project/milestones/index') do
- if can?(current_user, :admin_milestone, @group)
.text-center
= link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
@@ -26,7 +26,7 @@
= render 'milestone', milestone: milestone
= paginate @milestones, theme: "gitlab"
- else
- = render 'shared/empty_states/milestones', learn_more_path: help_page_path('user/group/milestones') do
+ = render 'shared/empty_states/milestones', learn_more_path: help_page_path('user/project/milestones/index') do
- if can?(current_user, :admin_milestone, @group)
.text-center
= link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
diff --git a/app/views/groups/settings/_export.html.haml b/app/views/groups/settings/_export.html.haml
index acb349e82b7..62bc574231f 100644
--- a/app/views/groups/settings/_export.html.haml
+++ b/app/views/groups/settings/_export.html.haml
@@ -1,5 +1,3 @@
-- return unless Feature.enabled?(:group_import_export, @group, default_enabled: true)
-
- group = local_assigns.fetch(:group)
.sub-section
diff --git a/config/feature_flags/development/group_import_export.yml b/config/feature_flags/development/ci_variables_builder_config_variables.yml
index 0eb01340bef..eb74993f90f 100644
--- a/config/feature_flags/development/group_import_export.yml
+++ b/config/feature_flags/development/ci_variables_builder_config_variables.yml
@@ -1,8 +1,8 @@
---
-name: group_import_export
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22423
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/282245
-milestone: '12.8'
+name: ci_variables_builder_config_variables
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79935
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/358791
+milestone: '14.10'
type: development
-group: group::import
-default_enabled: true
+group: group::pipeline execution
+default_enabled: false
diff --git a/config/feature_flags/development/vulnerability_reads_table.yml b/config/feature_flags/development/vulnerability_reads_table.yml
index 68e6ffead14..0269c5330ae 100644
--- a/config/feature_flags/development/vulnerability_reads_table.yml
+++ b/config/feature_flags/development/vulnerability_reads_table.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348151
milestone: '14.9'
type: development
group: group::threat insights
-default_enabled: false
+default_enabled: true
diff --git a/db/docs/approval_merge_request_rules_approved_approvers.yml b/db/docs/approval_merge_request_rules_approved_approvers.yml
index 53f1c8fbbab..a5d99aac598 100644
--- a/db/docs/approval_merge_request_rules_approved_approvers.yml
+++ b/db/docs/approval_merge_request_rules_approved_approvers.yml
@@ -2,5 +2,5 @@
table_name: approval_merge_request_rules_approved_approvers
classes: []
feature_categories:
-- source_code_management
+- code_review
description:
diff --git a/db/docs/boards.yml b/db/docs/boards.yml
index 7317e09b138..2994ea1b38f 100644
--- a/db/docs/boards.yml
+++ b/db/docs/boards.yml
@@ -3,5 +3,5 @@ table_name: boards
classes:
- Board
feature_categories:
-- team_planning
+- design_management
description:
diff --git a/db/docs/ci_build_report_results.yml b/db/docs/ci_build_report_results.yml
index 07168b4eb88..bd89755eb71 100644
--- a/db/docs/ci_build_report_results.yml
+++ b/db/docs/ci_build_report_results.yml
@@ -3,5 +3,5 @@ table_name: ci_build_report_results
classes:
- Ci::BuildReportResult
feature_categories:
-- continuous_integration
+- code_testing
description:
diff --git a/db/docs/ci_pipelines.yml b/db/docs/ci_pipelines.yml
index f0840009b47..ddd8becbde6 100644
--- a/db/docs/ci_pipelines.yml
+++ b/db/docs/ci_pipelines.yml
@@ -3,5 +3,5 @@ table_name: ci_pipelines
classes:
- Ci::Pipeline
feature_categories:
-- code_review
+- continuous_integration
description:
diff --git a/db/docs/ci_pipelines_config.yml b/db/docs/ci_pipelines_config.yml
index 55be432db4a..974d07eb1c8 100644
--- a/db/docs/ci_pipelines_config.yml
+++ b/db/docs/ci_pipelines_config.yml
@@ -3,5 +3,5 @@ table_name: ci_pipelines_config
classes:
- Ci::PipelineConfig
feature_categories:
-- pipeline_authoring
+- continuous_integration
description:
diff --git a/db/docs/ci_runner_namespaces.yml b/db/docs/ci_runner_namespaces.yml
index a8292bead80..9b208e7e1d9 100644
--- a/db/docs/ci_runner_namespaces.yml
+++ b/db/docs/ci_runner_namespaces.yml
@@ -3,5 +3,5 @@ table_name: ci_runner_namespaces
classes:
- Ci::RunnerNamespace
feature_categories:
-- continuous_integration
+- runner
description:
diff --git a/db/docs/ci_unit_test_failures.yml b/db/docs/ci_unit_test_failures.yml
index 7b4c6f01f7c..5d1fd1d76e4 100644
--- a/db/docs/ci_unit_test_failures.yml
+++ b/db/docs/ci_unit_test_failures.yml
@@ -3,5 +3,5 @@ table_name: ci_unit_test_failures
classes:
- Ci::UnitTestFailure
feature_categories:
-- continuous_integration
+- code_testing
description:
diff --git a/db/docs/deployment_merge_requests.yml b/db/docs/deployment_merge_requests.yml
index abb957ab155..a98349817b2 100644
--- a/db/docs/deployment_merge_requests.yml
+++ b/db/docs/deployment_merge_requests.yml
@@ -3,5 +3,5 @@ table_name: deployment_merge_requests
classes:
- DeploymentMergeRequest
feature_categories:
-- deployment_management
+- advanced_deployments
description:
diff --git a/db/docs/description_versions.yml b/db/docs/description_versions.yml
index ad8b39a2f29..2248981f842 100644
--- a/db/docs/description_versions.yml
+++ b/db/docs/description_versions.yml
@@ -3,5 +3,5 @@ table_name: description_versions
classes:
- DescriptionVersion
feature_categories:
-- team_planning
+- design_management
description:
diff --git a/db/docs/dora_daily_metrics.yml b/db/docs/dora_daily_metrics.yml
index 48ebb867eca..dfd04e704a0 100644
--- a/db/docs/dora_daily_metrics.yml
+++ b/db/docs/dora_daily_metrics.yml
@@ -3,5 +3,5 @@ table_name: dora_daily_metrics
classes:
- Dora::DailyMetrics
feature_categories:
-- continuous_delivery
+- value_stream_management
description:
diff --git a/db/docs/experiment_subjects.yml b/db/docs/experiment_subjects.yml
index e642e755c95..676641225ad 100644
--- a/db/docs/experiment_subjects.yml
+++ b/db/docs/experiment_subjects.yml
@@ -3,5 +3,5 @@ table_name: experiment_subjects
classes:
- ExperimentSubject
feature_categories:
-- experimentation_expansion
+- experimentation_conversion
description:
diff --git a/db/docs/experiment_users.yml b/db/docs/experiment_users.yml
index babe9fd2c11..3a296be6311 100644
--- a/db/docs/experiment_users.yml
+++ b/db/docs/experiment_users.yml
@@ -3,5 +3,5 @@ table_name: experiment_users
classes:
- ExperimentUser
feature_categories:
-- experimentation_expansion
+- experimentation_conversion
description:
diff --git a/db/docs/fork_network_members.yml b/db/docs/fork_network_members.yml
index b80b9498a74..c28d46ceb40 100644
--- a/db/docs/fork_network_members.yml
+++ b/db/docs/fork_network_members.yml
@@ -3,5 +3,5 @@ table_name: fork_network_members
classes:
- ForkNetworkMember
feature_categories:
-- devops_reports
+- source_code_management
description:
diff --git a/db/docs/in_product_marketing_emails.yml b/db/docs/in_product_marketing_emails.yml
index 3fdb657a768..d306062c9e5 100644
--- a/db/docs/in_product_marketing_emails.yml
+++ b/db/docs/in_product_marketing_emails.yml
@@ -3,5 +3,5 @@ table_name: in_product_marketing_emails
classes:
- Users::InProductMarketingEmail
feature_categories:
-- navigation
+- experimentation_activation
description:
diff --git a/db/docs/issue_email_participants.yml b/db/docs/issue_email_participants.yml
index eb11cf13d99..ebdf9d572d2 100644
--- a/db/docs/issue_email_participants.yml
+++ b/db/docs/issue_email_participants.yml
@@ -3,5 +3,5 @@ table_name: issue_email_participants
classes:
- IssueEmailParticipant
feature_categories:
-- users
+- service_desk
description:
diff --git a/db/docs/issue_metrics.yml b/db/docs/issue_metrics.yml
index d632c2a174f..b9f93b991bf 100644
--- a/db/docs/issue_metrics.yml
+++ b/db/docs/issue_metrics.yml
@@ -3,5 +3,5 @@ table_name: issue_metrics
classes:
- Issue::Metrics
feature_categories:
-- source_code_management
+- value_stream_management
description:
diff --git a/db/docs/issue_user_mentions.yml b/db/docs/issue_user_mentions.yml
index f4d1e199338..8a4d87b49d1 100644
--- a/db/docs/issue_user_mentions.yml
+++ b/db/docs/issue_user_mentions.yml
@@ -3,6 +3,5 @@ table_name: issue_user_mentions
classes:
- IssueUserMention
feature_categories:
-- users
- team_planning
description:
diff --git a/db/docs/lfs_objects_projects.yml b/db/docs/lfs_objects_projects.yml
index 08c65fb539d..536440301a5 100644
--- a/db/docs/lfs_objects_projects.yml
+++ b/db/docs/lfs_objects_projects.yml
@@ -4,4 +4,5 @@ classes:
- LfsObjectsProject
feature_categories:
- git_lfs
+- source_code_management
description:
diff --git a/db/docs/lists.yml b/db/docs/lists.yml
index d0ec6f2c337..7a20a2b0745 100644
--- a/db/docs/lists.yml
+++ b/db/docs/lists.yml
@@ -3,5 +3,5 @@ table_name: lists
classes:
- List
feature_categories:
-- team_planning
+- design_management
description:
diff --git a/db/docs/members.yml b/db/docs/members.yml
index b6b35593f53..8f3aa6c13f6 100644
--- a/db/docs/members.yml
+++ b/db/docs/members.yml
@@ -6,5 +6,6 @@ classes:
- ProjectMember
- ProjectNamespaceMember
feature_categories:
-- authentication_and_authorization
+- projects
+- subgroups
description:
diff --git a/db/docs/merge_request_assignees.yml b/db/docs/merge_request_assignees.yml
index 87d0fd1278a..fd9e42c2fca 100644
--- a/db/docs/merge_request_assignees.yml
+++ b/db/docs/merge_request_assignees.yml
@@ -3,5 +3,5 @@ table_name: merge_request_assignees
classes:
- MergeRequestAssignee
feature_categories:
-- team_planning
+- code_review
description:
diff --git a/db/docs/merge_request_metrics.yml b/db/docs/merge_request_metrics.yml
index 4421c5396f0..4f705bcfc05 100644
--- a/db/docs/merge_request_metrics.yml
+++ b/db/docs/merge_request_metrics.yml
@@ -3,5 +3,6 @@ table_name: merge_request_metrics
classes:
- MergeRequest::Metrics
feature_categories:
-- metrics
+- value_stream_management
+- code_review
description:
diff --git a/db/docs/merge_request_user_mentions.yml b/db/docs/merge_request_user_mentions.yml
index 96830a7c3ba..e5deb6a6c9c 100644
--- a/db/docs/merge_request_user_mentions.yml
+++ b/db/docs/merge_request_user_mentions.yml
@@ -4,5 +4,4 @@ classes:
- MergeRequestUserMention
feature_categories:
- code_review
-- users
description:
diff --git a/db/docs/merge_requests_closing_issues.yml b/db/docs/merge_requests_closing_issues.yml
index a4cac84e514..5855fe41eb9 100644
--- a/db/docs/merge_requests_closing_issues.yml
+++ b/db/docs/merge_requests_closing_issues.yml
@@ -3,5 +3,5 @@ table_name: merge_requests_closing_issues
classes:
- MergeRequestsClosingIssues
feature_categories:
-- team_planning
+- code_review
description:
diff --git a/db/docs/namespace_root_storage_statistics.yml b/db/docs/namespace_root_storage_statistics.yml
index ba6cbb99031..dcce777a59a 100644
--- a/db/docs/namespace_root_storage_statistics.yml
+++ b/db/docs/namespace_root_storage_statistics.yml
@@ -3,5 +3,5 @@ table_name: namespace_root_storage_statistics
classes:
- Namespace::RootStorageStatistics
feature_categories:
-- subgroups
+- utilization
description:
diff --git a/db/docs/namespace_statistics.yml b/db/docs/namespace_statistics.yml
index 1b072bba267..3d7797a6575 100644
--- a/db/docs/namespace_statistics.yml
+++ b/db/docs/namespace_statistics.yml
@@ -3,5 +3,5 @@ table_name: namespace_statistics
classes:
- NamespaceStatistics
feature_categories:
-- source_code_management
+- utilization
description:
diff --git a/db/docs/notification_settings.yml b/db/docs/notification_settings.yml
index aef66f16ce9..3abbbad24bf 100644
--- a/db/docs/notification_settings.yml
+++ b/db/docs/notification_settings.yml
@@ -3,5 +3,5 @@ table_name: notification_settings
classes:
- NotificationSetting
feature_categories:
-- users
+- team_planning
description:
diff --git a/db/docs/project_authorizations.yml b/db/docs/project_authorizations.yml
index d453dc307e5..8ad64f68b41 100644
--- a/db/docs/project_authorizations.yml
+++ b/db/docs/project_authorizations.yml
@@ -3,5 +3,5 @@ table_name: project_authorizations
classes:
- ProjectAuthorization
feature_categories:
-- authentication_and_authorization
+- projects
description:
diff --git a/db/docs/project_auto_devops.yml b/db/docs/project_auto_devops.yml
index 4fc42ece02e..358c4fbc418 100644
--- a/db/docs/project_auto_devops.yml
+++ b/db/docs/project_auto_devops.yml
@@ -3,5 +3,5 @@ table_name: project_auto_devops
classes:
- ProjectAutoDevops
feature_categories:
-- continuous_integration
+- auto_devops
description:
diff --git a/db/docs/project_repositories.yml b/db/docs/project_repositories.yml
index 31aad61e764..ffe13f66ef1 100644
--- a/db/docs/project_repositories.yml
+++ b/db/docs/project_repositories.yml
@@ -3,5 +3,5 @@ table_name: project_repositories
classes:
- ProjectRepository
feature_categories:
-- projects
+- source_code_management
description:
diff --git a/db/docs/project_repository_states.yml b/db/docs/project_repository_states.yml
index f3803455e19..baa7bbf9b6b 100644
--- a/db/docs/project_repository_states.yml
+++ b/db/docs/project_repository_states.yml
@@ -3,5 +3,5 @@ table_name: project_repository_states
classes:
- ProjectRepositoryState
feature_categories:
-- projects
+- source_code_management
description:
diff --git a/db/docs/project_repository_storage_moves.yml b/db/docs/project_repository_storage_moves.yml
index f56a68bfbef..d88887edcdc 100644
--- a/db/docs/project_repository_storage_moves.yml
+++ b/db/docs/project_repository_storage_moves.yml
@@ -3,5 +3,5 @@ table_name: project_repository_storage_moves
classes:
- Projects::RepositoryStorageMove
feature_categories:
-- gitaly
+- source_code_management
description:
diff --git a/db/docs/project_settings.yml b/db/docs/project_settings.yml
index efa6609ba1e..79375000db4 100644
--- a/db/docs/project_settings.yml
+++ b/db/docs/project_settings.yml
@@ -3,5 +3,5 @@ table_name: project_settings
classes:
- ProjectSetting
feature_categories:
-- error_tracking
+- projects
description:
diff --git a/db/docs/push_event_payloads.yml b/db/docs/push_event_payloads.yml
index c4c43846205..1fe5c4533e2 100644
--- a/db/docs/push_event_payloads.yml
+++ b/db/docs/push_event_payloads.yml
@@ -3,5 +3,5 @@ table_name: push_event_payloads
classes:
- PushEventPayload
feature_categories:
-- gitaly
+- source_code_management
description:
diff --git a/db/docs/push_rules.yml b/db/docs/push_rules.yml
index ffaa7880631..d20c96d4756 100644
--- a/db/docs/push_rules.yml
+++ b/db/docs/push_rules.yml
@@ -3,5 +3,5 @@ table_name: push_rules
classes:
- PushRule
feature_categories:
-- source_code_management
+- compliance_management
description:
diff --git a/db/docs/resource_iteration_events.yml b/db/docs/resource_iteration_events.yml
index 031ffa50072..86e1d7ca6f7 100644
--- a/db/docs/resource_iteration_events.yml
+++ b/db/docs/resource_iteration_events.yml
@@ -3,5 +3,5 @@ table_name: resource_iteration_events
classes:
- ResourceIterationEvent
feature_categories:
-- users
+- planning_analytics
description:
diff --git a/db/docs/resource_label_events.yml b/db/docs/resource_label_events.yml
index ccf097be41f..56b1897b316 100644
--- a/db/docs/resource_label_events.yml
+++ b/db/docs/resource_label_events.yml
@@ -3,6 +3,5 @@ table_name: resource_label_events
classes:
- ResourceLabelEvent
feature_categories:
-- projects
-- subgroups
+- planning_analytics
description:
diff --git a/db/docs/resource_state_events.yml b/db/docs/resource_state_events.yml
index 4b486fede11..8a47dfb3bb8 100644
--- a/db/docs/resource_state_events.yml
+++ b/db/docs/resource_state_events.yml
@@ -2,7 +2,6 @@
table_name: resource_state_events
classes:
- ResourceStateEvent
-feature_categories:
-- source_code_management
+feature_categories:
- team_planning
description:
diff --git a/db/docs/timelogs.yml b/db/docs/timelogs.yml
index bdfca681f0f..d4837c269d6 100644
--- a/db/docs/timelogs.yml
+++ b/db/docs/timelogs.yml
@@ -3,5 +3,5 @@ table_name: timelogs
classes:
- Timelog
feature_categories:
-- audit_events
+- team_planning
description:
diff --git a/doc/administration/packages/dependency_proxy.md b/doc/administration/packages/dependency_proxy.md
index b05a0a5f1a2..4c50cfeeb0f 100644
--- a/doc/administration/packages/dependency_proxy.md
+++ b/doc/administration/packages/dependency_proxy.md
@@ -265,6 +265,20 @@ Feature.disable(:dependency_proxy_for_private_groups)
Feature.enable(:dependency_proxy_for_private_groups)
```
+## Changing the JWT expiration
+
+The Dependency Proxy follows the [Docker v2 token authentication flow](https://docs.docker.com/registry/spec/auth/token/),
+issuing the client a JWT to use for the pull requests. The token expiration time is a configurable
+using the application setting `container_registry_token_expire_delay`. It can be changed from the
+rails console:
+
+```ruby
+# update the JWT expiration to 30 minutes
+ApplicationSetting.update(container_registry_token_expire_delay: 30)
+```
+
+The default expiration and the expiration on GitLab.com is 15 minutes.
+
## Using the dependency proxy behind a proxy
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines:
diff --git a/doc/development/database/index.md b/doc/development/database/index.md
index 1dc40e62840..0363d13ed4c 100644
--- a/doc/development/database/index.md
+++ b/doc/development/database/index.md
@@ -23,6 +23,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
## Migrations
+- [Migrations for multiple databases](migrations_for_multiple_databases.md)
- [Avoiding downtime in migrations](avoiding_downtime_in_migrations.md)
- [SQL guidelines](../sql.md) for working with SQL queries
- [Migrations style guide](../migration_style_guide.md) for creating safe SQL migrations
diff --git a/doc/development/database/migrations_for_multiple_databases.md b/doc/development/database/migrations_for_multiple_databases.md
new file mode 100644
index 00000000000..0ec4612e985
--- /dev/null
+++ b/doc/development/database/migrations_for_multiple_databases.md
@@ -0,0 +1,390 @@
+---
+stage: Enablement
+group: Database
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+---
+
+# Migrations for Multiple databases
+
+> Support for describing migration purposes was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73756) in GitLab 14.8.
+
+This document describes how to properly write database migrations
+for [the decomposed GitLab application using multiple databases](https://gitlab.com/groups/gitlab-org/-/epics/6168).
+
+Learn more about general multiple databases support in a [separate document](multiple_databases.md).
+
+WARNING:
+If you experience any issues using `Gitlab::Database::Migration[2.0]`,
+you can temporarily revert back to the previous behavior by changing the version to `Gitlab::Database::Migration[1.0]`.
+Please report any issues with `Gitlab::Database::Migration[2.0]` in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/358430).
+
+The design for multiple databases (except for the Geo database) assumes
+that all decomposed databases have **the same structure** (for example, schema), but **the data is different** in each database. This means that some tables do not contain data on each database.
+
+## Operations
+
+Depending on the used constructs, we can classify migrations to be either:
+
+1. Modifying structure ([DDL - Data Definition Language](https://www.postgresql.org/docs/current/ddl.html)) (for example, `ALTER TABLE`).
+1. Modifying data ([DML - Data Manipulation Language](https://www.postgresql.org/docs/current/dml.html)) (for example, `UPDATE`).
+1. Performing [other queries](https://www.postgresql.org/docs/current/queries.html) (for example, `SELECT`) that are treated as **DML** for the purposes of our migrations.
+
+**The usage of `Gitlab::Database::Migration[2.0]` requires migrations to always be of a single purpose**.
+Migrations cannot mix **DDL** and **DML** changes as the application requires the structure
+(as described by `db/structure.sql`) to be exactly the same across all decomposed databases.
+
+### Data Definition Language (DDL)
+
+The DDL migrations are all migrations that:
+
+1. Create or drop a table (for example, `create_table`).
+1. Add or remove an index (for example, `add_index`, `add_index_concurrently`).
+1. Add or remove a foreign key (for example `add_foreign_key`, `add_foreign_key_concurrently`).
+1. Add or remove a column with or without a default value (for example, `add_column`).
+1. Create or drop trigger functions (for example, `create_trigger_function`).
+1. Attach or detach triggers from tables (for example, `track_record_deletions`, `untrack_record_deletions`).
+1. Prepare or not async indexes (for example, `prepare_async_index`, `unprepare_async_index_by_name`).
+
+As such DDL migrations **CANNOT**:
+
+1. Read or modify data in any form, via SQL statements or ActiveRecord models.
+1. Update column values (for example, `update_column_in_batches`).
+1. Schedule background migrations (for example, `queue_background_migration_jobs_by_range_at_intervals`).
+1. Read the state of feature flags since they are stored in `main:` (a `features` and `feature_gates`).
+1. Read application settings (as settings are stored in `main:`).
+
+As the majority of migrations in the GitLab codebase are of the DDL-type,
+this is also the default mode of operation and requires no further changes
+to the migrations files.
+
+#### Example: perform DDL on all databases
+
+Example migration adding a concurrent index that is treated as change of the structure (DDL)
+that is executed on all configured databases.
+
+```ruby
+class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_on_merge_request_reviewers_user_id_and_state'
+
+ def up
+ add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :merge_request_reviewers, INDEX_NAME
+ end
+end
+```
+
+### Data Manipulation Language (DML)
+
+The DML migrations are all migrations that:
+
+1. Read data via SQL statements (for example, `SELECT * FROM projects WHERE id=1`).
+1. Read data via ActiveRecord models (for example, `User < MigrationRecord`).
+1. Create, update or delete data via ActiveRecord models (for example, `User.create!(...)`).
+1. Create, update or delete data via SQL statements (for example, `DELETE FROM projects WHERE id=1`).
+1. Update columns in batches (for example, `update_column_in_batches(:projects, :archived, true)`).
+1. Schedule background migrations (for example, `queue_background_migration_jobs_by_range_at_intervals`).
+1. Access application settings (for example, `ApplicationSetting.last` if run for `main:` database).
+1. Read and modify feature flags if run for the `main:` database.
+
+The DML migrations **CANNOT**:
+
+1. Make any changes to DDL since this breaks the rule of keeping `structure.sql` coherent across
+ all decomposed databases.
+1. **Read data from another database**.
+
+To indicate the `DML` migration type, a migration must use the `restrict_gitlab_migration gitlab_schema:`
+syntax in a migration class. This marks the given migration as DML and restricts access to it.
+
+#### Example: perform DML only in context of the database containing the given `gitlab_schema`
+
+Example migration updating `archived` column of `projects` that is executed
+only for the database containing `gitlab_main` schema.
+
+```ruby
+class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ update_column_in_batches(:projects, :archived, true) do |table, query|
+ query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
+```
+
+#### Example: usage of `ActiveRecord` classes
+
+A migration using `ActiveRecord` class to perform data manipulation
+must use the `MigrationRecord` class. This class is guaranteed to provide
+a correct connection in a context of a given migration.
+
+Underneath the `MigrationRecord == ActiveRecord::Base`, as once the `db:migrate`
+runs, it switches the active connection of `ActiveRecord::Base.establish_connection :ci`.
+To avoid confusion to using the `ActiveRecord::Base`, `MigrationRecord` is required.
+
+This implies that DML migrations are forbidden to read data from other
+databases. For example, running migration in context of `ci:` and reading feature flags
+from `main:`, as no established connection to another database is present.
+
+```ruby
+class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ class Project < MigrationRecord
+ end
+
+ def up
+ Project.where(archived: false).each_batch of |batch|
+ batch.update_all(archived: true)
+ end
+ end
+
+ def down
+ end
+end
+```
+
+### The special purpose of `gitlab_shared`
+
+As described in [gitlab_schema](multiple_databases.md#the-special-purpose-of-gitlab_shared),
+the `gitlab_shared` tables are allowed to contain data across all databases. This implies
+that such migrations should run across all databases to modify structure (DDL) or modify data (DML).
+
+As such migrations accessing `gitlab_shared` do not need to use `restrict_gitlab_migration gitlab_schema:`,
+migrations without restriction run across all databases and are allowed to modify data on each of them.
+If the `restrict_gitlab_migration gitlab_schema:` is specified, the `DML` migration
+runs only in a context of a database containing the given `gitlab_schema`.
+
+#### Example: run DML `gitlab_shared` migration on all databases
+
+Example migration updating `loose_foreign_keys_deleted_records` table
+that is marked in `lib/gitlab/database/gitlab_schemas.yml` as `gitlab_shared`.
+
+This migration is executed across all configured databases.
+
+```ruby
+class DeleteAllLooseForeignKeyRecords < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ def up
+ execute("DELETE FROM loose_foreign_keys_deleted_records")
+ end
+
+ def down
+ # no-op
+ end
+end
+```
+
+#### Example: run DML `gitlab_shared` only on the database containing the given `gitlab_schema`
+
+Example migration updating `loose_foreign_keys_deleted_records` table
+that is marked in `lib/gitlab/database/gitlab_schemas.yml` as `gitlab_shared`.
+
+This migration since it configures restriction on `gitlab_ci` is executed only
+in context of database containing `gitlab_ci` schema.
+
+```ruby
+class DeleteCiBuildsLooseForeignKeyRecords < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+ def up
+ execute("DELETE FROM loose_foreign_keys_deleted_records WHERE fully_qualified_table_name='ci_builds'")
+ end
+
+ def down
+ # no-op
+ end
+end
+```
+
+### The behavior of skipping migrations
+
+The only migrations that are skipped are the ones performing **DML** changes.
+The **DDL** migrations are **always and unconditionally** executed.
+
+The implemented [solution](https://gitlab.com/gitlab-org/gitlab/-/issues/355014#solution-2-use-database_tasks)
+uses the `database_tasks:` as a way to indicate which additional database configurations
+(in `config/database.yml`) share the same primary database. The database configurations
+marked with `database_tasks: false` are exempt from executing `db:migrate` for those
+database configurations.
+
+If database configurations do not share databases (all do have `database_tasks: true`),
+each migration runs for every database configuration:
+
+1. The DDL migration applies all structure changes on all databases.
+1. The DML migration runs only in the context of a database containing the given `gitlab_schema:`.
+1. If the DML migration is not eligible to run, it is skipped. It's still
+ marked as executed in `schema_migrations`. While running `db:migrate`, the skipped
+ migration outputs `Current migration is skipped since it modifies 'gitlab_ci' which is outside of 'gitlab_main, gitlab_shared`.
+
+To prevent loss of migrations if the `database_tasks: false` is configured, a dedicated
+Rake task is used [`gitlab:db:validate_config`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83118).
+The `gitlab:db:validate_config` validates the correctness of `database_tasks:` by checking database identifiers
+of each underlying database configuration. The ones that share the database are required to have
+the `database_tasks: false` set. `gitlab:db:validate_config` always runs before `db:migrate`.
+
+## Validation
+
+Validation in a nutshell uses [pg_query](https://github.com/pganalyze/pg_query) to analyze
+each query and classify tables with information from [`gitlab_schema.yml`](multiple_databases.md#gitlab-schema).
+The migration is skipped if the specified `gitlab_schema` is outside of a list of schemas
+managed by a given database connection (`Gitlab::Database::gitlab_schemas_for_connection`).
+
+The `Gitlab::Database::Migration[2.0]` includes `Gitlab::Database::MigrationHelpers::RestrictGitlabSchema`
+which extends the `#migrate` method. For the duration of a migration a dedicated query analyzer
+is installed `Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas` that accepts
+a list of allowed schemas as defined by `restrict_gitlab_migration:`. If the executed query
+is outside of allowed schemas, it raises an exception.
+
+## Exceptions
+
+Depending on misuse or lack of `restrict_gitlab_migration` various exceptions can be raised
+as part of the migration run and prevent the migration from being completed.
+
+### Exception 1: migration running in DDL mode does DML select
+
+```ruby
+class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ # Missing:
+ # restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ update_column_in_batches(:projects, :archived, true) do |table, query|
+ query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
+```
+
+```plaintext
+Select/DML queries (SELECT/UPDATE/DELETE) are disallowed in the DDL (structure) mode
+Modifying of 'projects' (gitlab_main) with 'SELECT * FROM projects...
+```
+
+The current migration do not use `restrict_gitlab_migration`. The lack indicates a migration
+running in **DDL** mode, but the executed payload appears to be reading data from `projects`.
+
+**The solution** is to add `restrict_gitlab_migration gitlab_schema: :gitlab_main`.
+
+### Exception 2: migration running in DML mode changes the structure
+
+```ruby
+class AddUserIdAndStateIndexToMergeRequestReviewers < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ # restrict_gitlab_migration if defined indicates DML, it should be removed
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ INDEX_NAME = 'index_on_merge_request_reviewers_user_id_and_state'
+
+ def up
+ add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :merge_request_reviewers, INDEX_NAME
+ end
+end
+```
+
+```plaintext
+DDL queries (structure) are disallowed in the Select/DML (SELECT/UPDATE/DELETE) mode.
+Modifying of 'merge_request_reviewers' with 'CREATE INDEX...
+```
+
+The current migration do use `restrict_gitlab_migration`. The presence indicates **DML** mode,
+but the executed payload appears to be doing structure changes (DDL).
+
+**The solution** is to remove `restrict_gitlab_migration gitlab_schema: :gitlab_main`.
+
+### Exception 3: migration running in DML mode accesses data from a table in another schema
+
+```ruby
+class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ # Since it modifies `projects` it should use `gitlab_main`
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+ def up
+ update_column_in_batches(:projects, :archived, true) do |table, query|
+ query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
+```
+
+```plaintext
+Select/DML queries (SELECT/UPDATE/DELETE) do access 'projects' (gitlab_main) " \
+which is outside of list of allowed schemas: 'gitlab_ci'
+```
+
+The current migration do restrict the migration to `gitlab_ci`, but appears to modify
+data in `gitlab_main`.
+
+**The solution** is to change `restrict_gitlab_migration gitlab_schema: :gitlab_ci`.
+
+### Exception 4: mixing DDL and DML mode
+
+```ruby
+class UpdateProjectsArchivedState < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ # This migration is invalid regardless of specification
+ # as it cannot modify structure and data at the same time
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+ def up
+ add_concurrent_index :merge_request_reviewers, [:user_id, :state], where: 'state = 2', name: 'index_on_merge_request_reviewers'
+ update_column_in_batches(:projects, :archived, true) do |table, query|
+ query.where(table[:archived].eq(false)) # rubocop:disable CodeReuse/ActiveRecord
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
+```
+
+The migrations mixing **DDL** and **DML** depending on ordering of operations raises
+one of the prior exceptions.
+
+## Upcoming changes on multiple database migrations
+
+The `restrict_gitlab_migration` using `gitlab_schema:` is considered as a first iteration
+of this feature for running migrations selectively depending on a context. It is possible
+to add additional restrictions to DML-only migrations (as the structure coherency is likely
+to stay as-is until further notice) to restrict when they run.
+
+A Potential extension is to limit running DML migration only to specific environments:
+
+```ruby
+restrict_gitlab_migration gitlab_schema: :gitlab_main, gitlab_env: :gitlab_com
+```
diff --git a/doc/development/database/multiple_databases.md b/doc/development/database/multiple_databases.md
index b9950cc0e43..bfd91939743 100644
--- a/doc/development/database/multiple_databases.md
+++ b/doc/development/database/multiple_databases.md
@@ -9,6 +9,63 @@ info: To determine the technical writer assigned to the Stage/Group associated w
To scale GitLab, the we are
[decomposing the GitLab application database into multiple databases](https://gitlab.com/groups/gitlab-org/-/epics/6168).
+## GitLab Schema
+
+For properly discovering allowed patterns between different databases
+the GitLab application implements the `lib/gitlab/database/gitlab_schemas.yml` YAML file.
+
+This file provides a virtual classification of tables into a `gitlab_schema`
+which conceptually is similar to [PostgreSQL Schema](https://www.postgresql.org/docs/current/ddl-schemas.html).
+We decided as part of [using database schemas to better isolated CI decomposed features](https://gitlab.com/gitlab-org/gitlab/-/issues/333415)
+that we cannot use PostgreSQL schema due to complex migration procedures. Instead we implemented
+the concept of application-level classification.
+Each table of GitLab needs to have a `gitlab_schema` assigned:
+
+- `gitlab_main`: describes all tables that are being stored in the `main:` database (for example, like `projects`, `users`).
+- `gitlab_ci`: describes all CI tables that are being stored in the `ci:` database (for example, `ci_pipelines`, `ci_builds`).
+- `gitlab_shared`: describe all application tables that contain data across all decomposed databases (for example, `loose_foreign_keys_deleted_records`).
+- `...`: more schemas to be introduced with additional decomposed databases
+
+The usage of schema enforces the base class to be used:
+
+- `ApplicationRecord` for `gitlab_main`
+- `Ci::ApplicationRecord` for `gitlab_ci`
+- `Gitlab::Database::SharedModel` for `gitlab_shared`
+
+### The impact of `gitlab_schema`
+
+The usage of `gitlab_schema` has a significant impact on the application.
+The `gitlab_schema` primary purpose is to introduce a barrier between different data access patterns.
+
+This is used as a primary source of classification for:
+
+- [Discovering cross-joins across tables from different schemas](#removing-joins-between-ci_-and-non-ci_-tables)
+- [Discovering cross-database transactions across tables from different schemas](#removing-cross-database-transactions)
+
+### The special purpose of `gitlab_shared`
+
+`gitlab_shared` is a special case describing tables or views that by design contain data across
+all decomposed databases. This does describe application-defined tables (like `loose_foreign_keys_deleted_records`),
+Rails-defined tables (like `schema_migrations` or `ar_internal_metadata` as well as internal PostgreSQL tables
+(for example, `pg_attribute`).
+
+**Be careful** to use `gitlab_shared` as it requires special handling while accessing data.
+Since `gitlab_shared` shares not only structure but also data, the application needs to be written in a way
+that traverses all data from all databases in sequential manner.
+
+```ruby
+Gitlab::Database::EachDatabase.each_model_connection([MySharedModel]) do |connection, connection_name|
+ MySharedModel.select_all_data...
+end
+```
+
+As such, migrations modifying data of `gitlab_shared` tables are expected to run across
+all decomposed databases.
+
+## Migrations
+
+Read [Migrations for Multiple Databases](migrations_for_multiple_databases.md).
+
## CI/CD Database
> Support for configuring the GitLab Rails application to use a distinct
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 1d010fa6ee6..086e061452b 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -240,7 +240,7 @@ of migration helpers.
In this example, we use version 1.0 of the migration class:
```ruby
-class TestMigration < Gitlab::Database::Migration[1.0]
+class TestMigration < Gitlab::Database::Migration[2.0]
def change
end
end
@@ -253,7 +253,7 @@ version of migration helpers automatically.
Migration helpers and versioning were [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68986)
in GitLab 14.3.
For merge requests targeting previous stable branches, use the old format and still inherit from
-`ActiveRecord::Migration[6.1]` instead of `Gitlab::Database::Migration[1.0]`.
+`ActiveRecord::Migration[6.1]` instead of `Gitlab::Database::Migration[2.0]`.
## Retry mechanism when acquiring database locks
@@ -535,7 +535,7 @@ by calling the method `disable_ddl_transaction!` in the body of your migration
class like so:
```ruby
-class MyMigration < Gitlab::Database::Migration[1.0]
+class MyMigration < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'index_name'
@@ -586,7 +586,7 @@ by calling the method `disable_ddl_transaction!` in the body of your migration
class like so:
```ruby
-class MyMigration < Gitlab::Database::Migration[1.0]
+class MyMigration < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'index_name'
@@ -629,7 +629,7 @@ The easiest way to test for existence of an index by name is to use the
be used with a name option. For example:
```ruby
-class MyMigration < Gitlab::Database::Migration[1.0]
+class MyMigration < Gitlab::Database::Migration[2.0]
INDEX_NAME = 'index_name'
def up
@@ -664,7 +664,7 @@ Here's an example where we add a new column with a foreign key
constraint. Note it includes `index: true` to create an index for it.
```ruby
-class Migration < Gitlab::Database::Migration[1.0]
+class Migration < Gitlab::Database::Migration[2.0]
def change
add_reference :model, :other_model, index: true, foreign_key: { on_delete: :cascade }
@@ -710,7 +710,7 @@ expensive and disruptive operation for larger tables, but in reality it's not.
Take the following migration as an example:
```ruby
-class DefaultRequestAccessGroups < Gitlab::Database::Migration[1.0]
+class DefaultRequestAccessGroups < Gitlab::Database::Migration[2.0]
def change
change_column_default(:namespaces, :request_access_enabled, from: false, to: true)
end
@@ -943,7 +943,7 @@ The Rails 5 natively supports `JSONB` (binary JSON) column type.
Example migration adding this column:
```ruby
-class AddOptionsToBuildMetadata < Gitlab::Database::Migration[1.0]
+class AddOptionsToBuildMetadata < Gitlab::Database::Migration[2.0]
def change
add_column :ci_builds_metadata, :config_options, :jsonb
end
@@ -975,7 +975,7 @@ Do not store `attr_encrypted` attributes as `:text` in the database; use
efficient:
```ruby
-class AddSecretToSomething < Gitlab::Database::Migration[1.0]
+class AddSecretToSomething < Gitlab::Database::Migration[2.0]
def change
add_column :something, :encrypted_secret, :binary
add_column :something, :encrypted_secret_iv, :binary
@@ -1033,8 +1033,8 @@ If you need more complex logic, you can define and use models local to a
migration. For example:
```ruby
-class MyMigration < Gitlab::Database::Migration[1.0]
- class Project < ActiveRecord::Base
+class MyMigration < Gitlab::Database::Migration[2.0]
+ class Project < MigrationRecord
self.table_name = 'projects'
end
@@ -1132,8 +1132,8 @@ in a previous migration.
It is important not to leave out the `User.reset_column_information` command, in order to ensure that the old schema is dropped from the cache and ActiveRecord loads the updated schema information.
```ruby
-class AddAndSeedMyColumn < Gitlab::Database::Migration[1.0]
- class User < ActiveRecord::Base
+class AddAndSeedMyColumn < Gitlab::Database::Migration[2.0]
+ class User < MigrationRecord
self.table_name = 'users'
end
diff --git a/doc/development/single_table_inheritance.md b/doc/development/single_table_inheritance.md
index 4acb9d76d79..0783721e628 100644
--- a/doc/development/single_table_inheritance.md
+++ b/doc/development/single_table_inheritance.md
@@ -31,8 +31,8 @@ could result in loading unexpected code or associations which may cause unintend
side effects or failures during upgrades.
```ruby
-class SomeMigration < Gitlab::Database::Migration[1.0]
- class Services < ActiveRecord::Base
+class SomeMigration < Gitlab::Database::Migration[2.0]
+ class Services < MigrationRecord
self.table_name = 'services'
self.inheritance_column = :_type_disabled
end
diff --git a/doc/development/sql.md b/doc/development/sql.md
index c1f4ad39650..4b6153b7205 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -254,13 +254,13 @@ of records plucked. `MAX_PLUCK` defaults to `1_000` in `ApplicationRecord`.
## Inherit from ApplicationRecord
-Most models in the GitLab codebase should inherit from `ApplicationRecord`,
-rather than from `ActiveRecord::Base`. This allows helper methods to be easily
-added.
+Most models in the GitLab codebase should inherit from `ApplicationRecord`
+or `Ci::ApplicationRecord` rather than from `ActiveRecord::Base`. This allows
+helper methods to be easily added.
An exception to this rule exists for models created in database migrations. As
these should be isolated from application code, they should continue to subclass
-from `ActiveRecord::Base`.
+from `MigrationRecord` which is available only in migration context.
## Use UNIONs
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index d07a1d647e6..8dae6ec8c74 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -1236,3 +1236,24 @@ analyzers, edit your `gitlab-ci.yml` file and either:
For example, currently the `gemnasium-maven-dependency_scanning` job pulls the latest
`gemnasium-maven` Docker image because `DS_ANALYZER_IMAGE` is set to
`"$SECURE_ANALYZERS_PREFIX/gemnasium-maven:$DS_MAJOR_VERSION"`.
+
+### Dependency Scanning of setuptools project fails with `use_2to3 is invalid` error
+
+Support for [2to3](https://docs.python.org/3/library/2to3.html)
+was [removed](https://setuptools.pypa.io/en/latest/history.html#v58-0-0)
+in `setuptools` version `v58.0.0`. Dependency Scanning (running `python 3.9`) uses `setuptools`
+version `58.1.0+`, which doesn't support `2to3`. Therefore, a `setuptools` dependency relying on
+`lib2to3` will fail with this message:
+
+```plaintext
+error in <dependency name> setup command: use_2to3 is invalid
+```
+
+To work around this error, downgrade the analyzer's version of `setuptools` (e.g. `v57.5.0`):
+
+```yaml
+gemnasium-python-dependency_scanning:
+ before_script:
+ - pip install setuptools==57.5.0
+ image: registry.gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python:2-python-3.9
+```
diff --git a/doc/user/clusters/agent/install/index.md b/doc/user/clusters/agent/install/index.md
index b1bec59984e..e76ef9e827d 100644
--- a/doc/user/clusters/agent/install/index.md
+++ b/doc/user/clusters/agent/install/index.md
@@ -63,13 +63,12 @@ To register an agent with GitLab:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259669) in GitLab 13.7, the agent configuration file can be added to multiple directories (or subdirectories) of the repository.
> - Group authorization was [introduced](https://gitlab.com/groups/gitlab-org/-/epics/5784) in GitLab 14.3.
-You can use an agent configuration file to specify details about your implementation.
-Creating a file is optional but is needed if:
+The agent is configured through a configuration file. This file is optional. Without a configuration file, you can still use the CI/CD workflow in the project where the agent is registered.
-- You use [a GitOps workflow](../gitops.md#gitops-configuration-reference) and you want a more advanced configuration.
-- You use a GitLab CI/CD workflow. In that workflow, you must [authorize the agent](../ci_cd_tunnel.md#authorize-the-agent).
+You need a configuration file if:
-If you do not create an agent configuration file, you can use the CI/CD workflow in the project where the agent is registered only.
+- You want to use [a GitOps workflow](../gitops.md#gitops-configuration-reference).
+- You want to authorize a different project to use the agent for a [GitLab CI/CD workflow](../ci_cd_tunnel.md#authorize-the-agent).
To create an agent configuration file, go to the GitLab project. In the repository, create a file called `config.yaml` at this path:
@@ -83,108 +82,62 @@ To create an agent configuration file, go to the GitLab project. In the reposito
- For a GitOps workflow, view [the configuration reference](../gitops.md#gitops-configuration-reference) for details.
- For a GitLab CI/CD workflow, you can leave the file blank for now.
-The agent bootstraps with the GitLab installation URL and an access token,
-and you provide the rest of the configuration in your repository, following
-Infrastructure as Code (IaaC) best practices.
-
### Install the agent in the cluster
-To connect your cluster to GitLab, install the registered agent
-in your cluster. To install it, you can use either:
-
-- [The one-liner installation method](#one-liner-installation).
-- [The advanced installation method](#advanced-installation).
+> Introduced in GitLab 14.10, GitLab recommends using Helm to install the agent.
-You can use the one-liner installation for trying to use the agent for the first time, to do internal setups with
-high trust, and to quickly get started. For long-term production usage, you may want to use the advanced installation
-method to benefit from more configuration options.
+To connect your cluster to GitLab, install the registered agent
+in your cluster. You can either:
-#### One-liner installation
+- [Install the agent with Helm](#install-the-agent-with-helm).
+- Or, follow the [advanced installation method](#advanced-installation-method).
-The one-liner installation is the simplest process, but you need
-Docker installed locally. If you don't have it, you can either install
-it or opt to the [advanced installation method](#advanced-installation).
+If you do not know which one to choose, we recommend starting with Helm.
-Use the one-liner process for simple use cases or to get started with the agent for Kubernetes.
-For production use, opt for the [advanced installation method](#advanced-installation)
-as it gives you more customization options and access to all settings.
+#### Install the agent with Helm
-To install the agent on your cluster using the one-liner installation:
+To install the agent on your cluster using Helm:
+1. [Install Helm](https://helm.sh/docs/intro/install/)
1. In your computer, open a terminal and [connect to your cluster](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/).
-1. Run the command you copied when registering your cluster in the previous step.
+1. Run the command you copied when registering your agent with GitLab.
-Optionally, you can [customize the one-liner installation command](#customize-the-one-liner-installation).
+Optionally, you can [customize the Helm installation](#customize-the-helm-installation).
-##### Customize the one-liner installation
+##### Customize the Helm installation
-By default, the one-liner command generated by GitLab:
+By default, the Helm installation command generated by GitLab:
-- Creates a namespace for the deployment (`gitlab-agent`).
-- Sets up a service account with `cluster-admin` rights (see [how to restrict this service account](#customize-the-permissions-for-the-agentk-service-account)).
-- Creates a `Secret` resource for the agent's access token.
+- Creates a namespace `gitlab-agent` for the deployment (`--namespace gitlab-agent`). You can skip creating the namespace by omitting the `--create-namespace` flag.
+- Sets up a service account for the agent with `cluster-admin` rights. You can:
+ - Skip creating the service account by adding `--set serviceAccount.create=false` to the `helm install` command. In this case, you must set `serviceAccount.name` to a pre-existing service account.
+ - Skip creating the RBAC permissions by adding `--set rbac.create=false` to the `helm install` command. In this case, you must bring your own RBAC permissions for the agent. Otherwise, it has no permissions at all.
+- Creates a `Secret` resource for the agent's access token. To instead bring your own secret with a token, omit the token (`--set token=...`) and instead use `--set config.secretName=<your secret name>`.
- Creates a `Deployment` resource for the `agentk` pod.
-You can edit these parameters to customize the one-liner installation command.
-To view all available options, open a terminal and run this command:
-
-```shell
-docker run --pull=always --rm registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/cli:stable generate --help
-
-Usage:
- cli generate [flags]
-
-Flags:
- --agent-token string Access token registered for agent
- --agent-version string Version of the agentk image to use (default "v14.8.1")
- -h, --help help for generate
- --kas-address string GitLab agent server for Kubernetes address
- --name-prefix string The prefix to use for names of Kubernetes objects
- --namespace string Kubernetes namespace to create resources in (default "gitlab-agent")
- --no-rbac Do not include corresponding Roles and RoleBindings for the agent service account
-```
-
-WARNING:
-Use `--agent-version stable` to refer to the latest stable
-release at the time when the command runs. For production, however,
-you should explicitly specify a matching version.
-
-#### Advanced installation
-
-For advanced installation options, use [the `kpt` installation method](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/tree/master/build/deployment/gitlab-agent).
-
-##### Customize the permissions for the `agentk` service account
-
-You own your cluster and can grant GitLab the permissions you want.
-By default, however, the generated manifests provide `cluster-admin` rights to the agent.
-
-You can restrict the agent's access rights by using Kustomize overlays. [An example is commented out](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/build/deployment/gitlab-agent/cluster/kustomization.yaml) in the `kpt` package you retrieved as part of the installation.
-
-To restrict permissions:
-
-1. Copy the `cluster` directory.
-1. Edit the `kustomization.yaml` and `components/*` files based on your requirements.
-1. Run `kustomize build <your copied directory> | kubectl apply -f -` to apply your configuration.
+To see the full list of customizations available, see the Helm chart's [default values file](https://gitlab.com/gitlab-org/charts/gitlab-agent/-/blob/main/values.yaml).
-#### Update the advanced installation base layer
+#### Advanced installation method
-Now you can update from the upstream package by using `kpt pkg update gitlab-agent --strategy resource-merge`.
-When the advanced installation setup changes, you will not need to change your custom overlays.
+GitLab also provides a [KPT package for the agent](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/tree/master/build/deployment/gitlab-agent). This method provides greater flexibility, but is only recommended for advanced users.
## Install multiple agents in your cluster
-For total separation between teams, you might need to run multiple `agentk` instances in your cluster.
-You might want multiple agents so you can restrict RBAC for every `agentk` deployment.
+To install a second agent in your cluster, you can follow the [previous steps](#register-the-agent-with-gitlab) a second time. To avoid resource name collisions within the cluster, you must either:
-To install multiple agents, follow the
-[advanced installation steps](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/tree/master/build/deployment/gitlab-agent)
-a second time and:
+- Use a different release name for the agent, e.g. `second-gitlab-agent`:
-1. Change the agent name and create a new configuration file.
-1. Register the new agent. You receive a new access token. Each token should be used only with one agent.
-1. Change the namespace or prefix you use for the installation.
+ ```shell
+ helm upgrade --install second-gitlab-agent gitlab/gitlab-agent ...
+ ```
-You should also change the RBAC for the installed `agentk`.
+- Or, install the agent in a different namespace, e.g. `different-namespace`:
+
+ ```shell
+ helm upgrade --install gitlab-agent gitlab/gitlab-agent \
+ --namespace different-namespace \
+ ...
+ ```
## Example projects
@@ -195,50 +148,37 @@ The following example projects can help you get started with the agent.
- [Auto DevOps setup that uses the CI/CD workflow](https://gitlab.com/gitlab-examples/ops/gitops-demo/hello-world-service)
- [Cluster management project template example that uses the CI/CD workflow](https://gitlab.com/gitlab-examples/ops/gitops-demo/cluster-management)
-## Upgrades and version compatibility
-
-The agent has two major components: `agentk` and `kas`.
-GitLab provides `kas` installers built into the various GitLab installation methods.
-The required `kas` version corresponds to the GitLab `major.minor` (X.Y) versions.
-
-At the same time, `agentk` and `kas` can differ by 1 minor version in either direction. For example,
-`agentk` 14.4 supports `kas` 14.3, 14.4, and 14.5 (regardless of the patch).
-
-A feature introduced in a given GitLab minor version might work with other `agentk` or `kas` versions.
-To ensure it works, use at least the same `agentk` and `kas` minor version. For example,
-if your GitLab version is 14.2, use at least `agentk` 14.2 and `kas` 14.2.
+## Updates and version compatibility
-We recommend upgrading your `kas` installations together with GitLab instances' upgrades, and to
-[upgrade the `agentk` installations](#update-the-agent-version) after upgrading GitLab.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340882) in GitLab 14.8, GitLab warns you on the agent's list page to update the agent version installed on your cluster.
-The available `agentk` and `kas` versions are available in
-[the Container Registry](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/container_registry/).
+For the best experience, the version of the agent installed in your cluster should match the GitLab major and minor version. The previous minor version is also supported. For example, if your GitLab version is v14.9.4 (major version 14, minor version 9), then versions v14.9.0 and v14.9.1 of the agent are ideal, but any v14.8.x version of the agent is also supported. See [this page](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/releases) of releases of the GitLab agent.
### Update the agent version
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340882) in GitLab 14.8, GitLab warns you on the agent's list page to update the agent version installed on your cluster.
-
-To update the agent's version, re-run the [installation command](#install-the-agent-in-the-cluster)
-with a newer `--agent-version`. Make sure to specify the other required parameters: `--kas-address`, `--namespace`, and `--agent-token`.
-The available `agentk` versions are in [the Container Registry](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/container_registry/1223205?sort=desc).
+To update the agent to the latest version, you can run:
-If you don't have access to your agent's access token, you can retrieve it from your cluster:
-
-1. Open a terminal and connect to your cluster.
-1. To retrieve the namespace, run:
+```shell
+helm repo update
+helm upgrade --install gitlab-agent gitlab/gitlab-agent \
+ --namespace gitlab-agent \
+ --reuse-values
+```
- ```shell
- kubectl get namespaces
- ```
+To set a specific version, you can override the `image.tag` value. For example, to install version `v14.9.1`, run:
-1. To retrieve the secret, run:
+```shell
+helm upgrade gitlab-agent gitlab/gitlab-agent \
+ --namespace gitlab-agent \
+ --reuse-values \
+ --set image.tag=v14.9.1
+```
- ```shell
- kubectl -n <namespace> get secrets
- ```
+## Uninstall the agent
-1. To retrieve the access token, run:
+If you [installed the agent with Helm](#install-the-agent-with-helm), then you can also uninstall with Helm. For example, if the release and namespace are both called `gitlab-agent`, then you can uninstall the agent using the following command:
- ```shell
- kubectl -n <namespace> get secret <secret-name> --template={{.data.token}} | base64 --decode
- ```
+```shell
+helm uninstall gitlab-agent \
+ --namespace gitlab-agent
+```
diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md
index 735932642a7..5e66c8ed7a5 100644
--- a/doc/user/packages/dependency_proxy/index.md
+++ b/doc/user/packages/dependency_proxy/index.md
@@ -96,6 +96,14 @@ You can authenticate using:
Users accessing the Dependency Proxy with a personal access token or username and password must
have at least the Guest role for the group they pull images from.
+The Dependency Proxy follows the [Docker v2 token authentication flow](https://docs.docker.com/registry/spec/auth/token/),
+issuing the client a JWT to use for the pull requests. The JWT issued as a result of authenticating
+expires after some time. When the token expires, most Docker clients store your credentials and
+automatically request a new token without further action.
+
+The token expiration time is a [configurable setting](../../../administration/packages/dependency_proxy.md#changing-the-jwt-expiration).
+On GitLab.com, the expiration time is 15 minutes.
+
#### SAML SSO
When [SSO enforcement](../../group/saml_sso/index.md#sso-enforcement)
diff --git a/generator_templates/active_record/migration/migration.rb b/generator_templates/active_record/migration/migration.rb
index 103c2fd7ea2..50d2b018ae7 100644
--- a/generator_templates/active_record/migration/migration.rb
+++ b/generator_templates/active_record/migration/migration.rb
@@ -16,6 +16,10 @@ class <%= migration_class_name %> < Gitlab::Database::Migration[<%= Gitlab::Data
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
+ #
+ # Configure the `gitlab_schema` to perform data manipulation (DML).
+ # Visit: https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html
+ # restrict_gitlab_migration gitlab_schema: :gitlab_main
<%- if migration_action == 'add' -%>
def change
diff --git a/generator_templates/post_deployment_migration/post_deployment_migration/migration.rb b/generator_templates/post_deployment_migration/post_deployment_migration/migration.rb
index c456fa29ea8..dcc9d1e4563 100644
--- a/generator_templates/post_deployment_migration/post_deployment_migration/migration.rb
+++ b/generator_templates/post_deployment_migration/post_deployment_migration/migration.rb
@@ -16,6 +16,10 @@ class <%= migration_class_name %> < Gitlab::Database::Migration[<%= Gitlab::Data
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
+ #
+ # Configure the `gitlab_schema` to perform data manipulation (DML).
+ # Visit: https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html
+ # restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
end
diff --git a/haml_lint/linter/documentation_links.rb b/haml_lint/linter/documentation_links.rb
index 8c696b26b13..0cabae40c4b 100644
--- a/haml_lint/linter/documentation_links.rb
+++ b/haml_lint/linter/documentation_links.rb
@@ -68,7 +68,15 @@ module HamlLint
# Sometimes links are provided via data attributes in html tag
return node.parsed_attributes.syntax_tree if node.type == :tag
- node.parsed_script.syntax_tree
+ parse_script(node).syntax_tree
+ end
+
+ def parse_script(node)
+ # It's a workaround for cases for scripts ending with "do"
+ # For some reason they don't parse correctly
+ code = node.script.delete_suffix(' do')
+
+ HamlLint::ParsedRuby.new(HamlLint::RubyParser.new.parse(code))
end
def detect_path_to_file(link)
diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb
index f0c0182a02f..5754eceda97 100644
--- a/lib/api/group_export.rb
+++ b/lib/api/group_export.rb
@@ -3,8 +3,6 @@
module API
class GroupExport < ::API::Base
before do
- not_found! unless Feature.enabled?(:group_import_export, user_group, default_enabled: true)
-
authorize! :admin_group, user_group
end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index a640d42a471..2c9524c89ff 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -6,6 +6,8 @@ module Gitlab
# Base GitLab CI Configuration facade
#
class Config
+ include Gitlab::Utils::StrongMemoize
+
ConfigError = Class.new(StandardError)
TIMEOUT_SECONDS = 30.seconds
TIMEOUT_MESSAGE = 'Resolving config took longer than expected'
@@ -22,6 +24,11 @@ module Gitlab
def initialize(config, project: nil, pipeline: nil, sha: nil, user: nil, parent_pipeline: nil, source: nil, logger: nil)
@logger = logger || ::Gitlab::Ci::Pipeline::Logger.new(project: project)
@source_ref_path = pipeline&.source_ref_path
+ @project = project
+
+ if use_config_variables?
+ pipeline ||= ::Ci::Pipeline.new(project: project, sha: sha, user: user, source: source)
+ end
@context = self.logger.instrument(:config_build_context) do
build_context(project: project, pipeline: pipeline, sha: sha, user: user, parent_pipeline: parent_pipeline)
@@ -155,6 +162,10 @@ module Gitlab
end
def build_variables_without_instrumentation(project:, pipeline:)
+ if use_config_variables?
+ return pipeline.variables_builder.config_variables
+ end
+
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless project
@@ -184,6 +195,12 @@ module Gitlab
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error, @context.sentry_payload)
end
+ def use_config_variables?
+ strong_memoize(:use_config_variables) do
+ ::Feature.enabled?(:ci_variables_builder_config_variables, @project, default_enabled: :yaml)
+ end
+ end
+
# Overridden in EE
def rescue_errors
RESCUE_ERRORS
diff --git a/lib/gitlab/ci/variables/builder.rb b/lib/gitlab/ci/variables/builder.rb
index f16abf6cf49..bcb1fe83ea2 100644
--- a/lib/gitlab/ci/variables/builder.rb
+++ b/lib/gitlab/ci/variables/builder.rb
@@ -10,7 +10,7 @@ module Gitlab
@pipeline = pipeline
@instance_variables_builder = Builder::Instance.new
@project_variables_builder = Builder::Project.new(project)
- @group_variables_builder = Builder::Group.new(project.group)
+ @group_variables_builder = Builder::Group.new(project&.group)
end
def scoped_variables(job, environment:, dependencies:)
@@ -32,6 +32,20 @@ module Gitlab
end
end
+ def config_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ break variables unless project
+
+ variables.concat(project.predefined_variables)
+ variables.concat(pipeline.predefined_variables)
+ variables.concat(secret_instance_variables)
+ variables.concat(secret_group_variables(environment: nil))
+ variables.concat(secret_project_variables(environment: nil))
+ variables.concat(pipeline.variables)
+ variables.concat(pipeline_schedule_variables)
+ end
+ end
+
def kubernetes_variables(environment:, job:)
::Gitlab::Ci::Variables::Collection.new.tap do |collection|
# NOTE: deployment_variables will be removed as part of cleanup for
diff --git a/lib/gitlab/database/migration.rb b/lib/gitlab/database/migration.rb
index d2e17eab614..dc695a74a4b 100644
--- a/lib/gitlab/database/migration.rb
+++ b/lib/gitlab/database/migration.rb
@@ -41,6 +41,12 @@ module Gitlab
class V2_0 < V1_0 # rubocop:disable Naming/ClassAndModuleCamelCase
include Gitlab::Database::MigrationHelpers::RestrictGitlabSchema
+
+ # When running migrations, the `db:migrate` switches connection of
+ # ActiveRecord::Base depending where the migration runs.
+ # This helper class is provided to avoid confusion using `ActiveRecord::Base`
+ class MigrationRecord < ActiveRecord::Base
+ end
end
def self.[](version)
@@ -53,7 +59,7 @@ module Gitlab
# The current version to be used in new migrations
def self.current_version
- 1.0
+ 2.0
end
end
end
diff --git a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
index ab40ba5d59b..3f0176cb654 100644
--- a/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
+++ b/lib/gitlab/database/query_analyzers/restrict_allowed_schemas.rb
@@ -69,8 +69,10 @@ module Gitlab
schemas = self.dml_schemas(tables)
if (schemas - self.allowed_gitlab_schemas).any?
- raise DMLAccessDeniedError, "Select/DML queries (SELECT/UPDATE/DELETE) do access '#{tables}' (#{schemas.to_a}) " \
- "which is outside of list of allowed schemas: '#{self.allowed_gitlab_schemas}'."
+ raise DMLAccessDeniedError, \
+ "Select/DML queries (SELECT/UPDATE/DELETE) do access '#{tables}' (#{schemas.to_a}) " \
+ "which is outside of list of allowed schemas: '#{self.allowed_gitlab_schemas}'. " \
+ "#{documentation_url}"
end
end
@@ -93,11 +95,19 @@ module Gitlab
end
def raise_dml_not_allowed_error(message)
- raise DMLNotAllowedError, "Select/DML queries (SELECT/UPDATE/DELETE) are disallowed in the DDL (structure) mode. #{message}"
+ raise DMLNotAllowedError, \
+ "Select/DML queries (SELECT/UPDATE/DELETE) are disallowed in the DDL (structure) mode. " \
+ "#{message}. #{documentation_url}" \
end
def raise_ddl_not_allowed_error(message)
- raise DDLNotAllowedError, "DDL queries (structure) are disallowed in the Select/DML (SELECT/UPDATE/DELETE) mode. #{message}"
+ raise DDLNotAllowedError, \
+ "DDL queries (structure) are disallowed in the Select/DML (SELECT/UPDATE/DELETE) mode. " \
+ "#{message}. #{documentation_url}"
+ end
+
+ def documentation_url
+ "For more information visit: https://docs.gitlab.com/ee/development/database/migrations_for_multiple_databases.html"
end
end
end
diff --git a/lib/gitlab/tracking.rb b/lib/gitlab/tracking.rb
index a58b4beb0df..0e7812d08b8 100644
--- a/lib/gitlab/tracking.rb
+++ b/lib/gitlab/tracking.rb
@@ -15,6 +15,21 @@ module Gitlab
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error, snowplow_category: category, snowplow_action: action)
end
+ def definition(basename, category: nil, action: nil, label: nil, property: nil, value: nil, context: [], project: nil, user: nil, namespace: nil, **extra) # rubocop:disable Metrics/ParameterLists
+ definition = YAML.load_file(Rails.root.join("config/events/#{basename}.yml"))
+
+ dispatch_from_definition(definition, label: label, property: property, value: value, context: context, project: project, user: user, namespace: namespace, **extra)
+ end
+
+ def dispatch_from_definition(definition, **event_data)
+ definition = definition.with_indifferent_access
+
+ category ||= definition[:category]
+ action ||= definition[:action]
+
+ event(category, action, **event_data)
+ end
+
def options(group)
snowplow.options(group)
end
@@ -39,3 +54,5 @@ module Gitlab
end
end
end
+
+Gitlab::Tracking.prepend_mod_with('Gitlab::Tracking')
diff --git a/lib/gitlab/view/presenter/base.rb b/lib/gitlab/view/presenter/base.rb
index 3bacad72050..a2d217fb42f 100644
--- a/lib/gitlab/view/presenter/base.rb
+++ b/lib/gitlab/view/presenter/base.rb
@@ -11,15 +11,19 @@ module Gitlab
include Gitlab::Routing
include Gitlab::Allowable
- attr_reader :subject
+ # Presenters should always access the subject through an explicit getter defined with
+ # `presents ..., as:`, the `__subject__` method is only intended for internal use.
+ def __subject__
+ @subject
+ end
def can?(user, action, overridden_subject = nil)
- super(user, action, overridden_subject || subject)
+ super(user, action, overridden_subject || __subject__)
end
# delegate all #can? queries to the subject
def declarative_policy_delegate
- subject
+ __subject__
end
def present(**attributes)
@@ -31,15 +35,15 @@ module Gitlab
end
def is_a?(type)
- super || subject.is_a?(type)
+ super || __subject__.is_a?(type)
end
def web_url
- url_builder.build(subject)
+ url_builder.build(__subject__)
end
def web_path
- url_builder.build(subject, only_path: true)
+ url_builder.build(__subject__, only_path: true)
end
class_methods do
@@ -58,7 +62,7 @@ module Gitlab
# no-op
end
- define_method(as) { subject } if as
+ define_method(as) { __subject__ } if as
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3c4c1b65c04..a618c56dcc8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7038,6 +7038,9 @@ msgstr ""
msgid "Changed assignee(s)."
msgstr ""
+msgid "Changed merge method to %{merge_method}"
+msgstr ""
+
msgid "Changed reviewer(s)."
msgstr ""
@@ -8001,7 +8004,7 @@ msgstr ""
msgid "ClusterAgents|Failed to register an agent"
msgstr ""
-msgid "ClusterAgents|From a terminal, connect to your cluster and run this command. The token is included."
+msgid "ClusterAgents|From a terminal, connect to your cluster and run this command. The token is included in the command."
msgstr ""
msgid "ClusterAgents|GitLab agent"
@@ -8019,6 +8022,9 @@ msgstr ""
msgid "ClusterAgents|How to update an agent?"
msgstr ""
+msgid "ClusterAgents|Install using Helm (recommended)"
+msgstr ""
+
msgid "ClusterAgents|Last connected %{timeAgo}."
msgstr ""
@@ -8052,9 +8058,6 @@ msgstr ""
msgid "ClusterAgents|Recommended"
msgstr ""
-msgid "ClusterAgents|Recommended installation method"
-msgstr ""
-
msgid "ClusterAgents|Register"
msgstr ""
diff --git a/package.json b/package.json
index b54e169ea5b..737beefc617 100644
--- a/package.json
+++ b/package.json
@@ -56,8 +56,8 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
- "@gitlab/svgs": "2.6.0",
- "@gitlab/ui": "38.0.1",
+ "@gitlab/svgs": "2.8.0",
+ "@gitlab/ui": "38.8.1",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7",
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 976188e45c6..0c0a1a90ff2 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -20,9 +20,7 @@ module QA
verify_ssl: false
}
- RestClient::Request.execute(
- default_args.merge(args)
- )
+ RestClient::Request.execute(default_args.merge(args))
rescue RestClient::ExceptionWithResponse => e
return_response_or_raise(e)
end
@@ -56,13 +54,16 @@ module QA
end
end
- def put(url, payload = nil)
+ def put(url, payload = nil, args = {})
with_retry_on_too_many_requests do
- RestClient::Request.execute(
+ default_args = {
method: :put,
url: url,
payload: payload,
- verify_ssl: false)
+ verify_ssl: false
+ }
+
+ RestClient::Request.execute(default_args.merge(args))
rescue RestClient::ExceptionWithResponse => e
return_response_or_raise(e)
end
diff --git a/qa/qa/tools/reliable_report.rb b/qa/qa/tools/reliable_report.rb
index 3d1845c68ab..b3df6de3d54 100644
--- a/qa/qa/tools/reliable_report.rb
+++ b/qa/qa/tools/reliable_report.rb
@@ -11,6 +11,8 @@ module QA
include Support::InfluxdbTools
include Support::API
+ RELIABLE_REPORT_LABEL = "reliable test report"
+
# Project for report creation: https://gitlab.com/gitlab-org/gitlab
PROJECT_ID = 278964
@@ -28,7 +30,11 @@ module QA
reporter = new(range)
reporter.print_report
- reporter.report_in_issue_and_slack if report_in_issue_and_slack == "true"
+
+ if report_in_issue_and_slack == "true"
+ reporter.report_in_issue_and_slack
+ reporter.close_previous_reports
+ end
rescue StandardError => e
reporter&.notify_failure(e)
raise(e)
@@ -51,16 +57,15 @@ module QA
# @return [void]
def report_in_issue_and_slack
puts "Creating report".colorize(:green)
- response = post(
- "#{gitlab_api_url}/projects/#{PROJECT_ID}/issues",
- {
- title: "Reliable e2e test report",
- description: report_issue_body,
- labels: "Quality,test,type::maintenance,reliable test report,automation:ml"
- },
- headers: { "PRIVATE-TOKEN" => gitlab_access_token }
+ issue = api_update(
+ :post,
+ "projects/#{PROJECT_ID}/issues",
+ title: "Reliable e2e test report",
+ description: report_issue_body,
+ labels: "#{RELIABLE_REPORT_LABEL},Quality,test,type::maintenance,automation:ml"
)
- web_url = parse_body(response)[:web_url]
+ @report_iid = issue[:iid]
+ web_url = issue[:web_url]
puts "Created report issue: #{web_url}"
puts "Sending slack notification".colorize(:green)
@@ -76,6 +81,25 @@ module QA
puts "Done!"
end
+ # Close previous reliable test reports
+ #
+ # @return [void]
+ def close_previous_reports
+ puts "Closing previous reports".colorize(:green)
+ issues = api_get("projects/#{PROJECT_ID}/issues?labels=#{RELIABLE_REPORT_LABEL}&state=opened")
+
+ issues
+ .reject { |issue| issue[:iid] == report_iid }
+ .each do |issue|
+ issue_iid = issue[:iid]
+ issue_endpoint = "projects/#{PROJECT_ID}/issues/#{issue_iid}"
+
+ puts "Closing previous report '#{issue[:web_url]}'"
+ api_update(:put, issue_endpoint, state_event: "close")
+ api_update(:post, "#{issue_endpoint}/notes", body: "Closed issue in favor of ##{report_iid}")
+ end
+ end
+
# Notify failure
#
# @param [StandardError] error
@@ -89,7 +113,39 @@ module QA
private
- attr_reader :range, :slack_channel
+ attr_reader :range, :slack_channel, :report_iid
+
+ # Slack notifier
+ #
+ # @return [Slack::Notifier]
+ def notifier
+ @notifier ||= Slack::Notifier.new(
+ slack_webhook_url,
+ channel: slack_channel,
+ username: "Reliable Spec Report"
+ )
+ end
+
+ # Gitlab access token
+ #
+ # @return [String]
+ def gitlab_access_token
+ @gitlab_access_token ||= ENV["GITLAB_ACCESS_TOKEN"] || raise("Missing GITLAB_ACCESS_TOKEN env variable")
+ end
+
+ # Gitlab api url
+ #
+ # @return [String]
+ def gitlab_api_url
+ @gitlab_api_url ||= ENV["CI_API_V4_URL"] || raise("Missing CI_API_V4_URL env variable")
+ end
+
+ # Slack webhook url
+ #
+ # @return [String]
+ def slack_webhook_url
+ @slack_webhook_url ||= ENV["SLACK_WEBHOOK"] || raise("Missing SLACK_WEBHOOK env variable")
+ end
# Markdown formatted report issue body
#
@@ -324,36 +380,30 @@ module QA
QUERY
end
- # Slack notifier
+ # Api get request
#
- # @return [Slack::Notifier]
- def notifier
- @notifier ||= Slack::Notifier.new(
- slack_webhook_url,
- channel: slack_channel,
- username: "Reliable Spec Report"
- )
+ # @param [String] path
+ # @param [Hash] payload
+ # @return [Hash, Array]
+ def api_get(path)
+ response = get("#{gitlab_api_url}/#{path}", { headers: { "PRIVATE-TOKEN" => gitlab_access_token } })
+ parse_body(response)
end
- # Gitlab access token
+ # Api update request
#
- # @return [String]
- def gitlab_access_token
- @gitlab_access_token ||= ENV["GITLAB_ACCESS_TOKEN"] || raise("Missing GITLAB_ACCESS_TOKEN env variable")
- end
-
- # Gitlab api url
- #
- # @return [String]
- def gitlab_api_url
- @gitlab_api_url ||= ENV["CI_API_V4_URL"] || raise("Missing CI_API_V4_URL env variable")
- end
-
- # Slack webhook url
- #
- # @return [String]
- def slack_webhook_url
- @slack_webhook_url ||= ENV["SLACK_WEBHOOK"] || raise("Missing SLACK_WEBHOOK env variable")
+ # @param [Symbol] verb :post or :put
+ # @param [String] path
+ # @param [Hash] payload
+ # @return [Hash, Array]
+ def api_update(verb, path, **payload)
+ response = send(
+ verb,
+ "#{gitlab_api_url}/#{path}",
+ payload,
+ { headers: { "PRIVATE-TOKEN" => gitlab_access_token } }
+ )
+ parse_body(response)
end
end
end
diff --git a/qa/spec/tools/reliable_report_spec.rb b/qa/spec/tools/reliable_report_spec.rb
index 924f9ef9f0d..d5c898779b8 100644
--- a/qa/spec/tools/reliable_report_spec.rb
+++ b/qa/spec/tools/reliable_report_spec.rb
@@ -5,7 +5,6 @@ describe QA::Tools::ReliableReport do
subject(:run) { described_class.run(range: range, report_in_issue_and_slack: create_issue) }
- let(:gitlab_response) { instance_double("RestClient::Response", code: 200, body: { web_url: issue_url }.to_json) }
let(:slack_notifier) { instance_double("Slack::Notifier", post: nil) }
let(:influx_client) { instance_double("InfluxDB2::Client", create_query_api: query_api) }
let(:query_api) { instance_double("InfluxDB2::QueryApi") }
@@ -118,7 +117,7 @@ describe QA::Tools::ReliableReport do
stub_env("CI_API_V4_URL", "gitlab_api_url")
stub_env("GITLAB_ACCESS_TOKEN", "gitlab_token")
- allow(RestClient::Request).to receive(:execute).and_return(gitlab_response)
+ allow(RestClient::Request).to receive(:execute)
allow(Slack::Notifier).to receive(:new).and_return(slack_notifier)
allow(InfluxDB2::Client).to receive(:new).and_return(influx_client)
@@ -139,6 +138,37 @@ describe QA::Tools::ReliableReport do
context "with report creation" do
let(:create_issue) { "true" }
+ let(:iid) { 2 }
+ let(:old_iid) { 1 }
+ let(:issue_endpoint) { "gitlab_api_url/projects/278964/issues" }
+
+ let(:common_api_args) do
+ {
+ verify_ssl: false,
+ headers: { "PRIVATE-TOKEN" => "gitlab_token" }
+ }
+ end
+
+ let(:create_issue_response) do
+ instance_double(
+ "RestClient::Response",
+ code: 200,
+ body: { web_url: issue_url, iid: iid }.to_json
+ )
+ end
+
+ let(:open_issues_response) do
+ instance_double(
+ "RestClient::Response",
+ code: 200,
+ body: [{ web_url: issue_url, iid: iid }, { web_url: issue_url, iid: old_iid }].to_json
+ )
+ end
+
+ let(:success_response) do
+ instance_double("RestClient::Response", code: 200, body: {}.to_json)
+ end
+
let(:issue_body) do
<<~TXT.strip
[[_TOC_]]
@@ -157,19 +187,48 @@ describe QA::Tools::ReliableReport do
TXT
end
- it "creates report issue", :aggregate_failures do
+ before do
+ allow(RestClient::Request).to receive(:execute).exactly(4).times.and_return(
+ create_issue_response,
+ open_issues_response,
+ success_response,
+ success_response
+ )
+ end
+
+ it "creates report issue" do
expect { run }.to output.to_stdout
expect(RestClient::Request).to have_received(:execute).with(
method: :post,
- url: "gitlab_api_url/projects/278964/issues",
- verify_ssl: false,
- headers: { "PRIVATE-TOKEN" => "gitlab_token" },
+ url: issue_endpoint,
payload: {
title: "Reliable e2e test report",
description: issue_body,
- labels: "Quality,test,type::maintenance,reliable test report,automation:ml"
- }
+ labels: "reliable test report,Quality,test,type::maintenance,automation:ml"
+ },
+ **common_api_args
+ )
+ expect(RestClient::Request).to have_received(:execute).with(
+ method: :get,
+ url: "#{issue_endpoint}?labels=reliable test report&state=opened",
+ **common_api_args
+ )
+ expect(RestClient::Request).to have_received(:execute).with(
+ method: :put,
+ url: "#{issue_endpoint}/#{old_iid}",
+ payload: {
+ state_event: "close"
+ },
+ **common_api_args
+ )
+ expect(RestClient::Request).to have_received(:execute).with(
+ method: :post,
+ url: "#{issue_endpoint}/#{old_iid}/notes",
+ payload: {
+ body: "Closed issue in favor of ##{iid}"
+ },
+ **common_api_args
)
expect(slack_notifier).to have_received(:post).with(
icon_emoji: ":tanuki-protect:",
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 6438eddb33f..be30011905c 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -1084,19 +1084,6 @@ RSpec.describe GroupsController, factory_default: :keep do
enable_admin_mode!(admin)
end
- context 'when the group export feature flag is not enabled' do
- before do
- sign_in(admin)
- stub_feature_flags(group_import_export: false)
- end
-
- it 'returns a not found error' do
- post :export, params: { id: group.to_param }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
context 'when the user does not have permission to export the group' do
before do
sign_in(guest)
@@ -1197,19 +1184,6 @@ RSpec.describe GroupsController, factory_default: :keep do
end
end
- context 'when the group export feature flag is not enabled' do
- before do
- sign_in(admin)
- stub_feature_flags(group_import_export: false)
- end
-
- it 'returns a not found error' do
- post :export, params: { id: group.to_param }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
context 'when the user does not have the required permissions' do
before do
sign_in(guest)
diff --git a/spec/features/clusters/create_agent_spec.rb b/spec/features/clusters/create_agent_spec.rb
index db8de24e105..c7326204bf6 100644
--- a/spec/features/clusters/create_agent_spec.rb
+++ b/spec/features/clusters/create_agent_spec.rb
@@ -34,7 +34,7 @@ RSpec.describe 'Cluster agent registration', :js do
expect(page).to have_content('You cannot see this token again after you close this window.')
expect(page).to have_content('example-agent-token')
- expect(page).to have_content('docker run --pull=always --rm')
+ expect(page).to have_content('helm upgrade --install')
within find('.modal-footer') do
click_button('Close')
diff --git a/spec/features/groups/import_export/export_file_spec.rb b/spec/features/groups/import_export/export_file_spec.rb
index 9feb8085e66..e3cb1ad77a7 100644
--- a/spec/features/groups/import_export/export_file_spec.rb
+++ b/spec/features/groups/import_export/export_file_spec.rb
@@ -26,22 +26,6 @@ RSpec.describe 'Group Export', :js do
end
end
- context 'when the group import/export FF is disabled' do
- before do
- stub_feature_flags(group_import_export: false)
-
- group.add_owner(user)
- sign_in(user)
- end
-
- it 'does not show the group export options' do
- visit edit_group_path(group)
-
- expect(page).to have_content('Advanced')
- expect(page).not_to have_content('Export group')
- end
- end
-
context 'when the signed in user does not have the required permission level' do
before do
group.add_guest(user)
diff --git a/spec/features/jira_connect/subscriptions_spec.rb b/spec/features/jira_connect/subscriptions_spec.rb
index 0b7321bf271..94c293c88b9 100644
--- a/spec/features/jira_connect/subscriptions_spec.rb
+++ b/spec/features/jira_connect/subscriptions_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe 'Subscriptions Content Security Policy' do
it 'appends to CSP directives' do
visit jira_connect_subscriptions_path(jwt: jwt)
- is_expected.to include("frame-ancestors 'self' https://*.atlassian.net")
+ is_expected.to include("frame-ancestors 'self' https://*.atlassian.net https://*.jira.com")
is_expected.to include("script-src 'self' https://some-cdn.test https://connect-cdn.atl-paas.net")
is_expected.to include("style-src 'self' https://some-cdn.test 'unsafe-inline'")
end
diff --git a/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap b/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap
index dd742419d32..36003154b58 100644
--- a/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap
+++ b/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap
@@ -8,7 +8,7 @@ exports[`~/access_tokens/components/expires_at_field should render datepicker wi
optionaltext="(optional)"
>
<gl-datepicker-stub
- ariallabel=""
+ arialabel=""
autocomplete=""
container=""
displayfield="true"
diff --git a/spec/frontend/clusters_list/components/agent_token_spec.js b/spec/frontend/clusters_list/components/agent_token_spec.js
index 2c47f12ace1..7f6ec2eb3a2 100644
--- a/spec/frontend/clusters_list/components/agent_token_spec.js
+++ b/spec/frontend/clusters_list/components/agent_token_spec.js
@@ -69,8 +69,8 @@ describe('InstallAgentModal', () => {
});
it('shows code block with agent installation command', () => {
- expect(findCodeBlock().props('code')).toContain(`--agent-token=${agentToken}`);
- expect(findCodeBlock().props('code')).toContain(`--kas-address=${kasAddress}`);
+ expect(findCodeBlock().props('code')).toContain(`--set config.token=${agentToken}`);
+ expect(findCodeBlock().props('code')).toContain(`--set config.kasAddress=${kasAddress}`);
});
});
});
diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js
index 5f6f7fc2031..a556f3c17f3 100644
--- a/spec/frontend/incidents/components/incidents_list_spec.js
+++ b/spec/frontend/incidents/components/incidents_list_spec.js
@@ -171,6 +171,7 @@ describe('Incidents List', () => {
expect(link.text()).toBe(title);
expect(link.attributes('href')).toContain(`issues/incident/${iid}`);
+ expect(link.find('.gl-text-truncate').exists()).toBe(true);
});
describe('Assignees', () => {
@@ -201,15 +202,14 @@ describe('Incidents List', () => {
describe('Escalation status', () => {
it('renders escalation status per row', () => {
- expect(findEscalationStatus().length).toBe(mockIncidents.length);
-
- const actualStatuses = findEscalationStatus().wrappers.map((status) => status.text());
- expect(actualStatuses).toEqual([
- 'Triggered',
- 'Acknowledged',
- 'Resolved',
- I18N.noEscalationStatus,
- ]);
+ const statuses = findEscalationStatus().wrappers;
+ const expectedStatuses = ['Triggered', 'Acknowledged', 'Resolved', I18N.noEscalationStatus];
+
+ expect(statuses.length).toBe(mockIncidents.length);
+ statuses.forEach((status, index) => {
+ expect(status.text()).toEqual(expectedStatuses[index]);
+ expect(status.classes('gl-text-truncate')).toBe(true);
+ });
});
describe('when feature is disabled', () => {
diff --git a/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap b/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap
index ceae8eebaef..3dd6023140f 100644
--- a/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap
+++ b/spec/frontend/packages_and_registries/shared/components/__snapshots__/registry_breadcrumb_spec.js.snap
@@ -10,11 +10,10 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = `
class="breadcrumb gl-breadcrumb-list"
>
<li
- class="breadcrumb-item gl-breadcrumb-item"
+ class="gl-breadcrumb-item"
>
<a
class=""
- href="/"
target="_self"
>
<span>
@@ -45,9 +44,10 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = `
<!---->
<li
- class="breadcrumb-item gl-breadcrumb-item"
+ class="gl-breadcrumb-item"
>
<a
+ aria-current="page"
class=""
href="#"
target="_self"
@@ -75,11 +75,11 @@ exports[`Registry Breadcrumb when is rootRoute renders 1`] = `
class="breadcrumb gl-breadcrumb-list"
>
<li
- class="breadcrumb-item gl-breadcrumb-item"
+ class="gl-breadcrumb-item"
>
<a
+ aria-current="page"
class=""
- href="/"
target="_self"
>
<span>
diff --git a/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js b/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js
index 6dfe116c285..15db454ac68 100644
--- a/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js
@@ -1,4 +1,4 @@
-import { mount } from '@vue/test-utils';
+import { mount, RouterLinkStub } from '@vue/test-utils';
import component from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
@@ -21,6 +21,9 @@ describe('Registry Breadcrumb', () => {
},
},
},
+ stubs: {
+ RouterLink: RouterLinkStub,
+ },
});
};
@@ -30,7 +33,6 @@ describe('Registry Breadcrumb', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('when is rootRoute', () => {
@@ -46,7 +48,6 @@ describe('Registry Breadcrumb', () => {
const links = wrapper.findAll('a');
expect(links).toHaveLength(1);
- expect(links.at(0).attributes('href')).toBe('/');
});
it('the link text is calculated by nameGenerator', () => {
@@ -67,7 +68,6 @@ describe('Registry Breadcrumb', () => {
const links = wrapper.findAll('a');
expect(links).toHaveLength(2);
- expect(links.at(0).attributes('href')).toBe('/');
expect(links.at(1).attributes('href')).toBe('#');
});
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
index 0822b293f75..6c743f92116 100644
--- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
@@ -173,7 +173,7 @@ describe('Pipelines filtered search', () => {
{ type: 'filtered-search-term', value: { data: '' } },
];
- expect(findFilteredSearch().props('value')).toEqual(expectedValueProp);
+ expect(findFilteredSearch().props('value')).toMatchObject(expectedValueProp);
expect(findFilteredSearch().props('value')).toHaveLength(expectedValueProp.length);
});
});
diff --git a/spec/frontend/pipelines/empty_state/ci_templates_spec.js b/spec/frontend/pipelines/empty_state/ci_templates_spec.js
new file mode 100644
index 00000000000..606fdc9cac1
--- /dev/null
+++ b/spec/frontend/pipelines/empty_state/ci_templates_spec.js
@@ -0,0 +1,85 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+import CiTemplates from '~/pipelines/components/pipelines_list/empty_state/ci_templates.vue';
+
+const pipelineEditorPath = '/-/ci/editor';
+const suggestedCiTemplates = [
+ { name: 'Android', logo: '/assets/illustrations/logos/android.svg' },
+ { name: 'Bash', logo: '/assets/illustrations/logos/bash.svg' },
+ { name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' },
+];
+
+describe('CI Templates', () => {
+ let wrapper;
+ let trackingSpy;
+
+ const createWrapper = () => {
+ return shallowMountExtended(CiTemplates, {
+ provide: {
+ pipelineEditorPath,
+ suggestedCiTemplates,
+ },
+ });
+ };
+
+ const findTemplateDescription = () => wrapper.findByTestId('template-description');
+ const findTemplateLink = () => wrapper.findByTestId('template-link');
+ const findTemplateName = () => wrapper.findByTestId('template-name');
+ const findTemplateLogo = () => wrapper.findByTestId('template-logo');
+
+ beforeEach(() => {
+ wrapper = createWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('renders template list', () => {
+ it('renders all suggested templates', () => {
+ const content = wrapper.text();
+
+ expect(content).toContain('Android', 'Bash', 'C++');
+ });
+
+ it('has the correct template name', () => {
+ expect(findTemplateName().text()).toBe('Android');
+ });
+
+ it('links to the correct template', () => {
+ expect(findTemplateLink().attributes('href')).toBe(
+ pipelineEditorPath.concat('?template=Android'),
+ );
+ });
+
+ it('has the description of the template', () => {
+ expect(findTemplateDescription().text()).toBe(
+ 'CI/CD template to test and deploy your Android project.',
+ );
+ });
+
+ it('has the right logo of the template', () => {
+ expect(findTemplateLogo().attributes('src')).toBe('/assets/illustrations/logos/android.svg');
+ });
+ });
+
+ describe('tracking', () => {
+ beforeEach(() => {
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('sends an event when template is clicked', () => {
+ findTemplateLink().vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledTimes(1);
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
+ label: 'Android',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/pipelines_ci_templates_spec.js b/spec/frontend/pipelines/empty_state/pipelines_ci_templates_spec.js
index 7064f7448ec..14860f20317 100644
--- a/spec/frontend/pipelines/pipelines_ci_templates_spec.js
+++ b/spec/frontend/pipelines/empty_state/pipelines_ci_templates_spec.js
@@ -1,12 +1,12 @@
import '~/commons';
import { GlButton, GlSprintf } from '@gitlab/ui';
-import { sprintf } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { mockTracking } from 'helpers/tracking_helper';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { stubExperiments } from 'helpers/experimentation_helper';
import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
import ExperimentTracking from '~/experimentation/experiment_tracking';
-import PipelinesCiTemplate from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
+import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue';
+import CiTemplates from '~/pipelines/components/pipelines_list/empty_state/ci_templates.vue';
import {
RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
@@ -16,11 +16,6 @@ import {
} from '~/pipeline_editor/constants';
const pipelineEditorPath = '/-/ci/editor';
-const suggestedCiTemplates = [
- { name: 'Android', logo: '/assets/illustrations/logos/android.svg' },
- { name: 'Bash', logo: '/assets/illustrations/logos/bash.svg' },
- { name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' },
-];
jest.mock('~/experimentation/experiment_tracking');
@@ -29,21 +24,17 @@ describe('Pipelines CI Templates', () => {
let trackingSpy;
const createWrapper = (propsData = {}, stubs = {}) => {
- return shallowMountExtended(PipelinesCiTemplate, {
+ return shallowMountExtended(PipelinesCiTemplates, {
provide: {
pipelineEditorPath,
- suggestedCiTemplates,
},
propsData,
stubs,
});
};
- const findTestTemplateLinks = () => wrapper.findAll('[data-testid="test-template-link"]');
- const findTemplateDescriptions = () => wrapper.findAll('[data-testid="template-description"]');
- const findTemplateLinks = () => wrapper.findAll('[data-testid="template-link"]');
- const findTemplateNames = () => wrapper.findAll('[data-testid="template-name"]');
- const findTemplateLogos = () => wrapper.findAll('[data-testid="template-logo"]');
+ const findTestTemplateLink = () => wrapper.findByTestId('test-template-link');
+ const findCiTemplates = () => wrapper.findComponent(CiTemplates);
const findSettingsLink = () => wrapper.findByTestId('settings-link');
const findDocumentationLink = () => wrapper.findByTestId('documentation-link');
const findSettingsButton = () => wrapper.findByTestId('settings-button');
@@ -59,63 +50,24 @@ describe('Pipelines CI Templates', () => {
});
it('links to the getting started template', () => {
- expect(findTestTemplateLinks().at(0).attributes('href')).toBe(
+ expect(findTestTemplateLink().attributes('href')).toBe(
pipelineEditorPath.concat('?template=Getting-Started'),
);
});
});
- describe('renders template list', () => {
- beforeEach(() => {
- wrapper = createWrapper();
- });
-
- it('renders all suggested templates', () => {
- const content = wrapper.text();
-
- expect(content).toContain('Android', 'Bash', 'C++');
- });
-
- it('has the correct template name', () => {
- expect(findTemplateNames().at(0).text()).toBe('Android');
- });
-
- it('links to the correct template', () => {
- expect(findTemplateLinks().at(0).attributes('href')).toBe(
- pipelineEditorPath.concat('?template=Android'),
- );
- });
-
- it('has the description of the template', () => {
- expect(findTemplateDescriptions().at(0).text()).toBe(
- sprintf(I18N.templates.description, { name: 'Android' }),
- );
- });
-
- it('has the right logo of the template', () => {
- expect(findTemplateLogos().at(0).attributes('src')).toBe(
- '/assets/illustrations/logos/android.svg',
- );
- });
- });
-
describe('tracking', () => {
beforeEach(() => {
wrapper = createWrapper();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
- it('sends an event when template is clicked', () => {
- findTemplateLinks().at(0).vm.$emit('click');
-
- expect(trackingSpy).toHaveBeenCalledTimes(1);
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
- label: 'Android',
- });
+ afterEach(() => {
+ unmockTracking();
});
it('sends an event when Getting-Started template is clicked', () => {
- findTestTemplateLinks().at(0).vm.$emit('click');
+ findTestTemplateLink().vm.$emit('click');
expect(trackingSpy).toHaveBeenCalledTimes(1);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
@@ -198,8 +150,8 @@ describe('Pipelines CI Templates', () => {
});
it(`renders the templates: ${templatesRendered}`, () => {
- expect(findTestTemplateLinks().exists()).toBe(templatesRendered);
- expect(findTemplateLinks().exists()).toBe(templatesRendered);
+ expect(findTestTemplateLink().exists()).toBe(templatesRendered);
+ expect(findCiTemplates().exists()).toBe(templatesRendered);
});
},
);
diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index 31b74a06efd..46dad4a035c 100644
--- a/spec/frontend/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
@@ -1,7 +1,7 @@
import '~/commons';
import { mount } from '@vue/test-utils';
import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
-import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
+import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue';
describe('Pipelines Empty State', () => {
let wrapper;
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 20ed12cd1f5..d2b30c93746 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -14,7 +14,7 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue';
-import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue';
+import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue';
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import { RAW_TEXT_WARNING } from '~/pipelines/constants';
import Store from '~/pipelines/stores/pipelines_store';
diff --git a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
index 880125894a9..b1b436e5443 100644
--- a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
+++ b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
@@ -113,7 +113,7 @@ describe('RunnerList', () => {
});
it('filter values are shown', () => {
- expect(findGlFilteredSearch().props('value')).toEqual(mockFilters);
+ expect(findGlFilteredSearch().props('value')).toMatchObject(mockFilters);
});
it('sort option is selected', () => {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
index 575e8a73050..b6a181e6a0b 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
@@ -26,7 +26,6 @@ import {
tokenValueMilestone,
tokenValueMembership,
tokenValueConfidential,
- tokenValueEmpty,
} from './mock_data';
jest.mock('~/vue_shared/components/filtered_search_bar/filtered_search_utils', () => ({
@@ -207,33 +206,14 @@ describe('FilteredSearchBarRoot', () => {
});
});
- describe('watchers', () => {
- describe('filterValue', () => {
- it('emits component event `onFilter` with empty array and false when filter was never selected', async () => {
- wrapper = createComponent({ initialFilterValue: [tokenValueEmpty] });
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- initialRender: false,
- filterValue: [tokenValueEmpty],
- });
-
- await nextTick();
- expect(wrapper.emitted('onFilter')[0]).toEqual([[], false]);
- });
+ describe('events', () => {
+ it('emits component event `onFilter` with empty array and true when initially selected filter value was cleared', async () => {
+ wrapper = createComponent({ initialFilterValue: [tokenValueLabel] });
- it('emits component event `onFilter` with empty array and true when initially selected filter value was cleared', async () => {
- wrapper = createComponent({ initialFilterValue: [tokenValueLabel] });
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- initialRender: false,
- filterValue: [tokenValueEmpty],
- });
+ wrapper.find(GlFilteredSearch).vm.$emit('clear');
- await nextTick();
- expect(wrapper.emitted('onFilter')[0]).toEqual([[], true]);
- });
+ await nextTick();
+ expect(wrapper.emitted('onFilter')[0]).toEqual([[], true]);
});
});
diff --git a/spec/haml_lint/linter/documentation_links_spec.rb b/spec/haml_lint/linter/documentation_links_spec.rb
index 75002097d69..f2aab4304c1 100644
--- a/spec/haml_lint/linter/documentation_links_spec.rb
+++ b/spec/haml_lint/linter/documentation_links_spec.rb
@@ -43,6 +43,12 @@ RSpec.describe HamlLint::Linter::DocumentationLinks do
let(:haml) { "= link_to 'Description', #{link_pattern}('wrong.md'), target: '_blank'" }
it { is_expected.to report_lint }
+
+ context 'when haml ends with block definition' do
+ let(:haml) { "= link_to 'Description', #{link_pattern}('wrong.md') do" }
+
+ it { is_expected.to report_lint }
+ end
end
context 'when link with wrong file path is assigned to a variable' do
diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb
index 534fc79d5ae..b9aa5f7c431 100644
--- a/spec/lib/gitlab/ci/variables/builder_spec.rb
+++ b/spec/lib/gitlab/ci/variables/builder_spec.rb
@@ -501,4 +501,115 @@ RSpec.describe Gitlab::Ci::Variables::Builder do
end
end
end
+
+ describe '#config_variables' do
+ subject(:config_variables) { builder.config_variables }
+
+ context 'without project' do
+ before do
+ pipeline.update!(project_id: nil)
+ end
+
+ it { expect(config_variables.size).to eq(0) }
+ end
+
+ context 'without repository' do
+ let(:project) { create(:project) }
+ let(:pipeline) { build(:ci_pipeline, ref: nil, sha: nil, project: project) }
+
+ it { expect(config_variables['CI_COMMIT_SHA']).to be_nil }
+ end
+
+ context 'with protected variables' do
+ let_it_be(:instance_variable) do
+ create(:ci_instance_variable, :protected, key: 'instance_variable')
+ end
+
+ let_it_be(:group_variable) do
+ create(:ci_group_variable, :protected, group: group, key: 'group_variable')
+ end
+
+ let_it_be(:project_variable) do
+ create(:ci_variable, :protected, project: project, key: 'project_variable')
+ end
+
+ it 'does not include protected variables' do
+ expect(config_variables[instance_variable.key]).to be_nil
+ expect(config_variables[group_variable.key]).to be_nil
+ expect(config_variables[project_variable.key]).to be_nil
+ end
+ end
+
+ context 'with scoped variables' do
+ let_it_be(:scoped_group_variable) do
+ create(:ci_group_variable,
+ group: group,
+ key: 'group_variable',
+ value: 'scoped',
+ environment_scope: 'scoped')
+ end
+
+ let_it_be(:group_variable) do
+ create(:ci_group_variable,
+ group: group,
+ key: 'group_variable',
+ value: 'unscoped')
+ end
+
+ let_it_be(:scoped_project_variable) do
+ create(:ci_variable,
+ project: project,
+ key: 'project_variable',
+ value: 'scoped',
+ environment_scope: 'scoped')
+ end
+
+ let_it_be(:project_variable) do
+ create(:ci_variable,
+ project: project,
+ key: 'project_variable',
+ value: 'unscoped')
+ end
+
+ it 'does not include scoped variables' do
+ expect(config_variables.to_hash[group_variable.key]).to eq('unscoped')
+ expect(config_variables.to_hash[project_variable.key]).to eq('unscoped')
+ end
+ end
+
+ context 'variables ordering' do
+ def var(name, value)
+ { key: name, value: value.to_s, public: true, masked: false }
+ end
+
+ before do
+ allow(pipeline.project).to receive(:predefined_variables) { [var('A', 1), var('B', 1)] }
+ allow(pipeline).to receive(:predefined_variables) { [var('B', 2), var('C', 2)] }
+ allow(builder).to receive(:secret_instance_variables) { [var('C', 3), var('D', 3)] }
+ allow(builder).to receive(:secret_group_variables) { [var('D', 4), var('E', 4)] }
+ allow(builder).to receive(:secret_project_variables) { [var('E', 5), var('F', 5)] }
+ allow(pipeline).to receive(:variables) { [var('F', 6), var('G', 6)] }
+ allow(pipeline).to receive(:pipeline_schedule) { double(job_variables: [var('G', 7), var('H', 7)]) }
+ end
+
+ it 'returns variables in order depending on resource hierarchy' do
+ expect(config_variables.to_runner_variables).to eq(
+ [var('A', 1), var('B', 1),
+ var('B', 2), var('C', 2),
+ var('C', 3), var('D', 3),
+ var('D', 4), var('E', 4),
+ var('E', 5), var('F', 5),
+ var('F', 6), var('G', 6),
+ var('G', 7), var('H', 7)])
+ end
+
+ it 'overrides duplicate keys depending on resource hierarchy' do
+ expect(config_variables.to_hash).to match(
+ 'A' => '1', 'B' => '2',
+ 'C' => '3', 'D' => '4',
+ 'E' => '5', 'F' => '6',
+ 'G' => '7', 'H' => '7')
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb b/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
index d873f1b7c75..e7b5bad8626 100644
--- a/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb
@@ -240,7 +240,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a
end
def software_license_class
- Class.new(ActiveRecord::Base) do
+ Class.new(Gitlab::Database::Migration[2.0]::MigrationRecord) do
self.table_name = 'software_licenses'
end
end
@@ -272,7 +272,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a
end
def ci_instance_variables_class
- Class.new(ActiveRecord::Base) do
+ Class.new(Gitlab::Database::Migration[2.0]::MigrationRecord) do
self.table_name = 'ci_instance_variables'
end
end
@@ -303,7 +303,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers::RestrictGitlabSchema, query_a
end
def detached_partitions_class
- Class.new(ActiveRecord::Base) do
+ Class.new(Gitlab::Database::Migration[2.0]::MigrationRecord) do
self.table_name = 'detached_partitions'
end
end
diff --git a/spec/lib/gitlab/database/migration_spec.rb b/spec/lib/gitlab/database/migration_spec.rb
index 287e738c24e..18bbc6c1dd3 100644
--- a/spec/lib/gitlab/database/migration_spec.rb
+++ b/spec/lib/gitlab/database/migration_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::Database::Migration do
# This breaks upon Rails upgrade. In that case, we'll add a new version in Gitlab::Database::Migration::MIGRATION_CLASSES,
# bump .current_version and leave existing migrations and already defined versions of Gitlab::Database::Migration
# untouched.
- expect(described_class[described_class.current_version].superclass).to eq(ActiveRecord::Migration::Current)
+ expect(described_class[described_class.current_version]).to be < ActiveRecord::Migration::Current
end
end
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index cd83971aef9..cc973be8be9 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -149,4 +149,42 @@ RSpec.describe Gitlab::Tracking do
described_class.event(nil, 'some_action')
end
end
+
+ describe '.definition' do
+ let(:namespace) { create(:namespace) }
+
+ let_it_be(:definition_action) { 'definition_action' }
+ let_it_be(:definition_category) { 'definition_category' }
+ let_it_be(:label_description) { 'definition label description' }
+ let_it_be(:test_definition) {{ 'category': definition_category, 'action': definition_action }}
+
+ before do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:event)
+ end
+ allow_next_instance_of(Gitlab::Tracking::Destinations::Snowplow) do |instance|
+ allow(instance).to receive(:event)
+ end
+ allow(YAML).to receive(:load_file).with(Rails.root.join('config/events/filename.yml')).and_return(test_definition)
+ end
+
+ it 'dispatchs the data to .event' do
+ project = build_stubbed(:project)
+ user = build_stubbed(:user)
+
+ expect(described_class).to receive(:event) do |category, action, args|
+ expect(category).to eq(definition_category)
+ expect(action).to eq(definition_action)
+ expect(args[:label]).to eq('label')
+ expect(args[:property]).to eq('...')
+ expect(args[:project]).to eq(project)
+ expect(args[:user]).to eq(user)
+ expect(args[:namespace]).to eq(namespace)
+ expect(args[:extra_key_1]).to eq('extra value 1')
+ end
+
+ described_class.definition('filename', category: nil, action: nil, label: 'label', property: '...',
+ project: project, user: user, namespace: namespace, extra_key_1: 'extra value 1')
+ end
+ end
end
diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb
index a7083bd2722..afb44c0d298 100644
--- a/spec/lib/gitlab/view/presenter/base_spec.rb
+++ b/spec/lib/gitlab/view/presenter/base_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::View::Presenter::Base do
let(:project) { double(:project) }
let(:presenter_class) do
- Struct.new(:subject).include(described_class)
+ Struct.new(:__subject__).include(described_class)
end
describe '.presenter?' do
@@ -17,17 +17,24 @@ RSpec.describe Gitlab::View::Presenter::Base do
end
describe '.presents' do
- it 'exposes #subject with the given keyword' do
- presenter_class.presents(Object, as: :foo)
- presenter = presenter_class.new(project)
-
- expect(presenter.foo).to eq(project)
- end
-
it 'raises an error when symbol is passed' do
expect { presenter_class.presents(:foo) }.to raise_error(ArgumentError)
end
+ context 'when the presenter class specifies a custom keyword' do
+ subject(:presenter) { presenter_class.new(project) }
+
+ before do
+ presenter_class.class_eval do
+ presents Object, as: :foo
+ end
+ end
+
+ it 'exposes the subject with the given keyword' do
+ expect(presenter.foo).to be(project)
+ end
+ end
+
context 'when the presenter class inherits Presenter::Delegated' do
let(:presenter_class) do
Class.new(::Gitlab::View::Presenter::Delegated) do
@@ -50,13 +57,22 @@ RSpec.describe Gitlab::View::Presenter::Base do
end
it 'does not set the delegator target' do
- expect(presenter_class).not_to receive(:delegator_target).with(Object)
+ expect(presenter_class).not_to receive(:delegator_target)
presenter_class.presents(Object, as: :foo)
end
end
end
+ describe '#__subject__' do
+ it 'returns the subject' do
+ subject = double
+ presenter = presenter_class.new(subject)
+
+ expect(presenter.__subject__).to be(subject)
+ end
+ end
+
describe '#can?' do
context 'user is not allowed' do
it 'returns false' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index c6a27b4b687..45b51d5bf44 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1168,6 +1168,28 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
end
+
+ context 'without a commit' do
+ let(:pipeline) { build(:ci_empty_pipeline, :created, sha: nil) }
+
+ it 'does not expose commit variables' do
+ expect(subject.to_hash.keys)
+ .not_to include(
+ 'CI_COMMIT_SHA',
+ 'CI_COMMIT_SHORT_SHA',
+ 'CI_COMMIT_BEFORE_SHA',
+ 'CI_COMMIT_REF_NAME',
+ 'CI_COMMIT_REF_SLUG',
+ 'CI_COMMIT_BRANCH',
+ 'CI_COMMIT_TAG',
+ 'CI_COMMIT_MESSAGE',
+ 'CI_COMMIT_TITLE',
+ 'CI_COMMIT_DESCRIPTION',
+ 'CI_COMMIT_REF_PROTECTED',
+ 'CI_COMMIT_TIMESTAMP',
+ 'CI_COMMIT_AUTHOR')
+ end
+ end
end
describe '#protected_ref?' do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 12da5de5ef1..b38135fc0b2 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -993,33 +993,6 @@ RSpec.describe Issuable do
end
end
- describe '#sync_escalation_attributes_from_alert?' do
- where(:issuable_type, :args, :sync_escalation_attributes_from_alert) do
- :issue | {} | false
- :issue | ref(:alert_args) | false
- :incident | {} | false
- :incident | ref(:alert_args) | true
- :merge_request | {} | false
- end
-
- with_them do
- let(:alert_args) { { alert_management_alert: build_stubbed(:alert_management_alert) } }
- let(:issuable) { build_stubbed(issuable_type, **args) }
-
- subject { issuable.sync_escalation_attributes_from_alert? }
-
- it { is_expected.to eq(false) }
-
- context 'with feature disabled' do
- before do
- stub_feature_flags(incident_escalations: false)
- end
-
- it { is_expected.to eq(sync_escalation_attributes_from_alert) }
- end
- end
- end
-
describe '#incident?' do
where(:issuable_type, :incident) do
:issue | false
diff --git a/spec/presenters/ci/bridge_presenter_spec.rb b/spec/presenters/ci/bridge_presenter_spec.rb
index 6291c3426e2..bd6c4777d0c 100644
--- a/spec/presenters/ci/bridge_presenter_spec.rb
+++ b/spec/presenters/ci/bridge_presenter_spec.rb
@@ -3,9 +3,10 @@
require 'spec_helper'
RSpec.describe Ci::BridgePresenter do
+ let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
- let_it_be(:bridge) { create(:ci_bridge, pipeline: pipeline, status: :failed) }
+ let_it_be(:bridge) { create(:ci_bridge, pipeline: pipeline, status: :failed, user: user) }
subject(:presenter) do
described_class.new(bridge)
@@ -14,4 +15,10 @@ RSpec.describe Ci::BridgePresenter do
it 'presents information about recoverable state' do
expect(presenter).to be_recoverable
end
+
+ it 'presents the detailed status for the user' do
+ expect(bridge).to receive(:detailed_status).with(user)
+
+ presenter.detailed_status
+ end
end
diff --git a/spec/requests/api/graphql/mutations/boards/create_spec.rb b/spec/requests/api/graphql/mutations/boards/create_spec.rb
index 22d05f36f0f..ca848c0c92f 100644
--- a/spec/requests/api/graphql/mutations/boards/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/boards/create_spec.rb
@@ -4,6 +4,16 @@ require 'spec_helper'
RSpec.describe Mutations::Boards::Create do
let_it_be(:parent) { create(:project) }
+ let_it_be(:current_user, reload: true) { create(:user) }
+
+ let(:name) { 'board name' }
+ let(:mutation) { graphql_mutation(:create_board, params) }
+
+ subject { post_graphql_mutation(mutation, current_user: current_user) }
+
+ def mutation_response
+ graphql_mutation_response(:create_board)
+ end
let(:project_path) { parent.full_path }
let(:params) do
diff --git a/spec/requests/api/group_export_spec.rb b/spec/requests/api/group_export_spec.rb
index 31eef21654a..ffa313d4464 100644
--- a/spec/requests/api/group_export_spec.rb
+++ b/spec/requests/api/group_export_spec.rb
@@ -30,76 +30,62 @@ RSpec.describe API::GroupExport do
group.add_owner(user)
end
- context 'group_import_export feature flag enabled' do
+ context 'when export file exists' do
before do
- stub_feature_flags(group_import_export: true)
-
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
.and_return(0)
- end
-
- context 'when export file exists' do
- before do
- upload.export_file = fixture_file_upload('spec/fixtures/group_export.tar.gz', "`/tar.gz")
- upload.save!
- end
- it 'downloads exported group archive' do
- get api(download_path, user)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
+ upload.export_file = fixture_file_upload('spec/fixtures/group_export.tar.gz', "`/tar.gz")
+ upload.save!
+ end
- context 'when export_file.file does not exist' do
- before do
- expect_next_instance_of(ImportExportUploader) do |uploader|
- expect(uploader).to receive(:file).and_return(nil)
- end
- end
+ it 'downloads exported group archive' do
+ get api(download_path, user)
- it 'returns 404' do
- get api(download_path, user)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
- expect(response).to have_gitlab_http_status(:not_found)
+ context 'when export_file.file does not exist' do
+ before do
+ expect_next_instance_of(ImportExportUploader) do |uploader|
+ expect(uploader).to receive(:file).and_return(nil)
end
end
- context 'when object is not present' do
- let(:other_group) { create(:group, :with_export) }
- let(:other_download_path) { "/groups/#{other_group.id}/export/download" }
+ it 'returns 404' do
+ get api(download_path, user)
- before do
- other_group.add_owner(user)
- other_group.export_file.file.delete
- end
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
- it 'returns 404' do
- get api(other_download_path, user)
+ context 'when object is not present' do
+ let(:other_group) { create(:group, :with_export) }
+ let(:other_download_path) { "/groups/#{other_group.id}/export/download" }
- expect(response).to have_gitlab_http_status(:not_found)
- expect(json_response['message']).to eq('The group export file is not available yet')
- end
+ before do
+ other_group.add_owner(user)
+ other_group.export_file.file.delete
end
- end
- context 'when export file does not exist' do
it 'returns 404' do
- get api(download_path, user)
+ get api(other_download_path, user)
expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('The group export file is not available yet')
end
end
end
- context 'group_import_export feature flag disabled' do
- before do
- stub_feature_flags(group_import_export: false)
- end
-
- it 'responds with 404 Not Found' do
+ context 'when export file does not exist' do
+ it 'returns 404' do
get api(download_path, user)
+ allow(Gitlab::ApplicationRateLimiter)
+ .to receive(:increment)
+ .and_return(0)
+
expect(response).to have_gitlab_http_status(:not_found)
end
end
@@ -122,58 +108,40 @@ RSpec.describe API::GroupExport do
end
describe 'POST /groups/:group_id/export' do
- context 'group_import_export feature flag enabled' do
+ context 'when user is a group owner' do
before do
- stub_feature_flags(group_import_export: true)
+ group.add_owner(user)
end
- context 'when user is a group owner' do
- before do
- group.add_owner(user)
- end
-
- it 'accepts download' do
- post api(path, user)
+ it 'accepts download' do
+ post api(path, user)
- expect(response).to have_gitlab_http_status(:accepted)
- end
+ expect(response).to have_gitlab_http_status(:accepted)
end
+ end
- context 'when the export cannot be started' do
- before do
- group.add_owner(user)
- allow(GroupExportWorker).to receive(:perform_async).and_return(nil)
- end
-
- it 'returns an error' do
- post api(path, user)
-
- expect(response).to have_gitlab_http_status(:error)
- end
+ context 'when the export cannot be started' do
+ before do
+ group.add_owner(user)
+ allow(GroupExportWorker).to receive(:perform_async).and_return(nil)
end
- context 'when user is not a group owner' do
- before do
- group.add_developer(user)
- end
-
- it 'forbids the request' do
- post api(path, user)
+ it 'returns an error' do
+ post api(path, user)
- expect(response).to have_gitlab_http_status(:forbidden)
- end
+ expect(response).to have_gitlab_http_status(:error)
end
end
- context 'group_import_export feature flag disabled' do
+ context 'when user is not a group owner' do
before do
- stub_feature_flags(group_import_export: false)
+ group.add_developer(user)
end
- it 'responds with 404 Not Found' do
+ it 'forbids the request' do
post api(path, user)
- expect(response).to have_gitlab_http_status(:not_found)
+ expect(response).to have_gitlab_http_status(:forbidden)
end
end
@@ -202,7 +170,6 @@ RSpec.describe API::GroupExport do
let(:status_path) { "/groups/#{group.id}/export_relations/status" }
before do
- stub_feature_flags(group_import_export: true)
group.add_owner(user)
end
diff --git a/spec/requests/import/gitlab_groups_controller_spec.rb b/spec/requests/import/gitlab_groups_controller_spec.rb
index 4abf99cf994..8d5c1e3ebab 100644
--- a/spec/requests/import/gitlab_groups_controller_spec.rb
+++ b/spec/requests/import/gitlab_groups_controller_spec.rb
@@ -155,20 +155,6 @@ RSpec.describe Import::GitlabGroupsController do
end
end
- context 'when group import FF is disabled' do
- let(:request_params) { { path: 'test-group-import', name: 'test-group-import' } }
-
- before do
- stub_feature_flags(group_import_export: false)
- end
-
- it 'returns an error' do
- expect { import_request }.not_to change { Group.count }
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
context 'when the parent group is invalid' do
let(:request_params) { { path: 'test-group-import', name: 'test-group-import', parent_id: -1 } }
diff --git a/spec/services/alert_management/alerts/update_service_spec.rb b/spec/services/alert_management/alerts/update_service_spec.rb
index 0388fe9e25c..882543fd701 100644
--- a/spec/services/alert_management/alerts/update_service_spec.rb
+++ b/spec/services/alert_management/alerts/update_service_spec.rb
@@ -253,51 +253,25 @@ RSpec.describe AlertManagement::Alerts::UpdateService do
end
end
- shared_examples 'updates the incident escalation status with the new alert status' do
- specify do
- expect(::Issues::UpdateService).to receive(:new).once.and_call_original
- expect(described_class).to receive(:new).once.and_call_original
-
- expect { response }.to change { escalation_status&.reload&.acknowledged? }.to(true)
- .and change { alert.reload.acknowledged? }.to(true)
- end
- end
-
it_behaves_like 'does not sync with the incident status'
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(incident_escalations: false)
- end
-
- it_behaves_like 'does not sync with the incident status'
- end
-
context 'when the issue is an incident' do
before do
issue.update!(issue_type: Issue.issue_types[:incident])
end
- context 'when the incident does not have an escalation status' do
- it_behaves_like 'updates the incident escalation status with the new alert status'
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(incident_escalations: false)
- end
-
- it_behaves_like 'updates the incident escalation status with the new alert status'
- end
-
- def escalation_status
- issue.reload.escalation_status
- end
- end
+ it_behaves_like 'does not sync with the incident status'
context 'when the incident has an escalation status' do
let_it_be(:escalation_status, reload: true) { create(:incident_management_issuable_escalation_status, issue: issue) }
- it_behaves_like 'updates the incident escalation status with the new alert status'
+ it 'updates the incident escalation status with the new alert status' do
+ expect(::Issues::UpdateService).to receive(:new).once.and_call_original
+ expect(described_class).to receive(:new).once.and_call_original
+
+ expect { response }.to change { escalation_status.reload.acknowledged? }.to(true)
+ .and change { alert.reload.acknowledged? }.to(true)
+ end
context 'when the statuses match' do
before do
@@ -312,7 +286,7 @@ RSpec.describe AlertManagement::Alerts::UpdateService do
stub_feature_flags(incident_escalations: false)
end
- it_behaves_like 'updates the incident escalation status with the new alert status'
+ it_behaves_like 'does not sync with the incident status'
end
end
end
diff --git a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb
index 0ba26bf2c13..25164df40ca 100644
--- a/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb
+++ b/spec/services/incident_management/issuable_escalation_statuses/prepare_update_service_spec.rb
@@ -48,29 +48,6 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::PrepareUpdateServ
end
it_behaves_like 'availability error response'
-
- context 'with incident associated with alert' do
- let(:alert_status) { :acknowledged }
-
- before do
- create(:alert_management_alert, alert_status, project: issue.project, issue: issue)
- issue.reload
- end
-
- it_behaves_like 'successful response', { status_event: :acknowledge }
-
- context 'when alert status does not match incident status' do
- let(:alert_status) { :triggered }
-
- include_examples 'error response', 'Invalid value was provided for parameters: status'
- end
-
- context 'with a standard issue' do
- let(:issue) { create(:issue) }
-
- it_behaves_like 'availability error response'
- end
- end
end
context 'when user is anonymous' do
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 4b11b3cf40b..6b7b72d83fc 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -98,7 +98,6 @@ RSpec.describe Issues::CreateService do
end
it_behaves_like 'not an incident issue'
- include_examples 'does not call the escalation status CreateService'
context 'when issue is incident type' do
before do
@@ -119,22 +118,12 @@ RSpec.describe Issues::CreateService do
end
it_behaves_like 'incident issue'
- include_examples 'calls the escalation status CreateService'
- context 'when :incident_escalations feature flag is disabled' do
- before do
- stub_feature_flags(incident_escalations: false)
- end
-
- include_examples 'does not call the escalation status CreateService'
+ it 'calls IncidentManagement::Incidents::CreateEscalationStatusService' do
+ expect_next(::IncidentManagement::IssuableEscalationStatuses::CreateService, a_kind_of(Issue))
+ .to receive(:execute)
- context 'with associated alert' do
- before do
- opts.merge!(alert_management_alert: build(:alert_management_alert, project: project))
- end
-
- include_examples 'calls the escalation status CreateService'
- end
+ issue
end
context 'when invalid' do
diff --git a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
index 2e3a3ce6b41..04bb2fb69bb 100644
--- a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
@@ -3,16 +3,6 @@
RSpec.shared_examples 'boards create mutation' do
include GraphqlHelpers
- let_it_be(:current_user, reload: true) { create(:user) }
- let(:name) { 'board name' }
- let(:mutation) { graphql_mutation(:create_board, params) }
-
- subject { post_graphql_mutation(mutation, current_user: current_user) }
-
- def mutation_response
- graphql_mutation_response(:create_board)
- end
-
context 'when the user does not have permission' do
it_behaves_like 'a mutation that returns a top-level access error'
diff --git a/spec/support/shared_examples/services/incident_management/escalation_status_shared_examples.rb b/spec/support/shared_examples/services/incident_management/escalation_status_shared_examples.rb
deleted file mode 100644
index d9fe217921c..00000000000
--- a/spec/support/shared_examples/services/incident_management/escalation_status_shared_examples.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-# This shared_example requires the following variables:
-# - issue (required)
-RSpec.shared_examples 'calls the escalation status CreateService' do
- it 'calls IncidentManagement::Incidents::CreateEscalationStatusService' do
- expect_next(::IncidentManagement::IssuableEscalationStatuses::CreateService, a_kind_of(Issue))
- .to receive(:execute)
-
- issue
- end
-end
-
-# This shared_example requires the following variables:
-# - issue (required)
-RSpec.shared_examples 'does not call the escalation status CreateService' do
- it 'does not call the ::IncidentManagement::IssuableEscalationStatuses::CreateService' do
- expect(::IncidentManagement::IssuableEscalationStatuses::CreateService).not_to receive(:new)
-
- issue
- end
-end
diff --git a/yarn.lock b/yarn.lock
index 0544a4aa9e1..e269a660d42 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1005,15 +1005,15 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.1.0"
-"@gitlab/svgs@2.6.0":
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.6.0.tgz#49f020b0a732f5df01138bd21b610a0a940badd6"
- integrity sha512-jI8CHlrriePtUsognRkpZQWVsZe7ZjytmKakeYyU1aKvsnJ4fAeySlVkCAiqKbbZm3T/eeH/6b3jxHn24U0k0Q==
+"@gitlab/svgs@2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.8.0.tgz#b32f3672d9cffa2d59f5edb6828ae931a36d220f"
+ integrity sha512-N1D6q5xKze3HwPMjLnsXMZOPQGX4CT+jEQgZYkB8akVx/rqT/YSZ9pZaxWoRdq1Tiwi9B2ArctopRgNiN8fqdw==
-"@gitlab/ui@38.0.1":
- version "38.0.1"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-38.0.1.tgz#73767792f6e27791f84899dbe29b09547ecd8004"
- integrity sha512-y83h2JsFypuvx1P3BFB7iEbusv/tc/jx3CkEEFY9N8/P25Fs3F3wcYChhlYZ2V1VX0wwEHzBV9cvPIGcmAGNMA==
+"@gitlab/ui@38.8.1":
+ version "38.8.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-38.8.1.tgz#f4892ffd51c4cacf30cb94ffca31487afdac2b0c"
+ integrity sha512-eTGt+LODmox1GZRkLfFH9/zl4dG9/6ewpYzXvIo1B4uzHz27lhEgVSM57nZRPXmk4o05LQMnm6eelHoBdMX13g==
dependencies:
"@babel/standalone" "^7.0.0"
bootstrap-vue "2.20.1"
@@ -10781,12 +10781,12 @@ sade@^1.7.3:
dependencies:
mri "^1.1.0"
-safe-buffer@5.1.2, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-safe-buffer@5.2.1:
+safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==