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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-10-31 21:07:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-10-31 21:07:22 +0300
commit1d21e1712158ee4e3cf8b71b45ead662529fc3f8 (patch)
treea8435cf28a026d7d7ef681d459e905bcf5609f65
parent47bf4294773cf15aa755193cdd7df9a491a49f52 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.haml-lint_todo.yml25
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml15
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_note.vue14
-rw-r--r--app/assets/javascripts/issues/show/components/header_actions.vue259
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/app.vue2
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue11
-rw-r--r--app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue210
-rw-r--r--app/assets/javascripts/work_items/components/work_item_milestone.vue133
-rw-r--r--app/assets/javascripts/work_items/components/work_item_parent.vue32
-rw-r--r--app/assets/stylesheets/page_bundles/issuable.scss23
-rw-r--r--app/assets/stylesheets/page_bundles/work_items.scss22
-rw-r--r--app/helpers/clusters_helper.rb2
-rw-r--r--app/helpers/environments_helper.rb4
-rw-r--r--app/helpers/ide_helper.rb10
-rw-r--r--app/helpers/merge_requests_helper.rb4
-rw-r--r--app/helpers/operations_helper.rb2
-rw-r--r--app/helpers/preferences_helper.rb4
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/presenters/clusters/cluster_presenter.rb2
-rw-r--r--app/serializers/issue_entity.rb6
-rw-r--r--app/serializers/merge_request_noteable_entity.rb4
-rw-r--r--app/serializers/merge_request_widget_entity.rb8
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/views/admin/application_settings/_email.html.haml2
-rw-r--r--app/views/admin/application_settings/_error_tracking.html.haml4
-rw-r--r--app/views/admin/application_settings/_floc.html.haml2
-rw-r--r--app/views/admin/application_settings/_gitlab_shell_operation_limits.html.haml2
-rw-r--r--app/views/admin/application_settings/_localization.html.haml2
-rw-r--r--app/views/admin/application_settings/_projects_api_limits.html.haml2
-rw-r--r--app/views/admin/application_settings/_runner_registrars_form.html.haml2
-rw-r--r--app/views/admin/application_settings/general.html.haml6
-rw-r--r--app/views/admin/application_settings/network.html.haml22
-rw-r--r--app/views/admin/application_settings/preferences.html.haml10
-rw-r--r--app/views/admin/application_settings/reporting.html.haml2
-rw-r--r--app/views/admin/application_settings/repository.html.haml10
-rw-r--r--app/views/admin/dev_ops_report/_score.html.haml2
-rw-r--r--app/views/groups/_import_group_from_another_instance_panel.html.haml2
-rw-r--r--app/views/profiles/gpg_keys/index.html.haml2
-rw-r--r--app/views/profiles/keys/_key.html.haml2
-rw-r--r--app/views/profiles/keys/index.html.haml2
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml2
-rw-r--r--app/views/projects/blob/_pipeline_tour_success.html.haml2
-rw-r--r--app/views/projects/commit/_signature_badge.html.haml6
-rw-r--r--db/post_migrate/20231016001000_fix_design_user_mentions_design_id_note_id_index_for_self_managed.rb28
-rw-r--r--db/post_migrate/20231024080150_cleanup_ci_sources_pipelines_pipeline_id_bigint.rb35
-rw-r--r--db/schema_migrations/202310160010001
-rw-r--r--db/schema_migrations/202310240801501
-rw-r--r--db/structure.sql24
-rw-r--r--doc/administration/auditor_users.md3
-rw-r--r--doc/administration/monitoring/prometheus/web_exporter.md8
-rw-r--r--doc/administration/settings/rate_limits_on_git_ssh_operations.md3
-rw-r--r--doc/administration/settings/scim_setup.md4
-rw-r--r--doc/api/graphql/reference/index.md21
-rw-r--r--doc/architecture/blueprints/gcp_integration/decisions/001_no_credentials.md19
-rw-r--r--doc/architecture/blueprints/gcp_integration/index.md95
-rw-r--r--doc/ci/quick_start/index.md2
-rw-r--r--doc/ci/yaml/index.md5
-rw-r--r--doc/development/ruby_upgrade.md14
-rw-r--r--doc/user/group/saml_sso/troubleshooting_scim.md19
-rw-r--r--doc/user/project/merge_requests/reviews/img/comment-on-any-diff-line_v13_10.pngbin21304 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/reviews/img/comment_on_any_diff_line_v16_6.pngbin0 -> 12677 bytes
-rw-r--r--doc/user/project/merge_requests/reviews/img/mr_review_new_comment_v15_3.pngbin32927 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/reviews/img/mr_review_new_comment_v16_6.pngbin0 -> 11833 bytes
-rw-r--r--doc/user/project/merge_requests/reviews/img/mr_summary_comment_v15_4.pngbin61841 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/reviews/img/mr_summary_comment_v16_6.pngbin0 -> 16816 bytes
-rw-r--r--doc/user/project/merge_requests/reviews/index.md8
-rw-r--r--doc/user/project/protected_branches.md1
-rw-r--r--lib/sidebars/projects/menus/infrastructure_menu.rb2
-rw-r--r--locale/gitlab.pot18
-rw-r--r--qa/qa/page/project/issue/show.rb1
-rw-r--r--qa/qa/page/project/settings/branch_rules.rb12
-rw-r--r--rubocop/cop/gitlab/doc_url.rb2
-rw-r--r--spec/features/issues/issue_state_spec.rb4
-rw-r--r--spec/features/issues/move_spec.rb4
-rw-r--r--spec/features/projects/work_items/work_item_spec.rb2
-rw-r--r--spec/frontend/design_management/components/design_notes/design_note_spec.js43
-rw-r--r--spec/frontend/issues/show/components/header_actions_spec.js52
-rw-r--r--spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js358
-rw-r--r--spec/frontend/work_items/components/work_item_milestone_spec.js53
-rw-r--r--spec/frontend/work_items/components/work_item_parent_spec.js5
-rw-r--r--spec/helpers/environments_helper_spec.rb4
-rw-r--r--spec/helpers/ide_helper_spec.rb2
-rw-r--r--spec/helpers/operations_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_spec.rb11
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb22
-rw-r--r--spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb13
-rw-r--r--spec/lib/gitlab/database/bulk_update_spec.rb2
-rw-r--r--spec/lib/gitlab/database/loose_foreign_keys_spec.rb16
-rw-r--r--spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb40
-rw-r--r--spec/lib/gitlab/database/migrations/instrumentation_spec.rb16
-rw-r--r--spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb104
-rw-r--r--spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb64
-rw-r--r--spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb36
-rw-r--r--spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb27
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb7
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb60
-rw-r--r--spec/lib/gitlab/database/postgres_constraint_spec.rb16
-rw-r--r--spec/lib/gitlab/database/tables_truncate_spec.rb2
-rw-r--r--spec/migrations/20231016001000_fix_design_user_mentions_design_id_note_id_index_for_self_managed_spec.rb114
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb2
-rw-r--r--spec/rubocop/cop/gitlab/doc_url_spec.rb6
-rw-r--r--spec/serializers/issue_entity_spec.rb2
-rw-r--r--spec/support/shared_examples/features/work_items_shared_examples.rb4
104 files changed, 1243 insertions, 1062 deletions
diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml
index c9b6930bc2b..c587e299c62 100644
--- a/.haml-lint_todo.yml
+++ b/.haml-lint_todo.yml
@@ -1,6 +1,6 @@
# This configuration was generated by
# `haml-lint --auto-gen-config`
-# on 2023-10-26 11:21:24 +0200 using Haml-Lint version 0.40.1.
+# on 2023-10-30 15:10:05 +0100 using Haml-Lint version 0.40.1.
# The point is for the user to remove these configuration records
# one by one as the lints are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -8,30 +8,11 @@
linters:
- # Offense count: 49
+ # Offense count: 37
DocumentationLinks:
exclude:
- - "app/views/admin/application_settings/_email.html.haml"
- - "app/views/admin/application_settings/_error_tracking.html.haml"
- - "app/views/admin/application_settings/_floc.html.haml"
- - "app/views/admin/application_settings/_gitlab_shell_operation_limits.html.haml"
- "app/views/admin/application_settings/_localization.html.haml"
- - "app/views/admin/application_settings/_projects_api_limits.html.haml"
- - "app/views/admin/application_settings/_runner_registrars_form.html.haml"
- - "app/views/admin/application_settings/general.html.haml"
- - "app/views/admin/application_settings/network.html.haml"
- - "app/views/admin/application_settings/preferences.html.haml"
- - "app/views/admin/application_settings/reporting.html.haml"
- - "app/views/admin/application_settings/repository.html.haml"
- - "app/views/admin/dev_ops_report/_score.html.haml"
- - "app/views/groups/_import_group_from_another_instance_panel.html.haml"
- - "app/views/profiles/gpg_keys/index.html.haml"
- - "app/views/profiles/keys/_key.html.haml"
- - "app/views/profiles/keys/index.html.haml"
- - "app/views/profiles/personal_access_tokens/index.html.haml"
- "app/views/profiles/two_factor_auths/show.html.haml"
- - "app/views/projects/blob/_pipeline_tour_success.html.haml"
- - "app/views/projects/commit/_signature_badge.html.haml"
- "app/views/projects/settings/merge_requests/_merge_request_merge_commit_template.html.haml"
- "app/views/projects/settings/merge_requests/_merge_request_merge_suggestions_settings.html.haml"
- "app/views/projects/settings/merge_requests/_merge_request_squash_commit_template.html.haml"
@@ -59,4 +40,4 @@ linters:
- "ee/app/views/projects/settings/merge_requests/_merge_request_approvals_settings.html.haml"
- "ee/app/views/projects/settings/merge_requests/_target_branch_rules_settings.html.haml"
- "ee/app/views/shared/_new_user_signups_cap_reached_alert.html.haml"
- - "ee/app/views/shared/promotions/_promote_mobile_devops.html.haml"
+ - "ee/app/views/shared/promotions/_promote_mobile_devops.html.haml" \ No newline at end of file
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index cab3185a377..3a0de7eb0b7 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -1466,22 +1466,7 @@ Layout/ArgumentAlignment:
- 'spec/lib/gitlab/cross_project_access/class_methods_spec.rb'
- 'spec/lib/gitlab/cross_project_access_spec.rb'
- 'spec/lib/gitlab/data_builder/push_spec.rb'
- - 'spec/lib/gitlab/database/background_migration/batched_migration_spec.rb'
- - 'spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb'
- - 'spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb'
- - 'spec/lib/gitlab/database/bulk_update_spec.rb'
- - 'spec/lib/gitlab/database/loose_foreign_keys_spec.rb'
- 'spec/lib/gitlab/database/migration_helpers_spec.rb'
- - 'spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb'
- - 'spec/lib/gitlab/database/migrations/instrumentation_spec.rb'
- - 'spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb'
- - 'spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb'
- - 'spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb'
- - 'spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb'
- - 'spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb'
- - 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb'
- - 'spec/lib/gitlab/database/postgres_constraint_spec.rb'
- - 'spec/lib/gitlab/database/tables_truncate_spec.rb'
- 'spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb'
- 'spec/lib/gitlab/diff/file_collection/compare_spec.rb'
- 'spec/lib/gitlab/diff/file_collection/merge_request_diff_batch_spec.rb'
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
index 54ffd986588..b247f17fd97 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
@@ -13,6 +13,7 @@ import SafeHtml from '~/vue_shared/directives/safe_html';
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPENAME_USER } from '~/graphql_shared/constants';
import { __ } from '~/locale';
+import { setUrlFragment } from '~/lib/utils/url_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import EmojiPicker from '~/emoji/components/picker.vue';
@@ -29,6 +30,7 @@ export default {
editCommentLabel: __('Edit comment'),
moreActionsLabel: __('More actions'),
deleteCommentText: __('Delete comment'),
+ copyCommentLink: __('Copy link'),
},
components: {
DesignNoteAwardsList,
@@ -133,6 +135,18 @@ export default {
},
},
{
+ text: this.$options.i18n.copyCommentLink,
+ action: () => {
+ this.$toast.show(__('Link copied to clipboard.'));
+ },
+ extraAttrs: {
+ 'data-clipboard-text': setUrlFragment(
+ window.location.href,
+ `note_${this.noteAnchorId}`,
+ ),
+ },
+ },
+ {
text: this.$options.i18n.deleteCommentText,
action: () => {
this.$emit('delete-note', this.note);
diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue
index 1c1b15e2026..c50b8009284 100644
--- a/app/assets/javascripts/issues/show/components/header_actions.vue
+++ b/app/assets/javascripts/issues/show/components/header_actions.vue
@@ -1,9 +1,9 @@
<script>
import {
GlButton,
- GlDropdown,
+ GlDisclosureDropdown,
GlDropdownDivider,
- GlDropdownItem,
+ GlDisclosureDropdownItem,
GlLink,
GlModal,
GlModalDirective,
@@ -59,9 +59,9 @@ export default {
components: {
DeleteIssueModal,
GlButton,
- GlDropdown,
+ GlDisclosureDropdown,
GlDropdownDivider,
- GlDropdownItem,
+ GlDisclosureDropdownItem,
GlLink,
GlModal,
AbuseCategorySelector,
@@ -184,6 +184,18 @@ export default {
showMovedSidebarOptions() {
return this.isMrSidebarMoved && this.isUserSignedIn;
},
+ newIssueItem() {
+ return {
+ text: this.newIssueTypeText,
+ href: this.newIssuePath,
+ };
+ },
+ submitSpamItem() {
+ return {
+ text: __('Submit as spam'),
+ href: this.submitAsSpamPath,
+ };
+ },
},
created() {
eventHub.$on('toggle.issuable.state', this.toggleIssueState);
@@ -197,6 +209,7 @@ export default {
toggleIssueState() {
if (!this.isClosed && this.getBlockedByIssues?.length) {
this.$refs.blockedByIssuesModal.show();
+ this.closeActionsDropdown();
return;
}
@@ -204,6 +217,7 @@ export default {
},
toggleReportAbuseDrawer(isOpen) {
this.isReportAbuseDrawerOpen = isOpen;
+ this.closeActionsDropdown();
},
invokeUpdateIssueMutation() {
this.toggleStateButtonLoading(true);
@@ -237,6 +251,7 @@ export default {
.catch(() => createAlert({ message: __('Error occurred while updating the issue status') }))
.finally(() => {
this.toggleStateButtonLoading(false);
+ this.closeActionsDropdown();
});
},
promoteToEpic() {
@@ -267,16 +282,24 @@ export default {
.catch(() => createAlert({ message: this.$options.i18n.promoteErrorMessage }))
.finally(() => {
this.toggleStateButtonLoading(false);
+ this.closeActionsDropdown();
});
},
edit() {
issuesEventHub.$emit('open.form');
+ this.closeActionsDropdown();
},
copyReference() {
toast(__('Reference copied'));
+ this.closeActionsDropdown();
},
copyEmailAddress() {
toast(__('Email address copied'));
+ this.closeActionsDropdown();
+ },
+ closeActionsDropdown() {
+ this.$refs.issuableActionsDropdownMobile?.close();
+ this.$refs.issuableActionsDropdownDesktop?.close();
},
},
TYPE_ISSUE,
@@ -285,87 +308,90 @@ export default {
<template>
<div class="detail-page-header-actions gl-display-flex gl-align-self-start gl-sm-gap-3">
- <gl-dropdown
- v-if="hasMobileDropdown"
- class="gl-sm-display-none! w-100"
- block
- :text="dropdownText"
- data-testid="mobile-dropdown"
- :loading="isToggleStateButtonLoading"
- >
- <template v-if="showMovedSidebarOptions">
- <sidebar-subscriptions-widget
- :iid="String(iid)"
- :full-path="fullPath"
- :issuable-type="$options.TYPE_ISSUE"
- data-testid="notification-toggle"
- />
+ <div class="gl-sm-display-none! w-100">
+ <gl-disclosure-dropdown
+ v-if="hasMobileDropdown"
+ ref="issuableActionsDropdownMobile"
+ toggle-class="gl-w-full"
+ block
+ :toggle-text="dropdownText"
+ :auto-close="false"
+ data-testid="mobile-dropdown"
+ :loading="isToggleStateButtonLoading"
+ placement="right"
+ >
+ <template v-if="showMovedSidebarOptions">
+ <sidebar-subscriptions-widget
+ :iid="String(iid)"
+ :full-path="fullPath"
+ :issuable-type="$options.TYPE_ISSUE"
+ data-testid="notification-toggle"
+ />
- <gl-dropdown-divider />
- </template>
+ <gl-dropdown-divider />
+ </template>
- <template v-if="showLockIssueOption">
- <issuable-lock-form :is-editable="false" data-testid="lock-issue-toggle" />
- </template>
+ <template v-if="showLockIssueOption">
+ <issuable-lock-form :is-editable="false" data-testid="lock-issue-toggle" />
+ </template>
- <gl-dropdown-item v-if="canUpdateIssue" @click="edit">
- {{ $options.i18n.edit }}
- </gl-dropdown-item>
- <gl-dropdown-item
- v-if="showToggleIssueStateButton"
- :data-testid="`mobile_${qaSelector}`"
- @click="toggleIssueState"
- >
- {{ buttonText }}
- </gl-dropdown-item>
- <gl-dropdown-item v-if="canCreateIssue" :href="newIssuePath">
- {{ newIssueTypeText }}
- </gl-dropdown-item>
- <gl-dropdown-item v-if="canPromoteToEpic" @click="promoteToEpic">
- {{ __('Promote to epic') }}
- </gl-dropdown-item>
- <template v-if="isMrSidebarMoved">
- <gl-dropdown-item
- :data-clipboard-text="issuableReference"
- button-class="js-copy-reference"
- data-testid="copy-reference"
- @click="copyReference"
- >{{ $options.i18n.copyReferenceText }}</gl-dropdown-item
- >
- <gl-dropdown-item
- v-if="issuableEmailAddress && showMovedSidebarOptions"
- :data-clipboard-text="issuableEmailAddress"
- data-testid="copy-email"
- @click="copyEmailAddress"
- >{{ copyMailAddressText }}</gl-dropdown-item
+ <gl-disclosure-dropdown-item v-if="canUpdateIssue" @action="edit">
+ <template #list-item>{{ $options.i18n.edit }}</template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item
+ v-if="showToggleIssueStateButton"
+ :data-testid="`mobile_${qaSelector}`"
+ @action="toggleIssueState"
>
- </template>
- <gl-dropdown-item
- v-if="canReportSpam"
- :href="submitAsSpamPath"
- data-method="post"
- rel="nofollow"
- >
- {{ __('Submit as spam') }}
- </gl-dropdown-item>
- <template v-if="canDestroyIssue">
- <gl-dropdown-divider />
- <gl-dropdown-item
- v-gl-modal="$options.deleteModalId"
- variant="danger"
- @click="track('click_dropdown')"
+ <template #list-item>{{ buttonText }}</template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item v-if="canCreateIssue" :item="newIssueItem" />
+ <gl-disclosure-dropdown-item v-if="canPromoteToEpic" @action="promoteToEpic">
+ <template #list-item>{{ __('Promote to epic') }}</template>
+ </gl-disclosure-dropdown-item>
+ <template v-if="isMrSidebarMoved">
+ <gl-disclosure-dropdown-item
+ :data-clipboard-text="issuableReference"
+ button-class="js-copy-reference"
+ data-testid="copy-reference"
+ @action="copyReference"
+ ><template #list-item>{{
+ $options.i18n.copyReferenceText
+ }}</template></gl-disclosure-dropdown-item
+ >
+ <gl-disclosure-dropdown-item
+ v-if="issuableEmailAddress && showMovedSidebarOptions"
+ :data-clipboard-text="issuableEmailAddress"
+ data-testid="copy-email"
+ @action="copyEmailAddress"
+ >{{ copyMailAddressText }}</gl-disclosure-dropdown-item
+ >
+ </template>
+ <gl-disclosure-dropdown-item
+ v-if="canReportSpam"
+ :item="submitSpamItem"
+ data-method="post"
+ rel="nofollow"
+ />
+ <template v-if="canDestroyIssue">
+ <gl-dropdown-divider />
+ <gl-disclosure-dropdown-item
+ v-gl-modal="$options.deleteModalId"
+ variant="danger"
+ @action="track('click_dropdown')"
+ >
+ <template #list-item>{{ deleteButtonText }}</template>
+ </gl-disclosure-dropdown-item>
+ </template>
+ <gl-disclosure-dropdown-item
+ v-if="!isIssueAuthor && isUserSignedIn"
+ data-testid="report-abuse-item"
+ @action="toggleReportAbuseDrawer(true)"
>
- {{ deleteButtonText }}
- </gl-dropdown-item>
- </template>
- <gl-dropdown-item
- v-if="!isIssueAuthor && isUserSignedIn"
- data-testid="report-abuse-item"
- @click="toggleReportAbuseDrawer(true)"
- >
- {{ $options.i18n.reportAbuse }}
- </gl-dropdown-item>
- </gl-dropdown>
+ <template #list-item>{{ $options.i18n.reportAbuse }}</template>
+ </gl-disclosure-dropdown-item>
+ </gl-disclosure-dropdown>
+ </div>
<gl-button
v-if="canUpdateIssue"
@@ -379,20 +405,22 @@ export default {
{{ $options.i18n.edit }}
</gl-button>
- <gl-dropdown
+ <gl-disclosure-dropdown
v-if="hasDesktopDropdown"
id="new-actions-header-dropdown"
+ ref="issuableActionsDropdownDesktop"
v-gl-tooltip.hover
class="gl-display-none gl-sm-display-inline-flex!"
icon="ellipsis_v"
category="tertiary"
- :text="dropdownText"
- :text-sr-only="true"
+ placement="left"
+ :toggle-text="dropdownText"
+ text-sr-only
:title="dropdownText"
:aria-label="dropdownText"
+ :auto-close="false"
data-testid="desktop-dropdown"
no-caret
- right
>
<template v-if="showMovedSidebarOptions && !glFeatures.notificationsTodosButtons">
<sidebar-subscriptions-widget
@@ -401,73 +429,70 @@ export default {
:issuable-type="$options.TYPE_ISSUE"
data-testid="notification-toggle"
/>
-
<gl-dropdown-divider />
</template>
- <gl-dropdown-item
+ <gl-disclosure-dropdown-item
v-if="showToggleIssueStateButton"
data-testid="toggle-issue-state-button"
- @click="toggleIssueState"
+ @action="toggleIssueState"
>
- {{ buttonText }}
- </gl-dropdown-item>
- <gl-dropdown-item v-if="canCreateIssue && isUserSignedIn" :href="newIssuePath">
- {{ newIssueTypeText }}
- </gl-dropdown-item>
- <gl-dropdown-item
+ <template #list-item>{{ buttonText }}</template>
+ </gl-disclosure-dropdown-item>
+ <gl-disclosure-dropdown-item v-if="canCreateIssue && isUserSignedIn" :item="newIssueItem" />
+ <gl-disclosure-dropdown-item
v-if="canPromoteToEpic"
:disabled="isToggleStateButtonLoading"
data-testid="promote-button"
- @click="promoteToEpic"
+ @action="promoteToEpic"
>
- {{ __('Promote to epic') }}
- </gl-dropdown-item>
+ <template #list-item>{{ __('Promote to epic') }}</template>
+ </gl-disclosure-dropdown-item>
<template v-if="showLockIssueOption">
<issuable-lock-form :is-editable="false" data-testid="lock-issue-toggle" />
</template>
<template v-if="isMrSidebarMoved">
- <gl-dropdown-item
+ <gl-disclosure-dropdown-item
:data-clipboard-text="issuableReference"
button-class="js-copy-reference"
data-testid="copy-reference"
- @click="copyReference"
- >{{ $options.i18n.copyReferenceText }}</gl-dropdown-item
+ @action="copyReference"
+ ><template #list-item>{{
+ $options.i18n.copyReferenceText
+ }}</template></gl-disclosure-dropdown-item
>
- <gl-dropdown-item
+ <gl-disclosure-dropdown-item
v-if="issuableEmailAddress && showMovedSidebarOptions"
:data-clipboard-text="issuableEmailAddress"
data-testid="copy-email"
- @click="copyEmailAddress"
- >{{ copyMailAddressText }}</gl-dropdown-item
+ @action="copyEmailAddress"
+ ><template #list-item>{{ copyMailAddressText }}</template></gl-disclosure-dropdown-item
>
</template>
- <gl-dropdown-divider v-if="canDestroyIssue || canReportSpam || !isIssueAuthor" />
- <gl-dropdown-item
+ <gl-dropdown-divider v-if="showToggleIssueStateButton || canDestroyIssue || canReportSpam" />
+ <gl-disclosure-dropdown-item
v-if="canReportSpam"
- :href="submitAsSpamPath"
+ :item="submitSpamItem"
data-method="post"
rel="nofollow"
- >
- {{ __('Submit as spam') }}
- </gl-dropdown-item>
- <gl-dropdown-item
+ />
+ <gl-disclosure-dropdown-item
v-if="!isIssueAuthor && isUserSignedIn"
data-testid="report-abuse-item"
- @click="toggleReportAbuseDrawer(true)"
+ @action="toggleReportAbuseDrawer(true)"
>
- {{ $options.i18n.reportAbuse }}
- </gl-dropdown-item>
+ <template #list-item>{{ $options.i18n.reportAbuse }}</template>
+ </gl-disclosure-dropdown-item>
<template v-if="canDestroyIssue">
- <gl-dropdown-item
+ <gl-disclosure-dropdown-item
v-gl-modal="$options.deleteModalId"
variant="danger"
data-testid="delete-issue-button"
- @click="track('click_dropdown')"
+ @action="track('click_dropdown')"
>
- {{ deleteButtonText }}
- </gl-dropdown-item>
+ <template #list-item>{{ deleteButtonText }}</template>
+ </gl-disclosure-dropdown-item>
</template>
- </gl-dropdown>
+ </gl-disclosure-dropdown>
<gl-modal
ref="blockedByIssuesModal"
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
index 7753b850744..7d9ad83a1c6 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/app.vue
@@ -76,7 +76,7 @@ export default {
v-gl-modal="$options.modalId"
size="small"
class="gl-ml-3"
- data-qa-selector="add_branch_rule_button"
+ data-testid="add-branch-rule-button"
>{{ $options.i18n.addBranchRule }}</gl-button
>
</template>
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
index f45a5b12db6..0a5fa288828 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
@@ -156,7 +156,7 @@ export default {
<li>
<div
class="gl-display-flex gl-justify-content-space-between"
- data-qa-selector="branch_content"
+ data-testid="branch-content"
:data-qa-branch-name="name"
>
<div>
@@ -178,7 +178,7 @@ export default {
class="gl-align-self-start"
category="tertiary"
size="small"
- data-qa-selector="details_button"
+ data-testid="details-button"
:href="detailsPath"
>
{{ $options.i18n.detailsButtonLabel }}</gl-button
diff --git a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
index 165499696de..16235275a54 100644
--- a/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
+++ b/app/assets/javascripts/sidebar/components/lock/issuable_lock_form.vue
@@ -170,9 +170,14 @@ export default {
</script>
<template>
- <li v-if="isMovedMrSidebar && isIssuable" class="gl-dropdown-item">
- <button type="button" class="dropdown-item" data-testid="issuable-lock" @click="toggleLocked">
- <span class="gl-dropdown-item-text-wrapper">
+ <li v-if="isMovedMrSidebar && isIssuable" class="gl-new-dropdown-item">
+ <button
+ type="button"
+ class="gl-new-dropdown-item-content"
+ data-testid="issuable-lock"
+ @click="toggleLocked"
+ >
+ <span class="gl-new-dropdown-item-text-wrapper">
<template v-if="isLoading">
<gl-loading-icon inline size="sm" /> {{ lockToggleInProgressText }}
</template>
diff --git a/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
index 34a4da946d6..ea8e0c4b950 100644
--- a/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
+++ b/app/assets/javascripts/sidebar/components/move/issuable_move_dropdown.vue
@@ -1,26 +1,20 @@
<script>
import {
GlIcon,
- GlLoadingIcon,
- GlDropdown,
- GlDropdownForm,
- GlDropdownItem,
- GlSearchBoxByType,
GlButton,
+ GlCollapsibleListbox,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
-
+import { debounce } from 'lodash';
+import { __ } from '~/locale';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import axios from '~/lib/utils/axios_utils';
export default {
components: {
GlIcon,
- GlLoadingIcon,
- GlDropdown,
- GlDropdownForm,
- GlDropdownItem,
- GlSearchBoxByType,
GlButton,
+ GlCollapsibleListbox,
},
directives: {
GlTooltip,
@@ -51,82 +45,58 @@ export default {
},
data() {
return {
- projectsListLoading: false,
- projectsListLoadFailed: false,
- searchKey: '',
projects: [],
- selectedProject: null,
- projectItemClick: false,
+ projectsList: [],
+ selectedProjects: [],
+ noResultsText: '',
+ isSearching: false,
};
},
- computed: {
- hasNoSearchResults() {
- return Boolean(
- !this.projectsListLoading &&
- !this.projectsListLoadFailed &&
- this.searchKey &&
- !this.projects.length,
- );
- },
- failedToLoadResults() {
- return !this.projectsListLoading && this.projectsListLoadFailed;
- },
- },
- watch: {
- searchKey(value = '') {
- this.fetchProjects(value);
- },
+ mounted() {
+ this.fetchProjects = debounce(this.fetchProjects, DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
},
methods: {
- fetchProjects(search = '') {
- this.projectsListLoading = true;
- this.projectsListLoadFailed = false;
- return axios
- .get(this.projectsFetchPath, {
+ triggerSearch() {
+ this.$refs.dropdown.search();
+ },
+ async fetchProjects(search = '') {
+ this.isSearching = true;
+
+ try {
+ const { data } = await axios.get(this.projectsFetchPath, {
params: {
search,
},
- })
- .then(({ data }) => {
- this.projects = data;
- this.$refs.searchInput.focusInput();
- })
- .catch(() => {
- this.projectsListLoadFailed = true;
- })
- .finally(() => {
- this.projectsListLoading = false;
});
- },
- isSelectedProject(project) {
- if (this.selectedProject) {
- return this.selectedProject.id === project.id;
- }
- return false;
- },
- /**
- * This handler is to prevent dropdown
- * from closing when an item is selected
- * and emit an event only when dropdown closes.
- */
- handleDropdownHide(e) {
- if (this.projectItemClick) {
- e.preventDefault();
- this.projectItemClick = false;
- } else {
- this.$emit('dropdown-close');
+ this.projects = data;
+ this.projectsList = data.map((item) => ({
+ value: item.id,
+ text: item.name_with_namespace,
+ }));
+
+ if (!this.projectsList.length) {
+ this.noResultsText = __('No matching results');
+ }
+ } catch (e) {
+ this.noResultsText = __('Failed to load projects');
+ } finally {
+ this.isSearching = false;
}
},
- handleDropdownCloseClick() {
- this.$refs.dropdown.hide();
- },
- handleProjectSelect(project) {
- this.selectedProject = project.id === this.selectedProject?.id ? null : project;
- this.projectItemClick = true;
+ handleProjectSelect(items) {
+ // hack: simulate a single select to prevent the dropdown from closing
+ // todo: switch back to single select when https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2363 is fixed
+ this.selectedProjects = [items[items.length - 1]];
},
handleMoveClick() {
- this.$refs.dropdown.hide();
- this.$emit('move-issuable', this.selectedProject);
+ this.$refs.dropdown.close();
+ this.$emit(
+ 'move-issuable',
+ this.projects.find((item) => item.id === this.selectedProjects[0]),
+ );
+ },
+ handleDropdownHide() {
+ this.$emit('dropdown-close');
},
},
};
@@ -143,79 +113,45 @@ export default {
>
<gl-icon name="arrow-right" />
</div>
- <gl-dropdown
+ <gl-collapsible-listbox
ref="dropdown"
+ v-model="selectedProjects"
+ :items="projectsList"
:block="true"
- :disabled="moveInProgress || disabled"
- class="hide-collapsed"
- toggle-class="js-sidebar-dropdown-toggle"
- @shown="fetchProjects"
- @hide="handleDropdownHide"
+ :multiple="true"
+ :searchable="true"
+ :searching="isSearching"
+ :search-placeholder="__('Search project')"
+ :no-results-text="noResultsText"
+ :header-text="dropdownButtonTitle"
+ @hidden="handleDropdownHide"
+ @shown="triggerSearch"
+ @search="fetchProjects"
+ @select="handleProjectSelect"
>
- <template #button-content
- ><gl-loading-icon v-if="moveInProgress" size="sm" class="gl-mr-3" />{{
- dropdownButtonTitle
- }}</template
- >
- <gl-dropdown-form class="gl-pt-0">
- <div
- data-testid="header"
- class="gl-display-flex gl-pb-3 gl-border-1 gl-border-b-solid gl-border-gray-100"
- >
- <span class="gl-flex-grow-1 gl-text-center gl-font-weight-bold gl-py-1">{{
- dropdownHeaderTitle
- }}</span>
- <gl-button
- variant="link"
- icon="close"
- class="gl-mr-2 gl-w-auto! gl-p-2!"
- :aria-label="__('Close')"
- @click.prevent="handleDropdownCloseClick"
- />
- </div>
- <gl-search-box-by-type
- ref="searchInput"
- v-model.trim="searchKey"
- :placeholder="__('Search project')"
- :debounce="300"
- />
- <div data-testid="content" class="dropdown-content">
- <gl-loading-icon v-if="projectsListLoading" size="lg" class="gl-p-5" />
- <ul v-else>
- <gl-dropdown-item
- v-for="project in projects"
- :key="project.id"
- is-check-item
- :is-checked="isSelectedProject(project)"
- @click.stop.prevent="handleProjectSelect(project)"
- >{{ project.name_with_namespace }}</gl-dropdown-item
- >
- </ul>
- <div v-if="hasNoSearchResults" class="gl-text-center gl-p-3">
- {{ __('No matching results') }}
- </div>
- <div
- v-if="failedToLoadResults"
- data-testid="failed-load-results"
- class="gl-text-center gl-p-3"
- >
- {{ __('Failed to load projects') }}
- </div>
- </div>
- <div
- data-testid="footer"
- class="gl-pt-3 gl-px-3 gl-border-1 gl-border-t-solid gl-border-gray-100"
+ <template #toggle>
+ <gl-button
+ :loading="moveInProgress"
+ size="medium"
+ class="gl-w-full js-sidebar-dropdown-toggle hide-collapsed"
+ data-testid="dropdown-button"
+ :disabled="moveInProgress || disabled"
+ >{{ dropdownButtonTitle }}</gl-button
>
+ </template>
+ <template #footer>
+ <div data-testid="footer" class="gl-p-3">
<gl-button
category="primary"
variant="confirm"
- :disabled="!Boolean(selectedProject)"
- class="gl-w-full issuable-move-button"
+ :disabled="!Boolean(selectedProjects.length)"
+ class="gl-w-full"
+ data-testid="dropdown-move-button"
@click="handleMoveClick"
>{{ __('Move') }}</gl-button
>
</div>
- </gl-dropdown-form>
- </gl-dropdown>
+ </template>
+ </gl-collapsible-listbox>
</div>
</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_milestone.vue b/app/assets/javascripts/work_items/components/work_item_milestone.vue
index fe09105e173..9c6fa158169 100644
--- a/app/assets/javascripts/work_items/components/work_item_milestone.vue
+++ b/app/assets/javascripts/work_items/components/work_item_milestone.vue
@@ -1,13 +1,5 @@
<script>
-import {
- GlFormGroup,
- GlDropdown,
- GlDropdownItem,
- GlDropdownDivider,
- GlSkeletonLoader,
- GlSearchBoxByType,
- GlDropdownText,
-} from '@gitlab/ui';
+import { GlCollapsibleListbox, GlFormGroup, GlSkeletonLoader } from '@gitlab/ui';
import { debounce } from 'lodash';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import Tracking from '~/tracking';
@@ -22,7 +14,8 @@ import {
TRACKING_CATEGORY_SHOW,
} from '../constants';
-const noMilestoneId = 'no-milestone-id';
+export const noMilestoneId = 'no-milestone-id';
+const noMilestoneItem = { text: s__('WorkItem|No milestone'), value: noMilestoneId };
export default {
i18n: {
@@ -37,13 +30,9 @@ export default {
EXPIRED_TEXT: __('(expired)'),
},
components: {
+ GlCollapsibleListbox,
GlFormGroup,
- GlDropdown,
- GlDropdownItem,
- GlDropdownDivider,
GlSkeletonLoader,
- GlSearchBoxByType,
- GlDropdownText,
},
mixins: [Tracking.mixin()],
props: {
@@ -74,11 +63,23 @@ export default {
data() {
return {
localMilestone: this.workItemMilestone,
+ localMilestoneId: this.workItemMilestone?.id,
searchTerm: '',
shouldFetch: false,
updateInProgress: false,
- isFocused: false,
milestones: [],
+ dropdownGroups: [
+ {
+ text: this.$options.i18n.NO_MILESTONE,
+ textSrOnly: true,
+ options: [noMilestoneItem],
+ },
+ {
+ text: __('Milestones'),
+ textSrOnly: true,
+ options: [],
+ },
+ ],
};
},
computed: {
@@ -103,23 +104,29 @@ export default {
isLoadingMilestones() {
return this.$apollo.queries.milestones.loading;
},
- isNoMilestone() {
- return this.localMilestone?.id === noMilestoneId || !this.localMilestone?.id;
+ milestonesList() {
+ return (
+ this.milestones.map(({ id, title, expired }) => {
+ return {
+ value: id,
+ text: title,
+ expired,
+ };
+ }) ?? []
+ );
},
- dropdownClasses() {
- return {
- 'gl-text-gray-500!': this.canUpdate && this.isNoMilestone,
- 'is-not-focused': !this.isFocused,
- 'gl-min-w-20': true,
- };
+ toggleClasses() {
+ const toggleClasses = ['gl-max-w-full'];
+
+ if (this.localMilestoneId === noMilestoneId) {
+ toggleClasses.push('gl-text-gray-500!');
+ }
+ return toggleClasses;
},
},
watch: {
- workItemMilestone: {
- handler(newVal) {
- this.localMilestone = newVal;
- },
- deep: true,
+ milestones() {
+ this.dropdownGroups[1].options = this.milestonesList;
},
},
created() {
@@ -152,15 +159,11 @@ export default {
this.localMilestone = milestone;
},
onDropdownShown() {
- this.$refs.search.focusInput();
this.shouldFetch = true;
- this.isFocused = true;
},
onDropdownHide() {
- this.isFocused = false;
this.searchTerm = '';
this.shouldFetch = false;
- this.updateMilestone();
},
setSearchKey(value) {
this.searchTerm = value;
@@ -169,6 +172,9 @@ export default {
return this.localMilestone?.id === milestone?.id;
},
updateMilestone() {
+ this.localMilestone =
+ this.milestones.find(({ id }) => id === this.localMilestoneId) ?? noMilestoneItem;
+
if (this.workItemMilestone?.id === this.localMilestone?.id) {
return;
}
@@ -182,8 +188,7 @@ export default {
input: {
id: this.workItemId,
milestoneWidget: {
- milestoneId:
- this.localMilestone?.id === 'no-milestone-id' ? null : this.localMilestone?.id,
+ milestoneId: this.localMilestoneId === noMilestoneId ? null : this.localMilestoneId,
},
},
},
@@ -222,49 +227,45 @@ export default {
>
{{ dropdownText }}
</span>
- <gl-dropdown
+
+ <gl-collapsible-listbox
v-else
id="milestone-value"
- class="gl-pl-0 gl-max-w-full work-item-field-value"
- :toggle-class="dropdownClasses"
- :text="dropdownText"
+ v-model="localMilestoneId"
+ :items="dropdownGroups"
+ category="tertiary"
+ data-testid="work-item-milestone-dropdown"
+ class="gl-max-w-full"
+ :toggle-text="dropdownText"
:loading="updateInProgress"
+ :toggle-class="toggleClasses"
+ searchable
+ @select="updateMilestone"
@shown="onDropdownShown"
- @hide="onDropdownHide"
+ @hidden="onDropdownHide"
+ @search="debouncedSearchKeyUpdate"
>
- <template #header>
- <gl-search-box-by-type ref="search" :value="searchTerm" @input="debouncedSearchKeyUpdate" />
+ <template #list-item="{ item }">
+ {{ item.text }}
+ <span v-if="item.expired">{{ $options.i18n.EXPIRED_TEXT }}</span>
</template>
- <gl-dropdown-item
- data-testid="no-milestone"
- is-check-item
- :is-checked="isNoMilestone"
- @click="handleMilestoneClick({ id: 'no-milestone-id' })"
- >
- {{ $options.i18n.NO_MILESTONE }}
- </gl-dropdown-item>
- <gl-dropdown-divider />
- <gl-dropdown-text v-if="isLoadingMilestones">
- <gl-skeleton-loader :height="90">
+ <template #footer>
+ <gl-skeleton-loader v-if="isLoadingMilestones" :height="90">
<rect width="380" height="10" x="10" y="15" rx="4" />
<rect width="280" height="10" x="10" y="30" rx="4" />
<rect width="380" height="10" x="10" y="50" rx="4" />
<rect width="280" height="10" x="10" y="65" rx="4" />
</gl-skeleton-loader>
- </gl-dropdown-text>
- <template v-else-if="milestones.length">
- <gl-dropdown-item
- v-for="milestone in milestones"
- :key="milestone.id"
- is-check-item
- :is-checked="isMilestoneChecked(milestone)"
- @click="handleMilestoneClick(milestone)"
+
+ <div
+ v-else-if="!milestones.length"
+ aria-live="assertive"
+ class="gl-pl-7 gl-pr-5 gl-py-3 gl-font-base gl-text-gray-600"
+ data-testid="no-results-text"
>
- {{ milestone.title }}
- <template v-if="milestone.expired">{{ $options.i18n.EXPIRED_TEXT }}</template>
- </gl-dropdown-item>
+ {{ $options.i18n.NO_MATCHING_RESULTS }}
+ </div>
</template>
- <gl-dropdown-text v-else>{{ $options.i18n.NO_MATCHING_RESULTS }}</gl-dropdown-text>
- </gl-dropdown>
+ </gl-collapsible-listbox>
</gl-form-group>
</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_parent.vue b/app/assets/javascripts/work_items/components/work_item_parent.vue
index 23660f24783..74f0ec42905 100644
--- a/app/assets/javascripts/work_items/components/work_item_parent.vue
+++ b/app/assets/javascripts/work_items/components/work_item_parent.vue
@@ -61,7 +61,6 @@ export default {
searchStarted: false,
availableWorkItems: [],
localSelectedItem: this.parent?.id,
- isNotFocused: true,
oldParent: this.parent,
};
},
@@ -82,14 +81,6 @@ export default {
workItems() {
return this.availableWorkItems.map(({ id, title }) => ({ text: title, value: id }));
},
- listboxCategory() {
- return this.searchStarted ? 'secondary' : 'tertiary';
- },
- listboxClasses() {
- return {
- 'is-not-focused': this.isNotFocused && !this.searchStarted,
- };
- },
parentType() {
return SUPPORTED_PARENT_TYPE_MAP[this.workItemType];
},
@@ -184,19 +175,10 @@ export default {
},
onListboxShown() {
this.searchStarted = true;
- this.isNotFocused = false;
},
onListboxHide() {
this.searchStarted = false;
this.search = '';
- this.isNotFocused = true;
- },
- setListboxFocused() {
- // This is to match the caret behaviour of parent listbox
- // to the other dropdown fields of work items
- if (document.activeElement.parentElement.id !== 'work-item-parent-listbox-value') {
- this.isNotFocused = true;
- }
},
},
};
@@ -219,30 +201,20 @@ export default {
>
{{ listboxText }}
</span>
- <div
- v-else
- :class="{ 'gl-max-w-max-content': !workItemsMvc2Enabled }"
- @mouseover="isNotFocused = false"
- @mouseleave="setListboxFocused"
- @focusout="isNotFocused = true"
- @focusin="isNotFocused = false"
- >
+ <div v-else :class="{ 'gl-max-w-max-content': !workItemsMvc2Enabled }">
<gl-collapsible-listbox
id="work-item-parent-listbox-value"
class="gl-max-w-max-content"
data-testid="work-item-parent-listbox"
- block
searchable
- :no-caret="isNotFocused && !searchStarted"
is-check-centered
- :category="listboxCategory"
+ category="tertiary"
:searching="isLoading"
:header-text="$options.i18n.assignParentLabel"
:no-results-text="$options.i18n.noMatchingResults"
:loading="updateInProgress"
:items="workItems"
:toggle-text="listboxText"
- :toggle-class="listboxClasses"
:selected="localSelectedItem"
:reset-button-label="$options.i18n.unAssign"
@reset="unAssignParent"
diff --git a/app/assets/stylesheets/page_bundles/issuable.scss b/app/assets/stylesheets/page_bundles/issuable.scss
index 07614c5271a..d52cd45e575 100644
--- a/app/assets/stylesheets/page_bundles/issuable.scss
+++ b/app/assets/stylesheets/page_bundles/issuable.scss
@@ -114,29 +114,6 @@
}
}
-/*
- * Following overrides are done to prevent
- * legacy dropdown styles from influencing
- * GitLab UI components used within GlDropdown
- */
-.issuable-move-dropdown {
- .b-dropdown-form {
- @include gl-p-0;
- }
-
- .gl-search-box-by-type button.gl-clear-icon-button:hover {
- @include gl-bg-transparent;
-
- &:focus {
- @include gl-focus($inset: true);
- }
- }
-
- .issuable-move-button:not(.disabled):hover {
- @include gl-text-white;
- }
-}
-
.suggestion-footer {
font-size: 12px;
line-height: 15px;
diff --git a/app/assets/stylesheets/page_bundles/work_items.scss b/app/assets/stylesheets/page_bundles/work_items.scss
index f41a1f540e3..154803c7d88 100644
--- a/app/assets/stylesheets/page_bundles/work_items.scss
+++ b/app/assets/stylesheets/page_bundles/work_items.scss
@@ -67,6 +67,7 @@ $work-item-sticky-header-height: 52px;
}
}
+//TODO: remove all the styles related to `gl-dropdown` when all `.work-item-dropdown`s are migrated
.work-item-dropdown {
// duplicate classname because we are fighting with gl-button styles
.gl-dropdown-toggle.gl-dropdown-toggle {
@@ -95,24 +96,25 @@ $work-item-sticky-header-height: 52px;
// need to override the listbox styles to match with dropdown
// till the dropdown are converted to listbox
- .gl-new-dropdown-toggle {
+ .gl-new-dropdown-toggle.gl-new-dropdown-toggle {
&:hover,
&:focus {
- background: none !important;
box-shadow: $work-item-field-inset-shadow;
background-color: $input-bg;
- }
- .is-not-focused {
- &.gl-new-dropdown-button-text {
- margin: 0 0.25rem;
+ .gl-dark & {
+ // $input-bg is overridden in dark mode but that does not
+ // work in page bundles currently, manually override here
+ background-color: var(--gray-50, $input-bg);
}
}
- }
- .gl-new-dropdown-toggle.is-not-focused {
- .gl-new-dropdown-button-text {
- margin: 0 0.25rem;
+ &:not(:hover, :focus) {
+ box-shadow: none;
+
+ .gl-new-dropdown-chevron {
+ visibility: hidden;
+ }
}
}
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 1989d6ab3d5..319cec6f140 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -38,7 +38,7 @@ module ClustersHelper
environment_scope: cluster.environment_scope,
base_domain: cluster.base_domain,
auto_devops_help_path: help_page_path('topics/autodevops/index'),
- external_endpoint_help_path: help_page_path('user/project/clusters/gitlab_managed_clusters.md', anchor: 'base-domain')
+ external_endpoint_help_path: help_page_path('user/project/clusters/gitlab_managed_clusters', anchor: 'base-domain')
}
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 6d48dac5c6d..28bdd3e69b6 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -76,8 +76,8 @@ module EnvironmentsHelper
def static_metrics_data
{
- 'documentation_path' => help_page_path('administration/monitoring/prometheus/index.md'),
- 'add_dashboard_documentation_path' => help_page_path('operations/metrics/dashboards/index.md', anchor: 'add-a-new-dashboard-to-your-project'),
+ 'documentation_path' => help_page_path('administration/monitoring/prometheus/index'),
+ 'add_dashboard_documentation_path' => help_page_path('operations/metrics/dashboards/index', anchor: 'add-a-new-dashboard-to-your-project'),
'empty_getting_started_svg_path' => image_path('illustrations/monitoring/getting_started.svg'),
'empty_loading_svg_path' => image_path('illustrations/monitoring/loading.svg'),
'empty_no_data_svg_path' => image_path('illustrations/monitoring/no_data.svg'),
diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb
index 2582d6fcc34..f2d393f1f77 100644
--- a/app/helpers/ide_helper.rb
+++ b/app/helpers/ide_helper.rb
@@ -5,7 +5,7 @@ module IdeHelper
def ide_data(project:, fork_info:, params:)
base_data = {
'use-new-web-ide' => use_new_web_ide?.to_s,
- 'new-web-ide-help-page-path' => help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'),
+ 'new-web-ide-help-page-path' => help_page_path('user/project/web_ide/index', anchor: 'vscode-reimplementation'),
'sign-in-path' => new_session_path(current_user),
'user-preferences-path' => profile_preferences_path
}.merge(use_new_web_ide? ? new_ide_data(project: project) : legacy_ide_data(project: project))
@@ -71,16 +71,16 @@ module IdeHelper
'switch-editor-svg-path': image_path('illustrations/rocket-launch-md.svg'),
'promotion-svg-path': image_path('illustrations/web-ide_promotion.svg'),
'ci-help-page-path' => help_page_path('ci/quick_start/index'),
- 'web-ide-help-page-path' => help_page_path('user/project/web_ide/index.md'),
+ 'web-ide-help-page-path' => help_page_path('user/project/web_ide/index'),
'render-whitespace-in-code': current_user.render_whitespace_in_code.to_s,
'default-branch' => project && project.default_branch,
'project' => convert_to_project_entity_json(project),
'enable-environments-guidance' => enable_environments_guidance?(project).to_s,
'preview-markdown-path' => project && preview_markdown_path(project),
'web-terminal-svg-path' => image_path('illustrations/web-ide_promotion.svg'),
- 'web-terminal-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'interactive-web-terminals-for-the-web-ide'),
- 'web-terminal-config-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'web-ide-configuration-file'),
- 'web-terminal-runners-help-path' => help_page_path('user/project/web_ide/index.md', anchor: 'runner-configuration')
+ 'web-terminal-help-path' => help_page_path('user/project/web_ide/index', anchor: 'interactive-web-terminals-for-the-web-ide'),
+ 'web-terminal-config-help-path' => help_page_path('user/project/web_ide/index', anchor: 'web-ide-configuration-file'),
+ 'web-terminal-runners-help-path' => help_page_path('user/project/web_ide/index', anchor: 'runner-configuration')
}
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 7eaa0d41297..4cb6260c03e 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -187,7 +187,7 @@ module MergeRequestsHelper
endpoint_batch: diffs_batch_project_json_merge_request_path(project, merge_request, 'json', params),
endpoint_coverage: @coverage_path,
endpoint_diff_for_path: diff_for_path_namespace_project_merge_request_path(format: 'json', id: merge_request.iid, namespace_id: project.namespace.to_param, project_id: project.path),
- help_page_path: help_page_path('user/project/merge_requests/reviews/suggestions.md'),
+ help_page_path: help_page_path('user/project/merge_requests/reviews/suggestions'),
current_user_data: @current_user_data,
update_current_user_path: @update_current_user_path,
project_path: project_path(merge_request.project),
@@ -219,7 +219,7 @@ module MergeRequestsHelper
source_project_full_path: merge_request.source_project&.full_path,
source_project_default_url: merge_request.source_project && default_url_to_repo(merge_request.source_project),
target_branch: merge_request.target_branch,
- reviewing_docs_path: help_page_path('user/project/merge_requests/reviews/index.md', anchor: "checkout-merge-requests-locally-through-the-head-ref")
+ reviewing_docs_path: help_page_path('user/project/merge_requests/reviews/index', anchor: "checkout-merge-requests-locally-through-the-head-ref")
}
end
diff --git a/app/helpers/operations_helper.rb b/app/helpers/operations_helper.rb
index 8528f5f04f7..d8b3cc3b36e 100644
--- a/app/helpers/operations_helper.rb
+++ b/app/helpers/operations_helper.rb
@@ -21,7 +21,7 @@ module OperationsHelper
'prometheus_authorization_key' => @project.alerting_setting&.token,
'prometheus_api_url' => prometheus_integration.api_url,
'prometheus_url' => notify_project_prometheus_alerts_url(@project, format: :json),
- 'alerts_setup_url' => help_page_path('operations/incident_management/integrations.md', anchor: 'configuration'),
+ 'alerts_setup_url' => help_page_path('operations/incident_management/integrations', anchor: 'configuration'),
'alerts_usage_url' => project_alert_management_index_path(@project),
'disabled' => disabled.to_s,
'project_path' => @project.full_path,
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 656d35e927d..d5bbd3a081d 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -122,8 +122,8 @@ module PreferencesHelper
def integration_views
[].tap do |views|
- views << { name: 'gitpod', message: gitpod_enable_description, message_url: gitpod_url_placeholder, help_link: help_page_path('integration/gitpod.md') } if Gitlab::CurrentSettings.gitpod_enabled
- views << { name: 'sourcegraph', message: sourcegraph_url_message, message_url: Gitlab::CurrentSettings.sourcegraph_url, help_link: help_page_path('user/profile/preferences.md', anchor: 'sourcegraph') } if Gitlab::Sourcegraph.feature_available? && Gitlab::CurrentSettings.sourcegraph_enabled
+ views << { name: 'gitpod', message: gitpod_enable_description, message_url: gitpod_url_placeholder, help_link: help_page_path('integration/gitpod') } if Gitlab::CurrentSettings.gitpod_enabled
+ views << { name: 'sourcegraph', message: sourcegraph_url_message, message_url: Gitlab::CurrentSettings.sourcegraph_url, help_link: help_page_path('user/profile/preferences', anchor: 'sourcegraph') } if Gitlab::Sourcegraph.feature_available? && Gitlab::CurrentSettings.sourcegraph_enabled
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 7dc0f63924f..c3b9a0a421b 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -187,7 +187,7 @@ module ProjectsHelper
end
def link_to_autodeploy_doc
- link_to _('About auto deploy'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-deploy'), target: '_blank', rel: 'noopener'
+ link_to _('About auto deploy'), help_page_path('topics/autodevops/stages', anchor: 'auto-deploy'), target: '_blank', rel: 'noopener'
end
def autodeploy_flash_notice(branch_name)
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index ec1dc96c2e3..5765d08dfb3 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -61,7 +61,7 @@ module Clusters
'clusters-path': clusterable.index_path,
'dashboard-endpoint': clusterable.metrics_dashboard_path(cluster),
'documentation-path': help_page_path('user/infrastructure/clusters/manage/clusters_health'),
- 'add-dashboard-documentation-path': help_page_path('operations/metrics/dashboards/index.md', anchor: 'add-a-new-dashboard-to-your-project'),
+ 'add-dashboard-documentation-path': help_page_path('operations/metrics/dashboards/index', anchor: 'add-a-new-dashboard-to-your-project'),
'empty-getting-started-svg-path': image_path('illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path': image_path('illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path': image_path('illustrations/monitoring/no_data.svg'),
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 657af578c7f..9a55e761bf0 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -73,11 +73,11 @@ class IssueEntity < IssuableEntity
end
expose :confidential_issues_docs_path, if: -> (issue) { issue.confidential? } do |issue|
- help_page_path('user/project/issues/confidential_issues.md')
+ help_page_path('user/project/issues/confidential_issues')
end
expose :locked_discussion_docs_path, if: -> (issue) { issue.discussion_locked? } do |issue|
- help_page_path('user/discussions/index.md', anchor: 'prevent-comments-by-locking-an-issue')
+ help_page_path('user/discussions/index', anchor: 'prevent-comments-by-locking-an-issue')
end
expose :is_project_archived do |issue|
@@ -85,7 +85,7 @@ class IssueEntity < IssuableEntity
end
expose :archived_project_docs_path, if: -> (issue) { issue.project.archived? } do |issue|
- help_page_path('user/project/settings/index.md', anchor: 'archive-a-project')
+ help_page_path('user/project/settings/index', anchor: 'archive-a-project')
end
expose :issue_email_participants do |issue|
diff --git a/app/serializers/merge_request_noteable_entity.rb b/app/serializers/merge_request_noteable_entity.rb
index 44f51f43998..04b801e29ad 100644
--- a/app/serializers/merge_request_noteable_entity.rb
+++ b/app/serializers/merge_request_noteable_entity.rb
@@ -52,7 +52,7 @@ class MergeRequestNoteableEntity < IssuableEntity
end
expose :locked_discussion_docs_path, if: -> (merge_request) { merge_request.discussion_locked? } do |merge_request|
- help_page_path('user/discussions/index.md', anchor: 'prevent-comments-by-locking-an-issue')
+ help_page_path('user/discussions/index', anchor: 'prevent-comments-by-locking-an-issue')
end
expose :is_project_archived do |merge_request|
@@ -62,7 +62,7 @@ class MergeRequestNoteableEntity < IssuableEntity
expose :project_id
expose :archived_project_docs_path, if: -> (merge_request) { merge_request.project.archived? } do |merge_request|
- help_page_path('user/project/settings/index.md', anchor: 'archive-a-project')
+ help_page_path('user/project/settings/index', anchor: 'archive-a-project')
end
private
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index cf984207ad1..95072ae815e 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -48,15 +48,15 @@ class MergeRequestWidgetEntity < Grape::Entity
end
expose :conflicts_docs_path do |merge_request|
- help_page_path('user/project/merge_requests/conflicts.md')
+ help_page_path('user/project/merge_requests/conflicts')
end
expose :reviewing_and_managing_merge_requests_docs_path do |merge_request|
- help_page_path('user/project/merge_requests/reviews/index.md', anchor: "checkout-merge-requests-locally-through-the-head-ref")
+ help_page_path('user/project/merge_requests/reviews/index', anchor: "checkout-merge-requests-locally-through-the-head-ref")
end
expose :merge_request_pipelines_docs_path do |merge_request|
- help_page_path('ci/pipelines/merge_request_pipelines.md')
+ help_page_path('ci/pipelines/merge_request_pipelines')
end
expose :ci_environments_status_path do |merge_request|
@@ -129,7 +129,7 @@ class MergeRequestWidgetEntity < Grape::Entity
end
expose :security_reports_docs_path do |merge_request|
- help_page_path('user/application_security/index.md', anchor: 'view-security-scan-information-in-merge-requests')
+ help_page_path('user/application_security/index', anchor: 'view-security-scan-information-in-merge-requests')
end
expose :enabled_reports do |merge_request|
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index e5e39247dbf..2d81bbdaa3b 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -92,7 +92,7 @@ module Projects
end
def ambiguous_head_documentation_link
- url = Rails.application.routes.url_helpers.help_page_path('user/project/repository/branches/index.md', anchor: 'error-ambiguous-head-branch-exists')
+ url = Rails.application.routes.url_helpers.help_page_path('user/project/repository/branches/index', anchor: 'error-ambiguous-head-branch-exists')
format('<a href="%{url}" target="_blank" rel="noopener noreferrer">', url: url)
end
diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml
index 2d45391a839..a9bc8ab9d32 100644
--- a/app/views/admin/application_settings/_email.html.haml
+++ b/app/views/admin/application_settings/_email.html.haml
@@ -10,7 +10,7 @@
= f.label :commit_email_hostname, _('Custom hostname (for private commit emails)'), class: 'label-bold'
= f.text_field :commit_email_hostname, class: 'form-control gl-form-input'
.form-text.text-muted
- - commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('administration/settings/email.md', anchor: 'custom-hostname-for-private-commit-emails'), target: '_blank', rel: 'noopener noreferrer'
+ - commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('administration/settings/email', anchor: 'custom-hostname-for-private-commit-emails'), target: '_blank', rel: 'noopener noreferrer'
= _("Hostname used in private commit emails. %{learn_more}").html_safe % { learn_more: commit_email_hostname_docs_link }
= render_if_exists 'admin/application_settings/email_additional_text_setting', form: f
diff --git a/app/views/admin/application_settings/_error_tracking.html.haml b/app/views/admin/application_settings/_error_tracking.html.haml
index 6754dd99bbc..ab4ed9917a0 100644
--- a/app/views/admin/application_settings/_error_tracking.html.haml
+++ b/app/views/admin/application_settings/_error_tracking.html.haml
@@ -7,8 +7,8 @@
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary
- = _('Allows projects to track errors using an Opstrace integration.').html_safe % { link: help_page_path('operations/error_tracking.md') }
- = link_to _('Learn more.'), help_page_path('operations/error_tracking.md'), target: '_blank', rel: 'noopener noreferrer'
+ = _('Allows projects to track errors using an Opstrace integration.').html_safe % { link: help_page_path('operations/error_tracking') }
+ = link_to _('Learn more.'), help_page_path('operations/error_tracking'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
diff --git a/app/views/admin/application_settings/_floc.html.haml b/app/views/admin/application_settings/_floc.html.haml
index e1576e84e66..27df417d225 100644
--- a/app/views/admin/application_settings/_floc.html.haml
+++ b/app/views/admin/application_settings/_floc.html.haml
@@ -7,7 +7,7 @@
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
%p.gl-text-secondary
- - floc_link_url = help_page_path('administration/settings/floc.md')
+ - floc_link_url = help_page_path('administration/settings/floc')
- floc_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: floc_link_url }
= html_escape(s_('FloC|Configure whether you want to participate in FLoC. %{floc_link_start}What is FLoC?%{floc_link_end}')) % { floc_link_start: floc_link_start, floc_link_end: '</a>'.html_safe }
diff --git a/app/views/admin/application_settings/_gitlab_shell_operation_limits.html.haml b/app/views/admin/application_settings/_gitlab_shell_operation_limits.html.haml
index 64549b97bd1..8529accff8e 100644
--- a/app/views/admin/application_settings/_gitlab_shell_operation_limits.html.haml
+++ b/app/views/admin/application_settings/_gitlab_shell_operation_limits.html.haml
@@ -6,7 +6,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= s_('ShellOperations|Limit the number of Git operations a user can perform per minute, per repository.')
- = link_to _('Learn more.'), help_page_path('administration/settings/rate_limits_on_git_ssh_operations.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/rate_limits_on_git_ssh_operations'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-gitlab-shell-operation-limits-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
diff --git a/app/views/admin/application_settings/_localization.html.haml b/app/views/admin/application_settings/_localization.html.haml
index 25038e6f221..e1eb0ba84eb 100644
--- a/app/views/admin/application_settings/_localization.html.haml
+++ b/app/views/admin/application_settings/_localization.html.haml
@@ -7,7 +7,7 @@
= f.select :first_day_of_week, first_day_of_week_choices, {}, class: 'form-control'
.form-text.text-muted
= _('Default first day of the week in calendars and date pickers.')
- = link_to _('Learn more.'), help_page_path('administration/settings/localization.md', anchor: 'change-the-default-first-day-of-the-week'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/localization', anchor: 'change-the-default-first-day-of-the-week'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= f.label :time_tracking, _('Time tracking'), class: 'label-bold'
diff --git a/app/views/admin/application_settings/_projects_api_limits.html.haml b/app/views/admin/application_settings/_projects_api_limits.html.haml
index dde8ab07958..d84df972c6d 100644
--- a/app/views/admin/application_settings/_projects_api_limits.html.haml
+++ b/app/views/admin/application_settings/_projects_api_limits.html.haml
@@ -6,7 +6,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Set the per-IP address rate limit applicable to unauthenticated requests for getting a list of projects via the API.')
- = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_projects_api.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_projects_api'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= gitlab_ui_form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-projects-api-limits-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
diff --git a/app/views/admin/application_settings/_runner_registrars_form.html.haml b/app/views/admin/application_settings/_runner_registrars_form.html.haml
index b112c273aad..36bab2f6650 100644
--- a/app/views/admin/application_settings/_runner_registrars_form.html.haml
+++ b/app/views/admin/application_settings/_runner_registrars_form.html.haml
@@ -7,7 +7,7 @@
= s_('Runners|Runner version management')
%span.form-text.gl-mb-3.gl-mt-0
- help_text = s_('Runners|Official runner version data is periodically fetched from GitLab.com to determine whether the runners need upgrades.')
- - learn_more_link = link_to _('Learn more.'), help_page_path('ci/runners/runners_scope.md', anchor: 'determine-which-runners-need-to-be-upgraded'), target: '_blank', rel: 'noopener noreferrer'
+ - learn_more_link = link_to _('Learn more.'), help_page_path('ci/runners/runners_scope', anchor: 'determine-which-runners-need-to-be-upgraded'), target: '_blank', rel: 'noopener noreferrer'
= f.gitlab_ui_checkbox_component :update_runner_versions_enabled,
s_('Runners|Fetch GitLab Runner release version data from GitLab.com'),
help_text: '%{help_text} %{learn_more_link}'.html_safe % { help_text: help_text, learn_more_link: learn_more_link }
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index dad0bf08bb0..d84fbe94f65 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -66,7 +66,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Set sign-in restrictions for all users.')
- = link_to _('Learn more.'), help_page_path('administration/settings/sign_in_restrictions.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/sign_in_restrictions'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'signin'
@@ -78,7 +78,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Add a Terms of Service agreement and Privacy Policy for users of this GitLab instance.')
- = link_to _('Learn more.'), help_page_path('administration/settings/terms.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/terms'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'terms'
@@ -95,7 +95,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Set the maximum session time for a web terminal.')
- = link_to _('How do I use a web terminal?'), help_page_path('ci/environments/index.md', anchor: 'web-terminals-deprecated'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('How do I use a web terminal?'), help_page_path('ci/environments/index', anchor: 'web-terminals-deprecated'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'terminal'
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
index fb22df9d066..ae5f7a5cec3 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -22,7 +22,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Set limits for web and API requests.')
- = link_to _('Learn more.'), help_page_path('administration/settings/user_and_ip_rate_limits.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/user_and_ip_rate_limits'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'ip_limits'
@@ -34,7 +34,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Set rate limits for package registry API requests that supersede the general user and IP rate limits.')
- = link_to _('Learn more.'), help_page_path('administration/settings/package_registry_rate_limits.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/package_registry_rate_limits'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render partial: 'network_rate_limits', locals: { anchor: 'js-packages-limits-settings', setting_fragment: 'packages_api' }
@@ -68,7 +68,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Configure specific limits for deprecated API requests that supersede the general user and IP rate limits.')
- = link_to _('Which API requests are affected?'), help_page_path('administration/settings/deprecated_api_rate_limits.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Which API requests are affected?'), help_page_path('administration/settings/deprecated_api_rate_limits'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render partial: 'network_rate_limits', locals: { anchor: 'js-deprecated-limits-settings', setting_fragment: 'deprecated_api' }
@@ -80,7 +80,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Configure specific limits for Git LFS requests that supersede the general user and IP rate limits.')
- = link_to _('Learn more.'), help_page_path('administration/settings/git_lfs_rate_limits.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/git_lfs_rate_limits'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'git_lfs_limits'
@@ -96,7 +96,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= s_('OutboundRequests|Allow requests to the local network from hooks and integrations.')
- = link_to _('Learn more.'), help_page_path('security/webhooks.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('security/webhooks'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'outbound'
@@ -108,7 +108,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Rate limit access to specified paths.')
- = link_to _('Learn more.'), help_page_path('administration/settings/protected_paths.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/protected_paths'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'protected_paths'
@@ -121,7 +121,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Limit the number of issues and epics per minute a user can create through web and API requests.')
- = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_issues_creation.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_issues_creation'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'issue_limits'
@@ -133,7 +133,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Set the per-user rate limit for notes created by web or API requests.')
- = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_notes_creation.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_notes_creation'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'note_limits'
@@ -145,7 +145,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Set the per-user rate limit for getting a user by ID via the API.')
- = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_users_api.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_users_api'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'users_api_limits'
@@ -159,7 +159,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Set per-user rate limits for imports and exports of projects and groups.')
- = link_to _('Learn more.'), help_page_path('administration/settings/import_export_rate_limits.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/import_export_rate_limits'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'import_export_limits'
@@ -171,7 +171,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Limit the number of pipeline creation requests per minute. This limit includes pipelines created through the UI, the API, and by background processing.')
- = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_pipelines_creation.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/rate_limit_on_pipelines_creation'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'pipeline_limits'
diff --git a/app/views/admin/application_settings/preferences.html.haml b/app/views/admin/application_settings/preferences.html.haml
index 4590b6f4586..3543e1d918a 100644
--- a/app/views/admin/application_settings/preferences.html.haml
+++ b/app/views/admin/application_settings/preferences.html.haml
@@ -33,7 +33,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Additional text for the sign-in and Help page.')
- = link_to _('Learn more.'), help_page_path('administration/settings/help_page.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/help_page'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'help_page'
@@ -56,7 +56,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Adjust how frequently the GitLab UI polls for updates.')
- = link_to _('Learn more.'), help_page_path('administration/polling.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/polling'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'realtime'
@@ -69,7 +69,7 @@
%p.gl-text-secondary
= _('Configure Gitaly timeouts.')
%span
- = link_to _('Learn more.'), help_page_path('administration/settings/gitaly_timeouts.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/gitaly_timeouts'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'gitaly'
@@ -93,7 +93,7 @@
%p.gl-text-secondary
= _('Limit the size of Sidekiq jobs stored in Redis.')
%span
- = link_to _('Learn more.'), help_page_path('administration/settings/sidekiq_job_limits.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/sidekiq_job_limits'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'sidekiq_job_limits'
@@ -106,6 +106,6 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= s_('TerraformLimits|Limits for Terraform features')
- = link_to s_('TerraformLimits|Learn more about Terraform limits.'), help_page_path('administration/settings/terraform_limits.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to s_('TerraformLimits|Learn more about Terraform limits.'), help_page_path('administration/settings/terraform_limits'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'terraform_limits'
diff --git a/app/views/admin/application_settings/reporting.html.haml b/app/views/admin/application_settings/reporting.html.haml
index 91fabb505c2..49279c4584b 100644
--- a/app/views/admin/application_settings/reporting.html.haml
+++ b/app/views/admin/application_settings/reporting.html.haml
@@ -25,7 +25,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Receive notification of abuse reports by email.')
- = link_to _('Learn more.'), help_page_path('administration/review_abuse_reports.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/review_abuse_reports'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'abuse'
diff --git a/app/views/admin/application_settings/repository.html.haml b/app/views/admin/application_settings/repository.html.haml
index a46275ff9d5..0b31da36804 100644
--- a/app/views/admin/application_settings/repository.html.haml
+++ b/app/views/admin/application_settings/repository.html.haml
@@ -22,7 +22,7 @@
= expanded_by_default? ? 'Collapse' : 'Expand'
%p.gl-text-secondary
= _('Configure repository mirroring.')
- = link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render partial: 'repository_mirrors_form'
@@ -34,7 +34,7 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Configure repository storage.')
- = link_to _('Learn more.'), help_page_path('administration/repository_storage_paths.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/repository_storage_paths'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'repository_storage'
@@ -45,9 +45,9 @@
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
- - repository_checks_link_url = help_page_path('administration/repository_checks.md')
+ - repository_checks_link_url = help_page_path('administration/repository_checks')
- repository_checks_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: repository_checks_link_url }
- - housekeeping_link_url = help_page_path('administration/housekeeping.md')
+ - housekeeping_link_url = help_page_path('administration/housekeeping')
- housekeeping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: housekeeping_link_url }
= html_escape(s_('Configure %{repository_checks_link_start}repository checks%{link_end} and %{housekeeping_link_start}housekeeping%{link_end} on repositories.')) % { repository_checks_link_start: repository_checks_link_start, housekeeping_link_start: housekeeping_link_start, link_end: '</a>'.html_safe }
.settings-content
@@ -61,6 +61,6 @@
= expanded_by_default? ? _('Collapse') : _('Expand')
%p.gl-text-secondary
= _('Serve repository static objects (for example, archives and blobs) from external storage.')
- = link_to _('Learn more.'), help_page_path('administration/static_objects_external_storage.md'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/static_objects_external_storage'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'repository_static_objects'
diff --git a/app/views/admin/dev_ops_report/_score.html.haml b/app/views/admin/dev_ops_report/_score.html.haml
index a504563ad91..59cb30e8447 100644
--- a/app/views/admin/dev_ops_report/_score.html.haml
+++ b/app/views/admin/dev_ops_report/_score.html.haml
@@ -1,6 +1,6 @@
- service_ping_enabled = Gitlab::CurrentSettings.usage_ping_enabled
- if !service_ping_enabled
- #js-devops-service-ping-disabled{ data: { is_admin: current_user&.admin.to_s, empty_state_svg_path: image_path('illustrations/convdev/convdev_no_index.svg'), enable_service_ping_path: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), docs_link: help_page_path('development/internal_analytics/service_ping/index.md') } }
+ #js-devops-service-ping-disabled{ data: { is_admin: current_user&.admin.to_s, empty_state_svg_path: image_path('illustrations/convdev/convdev_no_index.svg'), enable_service_ping_path: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), docs_link: help_page_path('development/internal_analytics/service_ping/index') } }
- else
#js-devops-score{ data: { devops_score_metrics: devops_score_metrics(@metric).to_json, no_data_image_path: image_path('dev_ops_report_no_data.svg'), devops_score_intro_image_path: image_path('dev_ops_report_overview.svg') } }
diff --git a/app/views/groups/_import_group_from_another_instance_panel.html.haml b/app/views/groups/_import_group_from_another_instance_panel.html.haml
index 38dc850553b..6c5a27e68c4 100644
--- a/app/views/groups/_import_group_from_another_instance_panel.html.haml
+++ b/app/views/groups/_import_group_from_another_instance_panel.html.haml
@@ -24,7 +24,7 @@
= render Pajamas::AlertComponent.new(dismissible: false,
variant: :warning) do |c|
- c.with_body do
- - docs_link = link_to('', help_page_path('user/group/import/index.md', anchor: 'migrated-group-items'), target: '_blank', rel: 'noopener noreferrer')
+ - docs_link = link_to('', help_page_path('user/group/import/index', anchor: 'migrated-group-items'), target: '_blank', rel: 'noopener noreferrer')
= safe_format(s_('GroupsNew|Not all group items are migrated. %{docs_link_start}What items are migrated%{docs_link_end}?'), tag_pair(docs_link, :docs_link_start, :docs_link_end))
%p.gl-mt-3
diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml
index 982199d3d6f..031869cc60e 100644
--- a/app/views/profiles/gpg_keys/index.html.haml
+++ b/app/views/profiles/gpg_keys/index.html.haml
@@ -28,7 +28,7 @@
%h4.gl-mt-0
= _('Add a GPG key')
%p
- - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/repository/signed_commits/gpg.md') }
+ - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/repository/signed_commits/gpg') }
= _('Add a GPG key for secure access to GitLab. %{help_link_start}Learn more%{help_link_end}.').html_safe % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
= render 'form'
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 7ba42274f88..f80cd8cddc5 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -25,7 +25,7 @@
-# TODO: Remove this conditional when https://gitlab.com/gitlab-org/gitlab/-/issues/324764 is resolved.
- if Feature.enabled?(:disable_ssh_key_used_tracking)
= _('Unavailable')
- = link_to sprite_icon('question-o'), help_page_path('user/ssh.md', anchor: 'view-your-accounts-ssh-keys')
+ = link_to sprite_icon('question-o'), help_page_path('user/ssh', anchor: 'view-your-accounts-ssh-keys')
- else
= key.last_used_at ? time_ago_with_tooltip(key.last_used_at) : _('Never')
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 0cd41788a53..8477d87a587 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -30,7 +30,7 @@
%h4.gl-mt-0
= _('Add an SSH key')
%p
- - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/ssh.md') }
+ - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/ssh') }
= _('Add an SSH key for secure access to GitLab. %{help_link_start}Learn more%{help_link_end}.').html_safe % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
= render 'form'
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index c12f6907afb..0457561b283 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -35,7 +35,7 @@
path: profile_personal_access_tokens_path,
token: @personal_access_token,
scopes: @scopes,
- help_path: help_page_path('user/profile/personal_access_tokens.md', anchor: 'personal-access-token-scopes')
+ help_path: help_page_path('user/profile/personal_access_tokens', anchor: 'personal-access-token-scopes')
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json } }
diff --git a/app/views/projects/blob/_pipeline_tour_success.html.haml b/app/views/projects/blob/_pipeline_tour_success.html.haml
index f645d23aa1c..be2654c9b86 100644
--- a/app/views/projects/blob/_pipeline_tour_success.html.haml
+++ b/app/views/projects/blob/_pipeline_tour_success.html.haml
@@ -1,6 +1,6 @@
.js-success-pipeline-modal{ data: { 'commit-cookie': suggest_pipeline_commit_cookie_name,
'go-to-pipelines-path': project_pipelines_path(@project),
'project-merge-requests-path': project_merge_requests_path(@project),
- 'example-link': help_page_path('ci/examples/index.md'),
+ 'example-link': help_page_path('ci/examples/index'),
'code-quality-link': help_page_path('ci/testing/code_quality'),
'human-access': @project.team.human_max_access(current_user&.id) } }
diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml
index 6aefc2eaa8b..d4a775728e3 100644
--- a/app/views/projects/commit/_signature_badge.html.haml
+++ b/app/views/projects/commit/_signature_badge.html.haml
@@ -17,17 +17,17 @@
- if signature.x509?
= render partial: "projects/commit/x509/certificate_details", locals: { signature: signature }
- = link_to(_('Learn more about X.509 signed commits'), help_page_path('user/project/repository/signed_commits/x509.md'), class: 'gl-link gl-display-block')
+ = link_to(_('Learn more about X.509 signed commits'), help_page_path('user/project/repository/signed_commits/x509'), class: 'gl-link gl-display-block')
- elsif signature.ssh?
= _('SSH key fingerprint:')
%span.gl-font-monospace= signature.key_fingerprint_sha256 || _('Unknown')
- = link_to(_('Learn about signing commits with SSH keys.'), help_page_path('user/project/repository/signed_commits/ssh.md'), class: 'gl-link gl-display-block gl-mt-3')
+ = link_to(_('Learn about signing commits with SSH keys.'), help_page_path('user/project/repository/signed_commits/ssh'), class: 'gl-link gl-display-block gl-mt-3')
- else
= _('GPG Key ID:')
%span.gl-font-monospace= signature.gpg_key_primary_keyid
- = link_to(_('Learn about signing commits'), help_page_path('user/project/repository/signed_commits/index.md'), class: 'gl-link gl-display-block gl-mt-3')
+ = link_to(_('Learn about signing commits'), help_page_path('user/project/repository/signed_commits/index'), class: 'gl-link gl-display-block gl-mt-3')
%a.signature-badge.gl-display-inline-block.gl-ml-4{ role: 'button', tabindex: 0, data: { toggle: 'popover', html: 'true', placement: 'top', title: title, content: content } }
= gl_badge_tag label, variant: variant
diff --git a/db/post_migrate/20231016001000_fix_design_user_mentions_design_id_note_id_index_for_self_managed.rb b/db/post_migrate/20231016001000_fix_design_user_mentions_design_id_note_id_index_for_self_managed.rb
new file mode 100644
index 00000000000..454158ecbee
--- /dev/null
+++ b/db/post_migrate/20231016001000_fix_design_user_mentions_design_id_note_id_index_for_self_managed.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class FixDesignUserMentionsDesignIdNoteIdIndexForSelfManaged < Gitlab::Database::Migration[2.1]
+ include Gitlab::Database::MigrationHelpers::ConvertToBigint
+
+ disable_ddl_transaction!
+
+ TABLE_NAME = 'design_user_mentions'
+ INDEX_NAME = 'design_user_mentions_on_design_id_and_note_id_unique_index'
+
+ def up
+ return if com_or_dev_or_test_but_not_jh?
+ return if index_exists?(TABLE_NAME, [:design_id, :note_id], unique: true, name: INDEX_NAME)
+
+ add_concurrent_index TABLE_NAME, [:design_id, :note_id], unique: true, name: "#{INDEX_NAME}_int8"
+
+ with_lock_retries(raise_on_exhaustion: true) do
+ execute "LOCK TABLE #{TABLE_NAME} IN ACCESS EXCLUSIVE MODE"
+
+ execute "DROP INDEX IF EXISTS #{INDEX_NAME}"
+ rename_index TABLE_NAME, "#{INDEX_NAME}_int8", INDEX_NAME
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20231024080150_cleanup_ci_sources_pipelines_pipeline_id_bigint.rb b/db/post_migrate/20231024080150_cleanup_ci_sources_pipelines_pipeline_id_bigint.rb
new file mode 100644
index 00000000000..6aa8019a182
--- /dev/null
+++ b/db/post_migrate/20231024080150_cleanup_ci_sources_pipelines_pipeline_id_bigint.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class CleanupCiSourcesPipelinesPipelineIdBigint < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ TABLE = :ci_sources_pipelines
+ REFERENCING_TABLE = :ci_pipelines
+ COLUMNS = [:pipeline_id, :source_pipeline_id]
+
+ def up
+ with_lock_retries(raise_on_exhaustion: true) do
+ lock_tables(:ci_pipelines, TABLE)
+ cleanup_conversion_of_integer_to_bigint(TABLE, COLUMNS) # rubocop:disable Migration/WithLockRetriesDisallowedMethod
+ end
+ end
+
+ def down
+ restore_conversion_of_integer_to_bigint(TABLE, COLUMNS)
+
+ add_concurrent_index(TABLE, :pipeline_id_convert_to_bigint,
+ name: :index_ci_sources_pipelines_on_pipeline_id_bigint)
+ add_concurrent_index(TABLE, :source_pipeline_id_convert_to_bigint,
+ name: :index_ci_sources_pipelines_on_source_pipeline_id_bigint)
+ add_concurrent_foreign_key(
+ TABLE, REFERENCING_TABLE,
+ column: :pipeline_id_convert_to_bigint,
+ on_delete: :cascade, validate: true, reverse_lock_order: true
+ )
+ add_concurrent_foreign_key(
+ TABLE, REFERENCING_TABLE,
+ column: :source_pipeline_id_convert_to_bigint,
+ on_delete: :cascade, validate: true, reverse_lock_order: true
+ )
+ end
+end
diff --git a/db/schema_migrations/20231016001000 b/db/schema_migrations/20231016001000
new file mode 100644
index 00000000000..54c77e2a080
--- /dev/null
+++ b/db/schema_migrations/20231016001000
@@ -0,0 +1 @@
+4b40cab24870578ece8648bb0c1e7e2ba4b118cf104ddb76eb0dd7599e51b320 \ No newline at end of file
diff --git a/db/schema_migrations/20231024080150 b/db/schema_migrations/20231024080150
new file mode 100644
index 00000000000..582b17c4325
--- /dev/null
+++ b/db/schema_migrations/20231024080150
@@ -0,0 +1 @@
+13b70e77df1309a1b0d93239b6deff9d34fd5e67650baa0b7495528c6521283d \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index e0682562a36..43e6cf345ad 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -323,16 +323,6 @@ BEGIN
END;
$$;
-CREATE FUNCTION trigger_68d7b6653c7d() RETURNS trigger
- LANGUAGE plpgsql
- AS $$
-BEGIN
- NEW."pipeline_id_convert_to_bigint" := NEW."pipeline_id";
- NEW."source_pipeline_id_convert_to_bigint" := NEW."source_pipeline_id";
- RETURN NEW;
-END;
-$$;
-
CREATE FUNCTION trigger_7f3d66a7d7f5() RETURNS trigger
LANGUAGE plpgsql
AS $$
@@ -14335,9 +14325,7 @@ ALTER SEQUENCE ci_secure_files_id_seq OWNED BY ci_secure_files.id;
CREATE TABLE ci_sources_pipelines (
id integer NOT NULL,
project_id integer,
- pipeline_id_convert_to_bigint integer,
source_project_id integer,
- source_pipeline_id_convert_to_bigint integer,
source_job_id bigint,
partition_id bigint NOT NULL,
source_partition_id bigint NOT NULL,
@@ -32048,8 +32036,6 @@ CREATE INDEX index_ci_secure_files_on_project_id ON ci_secure_files USING btree
CREATE INDEX index_ci_sources_pipelines_on_pipeline_id ON ci_sources_pipelines USING btree (pipeline_id);
-CREATE INDEX index_ci_sources_pipelines_on_pipeline_id_bigint ON ci_sources_pipelines USING btree (pipeline_id_convert_to_bigint);
-
CREATE INDEX index_ci_sources_pipelines_on_project_id ON ci_sources_pipelines USING btree (project_id);
CREATE INDEX index_ci_sources_pipelines_on_source_job_id ON ci_sources_pipelines USING btree (source_job_id);
@@ -32058,8 +32044,6 @@ CREATE INDEX index_ci_sources_pipelines_on_source_partition_id_source_job_id ON
CREATE INDEX index_ci_sources_pipelines_on_source_pipeline_id ON ci_sources_pipelines USING btree (source_pipeline_id);
-CREATE INDEX index_ci_sources_pipelines_on_source_pipeline_id_bigint ON ci_sources_pipelines USING btree (source_pipeline_id_convert_to_bigint);
-
CREATE INDEX index_ci_sources_pipelines_on_source_project_id ON ci_sources_pipelines USING btree (source_project_id);
CREATE INDEX index_ci_sources_projects_on_pipeline_id ON ci_sources_projects USING btree (pipeline_id);
@@ -36784,8 +36768,6 @@ CREATE TRIGGER trigger_10ee1357e825 BEFORE INSERT OR UPDATE ON p_ci_builds FOR E
CREATE TRIGGER trigger_1bd97da9c1a4 BEFORE INSERT OR UPDATE ON ci_pipelines FOR EACH ROW EXECUTE FUNCTION trigger_1bd97da9c1a4();
-CREATE TRIGGER trigger_68d7b6653c7d BEFORE INSERT OR UPDATE ON ci_sources_pipelines FOR EACH ROW EXECUTE FUNCTION trigger_68d7b6653c7d();
-
CREATE TRIGGER trigger_7f3d66a7d7f5 BEFORE INSERT OR UPDATE ON ci_pipeline_variables FOR EACH ROW EXECUTE FUNCTION trigger_7f3d66a7d7f5();
CREATE TRIGGER trigger_b2d852e1e2cb BEFORE INSERT OR UPDATE ON ci_pipelines FOR EACH ROW EXECUTE FUNCTION trigger_b2d852e1e2cb();
@@ -36969,9 +36951,6 @@ ALTER TABLE ONLY agent_project_authorizations
ALTER TABLE ONLY vulnerabilities
ADD CONSTRAINT fk_1d37cddf91 FOREIGN KEY (epic_id) REFERENCES epics(id) ON DELETE SET NULL;
-ALTER TABLE ONLY ci_sources_pipelines
- ADD CONSTRAINT fk_1df371767f FOREIGN KEY (source_pipeline_id_convert_to_bigint) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY boards
ADD CONSTRAINT fk_1e9a074a35 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -37677,9 +37656,6 @@ ALTER TABLE ONLY design_management_versions
ALTER TABLE ONLY packages_packages
ADD CONSTRAINT fk_c188f0dba4 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
-ALTER TABLE ONLY ci_sources_pipelines
- ADD CONSTRAINT fk_c1b5dc6b6f FOREIGN KEY (pipeline_id_convert_to_bigint) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY sbom_occurrences
ADD CONSTRAINT fk_c2a5562923 FOREIGN KEY (source_id) REFERENCES sbom_sources(id) ON DELETE CASCADE;
diff --git a/doc/administration/auditor_users.md b/doc/administration/auditor_users.md
index e9df9cc6e37..09d68e82782 100644
--- a/doc/administration/auditor_users.md
+++ b/doc/administration/auditor_users.md
@@ -25,6 +25,9 @@ Situations where auditor access for users could be helpful include:
you can create an account with auditor access and then share the credentials
with those users to which you want to grant access.
+NOTE:
+An auditor user counts as a billable user and consumes a license seat.
+
## Add a user with auditor access
To create a new user account with auditor access (or change an existing user):
diff --git a/doc/administration/monitoring/prometheus/web_exporter.md b/doc/administration/monitoring/prometheus/web_exporter.md
index a2dee80f6d4..fbf4a109813 100644
--- a/doc/administration/monitoring/prometheus/web_exporter.md
+++ b/doc/administration/monitoring/prometheus/web_exporter.md
@@ -71,3 +71,11 @@ To serve metrics via HTTPS instead of HTTP, enable TLS in the exporter settings:
When TLS is enabled, the same `port` and `address` is used as described above.
The metrics server cannot serve both HTTP and HTTPS at the same time.
+
+## Troubleshooting
+
+### Docker container runs out of space
+
+When running [GitLab in Docker](../../../install/docker.md), your container might run out of space. This can happen if you enable certain features which increase your space consumption, for example Web Exporter.
+
+To work around this issue, [update your `shm-size`](../../../install/docker.md#devshm-mount-not-having-enough-space-in-docker-container).
diff --git a/doc/administration/settings/rate_limits_on_git_ssh_operations.md b/doc/administration/settings/rate_limits_on_git_ssh_operations.md
index 677d8fea195..4e60fd55b19 100644
--- a/doc/administration/settings/rate_limits_on_git_ssh_operations.md
+++ b/doc/administration/settings/rate_limits_on_git_ssh_operations.md
@@ -20,8 +20,6 @@ Each command has a rate limit of 600 per minute. For example:
Because the same commands are shared by `git-upload-pack`, `git pull`, and `git clone`, they share a rate limit.
-Users on self-managed GitLab can disable this rate limit.
-
## Configure GitLab Shell operation limit
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123761) in GitLab 16.2.
@@ -33,4 +31,5 @@ Users on self-managed GitLab can disable this rate limit.
1. Select **Settings > Network**.
1. Expand **Git SSH operations rate limit**.
1. Enter a value for **Maximum number of Git operations per minute**.
+ - To disable the rate limit, set it to `0`.
1. Select **Save changes**.
diff --git a/doc/administration/settings/scim_setup.md b/doc/administration/settings/scim_setup.md
index 432c8598cf7..45020fdfb59 100644
--- a/doc/administration/settings/scim_setup.md
+++ b/doc/administration/settings/scim_setup.md
@@ -53,3 +53,7 @@ adding them to the SCIM identity provider.
After the identity provider performs a sync based on its configured schedule,
the user's SCIM identity is reactivated and their GitLab instance access is restored.
+
+## Troubleshooting
+
+See our [troubleshooting SCIM guide](../../user/group/saml_sso/troubleshooting_scim.md).
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index f73865cc502..6debd812a2c 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -13863,11 +13863,24 @@ Describes a rule for who can approve merge requests.
| <a id="approvalruleinvalid"></a>`invalid` | [`Boolean`](#boolean) | Indicates if the rule is invalid and cannot be approved. |
| <a id="approvalrulename"></a>`name` | [`String`](#string) | Name of the rule. |
| <a id="approvalruleoverridden"></a>`overridden` | [`Boolean`](#boolean) | Indicates if the rule was overridden for the merge request. |
+| <a id="approvalrulescanresultpolicies"></a>`scanResultPolicies` | [`[ApprovalScanResultPolicy!]`](#approvalscanresultpolicy) | List of scan result policies associated with the rule. |
| <a id="approvalrulesection"></a>`section` | [`String`](#string) | Named section of the Code Owners file that the rule applies to. |
| <a id="approvalrulesourcerule"></a>`sourceRule` | [`ApprovalRule`](#approvalrule) | Source rule used to create the rule. |
| <a id="approvalruletype"></a>`type` | [`ApprovalRuleType`](#approvalruletype) | Type of the rule. |
| <a id="approvalruleusers"></a>`users` | [`UserCoreConnection`](#usercoreconnection) | List of users added as approvers for the rule. (see [Connections](#connections)) |
+### `ApprovalScanResultPolicy`
+
+Represents the scan result policy.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="approvalscanresultpolicyapprovalsrequired"></a>`approvalsRequired` | [`Int!`](#int) | Represents the required approvals defined in the policy. |
+| <a id="approvalscanresultpolicyname"></a>`name` | [`String!`](#string) | Represents the name of the policy. |
+| <a id="approvalscanresultpolicyreporttype"></a>`reportType` | [`ApprovalReportType!`](#approvalreporttype) | Represents the report_type of the approval rule. |
+
### `AssetType`
Represents a vulnerability asset type.
@@ -27765,6 +27778,14 @@ All possible ways to specify the API surface for an API fuzzing scan.
| <a id="apifuzzingscanmodeopenapi"></a>`OPENAPI` | The API surface is specified by a OPENAPI file. |
| <a id="apifuzzingscanmodepostman"></a>`POSTMAN` | The API surface is specified by a POSTMAN file. |
+### `ApprovalReportType`
+
+| Value | Description |
+| ----- | ----------- |
+| <a id="approvalreporttypeany_merge_request"></a>`ANY_MERGE_REQUEST` | Represents report_type for any_merge_request related approval rules. |
+| <a id="approvalreporttypelicense_scanning"></a>`LICENSE_SCANNING` | Represents report_type for license scanning related approval rules. |
+| <a id="approvalreporttypescan_finding"></a>`SCAN_FINDING` | Represents report_type for vulnerability check related approval rules. |
+
### `ApprovalRuleType`
The kind of an approval rule.
diff --git a/doc/architecture/blueprints/gcp_integration/decisions/001_no_credentials.md b/doc/architecture/blueprints/gcp_integration/decisions/001_no_credentials.md
new file mode 100644
index 00000000000..70e696def96
--- /dev/null
+++ b/doc/architecture/blueprints/gcp_integration/decisions/001_no_credentials.md
@@ -0,0 +1,19 @@
+---
+description: 'GCP Integration ADR 001: Do not store GCP credentials in GitLab'
+---
+
+# GCP Integration ADR 001: Do not store GCP credentials in GitLab
+
+## Context
+
+Users need to be able to perform certain actions within GCP based on their
+GitLab project membership and permissions. For example, users of the
+integration need a mechanism to push packages to the Google Artifact Registry
+and to provision Runners within their GCP projects.
+
+## Decision
+
+Rely primarily on [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation)
+to manage authorization across the platform. This enables a GitLab user or a
+group / project owner to authenticate within GCP via OAuth and have the ability
+to assume / configure IAM roles in GCP based on their GitLab user attributes.
diff --git a/doc/architecture/blueprints/gcp_integration/index.md b/doc/architecture/blueprints/gcp_integration/index.md
new file mode 100644
index 00000000000..ec5412442e6
--- /dev/null
+++ b/doc/architecture/blueprints/gcp_integration/index.md
@@ -0,0 +1,95 @@
+---
+status: proposed
+creation-date: "2023-10-26"
+authors: [ "@sgoldstein" ]
+coach: "@jessieay"
+approvers: []
+owning-stage: "~devops::verify"
+participating-stages: ["~devops::verify", "~devops::package", "~devops::govern"]
+---
+
+<!-- Blueprints often contain forward-looking statements -->
+<!-- vale gitlab.FutureTense = NO -->
+
+# GCP Integration
+
+## Summary
+
+GitLab and Google Cloud Platform (GCP) provide complementary tooling which we are integrating via our [partnership](https://about.gitlab.com/blog/2023/08/29/gitlab-google-partnership-s3c/).
+
+As phase 1 of this integration, we plan to:
+
+1. Authentication/Authorization Integration. Onboarding, account-linking and permission management between GitLab and GCP Identities.
+1. Continuous Integration. Provision Runners into linked GCP projects.
+1. Artifact Management. Package Registry integration with Google Artifact Registry.
+
+This design doc intends to capture the architecture for this initial integration phase. It should answer the following:
+
+1. How users, permissions, and service accounts managed between GitLab and GCP identities
+1. How to automatically provision GitLab Runners on Google Cloud Compute (GCE) in GCP customer project via Terraform, with or without VPC configured
+1. How to automatically push containers from GitLab CI to Google Artifact Registry, view those containers in GitLab, and trace to builds.
+
+NOTE:
+GitLab package team has begun work on architecture design in [Google Artifact Registry Integration](../google_artifact_registry_integration/index.md)
+
+## Decisions
+
+- [ADR-001: Do not store GCP credentials in GitLab](decisions/001_no_credentials.md)
+
+## Proposal
+
+### Authentication/Authorization
+
+Rely primarily on [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation) to manage authorization across the platform. This enables a GitLab user to authenticate within GCP via OAuth and have the ability to assume IAM roles in GCP based on their GitLab user attributes.
+
+#### Workload Identity Pool Provider creation in GCP
+
+As a GCP and GitLab customer (GitLab Group Owner), I would like to create a Workload Identity Pool and Provider to allow GitLab users to authenticate with GCP when executing CI workloads that utilize GCP services.
+
+Steps:
+
+ 1. Customer navigates to GitLab → Operate → Google Cloud
+ 1. Customer authenticates with GCP through an OAuth flow, if the customer is not already authenticated.
+ 1. Creates a GCP Workload Identity Pool and Provider with OpenID Connect configuration in customers GCP Project, by providing the GCP project number at creation time.
+ 1. There is an [API to go from projectNum->projectID and back](https://cloud.google.com/resource-manager/reference/rest/v3/projects/get).
+
+#### GCP resource policy creation in GitLab for Workload Identity Provider
+
+As a GCP customer, I would like to authorize GitLab’s use of my GCP resources for the purpose of the GitLab - GCP Integration:
+
+Steps:
+
+1. Customer navigates to GitLab → Operate → Google Cloud
+1. Customer authenticates with GCP through an OAuth flow, if the customer is not already authenticated.
+1. Customer selects previously created Workload Identity Provider and adds applicable resource policies by specifying the:
+ 1. GCP project number
+ 1. One or more GitLab OpenID claims and associated values to use for authenticating a workload
+ 1. One or more GCP resources IAM roles the authenticated workload will be authorized to use
+1. Customer is presented with a prompt asking them to confirm that they wish to proceed in creating this GCP resource policy
+ 1. If a customer confirms, a policy for the Workload Identity Provider is created in the specified GCP project.
+1. If additional GCP resource policies are required, the customer can repeat steps 2 and 3.
+
+NOTE:
+The user should also be able to edit (claims, values and IAM roles) and delete GCP resources policies.
+
+#### User to User authentication linkage
+
+As an existing GitLab user, I can use OAuth to link my existing GCP account so that I can seamlessly exchange data between the two
+
+This will be used for the following use cases:
+
+ 1. In Artifact Registry where the individual user can see artifacts in GCP
+ 1. Creation of workload identity federation pools
+ 1. List, pull and push images to Artifact Registry from GitLab
+
+Also applies in the reverse direction. Existing GCP users can OAuth into GitLab and link their accounts.
+
+#### Runner Provisioning
+
+TBA
+
+#### GitLab CI to Google Artifact Registry Authn/Authz
+
+TBA
+
+## Design and implementation details
diff --git a/doc/ci/quick_start/index.md b/doc/ci/quick_start/index.md
index 8e6fa965aa4..1f8c33a9700 100644
--- a/doc/ci/quick_start/index.md
+++ b/doc/ci/quick_start/index.md
@@ -9,7 +9,7 @@ type: reference
This tutorial shows you how to configure and run your first CI/CD pipeline in GitLab.
-If you are already familiar with basic CI/CD concepts, you can learn about
+If you are already familiar with [basic CI/CD concepts](../index.md), you can learn about
common keywords in [Tutorial: Create a complex pipeline](tutorial.md).
## Prerequisites
diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md
index 1b6769a58f1..6a1b9d812ab 100644
--- a/doc/ci/yaml/index.md
+++ b/doc/ci/yaml/index.md
@@ -10,8 +10,9 @@ type: reference
This document lists the configuration options for the GitLab `.gitlab-ci.yml` file.
This file is where you define the CI/CD jobs that make up your pipeline.
-- To create your own `.gitlab-ci.yml` file, try a tutorial that demonstrates a
- [simple](../quick_start/index.md) or [complex](../quick_start/tutorial.md) pipeline.
+- If you are already familiar with [basic CI/CD concepts](../index.md), try creating
+ your own `.gitlab-ci.yml` file by following a tutorial that demonstrates a [simple](../quick_start/index.md)
+ or [complex](../quick_start/tutorial.md) pipeline.
- For a collection of examples, see [GitLab CI/CD examples](../examples/index.md).
- To view a large `.gitlab-ci.yml` file used in an enterprise, see the
[`.gitlab-ci.yml` file for `gitlab`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab-ci.yml).
diff --git a/doc/development/ruby_upgrade.md b/doc/development/ruby_upgrade.md
index 52f0f72e72a..61bc629e8c8 100644
--- a/doc/development/ruby_upgrade.md
+++ b/doc/development/ruby_upgrade.md
@@ -84,6 +84,8 @@ order reversed as described above.
Tracking this work in an epic is useful to get a sense of progress. For larger upgrades, include a
timeline in the epic description so stakeholders know when the final switch is expected to go live.
+Include the designated [performance testing template](https://gitlab.com/gitlab-org/quality/performance-testing/ruby-rollout-performance-testing)
+to help ensure performance standards on the upgrade.
Break changes to individual repositories into separate issues under this epic.
@@ -141,14 +143,13 @@ A [build matrix definition](../ci/yaml/index.md#parallelmatrix) can do this effi
#### Decide which repositories to update
-When upgrading Ruby, consider updating the following repositories:
+When upgrading Ruby, consider updating the repositories in the [`ruby/gems` group](https://gitlab.com/gitlab-org/ruby/gems/) as well.
+For reference, here is a list of merge requests that have updated Ruby for some of these projects in the past:
-- [Gitaly](https://gitlab.com/gitlab-org/gitaly) ([example](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/3771))
- [GitLab LabKit](https://gitlab.com/gitlab-org/labkit-ruby) ([example](https://gitlab.com/gitlab-org/labkit-ruby/-/merge_requests/79))
- [GitLab Exporter](https://gitlab.com/gitlab-org/ruby/gems/gitlab-exporter) ([example](https://gitlab.com/gitlab-org/ruby/gems/gitlab-exporter/-/merge_requests/150))
- [GitLab Experiment](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment) ([example](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment/-/merge_requests/128))
- [Gollum Lib](https://gitlab.com/gitlab-org/gollum-lib) ([example](https://gitlab.com/gitlab-org/gollum-lib/-/merge_requests/21))
-- [GitLab Helm Chart](https://gitlab.com/gitlab-org/charts/gitlab) ([example](https://gitlab.com/gitlab-org/charts/gitlab/-/merge_requests/2162))
- [GitLab Sidekiq fetcher](https://gitlab.com/gitlab-org/sidekiq-reliable-fetch) ([example](https://gitlab.com/gitlab-org/sidekiq-reliable-fetch/-/merge_requests/33))
- [Prometheus Ruby Mmap Client](https://gitlab.com/gitlab-org/prometheus-client-mmap) ([example](https://gitlab.com/gitlab-org/prometheus-client-mmap/-/merge_requests/59))
- [GitLab-mail_room](https://gitlab.com/gitlab-org/gitlab-mail_room) ([example](https://gitlab.com/gitlab-org/gitlab-mail_room/-/merge_requests/16))
@@ -213,8 +214,6 @@ the new Ruby to be the new default.
The last step is to use the new Ruby in production. This
requires updating Omnibus and production Docker images to use the new version.
-Helm charts may also have to be updated if there were changes to related systems that maintain
-their own charts (such as `gitlab-exporter`.)
To use the new Ruby in production, update the following projects:
@@ -222,6 +221,11 @@ To use the new Ruby in production, update the following projects:
- [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab) ([example](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5545))
- [Self-compiled installations](../install/installation.md): update the [Ruby system version check](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/system_check/app/ruby_version_check.rb)
+Charts like the [GitLab Helm Chart](https://gitlab.com/gitlab-org/charts/gitlab) should also be updated if
+they use Ruby in some capacity, for example
+to run tests (see [this example](https://gitlab.com/gitlab-org/charts/gitlab/-/merge_requests/2162)), though
+this may not strictly be necessary.
+
If you submit a change management request, coordinate the rollout with infrastructure
engineers. When dealing with larger upgrades, involve [Release Managers](https://about.gitlab.com/community/release-managers/)
in the rollout plan.
diff --git a/doc/user/group/saml_sso/troubleshooting_scim.md b/doc/user/group/saml_sso/troubleshooting_scim.md
index 703dff16fd5..b31c2eed9df 100644
--- a/doc/user/group/saml_sso/troubleshooting_scim.md
+++ b/doc/user/group/saml_sso/troubleshooting_scim.md
@@ -4,7 +4,7 @@ group: Authentication
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
---
-# Troubleshooting SCIM **(PREMIUM SAAS)**
+# Troubleshooting SCIM **(FREE ALL)**
This section contains possible solutions for problems you might encounter.
@@ -31,6 +31,8 @@ To solve this problem:
1. Have the user sign in directly to GitLab.
1. [Manually link](scim_setup.md#link-scim-and-saml-identities) their account.
+Alternatively, self-managed administrators can [add a user identity](../../../administration/admin_area.md#user-identities).
+
## User cannot sign in
The following are possible solutions for problems where users cannot sign in:
@@ -38,10 +40,11 @@ The following are possible solutions for problems where users cannot sign in:
- Ensure that the user was added to the SCIM app.
- If you receive the `User is not linked to a SAML account` error, the user probably already exists in GitLab. Have the
user follow the [Link SCIM and SAML identities](scim_setup.md#link-scim-and-saml-identities) instructions.
+ Alternatively, self-managed administrators can [add a user identity](../../../administration/admin_area.md#user-identities).
- The **Identity** (`extern_uid`) value stored by GitLab is updated by SCIM whenever `id` or `externalId` changes. Users
- cannot sign in unless the GitLab Identity (`extern_uid`) value matches the `NameId` sent by SAML. This value is also
- used by SCIM to match users on the `id`, and is updated by SCIM whenever the `id` or `externalId` values change.
-- The SCIM `id` and SCIM `externalId` must be configured to the same value as the SAML `NameId`. You can trace SAML responses
+ cannot sign in unless the GitLab identifier (`extern_uid`) of the sign-in method matches the ID sent by the provider, such as
+ the `NameId` sent by SAML. This value is also used by SCIM to match users on the `id`, and is updated by SCIM whenever the `id` or `externalId` values change.
+- On GitLab.com, the SCIM `id` and SCIM `externalId` must be configured to the same value as the SAML `NameId`. You can trace SAML responses
using [debugging tools](troubleshooting.md#saml-debugging-tools), and check any errors against the
[SAML troubleshooting](troubleshooting.md) information.
@@ -94,10 +97,12 @@ When the SCIM app changes:
- Users can follow the instructions in the [Change the SAML app](index.md#change-the-identity-provider) section.
- Administrators of the identity provider can:
- 1. Remove users from the SCIM app, which unlinks all removed users.
+ 1. Remove users from the SCIM app, which:
+ - In GitLab.com, removes all removed users from the group.
+ - In GitLab self-managed, blocks users.
1. Turn on sync for the new SCIM app to [link existing users](scim_setup.md#link-scim-and-saml-identities).
-## SCIM app returns `"User has already been taken","status":409` error
+## SCIM app returns `"User has already been taken","status":409` error **(PREMIUM SAAS)**
Changing the SAML or SCIM configuration or provider can cause the following problems:
@@ -109,7 +114,7 @@ Changing the SAML or SCIM configuration or provider can cause the following prob
the SCIM app.
1. Use the same SCIM API to update the SCIM `extern_uid` for the user on GitLab.com.
-## Search Rails logs for SCIM requests
+## Search Rails logs for SCIM requests **(PREMIUM SAAS)**
GitLab.com administrators can search for SCIM requests in the `api_json.log` using the `pubsub-rails-inf-gprd-*` index in
[Kibana](https://about.gitlab.com/handbook/support/workflows/kibana.html#using-kibana). Use the following filters based
diff --git a/doc/user/project/merge_requests/reviews/img/comment-on-any-diff-line_v13_10.png b/doc/user/project/merge_requests/reviews/img/comment-on-any-diff-line_v13_10.png
deleted file mode 100644
index a31fea85be9..00000000000
--- a/doc/user/project/merge_requests/reviews/img/comment-on-any-diff-line_v13_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/reviews/img/comment_on_any_diff_line_v16_6.png b/doc/user/project/merge_requests/reviews/img/comment_on_any_diff_line_v16_6.png
new file mode 100644
index 00000000000..5ed210ad8bb
--- /dev/null
+++ b/doc/user/project/merge_requests/reviews/img/comment_on_any_diff_line_v16_6.png
Binary files differ
diff --git a/doc/user/project/merge_requests/reviews/img/mr_review_new_comment_v15_3.png b/doc/user/project/merge_requests/reviews/img/mr_review_new_comment_v15_3.png
deleted file mode 100644
index b73dbb50cd2..00000000000
--- a/doc/user/project/merge_requests/reviews/img/mr_review_new_comment_v15_3.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/reviews/img/mr_review_new_comment_v16_6.png b/doc/user/project/merge_requests/reviews/img/mr_review_new_comment_v16_6.png
new file mode 100644
index 00000000000..3e11440a71b
--- /dev/null
+++ b/doc/user/project/merge_requests/reviews/img/mr_review_new_comment_v16_6.png
Binary files differ
diff --git a/doc/user/project/merge_requests/reviews/img/mr_summary_comment_v15_4.png b/doc/user/project/merge_requests/reviews/img/mr_summary_comment_v15_4.png
deleted file mode 100644
index 47b7be3886d..00000000000
--- a/doc/user/project/merge_requests/reviews/img/mr_summary_comment_v15_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/reviews/img/mr_summary_comment_v16_6.png b/doc/user/project/merge_requests/reviews/img/mr_summary_comment_v16_6.png
new file mode 100644
index 00000000000..965ce84a70f
--- /dev/null
+++ b/doc/user/project/merge_requests/reviews/img/mr_summary_comment_v16_6.png
Binary files differ
diff --git a/doc/user/project/merge_requests/reviews/index.md b/doc/user/project/merge_requests/reviews/index.md
index ea99a386da9..1120733251f 100644
--- a/doc/user/project/merge_requests/reviews/index.md
+++ b/doc/user/project/merge_requests/reviews/index.md
@@ -165,7 +165,7 @@ You can submit your completed review in multiple ways:
In the modal window, you can supply a **Summary comment**, approve the merge request, and
include quick actions:
- ![Finish review with comment](img/mr_summary_comment_v15_4.png)
+ ![Finish review with comment](img/mr_summary_comment_v16_6.png)
When you submit your review, GitLab:
@@ -194,7 +194,7 @@ Pending comments display information about the action to be taken when the comme
If you have a review in progress, you can also add a comment from the **Overview** tab by selecting
**Add to review**:
-![New thread](img/mr_review_new_comment_v15_3.png)
+![New thread](img/mr_review_new_comment_v16_6.png)
### Approval Rule information for Reviewers **(PREMIUM ALL)**
@@ -228,8 +228,6 @@ them a notification email.
When commenting on a diff, you can select which lines of code your comment refers
to by either:
-![Comment on any diff file line](img/comment-on-any-diff-line_v13_10.png)
-
- Dragging **Add a comment to this line** (**{comment}**) in the gutter to highlight
lines in the diff. GitLab expands the diff lines and displays a comment box.
- After starting a comment by selecting **Add a comment to this line** (**{comment}**) in the
@@ -237,6 +235,8 @@ to by either:
select box. New comments default to single-line comments, unless you select
a different starting line.
+![Comment on any diff file line](img/comment_on_any_diff_line_v16_6.png)
+
Multiline comments display the comment's line numbers above the body of the comment:
![Multiline comment selection displayed above comment](img/multiline-comment-saved.png)
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index fac07a1313a..cfa210faab8 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -17,6 +17,7 @@ A protected branch controls:
- If users can force push to the branch.
- If changes to files listed in the CODEOWNERS file can be pushed directly to the branch.
- Which users can unprotect the branch.
+- Which users can modify the branch via the [Commits API](../../api/commits.md).
The [default branch](repository/branches/default.md) for your repository is protected by default.
diff --git a/lib/sidebars/projects/menus/infrastructure_menu.rb b/lib/sidebars/projects/menus/infrastructure_menu.rb
index b08845a37e6..d3c9f3a6466 100644
--- a/lib/sidebars/projects/menus/infrastructure_menu.rb
+++ b/lib/sidebars/projects/menus/infrastructure_menu.rb
@@ -70,7 +70,7 @@ module Sidebars
highlight: Users::CalloutsHelper::GKE_CLUSTER_INTEGRATION,
highlight_priority: Users::Callout.feature_names[:GKE_CLUSTER_INTEGRATION],
dismiss_endpoint: callouts_path,
- auto_devops_help_path: help_page_path('topics/autodevops/index.md') } }
+ auto_devops_help_path: help_page_path('topics/autodevops/index') } }
end
def terraform_states_menu_item
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 645f04963a7..b96f86171ff 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -29273,15 +29273,30 @@ msgstr ""
msgid "MemberRoles|Add new role"
msgstr ""
+msgid "MemberRoles|Admin group member"
+msgstr ""
+
+msgid "MemberRoles|Admin merge requests"
+msgstr ""
+
msgid "MemberRoles|Admin vulnerability"
msgstr ""
+msgid "MemberRoles|Allows admin access to group members."
+msgstr ""
+
+msgid "MemberRoles|Allows admin access to the merge requests."
+msgstr ""
+
msgid "MemberRoles|Allows admin access to the vulnerability reports. Select 'Read vulnerability' for this to take effect."
msgstr ""
msgid "MemberRoles|Allows manage access to the project access tokens. Select 'Manage Project Access Tokens' for this to take effect."
msgstr ""
+msgid "MemberRoles|Allows read-only access to the dependencies."
+msgstr ""
+
msgid "MemberRoles|Allows read-only access to the source code."
msgstr ""
@@ -29348,6 +29363,9 @@ msgstr ""
msgid "MemberRoles|Read code"
msgstr ""
+msgid "MemberRoles|Read dependency"
+msgstr ""
+
msgid "MemberRoles|Read vulnerability"
msgstr ""
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index e75682aee57..7451a05daec 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -89,6 +89,7 @@ module QA
def open_actions_dropdown
# We use find here because these are gitlab-ui elements
+ wait_for_requests
find('[data-testid="desktop-dropdown"] > button').click
end
end
diff --git a/qa/qa/page/project/settings/branch_rules.rb b/qa/qa/page/project/settings/branch_rules.rb
index b01f493addf..401d75fd1ce 100644
--- a/qa/qa/page/project/settings/branch_rules.rb
+++ b/qa/qa/page/project/settings/branch_rules.rb
@@ -6,22 +6,22 @@ module QA
module Settings
class BranchRules < Page::Base
view 'app/assets/javascripts/projects/settings/repository/branch_rules/app.vue' do
- element :add_branch_rule_button
+ element 'add-branch-rule-button'
end
view 'app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue' do
- element :branch_content
- element :details_button
+ element 'branch-content'
+ element 'details-button'
end
def click_add_branch_rule
- click_element(:add_branch_rule_button)
+ click_element('add-branch-rule-button')
click_button('Create protected branch')
end
def navigate_to_branch_rules_details(branch_name)
- within_element(:branch_content, branch_name: branch_name) do
- click_element(:details_button)
+ within_element('branch-content', branch_name: branch_name) do
+ click_element('details-button')
end
end
end
diff --git a/rubocop/cop/gitlab/doc_url.rb b/rubocop/cop/gitlab/doc_url.rb
index 41a1c2f8b36..79e25142f26 100644
--- a/rubocop/cop/gitlab/doc_url.rb
+++ b/rubocop/cop/gitlab/doc_url.rb
@@ -19,7 +19,7 @@ module RuboCop
include RangeHelp
MSG = 'Use `#help_page_url` instead of directly including link. ' \
- 'See https://docs.gitlab.com/ee/development/documentation/#linking-to-help-in-ruby.'
+ 'See https://docs.gitlab.com/ee/development/documentation/help#linking-to-help.'
DOCS_URL_REGEXP = %r{https://docs.gitlab.com/ee/[\w#%./-]+}
diff --git a/spec/features/issues/issue_state_spec.rb b/spec/features/issues/issue_state_spec.rb
index 3fe49ff7080..125329764c6 100644
--- a/spec/features/issues/issue_state_spec.rb
+++ b/spec/features/issues/issue_state_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe 'issue state', :js, feature_category: :team_planning do
find('#new-actions-header-dropdown > button').click
end
- it_behaves_like 'issue closed', '.dropdown-menu-right'
+ it_behaves_like 'issue closed', '.gl-new-dropdown-contents'
end
context 'when clicking the bottom `Close issue` button', :aggregate_failures do
@@ -74,7 +74,7 @@ RSpec.describe 'issue state', :js, feature_category: :team_planning do
find('#new-actions-header-dropdown > button').click
end
- it_behaves_like 'issue reopened', '.dropdown-menu-right'
+ it_behaves_like 'issue reopened', '.gl-new-dropdown-contents'
end
context 'when clicking the bottom `Reopen issue` button', :aggregate_failures do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 4a38373db71..eca52a3a2c3 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe 'issue move to another project', feature_category: :team_planning
it 'moving issue to another project', :js do
click_button _('Move issue')
wait_for_requests
- all('.gl-dropdown-item')[0].click
+ all('.gl-new-dropdown-item')[0].click
click_button _('Move')
expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
@@ -116,7 +116,7 @@ RSpec.describe 'issue move to another project', feature_category: :team_planning
click_button _('Move issue')
wait_for_requests
- find('.gl-dropdown-item', text: project_title).click
+ find('.gl-new-dropdown-item', text: project_title).click
click_button _('Move')
end
diff --git a/spec/features/projects/work_items/work_item_spec.rb b/spec/features/projects/work_items/work_item_spec.rb
index 09a8636359d..876444e451a 100644
--- a/spec/features/projects/work_items/work_item_spec.rb
+++ b/spec/features/projects/work_items/work_item_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Work item', :js, feature_category: :team_planning do
+ include ListboxHelpers
+
let_it_be_with_reload(:user) { create(:user, :no_super_sidebar) }
let_it_be_with_reload(:user2) { create(:user, :no_super_sidebar, name: 'John') }
diff --git a/spec/frontend/design_management/components/design_notes/design_note_spec.js b/spec/frontend/design_management/components/design_notes/design_note_spec.js
index 91c6acd8890..28b264cede9 100644
--- a/spec/frontend/design_management/components/design_notes/design_note_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_note_spec.js
@@ -51,13 +51,19 @@ describe('Design note component', () => {
const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
const findDropdownItems = () => findDropdown().findAllComponents(GlDisclosureDropdownItem);
const findEditDropdownItem = () => findDropdownItems().at(0);
- const findDeleteDropdownItem = () => findDropdownItems().at(1);
+ const findCopyLinkDropdownItem = () => findDropdownItems().at(1);
+ const findDeleteDropdownItem = () => findDropdownItems().at(2);
+
+ const showToast = jest.fn();
function createComponent({
props = {},
data = { isEditing: false },
mountFn = mountExtended,
mocks = {
+ $toast: {
+ show: showToast,
+ },
$route,
$apollo: {
mutate: jest.fn().mockResolvedValue({ data: { updateNote: {} } }),
@@ -239,6 +245,7 @@ describe('Design note component', () => {
expect(findDropdown().exists()).toBe(true);
expect(findEditDropdownItem().exists()).toBe(true);
+ expect(findCopyLinkDropdownItem().exists()).toBe(true);
expect(findDeleteDropdownItem().exists()).toBe(true);
expect(findDropdown().props('items')[0].extraAttrs.class).toBe('gl-sm-display-none!');
});
@@ -266,6 +273,40 @@ describe('Design note component', () => {
expect(wrapper.emitted()).toEqual({ 'delete-note': [[{ ...payload }]] });
});
+ it('shows a success toast after copying the url to the clipboard', () => {
+ createComponent({
+ props: {
+ note: {
+ ...note,
+ userPermissions: {
+ adminNote: true,
+ },
+ },
+ },
+ });
+
+ findCopyLinkDropdownItem().find('button').trigger('click');
+
+ expect(showToast).toHaveBeenCalledWith('Link copied to clipboard.');
+ });
+
+ it('has data-clipboard-text set to the correct url', () => {
+ createComponent({
+ props: {
+ note: {
+ ...note,
+ userPermissions: {
+ adminNote: true,
+ },
+ },
+ },
+ });
+
+ expect(findCopyLinkDropdownItem().props('item').extraAttrs['data-clipboard-text']).toBe(
+ 'http://test.host/#note_123',
+ );
+ });
+
describe('when user has award emoji permissions', () => {
const findEmojiPicker = () => wrapper.findComponent(EmojiPicker);
const propsData = {
diff --git a/spec/frontend/issues/show/components/header_actions_spec.js b/spec/frontend/issues/show/components/header_actions_spec.js
index e508045eff3..d0c2a1a5f1b 100644
--- a/spec/frontend/issues/show/components/header_actions_spec.js
+++ b/spec/frontend/issues/show/components/header_actions_spec.js
@@ -1,9 +1,16 @@
import Vue, { nextTick } from 'vue';
-import { GlDropdown, GlDropdownItem, GlLink, GlModal, GlButton } from '@gitlab/ui';
+import {
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
+ GlLink,
+ GlModal,
+ GlButton,
+} from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import VueApollo from 'vue-apollo';
+import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import { mockTracking } from 'helpers/tracking_helper';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
@@ -120,8 +127,10 @@ describe('HeaderActions component', () => {
const findDropdownBy = (dataTestId) => wrapper.find(`[data-testid="${dataTestId}"]`);
const findMobileDropdown = () => findDropdownBy('mobile-dropdown');
const findDesktopDropdown = () => findDropdownBy('desktop-dropdown');
- const findMobileDropdownItems = () => findMobileDropdown().findAllComponents(GlDropdownItem);
- const findDesktopDropdownItems = () => findDesktopDropdown().findAllComponents(GlDropdownItem);
+ const findMobileDropdownItems = () =>
+ findMobileDropdown().findAllComponents(GlDisclosureDropdownItem);
+ const findDesktopDropdownItems = () =>
+ findDesktopDropdown().findAllComponents(GlDisclosureDropdownItem);
const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector);
const findReportAbuseButton = () => wrapper.find(`[data-testid="report-abuse-item"]`);
const findNotificationWidget = () => wrapper.find(`[data-testid="notification-toggle"]`);
@@ -179,6 +188,11 @@ describe('HeaderActions component', () => {
},
stubs: {
GlButton,
+ GlDisclosureDropdown: stubComponent(GlDisclosureDropdown, {
+ methods: {
+ close: jest.fn(),
+ },
+ }),
},
});
};
@@ -217,7 +231,7 @@ describe('HeaderActions component', () => {
});
it('calls apollo mutation', () => {
- findToggleIssueStateButton().vm.$emit('click');
+ findToggleIssueStateButton().vm.$emit('action');
expect(updateIssueMutationResponseHandler).toHaveBeenCalledWith({
input: {
@@ -229,7 +243,7 @@ describe('HeaderActions component', () => {
});
it('dispatches a custom event to update the issue page', async () => {
- findToggleIssueStateButton().vm.$emit('click');
+ findToggleIssueStateButton().vm.$emit('action');
await waitForPromises();
@@ -286,7 +300,11 @@ describe('HeaderActions component', () => {
it(`${isItemVisible ? 'shows' : 'hides'} "${itemText}" item`, () => {
expect(
findDropdownItems()
- .filter((item) => item.text() === itemText)
+ .filter((item) => {
+ return item.props('item')
+ ? item.props('item').text === itemText
+ : item.text() === itemText;
+ })
.exists(),
).toBe(isItemVisible);
});
@@ -313,7 +331,7 @@ describe('HeaderActions component', () => {
it('should trigger "open.form" event when clicked', async () => {
expect(issuesEventHub.$emit).not.toHaveBeenCalled();
- await findEditButton().trigger('click');
+ await findEditButton().vm.$emit('click');
expect(issuesEventHub.$emit).toHaveBeenCalledWith('open.form');
});
});
@@ -328,7 +346,7 @@ describe('HeaderActions component', () => {
});
it('tracks clicking on button', () => {
- findDesktopDropdownItems().at(4).vm.$emit('click');
+ findDesktopDropdownItems().at(4).vm.$emit('action');
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_dropdown', {
label: 'delete_issue',
@@ -345,7 +363,7 @@ describe('HeaderActions component', () => {
promoteToEpicHandler: promoteToEpicMutationSuccessResponseHandler,
});
- wrapper.find('[data-testid="promote-button"]').vm.$emit('click');
+ wrapper.find('[data-testid="promote-button"]').vm.$emit('action');
await waitForPromises();
});
@@ -381,7 +399,7 @@ describe('HeaderActions component', () => {
promoteToEpicHandler: promoteToEpicMutationErrorHandler,
});
- wrapper.find('[data-testid="promote-button"]').vm.$emit('click');
+ wrapper.find('[data-testid="promote-button"]').vm.$emit('action');
await waitForPromises();
});
@@ -483,7 +501,7 @@ describe('HeaderActions component', () => {
});
it('opens the abuse category drawer', async () => {
- findReportAbuseButton().vm.$emit('click');
+ findReportAbuseButton().vm.$emit('action');
await nextTick();
@@ -491,7 +509,7 @@ describe('HeaderActions component', () => {
});
it('closes the abuse category drawer', async () => {
- await findReportAbuseButton().vm.$emit('click');
+ await findReportAbuseButton().vm.$emit('action');
expect(findAbuseCategorySelector().exists()).toEqual(true);
await findAbuseCategorySelector().vm.$emit('close-drawer');
@@ -603,7 +621,7 @@ describe('HeaderActions component', () => {
});
it('shows toast message', () => {
- findCopyRefenceDropdownItem().vm.$emit('click');
+ findCopyRefenceDropdownItem().vm.$emit('action');
expect(toast).toHaveBeenCalledWith('Reference copied');
});
@@ -652,7 +670,7 @@ describe('HeaderActions component', () => {
});
it('shows toast message', () => {
- findCopyEmailItem().vm.$emit('click');
+ findCopyEmailItem().vm.$emit('action');
expect(toast).toHaveBeenCalledWith('Email address copied');
});
@@ -710,11 +728,13 @@ describe('HeaderActions component', () => {
props: { issueType, issuableEmailAddress: 'mock-email-address' },
});
- expect(wrapper.findComponent(GlDropdown).attributes('text')).toBe(
+ expect(wrapper.findComponent(GlDisclosureDropdown).props('toggleText')).toBe(
`${capitalizeFirstCharacter(expectedText)} actions`,
);
expect(findDropdownBy('copy-email').text()).toBe(`Copy ${expectedText} email address`);
- expect(findDesktopDropdownItems().at(1).text()).toBe(`New related ${expectedText}`);
+ expect(findDesktopDropdownItems().at(1).props('item').text).toBe(
+ `New related ${expectedText}`,
+ );
});
});
});
diff --git a/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js b/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js
index 56c915c4cae..f049001ba45 100644
--- a/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js
@@ -1,19 +1,9 @@
-import { nextTick } from 'vue';
-import {
- GlIcon,
- GlLoadingIcon,
- GlDropdown,
- GlDropdownForm,
- GlDropdownItem,
- GlSearchBoxByType,
- GlButton,
-} from '@gitlab/ui';
+import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
import IssuableMoveDropdown from '~/sidebar/components/move/issuable_move_dropdown.vue';
-import { stubComponent } from 'helpers/stub_component';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
const mockProjects = [
@@ -42,318 +32,136 @@ const mockProps = {
disabled: false,
};
-const mockEvent = {
- stopPropagation: jest.fn(),
- preventDefault: jest.fn(),
-};
-
-const focusInputMock = jest.fn();
-const hideMock = jest.fn();
-
describe('IssuableMoveDropdown', () => {
let mock;
let wrapper;
- const createComponent = (propsData = mockProps) => {
- wrapper = shallowMountExtended(IssuableMoveDropdown, {
- propsData,
- stubs: {
- GlDropdown: stubComponent(GlDropdown, {
- methods: {
- hide: hideMock,
- },
- }),
- GlSearchBoxByType: stubComponent(GlSearchBoxByType, {
- methods: {
- focusInput: focusInputMock,
- },
- }),
- },
- });
+ const createComponent = (propsData = {}) => {
+ wrapper = mountExtended(IssuableMoveDropdown, { propsData: { ...mockProps, ...propsData } });
};
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet(mockProps.projectsFetchPath).reply(HTTP_STATUS_OK, mockProjects);
-
- createComponent();
});
afterEach(() => {
mock.restore();
});
- const findCollapsedEl = () => wrapper.findByTestId('move-collapsed');
- const findFooter = () => wrapper.findByTestId('footer');
- const findHeader = () => wrapper.findByTestId('header');
- const findFailedLoadResults = () => wrapper.findByTestId('failed-load-results');
- const findDropdownContent = () => wrapper.findByTestId('content');
- const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
- const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findDropdownEl = () => wrapper.findComponent(GlDropdown);
- const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
-
- describe('watch', () => {
- describe('searchKey', () => {
- it('calls `fetchProjects` with value of the prop', async () => {
- jest.spyOn(axios, 'get');
- findSearchBox().vm.$emit('input', 'foo');
-
- await waitForPromises();
-
- expect(axios.get).toHaveBeenCalledWith('/-/autocomplete/projects?project_id=1', {
- params: { search: 'foo' },
- });
- });
- });
- });
-
- describe('methods', () => {
- describe('fetchProjects', () => {
- it('sets projectsListLoading to true and projectsListLoadFailed to false', async () => {
- findDropdownEl().vm.$emit('shown');
- await nextTick();
-
- expect(findLoadingIcon().exists()).toBe(true);
- expect(findFailedLoadResults().exists()).toBe(false);
- });
-
- it('calls `axios.get` with `projectsFetchPath` and query param `search`', async () => {
- jest.spyOn(axios, 'get');
-
- findSearchBox().vm.$emit('input', 'foo');
- await waitForPromises();
-
- expect(axios.get).toHaveBeenCalledWith(
- mockProps.projectsFetchPath,
- expect.objectContaining({
- params: {
- search: 'foo',
- },
- }),
- );
- });
-
- it('sets response to `projects` and focuses on searchInput when request is successful', async () => {
- jest.spyOn(axios, 'get');
-
- findSearchBox().vm.$emit('input', 'foo');
- await waitForPromises();
+ const findDropdownButton = () => wrapper.findByTestId('dropdown-button');
+ const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
+ const findDropdownMoveButton = () => wrapper.findByTestId('dropdown-move-button');
+ const findDropdownItemsText = () =>
+ wrapper.findAllComponents(GlListboxItem).wrappers.map((item) => item.text());
- expect(findAllDropdownItems()).toHaveLength(mockProjects.length);
- expect(focusInputMock).toHaveBeenCalled();
- });
-
- it('sets projectsListLoadFailed to true when request fails', async () => {
- jest.spyOn(axios, 'get').mockRejectedValue({});
-
- findSearchBox().vm.$emit('input', 'foo');
- await waitForPromises();
-
- expect(findFailedLoadResults().exists()).toBe(true);
- });
-
- it('sets projectsListLoading to false when request completes', async () => {
- jest.spyOn(axios, 'get');
-
- findDropdownEl().vm.$emit('shown');
- await waitForPromises();
-
- expect(findLoadingIcon().exists()).toBe(false);
- });
- });
-
- describe('isSelectedProject', () => {
- it.each`
- projectIndex | selectedProjectIndex | title | returnValue
- ${0} | ${0} | ${'are same projects'} | ${true}
- ${0} | ${1} | ${'are different projects'} | ${false}
- `(
- 'returns $returnValue when selectedProject and provided project param $title',
- async ({ projectIndex, selectedProjectIndex, returnValue }) => {
- findDropdownEl().vm.$emit('shown');
- await waitForPromises();
-
- findAllDropdownItems().at(selectedProjectIndex).vm.$emit('click', mockEvent);
-
- await nextTick();
-
- expect(findAllDropdownItems().at(projectIndex).props('isChecked')).toBe(returnValue);
- },
- );
-
- it('returns false when selectedProject is null', async () => {
- findDropdownEl().vm.$emit('shown');
- await waitForPromises();
+ it('renders a dropdown button with provided title and header', () => {
+ createComponent();
- expect(findAllDropdownItems().at(0).props('isChecked')).toBe(false);
- });
- });
+ expect(findDropdownButton().text()).toBe(mockProps.dropdownButtonTitle);
+ expect(findDropdown().props('headerText')).toBe(mockProps.dropdownHeaderTitle);
});
- describe('template', () => {
- it('renders collapsed state element with icon', () => {
- const collapsedEl = findCollapsedEl();
-
- expect(collapsedEl.exists()).toBe(true);
- expect(collapsedEl.attributes('title')).toBe(mockProps.dropdownButtonTitle);
- expect(collapsedEl.findComponent(GlIcon).exists()).toBe(true);
- expect(collapsedEl.findComponent(GlIcon).props('name')).toBe('arrow-right');
- });
-
- describe('gl-dropdown component', () => {
- it('renders component container element', () => {
- expect(findDropdownEl().exists()).toBe(true);
- expect(findDropdownEl().props('block')).toBe(true);
- });
+ it('renders the dropdown button as disabled when disabled prop is true', () => {
+ createComponent({ disabled: true });
- it('renders gl-dropdown-form component', () => {
- expect(findDropdownEl().findComponent(GlDropdownForm).exists()).toBe(true);
- });
-
- it('renders disabled dropdown when `disabled` is true', () => {
- createComponent({ ...mockProps, disabled: true });
- expect(findDropdownEl().props('disabled')).toBe(true);
- });
-
- it('renders header element', () => {
- const headerEl = findHeader();
-
- expect(headerEl.exists()).toBe(true);
- expect(headerEl.find('span').text()).toBe(mockProps.dropdownHeaderTitle);
- expect(headerEl.findComponent(GlButton).props('icon')).toBe('close');
- });
-
- it('renders gl-search-box-by-type component', () => {
- const searchEl = findDropdownEl().findComponent(GlSearchBoxByType);
-
- expect(searchEl.exists()).toBe(true);
- expect(searchEl.attributes()).toMatchObject({
- placeholder: 'Search project',
- debounce: '300',
- });
- });
-
- it('renders gl-loading-icon component when projectsListLoading prop is true', async () => {
- findDropdownEl().vm.$emit('shown');
- await nextTick();
-
- expect(findLoadingIcon().exists()).toBe(true);
- });
-
- it('renders gl-dropdown-item components for available projects', async () => {
- findDropdownEl().vm.$emit('shown');
- await waitForPromises();
-
- findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
- await nextTick();
-
- expect(findAllDropdownItems()).toHaveLength(mockProjects.length);
- expect(findAllDropdownItems().at(0).props()).toMatchObject({
- isCheckItem: true,
- isChecked: true,
- });
- expect(findAllDropdownItems().at(0).text()).toBe(mockProjects[0].name_with_namespace);
- });
-
- it('renders string "No matching results" when search does not yield any matches', async () => {
- mock.onGet(mockProps.projectsFetchPath).reply(HTTP_STATUS_OK, []);
+ expect(findDropdownButton().props('disabled')).toBe(true);
+ });
- findSearchBox().vm.$emit('input', 'foo');
- await waitForPromises();
+ it('triggers a project search when dropdown button is clicked', async () => {
+ createComponent();
- expect(findDropdownContent().text()).toContain('No matching results');
- });
+ await findDropdownButton().trigger('click');
+ await waitForPromises();
- it('renders string "Failed to load projects" when loading projects list fails', async () => {
- mock.onGet(mockProps.projectsFetchPath).reply(HTTP_STATUS_OK, []);
- jest.spyOn(axios, 'get').mockRejectedValue({});
+ expect(mock.history.get).toHaveLength(1);
- findDropdownEl().vm.$emit('shown');
- await waitForPromises();
+ expect(findDropdownItemsText()).toEqual([
+ 'Gitlab Org / Gitlab Shell',
+ 'Gnuwget / Wget2',
+ 'Commit451 / Lab Coat',
+ ]);
+ });
- expect(findDropdownContent().text()).toContain('Failed to load projects');
- });
+ it('shows "No matching results" when no projects are found', async () => {
+ createComponent();
- it('renders gl-button within footer', async () => {
- const moveButtonEl = findFooter().findComponent(GlButton);
+ mock.onGet(mockProps.projectsFetchPath).reply(HTTP_STATUS_OK, []);
- expect(moveButtonEl.text()).toBe('Move');
- expect(moveButtonEl.attributes('disabled')).toBeDefined();
+ await findDropdown().vm.$emit('search', 'foobar');
+ await waitForPromises();
- findDropdownEl().vm.$emit('shown');
- await waitForPromises();
+ expect(findDropdown().text()).toContain('No matching results');
+ expect(findDropdownItemsText()).toEqual([]);
+ });
- findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
- await nextTick();
+ it('shows "Failed to load projects" when request fails', async () => {
+ createComponent();
- expect(findFooter().findComponent(GlButton).attributes('disabled')).not.toBeDefined();
- });
- });
+ mock.onGet(mockProps.projectsFetchPath).networkError();
- describe('events', () => {
- it('collapsed state element emits `toggle-collapse` event on component when clicked', () => {
- findCollapsedEl().trigger('click');
+ await findDropdown().vm.$emit('search', 'foobar');
+ await waitForPromises();
- expect(wrapper.emitted('toggle-collapse')).toHaveLength(1);
- });
+ expect(findDropdown().text()).toContain('Failed to load projects');
+ expect(findDropdownItemsText()).toEqual([]);
+ });
- it('gl-dropdown component calls `fetchProjects` on `shown` event', () => {
- jest.spyOn(axios, 'get');
+ it('disables the Move issuable button if no project is selected', async () => {
+ createComponent();
- findDropdownEl().vm.$emit('shown');
+ await findDropdownButton().trigger('click');
+ await waitForPromises();
- expect(axios.get).toHaveBeenCalled();
- });
+ expect(findDropdownMoveButton().props('disabled')).toBe(true);
+ });
- it('gl-dropdown component prevents dropdown body from closing on `hide` event when `projectItemClick` prop is true', async () => {
- findDropdownEl().vm.$emit('shown');
- await waitForPromises();
+ it('shows search results when search is successful', async () => {
+ createComponent();
- findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
- await nextTick();
+ mock.onGet(mockProps.projectsFetchPath).reply(HTTP_STATUS_OK, [
+ {
+ id: 2,
+ name_with_namespace: 'Gitlab Org / Gitlab Shell',
+ full_path: 'gitlab-org/gitlab-shell',
+ },
+ ]);
- findDropdownEl().vm.$emit('hide', mockEvent);
+ await findDropdown().vm.$emit('search', 'shell');
+ await waitForPromises();
- expect(mockEvent.preventDefault).toHaveBeenCalled();
- });
+ expect(findDropdownItemsText()).toEqual(['Gitlab Org / Gitlab Shell']);
+ });
- it('gl-dropdown component emits `dropdown-close` event on component from `hide` event', () => {
- findDropdownEl().vm.$emit('hide');
+ it('emits "move-issuable" event when Move issuable button is clicked', async () => {
+ createComponent();
- expect(wrapper.emitted('dropdown-close')).toHaveLength(1);
- });
+ await findDropdownButton().trigger('click');
+ await waitForPromises();
- it('close icon in dropdown header closes the dropdown when clicked', async () => {
- findHeader().findComponent(GlButton).vm.$emit('click', mockEvent);
+ await wrapper.findAllComponents(GlListboxItem).wrappers[0].trigger('click');
+ await findDropdownMoveButton().trigger('click');
- await nextTick();
- expect(hideMock).toHaveBeenCalled();
- });
+ expect(wrapper.emitted('move-issuable')).toEqual([[mockProjects[0]]]);
+ });
- it('sets project for clicked gl-dropdown-item to selectedProject', async () => {
- findDropdownEl().vm.$emit('shown');
- await waitForPromises();
+ it('disables the Move issuable button when moveInProgress prop is true', async () => {
+ createComponent({ moveInProgress: true });
- findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
- await nextTick();
+ await findDropdownButton().trigger('click');
+ await waitForPromises();
- expect(findAllDropdownItems().at(0).props('isChecked')).toBe(true);
- });
+ expect(findDropdownMoveButton().props('disabled')).toBe(true);
+ });
- it('hides dropdown and emits `move-issuable` event when move button is clicked', async () => {
- findDropdownEl().vm.$emit('shown');
- await waitForPromises();
+ it('emits "dropdown-close" event when dropdown is hidden', async () => {
+ createComponent();
- findAllDropdownItems().at(0).vm.$emit('click', mockEvent);
- await nextTick();
+ await findDropdownButton().trigger('click');
+ await waitForPromises();
- findFooter().findComponent(GlButton).vm.$emit('click');
+ await findDropdown().vm.$emit('hidden');
- expect(hideMock).toHaveBeenCalled();
- expect(wrapper.emitted('move-issuable')).toHaveLength(1);
- expect(wrapper.emitted('move-issuable')[0]).toEqual([mockProjects[0]]);
- });
- });
+ expect(wrapper.emitted('dropdown-close')).toHaveLength(1);
});
});
diff --git a/spec/frontend/work_items/components/work_item_milestone_spec.js b/spec/frontend/work_items/components/work_item_milestone_spec.js
index e303ad4b481..fc2c5eb2af2 100644
--- a/spec/frontend/work_items/components/work_item_milestone_spec.js
+++ b/spec/frontend/work_items/components/work_item_milestone_spec.js
@@ -1,14 +1,8 @@
-import {
- GlDropdown,
- GlDropdownItem,
- GlSearchBoxByType,
- GlSkeletonLoader,
- GlFormGroup,
- GlDropdownText,
-} from '@gitlab/ui';
+import { GlCollapsibleListbox, GlListboxItem, GlSkeletonLoader, GlFormGroup } from '@gitlab/ui';
+
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue';
+import WorkItemMilestone, { noMilestoneId } from '~/work_items/components/work_item_milestone.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -32,17 +26,13 @@ describe('WorkItemMilestone component', () => {
const workItemId = 'gid://gitlab/WorkItem/1';
const workItemType = 'Task';
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
- const findNoMilestoneDropdownItem = () => wrapper.findByTestId('no-milestone');
- const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
- const findFirstDropdownItem = () => findDropdownItems().at(0);
- const findDropdownTexts = () => wrapper.findAllComponents(GlDropdownText);
- const findDropdownItemAtIndex = (index) => findDropdownItems().at(index);
+ const findNoMilestoneDropdownItem = () => wrapper.findByTestId('listbox-item-no-milestone-id');
+ const findDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
const findDisabledTextSpan = () => wrapper.findByTestId('disabled-text');
- const findDropdownTextAtIndex = (index) => findDropdownTexts().at(index);
const findInputGroup = () => wrapper.findComponent(GlFormGroup);
+ const findNoResultsText = () => wrapper.findByTestId('no-results-text');
const successSearchQueryHandler = jest.fn().mockResolvedValue(projectMilestonesResponse);
const successSearchWithNoMatchingMilestones = jest
@@ -74,8 +64,7 @@ describe('WorkItemMilestone component', () => {
workItemType,
},
stubs: {
- GlDropdown,
- GlSearchBoxByType,
+ GlCollapsibleListbox,
},
});
};
@@ -106,7 +95,7 @@ describe('WorkItemMilestone component', () => {
it(`has a value of "Add to milestone"`, () => {
createComponent({ canUpdate: true, milestone: null });
- expect(findDropdown().props('text')).toBe(WorkItemMilestone.i18n.MILESTONE_PLACEHOLDER);
+ expect(findDropdown().props('toggleText')).toBe(WorkItemMilestone.i18n.MILESTONE_PLACEHOLDER);
});
});
@@ -114,7 +103,7 @@ describe('WorkItemMilestone component', () => {
it('has the search box', () => {
createComponent();
- expect(findSearchBox().exists()).toBe(true);
+ expect(findDropdown().props('searchable')).toBe(true);
});
it('shows no matching results when no items', () => {
@@ -122,9 +111,8 @@ describe('WorkItemMilestone component', () => {
searchQueryHandler: successSearchWithNoMatchingMilestones,
});
- expect(findDropdownTextAtIndex(0).text()).toBe(WorkItemMilestone.i18n.NO_MATCHING_RESULTS);
+ expect(findNoResultsText().text()).toBe(WorkItemMilestone.i18n.NO_MATCHING_RESULTS);
expect(findDropdownItems()).toHaveLength(1);
- expect(findDropdownTexts()).toHaveLength(1);
});
});
@@ -165,16 +153,18 @@ describe('WorkItemMilestone component', () => {
it('changes the milestone to null when clicked on no milestone', async () => {
showDropdown();
- findFirstDropdownItem().vm.$emit('click');
+ findDropdown().vm.$emit('select', noMilestoneId);
hideDropdown();
await nextTick();
expect(findDropdown().props('loading')).toBe(true);
await waitForPromises();
-
- expect(findDropdown().props('loading')).toBe(false);
- expect(findDropdown().props('text')).toBe(WorkItemMilestone.i18n.MILESTONE_PLACEHOLDER);
+ expect(findDropdown().props()).toMatchObject({
+ loading: false,
+ toggleText: WorkItemMilestone.i18n.MILESTONE_PLACEHOLDER,
+ toggleClass: expect.arrayContaining(['gl-text-gray-500!']),
+ });
});
it('changes the milestone to the selected milestone', async () => {
@@ -182,15 +172,16 @@ describe('WorkItemMilestone component', () => {
/** the index is -1 since no matching results is also a dropdown item */
const milestoneAtIndex =
projectMilestonesResponse.data.workspace.attributes.nodes[milestoneIndex - 1];
+
showDropdown();
await waitForPromises();
- findDropdownItemAtIndex(milestoneIndex).vm.$emit('click');
+ findDropdown().vm.$emit('select', milestoneAtIndex.id);
hideDropdown();
await waitForPromises();
- expect(findDropdown().props('text')).toBe(milestoneAtIndex.title);
+ expect(findDropdown().props('toggleText')).toBe(milestoneAtIndex.title);
});
});
@@ -208,7 +199,7 @@ describe('WorkItemMilestone component', () => {
});
showDropdown();
- findFirstDropdownItem().vm.$emit('click');
+ findDropdown().vm.$emit('select', noMilestoneId);
hideDropdown();
await waitForPromises();
@@ -224,7 +215,7 @@ describe('WorkItemMilestone component', () => {
createComponent({ canUpdate: true });
showDropdown();
- findFirstDropdownItem().vm.$emit('click');
+ findDropdown().vm.$emit('select', noMilestoneId);
hideDropdown();
await waitForPromises();
diff --git a/spec/frontend/work_items/components/work_item_parent_spec.js b/spec/frontend/work_items/components/work_item_parent_spec.js
index a977e7d396f..c60620d210a 100644
--- a/spec/frontend/work_items/components/work_item_parent_spec.js
+++ b/spec/frontend/work_items/components/work_item_parent_spec.js
@@ -87,7 +87,6 @@ describe('WorkItemParent component', () => {
headerText: 'Assign parent',
category: 'tertiary',
loading: false,
- noCaret: true,
isCheckCentered: true,
searchable: true,
searching: false,
@@ -96,7 +95,6 @@ describe('WorkItemParent component', () => {
toggleText: 'None',
searchPlaceholder: 'Search',
resetButtonLabel: 'Unassign',
- block: true,
});
});
@@ -104,14 +102,12 @@ describe('WorkItemParent component', () => {
createComponent({ canUpdate: false, parent: mockParentWidgetResponse });
expect(findCollapsibleListbox().exists()).toBe(false);
- expect(findParentText().exists()).toBe(true);
expect(findParentText().text()).toBe('Objective 101');
});
it('shows loading while searching', async () => {
await findCollapsibleListbox().vm.$emit('shown');
expect(findCollapsibleListbox().props('searching')).toBe(true);
- expect(findCollapsibleListbox().props('no-caret')).toBeUndefined();
});
});
@@ -170,7 +166,6 @@ describe('WorkItemParent component', () => {
describe('listbox', () => {
const selectWorkItem = async (workItem) => {
- await findCollapsibleListbox().vm.$emit('shown');
await findCollapsibleListbox().vm.$emit('select', workItem);
};
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
index 997f9e6eac7..14f99f144b2 100644
--- a/spec/helpers/environments_helper_spec.rb
+++ b/spec/helpers/environments_helper_spec.rb
@@ -23,8 +23,8 @@ RSpec.describe EnvironmentsHelper, feature_category: :environment_management do
'settings_path' => edit_project_settings_integration_path(project, 'prometheus'),
'clusters_path' => project_clusters_path(project),
'current_environment_name' => environment.name,
- 'documentation_path' => help_page_path('administration/monitoring/prometheus/index.md'),
- 'add_dashboard_documentation_path' => help_page_path('operations/metrics/dashboards/index.md', anchor: 'add-a-new-dashboard-to-your-project'),
+ 'documentation_path' => help_page_path('administration/monitoring/prometheus/index'),
+ 'add_dashboard_documentation_path' => help_page_path('operations/metrics/dashboards/index', anchor: 'add-a-new-dashboard-to-your-project'),
'empty_getting_started_svg_path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
'empty_loading_svg_path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'),
'empty_no_data_svg_path' => match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
diff --git a/spec/helpers/ide_helper_spec.rb b/spec/helpers/ide_helper_spec.rb
index 47500b8e21e..d5d7f8f72b3 100644
--- a/spec/helpers/ide_helper_spec.rb
+++ b/spec/helpers/ide_helper_spec.rb
@@ -101,7 +101,7 @@ RSpec.describe IdeHelper, feature_category: :web_ide do
'user-preferences-path' => profile_preferences_path,
'sign-in-path' => 'test-sign-in-path',
'new-web-ide-help-page-path' =>
- help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'),
+ help_page_path('user/project/web_ide/index', anchor: 'vscode-reimplementation'),
'csp-nonce' => 'test-csp-nonce',
'ide-remote-path' => ide_remote_path(remote_host: ':remote_host', remote_path: ':remote_path')
}
diff --git a/spec/helpers/operations_helper_spec.rb b/spec/helpers/operations_helper_spec.rb
index 9e50712a386..e018ab41a83 100644
--- a/spec/helpers/operations_helper_spec.rb
+++ b/spec/helpers/operations_helper_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe OperationsHelper do
it 'returns the correct values' do
expect(subject).to eq(
- 'alerts_setup_url' => help_page_path('operations/incident_management/integrations.md', anchor: 'configuration'),
+ 'alerts_setup_url' => help_page_path('operations/incident_management/integrations', anchor: 'configuration'),
'alerts_usage_url' => project_alert_management_index_path(project),
'prometheus_form_path' => project_settings_integration_path(project, prometheus_integration),
'prometheus_reset_key_path' => reset_alerting_token_project_settings_operations_path(project),
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
index 213dee0d19d..f70b38377d8 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
@@ -422,11 +422,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
describe '#create_batched_job!' do
let(:batched_migration) do
- create(:batched_background_migration,
- batch_size: 999,
- sub_batch_size: 99,
- pause_ms: 250
- )
+ create(
+ :batched_background_migration,
+ batch_size: 999,
+ sub_batch_size: 99,
+ pause_ms: 250
+ )
end
it 'creates a batched_job with the correct batch configuration' do
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
index 8d74d16f4e5..bbcb65109ce 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb
@@ -32,17 +32,17 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
end
it 'runs the migration job' do
- expect(job_class).to receive(:new)
- .with(start_id: 1,
- end_id: 10,
- batch_table: 'events',
- batch_column: 'id',
- sub_batch_size: 1,
- pause_ms: pause_ms,
- job_arguments: active_migration.job_arguments,
- connection: connection,
- sub_batch_exception: sub_batch_exception)
- .and_return(job_instance)
+ expect(job_class).to receive(:new).with(
+ start_id: 1,
+ end_id: 10,
+ batch_table: 'events',
+ batch_column: 'id',
+ sub_batch_size: 1,
+ pause_ms: pause_ms,
+ job_arguments: active_migration.job_arguments,
+ connection: connection,
+ sub_batch_exception: sub_batch_exception
+ ).and_return(job_instance)
expect(job_instance).to receive(:perform).with(no_args)
diff --git a/spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb b/spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb
index 1f256de35ec..8f380a8229c 100644
--- a/spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/prometheus_metrics_spec.rb
@@ -5,11 +5,14 @@ require 'spec_helper'
RSpec.describe Gitlab::Database::BackgroundMigration::PrometheusMetrics, :prometheus do
describe '#track' do
let(:job_record) do
- build(:batched_background_migration_job, :succeeded,
- started_at: Time.current - 2.minutes,
- finished_at: Time.current - 1.minute,
- updated_at: Time.current,
- metrics: { 'timings' => { 'update_all' => [0.05, 0.2, 0.4, 0.9, 4] } })
+ build(
+ :batched_background_migration_job,
+ :succeeded,
+ started_at: Time.current - 2.minutes,
+ finished_at: Time.current - 1.minute,
+ updated_at: Time.current,
+ metrics: { 'timings' => { 'update_all' => [0.05, 0.2, 0.4, 0.9, 4] } }
+ )
end
let(:labels) { job_record.batched_migration.prometheus_labels }
diff --git a/spec/lib/gitlab/database/bulk_update_spec.rb b/spec/lib/gitlab/database/bulk_update_spec.rb
index fa519cffd6b..2f0859dba74 100644
--- a/spec/lib/gitlab/database/bulk_update_spec.rb
+++ b/spec/lib/gitlab/database/bulk_update_spec.rb
@@ -92,7 +92,7 @@ RSpec.describe Gitlab::Database::BulkUpdate do
end
context 'validates prepared_statements support', :reestablished_active_record_base,
- :suppress_gitlab_schemas_validate_connection do
+ :suppress_gitlab_schemas_validate_connection do
using RSpec::Parameterized::TableSyntax
where(:prepared_statements) do
diff --git a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
index 552df64096a..1824a50cb28 100644
--- a/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/loose_foreign_keys_spec.rb
@@ -8,14 +8,14 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do
it 'all definitions have assigned a known gitlab_schema and on_delete' do
is_expected.to all(have_attributes(
- options: a_hash_including(
- column: be_a(String),
- gitlab_schema: be_in(Gitlab::Database.schemas_to_base_models.symbolize_keys.keys),
- on_delete: be_in([:async_delete, :async_nullify])
- ),
- from_table: be_a(String),
- to_table: be_a(String)
- ))
+ options: a_hash_including(
+ column: be_a(String),
+ gitlab_schema: be_in(Gitlab::Database.schemas_to_base_models.symbolize_keys.keys),
+ on_delete: be_in([:async_delete, :async_nullify])
+ ),
+ from_table: be_a(String),
+ to_table: be_a(String)
+ ))
end
context 'ensure keys are sorted' do
diff --git a/spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb b/spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb
index 476b5f3a784..4d7c51a3fbf 100644
--- a/spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migrations/constraints_helpers_spec.rb
@@ -13,9 +13,11 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
describe '#check_constraint_name' do
it 'returns a valid constraint name' do
- name = model.check_constraint_name(:this_is_a_very_long_table_name,
- :with_a_very_long_column_name,
- :with_a_very_long_type)
+ name = model.check_constraint_name(
+ :this_is_a_very_long_table_name,
+ :with_a_very_long_column_name,
+ :with_a_very_long_type
+ )
expect(name).to be_an_instance_of(String)
expect(name).to start_with('check_')
@@ -404,9 +406,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
describe '#add_text_limit' do
context 'when it is called with the default options' do
it 'calls add_check_constraint with an infered constraint name and validate: true' do
- constraint_name = model.check_constraint_name(:test_table,
- :name,
- 'max_length')
+ constraint_name = model.check_constraint_name(:test_table, :name, 'max_length')
check = "char_length(name) <= 255"
expect(model).to receive(:check_constraint_name).and_call_original
@@ -440,9 +440,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
describe '#validate_text_limit' do
context 'when constraint_name is not provided' do
it 'calls validate_check_constraint with an infered constraint name' do
- constraint_name = model.check_constraint_name(:test_table,
- :name,
- 'max_length')
+ constraint_name = model.check_constraint_name(:test_table, :name, 'max_length')
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:validate_check_constraint)
@@ -468,9 +466,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
describe '#remove_text_limit' do
context 'when constraint_name is not provided' do
it 'calls remove_check_constraint with an infered constraint name' do
- constraint_name = model.check_constraint_name(:test_table,
- :name,
- 'max_length')
+ constraint_name = model.check_constraint_name(:test_table, :name, 'max_length')
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:remove_check_constraint)
@@ -496,9 +492,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
describe '#check_text_limit_exists?' do
context 'when constraint_name is not provided' do
it 'calls check_constraint_exists? with an infered constraint name' do
- constraint_name = model.check_constraint_name(:test_table,
- :name,
- 'max_length')
+ constraint_name = model.check_constraint_name(:test_table, :name, 'max_length')
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:check_constraint_exists?)
@@ -524,9 +518,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
describe '#add_not_null_constraint' do
context 'when it is called with the default options' do
it 'calls add_check_constraint with an infered constraint name and validate: true' do
- constraint_name = model.check_constraint_name(:test_table,
- :name,
- 'not_null')
+ constraint_name = model.check_constraint_name(:test_table, :name, 'not_null')
check = "name IS NOT NULL"
expect(model).to receive(:column_is_nullable?).and_return(true)
@@ -571,9 +563,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
describe '#validate_not_null_constraint' do
context 'when constraint_name is not provided' do
it 'calls validate_check_constraint with an infered constraint name' do
- constraint_name = model.check_constraint_name(:test_table,
- :name,
- 'not_null')
+ constraint_name = model.check_constraint_name(:test_table, :name, 'not_null')
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:validate_check_constraint)
@@ -599,9 +589,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
describe '#remove_not_null_constraint' do
context 'when constraint_name is not provided' do
it 'calls remove_check_constraint with an infered constraint name' do
- constraint_name = model.check_constraint_name(:test_table,
- :name,
- 'not_null')
+ constraint_name = model.check_constraint_name(:test_table, :name, 'not_null')
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:remove_check_constraint)
@@ -627,9 +615,7 @@ RSpec.describe Gitlab::Database::Migrations::ConstraintsHelpers do
describe '#check_not_null_constraint_exists?' do
context 'when constraint_name is not provided' do
it 'calls check_constraint_exists? with an infered constraint name' do
- constraint_name = model.check_constraint_name(:test_table,
- :name,
- 'not_null')
+ constraint_name = model.check_constraint_name(:test_table, :name, 'not_null')
expect(model).to receive(:check_constraint_name).and_call_original
expect(model).to receive(:check_constraint_exists?)
diff --git a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb
index a12e0909dc2..81368dde94d 100644
--- a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb
+++ b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb
@@ -75,8 +75,12 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do
context 'on successful execution' do
subject do
- instrumentation.observe(version: migration_version, name: migration_name,
- connection: connection, meta: migration_meta) {}
+ instrumentation.observe(
+ version: migration_version,
+ name: migration_name,
+ connection: connection,
+ meta: migration_meta
+ ) {}
end
it 'records a valid observation', :aggregate_failures do
@@ -98,8 +102,12 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do
with_them do
subject(:observe) do
- instrumentation.observe(version: migration_version, name: migration_name,
- connection: connection, meta: migration_meta) { raise exception, error_message }
+ instrumentation.observe(
+ version: migration_version,
+ name: migration_name,
+ connection: connection,
+ meta: migration_meta
+ ) { raise exception, error_message }
end
context 'retrieving observations' do
diff --git a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
index 31a194ae883..660c4347ffa 100644
--- a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
+++ b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb
@@ -80,8 +80,11 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
end
subject(:sample_migration) do
- described_class.new(result_dir: result_dir, connection: connection,
- from_id: from_id).run_jobs(for_duration: 1.minute)
+ described_class.new(
+ result_dir: result_dir,
+ connection: connection,
+ from_id: from_id
+ ).run_jobs(for_duration: 1.minute)
end
it 'runs sampled jobs from the batched background migration' do
@@ -125,12 +128,19 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
calls << args
end
- queue_migration(migration_name, table_name, :id,
- job_interval: 5.minutes,
- batch_size: 100)
+ queue_migration(
+ migration_name,
+ table_name,
+ :id,
+ job_interval: 5.minutes,
+ batch_size: 100
+ )
- described_class.new(result_dir: result_dir, connection: connection,
- from_id: from_id).run_jobs(for_duration: 3.minutes)
+ described_class.new(
+ result_dir: result_dir,
+ connection: connection,
+ from_id: from_id
+ ).run_jobs(for_duration: 3.minutes)
expect(calls).not_to be_empty
end
@@ -142,13 +152,19 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
calls << args
end
- queue_migration(migration_name, table_name, :id,
- job_interval: 5.minutes,
- batch_size: num_rows_in_table * 2,
- sub_batch_size: num_rows_in_table * 2)
+ queue_migration(
+ migration_name,
+ table_name, :id,
+ job_interval: 5.minutes,
+ batch_size: num_rows_in_table * 2,
+ sub_batch_size: num_rows_in_table * 2
+ )
- described_class.new(result_dir: result_dir, connection: connection,
- from_id: from_id).run_jobs(for_duration: 3.minutes)
+ described_class.new(
+ result_dir: result_dir,
+ connection: connection,
+ from_id: from_id
+ ).run_jobs(for_duration: 3.minutes)
expect(calls.size).to eq(1)
end
@@ -161,13 +177,20 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
calls << args
end
- queue_migration(migration_name, table_name, :id,
- job_interval: 5.minutes,
- batch_size: num_rows_in_table * 2,
- sub_batch_size: num_rows_in_table * 2)
-
- described_class.new(result_dir: result_dir, connection: connection,
- from_id: from_id).run_jobs(for_duration: 3.minutes)
+ queue_migration(
+ migration_name,
+ table_name,
+ :id,
+ job_interval: 5.minutes,
+ batch_size: num_rows_in_table * 2,
+ sub_batch_size: num_rows_in_table * 2
+ )
+
+ described_class.new(
+ result_dir: result_dir,
+ connection: connection,
+ from_id: from_id
+ ).run_jobs(for_duration: 3.minutes)
expect(calls.count).to eq(0)
end
@@ -181,26 +204,41 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
it 'runs all pending jobs based on the last migration id' do
old_migration = define_background_migration(migration_name)
- queue_migration(migration_name, table_name, :id,
- job_interval: 5.minutes,
- batch_size: 100)
+ queue_migration(
+ migration_name,
+ table_name,
+ :id,
+ job_interval: 5.minutes,
+ batch_size: 100
+ )
last_id
new_migration = define_background_migration('NewMigration') { travel 1.second }
- queue_migration('NewMigration', table_name, :id,
- job_interval: 5.minutes,
- batch_size: 10,
- sub_batch_size: 5)
+ queue_migration(
+ 'NewMigration',
+ table_name,
+ :id,
+ job_interval: 5.minutes,
+ batch_size: 10,
+ sub_batch_size: 5
+ )
other_new_migration = define_background_migration('NewMigration2') { travel 2.seconds }
- queue_migration('NewMigration2', table_name, :id,
- job_interval: 5.minutes,
- batch_size: 10,
- sub_batch_size: 5)
+ queue_migration(
+ 'NewMigration2',
+ table_name,
+ :id,
+ job_interval: 5.minutes,
+ batch_size: 10,
+ sub_batch_size: 5
+ )
expect_migration_runs(new_migration => 3, other_new_migration => 2, old_migration => 0) do
- described_class.new(result_dir: result_dir, connection: connection,
- from_id: last_id).run_jobs(for_duration: 5.seconds)
+ described_class.new(
+ result_dir: result_dir,
+ connection: connection,
+ from_id: last_id
+ ).run_jobs(for_duration: 5.seconds)
end
end
end
diff --git a/spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb b/spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb
index 04940028aee..eb78d836be0 100644
--- a/spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/detached_partition_dropper_spec.rb
@@ -57,17 +57,19 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do
SQL
end
- Postgresql::DetachedPartition.create!(table_name: name,
- drop_after: drop_after)
+ Postgresql::DetachedPartition.create!(table_name: name, drop_after: drop_after)
end
describe '#perform' do
context 'when the partition should not be dropped yet' do
it 'does not drop the partition' do
- create_partition(name: :_test_partition,
- from: 2.months.ago, to: 1.month.ago,
- attached: false,
- drop_after: 1.day.from_now)
+ create_partition(
+ name: :_test_partition,
+ from: 2.months.ago,
+ to: 1.month.ago,
+ attached: false,
+ drop_after: 1.day.from_now
+ )
dropper.perform
@@ -77,11 +79,13 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do
context 'with a partition to drop' do
before do
- create_partition(name: :_test_partition,
- from: 2.months.ago,
- to: 1.month.ago.beginning_of_month,
- attached: false,
- drop_after: 1.second.ago)
+ create_partition(
+ name: :_test_partition,
+ from: 2.months.ago,
+ to: 1.month.ago.beginning_of_month,
+ attached: false,
+ drop_after: 1.second.ago
+ )
end
it 'drops the partition' do
@@ -159,11 +163,13 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do
context 'when the partition to drop is still attached to its table' do
before do
- create_partition(name: :_test_partition,
- from: 2.months.ago,
- to: 1.month.ago.beginning_of_month,
- attached: true,
- drop_after: 1.second.ago)
+ create_partition(
+ name: :_test_partition,
+ from: 2.months.ago,
+ to: 1.month.ago.beginning_of_month,
+ attached: true,
+ drop_after: 1.second.ago
+ )
end
it 'does not drop the partition, but does remove the DetachedPartition entry' do
@@ -192,17 +198,21 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do
context 'with multiple partitions to drop' do
before do
- create_partition(name: :_test_partition_1,
- from: 3.months.ago,
- to: 2.months.ago,
- attached: false,
- drop_after: 1.second.ago)
-
- create_partition(name: :_test_partition_2,
- from: 2.months.ago,
- to: 1.month.ago,
- attached: false,
- drop_after: 1.second.ago)
+ create_partition(
+ name: :_test_partition_1,
+ from: 3.months.ago,
+ to: 2.months.ago,
+ attached: false,
+ drop_after: 1.second.ago
+ )
+
+ create_partition(
+ name: :_test_partition_2,
+ from: 2.months.ago,
+ to: 1.month.ago,
+ attached: false,
+ drop_after: 1.second.ago
+ )
end
it 'drops both partitions' do
diff --git a/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb b/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb
index 3afa338fdf7..8b18e5b6d08 100644
--- a/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/monthly_strategy_spec.rb
@@ -235,8 +235,12 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category
subject { described_class.new(model, partitioning_key, retain_for: 3.months).extra_partitions }
it 'prunes the unbounded partition ending 2020-05-01' do
- min_value_to_may = Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-05-01',
- partition_name: '_test_partitioned_test_000000')
+ min_value_to_may = Gitlab::Database::Partitioning::TimePartition.new(
+ model.table_name,
+ nil,
+ '2020-05-01',
+ partition_name: '_test_partitioned_test_000000'
+ )
expect(subject).to contain_exactly(min_value_to_may)
end
@@ -247,8 +251,18 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category
it 'prunes the unbounded partition and the partition for May-June' do
expect(subject).to contain_exactly(
- Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-05-01', partition_name: '_test_partitioned_test_000000'),
- Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01', partition_name: '_test_partitioned_test_202005')
+ Gitlab::Database::Partitioning::TimePartition.new(
+ model.table_name,
+ nil,
+ '2020-05-01',
+ partition_name: '_test_partitioned_test_000000'
+ ),
+ Gitlab::Database::Partitioning::TimePartition.new(
+ model.table_name,
+ '2020-05-01',
+ '2020-06-01',
+ partition_name: '_test_partitioned_test_202005'
+ )
)
end
@@ -257,8 +271,18 @@ RSpec.describe Gitlab::Database::Partitioning::MonthlyStrategy, feature_category
it 'prunes empty partitions' do
expect(subject).to contain_exactly(
- Gitlab::Database::Partitioning::TimePartition.new(model.table_name, nil, '2020-05-01', partition_name: '_test_partitioned_test_000000'),
- Gitlab::Database::Partitioning::TimePartition.new(model.table_name, '2020-05-01', '2020-06-01', partition_name: '_test_partitioned_test_202005')
+ Gitlab::Database::Partitioning::TimePartition.new(
+ model.table_name,
+ nil,
+ '2020-05-01',
+ partition_name: '_test_partitioned_test_000000'
+ ),
+ Gitlab::Database::Partitioning::TimePartition.new(
+ model.table_name,
+ '2020-05-01',
+ '2020-06-01',
+ partition_name: '_test_partitioned_test_202005'
+ )
)
end
diff --git a/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
index ac4d345271e..9ca0a1b6e57 100644
--- a/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb
@@ -15,9 +15,12 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy, feature_cate
let(:detach_partition_if) { double('detach_partition_if') }
subject(:strategy) do
- described_class.new(model, :partition,
- next_partition_if: next_partition_if,
- detach_partition_if: detach_partition_if)
+ described_class.new(
+ model,
+ :partition,
+ next_partition_if: next_partition_if,
+ detach_partition_if: detach_partition_if
+ )
end
before do
@@ -213,9 +216,9 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy, feature_cate
include PartitionedTable
partitioned_by :partition,
- strategy: :sliding_list,
- next_partition_if: proc { false },
- detach_partition_if: proc { false }
+ strategy: :sliding_list,
+ next_partition_if: proc { false },
+ detach_partition_if: proc { false }
end
end.to raise_error(/ignored_columns/)
end
@@ -228,9 +231,9 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy, feature_cate
self.ignored_columns = [:partition]
partitioned_by :partition,
- strategy: :sliding_list,
- next_partition_if: proc { false },
- detach_partition_if: proc { false }
+ strategy: :sliding_list,
+ next_partition_if: proc { false },
+ detach_partition_if: proc { false }
end
end.not_to raise_error
end
@@ -255,9 +258,9 @@ RSpec.describe Gitlab::Database::Partitioning::SlidingListStrategy, feature_cate
detach_partition?(...)
end
partitioned_by :partition,
- strategy: :sliding_list,
- next_partition_if: method(:next_partition_if_wrapper),
- detach_partition_if: method(:detach_partition_if_wrapper)
+ strategy: :sliding_list,
+ next_partition_if: method(:next_partition_if_wrapper),
+ detach_partition_if: method(:detach_partition_if_wrapper)
def self.next_partition?(current_partition); end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb
index a81c8a5a49c..aa644885306 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb
@@ -99,8 +99,11 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers do
expect(migration).to receive(:add_index)
.with(table_name, column_name, { name: index_name, where: 'x > 0', unique: true })
- migration.add_concurrent_partitioned_index(table_name, column_name,
- { name: index_name, where: 'x > 0', unique: true })
+ migration.add_concurrent_partitioned_index(
+ table_name,
+ column_name,
+ { name: index_name, where: 'x > 0', unique: true }
+ )
end
end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
index 6a947044317..182b6960384 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
@@ -51,13 +51,15 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
end
it 'delegates to a method on List::ConvertTable' do
- expect_next_instance_of(Gitlab::Database::Partitioning::List::ConvertTable,
- migration_context: migration,
- table_name: source_table,
- parent_table_name: partitioned_table,
- partitioning_column: partition_column,
- zero_partition_value: min_date,
- **extra_options) do |converter|
+ expect_next_instance_of(
+ Gitlab::Database::Partitioning::List::ConvertTable,
+ migration_context: migration,
+ table_name: source_table,
+ parent_table_name: partitioned_table,
+ partitioning_column: partition_column,
+ zero_partition_value: min_date,
+ **extra_options
+ ) do |converter|
expect(converter).to receive(expected_method)
end
@@ -70,11 +72,13 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
let(:lock_tables) { [source_table] }
let(:expected_method) { :partition }
let(:migrate) do
- migration.convert_table_to_first_list_partition(table_name: source_table,
- partitioning_column: partition_column,
- parent_table_name: partitioned_table,
- initial_partitioning_value: min_date,
- lock_tables: lock_tables)
+ migration.convert_table_to_first_list_partition(
+ table_name: source_table,
+ partitioning_column: partition_column,
+ parent_table_name: partitioned_table,
+ initial_partitioning_value: min_date,
+ lock_tables: lock_tables
+ )
end
end
end
@@ -83,10 +87,12 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
it_behaves_like 'delegates to ConvertTable' do
let(:expected_method) { :revert_partitioning }
let(:migrate) do
- migration.revert_converting_table_to_first_list_partition(table_name: source_table,
- partitioning_column: partition_column,
- parent_table_name: partitioned_table,
- initial_partitioning_value: min_date)
+ migration.revert_converting_table_to_first_list_partition(
+ table_name: source_table,
+ partitioning_column: partition_column,
+ parent_table_name: partitioned_table,
+ initial_partitioning_value: min_date
+ )
end
end
end
@@ -95,11 +101,13 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
it_behaves_like 'delegates to ConvertTable' do
let(:expected_method) { :prepare_for_partitioning }
let(:migrate) do
- migration.prepare_constraint_for_list_partitioning(table_name: source_table,
- partitioning_column: partition_column,
- parent_table_name: partitioned_table,
- initial_partitioning_value: min_date,
- async: false)
+ migration.prepare_constraint_for_list_partitioning(
+ table_name: source_table,
+ partitioning_column: partition_column,
+ parent_table_name: partitioned_table,
+ initial_partitioning_value: min_date,
+ async: false
+ )
end
end
end
@@ -108,10 +116,12 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
it_behaves_like 'delegates to ConvertTable' do
let(:expected_method) { :revert_preparation_for_partitioning }
let(:migrate) do
- migration.revert_preparing_constraint_for_list_partitioning(table_name: source_table,
- partitioning_column: partition_column,
- parent_table_name: partitioned_table,
- initial_partitioning_value: min_date)
+ migration.revert_preparing_constraint_for_list_partitioning(
+ table_name: source_table,
+ partitioning_column: partition_column,
+ parent_table_name: partitioned_table,
+ initial_partitioning_value: min_date
+ )
end
end
end
diff --git a/spec/lib/gitlab/database/postgres_constraint_spec.rb b/spec/lib/gitlab/database/postgres_constraint_spec.rb
index 75084a69115..140180540e0 100644
--- a/spec/lib/gitlab/database/postgres_constraint_spec.rb
+++ b/spec/lib/gitlab/database/postgres_constraint_spec.rb
@@ -51,9 +51,11 @@ RSpec.describe Gitlab::Database::PostgresConstraint, type: :model do
subject(:check_constraints) { described_class.check_constraints.by_table_identifier(table_identifier) }
it 'finds check constraints for the table' do
- expect(check_constraints.map(&:name)).to contain_exactly(check_constraint_a_positive,
- check_constraint_a_gt_b,
- invalid_constraint_a)
+ expect(check_constraints.map(&:name)).to contain_exactly(
+ check_constraint_a_positive,
+ check_constraint_a_gt_b,
+ invalid_constraint_a
+ )
end
it 'includes columns for the check constraints', :aggregate_failures do
@@ -108,8 +110,12 @@ RSpec.describe Gitlab::Database::PostgresConstraint, type: :model do
describe '#including_column' do
it 'only matches constraints on the given column' do
constraints_on_a = described_class.by_table_identifier(table_identifier).including_column('a').map(&:name)
- expect(constraints_on_a).to contain_exactly(check_constraint_a_positive, check_constraint_a_gt_b,
- unique_constraint_a, invalid_constraint_a)
+ expect(constraints_on_a).to contain_exactly(
+ check_constraint_a_positive,
+ check_constraint_a_gt_b,
+ unique_constraint_a,
+ invalid_constraint_a
+ )
end
end
diff --git a/spec/lib/gitlab/database/tables_truncate_spec.rb b/spec/lib/gitlab/database/tables_truncate_spec.rb
index e41c7d34378..350e36ff1f1 100644
--- a/spec/lib/gitlab/database/tables_truncate_spec.rb
+++ b/spec/lib/gitlab/database/tables_truncate_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::TablesTruncate, :reestablished_active_record_base,
- :suppress_gitlab_schemas_validate_connection, feature_category: :cell do
+ :suppress_gitlab_schemas_validate_connection, feature_category: :cell do
include MigrationsHelpers
let(:min_batch_size) { 1 }
diff --git a/spec/migrations/20231016001000_fix_design_user_mentions_design_id_note_id_index_for_self_managed_spec.rb b/spec/migrations/20231016001000_fix_design_user_mentions_design_id_note_id_index_for_self_managed_spec.rb
new file mode 100644
index 00000000000..8d890bb7cac
--- /dev/null
+++ b/spec/migrations/20231016001000_fix_design_user_mentions_design_id_note_id_index_for_self_managed_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe FixDesignUserMentionsDesignIdNoteIdIndexForSelfManaged, feature_category: :database do
+ let(:connection) { described_class.new.connection }
+ let(:design_user_mentions) { table(:design_user_mentions) }
+
+ shared_examples 'index `design_user_mentions_on_design_id_and_note_id_unique_index` already exists' do
+ it 'does not swap the columns' do
+ disable_migrations_output do
+ reversible_migration do |migration|
+ migration.before -> {
+ index = connection.indexes(:design_user_mentions).find do |i|
+ i.name == 'design_user_mentions_on_design_id_and_note_id_unique_index'
+ end
+ expect(index.columns).to eq(%w[design_id note_id])
+ }
+
+ migration.after -> {
+ index = connection.indexes(:design_user_mentions).find do |i|
+ i.name == 'design_user_mentions_on_design_id_and_note_id_unique_index'
+ end
+ expect(index.columns).to eq(%w[design_id note_id])
+ }
+ end
+ end
+ end
+ end
+
+ describe '#up' do
+ before do
+ # rubocop:disable RSpec/AnyInstanceOf
+ allow_any_instance_of(described_class).to(
+ receive(:com_or_dev_or_test_but_not_jh?).and_return(com_or_dev_or_test_but_not_jh?)
+ )
+ # rubocop:enable RSpec/AnyInstanceOf
+ end
+
+ context 'when GitLab.com, dev, or test' do
+ let(:com_or_dev_or_test_but_not_jh?) { true }
+
+ it_behaves_like 'index `design_user_mentions_on_design_id_and_note_id_unique_index` already exists'
+ end
+
+ context 'when self-managed instance' do
+ let(:com_or_dev_or_test_but_not_jh?) { false }
+
+ context "when index does not exist" do
+ before do
+ connection.execute('DROP INDEX IF EXISTS design_user_mentions_on_design_id_and_note_id_unique_index')
+ end
+
+ after do
+ connection.execute('CREATE UNIQUE INDEX IF NOT EXISTS
+ design_user_mentions_on_design_id_and_note_id_unique_index
+ ON design_user_mentions (design_id, note_id)')
+ end
+
+ it 'creates the index' do
+ disable_migrations_output { migrate! }
+
+ index = connection.indexes(:design_user_mentions).find do |i|
+ i.name == 'design_user_mentions_on_design_id_and_note_id_unique_index'
+ end
+
+ expect(index.columns).to eq(%w[design_id note_id])
+ end
+ end
+
+ context "when index does exist" do
+ it_behaves_like 'index `design_user_mentions_on_design_id_and_note_id_unique_index` already exists'
+ end
+
+ context "when index does exists on the int4 column" do
+ before do
+ connection.execute('DROP INDEX IF EXISTS design_user_mentions_on_design_id_and_note_id_unique_index')
+ connection.execute(
+ 'ALTER TABLE design_user_mentions ADD COLUMN IF NOT EXISTS note_id_convert_to_bigint integer'
+ )
+ connection.execute('CREATE UNIQUE INDEX
+ design_user_mentions_on_design_id_and_note_id_unique_index
+ ON design_user_mentions (design_id, note_id_convert_to_bigint)')
+ end
+
+ after do
+ connection.execute('DROP INDEX IF EXISTS design_user_mentions_on_design_id_and_note_id_unique_index')
+ connection.execute('ALTER TABLE design_user_mentions DROP COLUMN IF EXISTS note_id_convert_to_bigint')
+ connection.execute('CREATE UNIQUE INDEX
+ design_user_mentions_on_design_id_and_note_id_unique_index
+ ON design_user_mentions (design_id, note_id)')
+ end
+
+ it 'creates the index on the int8 column' do
+ index = connection.indexes(:design_user_mentions).find do |i|
+ i.name == 'design_user_mentions_on_design_id_and_note_id_unique_index'
+ end
+
+ expect(index.columns).to eq(%w[design_id note_id_convert_to_bigint])
+
+ disable_migrations_output { migrate! }
+
+ index = connection.indexes(:design_user_mentions).find do |i|
+ i.name == 'design_user_mentions_on_design_id_and_note_id_unique_index'
+ end
+
+ expect(index.columns).to eq(%w[design_id note_id])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 755f1ea6078..aacb696a88e 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -123,7 +123,7 @@ RSpec.describe Clusters::ClusterPresenter do
'clusters-path': clusterable_presenter.index_path,
'dashboard-endpoint': clusterable_presenter.metrics_dashboard_path(cluster),
'documentation-path': help_page_path('user/infrastructure/clusters/manage/clusters_health'),
- 'add-dashboard-documentation-path': help_page_path('operations/metrics/dashboards/index.md', anchor: 'add-a-new-dashboard-to-your-project'),
+ 'add-dashboard-documentation-path': help_page_path('operations/metrics/dashboards/index', anchor: 'add-a-new-dashboard-to-your-project'),
'empty-getting-started-svg-path': match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path': match_asset_path('/assets/illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path': match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
diff --git a/spec/rubocop/cop/gitlab/doc_url_spec.rb b/spec/rubocop/cop/gitlab/doc_url_spec.rb
index 957edc8286b..fa055f9b2fe 100644
--- a/spec/rubocop/cop/gitlab/doc_url_spec.rb
+++ b/spec/rubocop/cop/gitlab/doc_url_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe RuboCop::Cop::Gitlab::DocUrl, feature_category: :shared do
it 'registers an offense' do
expect_offense(<<~RUBY)
'See [the docs](https://docs.gitlab.com/ee/user/permissions#roles).'
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `#help_page_url` instead of directly including link. See https://docs.gitlab.com/ee/development/documentation/#linking-to-help-in-ruby.
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `#help_page_url` instead of directly including link. See [...]
RUBY
end
end
@@ -19,7 +19,7 @@ RSpec.describe RuboCop::Cop::Gitlab::DocUrl, feature_category: :shared do
expect_offense(<<~'RUBY')
'See the docs: ' \
'https://docs.gitlab.com/ee/user/permissions#roles'
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `#help_page_url` instead of directly including link. See https://docs.gitlab.com/ee/development/documentation/#linking-to-help-in-ruby.
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `#help_page_url` instead of directly including link. See [...]
RUBY
end
end
@@ -30,7 +30,7 @@ RSpec.describe RuboCop::Cop::Gitlab::DocUrl, feature_category: :shared do
<<-HEREDOC
See the docs:
https://docs.gitlab.com/ee/user/permissions#roles
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `#help_page_url` instead of directly including link. See https://docs.gitlab.com/ee/development/documentation/#linking-to-help-in-ruby.
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `#help_page_url` instead of directly including link. See [...]
HEREDOC
RUBY
end
diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb
index a8fd96a03bb..1faf4c6fe4c 100644
--- a/spec/serializers/issue_entity_spec.rb
+++ b/spec/serializers/issue_entity_spec.rb
@@ -149,7 +149,7 @@ RSpec.describe IssueEntity do
end
it 'returns archived project doc' do
- expect(subject[:archived_project_docs_path]).to eq('/help/user/project/settings/index.md#archive-a-project')
+ expect(subject[:archived_project_docs_path]).to eq('/help/user/project/settings/index#archive-a-project')
end
end
end
diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb
index 8c577749107..94db1c55a90 100644
--- a/spec/support/shared_examples/features/work_items_shared_examples.rb
+++ b/spec/support/shared_examples/features/work_items_shared_examples.rb
@@ -371,12 +371,12 @@ RSpec.shared_examples 'work items milestone' do
it 'searches and sets or removes milestone for the work item' do
click_button s_('WorkItem|Add to milestone')
send_keys "\"#{milestone.title}\""
- click_button milestone.title
+ select_listbox_item(milestone.title, exact_text: true)
expect(page).to have_button(milestone.title)
click_button milestone.title
- click_button s_('WorkItem|No milestone')
+ select_listbox_item(s_('WorkItem|No milestone'), exact_text: true)
expect(page).to have_button(s_('WorkItem|Add to milestone'))
end