diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-10 15:09:48 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-10 15:09:48 +0300 |
commit | 1c00bf77814669d7d35c8aede82553c7e8883e18 (patch) | |
tree | 34f35f73a4631c81c18b07b05a4cf5886c5c2cec | |
parent | f605b80ff70b395afa345bad11c7f8aa0506a9bf (diff) |
Add latest changes from gitlab-org/gitlab@master
39 files changed, 690 insertions, 352 deletions
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index 8d202194de7..befb96034f5 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -201,7 +201,7 @@ export default { data-testid="group-name" :href="group.relativePath" :title="group.fullName" - class="no-expand gl-mr-3 gl-text-gray-900!" + class="no-expand gl-mr-3 gl-text-gray-900! gl-word-break-word" :itemprop="microdata.nameItemprop" > <!-- ending bracket must be by closing tag to prevent --> diff --git a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_issuables.vue b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_issuables.vue new file mode 100644 index 00000000000..dbab0000594 --- /dev/null +++ b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_issuables.vue @@ -0,0 +1,44 @@ +<script> +import { GlDisclosureDropdownGroup } from '@gitlab/ui'; +import { mapState, mapGetters } from 'vuex'; +import { ALL_GITLAB } from '~/vue_shared/global_search/constants'; + +export default { + name: 'DefaultIssuables', + i18n: { + ALL_GITLAB, + }, + components: { + GlDisclosureDropdownGroup, + }, + computed: { + ...mapState(['searchContext']), + ...mapGetters(['defaultSearchOptions']), + currentContextName() { + return ( + this.searchContext?.project?.name || + this.searchContext?.group?.name || + this.$options.i18n.ALL_GITLAB + ); + }, + shouldRender() { + return this.group.items.length > 0; + }, + group() { + return { + name: this.currentContextName, + items: this.defaultSearchOptions, + }; + }, + }, + created() { + if (!this.shouldRender) { + this.$emit('nothing-to-render'); + } + }, +}; +</script> + +<template> + <gl-disclosure-dropdown-group v-if="shouldRender" v-bind="$attrs" :group="group" /> +</template> diff --git a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_items.vue b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_items.vue index eb76277d12b..0c325f6d13f 100644 --- a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_items.vue +++ b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_items.vue @@ -1,54 +1,51 @@ <script> -import { GlDisclosureDropdownGroup } from '@gitlab/ui'; -import { mapState, mapGetters } from 'vuex'; -import { ALL_GITLAB, PLACES } from '~/vue_shared/global_search/constants'; +import DefaultPlaces from './global_search_default_places.vue'; +import DefaultIssuables from './global_search_default_issuables.vue'; + +const components = [DefaultPlaces, DefaultIssuables]; export default { name: 'GlobalSearchDefaultItems', - i18n: { - ALL_GITLAB, - PLACES, - }, - components: { - GlDisclosureDropdownGroup, + data() { + return { + // The components here are expected to: + // - be responsible for getting their own data, + // - render a GlDisclosureDropdownGroup as the root vnode, + // - transparently pass all attrs to it (e.g., `bordered`), + // - not render anything if they have no data, + // - emit a `nothing-to-render` event if they have nothing to render. + // - have a unique `name` + componentNames: components.map(({ name }) => name), + }; }, - inject: ['contextSwitcherLinks'], - computed: { - ...mapState(['searchContext']), - ...mapGetters(['defaultSearchOptions']), - currentContextName() { - return ( - this.searchContext?.project?.name || - this.searchContext?.group?.name || - this.$options.i18n.ALL_GITLAB - ); + methods: { + componentFromName(name) { + return components.find((component) => component.name === name); }, - groups() { - const groups = [ - { - name: this.$options.i18n.PLACES, - items: this.contextSwitcherLinks.map(({ title, link }) => ({ text: title, href: link })), - }, - { - name: this.currentContextName, - items: this.defaultSearchOptions, - }, - ]; - - return groups.filter(({ items }) => items.length > 0); + remove(nameToRemove) { + const indexToRemove = this.componentNames.findIndex((name) => name === nameToRemove); + if (indexToRemove !== -1) this.componentNames.splice(indexToRemove, 1); + }, + attrs(index) { + return index === 0 + ? null + : { + bordered: true, + class: 'gl-mt-3', + }; }, }, }; </script> <template> - <ul class="gl-p-0 gl-m-0 gl-list-style-none"> - <gl-disclosure-dropdown-group - v-for="(group, index) of groups" - :key="group.name" - :group="group" - bordered - :class="{ 'gl-mt-0!': index === 0 }" + <ul class="gl-p-0 gl-m-0 gl-pt-2 gl-list-style-none"> + <component + :is="componentFromName(name)" + v-for="(name, index) in componentNames" + :key="name" + v-bind="attrs(index)" + @nothing-to-render="remove(name)" /> </ul> </template> diff --git a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_places.vue b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_places.vue new file mode 100644 index 00000000000..9a375837102 --- /dev/null +++ b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search_default_places.vue @@ -0,0 +1,35 @@ +<script> +import { GlDisclosureDropdownGroup } from '@gitlab/ui'; +import { PLACES } from '~/vue_shared/global_search/constants'; + +export default { + name: 'DefaultPlaces', + i18n: { + PLACES, + }, + components: { + GlDisclosureDropdownGroup, + }, + inject: ['contextSwitcherLinks'], + computed: { + shouldRender() { + return this.contextSwitcherLinks.length > 0; + }, + group() { + return { + name: this.$options.i18n.PLACES, + items: this.contextSwitcherLinks.map(({ title, link }) => ({ text: title, href: link })), + }; + }, + }, + created() { + if (!this.shouldRender) { + this.$emit('nothing-to-render'); + } + }, +}; +</script> + +<template> + <gl-disclosure-dropdown-group v-if="shouldRender" v-bind="$attrs" :group="group" /> +</template> diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 8c0a0c766b2..71936f37333 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -493,31 +493,6 @@ } } -.protected-branches-list { - margin-bottom: 32px; - - .settings-message { - margin: 0; - border-radius: 0 0 1px 1px; - padding: 20px 0; - border: 0; - } - - .table-bordered { - border-radius: 1px; - - th:not(:last-child), - td:not(:last-child) { - border-right: solid 1px transparent; - } - } - - .flash-container { - padding: 0; - } -} - - .compare-revision-cards { @media (max-width: $breakpoint-lg) { .swap-button { diff --git a/app/models/customer_relations/contact.rb b/app/models/customer_relations/contact.rb index 16c741d340f..f99b8fa5549 100644 --- a/app/models/customer_relations/contact.rb +++ b/app/models/customer_relations/contact.rb @@ -35,6 +35,22 @@ class CustomerRelations::Contact < ApplicationRecord scope :order_by_organization_asc, -> { includes(:organization).order("customer_relations_organizations.name ASC NULLS LAST") } scope :order_by_organization_desc, -> { includes(:organization).order("customer_relations_organizations.name DESC NULLS LAST") } + SAFE_ATTRIBUTES = %w[ + created_at + description + first_name + group_id + id + last_name + organization_id + state + updated_at + ].freeze + + def hook_attrs + attributes.slice(*SAFE_ATTRIBUTES) + end + def self.reference_prefix '[contact:' end diff --git a/app/models/issue.rb b/app/models/issue.rb index 2b280f62ee6..5df1ce89350 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -770,7 +770,7 @@ class Issue < ApplicationRecord return unless persisted? if confidential? && WorkItems::ParentLink.has_public_children?(id) - errors.add(:base, _('A confidential issue cannot have a parent that already has non-confidential children.')) + errors.add(:base, _('A confidential issue must have only confidential children. Make any child items confidential and try again.')) end if !confidential? && WorkItems::ParentLink.has_confidential_parent?(id) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 995fabf0137..469dba42952 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1166,7 +1166,7 @@ class MergeRequest < ApplicationRecord MergeRequests::ReloadDiffsService.new(self, current_user).execute end - def check_mergeability(async: false) + def check_mergeability(async: false, sync_retry_lease: false) return unless recheck_merge_status? check_service = MergeRequests::MergeabilityCheckService.new(self) @@ -1174,7 +1174,7 @@ class MergeRequest < ApplicationRecord if async check_service.async_execute else - check_service.execute(retry_lease: false) + check_service.execute(retry_lease: sync_retry_lease) end end # rubocop: enable CodeReuse/ServiceClass @@ -1218,14 +1218,14 @@ class MergeRequest < ApplicationRecord } end - def mergeable?(skip_ci_check: false, skip_discussions_check: false, skip_approved_check: false) + def mergeable?(skip_ci_check: false, skip_discussions_check: false, skip_approved_check: false, check_mergeability_retry_lease: false) return false unless mergeable_state?( skip_ci_check: skip_ci_check, skip_discussions_check: skip_discussions_check, skip_approved_check: skip_approved_check ) - check_mergeability + check_mergeability(sync_retry_lease: check_mergeability_retry_lease) can_be_merged? && !should_be_rebased? end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 5e41375e7a0..844ea56c71b 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -67,7 +67,7 @@ module MergeRequests error = if @merge_request.should_be_rebased? 'Only fast-forward merge is allowed for your project. Please update your source branch' - elsif !@merge_request.mergeable?(skip_discussions_check: @options[:skip_discussions_check]) + elsif !@merge_request.mergeable?(skip_discussions_check: @options[:skip_discussions_check], check_mergeability_retry_lease: @options[:check_mergeability_retry_lease]) 'Merge request is not mergeable' elsif !@merge_request.squash && project.squash_always? 'This project requires squashing commits when merge requests are accepted.' diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml index fac0fd3d2a4..ca7798257cb 100644 --- a/app/views/groups/_home_panel.html.haml +++ b/app/views/groups/_home_panel.html.haml @@ -8,7 +8,7 @@ .avatar-container.rect-avatar.s64.home-panel-avatar.gl-flex-shrink-0.float-none{ class: 'gl-mr-3!' } = group_icon(@group, class: 'avatar avatar-tile s64', width: 64, height: 64, itemprop: 'logo') %div - %h1.home-panel-title.gl-font-size-h1.gl-mt-3.gl-mb-2.gl-display-flex{ itemprop: 'name' } + %h1.home-panel-title.gl-font-size-h1.gl-mt-3.gl-mb-2.gl-display-flex.gl-word-break-word{ itemprop: 'name' } = @group.name %span.visibility-icon.gl-text-secondary.has-tooltip.gl-ml-2{ data: { container: 'body' }, title: visibility_icon_description(@group) } = visibility_level_icon(@group.visibility_level, options: {class: 'icon'}) diff --git a/app/views/help/instance_configuration/_size_limits.html.haml b/app/views/help/instance_configuration/_size_limits.html.haml index 4f085ca5db6..add484feac9 100644 --- a/app/views/help/instance_configuration/_size_limits.html.haml +++ b/app/views/help/instance_configuration/_size_limits.html.haml @@ -46,4 +46,4 @@ %td= instance_configuration_human_size_cell(size_limits[:max_import_remote_file_size]) %tr %td= s_('BulkImport|Direct transfer maximum download file size (MB)') - %td= instance_configuration_human_size_cell(size_limits[:max_import_remote_file_size]) + %td= instance_configuration_human_size_cell(size_limits[:bulk_import_max_download_file_size]) diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index e98bdde1c34..4ac30547ce3 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -8,7 +8,7 @@ %div{ class: 'avatar-container rect-avatar s64 home-panel-avatar gl-flex-shrink-0 gl-w-11 gl-h-11 gl-mr-3! float-none' } = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64, itemprop: 'image') %div - %h1.home-panel-title.gl-font-size-h1.gl-mt-3.gl-mb-2.gl-display-flex{ data: { qa_selector: 'project_name_content' }, itemprop: 'name' } + %h1.home-panel-title.gl-font-size-h1.gl-mt-3.gl-mb-2.gl-display-flex.gl-word-break-word{ data: { qa_selector: 'project_name_content' }, itemprop: 'name' } = @project.name = visibility_level_content(@project, css_class: 'visibility-icon gl-text-secondary gl-ml-2', icon_css_class: 'icon') = render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: @project, additional_classes: 'gl-align-self-center gl-ml-2' diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index a9a2ca6a494..5031913a9a2 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -24,7 +24,7 @@ %span.js-mirrored-repo-count = mirrored_repositories_count .gl-new-card-actions - = render Pajamas::ButtonComponent.new(size: :small, button_options: { class: "js-toggle-button js-toggle-content" }) do + = render Pajamas::ButtonComponent.new(size: :small, button_options: { class: "js-toggle-button js-toggle-content", data: { testid: 'add-new-mirror' } }) do = _('Add new') - c.with_body do - if mirror_settings_enabled diff --git a/app/views/protected_branches/shared/_branches_list.html.haml b/app/views/protected_branches/shared/_branches_list.html.haml index ed2d420ffcd..1edec308283 100644 --- a/app/views/protected_branches/shared/_branches_list.html.haml +++ b/app/views/protected_branches/shared/_branches_list.html.haml @@ -1,12 +1,10 @@ .protected-branches-list.js-protected-branches-list{ data: { testid: 'protected-branches-list' } } - if @protected_branches.empty? - .card-header.bg-white - = s_("ProtectedBranch|Protected branch (%{protected_branches_count})") % { protected_branches_count: 0 } - %p.settings-message.text-center - = s_("ProtectedBranch|There are currently no protected branches, protect a branch with the form above.") + %p.gl-new-card-empty.gl-px-5.gl-py-4.js-toggle-content + = s_("ProtectedBranch|There are currently no protected branches, to protect a branch start by creating a new one above.") - else .flash-container - %table.table.table-bordered + %table.table.b-table.gl-table.b-table-stacked-md %colgroup %col{ width: "30%" } %col{ width: "20%" } @@ -34,5 +32,3 @@ %th %tbody = yield - - = paginate @protected_branches, theme: 'gitlab' diff --git a/app/views/protected_branches/shared/_create_protected_branch.html.haml b/app/views/protected_branches/shared/_create_protected_branch.html.haml index d97347b89de..96e6990b080 100644 --- a/app/views/protected_branches/shared/_create_protected_branch.html.haml +++ b/app/views/protected_branches/shared/_create_protected_branch.html.haml @@ -1,44 +1,43 @@ = gitlab_ui_form_for [protected_branch_entity, @protected_branch], html: { class: 'new-protected-branch js-new-protected-branch' } do |f| %input{ type: 'hidden', name: 'update_section', value: 'js-protected-branches-settings' } - = render Pajamas::CardComponent.new(card_options: { class: "gl-mb-5" }) do |c| - - c.with_header do - = s_("ProtectedBranch|Protect a branch") - - c.with_body do - = form_errors(@protected_branch) - .form-group.row - = f.label :name, s_('ProtectedBranch|Branch:'), class: 'col-sm-12' - .col-sm-12 - - if protected_branch_entity.is_a?(Group) - = f.text_field :name, placeholder: 'prod*', class: 'form-control gl-w-full! gl-form-input-lg' - - else - = render partial: "protected_branches/shared/dropdown", locals: { f: f, toggle_classes: 'gl-w-full! gl-form-input-lg' } - .form-text.text-muted - - wildcards_url = help_page_url('user/project/protected_branches', anchor: 'protect-multiple-branches-with-wildcard-rules') - - wildcards_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: wildcards_url } - - placeholders = { wildcards_link_start: wildcards_link_start, wildcards_link_end: '</a>', code_tag_start: '<code>', code_tag_end: '</code>' } - - if protected_branch_entity.is_a?(Group) - = (s_("ProtectedBranch|Only %{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported.") % placeholders).html_safe - - else - = (s_("ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported.") % placeholders).html_safe - .form-group.row - = f.label :merge_access_levels_attributes, s_("ProtectedBranch|Allowed to merge:"), class: 'col-sm-12' - .col-sm-12 - = yield :merge_access_levels - .form-group.row - = f.label :push_access_levels_attributes, s_("ProtectedBranch|Allowed to push and merge:"), class: 'col-sm-12' - .col-sm-12 - = yield :push_access_levels - .form-group.row - = f.label :allow_force_push, s_("ProtectedBranch|Allowed to force push:"), class: 'col-sm-12' - .col-sm-12 - = render Pajamas::ToggleComponent.new(classes: 'js-force-push-toggle', - label: s_("ProtectedBranch|Allowed to force push"), - label_position: :hidden) do - - force_push_docs_url = help_page_url('topics/git/git_rebase', anchor: 'force-push') - - force_push_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: force_push_docs_url } - = (s_("ProtectedBranch|Allow all users with push access to %{tag_start}force push%{tag_end}.") % { tag_start: force_push_link_start, tag_end: '</a>' }).html_safe - = render_if_exists 'protected_branches/ee/code_owner_approval_form', f: f, protected_branch_entity: protected_branch_entity - - c.with_footer do - = f.submit s_('ProtectedBranch|Protect'), disabled: true, data: { qa_selector: 'protect_button' }, pajamas_button: true - .js-alert-protected-branch-created-container.gl-mb-5 + = form_errors(@protected_branch) + + %h4.gl-mt-0= s_("ProtectedBranch|Protect a branch") + + .form-group.row + = f.label :name, s_('ProtectedBranch|Branch:'), class: 'col-sm-12' + .col-sm-12 + - if protected_branch_entity.is_a?(Group) + = f.text_field :name, placeholder: 'prod*', class: 'form-control gl-w-full! gl-form-input-lg' + - else + = render partial: "protected_branches/shared/dropdown", locals: { f: f, toggle_classes: 'gl-w-full! gl-form-input-lg' } + .form-text.text-muted + - wildcards_url = help_page_url('user/project/protected_branches', anchor: 'protect-multiple-branches-with-wildcard-rules') + - wildcards_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: wildcards_url } + - placeholders = { wildcards_link_start: wildcards_link_start, wildcards_link_end: '</a>', code_tag_start: '<code>', code_tag_end: '</code>' } + - if protected_branch_entity.is_a?(Group) + = (s_("ProtectedBranch|Only %{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported.") % placeholders).html_safe + - else + = (s_("ProtectedBranch|%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}*-stable%{code_tag_end} or %{code_tag_start}production/*%{code_tag_end} are supported.") % placeholders).html_safe + .form-group.row + = f.label :merge_access_levels_attributes, s_("ProtectedBranch|Allowed to merge:"), class: 'col-sm-12' + .col-sm-12 + = yield :merge_access_levels + .form-group.row + = f.label :push_access_levels_attributes, s_("ProtectedBranch|Allowed to push and merge:"), class: 'col-sm-12' + .col-sm-12 + = yield :push_access_levels + .form-group.row + = f.label :allow_force_push, s_("ProtectedBranch|Allowed to force push:"), class: 'col-sm-12' + .col-sm-12 + = render Pajamas::ToggleComponent.new(classes: 'js-force-push-toggle', + label: s_("ProtectedBranch|Allowed to force push"), + label_position: :hidden) do + - force_push_docs_url = help_page_url('topics/git/git_rebase', anchor: 'force-push') + - force_push_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: force_push_docs_url } + = (s_("ProtectedBranch|Allow all users with push access to %{tag_start}force push%{tag_end}.") % { tag_start: force_push_link_start, tag_end: '</a>' }).html_safe + = render_if_exists 'protected_branches/ee/code_owner_approval_form', f: f, protected_branch_entity: protected_branch_entity + = f.submit s_('ProtectedBranch|Protect'), disabled: true, data: { qa_selector: 'protect_button' }, pajamas_button: true + = render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do + = _('Cancel') diff --git a/app/views/protected_branches/shared/_index.html.haml b/app/views/protected_branches/shared/_index.html.haml index d0e21e38429..03b842189a8 100644 --- a/app/views/protected_branches/shared/_index.html.haml +++ b/app/views/protected_branches/shared/_index.html.haml @@ -7,15 +7,31 @@ = s_("ProtectedBranch|Protected branches") = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do = expanded ? _('Collapse') : _('Expand') - %p + %p.gl-text-secondary.gl-mb-0 = s_("ProtectedBranch|Keep stable branches secure and force developers to use merge requests.") = link_to s_("ProtectedBranch|What are protected branches?"), help_page_path("user/project/protected_branches") .settings-content - %p - = s_("ProtectedBranch|By default, protected branches restrict who can modify the branch.") - = link_to s_("ProtectedBranch|Learn more."), help_page_path("user/project/protected_branches", anchor: "who-can-modify-a-protected-branch") + .js-alert-protected-branch-created-container.gl-mt-5 - - if can_admin_entity - = content_for :create_protected_branch + = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card js-toggle-container' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-0' }) do |c| + - c.with_header do + .gl-new-card-title-wrapper.gl-justify-content-space-between + %h3.gl-new-card-title + = s_("ProtectedBranch|Protected branches") + .gl-new-card-count + = sprite_icon('branch', css_class: 'gl-mr-2') + %span= @protected_branches.size + .gl-new-card-actions + = render Pajamas::ButtonComponent.new(size: :small, button_options: { class: 'js-toggle-button js-toggle-content', data: { testid: 'add-new-token-button' } }) do + = _('Add protected branch') + .gl-new-card-description.gl-mt-2.gl-sm-mt-0 + = s_("ProtectedBranch|By default, protected branches restrict who can modify the branch.") + = link_to s_("ProtectedBranch|Learn more."), help_page_path("user/project/protected_branches", anchor: "who-can-modify-a-protected-branch") + - c.with_body do + - if can_admin_entity + .gl-new-card-add-form.gl-m-3.gl-display-none.js-toggle-content + = content_for :create_protected_branch - = content_for :branches_list + = content_for :branches_list + + = paginate @protected_branches, theme: 'gitlab' diff --git a/app/views/protected_branches/shared/_protected_branch.html.haml b/app/views/protected_branches/shared/_protected_branch.html.haml index 69969b7f848..93c84e67d81 100644 --- a/app/views/protected_branches/shared/_protected_branch.html.haml +++ b/app/views/protected_branches/shared/_protected_branch.html.haml @@ -3,14 +3,14 @@ - protected_branch_test_type = protected_branch.project_level? ? 'project-level' : 'group-level' %tr.js-protected-branch-edit-form{ data: { url: url, testid: 'protected-branch', test_type: protected_branch_test_type } } - %td - %span.ref-name= protected_branch.name + %td{ class: 'gl-vertical-align-middle!', data: { label: s_("ProtectedBranch|Branch") } } + %div + %span.ref-name= protected_branch.name - - if protected_branch.project_level? - - if protected_branch_entity.root_ref?(protected_branch.name) - = gl_badge_tag s_('ProtectedBranch|default'), variant: :info + - if protected_branch.project_level? + - if protected_branch_entity.root_ref?(protected_branch.name) + = gl_badge_tag s_('ProtectedBranch|default'), variant: :info - %div - if protected_branch.wildcard? - matching_branches = protected_branch.matching(repository.branch_names) = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) @@ -22,9 +22,9 @@ = render_if_exists 'protected_branches/ee/code_owner_approval_table', can_update: local_assigns[:can_update], protected_branch: protected_branch, protected_branch_entity: protected_branch_entity - if can_admin_entity - %td.text-right{ data: { testid: 'protected-branch-action' } } + %td.text-right{ data: { label: _('Actions'), testid: 'protected-branch-action' } } - if local_assigns[:is_inherited] %span.has-tooltip{ data: { container: 'body' }, title: s_('ProtectedBranch|Inherited - This setting can be changed at the group level'), 'aria-hidden': 'true' } = sprite_icon 'lock' - else - = link_button_to s_('ProtectedBranch|Unprotect'), [protected_branch_entity, protected_branch, { update_section: 'js-protected-branches-settings' }], disabled: local_assigns[:disabled], aria: { label: s_('ProtectedBranch|Unprotect branch') }, data: { confirm: s_('ProtectedBranch|Branch will be writable for developers. Are you sure?'), confirm_btn_variant: 'danger' }, method: :delete, variant: :danger, size: :small + = link_button_to s_('ProtectedBranch|Unprotect'), [protected_branch_entity, protected_branch, { update_section: 'js-protected-branches-settings' }], disabled: local_assigns[:disabled], aria: { label: s_('ProtectedBranch|Unprotect branch') }, data: { confirm: s_('ProtectedBranch|Branch will be writable for developers. Are you sure?'), confirm_btn_variant: 'danger' }, method: :delete, variant: :danger, category: :secondary, size: :small diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md index 8d345bc62f1..72240be0b35 100644 --- a/doc/administration/monitoring/performance/grafana_configuration.md +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -4,34 +4,18 @@ group: Respond 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 --- -# Grafana Configuration **(FREE SELF)** +# Configure Grafana **(FREE SELF)** > - Grafana bundled with GitLab was [deprecated](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/7772) in GitLab 16.0. > - Grafana bundled with GitLab was [removed](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/7772) in GitLab 16.3. -WARNING: -Grafana bundled with GitLab was deprecated GitLab 16.0 and removed in GitLab 16.3. -For more information, see [deprecation notes](#deprecation-of-bundled-grafana). - [Grafana](https://grafana.com/) is a tool that enables you to visualize time series metrics through graphs and dashboards. GitLab writes performance data to Prometheus, and Grafana allows you to query the data to display graphs. -## Deprecation of bundled Grafana - -Bundled Grafana was an optional service for Linux package installations that provided a user interface to GitLab metrics. - -The version of Grafana that is bundled with Linux package installations is no longer supported. If you're using the -bundled Grafana, you should switch to a newer version from [Grafana Labs](https://grafana.com/grafana/). - -## Switch to new Grafana instance - -To switch away from bundled Grafana to a newer version of Grafana from Grafana Labs: - -1. Set up a version of Grafana from Grafana Labs. -1. [Export the existing dashboards](https://grafana.com/docs/grafana/latest/dashboards/manage-dashboards/#export-a-dashboard) from bundled Grafana. -1. [Import the existing dashboards](https://grafana.com/docs/grafana/latest/dashboards/manage-dashboards/#import-a-dashboard) in the new Grafana instance. -1. [Configure GitLab](#integrate-with-gitlab-ui) to use the new Grafana instance. +WARNING: +Grafana bundled with GitLab was deprecated GitLab 16.0 and removed in GitLab 16.3. +For more information, see [deprecation notes](#deprecation-of-bundled-grafana). ## Import GitLab dashboards @@ -80,3 +64,19 @@ configuration screen: - No scopes appear. - The `read_user` scope is included. + +## Deprecation of bundled Grafana + +Bundled Grafana was an optional service for Linux package installations that provided a user interface to GitLab metrics. + +The version of Grafana that is bundled with Linux package installations is no longer supported. If you're using the +bundled Grafana, you should switch to a newer version from [Grafana Labs](https://grafana.com/grafana/). + +### Switch to new Grafana instance + +To switch away from bundled Grafana to a newer version of Grafana from Grafana Labs: + +1. Set up a version of Grafana from Grafana Labs. +1. [Export the existing dashboards](https://grafana.com/docs/grafana/latest/dashboards/manage-dashboards/#export-a-dashboard) from bundled Grafana. +1. [Import the existing dashboards](https://grafana.com/docs/grafana/latest/dashboards/manage-dashboards/#import-a-dashboard) in the new Grafana instance. +1. [Configure GitLab](#integrate-with-gitlab-ui) to use the new Grafana instance. diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index 066da445eeb..06fac0b0323 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -7,18 +7,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w # GitLab quick actions **(FREE)** -> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/26672) in GitLab 12.1: -> once an action is executed, an alert appears when a quick action is successfully applied. -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16877) in GitLab 13.2: you can use -> quick actions when updating the description of issues, epics, and merge requests. -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292393) in GitLab 13.8: when you enter -> `/` into a description or comment field, all available quick actions are displayed in a scrollable list. -> - The rebase quick action was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49800) in GitLab 13.8. - Quick actions are text-based shortcuts for common actions that are usually done by selecting buttons or dropdowns in the GitLab user interface. You can enter these commands in the descriptions or comments of issues, epics, merge requests, -and commits. +and commits. Quick actions are executed from both new comments and description, and when you edit +existing ones. Many quick actions are context-aware, requiring certain conditions be met. For example, to remove an issue due date with `/remove_due_date`, the issue must have a due date set. @@ -49,7 +42,7 @@ The following quick actions are applicable to descriptions, discussions, and threads. Some quick actions might not be available to all subscription tiers. <!-- Keep this table sorted alphabetically --> -<!-- Don't prettify this table; it will expand out the last field into illegibility --> +<!-- Don't auto-prettify this table; it will expand out the last field into illegibility --> | Command | Issue | Merge request | Epic | Action | |:-------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------| @@ -60,11 +53,13 @@ threads. Some quick actions might not be available to all subscription tiers. | `/assign_reviewer @user1 @user2` or `/reviewer @user1 @user2` or `/request_review @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign one or more users as reviewers. | `/assign_reviewer me` or `/reviewer me` or `/request_review me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Assign yourself as a reviewer. | `/award :emoji:` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Toggle an emoji reaction. +| `/blocked_by <issue1> <issue2>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Mark the issue as blocked by other issues. The `<issue>` value should be in the format of `#issue`, `group/project#issue`, or the full issue URL. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214232) in GitLab 16.0). +| `/blocks <issue1> <issue2>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Mark the issue as blocking other issues. The `<issue>` value should be in the format of `#issue`, `group/project#issue`, or the full issue URL. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214232) in GitLab 16.0). | `/cc @user` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mention a user. In GitLab 15.0 and later, this command performs no action. You can instead type `CC @user` or only `@user`. [In GitLab 14.9 and earlier](https://gitlab.com/gitlab-org/gitlab/-/issues/31200), mentioning a user at the start of a line created a specific type of to-do item notification. | | `/child_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Add child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. | `/clear_health_status` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear [health status](issues/managing_issues.md#health-status) ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213814) in GitLab 14.7). | `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight. -| `/clone <path/to/project> [--with_notes]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clone the issue to given project, or the current one if no arguments are given ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9421) in GitLab 13.7). Copies as much data as possible as long as the target project contains equivalent labels, milestones, and so on. Does not copy comments or system notes unless `--with_notes` is provided as an argument. +| `/clone <path/to/project> [--with_notes]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clone the issue to given project, or the current one if no arguments are given. Copies as much data as possible as long as the target project contains equivalent objects like labels, milestones, or epics. Does not copy comments or system notes unless `--with_notes` is provided as an argument. | `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close. | `/confidential` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes | Mark issue or epic as confidential. Support for epics [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213741) in GitLab 15.6. | | `/copy_metadata <!merge_request>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another merge request in the project. @@ -78,18 +73,18 @@ threads. Some quick actions might not be available to all subscription tiers. | `/estimate <time>` or `/estimate_time <time>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set time estimate. For example, `/estimate 1mo 2w 3d 4h 5m`. For more information, see [Time tracking](time_tracking.md). Alias `/estimate_time` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6. | `/health_status <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set [health status](issues/managing_issues.md#health-status). Valid options for `<value>` are `on_track`, `needs_attention`, and `at_risk` ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213814) in GitLab 14.7). | `/invite_email email1 email2` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add up to six email participants. This action is behind feature flag `issue_email_participants` and is not yet supported in issue templates. -| `/iteration *iteration:"iteration name"` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196795) in GitLab 13.1). +| `/iteration *iteration:"iteration name"` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"`. | `/label ~label1 ~label2` or `/labels ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. -| `/lock` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Lock the discussions. | `/link` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add a link and description to [linked resources](../../operations/incident_management/linked_resources.md) in an incident ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/374964) in GitLab 15.5). | +| `/lock` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Lock the discussions. | `/merge` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Merge changes. Depending on the project setting, this may be [when the pipeline succeeds](merge_requests/merge_when_pipeline_succeeds.md), or adding to a [Merge Train](../../ci/pipelines/merge_trains.md). | `/milestone %milestone` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Set milestone. | `/move <path/to/project>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Move this issue to another project. Be careful when moving an issue to a project with different access rules. Before moving the issue, make sure it does not contain sensitive data. +| `/page <policy name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Start escalations for the incident ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79977) in GitLab 14.9). | `/parent_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Set parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. -| `/promote` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to epic. | `/promote_to_incident` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to incident ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/296787) in GitLab 14.5). In [GitLab 15.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/376760), you can also use the quick action when creating a new issue. -| `/page <policy name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Start escalations for the incident ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79977) in GitLab 14.9). -| `/publish` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906) in GitLab 13.0) +| `/promote` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to epic. +| `/publish` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md). | `/ready` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set the [ready status](merge_requests/drafts.md#mark-merge-requests-as-ready) ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90361) in GitLab 15.1). | `/reassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Replace current assignees with those specified. | `/reassign_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Replace current reviewers with those specified. @@ -101,37 +96,35 @@ threads. Some quick actions might not be available to all subscription tiers. | `/remove_due_date` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove due date. | `/remove_epic` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove from epic. | `/remove_estimate` or `/remove_time_estimate` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove time estimate. Alias `/remove_time_estimate` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6. -| `/remove_iteration` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove iteration ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/196795) in GitLab 13.1). +| `/remove_iteration` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove iteration. | `/remove_milestone` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove milestone. | `/remove_parent_epic` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Remove parent epic from epic. | `/remove_time_spent` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove time spent. | `/remove_zoom` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove Zoom meeting from this issue. | `/reopen` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Reopen. -| `/severity <severity>` | **{check-circle}** Yes | **{check-circle}** No | **{check-circle}** No | Set the severity. Issue type must be `Incident`. Options for `<severity>` are `S1` ... `S4`, `critical`, `high`, `medium`, `low`, `unknown`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334045) in GitLab 14.2. +| `/severity <severity>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set the severity. Issue type must be `Incident`. Options for `<severity>` are `S1` ... `S4`, `critical`, `high`, `medium`, `low`, `unknown`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334045) in GitLab 14.2. | `/shrug <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `¯\_(ツ)_/¯`. | `/spend <time> [<date>]` or `/spend_time <time> [<date>]` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Add or subtract spent time. Optionally, specify the date that time was spent on. For example, `/spend 1mo 2w 3d 4h 5m 2018-08-26` or `/spend -1h 30m`. For more information, see [Time tracking](time_tracking.md). Alias `/spend_time` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16501) in GitLab 15.6. | `/submit_review` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Submit a pending review. | `/subscribe` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Subscribe to notifications. | `/tableflip <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `(╯°□°)╯︵ ┻━┻`. | `/target_branch <local branch name>` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set target branch. -| `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title. | `/timeline <timeline comment> \| <date(YYYY-MM-DD)> <time(HH:MM)>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add a timeline event to this incident. For example, `/timeline DB load spiked \| 2022-09-07 09:30`. ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368721) in GitLab 15.4). | +| `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title. | `/todo` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add a to-do item. -| `/unapprove` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Unapprove the merge request. ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8103) in GitLab 14.3. +| `/unapprove` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Unapprove the merge request. ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8103) in GitLab 14.3). | `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific assignees. -| `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove all assignees. | `/unassign_reviewer @user1 @user2` or `/remove_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove specific reviewers. | `/unassign_reviewer me` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove yourself as a reviewer. | `/unassign_reviewer` or `/remove_reviewer` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove all reviewers. +| `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Remove all assignees. | `/unlabel ~label1 ~label2` or `/remove_label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove specified labels. | `/unlabel` or `/remove_label` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove all labels. +| `/unlink <issue>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove link with to the provided issue. The `<issue>` value should be in the format of `#issue`, `group/project#issue`, or the full issue URL. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/414400) in GitLab 16.1). | `/unlock` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Unlock the discussions. | `/unsubscribe` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Unsubscribe from notifications. -| `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid options for `<value>` include `0`, `1`, `2`, and so on. +| `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid values are integers like `0`, `1`, or `2`. | `/zoom <Zoom URL>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add a Zoom meeting to this issue or incident. In [GitLab 15.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/230853) users on GitLab Premium can add a short description when [adding a Zoom link to an incident](../../operations/incident_management/linked_resources.md#link-zoom-meetings-from-an-incident). -| `/blocks <issue1> <issue2>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Mark the issue as blocking other issues. The `<issue>` value should be in the format of `#issue`, `group/project#issue`, or the full issue URL. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214232) in GitLab 16.0). -| `/blocked_by <issue1> <issue2>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Mark the issue as blocked by other issues. The `<issue>` value should be in the format of `#issue`, `group/project#issue`, or the full issue URL. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214232) in GitLab 16.0). -| `/unlink <issue>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Remove link with to the provided issue. The `<issue>` value should be in the format of `#issue`, `group/project#issue`, or the full issue URL. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/414400) in GitLab 16.1). ## Work items @@ -140,33 +133,36 @@ threads. Some quick actions might not be available to all subscription tiers. Work items in GitLab include [tasks](../tasks.md) and [OKRs](../okrs.md). The following quick actions can be applied through the description field when editing or commenting on work items. -| Command | Task | Objective | Key Result | Action -|:-------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------| -| `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title. -| `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close. -| `/reopen` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Reopen. -| `/shrug <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `¯\_(ツ)_/¯`. -| `/tableflip <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `(╯°□°)╯︵ ┻━┻`. -| `/cc @user` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mention a user. In GitLab 15.0 and later, this command performs no action. You can instead type `CC @user` or only `@user`. [In GitLab 14.9 and earlier](https://gitlab.com/gitlab-org/gitlab/-/issues/31200), mentioning a user at the start of a line creates a specific type of to-do item notification. -| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Assign one or more users. -| `/assign me` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Assign yourself. -| `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Remove specific assignees. -| `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** Yes | Remove all assignees. -| `/reassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** Yes | Replace current assignees with those specified. -| `/label ~label1 ~label2` or `/labels ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. -| `/relabel ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Replace current labels with those specified. -| `/unlabel ~label1 ~label2` or `/remove_label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove specified labels. -| `/unlabel` or `/remove_label` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove all labels. -| `/due <date>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** Yes | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. -| `/remove_due_date` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** Yes | Remove due date. -| `/health_status <value>` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Set [health status](issues/managing_issues.md#health-status). Valid options for `<value>` are `on_track`, `needs_attention`, and `at_risk`. -| `/clear_health_status` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Clear [health status](issues/managing_issues.md#health-status). -| `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid options for `<value>` include `0`, `1`, and `2`. -| `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight. -| `/type` | **{check-circle}** Yes | **{dotted-circle}** Yes | **{dotted-circle}** Yes | Converts work item to specified type. Available options for `<type>` include `issue`, `task`, `objective` and `key result`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385227) in GitLab 16.0. -| `/promote_to <type>` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes | Promotes work item to specified type. Available options for `<type>`: `issue` (promote a task) and `objective` (promote a key result). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/412534) in GitLab 16.1. -| `/todo` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add a to-do item. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/412277) in GitLab 16.2. -| `/done` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mark to-do item as done. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/412277) in GitLab 16.2. +<!-- Keep this table sorted alphabetically --> +<!-- Don't auto-prettify this table; it will expand out the last field into illegibility --> + +| Command | Task | Objective | Key Result | Action +|:--------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-------| +| `/assign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Assign one or more users. +| `/assign me` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Assign yourself. +| `/cc @user` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mention a user. In GitLab 15.0 and later, this command performs no action. You can instead type `CC @user` or only `@user`. [In GitLab 14.9 and earlier](https://gitlab.com/gitlab-org/gitlab/-/issues/31200), mentioning a user at the start of a line creates a specific type of to-do item notification. +| `/clear_health_status` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Clear [health status](issues/managing_issues.md#health-status). +| `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight. +| `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close. +| `/done` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mark to-do item as done. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/412277) in GitLab 16.2. +| `/due <date>` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. +| `/health_status <value>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Set [health status](issues/managing_issues.md#health-status). Valid options for `<value>` are `on_track`, `needs_attention`, or `at_risk`. +| `/label ~label1 ~label2` or `/labels ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. +| `/promote_to <type>` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes | Promotes work item to specified type. Available options for `<type>`: `issue` (promote a task) or `objective` (promote a key result). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/412534) in GitLab 16.1. +| `/reassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Replace current assignees with those specified. +| `/relabel ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Replace current labels with those specified. +| `/remove_due_date` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes | Remove due date. +| `/reopen` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Reopen. +| `/shrug <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `¯\_(ツ)_/¯`. +| `/tableflip <comment>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Append the comment with `(╯°□°)╯︵ ┻━┻`. +| `/title <new title>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Change title. +| `/todo` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Add a to-do item. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/412277) in GitLab 16.2. +| `/type` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Converts work item to specified type. Available options for `<type>` include `issue`, `task`, `objective` and `key result`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385227) in GitLab 16.0. +| `/unassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove specific assignees. +| `/unassign` | **{dotted-circle}** No | **{check-circle}** Yes | **{check-circle}** Yes | Remove all assignees. +| `/unlabel ~label1 ~label2` or `/remove_label ~label1 ~label2` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove specified labels. +| `/unlabel` or `/remove_label` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Remove all labels. +| `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid options for `<value>` include `0`, `1`, and `2`. ## Commit messages @@ -176,14 +172,15 @@ The following quick actions are applicable for commit messages: |:----------------------- |:------------------------------------------| | `/tag v1.2.3 <message>` | Tags the commit with an optional message. | -<!-- ## Troubleshooting +## Troubleshooting + +### Quick action isn't executed -Include any troubleshooting steps that you can foresee. If you know beforehand what issues -one might have when setting this up, or when something is changed, or on upgrading, it's -important to describe those, too. Think of things that may go wrong and include them here. -This is important to minimize requests for support, and to avoid doc comments with -questions that you know someone might ask. +If you run a quick action, but nothing happens, check if the quick action appears in the autocomplete +box as you type it. +If it doesn't, it's possible that: -Each scenario can be a third-level heading, for example `### Getting error message X`. -If you have none to add when creating a doc, leave this section in place -but commented out to help encourage others to add to it in the future. --> +- The feature related to the quick action isn't available to you based on your subscription tier or + user role in the group or project. +- A required condition for the quick action isn't met. + For example, you're running `/unlabel` on an issue without any labels. diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb index bd0603c5e5b..aacc3f8b821 100644 --- a/lib/gitlab/hook_data/issue_builder.rb +++ b/lib/gitlab/hook_data/issue_builder.rb @@ -11,6 +11,7 @@ module Gitlab time_change severity escalation_status + customer_relations_contacts ].freeze end @@ -56,7 +57,8 @@ module Gitlab assignee_id: issue.assignee_ids.first, # This key is deprecated labels: issue.labels_hook_attrs, state: issue.state, - severity: issue.severity + severity: issue.severity, + customer_relations_contacts: issue.customer_relations_contacts.map(&:hook_attrs) } if issue.supports_escalation? && issue.escalation_status diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 68f55630e60..55ac7a8e421 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1777,7 +1777,7 @@ msgstr "" msgid "A complete DevOps platform" msgstr "" -msgid "A confidential issue cannot have a parent that already has non-confidential children." +msgid "A confidential issue must have only confidential children. Make any child items confidential and try again." msgstr "" msgid "A confidential work item cannot have a parent that already has non-confidential children." @@ -2941,6 +2941,9 @@ msgstr "" msgid "Add projects" msgstr "" +msgid "Add protected branch" +msgstr "" + msgid "Add reaction" msgstr "" @@ -37944,9 +37947,6 @@ msgstr "" msgid "ProtectedBranch|Protect a branch" msgstr "" -msgid "ProtectedBranch|Protected branch (%{protected_branches_count})" -msgstr "" - msgid "ProtectedBranch|Protected branches" msgstr "" @@ -37965,7 +37965,7 @@ msgstr "" msgid "ProtectedBranch|Tag" msgstr "" -msgid "ProtectedBranch|There are currently no protected branches, protect a branch with the form above." +msgid "ProtectedBranch|There are currently no protected branches, to protect a branch start by creating a new one above." msgstr "" msgid "ProtectedBranch|Toggle allowed to force push" diff --git a/patches/@vue+test-utils-vue3+2.3.1.patch b/patches/@vue+test-utils-vue3+2.3.1.patch index c455db38089..c4d0324baf8 100644 --- a/patches/@vue+test-utils-vue3+2.3.1.patch +++ b/patches/@vue+test-utils-vue3+2.3.1.patch @@ -1,14 +1,15 @@ diff --git a/node_modules/@vue/test-utils-vue3/dist/vue-test-utils.cjs.js b/node_modules/@vue/test-utils-vue3/dist/vue-test-utils.cjs.js -index 2902a1e..4f5ab30 100644 +index 2902a1e..0c1e6b5 100644 --- a/node_modules/@vue/test-utils-vue3/dist/vue-test-utils.cjs.js +++ b/node_modules/@vue/test-utils-vue3/dist/vue-test-utils.cjs.js -@@ -7945,7 +7945,8 @@ function createStubComponentsTransformer(_a) { +@@ -7945,7 +7945,9 @@ function createStubComponentsTransformer(_a) { var stubName = name || registeredName || componentName; return ((_c = (_b = (_a = config.plugins).createStubs) === null || _b === void 0 ? void 0 : _b.call(_a, { name: stubName, - component: type + component: type, -+ registerStub ++ registerStub, ++ stubs })) !== null && _c !== void 0 ? _c : createStub({ name: stubName, type: type, diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb index c9ce80076e2..85b413ba880 100644 --- a/qa/qa/page/project/settings/mirroring_repositories.rb +++ b/qa/qa/page/project/settings/mirroring_repositories.rb @@ -13,6 +13,7 @@ module QA view 'app/views/projects/mirrors/_mirror_repos.html.haml' do element :mirror_repository_url_field element :mirror_repository_button + element 'add-new-mirror' end view 'app/views/projects/mirrors/_mirror_repos_list.html.haml' do @@ -37,6 +38,7 @@ module QA end def repository_url=(value) + click_element 'add-new-mirror' fill_element :mirror_repository_url_field, value end diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 9244cafbc0b..ee5d92b7cdb 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -40,6 +40,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem it 'allows to create a protected branch with name containing HTML tags' do visit project_protected_branches_path(project) + + show_add_form set_defaults set_protected_branch_name('foo<b>bar<\b>') click_on "Protect" @@ -89,6 +91,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem describe "explicit protected branches" do it "allows creating explicit protected branches" do visit project_protected_branches_path(project) + + show_add_form set_defaults set_protected_branch_name('some->branch') click_on "Protect" @@ -100,6 +104,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem it "shows success alert once protected branch is created" do visit project_protected_branches_path(project) + + show_add_form set_defaults set_protected_branch_name('some->branch') click_on "Protect" @@ -112,6 +118,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem project.repository.add_branch(admin, 'some-branch', commit.id) visit project_protected_branches_path(project) + + show_add_form set_defaults set_protected_branch_name('some-branch') click_on "Protect" @@ -124,6 +132,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem it "displays an error message if the named branch does not exist" do visit project_protected_branches_path(project) + + show_add_form set_defaults set_protected_branch_name('some-branch') click_on "Protect" @@ -135,6 +145,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem describe "wildcard protected branches" do it "allows creating protected branches with a wildcard" do visit project_protected_branches_path(project) + + show_add_form set_defaults set_protected_branch_name('*-stable') click_on "Protect" @@ -149,6 +161,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem project.repository.add_branch(admin, 'staging-stable', 'master') visit project_protected_branches_path(project) + + show_add_form set_defaults set_protected_branch_name('*-stable') click_on "Protect" @@ -164,6 +178,8 @@ RSpec.describe 'Protected Branches', :js, feature_category: :source_code_managem project.repository.add_branch(admin, 'development', 'master') visit project_protected_branches_path(project) + + show_add_form set_protected_branch_name('*-stable') set_defaults click_on "Protect" diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_issuables_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_issuables_spec.js new file mode 100644 index 00000000000..934cb1678df --- /dev/null +++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_issuables_spec.js @@ -0,0 +1,139 @@ +import { GlDisclosureDropdownGroup, GlDisclosureDropdownItem } from '@gitlab/ui'; +import Vue from 'vue'; +import Vuex from 'vuex'; +import { shallowMount } from '@vue/test-utils'; +import GlobalSearchDefaultIssuables from '~/super_sidebar/components/global_search/components/global_search_default_issuables.vue'; +import { + MOCK_SEARCH_CONTEXT, + MOCK_PROJECT_SEARCH_CONTEXT, + MOCK_GROUP_SEARCH_CONTEXT, + MOCK_DEFAULT_SEARCH_OPTIONS, +} from '../mock_data'; + +Vue.use(Vuex); + +describe('GlobalSearchDefaultPlaces', () => { + let wrapper; + + const createComponent = ({ + searchContext = null, + mockDefaultSearchOptions = [], + ...options + } = {}) => { + const store = new Vuex.Store({ + state: { + searchContext, + }, + getters: { + defaultSearchOptions: () => mockDefaultSearchOptions, + }, + }); + + wrapper = shallowMount(GlobalSearchDefaultIssuables, { + store, + stubs: { + GlDisclosureDropdownGroup, + }, + ...options, + }); + }; + + const findGroup = () => wrapper.findComponent(GlDisclosureDropdownGroup); + const findItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem); + + describe('given no contextSwitcherLinks', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders nothing', () => { + expect(wrapper.html()).toBe(''); + }); + + it('emits a nothing-to-render event', () => { + expect(wrapper.emitted('nothing-to-render')).toEqual([[]]); + }); + }); + + describe('given some contextSwitcherLinks', () => { + beforeEach(() => { + createComponent({ + searchContext: MOCK_SEARCH_CONTEXT, + mockDefaultSearchOptions: MOCK_DEFAULT_SEARCH_OPTIONS, + attrs: { + bordered: true, + class: 'test-class', + }, + }); + }); + + it('renders a disclosure dropdown group', () => { + expect(findGroup().exists()).toBe(true); + }); + + it('renders the expected header', () => { + expect(wrapper.text()).toContain('All GitLab'); + }); + + it('passes attrs down', () => { + const group = findGroup(); + expect(group.props('bordered')).toBe(true); + expect(group.classes()).toContain('test-class'); + }); + + it('renders the links', () => { + const itemProps = findItems().wrappers.map((item) => item.props('item')); + + expect(itemProps).toEqual([ + { + text: 'Issues assigned to me', + href: '/dashboard/issues/?assignee_username=anyone', + }, + { + text: "Issues I've created", + href: '/dashboard/issues/?author_username=anyone', + }, + { + text: 'Merge requests assigned to me', + href: '/dashboard/merge_requests/?assignee_username=anyone', + }, + { + text: "Merge requests that I'm a reviewer", + href: '/dashboard/merge_requests/?reviewer_username=anyone', + }, + { + text: "Merge requests I've created", + href: '/dashboard/merge_requests/?author_username=anyone', + }, + ]); + }); + }); + + describe('group name', () => { + describe('in a project context', () => { + beforeEach(() => { + createComponent({ + searchContext: MOCK_PROJECT_SEARCH_CONTEXT, + mockDefaultSearchOptions: MOCK_DEFAULT_SEARCH_OPTIONS, + }); + }); + + it('renders the expected header', () => { + expect(wrapper.text()).toContain('MockProject'); + }); + }); + + describe('in a group context', () => { + beforeEach(() => { + createComponent({ + searchContext: MOCK_GROUP_SEARCH_CONTEXT, + mockDefaultSearchOptions: MOCK_DEFAULT_SEARCH_OPTIONS, + }); + }); + + it('renders the expected header', () => { + expect(wrapper.text()).toContain('MockGroup'); + }); + }); + }); +}); diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js index 0fb6585e8ca..18a6e4aa35a 100644 --- a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js +++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js @@ -1,110 +1,59 @@ -import { GlDisclosureDropdownGroup, GlDisclosureDropdownItem } from '@gitlab/ui'; -import Vue from 'vue'; -import Vuex from 'vuex'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { shallowMount } from '@vue/test-utils'; import GlobalSearchDefaultItems from '~/super_sidebar/components/global_search/components/global_search_default_items.vue'; -import { MOCK_SEARCH_CONTEXT, MOCK_DEFAULT_SEARCH_OPTIONS } from '../mock_data'; -import { contextSwitcherLinks } from '../../../mock_data'; - -Vue.use(Vuex); +import GlobalSearchDefaultPlaces from '~/super_sidebar/components/global_search/components/global_search_default_places.vue'; +import GlobalSearchDefaultIssuables from '~/super_sidebar/components/global_search/components/global_search_default_issuables.vue'; describe('GlobalSearchDefaultItems', () => { let wrapper; - const createComponent = ({ - storeState, - mockDefaultSearchOptions = MOCK_DEFAULT_SEARCH_OPTIONS, - ...options - } = {}) => { - const store = new Vuex.Store({ - state: { - searchContext: MOCK_SEARCH_CONTEXT, - ...storeState, - }, - getters: { - defaultSearchOptions: () => mockDefaultSearchOptions, - }, - }); - - wrapper = shallowMountExtended(GlobalSearchDefaultItems, { - store, - provide: { - contextSwitcherLinks, - }, - stubs: { - GlDisclosureDropdownGroup, - }, - ...options, - }); + const createComponent = () => { + wrapper = shallowMount(GlobalSearchDefaultItems); }; - const findGroups = () => wrapper.findAllComponents(GlDisclosureDropdownGroup); - const findItems = (root = wrapper) => root.findAllComponents(GlDisclosureDropdownItem); - - describe('template', () => { - describe('Dropdown items', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders two groups', () => { - const groups = findGroups(); - - expect(groups).toHaveLength(2); - - const actualNames = groups.wrappers.map((group) => group.props('group').name); - expect(actualNames).toEqual(['Places', 'All GitLab']); - }); - - it('renders context switcher links in first group', () => { - const group = findGroups().at(0); - expect(group.props('group').name).toBe('Places'); + const findPlaces = () => wrapper.findComponent(GlobalSearchDefaultPlaces); + const findIssuables = () => wrapper.findComponent(GlobalSearchDefaultIssuables); + const receivedAttrs = (wrapperInstance) => ({ + // See https://github.com/vuejs/test-utils/issues/2151. + ...wrapperInstance.vm.$attrs, + }); - const items = findItems(group); - expect(items).toHaveLength(contextSwitcherLinks.length); - }); + beforeEach(() => { + createComponent(); + }); - it('renders default search options in second group', () => { - const group = findGroups().at(1); - expect(group.props('group').name).toBe('All GitLab'); + describe('all child components can render', () => { + it('renders the components', () => { + expect(findPlaces().exists()).toBe(true); + expect(findIssuables().exists()).toBe(true); + }); - const items = findItems(group); - expect(items).toHaveLength(MOCK_DEFAULT_SEARCH_OPTIONS.length); - }); + it('sets the expected props on first component', () => { + const places = findPlaces(); + expect(receivedAttrs(places)).toEqual({}); + expect(places.classes()).toEqual([]); }); - describe('Empty groups', () => { - beforeEach(() => { - createComponent({ mockDefaultSearchOptions: [], provide: { contextSwitcherLinks: [] } }); - }); + it('sets the expected props on second component', () => { + const issuables = findIssuables(); + expect(receivedAttrs(issuables)).toEqual({ bordered: true }); + expect(issuables.classes()).toEqual(['gl-mt-3']); + }); + }); - it('does not render groups with no items', () => { - expect(findGroups()).toHaveLength(0); - }); + describe('when a child component emits nothing-to-render', () => { + beforeEach(() => { + findPlaces().vm.$emit('nothing-to-render'); }); - describe.each` - group | project | groupHeader - ${null} | ${null} | ${'All GitLab'} - ${{ name: 'Test Group' }} | ${null} | ${'Test Group'} - ${{ name: 'Test Group' }} | ${{ name: 'Test Project' }} | ${'Test Project'} - `('Current context header', ({ group, project, groupHeader }) => { - describe(`when group is ${group?.name} and project is ${project?.name}`, () => { - beforeEach(() => { - createComponent({ - storeState: { - searchContext: { - group, - project, - }, - }, - }); - }); + it('does not render the component', () => { + expect(findPlaces().exists()).toBe(false); + expect(findIssuables().exists()).toBe(true); + }); - it(`should render as ${groupHeader}`, () => { - expect(wrapper.text()).toContain(groupHeader); - }); - }); + it('sets the expected props on first component', () => { + const issuables = findIssuables(); + expect(receivedAttrs(issuables)).toEqual({}); + expect(issuables.classes()).toEqual([]); }); }); }); diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js new file mode 100644 index 00000000000..c6126a348f5 --- /dev/null +++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_places_spec.js @@ -0,0 +1,78 @@ +import { GlDisclosureDropdownGroup, GlDisclosureDropdownItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import GlobalSearchDefaultPlaces from '~/super_sidebar/components/global_search/components/global_search_default_places.vue'; +import { contextSwitcherLinks } from '../../../mock_data'; + +describe('GlobalSearchDefaultPlaces', () => { + let wrapper; + + const createComponent = ({ links = [], attrs } = {}) => { + wrapper = shallowMount(GlobalSearchDefaultPlaces, { + provide: { + contextSwitcherLinks: links, + }, + attrs, + stubs: { + GlDisclosureDropdownGroup, + }, + }); + }; + + const findGroup = () => wrapper.findComponent(GlDisclosureDropdownGroup); + const findItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem); + + describe('given no contextSwitcherLinks', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders nothing', () => { + expect(wrapper.html()).toBe(''); + }); + + it('emits a nothing-to-render event', () => { + expect(wrapper.emitted('nothing-to-render')).toEqual([[]]); + }); + }); + + describe('given some contextSwitcherLinks', () => { + beforeEach(() => { + createComponent({ + links: contextSwitcherLinks, + attrs: { + bordered: true, + class: 'test-class', + }, + }); + }); + + it('renders a disclosure dropdown group', () => { + expect(findGroup().exists()).toBe(true); + }); + + it('renders the expected header', () => { + expect(wrapper.text()).toContain('Places'); + }); + + it('passes attrs down', () => { + const group = findGroup(); + expect(group.props('bordered')).toBe(true); + expect(group.classes()).toContain('test-class'); + }); + + it('renders the links', () => { + const itemProps = findItems().wrappers.map((item) => item.props('item')); + + expect(itemProps).toEqual([ + { + text: 'Explore', + href: '/explore', + }, + { + text: 'Admin area', + href: '/admin', + }, + ]); + }); + }); +}); diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js index 0d34329c60d..6fb9715824f 100644 --- a/spec/frontend/super_sidebar/mock_data.js +++ b/spec/frontend/super_sidebar/mock_data.js @@ -73,6 +73,7 @@ export const mergeRequestMenuGroup = [ export const contextSwitcherLinks = [ { title: 'Explore', link: '/explore', icon: 'compass', link_classes: 'persistent-link-class' }, + { title: 'Admin area', link: '/admin', icon: 'admin' }, ]; export const sidebarData = { diff --git a/spec/frontend/vue_compat_test_setup.js b/spec/frontend/vue_compat_test_setup.js index fe43f8f2617..ad1230f2ca9 100644 --- a/spec/frontend/vue_compat_test_setup.js +++ b/spec/frontend/vue_compat_test_setup.js @@ -21,6 +21,13 @@ function isLegacyExtendedComponent(component) { function unwrapLegacyVueExtendComponent(selector) { return isLegacyExtendedComponent(selector) ? selector.options : selector; } +function getStubProps(component) { + const stubProps = { ...component.props }; + component.mixins?.forEach((mixin) => { + Object.assign(stubProps, unwrapLegacyVueExtendComponent(mixin).props); + }); + return stubProps; +} if (global.document) { const compatConfig = { @@ -148,33 +155,27 @@ if (global.document) { return true; }; - VTU.config.plugins.createStubs = ({ name, component: rawComponent, registerStub }) => { + VTU.config.plugins.createStubs = ({ name, component: rawComponent, registerStub, stubs }) => { const component = unwrapLegacyVueExtendComponent(rawComponent); const hyphenatedName = name.replace(/\B([A-Z])/g, '-$1').toLowerCase(); + const stubTag = stubs?.[name] ? name : hyphenatedName; const stub = Vue.defineComponent({ name: getComponentName(component), - props: component.props, - model: component.model, + props: getStubProps(component), + model: component.model ?? component.mixins?.find((m) => m.model), methods: Object.fromEntries( Object.entries(component.methods ?? {}).map(([key]) => [key, noop]), ), render() { - const { - $slots: slots = {}, - $scopedSlots: scopedSlots = {}, - $parent: parent, - $vnode: vnode, - } = this; - - const hasStaticDefaultSlot = 'default' in slots && !('default' in scopedSlots); - const isTheOnlyChild = parent?.$.subTree === vnode; - // this condition should be altered when https://github.com/vuejs/vue-test-utils/pull/2068 is merged - // and our codebase will be updated to include it (@vue/test-utils@1.3.6 I assume) - const shouldRenderAllSlots = !hasStaticDefaultSlot && isTheOnlyChild; + const { $scopedSlots: scopedSlots = {} } = this; + + // eslint-disable-next-line no-underscore-dangle + const hasDefaultSlot = 'default' in scopedSlots && scopedSlots.default._ns; + const shouldRenderAllSlots = !component.functional && !hasDefaultSlot; const renderSlotByName = (slotName) => { - const slot = scopedSlots[slotName] || slots[slotName]; + const slot = scopedSlots[slotName]; let result; if (typeof slot === 'function') { try { @@ -189,16 +190,16 @@ if (global.document) { }; const slotContents = shouldRenderAllSlots - ? [...new Set([...Object.keys(slots), ...Object.keys(scopedSlots)])] - .map(renderSlotByName) - .filter(Boolean) + ? Object.keys(scopedSlots).map(renderSlotByName).filter(Boolean) : renderSlotByName('default'); const props = Object.fromEntries( - Object.entries(this.$props).filter(([prop]) => isPropertyValidOnDomNode(prop)), + Object.entries(this.$props) + .filter(([prop]) => isPropertyValidOnDomNode(prop)) + .map(([key, value]) => [key, typeof value === 'function' ? '[Function]' : value]), ); - return Vue.h(`${hyphenatedName || 'anonymous'}-stub`, props, slotContents); + return Vue.h(`${stubTag || 'anonymous'}-stub`, props, slotContents); }, }); diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb index b9490306410..9f7aaa21f5b 100644 --- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -2,9 +2,13 @@ require 'spec_helper' -RSpec.describe Gitlab::HookData::IssueBuilder do - let_it_be(:label) { create(:label) } - let_it_be(:issue) { create(:labeled_issue, labels: [label], project: label.project) } +RSpec.describe Gitlab::HookData::IssueBuilder, feature_category: :webhooks do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:label) { create(:label, project: project) } + let_it_be(:issue) { create(:labeled_issue, labels: [label], project: project) } + let_it_be(:contact) { create(:contact, group: project.group) } + let_it_be(:issue_contact) { create(:issue_customer_relations_contact, issue: issue, contact: contact) } let(:builder) { described_class.new(issue) } @@ -50,6 +54,7 @@ RSpec.describe Gitlab::HookData::IssueBuilder do expect(data).to include(:state) expect(data).to include(:severity) expect(data).to include('labels' => [label.hook_attrs]) + expect(data).to include('customer_relations_contacts' => [contact.reload.hook_attrs]) end context 'when the issue has an image in the description' do diff --git a/spec/models/customer_relations/contact_spec.rb b/spec/models/customer_relations/contact_spec.rb index 3d78a9089ca..6f124662b8e 100644 --- a/spec/models/customer_relations/contact_spec.rb +++ b/spec/models/customer_relations/contact_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe CustomerRelations::Contact, type: :model do +RSpec.describe CustomerRelations::Contact, type: :model, feature_category: :team_planning do let_it_be(:group) { create(:group) } describe 'associations' do @@ -280,4 +280,25 @@ RSpec.describe CustomerRelations::Contact, type: :model do end end end + + describe '#hook_attrs' do + let_it_be(:contact) { create(:contact, group: group) } + + it 'includes the expected attributes' do + expect(contact.hook_attrs).to match a_hash_including( + { + 'created_at' => contact.created_at, + 'description' => contact.description, + 'first_name' => contact.first_name, + 'group_id' => group.id, + 'id' => contact.id, + 'last_name' => contact.last_name, + 'organization_id' => contact.organization_id, + 'state' => contact.state, + 'updated_at' => contact.updated_at + } + ) + expect(contact.hook_attrs.keys).to match_array(described_class::SAFE_ATTRIBUTES) + end + end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index a84445f397f..a26ab2501ec 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -188,7 +188,7 @@ RSpec.describe Issue, feature_category: :team_planning do expect(issue).not_to be_valid expect(issue.errors[:base]) - .to include(_('A confidential issue cannot have a parent that already has non-confidential children.')) + .to include(_('A confidential issue must have only confidential children. Make any child items confidential and try again.')) end it 'allows to make child confidential' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index b463199a85b..da3f691b63a 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -3266,6 +3266,15 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev end end end + + context 'with check_mergeability_retry_lease option' do + it 'call check_mergeability with sync_retry_lease' do + allow(subject).to receive(:mergeable_state?) { true } + expect(subject).to receive(:check_mergeability).with(sync_retry_lease: true) + + subject.mergeable?(check_mergeability_retry_lease: true) + end + end end describe '#skipped_mergeable_checks' do @@ -3300,6 +3309,14 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev subject.check_mergeability end + context 'when sync_retry_lease is true' do + it 'executes MergeabilityCheckService' do + expect(mergeability_service).to receive(:execute).with(retry_lease: true) + + subject.check_mergeability(sync_retry_lease: true) + end + end + context 'when async is true' do it 'executes MergeabilityCheckService asynchronously' do expect(mergeability_service).to receive(:async_execute) diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index c77cf288f56..c32b7b16f63 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -577,6 +577,14 @@ RSpec.describe MergeRequests::MergeService, feature_category: :code_review_workf end end end + + context 'when passing `check_mergeability_retry_lease: true` as `options` parameter' do + it 'call mergeable? with check_mergeability_retry_lease' do + expect(merge_request).to receive(:mergeable?).with(hash_including(check_mergeability_retry_lease: true)).and_call_original + + service.execute(merge_request, check_mergeability_retry_lease: true) + end + end end end diff --git a/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb b/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb index 8a1581d513a..a764e751bf5 100644 --- a/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb +++ b/spec/support/helpers/models/ci/partitioning_testing/rspec_hooks.rb @@ -4,10 +4,6 @@ RSpec.configure do |config| config.include ::Ci::PartitioningTesting::PartitionIdentifiers config.around(:each, :ci_partitionable) do |example| - unless ::Ci::Build.table_name.to_s.starts_with?('p_') - skip 'Skipping partitioning tests until `ci_builds` is partitioned' - end - ::Ci::PartitioningTesting::SchemaHelpers.with_routing_tables do example.run end diff --git a/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb b/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb index f31dbb111ee..df653c853b9 100644 --- a/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb +++ b/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb @@ -6,16 +6,22 @@ module Ci module_function def with_routing_tables - # model.table_name = :routing_table + previous_table_name = CommitStatus.table_name + CommitStatus.table_name = :p_ci_builds + CommitStatus.descendants.each(&:reset_table_name) + yield - # ensure - # model.table_name = :regular_table + + ensure + CommitStatus.table_name = previous_table_name + CommitStatus.descendants.each(&:reset_table_name) end def setup(connection: Ci::ApplicationRecord.connection) each_partitionable_table do |table_name| create_test_partition("p_#{table_name}", connection: connection) end + ensure_builds_id_uniquness(connection: connection) end def teardown(connection: Ci::ApplicationRecord.connection) @@ -57,6 +63,16 @@ module Ci SQL end + # This can be removed after https://gitlab.com/gitlab-org/gitlab/-/issues/421173 + # is implemented + def ensure_builds_id_uniquness(connection:) + connection.execute(<<~SQL.squish) + CREATE TRIGGER assign_p_ci_builds_id_trigger + BEFORE INSERT ON #{full_partition_name('ci_builds')} + FOR EACH ROW EXECUTE FUNCTION assign_p_ci_builds_id_value(); + SQL + end + def table_available?(table_name, connection:) connection.table_exists?(table_name) && connection.column_exists?(table_name, :partition_id) diff --git a/spec/support/protected_branch_helpers.rb b/spec/support/protected_branch_helpers.rb index d983d03fd2e..576275e9d1d 100644 --- a/spec/support/protected_branch_helpers.rb +++ b/spec/support/protected_branch_helpers.rb @@ -9,6 +9,10 @@ module ProtectedBranchHelpers end end + def show_add_form + click_button 'Add protected branch' + end + def set_protected_branch_name(branch_name) find('.js-protected-branch-select').click find('.dropdown-input-field').set(branch_name) diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb index 2d3f1949716..fb882ef8a23 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb @@ -7,6 +7,7 @@ RSpec.shared_examples "protected branches > access control > CE" do it "allows creating protected branches that #{access_type_name} can push to" do visit project_protected_branches_path(project) + show_add_form set_protected_branch_name('master') set_allowed_to('merge', no_one) set_allowed_to('push', access_type_name) @@ -19,6 +20,7 @@ RSpec.shared_examples "protected branches > access control > CE" do it "allows creating protected branches that #{access_type_name} can merge to" do visit project_protected_branches_path(project) + show_add_form set_protected_branch_name('master') set_allowed_to('merge', access_type_name) set_allowed_to('push', no_one) @@ -31,6 +33,7 @@ RSpec.shared_examples "protected branches > access control > CE" do it "allows updating protected branches so that #{access_type_name} can push to them" do visit project_protected_branches_path(project) + show_add_form set_protected_branch_name('master') set_allowed_to('merge', no_one) set_allowed_to('push', no_one) @@ -52,6 +55,7 @@ RSpec.shared_examples "protected branches > access control > CE" do it "allows updating protected branches so that #{access_type_name} can merge to them" do visit project_protected_branches_path(project) + show_add_form set_protected_branch_name('master') set_allowed_to('merge', no_one) set_allowed_to('push', no_one) diff --git a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb index 90b0e600228..a15ee47de34 100644 --- a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb +++ b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb @@ -20,6 +20,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do it "shows all dropdown sections in the 'Allowed to push' main dropdown, with only one deploy key" do visit project_protected_branches_path(project) + click_button 'Add protected branch' find(".js-allowed-to-push").click wait_for_requests @@ -35,6 +36,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do it "shows all sections but not deploy keys in the 'Allowed to merge' main dropdown" do visit project_protected_branches_path(project) + click_button 'Add protected branch' find(".js-allowed-to-merge").click wait_for_requests @@ -65,6 +67,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do it "just shows all sections but not deploy keys in the 'Allowed to push' dropdown" do visit project_protected_branches_path(project) + click_button 'Add protected branch' find(".js-allowed-to-push").click wait_for_requests |