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:
-rw-r--r--.gitlab/issue_templates/Pipeline Security issue implementation.md (renamed from .gitlab/issue_templates/Pipeline Security issue implementation)0
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml33
-rw-r--r--.rubocop_todo/rspec/missing_feature_category.yml2
-rw-r--r--.rubocop_todo/search/namespaced_class.yml4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue32
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue37
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.vue (renamed from app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js)77
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue14
-rw-r--r--app/assets/javascripts/work_items/components/work_item_description.vue9
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue58
-rw-r--r--app/assets/javascripts/work_items/components/work_item_labels.vue9
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item_assignees.subscription.graphql21
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item_description.subscription.graphql20
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item_labels.subscription.graphql19
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item_milestone.subscription.graphql17
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item_title.subscription.graphql8
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item_updated.subscription.graphql7
-rw-r--r--app/models/design_management/repository.rb2
-rw-r--r--app/models/personal_access_token.rb10
-rw-r--r--app/services/projects/destroy_service.rb4
-rw-r--r--config/locales/en.yml2
-rw-r--r--doc/administration/audit_event_streaming/graphql_api.md14
-rw-r--r--doc/administration/geo/setup/index.md4
-rw-r--r--doc/ci/chatops/index.md14
-rw-r--r--doc/ci/runners/saas/linux_saas_runner.md8
-rw-r--r--doc/development/migration_style_guide.md5
-rw-r--r--doc/integration/slash_commands.md54
-rw-r--r--doc/operations/incident_management/slack.md2
-rw-r--r--doc/subscriptions/gitlab_dedicated/index.md2
-rw-r--r--doc/user/admin_area/moderate_users.md4
-rw-r--r--doc/user/profile/achievements.md17
-rw-r--r--doc/user/project/integrations/gitlab_slack_application.md42
-rw-r--r--doc/user/project/integrations/mattermost_slash_commands.md23
-rw-r--r--doc/user/project/integrations/slack_slash_commands.md20
-rw-r--r--locale/gitlab.pot36
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb6
-rw-r--r--spec/features/projects/settings/external_authorization_service_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/monitor_settings_spec.rb7
-rw-r--r--spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb2
-rw-r--r--spec/features/projects/settings/registry_settings_spec.rb2
-rw-r--r--spec/features/projects/show/download_buttons_spec.rb24
-rw-r--r--spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb2
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb22
-rw-r--r--spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb21
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb2
-rw-r--r--spec/features/snippets/spam_snippets_spec.rb2
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb6
-rw-r--r--spec/features/task_lists_spec.rb9
-rw-r--r--spec/features/user_sees_revert_modal_spec.rb2
-rw-r--r--spec/features/users/email_verification_on_login_spec.rb12
-rw-r--r--spec/features/users/login_spec.rb17
-rw-r--r--spec/features/users/overview_spec.rb14
-rw-r--r--spec/frontend/diffs/components/diff_discussions_spec.js16
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap32
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js39
-rw-r--r--spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js46
-rw-r--r--spec/frontend/work_items/components/notes/work_item_note_actions_spec.js36
-rw-r--r--spec/frontend/work_items/components/work_item_description_spec.js12
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js113
-rw-r--r--spec/frontend/work_items/components/work_item_labels_spec.js14
-rw-r--r--spec/frontend/work_items/mock_data.js148
-rw-r--r--spec/frontend/work_items/router_spec.js38
-rw-r--r--spec/migrations/cleanup_bigint_conversion_for_merge_request_metrics_for_self_hosts_spec.rb2
-rw-r--r--spec/models/personal_access_token_spec.rb8
-rw-r--r--spec/services/projects/destroy_service_spec.rb61
-rw-r--r--spec/services/resource_access_tokens/create_service_spec.rb16
-rw-r--r--spec/support/rspec_order_todo.yml2
73 files changed, 544 insertions, 846 deletions
diff --git a/.gitlab/issue_templates/Pipeline Security issue implementation b/.gitlab/issue_templates/Pipeline Security issue implementation.md
index 72807e98012..72807e98012 100644
--- a/.gitlab/issue_templates/Pipeline Security issue implementation
+++ b/.gitlab/issue_templates/Pipeline Security issue implementation.md
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index 66d5737ab9c..826e20fb40f 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -1034,21 +1034,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/elastic/migrate/20220119120500_populate_commit_permissions_in_main_index_spec.rb'
- 'ee/spec/elastic/migrate/20221124090600_add_namespace_ancestry_ids_to_original_index_mapping_spec.rb'
- 'ee/spec/elastic/migrate/20221221110300_backfill_traversal_ids_to_blobs_and_wiki_blobs_spec.rb'
- - 'ee/spec/features/account_recovery_regular_check_spec.rb'
- - 'ee/spec/features/admin/admin_emails_spec.rb'
- - 'ee/spec/features/admin/admin_settings_spec.rb'
- - 'ee/spec/features/billings/billing_plans_spec.rb'
- - 'ee/spec/features/boards/boards_spec.rb'
- - 'ee/spec/features/boards/swimlanes/epics_swimlanes_drag_drop_spec.rb'
- - 'ee/spec/features/burndown_charts_spec.rb'
- - 'ee/spec/features/dashboards/todos_spec.rb'
- - 'ee/spec/features/epic_boards/epic_boards_spec.rb'
- - 'ee/spec/features/groups/saml_enforcement_spec.rb'
- - 'ee/spec/features/groups/settings/domain_verification_spec.rb'
- - 'ee/spec/features/groups/settings/protected_environments_spec.rb'
- - 'ee/spec/features/groups/usage_quotas/pipelines_tab_spec.rb'
- - 'ee/spec/features/markdown/metrics_spec.rb'
- - 'ee/spec/features/merge_request/user_sees_approval_widget_spec.rb'
- 'ee/spec/features/namespace_user_cap_reached_alert_spec.rb'
- 'ee/spec/features/projects/environments/environment_spec.rb'
- 'ee/spec/features/projects/environments/environments_spec.rb'
@@ -1120,7 +1105,6 @@ Layout/ArgumentAlignment:
- 'ee/spec/lib/ee/gitlab/scim/provisioning_service_spec.rb'
- 'ee/spec/lib/ee/gitlab/usage/service_ping_report_spec.rb'
- 'ee/spec/lib/ee/gitlab/usage_data_spec.rb'
- - 'ee/spec/lib/elastic/latest/project_wiki_class_proxy_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/data_collector_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/distinct_stage_loader_spec.rb'
- 'ee/spec/lib/gitlab/analytics/cycle_analytics/summary/stage_time_summary_spec.rb'
@@ -1610,23 +1594,6 @@ Layout/ArgumentAlignment:
- 'spec/features/projects/pipelines/pipeline_spec.rb'
- 'spec/features/projects/pipelines/pipelines_spec.rb'
- 'spec/features/projects/releases/user_views_release_spec.rb'
- - 'spec/features/projects/settings/external_authorization_service_settings_spec.rb'
- - 'spec/features/projects/settings/monitor_settings_spec.rb'
- - 'spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb'
- - 'spec/features/projects/settings/registry_settings_spec.rb'
- - 'spec/features/projects/show/download_buttons_spec.rb'
- - 'spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb'
- - 'spec/features/projects/tags/download_buttons_spec.rb'
- - 'spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb'
- - 'spec/features/search/user_searches_for_milestones_spec.rb'
- - 'spec/features/search/user_searches_for_wiki_pages_spec.rb'
- - 'spec/features/snippets/spam_snippets_spec.rb'
- - 'spec/features/snippets/user_creates_snippet_spec.rb'
- - 'spec/features/task_lists_spec.rb'
- - 'spec/features/user_sees_revert_modal_spec.rb'
- - 'spec/features/users/email_verification_on_login_spec.rb'
- - 'spec/features/users/login_spec.rb'
- - 'spec/features/users/overview_spec.rb'
- 'spec/frontend/fixtures/autocomplete.rb'
- 'spec/frontend/fixtures/autocomplete_sources.rb'
- 'spec/frontend/fixtures/environments.rb'
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml
index 4d635a3071c..e3829a97b02 100644
--- a/.rubocop_todo/rspec/missing_feature_category.yml
+++ b/.rubocop_todo/rspec/missing_feature_category.yml
@@ -723,8 +723,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/lib/elastic/latest/issue_config_spec.rb'
- 'ee/spec/lib/elastic/latest/merge_request_config_spec.rb'
- 'ee/spec/lib/elastic/latest/note_config_spec.rb'
- - 'ee/spec/lib/elastic/latest/project_wiki_class_proxy_spec.rb'
- - 'ee/spec/lib/elastic/latest/project_wiki_instance_proxy_spec.rb'
- 'ee/spec/lib/elastic/latest/routing_spec.rb'
- 'ee/spec/lib/elastic/latest/snippet_instance_proxy_spec.rb'
- 'ee/spec/lib/elastic/latest/user_config_spec.rb'
diff --git a/.rubocop_todo/search/namespaced_class.yml b/.rubocop_todo/search/namespaced_class.yml
index ba2dbafb3c7..71067dd4e1c 100644
--- a/.rubocop_todo/search/namespaced_class.yml
+++ b/.rubocop_todo/search/namespaced_class.yml
@@ -108,8 +108,6 @@ Search/NamespacedClass:
- 'ee/lib/elastic/latest/note_instance_proxy.rb'
- 'ee/lib/elastic/latest/project_class_proxy.rb'
- 'ee/lib/elastic/latest/project_instance_proxy.rb'
- - 'ee/lib/elastic/latest/project_wiki_class_proxy.rb'
- - 'ee/lib/elastic/latest/project_wiki_instance_proxy.rb'
- 'ee/lib/elastic/latest/query_context.rb'
- 'ee/lib/elastic/latest/repository_class_proxy.rb'
- 'ee/lib/elastic/latest/repository_instance_proxy.rb'
@@ -142,8 +140,6 @@ Search/NamespacedClass:
- 'ee/lib/elastic/v12p1/note_instance_proxy.rb'
- 'ee/lib/elastic/v12p1/project_class_proxy.rb'
- 'ee/lib/elastic/v12p1/project_instance_proxy.rb'
- - 'ee/lib/elastic/v12p1/project_wiki_class_proxy.rb'
- - 'ee/lib/elastic/v12p1/project_wiki_instance_proxy.rb'
- 'ee/lib/elastic/v12p1/repository_class_proxy.rb'
- 'ee/lib/elastic/v12p1/repository_instance_proxy.rb'
- 'ee/lib/elastic/v12p1/routing.rb'
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue
index 6655af92a55..c38c253564a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/action_buttons.vue
@@ -23,7 +23,7 @@ export default {
default: () => [],
},
},
- data: () => {
+ data() {
return {
timeout: null,
updatingTooltip: false,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue
index 334fc01c9f7..258fa4edcda 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/app.vue
@@ -5,16 +5,24 @@ export default {
import(
'~/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue'
),
+
+ MrTerraformWidget: () => import('~/vue_merge_request_widget/extensions/terraform/index.vue'),
},
+
props: {
mr: {
type: Object,
required: true,
},
},
+
computed: {
+ terraformPlansWidget() {
+ return this.mr.terraformReportsPath && 'MrTerraformWidget';
+ },
+
widgets() {
- return ['MrSecurityWidget'];
+ return [this.terraformPlansWidget, 'MrSecurityWidget'].filter((w) => w);
},
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
index cdce7c6625a..ec979861283 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/dynamic_content.vue
@@ -30,6 +30,11 @@ export default {
required: false,
default: 2,
},
+ rowIndex: {
+ type: Number,
+ required: false,
+ default: -1,
+ },
},
computed: {
statusIcon() {
@@ -44,6 +49,9 @@ export default {
generatedSupportingText() {
return generateText(this.data.supportingText);
},
+ shouldShowThirdLevel() {
+ return this.data.children?.length > 0 && this.level === 2;
+ },
},
methods: {
onClickedAction(action) {
@@ -60,16 +68,19 @@ export default {
:widget-name="widgetName"
:header="data.header"
:help-popover="data.helpPopover"
+ :class="{ 'gl-border-top-0': rowIndex === 0 }"
>
<template #body>
- <div class="gl-display-flex gl-flex-direction-column">
- <div>
- <p v-safe-html="generatedText" class="gl-mb-0"></p>
- <gl-link v-if="data.link" :href="data.link.href">{{ data.link.text }}</gl-link>
- <p v-if="data.supportingText" v-safe-html="generatedSupportingText" class="gl-mb-0"></p>
- <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
- {{ data.badge.text }}
- </gl-badge>
+ <div class="gl-w-full gl-display-flex" :class="{ 'gl-flex-direction-column': level === 1 }">
+ <div class="gl-display-flex gl-flex-grow-1">
+ <div class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column">
+ <p v-safe-html="generatedText" class="gl-mb-0 gl-mr-1"></p>
+ <gl-link v-if="data.link" :href="data.link.href">{{ data.link.text }}</gl-link>
+ <p v-if="data.supportingText" v-safe-html="generatedSupportingText" class="gl-mb-0"></p>
+ <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
+ {{ data.badge.text }}
+ </gl-badge>
+ </div>
<actions
:widget="widgetName"
:tertiary-buttons="data.actions"
@@ -78,10 +89,7 @@ export default {
/>
<p v-if="data.subtext" v-safe-html="generatedSubtext" class="gl-m-0 gl-font-sm"></p>
</div>
- <ul
- v-if="data.children && data.children.length > 0 && level === 2"
- class="gl-m-0 gl-p-0 gl-list-style-none"
- >
+ <ul v-if="shouldShowThirdLevel" class="gl-m-0 gl-p-0 gl-list-style-none">
<li v-for="(childData, index) in data.children" :key="childData.id || index">
<dynamic-content
:data="childData"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
index 54eb15c8ac8..e327d848d8f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue
@@ -10,6 +10,7 @@ import HelpPopover from '~/vue_shared/components/help_popover.vue';
import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
import { EXTENSION_ICONS } from '../../constants';
import { createTelemetryHub } from '../extensions/telemetry';
+import { generateText } from '../extensions/utils';
import ContentRow from './widget_content_row.vue';
import DynamicContent from './dynamic_content.vue';
import StatusIcon from './status_icon.vue';
@@ -72,9 +73,12 @@ export default {
},
// If the summary slot is not used, this value will be used as a fallback.
summary: {
- type: String,
+ type: Object,
required: false,
default: undefined,
+ validator: (s) => {
+ return Boolean(s.title);
+ },
},
// If the content slot is not used, this value will be used as a fallback.
content: {
@@ -154,7 +158,7 @@ export default {
return {
isExpandedForTheFirstTime: true,
isCollapsed: true,
- isLoading: false,
+ isLoading: true,
isLoadingExpandedContent: false,
summaryError: null,
contentError: null,
@@ -162,6 +166,12 @@ export default {
};
},
computed: {
+ generatedSummary() {
+ return generateText(this.summary?.title || '');
+ },
+ generatedSubSummary() {
+ return generateText(this.summary?.subtitle || '');
+ },
collapseButtonLabel() {
return sprintf(this.isCollapsed ? __('Show details') : __('Hide details'));
},
@@ -171,6 +181,9 @@ export default {
hasActionButtons() {
return this.actionButtons.length > 0 || Boolean(this.$scopedSlots['action-buttons']);
},
+ contentWithKeyField() {
+ return this.content?.map((item, index) => ({ ...item, id: item.id || index }));
+ },
},
watch: {
hasError: {
@@ -289,7 +302,7 @@ export default {
<template>
<section class="media-section" data-testid="widget-extension">
- <div class="gl-px-5 gl-pr-4 gl-py-4 gl-align-items-center gl-display-flex">
+ <div class="gl-px-5 gl-pr-4 gl-py-4 gl-display-flex">
<status-icon
:level="1"
:name="widgetName"
@@ -302,7 +315,14 @@ export default {
>
<div class="gl-flex-grow-1" data-testid="widget-extension-top-level-summary">
<span v-if="summaryError">{{ summaryError }}</span>
- <slot v-else name="summary">{{ isLoading ? loadingText : summary }}</slot>
+ <slot v-else name="summary"
+ ><div v-safe-html="isLoading ? loadingText : generatedSummary"></div>
+ <div
+ v-if="!isLoading && generatedSubSummary"
+ v-safe-html="generatedSubSummary"
+ class="gl-font-sm gl-text-gray-700"
+ ></div
+ ></slot>
</div>
<div class="gl-display-flex">
<help-popover
@@ -336,7 +356,7 @@ export default {
</slot>
</div>
<div
- v-if="isCollapsible"
+ v-if="isCollapsible && !isLoading"
class="gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6"
>
<gl-button
@@ -376,8 +396,8 @@ export default {
<div v-else class="gl-w-full">
<slot name="content">
<dynamic-scroller
- v-if="content"
- :items="content"
+ v-if="contentWithKeyField"
+ :items="contentWithKeyField"
:min-item-size="32"
:style="{ maxHeight: '170px' }"
data-testid="dynamic-content-scroller"
@@ -390,6 +410,9 @@ export default {
:data="item"
:widget-name="widgetName"
:level="2"
+ :row-index="index"
+ data-testid="extension-list-item"
+ @clickedAction="onActionClick"
/>
</dynamic-scroller-item>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue b/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue
index 6155a912683..e7d8de97f20 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/security_reports/mr_widget_security_reports.vue
@@ -73,6 +73,9 @@ export default {
hasSecurityReports() {
return this.artifacts.length > 0;
},
+ summary() {
+ return { title: this.$options.i18n.scansHaveRun };
+ },
},
methods: {
handleIsLoading(value) {
@@ -109,7 +112,7 @@ export default {
:widget-name="$options.name"
:is-collapsible="false"
:help-popover="$options.widgetHelpPopover"
- :summary="$options.i18n.scansHaveRun"
+ :summary="summary"
@is-loading="handleIsLoading"
>
<template #action-buttons>
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.vue
index c5cbed4a280..a6d12ed7aec 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.vue
@@ -1,12 +1,29 @@
+<script>
import { __, n__, s__, sprintf } from '~/locale';
import axios from '~/lib/utils/axios_utils';
+import MrWidget from '~/vue_merge_request_widget/components/widget/widget.vue';
import { EXTENSION_ICONS } from '../../constants';
export default {
name: 'WidgetTerraform',
- enablePolling: true,
+ components: {
+ MrWidget,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ terraformData: {
+ collapsed: null,
+ expanded: null,
+ },
+ };
+ },
i18n: {
- label: s__('Terraform|Terraform reports'),
loading: s__('Terraform|Loading Terraform reports...'),
error: s__('Terraform|Failed to load Terraform reports'),
reportGenerated: s__('Terraform|A Terraform report was generated in your pipelines.'),
@@ -23,18 +40,13 @@ export default {
reportErrored: s__('Terraform|Generating the report caused an error.'),
fullLog: __('Full log'),
},
- props: ['terraformReportsPath'],
computed: {
- // Extension computed props
- statusIcon() {
- return EXTENSION_ICONS.warning;
+ terraformReportsPath() {
+ return this.mr.terraformReportsPath;
},
- },
- methods: {
- // Extension methods
- summary({ valid = [], invalid = [] }) {
- let title;
- let subtitle = '';
+
+ summary() {
+ const { valid = [], invalid = [] } = this.terraformData.collapsed || {};
const validText = sprintf(
n__(
@@ -60,20 +72,13 @@ export default {
false,
);
- if (valid.length) {
- title = validText;
- if (invalid.length) {
- subtitle = invalidText;
- }
- } else {
- title = invalidText;
- }
-
return {
- subject: title,
- meta: subtitle,
+ title: valid.length ? validText : invalidText,
+ subtitle: valid.length && invalid.length ? invalidText : undefined,
};
},
+ },
+ methods: {
fetchCollapsedData() {
return axios
.get(this.terraformReportsPath)
@@ -84,6 +89,10 @@ export default {
const formattedData = this.prepareReports(reports);
+ const { valid, invalid } = formattedData;
+ this.terraformData.collapsed = formattedData;
+ this.terraformData.expanded = [...valid, ...invalid];
+
return {
...res,
data: formattedData,
@@ -91,14 +100,10 @@ export default {
})
.catch(() => {
const formattedData = this.prepareReports([{ tf_report_error: 'api_error' }]);
-
+ this.terraformData.collapsed = formattedData;
return { data: formattedData };
});
},
- fetchFullData() {
- const { valid, invalid } = this.collapsedData;
- return Promise.resolve([...valid, ...invalid]);
- },
createReportRow(report, iconName) {
const addNum = Number(report.create);
const changeNum = Number(report.update);
@@ -176,4 +181,20 @@ export default {
return { valid, invalid };
},
},
+
+ WARNING_ICON: EXTENSION_ICONS.warning,
};
+</script>
+
+<template>
+ <mr-widget
+ :error-text="$options.i18n.error"
+ :status-icon-name="$options.WARNING_ICON"
+ :loading-text="$options.i18n.loading"
+ :widget-name="$options.name"
+ :is-collapsible="Boolean(terraformData.collapsed)"
+ :summary="summary"
+ :content="terraformData.expanded"
+ :fetch-collapsed-data="fetchCollapsedData"
+ />
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index af9e303594a..9baa2ed15d6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -55,7 +55,6 @@ import eventHub from './event_hub';
import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variables';
import getStateQuery from './queries/get_state.query.graphql';
import getStateSubscription from './queries/get_state.subscription.graphql';
-import terraformExtension from './extensions/terraform';
import accessibilityExtension from './extensions/accessibility';
import codeQualityExtension from './extensions/code_quality';
import testReportExtension from './extensions/test_report';
@@ -238,9 +237,6 @@ export default {
this.mr.mergePipelinesEnabled && this.mr.sourceProjectId !== this.mr.targetProjectId,
);
},
- shouldRenderTerraformPlans() {
- return Boolean(this.mr?.terraformReportsPath);
- },
shouldRenderTestReport() {
return Boolean(this.mr?.testResultsPath);
},
@@ -292,11 +288,6 @@ export default {
this.initPostMergeDeploymentsPolling();
}
},
- shouldRenderTerraformPlans(newVal) {
- if (newVal) {
- this.registerTerraformPlans();
- }
- },
shouldRenderCodeQuality(newVal) {
if (newVal) {
this.registerCodeQualityExtension();
@@ -546,11 +537,6 @@ export default {
dismissSuggestPipelines() {
this.mr.isDismissedSuggestPipeline = true;
},
- registerTerraformPlans() {
- if (this.shouldRenderTerraformPlans) {
- registerExtension(terraformExtension);
- }
- },
registerAccessibilityExtension() {
if (this.shouldShowAccessibilityReport) {
registerExtension(accessibilityExtension);
diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue
index 61dec21cae4..7e062776f98 100644
--- a/app/assets/javascripts/work_items/components/work_item_description.vue
+++ b/app/assets/javascripts/work_items/components/work_item_description.vue
@@ -9,7 +9,6 @@ import EditedAt from '~/issues/show/components/edited.vue';
import Tracking from '~/tracking';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import { autocompleteDataSources, markdownPreviewPath } from '../utils';
-import workItemDescriptionSubscription from '../graphql/work_item_description.subscription.graphql';
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
import { i18n, TRACKING_CATEGORY_SHOW, WIDGET_TYPE_DESCRIPTION } from '../constants';
@@ -75,14 +74,6 @@ export default {
error() {
this.$emit('error', i18n.fetchError);
},
- subscribeToMore: {
- document: workItemDescriptionSubscription,
- variables() {
- return {
- issuableId: this.workItemId,
- };
- },
- },
},
},
computed: {
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 1ac40fe7dcb..5539226e84e 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -39,10 +39,7 @@ import {
WIDGET_TYPE_NOTES,
} from '../constants';
-import workItemDatesSubscription from '../../graphql_shared/subscriptions/work_item_dates.subscription.graphql';
-import workItemTitleSubscription from '../graphql/work_item_title.subscription.graphql';
-import workItemAssigneesSubscription from '../graphql/work_item_assignees.subscription.graphql';
-import workItemMilestoneSubscription from '../graphql/work_item_milestone.subscription.graphql';
+import workItemUpdatedSubscription from '../graphql/work_item_updated.subscription.graphql';
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
import updateWorkItemTaskMutation from '../graphql/update_work_item_task.mutation.graphql';
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
@@ -165,52 +162,17 @@ export default {
document.title = `${this.workItem.title} · ${this.workItem?.workItemType?.name}${path}`;
}
},
- subscribeToMore: [
- {
- document: workItemTitleSubscription,
- variables() {
- return {
- issuableId: this.workItem.id,
- };
- },
- skip() {
- return !this.workItem?.id;
- },
- },
- {
- document: workItemDatesSubscription,
- variables() {
- return {
- issuableId: this.workItem.id,
- };
- },
- skip() {
- return !this.isWidgetPresent(WIDGET_TYPE_START_AND_DUE_DATE) || !this.workItem?.id;
- },
- },
- {
- document: workItemAssigneesSubscription,
- variables() {
- return {
- issuableId: this.workItem.id,
- };
- },
- skip() {
- return !this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES) || !this.workItem?.id;
- },
+ subscribeToMore: {
+ document: workItemUpdatedSubscription,
+ variables() {
+ return {
+ id: this.workItem.id,
+ };
},
- {
- document: workItemMilestoneSubscription,
- variables() {
- return {
- issuableId: this.workItem.id,
- };
- },
- skip() {
- return !this.isWidgetPresent(WIDGET_TYPE_MILESTONE) || !this.workItem?.id;
- },
+ skip() {
+ return !this.workItem?.id;
},
- ],
+ },
},
},
computed: {
diff --git a/app/assets/javascripts/work_items/components/work_item_labels.vue b/app/assets/javascripts/work_items/components/work_item_labels.vue
index 015c86ba043..662ba880753 100644
--- a/app/assets/javascripts/work_items/components/work_item_labels.vue
+++ b/app/assets/javascripts/work_items/components/work_item_labels.vue
@@ -7,7 +7,6 @@ import labelSearchQuery from '~/sidebar/components/labels/labels_select_widget/g
import LabelItem from '~/sidebar/components/labels/labels_select_widget/label_item.vue';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { isScopedLabel } from '~/lib/utils/common_utils';
-import workItemLabelsSubscription from 'ee_else_ce/work_items/graphql/work_item_labels.subscription.graphql';
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
@@ -87,14 +86,6 @@ export default {
error() {
this.$emit('error', i18n.fetchError);
},
- subscribeToMore: {
- document: workItemLabelsSubscription,
- variables() {
- return {
- issuableId: this.workItemId,
- };
- },
- },
},
searchLabels: {
query: labelSearchQuery,
diff --git a/app/assets/javascripts/work_items/graphql/work_item_assignees.subscription.graphql b/app/assets/javascripts/work_items/graphql/work_item_assignees.subscription.graphql
deleted file mode 100644
index d5b2de8c4c6..00000000000
--- a/app/assets/javascripts/work_items/graphql/work_item_assignees.subscription.graphql
+++ /dev/null
@@ -1,21 +0,0 @@
-#import "~/graphql_shared/fragments/user.fragment.graphql"
-
-subscription issuableAssignees($issuableId: IssuableID!) {
- issuableAssigneesUpdated(issuableId: $issuableId) {
- ... on WorkItem {
- id
- widgets {
- ... on WorkItemWidgetAssignees {
- type
- allowsMultipleAssignees
- canInviteMembers
- assignees {
- nodes {
- ...User
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/javascripts/work_items/graphql/work_item_description.subscription.graphql b/app/assets/javascripts/work_items/graphql/work_item_description.subscription.graphql
deleted file mode 100644
index 4eb3d8067d9..00000000000
--- a/app/assets/javascripts/work_items/graphql/work_item_description.subscription.graphql
+++ /dev/null
@@ -1,20 +0,0 @@
-subscription issuableDescription($issuableId: IssuableID!) {
- issuableDescriptionUpdated(issuableId: $issuableId) {
- ... on WorkItem {
- id
- widgets {
- ... on WorkItemWidgetDescription {
- type
- description
- descriptionHtml
- lastEditedAt
- lastEditedBy {
- id
- name
- webPath
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/javascripts/work_items/graphql/work_item_labels.subscription.graphql b/app/assets/javascripts/work_items/graphql/work_item_labels.subscription.graphql
deleted file mode 100644
index 86d936bf4dd..00000000000
--- a/app/assets/javascripts/work_items/graphql/work_item_labels.subscription.graphql
+++ /dev/null
@@ -1,19 +0,0 @@
-#import "~/graphql_shared/fragments/label.fragment.graphql"
-
-subscription workItemLabels($issuableId: IssuableID!) {
- issuableLabelsUpdated(issuableId: $issuableId) {
- ... on WorkItem {
- id
- widgets {
- ... on WorkItemWidgetLabels {
- type
- labels {
- nodes {
- ...Label
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/javascripts/work_items/graphql/work_item_milestone.subscription.graphql b/app/assets/javascripts/work_items/graphql/work_item_milestone.subscription.graphql
deleted file mode 100644
index f5163003fe5..00000000000
--- a/app/assets/javascripts/work_items/graphql/work_item_milestone.subscription.graphql
+++ /dev/null
@@ -1,17 +0,0 @@
-#import "~/work_items/graphql/milestone.fragment.graphql"
-
-subscription issuableMilestone($issuableId: IssuableID!) {
- issuableMilestoneUpdated(issuableId: $issuableId) {
- ... on WorkItem {
- id
- widgets {
- ... on WorkItemWidgetMilestone {
- type
- milestone {
- ...MilestoneFragment
- }
- }
- }
- }
- }
-}
diff --git a/app/assets/javascripts/work_items/graphql/work_item_title.subscription.graphql b/app/assets/javascripts/work_items/graphql/work_item_title.subscription.graphql
deleted file mode 100644
index 2ac01b79d6f..00000000000
--- a/app/assets/javascripts/work_items/graphql/work_item_title.subscription.graphql
+++ /dev/null
@@ -1,8 +0,0 @@
-subscription issuableTitleUpdated($issuableId: IssuableID!) {
- issuableTitleUpdated(issuableId: $issuableId) {
- ... on WorkItem {
- id
- title
- }
- }
-}
diff --git a/app/assets/javascripts/work_items/graphql/work_item_updated.subscription.graphql b/app/assets/javascripts/work_items/graphql/work_item_updated.subscription.graphql
new file mode 100644
index 00000000000..8b63f46ab28
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/work_item_updated.subscription.graphql
@@ -0,0 +1,7 @@
+#import "./work_item.fragment.graphql"
+
+subscription workItemUpdated($id: WorkItemID!) {
+ workItemUpdated(workItemId: $id) {
+ ...WorkItem
+ }
+}
diff --git a/app/models/design_management/repository.rb b/app/models/design_management/repository.rb
index 39077fdbcb1..7410944e174 100644
--- a/app/models/design_management/repository.rb
+++ b/app/models/design_management/repository.rb
@@ -8,7 +8,7 @@ module DesignManagement
belongs_to :project, inverse_of: :design_management_repository
validates :project, presence: true, uniqueness: true
- delegate :lfs_enabled?, :storage, :repository_storage, to: :project
+ delegate :lfs_enabled?, :storage, :repository_storage, :run_after_commit, to: :project
def repository
::DesignManagement::GitRepository.new(
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 2749404b7b5..af804865b8b 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -99,9 +99,13 @@ class PersonalAccessToken < ApplicationRecord
def expires_at_before_instance_max_expiry_date
return unless expires_at
- if expires_at > MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now
- errors.add(:expires_at, _('must expire in 365 days'))
- end
+ max_expiry_date = Date.current.advance(days: MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS)
+ return unless expires_at > max_expiry_date
+
+ errors.add(
+ :expires_at,
+ format(_("must be before %{expiry_date}"), expiry_date: max_expiry_date)
+ )
end
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 2279ab301dc..a5c12384b59 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -58,6 +58,10 @@ module Projects
unless remove_repository(project.wiki.repository)
raise_error(s_('DeleteProject|Failed to remove wiki repository. Please try again or contact administrator.'))
end
+
+ unless remove_repository(project.design_repository)
+ raise_error(s_('DeleteProject|Failed to remove design repository. Please try again or contact administrator.'))
+ end
end
def trash_relation_repositories!
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 1de1c20259d..0d7110be68a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -25,6 +25,8 @@ en:
member:
user: "The member's email address"
invite_email: "The member's email address"
+ personal_access_token:
+ expires_at: "Expiration date"
project/error_tracking_setting:
token: "Auth Token"
project: "Project"
diff --git a/doc/administration/audit_event_streaming/graphql_api.md b/doc/administration/audit_event_streaming/graphql_api.md
index c1e24fae901..89f6cb22b13 100644
--- a/doc/administration/audit_event_streaming/graphql_api.md
+++ b/doc/administration/audit_event_streaming/graphql_api.md
@@ -481,6 +481,20 @@ Streaming destination is deleted if:
- The returned `errors` object is empty.
- The API responds with `200 OK`.
+To remove an HTTP header, use the GraphQL `auditEventsStreamingInstanceHeadersDestroy` mutation.
+To retrieve the header ID,
+[list all the custom HTTP headers](#list-streaming-destinations) for the instance.
+
+```graphql
+mutation {
+ auditEventsStreamingInstanceHeadersDestroy(input: { headerId: "gid://gitlab/AuditEvents::Streaming::InstanceHeader/<id>" }) {
+ errors
+ }
+}
+```
+
+The header is deleted if the returned `errors` object is empty.
+
### Google Cloud Logging configurations
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/409422) in GitLab 16.1.
diff --git a/doc/administration/geo/setup/index.md b/doc/administration/geo/setup/index.md
index 359f706a8aa..8ac64a963bb 100644
--- a/doc/administration/geo/setup/index.md
+++ b/doc/administration/geo/setup/index.md
@@ -34,8 +34,8 @@ If both Geo sites are based on the [1K reference architecture](../../reference_a
- [Using Linux package PostgreSQL instances](database.md) .
- [Using external PostgreSQL instances](external_database.md)
1. [Configure GitLab](../replication/configuration.md) to set the **primary** and **secondary** sites.
-1. Recommended: [Configure unified URLs](../secondary_proxy/index.md) to use a single, unified URL for all Geo sites.
-1. Optional: [Configure Object storage replication](../../object_storage.md)
+1. Recommended: [Configure unified URLs](../secondary_proxy/index.md#set-up-a-unified-url-for-geo-sites) to use a single, unified URL for all Geo sites.
+1. Optional: [Configure Object storage replication](../replication/object_storage.md)
1. Optional: [Configure a secondary LDAP server](../../auth/ldap/index.md) for the **secondary** sites. See [notes on LDAP](../index.md#ldap).
1. Optional: [Configure Container Registry for the secondary site](../replication/container_registry.md).
1. Follow the [Using a Geo Site](../replication/usage.md) guide.
diff --git a/doc/ci/chatops/index.md b/doc/ci/chatops/index.md
index 7be12d0c0fd..09f2758113f 100644
--- a/doc/ci/chatops/index.md
+++ b/doc/ci/chatops/index.md
@@ -49,18 +49,20 @@ When the job runs:
## Run a CI/CD job
-You can run a CI/CD job from chat with the `/project-name run`
-[slash command](../../integration/slash_commands.md).
-
-Prerequisites:
+Prerequisite:
- You must have at least the Developer role for the project.
-To run a CI/CD job:
+You can run a CI/CD job on the default branch from chat. To run a CI/CD job:
+
+- In the chat client, enter `/<project-name> run <job name> <arguments>` where:
-- In the chat client, enter `/project-name run <job name> <arguments>`.
+ - `<project-name>` is the name of the project.
+ - `<job name>` is the name of the CI/CD job to run.
+ - `<arguments>` is the arguments to pass to the CI/CD job.
ChatOps schedules a pipeline that contains only the specified job.
+Other [slash commands](../../user/project/integrations/gitlab_slack_application.md#slash-commands) are also available.
### Exclude a job from ChatOps
diff --git a/doc/ci/runners/saas/linux_saas_runner.md b/doc/ci/runners/saas/linux_saas_runner.md
index d95fc701056..c340a2b0f89 100644
--- a/doc/ci/runners/saas/linux_saas_runner.md
+++ b/doc/ci/runners/saas/linux_saas_runner.md
@@ -85,12 +85,8 @@ Unlike the normal SaaS runners on Linux, each virtual machine is re-used up to 4
As we want to encourage people to contribute, these runners are free of charge.
-<!--- start_remove The following content will be removed on remove_date: '2023-08-22' -->
-
-## Pre-clone script (removed)
+## Pre-clone script (deprecated)
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/391896) in GitLab 15.9
-and [removed](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29405) in 16.0.
+and [will be removed](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29405) in 17.0.
Use [`pre_get_sources_script`](../../../ci/yaml/index.md#hookspre_get_sources_script) instead.
-
-<!--- end_remove -->
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 3d6fa87d809..0cfdf7fb4ea 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -203,6 +203,11 @@ and explains how to perform them without requiring downtime.
Your migration **must be** reversible. This is very important, as it should
be possible to downgrade in case of a vulnerability or bugs.
+**Note**: On GitLab production environments, if a problem occurs, a roll-forward strategy is used instead of rolling back migrations using `db:rollback`.
+On self-managed instances we advise users to restore the backup which was created before the upgrade process started.
+The `down` method is used primarily in the development environment, for example, when a developer wants to ensure
+their local copy of `structure.sql` file and database are in a consistent state when switching between commits or branches.
+
In your migration, add a comment describing how the reversibility of the
migration was tested.
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index 02c7debc6dc..e36ee164002 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -1,51 +1,11 @@
---
-stage: Manage
-group: Import and Integrate
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+redirect_to: '../user/project/integrations/gitlab_slack_application.md#slash-commands'
+remove_date: '2023-09-19'
---
-# Slash commands in Mattermost and Slack **(FREE)**
+This document was moved to [another location](../user/project/integrations/gitlab_slack_application.md#slash-commands).
-> [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24780) from GitLab Ultimate to GitLab Free in 11.9.
-
-If you want to control and view GitLab content while you're
-working in Slack and Mattermost, you can use slash commands.
-Type the command as a message in your chat client to activate it.
-For Slack, this requires an [integration configuration](../user/project/integrations/slack_slash_commands.md).
-
-Slash commands are scoped to a project and require
-a specified trigger command during configuration.
-You should use the project name as the trigger command.
-
-If you're using the [GitLab for Slack app](../user/project/integrations/gitlab_slack_application.md) for
-your GitLab.com projects, [add the `gitlab` keyword at the beginning of the command](../user/project/integrations/gitlab_slack_application.md#slash-commands)
-(for example, `/gitlab <project-name> issue show <id>`).
-
-Assuming `project-name` is the trigger command, the slash commands are:
-
-| Command | Effect |
-| ------- | ------ |
-| `/project-name help` | Shows all available slash commands. |
-| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>`. |
-| `/project-name issue show <id>` | Shows the issue with ID `<id>`. |
-| `/project-name issue close <id>` | Closes the issue with ID `<id>`. |
-| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>`. |
-| `/project-name issue move <id> to <project>` | Moves the issue with ID `<id>` to `<project>`. |
-| `/project-name issue comment <id> <shift+return> <comment>` | Adds a new comment with comment body `<comment>` to the issue with ID `<id>`. |
-| `/project-name deploy <from> to <to>` | [Deploys](#deploy-command) from the `<from>` environment to the `<to>` environment. |
-| `/project-name run <job name> <arguments>` | Executes the [ChatOps](../ci/chatops/index.md) job `<job name>` on the default branch. |
-
-## Issue commands
-
-You can create a new issue, display issue details, and search up to 5 issues.
-
-## Deploy command
-
-To deploy to an environment, GitLab tries to find a deployment
-manual action in the pipeline.
-
-If there's only one action for a given environment, it is triggered.
-If more than one action is defined, GitLab finds an action
-name that equals the environment name to deploy to.
-
-The command returns an error if no matching action is found.
+<!-- This redirect file can be deleted after 2023-09-19. -->
+<!-- Redirects that point to other docs in the same project expire in three months. -->
+<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
+<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/operations/incident_management/slack.md b/doc/operations/incident_management/slack.md
index b007f253fd8..aa0d057bfd8 100644
--- a/doc/operations/incident_management/slack.md
+++ b/doc/operations/incident_management/slack.md
@@ -37,7 +37,7 @@ Prerequisites:
1. Authorize GitLab to take actions on behalf of your Slack user.
Each user must do this before they can use any of the incident slash commands.
- To start the authorization flow, try executing a non-incident [Slack slash command](../../integration/slash_commands.md),
+ To start the authorization flow, try executing a non-incident [Slack slash command](../../user/project/integrations/gitlab_slack_application.md#slash-commands),
like `/gitlab <project-alias> issue show <id>`.
The `<project-alias>` you select must be a project that has the GitLab for Slack app set up.
For more information, see [issue 377548](https://gitlab.com/gitlab-org/gitlab/-/issues/377548).
diff --git a/doc/subscriptions/gitlab_dedicated/index.md b/doc/subscriptions/gitlab_dedicated/index.md
index f14f38f4717..9bdf089e4ee 100644
--- a/doc/subscriptions/gitlab_dedicated/index.md
+++ b/doc/subscriptions/gitlab_dedicated/index.md
@@ -43,7 +43,7 @@ GitLab Dedicated supports instance-level [SAML OmniAuth](../../integration/saml.
GitLab Dedicated offers public connectivity by default with support for IP allowlists. You can [optionally specify a list of IP addresses](../../administration/dedicated/index.md#ip-allowlist) that can access your GitLab Dedicated instance. Subsequently, when an IP not on the allowlist tries to access your instance the connection is refused.
-Private connectivity via [AWS PrivateLink](https://aws.amazon.com/privatelink/) is also offered as an option. Both [inbound](../../administration/dedicated/index.md#inbound-private-link) and [outbound](../../administration/dedicated/index.md#outbound-private-link) PrivateLinks are supported. When connecting to an internal service running in your VPC over HTTPS via PrivateLink, GitLab Dedicated supports the ability to use a private SSL certificate, which can be provided when [updating your instance configuration](../../administration/dedicated/index.md#custom-certificates).
+Private connectivity via [AWS PrivateLink](https://aws.amazon.com/privatelink/) is also offered as an option. Both [inbound](../../administration/dedicated/index.md#inbound-private-link) and [outbound](../../administration/dedicated/index.md#outbound-private-link) PrivateLinks are supported. When connecting to internal resources over an outbound PrivateLink with non public certificates, you can specify a list of certificates that are trusted by GitLab. These certificates can be provided when [updating your instance configuration](../../administration/dedicated/index.md#custom-certificates).
#### Encryption
diff --git a/doc/user/admin_area/moderate_users.md b/doc/user/admin_area/moderate_users.md
index c357e16c967..fd08c58cbcd 100644
--- a/doc/user/admin_area/moderate_users.md
+++ b/doc/user/admin_area/moderate_users.md
@@ -91,7 +91,7 @@ A blocked user:
- Cannot sign in.
- Cannot access Git repositories or the API.
- Does not receive any notifications from GitLab.
-- Cannot use [slash commands](../../integration/slash_commands.md).
+- Cannot use [slash commands](../../user/project/integrations/gitlab_slack_application.md#slash-commands).
- Does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users).
Personal projects, and group and user history of the blocked user are left intact.
@@ -149,7 +149,7 @@ A deactivated user:
- Cannot access Git repositories or the API.
- Does not receive any notifications from GitLab.
-- Cannot use [slash commands](../../integration/slash_commands.md).
+- Cannot use [slash commands](../../user/project/integrations/gitlab_slack_application.md#slash-commands).
- Does not consume a [seat](../../subscriptions/self_managed/index.md#billable-users).
Personal projects, and group and user history of the deactivated user are left intact.
diff --git a/doc/user/profile/achievements.md b/doc/user/profile/achievements.md
index 4e3248ceb10..31ce72e8423 100644
--- a/doc/user/profile/achievements.md
+++ b/doc/user/profile/achievements.md
@@ -109,6 +109,23 @@ mutation achievementsCreate($file: Upload!) {
}
```
+To supply the avatar file, call the mutation using `curl`:
+
+```shell
+curl 'https://gitlab.com/api/graphql' \
+ -H "Authorization: Bearer <your-pat-token>" \
+ -H "Content-Type: multipart/form-data" \
+ -F operations='{ "query": "mutation ($file: Upload!) { achievementsCreate(input: { namespaceId: \"gid://gitlab/Namespace/<namespace-id>\", name: \"<name>\", description: \"<description>\", avatar: $file }) { achievement { id name description avatarUrl } } }", "variables": { "file": null } }' \
+ -F map='{ "0": ["variables.file"] }' \
+ -F 0='@/path/to/your/file.jpg'
+```
+
+When successful, the response returns the achievement ID:
+
+```shell
+{"data":{"achievementsCreate":{"achievement":{"id":"gid://gitlab/Achievements::Achievement/1","name":"<name>","description":"<description>","avatarUrl":"https://gitlab.com/uploads/-/system/achievements/achievement/avatar/1/file.jpg"}}}}
+```
+
## Update an achievement
You can change the name, description, and avatar of an achievement at any time.
diff --git a/doc/user/project/integrations/gitlab_slack_application.md b/doc/user/project/integrations/gitlab_slack_application.md
index 8790c7213e5..77a08585e08 100644
--- a/doc/user/project/integrations/gitlab_slack_application.md
+++ b/doc/user/project/integrations/gitlab_slack_application.md
@@ -52,43 +52,43 @@ you can also select **Reinstall GitLab for Slack app**.
## Slash commands
-After installing the GitLab for Slack app, everyone in your Slack workspace can use slash commands.
+You can use slash commands to run common GitLab operations. Replace `<project>` with a project full path.
-Replace `<project>` with the project full path, or create a shorter [project alias](#create-a-project-alias-for-slash-commands) for the slash commands.
+**For the GitLab for Slack app**:
-| Command | Effect |
-| ------- | ------ |
+- You must authorize your Slack user on GitLab.com when you run your first slash command.
+- You can [create a shorter project alias](#create-a-project-alias-for-slash-commands) for slash commands.
+
+**For [Slack slash commands](slack_slash_commands.md) on self-managed GitLab, [Mattermost slash commands](mattermost_slash_commands.md), and [ChatOps](../../../ci/chatops/index.md)**, do not add `gitlab` at the beginning of the command.
+
+The following slash commands are available:
+
+| Command | Description |
+| ------- | ----------- |
| `/gitlab help` | Shows all available slash commands. |
-| `/gitlab <project> issue new <title> <shift+return> <description>` | Creates a new issue with the title `<title>` and description `<description>`. |
+| `/gitlab <project> issue new <title>` <kbd>Shift</kbd>+<kbd>Enter</kbd> `<description>` | Creates a new issue with the title `<title>` and description `<description>`. |
| `/gitlab <project> issue show <id>` | Shows the issue with the ID `<id>`. |
| `/gitlab <project> issue close <id>` | Closes the issue with the ID `<id>`. |
-| `/gitlab <project> issue search <query>` | Shows up to 5 issues matching `<query>`. |
+| `/gitlab <project> issue search <query>` | Shows up to five issues matching `<query>`. |
| `/gitlab <project> issue move <id> to <project>` | Moves the issue with the ID `<id>` to `<project>`. |
-| `/gitlab <project> issue comment <id> <shift+return> <comment>` | Adds a new comment with the comment body `<comment>` to the issue with the ID `<id>`. |
+| `/gitlab <project> issue comment <id>` <kbd>Shift</kbd>+<kbd>Enter</kbd> `<comment>` | Adds a new comment with the comment body `<comment>` to the issue with the ID `<id>`. |
| `/gitlab <project> deploy <from> to <to>` | [Deploys](#the-deploy-slash-command) from the `<from>` environment to the `<to>` environment. |
| `/gitlab <project> run <job name> <arguments>` | Executes the [ChatOps](../../../ci/chatops/index.md) job `<job name>` on the default branch. |
-| `/gitlab incident declare` | **Beta** Opens modal to [create a new incident](../../../operations/incident_management/slack.md). |
+| `/gitlab incident declare` | Opens a modal to [create a new incident from Slack](../../../operations/incident_management/slack.md) (Beta). |
-### The deploy slash command
+### The `deploy` slash command
-To deploy to an environment, GitLab tries to find a deployment
-manual action in the pipeline.
+To deploy to an environment, GitLab tries to find a deployment manual action in the pipeline.
-If there's only one action for a given environment, it is triggered.
-If more than one action is defined, GitLab finds an action
-name that matches the environment name to deploy to.
+If only one action is defined for a given environment, it is triggered.
+If more than one action is defined, GitLab tries to find an action name
+that matches the environment name to deploy to.
The command returns an error if no matching action is found.
-### User authorization
-
-When you perform your first slash command, you must
-authorize your Slack user on GitLab.com.
-
### Create a project alias for slash commands
-By default, slash commands expect a project full path. To use a shorter alias
-instead:
+By default, slash commands expect a project full path. To create a shorter project alias in the GitLab for Slack app:
1. Go to your project's home page.
1. Go to **Settings > Integrations** (only visible on GitLab.com).
diff --git a/doc/user/project/integrations/mattermost_slash_commands.md b/doc/user/project/integrations/mattermost_slash_commands.md
index a8d8323a651..6a09a1cfbcd 100644
--- a/doc/user/project/integrations/mattermost_slash_commands.md
+++ b/doc/user/project/integrations/mattermost_slash_commands.md
@@ -6,12 +6,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Mattermost slash commands **(FREE)**
-You can use slash commands to run common GitLab operations, like creating an issue,
-from a [Mattermost](https://mattermost.com/) chat environment.
+You can use [slash commands](gitlab_slack_application.md#slash-commands) to run common GitLab operations,
+like creating an issue, from a [Mattermost](https://mattermost.com/) chat environment.
GitLab can also send events (such as `issue created`) to Mattermost as part of the
separately configured [Mattermost notifications](mattermost.md).
+For a list of available slash commands, see [Slash commands](gitlab_slack_application.md#slash-commands).
+
## Configuration options
GitLab provides different ways to configure Mattermost slash commands. For any of these options,
@@ -109,7 +111,7 @@ Your slash command can now communicate with your GitLab project.
Prerequisite:
-- To run [slash commands](#available-slash-commands), you must have
+- To run [slash commands](gitlab_slack_application.md#slash-commands), you must have
[permission](../../permissions.md#project-members-permissions) to
perform the action in the GitLab project.
@@ -120,21 +122,10 @@ To interact with GitLab using Mattermost slash commands:
You can see all authorized chat accounts in your Mattermost profile page under **Chat**.
-## Available slash commands
-
-The available slash commands for Mattermost are:
-
-| Command | Description | Example |
-| ------- | ----------- | ------- |
-| `/<trigger> issue new <title>` <kbd>Shift</kbd>+<kbd>Enter</kbd> `<description>` | Create a new issue in the project that `<trigger>` is tied to. `<description>` is optional. | `/gitlab issue new We need to change the homepage` |
-| `/<trigger> issue show <issue-number>` | Show the issue with ID `<issue-number>` from the project that `<trigger>` is tied to. | `/gitlab issue show 42` |
-| `/<trigger> deploy <environment> to <environment>` | Start the CI/CD job that deploys from one environment to another (for example, `staging` to `production`). CI/CD must be [properly configured](../../../ci/yaml/index.md). | `/gitlab deploy staging to production` |
-| `/<trigger> help` | View a list of available slash commands. | `/gitlab help` |
-
## Related topics
-- [Mattermost slash commands](https://developers.mattermost.com/integrate/slash-commands/)
-- [Linux package Mattermost](../../../integration/mattermost/index.md)
+- [Mattermost Linux package](../../../integration/mattermost/index.md)
+- [Slash commands at Mattermost](https://developers.mattermost.com/integrate/slash-commands/)
## Troubleshooting
diff --git a/doc/user/project/integrations/slack_slash_commands.md b/doc/user/project/integrations/slack_slash_commands.md
index 74bd12561fb..c1e04f2aa63 100644
--- a/doc/user/project/integrations/slack_slash_commands.md
+++ b/doc/user/project/integrations/slack_slash_commands.md
@@ -10,16 +10,18 @@ NOTE:
This feature is only configurable on self-managed GitLab instances.
For GitLab.com, use the [GitLab for Slack app](gitlab_slack_application.md) instead.
-If you want to control and view GitLab content while you're
-working in Slack, you can use Slack slash commands.
-To use Slack slash commands, you must configure both Slack and GitLab.
+You can use [slash commands](gitlab_slack_application.md#slash-commands) to run common GitLab operations,
+like creating an issue, from a [Slack](https://slack.com/) chat environment.
+To use slash commands in Slack, you must configure both Slack and GitLab.
-GitLab can also send events (for example, `issue created`) to Slack as notifications.
-The [Slack notifications integration](slack.md) is configured separately.
+GitLab can also send events (such as `issue created`) to Slack as part of the
+separately configured [Slack notifications](slack.md).
-## Configure GitLab and Slack
+For a list of available slash commands, see [Slash commands](gitlab_slack_application.md#slash-commands).
-Slack slash command integrations are scoped to a project.
+## Configure the integration
+
+Slack slash commands are scoped to a project. To configure Slack slash commands:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. Select **Settings > Integrations**.
@@ -35,7 +37,3 @@ Slack slash command integrations are scoped to a project.
1. On the Slack configuration page, select **Save Integration** and copy the **Token**.
1. Go back to the GitLab configuration page and paste in the **Token**.
1. Ensure the **Active** checkbox is selected and select **Save changes**.
-
-## Slash commands
-
-You can now use the available [Slack slash commands](../../../integration/slash_commands.md).
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2bf093bdfda..7d92f9e38b3 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6288,6 +6288,9 @@ msgstr ""
msgid "Artifacts|We want you to be able to use this page to easily manage your CI/CD job artifacts. We are working to improve this experience and would appreciate any feedback you have about the improvements we are making."
msgstr ""
+msgid "As this is a newly created account, to get started, click the link below to confirm your account."
+msgstr ""
+
msgid "As we continue to build more features for SAST, we'd love your feedback on the SAST configuration feature in %{linkStart}this issue%{linkEnd}."
msgstr ""
@@ -8567,6 +8570,9 @@ msgstr ""
msgid "By quarter"
msgstr ""
+msgid "By using a primary email tied to an Enterprise e-mail address, it is understood that this account is an Enterprise User."
+msgstr ""
+
msgid "By week"
msgstr ""
@@ -14934,6 +14940,9 @@ msgstr ""
msgid "Delete video"
msgstr ""
+msgid "DeleteProject|Failed to remove design repository. Please try again or contact administrator."
+msgstr ""
+
msgid "DeleteProject|Failed to remove events. Please try again or contact administrator."
msgstr ""
@@ -17288,6 +17297,12 @@ msgstr ""
msgid "Enterprise"
msgstr ""
+msgid "Enterprise User"
+msgstr ""
+
+msgid "Enterprise User Account on GitLab"
+msgstr ""
+
msgid "EnterpriseUsers|The user detail cannot be updated"
msgstr ""
@@ -17297,9 +17312,6 @@ msgstr ""
msgid "EnterpriseUsers|The user is already an enterprise user of the group"
msgstr ""
-msgid "EnterpriseUsers|The user is not a member of the group"
-msgstr ""
-
msgid "Environment"
msgstr ""
@@ -19477,6 +19489,9 @@ msgstr ""
msgid "For individual use, create a separate account under your personal email address, not tied to the Enterprise email domain or group."
msgstr ""
+msgid "For individual use, create a separate account under your personal email address, not tied to the Enterprise email domain."
+msgstr ""
+
msgid "For investigating IT service disruptions or outages"
msgstr ""
@@ -45536,9 +45551,6 @@ msgstr ""
msgid "Terraform|Terraform init command"
msgstr ""
-msgid "Terraform|Terraform reports"
-msgstr ""
-
msgid "Terraform|Terraform states"
msgstr ""
@@ -53072,6 +53084,12 @@ msgstr ""
msgid "Your GitLab account has been locked due to an excessive number of unsuccessful sign in attempts. You can wait for your account to automatically unlock in %{duration} or you can click the link below to unlock now."
msgstr ""
+msgid "Your GitLab account is now an %{source_link}:"
+msgstr ""
+
+msgid "Your GitLab account is now an Enterprise User (%{source_link}):"
+msgstr ""
+
msgid "Your GitLab account request has been approved!"
msgstr ""
@@ -55035,6 +55053,9 @@ msgstr ""
msgid "must be at least 1 day"
msgstr ""
+msgid "must be before %{expiry_date}"
+msgstr ""
+
msgid "must be greater than start date"
msgstr ""
@@ -55068,9 +55089,6 @@ msgstr ""
msgid "must contain only a discord user ID."
msgstr ""
-msgid "must expire in 365 days"
-msgstr ""
-
msgid "must have a repository"
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index a06914ca7de..c907240333f 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -3,7 +3,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa', '~> 11', '>= 11.2.0', require: 'gitlab/qa'
-gem 'gitlab_quality-test_tooling', '~> 0.8.1', require: false
+gem 'gitlab_quality-test_tooling', '~> 0.8.2', require: false
gem 'activesupport', '~> 6.1.7.2' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.20.0'
gem 'capybara', '~> 3.39.2'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index e4e89e7e2e1..94eae5f80bb 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -111,7 +111,7 @@ GEM
rainbow (>= 3, < 4)
table_print (= 1.5.7)
zeitwerk (>= 2, < 3)
- gitlab_quality-test_tooling (0.8.1)
+ gitlab_quality-test_tooling (0.8.2)
activesupport (>= 6.1, < 7.1)
gitlab (~> 4.19)
http (~> 5.0)
@@ -324,7 +324,7 @@ DEPENDENCIES
fog-core (= 2.1.0)
fog-google (~> 1.19)
gitlab-qa (~> 11, >= 11.2.0)
- gitlab_quality-test_tooling (~> 0.8.1)
+ gitlab_quality-test_tooling (~> 0.8.2)
influxdb-client (~> 2.9)
knapsack (~> 4.0)
nokogiri (~> 1.15, >= 1.15.2)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb
index 82074919ad4..1d9dccbddf6 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb
@@ -2,11 +2,7 @@
module QA
RSpec.describe 'Create' do
- describe 'Branch Rules Overview', product_group: :source_code,
- quarantine: {
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/403583',
- type: :flaky
- } do
+ describe 'Branch Rules Overview', product_group: :source_code do
let(:branch_name) { 'new-branch' }
let(:allowed_to_push_role) { Resource::ProtectedBranch::Roles::NO_ONE }
let(:allowed_to_merge_role) { Resource::ProtectedBranch::Roles::MAINTAINERS }
diff --git a/spec/features/projects/settings/external_authorization_service_settings_spec.rb b/spec/features/projects/settings/external_authorization_service_settings_spec.rb
index 4a56e6c8bbf..4214e6fc767 100644
--- a/spec/features/projects/settings/external_authorization_service_settings_spec.rb
+++ b/spec/features/projects/settings/external_authorization_service_settings_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Projects > Settings > External Authorization Classification Label setting',
-feature_category: :groups_and_projects do
+ feature_category: :groups_and_projects do
let(:user) { create(:user) }
let(:project) { create(:project_empty_repo) }
diff --git a/spec/features/projects/settings/monitor_settings_spec.rb b/spec/features/projects/settings/monitor_settings_spec.rb
index c5a5826a778..b46451f4255 100644
--- a/spec/features/projects/settings/monitor_settings_spec.rb
+++ b/spec/features/projects/settings/monitor_settings_spec.rb
@@ -18,8 +18,11 @@ RSpec.describe 'Projects > Settings > For a forked project', :js, feature_catego
visit project_path(project)
wait_for_requests
- expect(page).to have_selector('.sidebar-sub-level-items a[aria-label="Error Tracking"]',
- text: 'Error Tracking', visible: :hidden)
+ expect(page).to have_selector(
+ '.sidebar-sub-level-items a[aria-label="Error Tracking"]',
+ text: 'Error Tracking',
+ visible: :hidden
+ )
end
end
diff --git a/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb b/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb
index 50693dda685..1ab88ec0fff 100644
--- a/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb
+++ b/spec/features/projects/settings/registry_settings_cleanup_tags_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Project > Settings > Packages and registries > Container registry tag expiration policy',
-feature_category: :groups_and_projects do
+ feature_category: :groups_and_projects do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) }
diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb
index b8016a5d2df..9df82e447aa 100644
--- a/spec/features/projects/settings/registry_settings_spec.rb
+++ b/spec/features/projects/settings/registry_settings_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Project > Settings > Packages and registries > Container registry tag expiration policy',
-feature_category: :groups_and_projects do
+ feature_category: :groups_and_projects do
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) }
diff --git a/spec/features/projects/show/download_buttons_spec.rb b/spec/features/projects/show/download_buttons_spec.rb
index a4df6a56e02..8e27b4b2ede 100644
--- a/spec/features/projects/show/download_buttons_spec.rb
+++ b/spec/features/projects/show/download_buttons_spec.rb
@@ -9,18 +9,24 @@ RSpec.describe 'Projects > Show > Download buttons', feature_category: :groups_a
let(:project) { create(:project, :repository) }
let(:pipeline) do
- create(:ci_pipeline,
- project: project,
- sha: project.commit.sha,
- ref: project.default_branch,
- status: status)
+ create(
+ :ci_pipeline,
+ project: project,
+ sha: project.commit.sha,
+ ref: project.default_branch,
+ status: status
+ )
end
let!(:build) do
- create(:ci_build, :success, :artifacts,
- pipeline: pipeline,
- status: pipeline.status,
- name: 'build')
+ create(
+ :ci_build,
+ :success,
+ :artifacts,
+ pipeline: pipeline,
+ status: pipeline.status,
+ name: 'build'
+ )
end
before do
diff --git a/spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb b/spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb
index 997a804e6ac..98714da34f2 100644
--- a/spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb
+++ b/spec/features/projects/show/user_interacts_with_auto_devops_banner_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Project > Show > User interacts with auto devops implicitly enabled banner',
-feature_category: :groups_and_projects do
+ feature_category: :groups_and_projects do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index 570721fc951..275d364f267 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -10,18 +10,22 @@ RSpec.describe 'Download buttons in tags page', feature_category: :source_code_m
let(:project) { create(:project, :repository) }
let(:pipeline) do
- create(:ci_pipeline,
- project: project,
- sha: project.commit(tag).sha,
- ref: tag,
- status: status)
+ create(
+ :ci_pipeline,
+ project: project,
+ sha: project.commit(tag).sha,
+ ref: tag,
+ status: status
+ )
end
let!(:build) do
- create(:ci_build, :success, :artifacts,
- pipeline: pipeline,
- status: pipeline.status,
- name: 'build')
+ create(
+ :ci_build, :success, :artifacts,
+ pipeline: pipeline,
+ status: pipeline.status,
+ name: 'build'
+ )
end
before do
diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
index 79744633d0c..9a772ff8e44 100644
--- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
@@ -9,11 +9,13 @@ RSpec.describe 'Projects > Wiki > User views wiki in project page', feature_cate
context 'when repository is disabled for project' do
let(:project) do
- create(:project,
- :wiki_repo,
- :repository_disabled,
- :merge_requests_disabled,
- :builds_disabled)
+ create(
+ :project,
+ :wiki_repo,
+ :repository_disabled,
+ :merge_requests_disabled,
+ :builds_disabled
+ )
end
context 'when wiki homepage contains a link' do
@@ -37,8 +39,13 @@ RSpec.describe 'Projects > Wiki > User views wiki in project page', feature_cate
context 'when using asciidoc' do
before do
- create(:wiki_page, wiki: project.wiki, title: 'home', content: 'link:other-page[some link]',
- format: :asciidoc)
+ create(
+ :wiki_page,
+ wiki: project.wiki,
+ title: 'home',
+ content: 'link:other-page[some link]',
+ format: :asciidoc
+ )
end
it_behaves_like 'wiki homepage contains a link'
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 238e59be940..7ca7958f61b 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'User searches for milestones', :js, :clean_gitlab_redis_rate_limiting,
-feature_category: :global_search do
+ feature_category: :global_search do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, namespace: user.namespace) }
let_it_be(:milestone1) { create(:milestone, title: 'Foo', project: project) }
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 1d8bdc58ce6..65f262075f9 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'User searches for wiki pages', :js, :clean_gitlab_redis_rate_limiting,
-feature_category: :global_search do
+ feature_category: :global_search do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
let_it_be(:wiki_page) do
diff --git a/spec/features/snippets/spam_snippets_spec.rb b/spec/features/snippets/spam_snippets_spec.rb
index 0e3f96906de..c2ac33a3b8f 100644
--- a/spec/features/snippets/spam_snippets_spec.rb
+++ b/spec/features/snippets/spam_snippets_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'snippet editor with spam', skip: "Will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/217722",
- feature_category: :source_code_management do
+ feature_category: :source_code_management do
include_context 'includes Spam constants'
let_it_be(:user) { create(:user) }
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index f4b6b552d46..090d854081a 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -81,8 +81,10 @@ RSpec.describe 'User creates snippet', :js, feature_category: :source_code_manag
context 'when snippets default visibility level is restricted' do
before do
- stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE],
- default_snippet_visibility: Gitlab::VisibilityLevel::PRIVATE)
+ stub_application_setting(
+ restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE],
+ default_snippet_visibility: Gitlab::VisibilityLevel::PRIVATE
+ )
end
it 'creates a snippet using the lowest available visibility level as default' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 8a9d2ff42d9..beadeab1736 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -137,8 +137,7 @@ RSpec.describe 'Task Lists', :js, feature_category: :team_planning do
describe 'multiple tasks' do
let!(:note) do
- create(:note, note: markdown, noteable: issue,
- project: project, author: user)
+ create(:note, note: markdown, noteable: issue, project: project, author: user)
end
it 'renders for note body' do
@@ -171,8 +170,7 @@ RSpec.describe 'Task Lists', :js, feature_category: :team_planning do
describe 'single incomplete task' do
let!(:note) do
- create(:note, note: single_incomplete_markdown, noteable: issue,
- project: project, author: user)
+ create(:note, note: single_incomplete_markdown, noteable: issue, project: project, author: user)
end
it 'renders for note body' do
@@ -186,8 +184,7 @@ RSpec.describe 'Task Lists', :js, feature_category: :team_planning do
describe 'single complete task' do
let!(:note) do
- create(:note, note: single_complete_markdown, noteable: issue,
- project: project, author: user)
+ create(:note, note: single_complete_markdown, noteable: issue, project: project, author: user)
end
it 'renders for note body' do
diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb
index aca32d26bdb..2b0ff2cdf01 100644
--- a/spec/features/user_sees_revert_modal_spec.rb
+++ b/spec/features/user_sees_revert_modal_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'Merge request > User sees revert modal', :js, :sidekiq_might_not_need_inline,
-feature_category: :code_review_workflow do
+ feature_category: :code_review_workflow do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project) }
diff --git a/spec/features/users/email_verification_on_login_spec.rb b/spec/features/users/email_verification_on_login_spec.rb
index 481ff52b800..1854e812b73 100644
--- a/spec/features/users/email_verification_on_login_spec.rb
+++ b/spec/features/users/email_verification_on_login_spec.rb
@@ -358,10 +358,12 @@ RSpec.describe 'Email Verification On Login', :clean_gitlab_redis_rate_limiting,
def expect_log_message(event = nil, times = 1, reason: '', message: nil)
expect(Gitlab::AppLogger).to have_received(:info)
.exactly(times).times
- .with(message || hash_including(message: 'Email Verification',
- event: event,
- username: user.username,
- ip: '127.0.0.1',
- reason: reason))
+ .with(message || hash_including(
+ message: 'Email Verification',
+ event: event,
+ username: user.username,
+ ip: '127.0.0.1',
+ reason: reason
+ ))
end
end
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 5529f0fa49e..047590fb3aa 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -390,8 +390,12 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
end
before do
- stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
- providers: [mock_saml_config_with_upstream_two_factor_authn_contexts])
+ stub_omniauth_saml_config(
+ enabled: true,
+ auto_link_saml_user: true,
+ allow_single_sign_on: ['saml'],
+ providers: [mock_saml_config_with_upstream_two_factor_authn_contexts]
+ )
end
it 'displays the remember me checkbox' do
@@ -415,8 +419,10 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
context 'when authn_context is worth two factors' do
let(:mock_saml_response) do
File.read('spec/fixtures/authentication/saml_response.xml')
- .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
- 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS')
+ .gsub(
+ 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
+ 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS'
+ )
end
it 'signs user in without prompting for second factor' do
@@ -991,8 +997,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
context 'when the user already enabled 2FA' do
before do
- user.update!(otp_required_for_login: true,
- otp_secret: User.generate_otp_secret(32))
+ user.update!(otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
end
it 'asks the user to accept the terms' do
diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb
index ff903358931..fdd0c38a718 100644
--- a/spec/features/users/overview_spec.rb
+++ b/spec/features/users/overview_spec.rb
@@ -9,12 +9,14 @@ RSpec.describe 'Overview tab on a user profile', :js, feature_category: :user_pr
def push_code_contribution
event = create(:push_event, project: contributed_project, author: user)
- create(:push_event_payload,
- event: event,
- commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce',
- commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
- commit_count: 3,
- ref: 'master')
+ create(
+ :push_event_payload,
+ event: event,
+ commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce',
+ commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ commit_count: 3,
+ ref: 'master'
+ )
end
before do
diff --git a/spec/frontend/diffs/components/diff_discussions_spec.js b/spec/frontend/diffs/components/diff_discussions_spec.js
index 73d9f2d6d45..40c617da0aa 100644
--- a/spec/frontend/diffs/components/diff_discussions_spec.js
+++ b/spec/frontend/diffs/components/diff_discussions_spec.js
@@ -25,11 +25,13 @@ describe('DiffDiscussions', () => {
});
};
+ const findNoteableDiscussion = () => wrapper.findComponent(NoteableDiscussion);
+
describe('template', () => {
it('should have notes list', () => {
createComponent();
- expect(wrapper.findComponent(NoteableDiscussion).exists()).toBe(true);
+ expect(findNoteableDiscussion().exists()).toBe(true);
expect(wrapper.findComponent(DiscussionNotes).exists()).toBe(true);
expect(
wrapper.findComponent(DiscussionNotes).findAllComponents(TimelineEntryItem).length,
@@ -51,11 +53,11 @@ describe('DiffDiscussions', () => {
it('dispatches toggleDiscussion when clicking collapse button', () => {
createComponent({ shouldCollapseDiscussions: true });
- jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation();
- const diffNotesToggle = findDiffNotesToggle();
- diffNotesToggle.trigger('click');
+ jest.spyOn(store, 'dispatch').mockImplementation();
+
+ findDiffNotesToggle().trigger('click');
- expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('toggleDiscussion', {
+ expect(store.dispatch).toHaveBeenCalledWith('toggleDiscussion', {
discussionId: discussionsMockData.id,
});
});
@@ -77,12 +79,12 @@ describe('DiffDiscussions', () => {
discussions[0].expanded = false;
createComponent({ discussions, shouldCollapseDiscussions: true });
- expect(wrapper.findComponent(NoteableDiscussion).isVisible()).toBe(false);
+ expect(findNoteableDiscussion().isVisible()).toBe(false);
});
it('renders badge on avatar', () => {
createComponent({ renderAvatarBadge: true });
- const noteableDiscussion = wrapper.findComponent(NoteableDiscussion);
+ const noteableDiscussion = findNoteableDiscussion();
expect(noteableDiscussion.find('.design-note-pin').exists()).toBe(true);
expect(noteableDiscussion.find('.design-note-pin').text().trim()).toBe('1');
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
index 296d7924243..02d17b8dfd2 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
+++ b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
@@ -16,14 +16,16 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render
</div>
<div class=\\"gl-display-flex gl-align-items-baseline\\">
<status-icon-stub level=\\"2\\" name=\\"MyWidget\\" iconname=\\"success\\"></status-icon-stub>
- <div class=\\"gl-display-flex gl-flex-direction-column\\">
- <div>
- <p class=\\"gl-mb-0\\">Main text for the row</p>
- <gl-link-stub href=\\"https://gitlab.com\\">Optional link to display after text</gl-link-stub>
- <!---->
- <gl-badge-stub size=\\"md\\" variant=\\"info\\" iconsize=\\"md\\">
- Badge is optional. Text to be displayed inside badge
- </gl-badge-stub>
+ <div class=\\"gl-w-full gl-display-flex\\">
+ <div class=\\"gl-display-flex gl-flex-grow-1\\">
+ <div class=\\"gl-display-flex gl-flex-grow-1 gl-flex-direction-column\\">
+ <p class=\\"gl-mb-0 gl-mr-1\\">Main text for the row</p>
+ <gl-link-stub href=\\"https://gitlab.com\\">Optional link to display after text</gl-link-stub>
+ <!---->
+ <gl-badge-stub size=\\"md\\" variant=\\"info\\" iconsize=\\"md\\">
+ Badge is optional. Text to be displayed inside badge
+ </gl-badge-stub>
+ </div>
<actions-stub widget=\\"MyWidget\\" tertiarybuttons=\\"\\" class=\\"gl-ml-auto gl-pl-3\\"></actions-stub>
<p class=\\"gl-m-0 gl-font-sm\\">Optional: Smaller sub-text to be displayed below the main text</p>
</div>
@@ -40,12 +42,14 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render
</div>
<div class=\\"gl-display-flex gl-align-items-baseline\\">
<!---->
- <div class=\\"gl-display-flex gl-flex-direction-column\\">
- <div>
- <p class=\\"gl-mb-0\\">This is recursive. It will be listed in level 3.</p>
- <!---->
- <!---->
- <!---->
+ <div class=\\"gl-w-full gl-display-flex\\">
+ <div class=\\"gl-display-flex gl-flex-grow-1\\">
+ <div class=\\"gl-display-flex gl-flex-grow-1 gl-flex-direction-column\\">
+ <p class=\\"gl-mb-0 gl-mr-1\\">This is recursive. It will be listed in level 3.</p>
+ <!---->
+ <!---->
+ <!---->
+ </div>
<actions-stub widget=\\"MyWidget\\" tertiarybuttons=\\"\\" class=\\"gl-ml-auto gl-pl-3\\"></actions-stub>
<!---->
</div>
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
index 4972c522733..9343a3a5e90 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
@@ -9,6 +9,7 @@ import ActionButtons from '~/vue_merge_request_widget/components/widget/action_b
import Widget from '~/vue_merge_request_widget/components/widget/widget.vue';
import WidgetContentRow from '~/vue_merge_request_widget/components/widget/widget_content_row.vue';
import * as logger from '~/lib/logger';
+import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
jest.mock('~/vue_merge_request_widget/components/extensions/telemetry', () => ({
@@ -29,7 +30,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
const findHelpPopover = () => wrapper.findComponent(HelpPopover);
const findDynamicScroller = () => wrapper.findByTestId('dynamic-content-scroller');
- const createComponent = ({ propsData, slots, mountFn = shallowMountExtended } = {}) => {
+ const createComponent = async ({ propsData, slots, mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(Widget, {
propsData: {
isCollapsible: false,
@@ -49,6 +50,8 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
ContentRow: WidgetContentRow,
},
});
+
+ await axios.waitForAll();
};
describe('on mount', () => {
@@ -105,9 +108,9 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
},
});
- expect(wrapper.text()).not.toContain('Loading');
- await nextTick();
expect(wrapper.text()).toContain('Loading');
+ await axios.waitForAll();
+ expect(wrapper.text()).not.toContain('Loading');
});
it('validates widget name', () => {
@@ -185,10 +188,10 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
});
describe('content', () => {
- it('displays summary property when summary slot is not provided', () => {
- createComponent({
+ it('displays summary property when summary slot is not provided', async () => {
+ await createComponent({
propsData: {
- summary: 'Hello world',
+ summary: { title: 'Hello world' },
},
});
@@ -256,8 +259,8 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
});
describe('handle collapse toggle', () => {
- it('displays the toggle button correctly', () => {
- createComponent({
+ it('displays the toggle button correctly', async () => {
+ await createComponent({
propsData: {
isCollapsible: true,
},
@@ -271,7 +274,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
});
it('does not display the content slot until toggle is clicked', async () => {
- createComponent({
+ await createComponent({
propsData: {
isCollapsible: true,
},
@@ -286,8 +289,8 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
expect(findExpandedSection().text()).toBe('More complex content');
});
- it('emits a toggle even when button is toggled', () => {
- createComponent({
+ it('emits a toggle even when button is toggled', async () => {
+ await createComponent({
propsData: {
isCollapsible: true,
},
@@ -301,8 +304,8 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
expect(wrapper.emitted('toggle')).toEqual([[{ expanded: true }]]);
});
- it('does not display the toggle button if isCollapsible is false', () => {
- createComponent({
+ it('does not display the toggle button if isCollapsible is false', async () => {
+ await createComponent({
propsData: {
isCollapsible: false,
},
@@ -326,7 +329,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
const fetchExpandedData = jest.fn().mockResolvedValue(mockDataExpanded);
- createComponent({
+ await createComponent({
propsData: {
isCollapsible: true,
fetchCollapsedData: () => Promise.resolve(mockDataCollapsed),
@@ -358,7 +361,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
it('allows refetching when fetch expanded data returns an error', async () => {
const fetchExpandedData = jest.fn().mockRejectedValue({ error: true });
- createComponent({
+ await createComponent({
propsData: {
isCollapsible: true,
fetchExpandedData,
@@ -385,7 +388,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
it('resets the error message when another request is fetched', async () => {
const fetchExpandedData = jest.fn().mockRejectedValue({ error: true });
- createComponent({
+ await createComponent({
propsData: {
isCollapsible: true,
fetchExpandedData,
@@ -465,8 +468,8 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
},
];
- beforeEach(() => {
- createComponent({
+ beforeEach(async () => {
+ await createComponent({
mountFn: mountExtended,
propsData: {
isCollapsible: true,
diff --git a/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js
index 5baed8ff211..6aa12c37374 100644
--- a/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js
+++ b/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js
@@ -5,9 +5,7 @@ import api from '~/api';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import Poll from '~/lib/utils/poll';
-import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container';
-import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
-import terraformExtension from '~/vue_merge_request_widget/extensions/terraform';
+import terraformExtension from '~/vue_merge_request_widget/extensions/terraform/index.vue';
import {
plans,
validPlanWithName,
@@ -25,22 +23,20 @@ describe('Terraform extension', () => {
const endpoint = '/path/to/terraform/report.json';
const findListItem = (at) => wrapper.findAllByTestId('extension-list-item').at(at);
-
- registerExtension(terraformExtension);
+ const findActionButton = (at) => wrapper.findAllByTestId('extension-actions-button').at(at);
const mockPollingApi = (response, body, header) => {
mock.onGet(endpoint).reply(response, body, header);
};
const createComponent = () => {
- wrapper = mountExtended(extensionsContainer, {
+ wrapper = mountExtended(terraformExtension, {
propsData: {
mr: {
terraformReportsPath: endpoint,
},
},
});
- return axios.waitForAll();
};
beforeEach(() => {
@@ -54,24 +50,27 @@ describe('Terraform extension', () => {
describe('summary', () => {
describe('while loading', () => {
const loadingText = 'Loading Terraform reports...';
+
it('should render loading text', async () => {
mockPollingApi(HTTP_STATUS_OK, plans, {});
createComponent();
expect(wrapper.text()).toContain(loadingText);
+
await waitForPromises();
expect(wrapper.text()).not.toContain(loadingText);
});
});
describe('when the fetching fails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockPollingApi(HTTP_STATUS_INTERNAL_SERVER_ERROR, null, {});
- return createComponent();
+ createComponent();
+ await axios.waitForAll();
});
- it('should generate one invalid plan and render correct summary text', () => {
- expect(wrapper.text()).toContain('1 Terraform report failed to generate');
+ it('should show the error text', () => {
+ expect(wrapper.text()).toContain('Failed to load Terraform reports');
});
});
@@ -82,9 +81,10 @@ describe('Terraform extension', () => {
${'2 valid reports'} | ${{ 0: validPlanWithName, 1: validPlanWithName }} | ${'2 Terraform reports were generated in your pipelines'} | ${''}
${'1 valid and 2 invalid reports'} | ${{ 0: validPlanWithName, 1: invalidPlanWithName, 2: invalidPlanWithName }} | ${'Terraform report was generated in your pipelines'} | ${'2 Terraform reports failed to generate'}
`('and received $responseType', ({ response, summaryTitle, summarySubtitle }) => {
- beforeEach(() => {
+ beforeEach(async () => {
mockPollingApi(HTTP_STATUS_OK, response, {});
- return createComponent();
+ createComponent();
+ await axios.waitForAll();
});
it(`should render correct summary text`, () => {
@@ -101,7 +101,8 @@ describe('Terraform extension', () => {
describe('expanded data', () => {
beforeEach(async () => {
mockPollingApi(HTTP_STATUS_OK, plans, {});
- await createComponent();
+ createComponent();
+ await axios.waitForAll();
wrapper.findByTestId('toggle-button').trigger('click');
});
@@ -136,7 +137,7 @@ describe('Terraform extension', () => {
api.trackRedisHllUserEvent.mockClear();
api.trackRedisCounterEvent.mockClear();
- findListItem(0).find('[data-testid="extension-actions-button"]').trigger('click');
+ findActionButton(0).trigger('click');
expect(api.trackRedisHllUserEvent).toHaveBeenCalledTimes(1);
expect(api.trackRedisHllUserEvent).toHaveBeenCalledWith(
@@ -161,10 +162,10 @@ describe('Terraform extension', () => {
});
describe('successful poll', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockPollingApi(HTTP_STATUS_OK, plans, {});
-
- return createComponent();
+ createComponent();
+ await axios.waitForAll();
});
it('does not make additional requests after poll is successful', () => {
@@ -173,13 +174,14 @@ describe('Terraform extension', () => {
});
describe('polling fails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockPollingApi(HTTP_STATUS_INTERNAL_SERVER_ERROR, null, {});
- return createComponent();
+ createComponent();
+ await axios.waitForAll();
});
- it('generates one broken plan', () => {
- expect(wrapper.text()).toContain('1 Terraform report failed to generate');
+ it('renders the error text', () => {
+ expect(wrapper.text()).toContain('Failed to load Terraform reports');
});
it('does not make additional requests after poll is unsuccessful', () => {
diff --git a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js
index 2e901783e07..18d7b2397c9 100644
--- a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js
@@ -1,9 +1,10 @@
import { GlDisclosureDropdown } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective } from 'helpers/vue_mock_directive';
+import { stubComponent } from 'helpers/stub_component';
import EmojiPicker from '~/emoji/components/picker.vue';
import waitForPromises from 'helpers/wait_for_promises';
import ReplyButton from '~/notes/components/note_actions/reply_button.vue';
@@ -15,18 +16,19 @@ Vue.use(VueApollo);
describe('Work Item Note Actions', () => {
let wrapper;
const noteId = '1';
+ const showSpy = jest.fn();
const findReplyButton = () => wrapper.findComponent(ReplyButton);
- const findEditButton = () => wrapper.find('[data-testid="edit-work-item-note"]');
- const findEmojiButton = () => wrapper.find('[data-testid="note-emoji-button"]');
+ const findEditButton = () => wrapper.findByTestId('edit-work-item-note');
+ const findEmojiButton = () => wrapper.findByTestId('note-emoji-button');
const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
- const findDeleteNoteButton = () => wrapper.find('[data-testid="delete-note-action"]');
- const findCopyLinkButton = () => wrapper.find('[data-testid="copy-link-action"]');
- const findAssignUnassignButton = () => wrapper.find('[data-testid="assign-note-action"]');
- const findReportAbuseToAdminButton = () => wrapper.find('[data-testid="abuse-note-action"]');
- const findAuthorBadge = () => wrapper.find('[data-testid="author-badge"]');
- const findMaxAccessLevelBadge = () => wrapper.find('[data-testid="max-access-level-badge"]');
- const findContributorBadge = () => wrapper.find('[data-testid="contributor-badge"]');
+ const findDeleteNoteButton = () => wrapper.findByTestId('delete-note-action');
+ const findCopyLinkButton = () => wrapper.findByTestId('copy-link-action');
+ const findAssignUnassignButton = () => wrapper.findByTestId('assign-note-action');
+ const findReportAbuseToAdminButton = () => wrapper.findByTestId('abuse-note-action');
+ const findAuthorBadge = () => wrapper.findByTestId('author-badge');
+ const findMaxAccessLevelBadge = () => wrapper.findByTestId('max-access-level-badge');
+ const findContributorBadge = () => wrapper.findByTestId('contributor-badge');
const addEmojiMutationResolver = jest.fn().mockResolvedValue({
data: {
@@ -51,7 +53,7 @@ describe('Work Item Note Actions', () => {
maxAccessLevelOfAuthor = '',
projectName = 'Project name',
} = {}) => {
- wrapper = shallowMount(WorkItemNoteActions, {
+ wrapper = shallowMountExtended(WorkItemNoteActions, {
propsData: {
showReply,
showEdit,
@@ -72,15 +74,21 @@ describe('Work Item Note Actions', () => {
},
stubs: {
EmojiPicker: EmojiPickerStub,
+ GlDisclosureDropdown: stubComponent(GlDisclosureDropdown, {
+ methods: { close: showSpy },
+ }),
},
apolloProvider: createMockApollo([[addAwardEmojiMutation, addEmojiMutationResolver]]),
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
});
- wrapper.vm.$refs.dropdown.close = jest.fn();
};
+ afterEach(() => {
+ showSpy.mockClear();
+ });
+
describe('reply button', () => {
it('is visible by default', () => {
createComponent();
@@ -173,6 +181,7 @@ describe('Work Item Note Actions', () => {
findDeleteNoteButton().vm.$emit('action');
expect(wrapper.emitted('deleteNote')).toEqual([[]]);
+ expect(showSpy).toHaveBeenCalled();
});
});
@@ -188,6 +197,7 @@ describe('Work Item Note Actions', () => {
findCopyLinkButton().vm.$emit('action');
expect(wrapper.emitted('notifyCopyDone')).toEqual([[]]);
+ expect(showSpy).toHaveBeenCalled();
});
});
@@ -214,6 +224,7 @@ describe('Work Item Note Actions', () => {
findAssignUnassignButton().vm.$emit('action');
expect(wrapper.emitted('assignUser')).toEqual([[]]);
+ expect(showSpy).toHaveBeenCalled();
});
});
@@ -240,6 +251,7 @@ describe('Work Item Note Actions', () => {
findReportAbuseToAdminButton().vm.$emit('action');
expect(wrapper.emitted('reportAbuse')).toEqual([[]]);
+ expect(showSpy).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js
index b910e9854f8..2b3fdd1b31c 100644
--- a/spec/frontend/work_items/components/work_item_description_spec.js
+++ b/spec/frontend/work_items/components/work_item_description_spec.js
@@ -12,14 +12,12 @@ import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
import WorkItemDescriptionRendered from '~/work_items/components/work_item_description_rendered.vue';
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
-import workItemDescriptionSubscription from '~/work_items/graphql/work_item_description.subscription.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils';
import {
updateWorkItemMutationResponse,
workItemByIidResponseFactory,
- workItemDescriptionSubscriptionResponse,
workItemQueryResponse,
} from '../mock_data';
@@ -34,7 +32,6 @@ describe('WorkItemDescription', () => {
Vue.use(VueApollo);
const mutationSuccessHandler = jest.fn().mockResolvedValue(updateWorkItemMutationResponse);
- const subscriptionHandler = jest.fn().mockResolvedValue(workItemDescriptionSubscriptionResponse);
let workItemResponseHandler;
const findForm = () => wrapper.findComponent(GlForm);
@@ -63,7 +60,6 @@ describe('WorkItemDescription', () => {
apolloProvider: createMockApollo([
[workItemByIidQuery, workItemResponseHandler],
[updateWorkItemMutation, mutationHandler],
- [workItemDescriptionSubscription, subscriptionHandler],
]),
propsData: {
workItemId: id,
@@ -83,14 +79,6 @@ describe('WorkItemDescription', () => {
}
};
- it('has a subscription', async () => {
- await createComponent();
-
- expect(subscriptionHandler).toHaveBeenCalledWith({
- issuableId: workItemQueryResponse.data.workItem.id,
- });
- });
-
describe('editing description', () => {
it('passes correct autocompletion data and preview markdown sources and enables quick actions', async () => {
const {
diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js
index d8ba8ea74f2..0266533a11c 100644
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -31,20 +31,13 @@ import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_sel
import WorkItemTodos from '~/work_items/components/work_item_todos.vue';
import { i18n } from '~/work_items/constants';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
-import workItemDatesSubscription from '~/graphql_shared/subscriptions/work_item_dates.subscription.graphql';
-import workItemTitleSubscription from '~/work_items/graphql/work_item_title.subscription.graphql';
-import workItemAssigneesSubscription from '~/work_items/graphql/work_item_assignees.subscription.graphql';
-import workItemMilestoneSubscription from '~/work_items/graphql/work_item_milestone.subscription.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_task.mutation.graphql';
+import workItemUpdatedSubscription from '~/work_items/graphql/work_item_updated.subscription.graphql';
import {
mockParent,
- workItemDatesSubscriptionResponse,
workItemByIidResponseFactory,
- workItemTitleSubscriptionResponse,
- workItemAssigneesSubscriptionResponse,
- workItemMilestoneSubscriptionResponse,
objectiveType,
mockWorkItemCommentNote,
} from '../mock_data';
@@ -63,16 +56,11 @@ describe('WorkItemDetail component', () => {
canDelete: true,
});
const successHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
- const datesSubscriptionHandler = jest.fn().mockResolvedValue(workItemDatesSubscriptionResponse);
- const titleSubscriptionHandler = jest.fn().mockResolvedValue(workItemTitleSubscriptionResponse);
- const milestoneSubscriptionHandler = jest
- .fn()
- .mockResolvedValue(workItemMilestoneSubscriptionResponse);
- const assigneesSubscriptionHandler = jest
- .fn()
- .mockResolvedValue(workItemAssigneesSubscriptionResponse);
const showModalHandler = jest.fn();
const { id } = workItemQueryResponse.data.workspace.workItems.nodes[0];
+ const workItemUpdatedSubscriptionHandler = jest
+ .fn()
+ .mockResolvedValue({ data: { workItemUpdated: null } });
const findAlert = () => wrapper.findComponent(GlAlert);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
@@ -102,17 +90,13 @@ describe('WorkItemDetail component', () => {
updateInProgress = false,
workItemIid = '1',
handler = successHandler,
- subscriptionHandler = titleSubscriptionHandler,
confidentialityMock = [updateWorkItemMutation, jest.fn()],
error = undefined,
workItemsMvc2Enabled = false,
} = {}) => {
const handlers = [
[workItemByIidQuery, handler],
- [workItemTitleSubscription, subscriptionHandler],
- [workItemDatesSubscription, datesSubscriptionHandler],
- [workItemAssigneesSubscription, assigneesSubscriptionHandler],
- [workItemMilestoneSubscription, milestoneSubscriptionHandler],
+ [workItemUpdatedSubscription, workItemUpdatedSubscriptionHandler],
confidentialityMock,
];
@@ -163,13 +147,18 @@ describe('WorkItemDetail component', () => {
});
describe('when there is no `workItemIid` prop', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent({ workItemIid: null });
+ await waitForPromises();
});
it('skips the work item query', () => {
expect(successHandler).not.toHaveBeenCalled();
});
+
+ it('skips the work item updated subscription', () => {
+ expect(workItemUpdatedSubscriptionHandler).not.toHaveBeenCalled();
+ });
});
describe('when loading', () => {
@@ -203,6 +192,10 @@ describe('WorkItemDetail component', () => {
it('renders todos widget if logged in', () => {
expect(findWorkItemTodos().exists()).toBe(true);
});
+
+ it('calls the work item updated subscription', () => {
+ expect(workItemUpdatedSubscriptionHandler).toHaveBeenCalledWith({ id });
+ });
});
describe('close button', () => {
@@ -487,60 +480,6 @@ describe('WorkItemDetail component', () => {
expect(findAlert().text()).toBe(updateError);
});
-
- describe('subscriptions', () => {
- it('calls the title subscription', async () => {
- createComponent();
- await waitForPromises();
-
- expect(titleSubscriptionHandler).toHaveBeenCalledWith({ issuableId: id });
- });
-
- describe('assignees subscription', () => {
- describe('when the assignees widget exists', () => {
- it('calls the assignees subscription', async () => {
- createComponent();
- await waitForPromises();
-
- expect(assigneesSubscriptionHandler).toHaveBeenCalledWith({ issuableId: id });
- });
- });
-
- describe('when the assignees widget does not exist', () => {
- it('does not call the assignees subscription', async () => {
- const response = workItemByIidResponseFactory({ assigneesWidgetPresent: false });
- const handler = jest.fn().mockResolvedValue(response);
- createComponent({ handler });
- await waitForPromises();
-
- expect(assigneesSubscriptionHandler).not.toHaveBeenCalled();
- });
- });
- });
-
- describe('dates subscription', () => {
- describe('when the due date widget exists', () => {
- it('calls the dates subscription', async () => {
- createComponent();
- await waitForPromises();
-
- expect(datesSubscriptionHandler).toHaveBeenCalledWith({ issuableId: id });
- });
- });
-
- describe('when the due date widget does not exist', () => {
- it('does not call the dates subscription', async () => {
- const response = workItemByIidResponseFactory({ datesWidgetPresent: false });
- const handler = jest.fn().mockResolvedValue(response);
- createComponent({ handler });
- await waitForPromises();
-
- expect(datesSubscriptionHandler).not.toHaveBeenCalled();
- });
- });
- });
- });
-
describe('assignees widget', () => {
it('renders assignees component when widget is returned from the API', async () => {
createComponent();
@@ -617,28 +556,6 @@ describe('WorkItemDetail component', () => {
expect(findWorkItemMilestone().exists()).toBe(exists);
});
-
- describe('milestone subscription', () => {
- describe('when the milestone widget exists', () => {
- it('calls the milestone subscription', async () => {
- createComponent();
- await waitForPromises();
-
- expect(milestoneSubscriptionHandler).toHaveBeenCalledWith({ issuableId: id });
- });
- });
-
- describe('when the assignees widget does not exist', () => {
- it('does not call the milestone subscription', async () => {
- const response = workItemByIidResponseFactory({ milestoneWidgetPresent: false });
- const handler = jest.fn().mockResolvedValue(response);
- createComponent({ handler });
- await waitForPromises();
-
- expect(milestoneSubscriptionHandler).not.toHaveBeenCalled();
- });
- });
- });
});
it('calls the work item query', async () => {
diff --git a/spec/frontend/work_items/components/work_item_labels_spec.js b/spec/frontend/work_items/components/work_item_labels_spec.js
index 6894aa236e3..4a20e654060 100644
--- a/spec/frontend/work_items/components/work_item_labels_spec.js
+++ b/spec/frontend/work_items/components/work_item_labels_spec.js
@@ -6,7 +6,6 @@ import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import labelSearchQuery from '~/sidebar/components/labels/labels_select_widget/graphql/project_labels.query.graphql';
-import workItemLabelsSubscription from 'ee_else_ce/work_items/graphql/work_item_labels.subscription.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
@@ -16,7 +15,6 @@ import {
mockLabels,
workItemByIidResponseFactory,
updateWorkItemMutationResponse,
- workItemLabelsSubscriptionResponse,
} from '../mock_data';
Vue.use(VueApollo);
@@ -38,7 +36,6 @@ describe('WorkItemLabels component', () => {
const successUpdateWorkItemMutationHandler = jest
.fn()
.mockResolvedValue(updateWorkItemMutationResponse);
- const subscriptionHandler = jest.fn().mockResolvedValue(workItemLabelsSubscriptionResponse);
const errorHandler = jest.fn().mockRejectedValue('Houston, we have a problem');
const createComponent = ({
@@ -53,7 +50,6 @@ describe('WorkItemLabels component', () => {
[workItemByIidQuery, workItemQueryHandler],
[labelSearchQuery, searchQueryHandler],
[updateWorkItemMutation, updateWorkItemMutationHandler],
- [workItemLabelsSubscription, subscriptionHandler],
]),
provide: {
fullPath: 'test-project-path',
@@ -246,16 +242,6 @@ describe('WorkItemLabels component', () => {
expect(updateWorkItemMutationHandler).not.toHaveBeenCalled();
});
-
- it('has a subscription', async () => {
- createComponent();
-
- await waitForPromises();
-
- expect(subscriptionHandler).toHaveBeenCalledWith({
- issuableId: workItemId,
- });
- });
});
it('calls the work item query', async () => {
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index a873462ea63..c9c73d29903 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -803,154 +803,6 @@ export const deleteWorkItemMutationErrorResponse = {
},
};
-export const workItemDatesSubscriptionResponse = {
- data: {
- issuableDatesUpdated: {
- id: 'gid://gitlab/WorkItem/1',
- widgets: [
- {
- __typename: 'WorkItemWidgetStartAndDueDate',
- dueDate: '2022-12-31',
- startDate: '2022-01-01',
- },
- ],
- },
- },
-};
-
-export const workItemTitleSubscriptionResponse = {
- data: {
- issuableTitleUpdated: {
- id: 'gid://gitlab/WorkItem/1',
- title: 'new title',
- },
- },
-};
-
-export const workItemDescriptionSubscriptionResponse = {
- data: {
- issuableDescriptionUpdated: {
- id: 'gid://gitlab/WorkItem/1',
- widgets: [
- {
- __typename: 'WorkItemWidgetDescription',
- type: 'DESCRIPTION',
- description: 'New description',
- descriptionHtml: '<p>New description</p>',
- lastEditedAt: '2022-09-21T06:18:42Z',
- lastEditedBy: {
- id: 'gid://gitlab/User/2',
- name: 'Someone else',
- webPath: '/not-you',
- },
- },
- ],
- },
- },
-};
-
-export const workItemWeightSubscriptionResponse = {
- data: {
- issuableWeightUpdated: {
- id: 'gid://gitlab/WorkItem/1',
- widgets: [
- {
- __typename: 'WorkItemWidgetWeight',
- weight: 1,
- },
- ],
- },
- },
-};
-
-export const workItemAssigneesSubscriptionResponse = {
- data: {
- issuableAssigneesUpdated: {
- id: 'gid://gitlab/WorkItem/1',
- widgets: [
- {
- __typename: 'WorkItemAssigneesWeight',
- assignees: {
- nodes: [mockAssignees[0]],
- },
- },
- ],
- },
- },
-};
-
-export const workItemLabelsSubscriptionResponse = {
- data: {
- issuableLabelsUpdated: {
- id: 'gid://gitlab/WorkItem/1',
- widgets: [
- {
- __typename: 'WorkItemWidgetLabels',
- type: 'LABELS',
- allowsScopedLabels: false,
- labels: {
- nodes: mockLabels,
- },
- },
- ],
- },
- },
-};
-
-export const workItemIterationSubscriptionResponse = {
- data: {
- issuableIterationUpdated: {
- id: 'gid://gitlab/WorkItem/1',
- widgets: [
- {
- __typename: 'WorkItemWidgetIteration',
- iteration: {
- description: 'Iteration description',
- dueDate: '2022-07-29',
- id: 'gid://gitlab/Iteration/1125',
- iid: '95',
- startDate: '2022-06-22',
- title: 'Iteration subcription title',
- },
- },
- ],
- },
- },
-};
-
-export const workItemHealthStatusSubscriptionResponse = {
- data: {
- issuableHealthStatusUpdated: {
- id: 'gid://gitlab/WorkItem/1',
- widgets: [
- {
- __typename: 'WorkItemWidgetHealthStatus',
- healthStatus: 'needsAttention',
- },
- ],
- },
- },
-};
-
-export const workItemMilestoneSubscriptionResponse = {
- data: {
- issuableMilestoneUpdated: {
- id: 'gid://gitlab/WorkItem/1',
- widgets: [
- {
- __typename: 'WorkItemWidgetMilestone',
- type: 'MILESTONE',
- milestone: {
- id: 'gid://gitlab/Milestone/1125',
- expired: false,
- title: 'Milestone title',
- },
- },
- ],
- },
- },
-};
-
export const workItemHierarchyEmptyResponse = {
data: {
workspace: {
diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js
index b5d54a7c319..4e31ee30552 100644
--- a/spec/frontend/work_items/router_spec.js
+++ b/spec/frontend/work_items/router_spec.js
@@ -2,28 +2,14 @@ import { mount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import {
- currentUserResponse,
- workItemAssigneesSubscriptionResponse,
- workItemDatesSubscriptionResponse,
- workItemByIidResponseFactory,
- workItemTitleSubscriptionResponse,
- workItemLabelsSubscriptionResponse,
- workItemMilestoneSubscriptionResponse,
- workItemDescriptionSubscriptionResponse,
-} from 'jest/work_items/mock_data';
+import { currentUserResponse, workItemByIidResponseFactory } from 'jest/work_items/mock_data';
import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql';
import App from '~/work_items/components/app.vue';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
-import workItemDatesSubscription from '~/graphql_shared/subscriptions/work_item_dates.subscription.graphql';
-import workItemTitleSubscription from '~/work_items/graphql/work_item_title.subscription.graphql';
-import workItemAssigneesSubscription from '~/work_items/graphql/work_item_assignees.subscription.graphql';
-import workItemLabelsSubscription from 'ee_else_ce/work_items/graphql/work_item_labels.subscription.graphql';
-import workItemMilestoneSubscription from '~/work_items/graphql/work_item_milestone.subscription.graphql';
-import workItemDescriptionSubscription from '~/work_items/graphql/work_item_description.subscription.graphql';
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
import { createRouter } from '~/work_items/router';
+import workItemUpdatedSubscription from '~/work_items/graphql/work_item_updated.subscription.graphql';
jest.mock('~/behaviors/markdown/render_gfm');
@@ -34,18 +20,9 @@ describe('Work items router', () => {
const workItemQueryHandler = jest.fn().mockResolvedValue(workItemByIidResponseFactory());
const currentUserQueryHandler = jest.fn().mockResolvedValue(currentUserResponse);
- const datesSubscriptionHandler = jest.fn().mockResolvedValue(workItemDatesSubscriptionResponse);
- const titleSubscriptionHandler = jest.fn().mockResolvedValue(workItemTitleSubscriptionResponse);
- const assigneesSubscriptionHandler = jest
+ const workItemUpdatedSubscriptionHandler = jest
.fn()
- .mockResolvedValue(workItemAssigneesSubscriptionResponse);
- const labelsSubscriptionHandler = jest.fn().mockResolvedValue(workItemLabelsSubscriptionResponse);
- const milestoneSubscriptionHandler = jest
- .fn()
- .mockResolvedValue(workItemMilestoneSubscriptionResponse);
- const descriptionSubscriptionHandler = jest
- .fn()
- .mockResolvedValue(workItemDescriptionSubscriptionResponse);
+ .mockResolvedValue({ data: { workItemUpdated: null } });
const createComponent = async (routeArg) => {
const router = createRouter('/work_item');
@@ -56,12 +33,7 @@ describe('Work items router', () => {
const handlers = [
[workItemByIidQuery, workItemQueryHandler],
[currentUserQuery, currentUserQueryHandler],
- [workItemDatesSubscription, datesSubscriptionHandler],
- [workItemTitleSubscription, titleSubscriptionHandler],
- [workItemAssigneesSubscription, assigneesSubscriptionHandler],
- [workItemLabelsSubscription, labelsSubscriptionHandler],
- [workItemMilestoneSubscription, milestoneSubscriptionHandler],
- [workItemDescriptionSubscription, descriptionSubscriptionHandler],
+ [workItemUpdatedSubscription, workItemUpdatedSubscriptionHandler],
];
wrapper = mount(App, {
diff --git a/spec/migrations/cleanup_bigint_conversion_for_merge_request_metrics_for_self_hosts_spec.rb b/spec/migrations/cleanup_bigint_conversion_for_merge_request_metrics_for_self_hosts_spec.rb
index b056ade9b7b..dc4adae9138 100644
--- a/spec/migrations/cleanup_bigint_conversion_for_merge_request_metrics_for_self_hosts_spec.rb
+++ b/spec/migrations/cleanup_bigint_conversion_for_merge_request_metrics_for_self_hosts_spec.rb
@@ -48,6 +48,7 @@ RSpec.describe CleanupBigintConversionForMergeRequestMetricsForSelfHosts, featur
# As we call `schema_migrate_down!` before each example, and for this migration
# `#down` is same as `#up`, we need to ensure we start from the expected state.
connection = described_class.new.connection
+ connection.execute('ALTER TABLE merge_request_metrics ALTER COLUMN id TYPE bigint')
connection.execute('ALTER TABLE merge_request_metrics DROP COLUMN IF EXISTS id_convert_to_bigint')
end
@@ -70,6 +71,7 @@ RSpec.describe CleanupBigintConversionForMergeRequestMetricsForSelfHosts, featur
# As we call `schema_migrate_down!` before each example, and for this migration
# `#down` is same as `#up`, we need to ensure we start from the expected state.
connection = described_class.new.connection
+ connection.execute('ALTER TABLE merge_request_metrics ALTER COLUMN id TYPE bigint')
connection.execute('ALTER TABLE merge_request_metrics ADD COLUMN IF NOT EXISTS id_convert_to_bigint integer')
end
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index 8e86518912c..fd60d279110 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -257,7 +257,7 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
end
context 'validates expires_at' do
- let(:max_expiration_date) { described_class::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS.days.from_now }
+ let(:max_expiration_date) { Date.current + described_class::MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS }
it "can't be blank" do
personal_access_token.expires_at = nil
@@ -274,12 +274,14 @@ RSpec.describe PersonalAccessToken, feature_category: :system_access do
end
end
- context 'when expires_in is more than MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS days' do
+ context 'when expires_in is more than MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS days', :freeze_time do
it 'is invalid' do
personal_access_token.expires_at = max_expiration_date + 1.day
expect(personal_access_token).not_to be_valid
- expect(personal_access_token.errors[:expires_at].first).to eq('must expire in 365 days')
+ expect(personal_access_token.errors.full_messages.to_sentence).to eq(
+ "Expiration date must be before #{max_expiration_date}"
+ )
end
end
end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 7aa6980fb24..ccf58964c71 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -456,14 +456,63 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
end
context 'repository removal' do
- # 1. Project repository
- # 2. Wiki repository
- it 'removal of existing repos' do
- expect_next_instances_of(Repositories::DestroyService, 2) do |instance|
- expect(instance).to receive(:execute).and_return(status: :success)
+ describe '.trash_project_repositories!' do
+ let(:trash_project_repositories!) { described_class.new(project, user, {}).send(:trash_project_repositories!) }
+
+ # Destroys 3 repositories:
+ # 1. Project repository
+ # 2. Wiki repository
+ # 3. Design repository
+
+ it 'Repositories::DestroyService is called for existing repos' do
+ expect_next_instances_of(Repositories::DestroyService, 3) do |instance|
+ expect(instance).to receive(:execute).and_return(status: :success)
+ end
+
+ trash_project_repositories!
end
- described_class.new(project, user, {}).execute
+ context 'when the removal has errors' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:mock_error) { instance_double(Repositories::DestroyService, execute: { message: 'foo', status: :error }) }
+ let(:project_repository) { project.repository }
+ let(:wiki_repository) { project.wiki.repository }
+ let(:design_repository) { project.design_repository }
+
+ where(:repo, :message) do
+ ref(:project_repository) | 'Failed to remove project repository. Please try again or contact administrator.'
+ ref(:wiki_repository) | 'Failed to remove wiki repository. Please try again or contact administrator.'
+ ref(:design_repository) | 'Failed to remove design repository. Please try again or contact administrator.'
+ end
+
+ with_them do
+ before do
+ allow(Repositories::DestroyService).to receive(:new).with(anything).and_call_original
+ allow(Repositories::DestroyService).to receive(:new).with(repo).and_return(mock_error)
+ end
+
+ it 'raises correct error' do
+ expect { trash_project_repositories! }.to raise_error(Projects::DestroyService::DestroyError, message)
+ end
+ end
+ end
+ end
+
+ it 'removes project repository' do
+ expect { destroy_project(project, user, {}) }.to change { project.repository.exists? }.from(true).to(false)
+ end
+
+ it 'removes wiki repository' do
+ project.create_wiki unless project.wiki.repository.exists?
+
+ expect { destroy_project(project, user, {}) }.to change { project.wiki.repository.exists? }.from(true).to(false)
+ end
+
+ it 'removes design repository' do
+ project.design_repository.create_if_not_exists
+
+ expect { destroy_project(project, user, {}) }.to change { project.design_repository.exists? }.from(true).to(false)
end
end
diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb
index 31e4e008d4f..940d9858cdd 100644
--- a/spec/services/resource_access_tokens/create_service_spec.rb
+++ b/spec/services/resource_access_tokens/create_service_spec.rb
@@ -199,16 +199,14 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
end
end
- context 'expiry of the project bot member' do
- it 'project bot membership does not expire' do
- response = subject
- access_token = response.payload[:access_token]
- project_bot = access_token.user
+ it 'project bot membership expires when PAT expires' do
+ response = subject
+ access_token = response.payload[:access_token]
+ project_bot = access_token.user
- expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(
- max_pat_access_token_lifetime.to_date
- )
- end
+ expect(resource.members.find_by(user_id: project_bot.id).expires_at).to eq(
+ max_pat_access_token_lifetime.to_date
+ )
end
end
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index adee31ca644..b9ce1f80c02 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -1213,8 +1213,6 @@
- './ee/spec/lib/elastic/latest/merge_request_config_spec.rb'
- './ee/spec/lib/elastic/latest/note_config_spec.rb'
- './ee/spec/lib/elastic/latest/project_instance_proxy_spec.rb'
-- './ee/spec/lib/elastic/latest/project_wiki_class_proxy_spec.rb'
-- './ee/spec/lib/elastic/latest/project_wiki_instance_proxy_spec.rb'
- './ee/spec/lib/elastic/latest/routing_spec.rb'
- './ee/spec/lib/elastic/latest/snippet_instance_proxy_spec.rb'
- './ee/spec/lib/elastic/migration_spec.rb'