diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-11 06:09:14 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-11 06:09:14 +0300 |
commit | f4b4020ba3e083a1f3a828da755366fbfbb900ce (patch) | |
tree | 45365634493c7ce6fc37e6d46b4f93c3cc2bbc28 | |
parent | 1326fc930c76c78b4943c86b0b18b939004a07ee (diff) |
Add latest changes from gitlab-org/gitlab@master
25 files changed, 336 insertions, 131 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 131b990ed5e..fa65af81679 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -92e441aabf41c0793da9cb0ea1be58175f990f80 +b5065d5e104d2625113b11b20852ebffd7096326 diff --git a/app/assets/images/service_desk_callout.svg b/app/assets/images/service_desk_callout.svg new file mode 100644 index 00000000000..2886388279e --- /dev/null +++ b/app/assets/images/service_desk_callout.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><rect width="7" height="1" x="59" y="38" fill="#E1DBF2" rx=".5"/><path fill="#6B4FBB" d="M60.5 42a3.5 3.5 0 0 0 0-7v7z"/><rect width="7" height="1" x="12" y="38" fill="#E1DBF2" transform="matrix(-1 0 0 1 31 0)" rx=".5"/><path fill="#6B4FBB" d="M17.5 42a3.5 3.5 0 0 1 0-7v7z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M39 58c10.493 0 19-8.507 19-19s-8.507-19-19-19-19 8.507-19 19 8.507 19 19 19zm0 4c-12.703 0-23-10.297-23-23s10.297-23 23-23 23 10.297 23 23-10.297 23-23 23z"/><path fill="#6B4FBB" d="M35 56a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm4 0a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M26.5 40c0 4.143 3.355 7.5 7.494 7.5h10.012A7.497 7.497 0 0 0 51.5 40c0-4.143-3.355-7.5-7.494-7.5H33.994A7.497 7.497 0 0 0 26.5 40zm-3 0c0-5.799 4.698-10.5 10.494-10.5h10.012C49.802 29.5 54.5 34.2 54.5 40c0 5.799-4.698 10.5-10.494 10.5H33.994C28.198 50.5 23.5 45.8 23.5 40z"/><path fill="#6B4FBB" fill-rule="nonzero" d="M35.255 42.406a1 1 0 1 1 1.872-.703 2.001 2.001 0 0 0 3.76-.038 1 1 0 1 1 1.886.665 4 4 0 0 1-7.518.076zM31.5 40a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm15 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/><path fill="#6B4FBB" d="M38 22h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2zm0 3h2a1 1 0 0 1 0 2h-2a1 1 0 0 1 0-2z" style="mix-blend-mode:multiply"/></g></svg>
\ No newline at end of file diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue index 61a9b22bfc5..8db86d0e894 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -1,12 +1,12 @@ <script> import { GlButton, + GlButtonGroup, GlLabel, GlTooltip, GlIcon, GlSprintf, GlTooltipDirective, - GlDisclosureDropdown, } from '@gitlab/ui'; import { mapActions, mapState } from 'vuex'; import { isListDraggable } from '~/boards/boards_util'; @@ -35,15 +35,14 @@ import ItemCount from './item_count.vue'; export default { i18n: { newIssue: s__('Boards|Create new issue'), - listActions: s__('Boards|List actions'), newEpic: s__('Boards|Create new epic'), listSettings: s__('Boards|Edit list settings'), expand: s__('Boards|Expand'), collapse: s__('Boards|Collapse'), }, components: { - GlDisclosureDropdown, GlButton, + GlButtonGroup, GlLabel, GlTooltip, GlIcon, @@ -194,50 +193,6 @@ export default { canShowTotalWeight() { return this.weightFeatureAvailable && !this.isLoading; }, - actionListItems() { - const items = []; - - if (this.isNewIssueShown) { - const newIssueText = this.$options.i18n.newIssue; - items.push({ - text: newIssueText, - action: this.showNewIssueForm, - extraAttrs: { - 'data-testid': 'newIssueBtn', - title: newIssueText, - 'aria-label': newIssueText, - }, - }); - } - - if (this.isNewEpicShown) { - const newEpicText = this.$options.i18n.newEpic; - items.push({ - text: newEpicText, - action: this.showNewEpicForm, - extraAttrs: { - 'data-testid': 'newEpicBtn', - title: newEpicText, - 'aria-label': newEpicText, - }, - }); - } - - if (this.isSettingsShown) { - const listSettingsText = this.$options.i18n.listSettings; - items.push({ - text: listSettingsText, - action: this.openSidebarSettings, - extraAttrs: { - 'data-testid': 'settingsBtn', - title: listSettingsText, - 'aria-label': listSettingsText, - }, - }); - } - - return items; - }, }, apollo: { boardList: { @@ -525,23 +480,42 @@ export default { <!-- EE end --> </span> </div> - <gl-disclosure-dropdown - v-if="showListHeaderActions" - v-gl-tooltip.hover.top="{ - title: $options.i18n.listActions, - boundary: 'viewport', - }" - data-testid="header-list-actions" - class="gl-py-2 gl-ml-3" - :aria-label="$options.i18n.listActions" - :title="$options.i18n.listActions" - category="tertiary" - icon="ellipsis_v" - :text-sr-only="true" - :items="actionListItems" - no-caret - placement="right" - /> + <gl-button-group v-if="showListHeaderActions" class="board-list-button-group gl-pl-2"> + <gl-button + v-if="isNewIssueShown" + ref="newIssueBtn" + v-gl-tooltip.hover + :aria-label="$options.i18n.newIssue" + :title="$options.i18n.newIssue" + size="small" + icon="plus" + data-testid="new-issue-btn" + @click="showNewIssueForm" + /> + + <gl-button + v-if="isNewEpicShown" + v-gl-tooltip.hover + :aria-label="$options.i18n.newEpic" + :title="$options.i18n.newEpic" + size="small" + icon="plus" + data-testid="new-epic-btn" + @click="showNewEpicForm" + /> + + <gl-button + v-if="isSettingsShown" + ref="settingsBtn" + v-gl-tooltip.hover + :aria-label="$options.i18n.listSettings" + size="small" + :title="$options.i18n.listSettings" + icon="settings" + data-testid="settings-btn" + @click="openSidebarSettings" + /> + </gl-button-group> </h3> </header> </template> diff --git a/app/assets/javascripts/service_desk/components/info_banner.vue b/app/assets/javascripts/service_desk/components/info_banner.vue new file mode 100644 index 00000000000..8aaced839a5 --- /dev/null +++ b/app/assets/javascripts/service_desk/components/info_banner.vue @@ -0,0 +1,64 @@ +<script> +import { GlLink, GlButton } from '@gitlab/ui'; +import { + infoBannerTitle, + infoBannerAdminNote, + infoBannerUserNote, + enableServiceDesk, + learnMore, +} from '../constants'; + +export default { + name: 'InfoBanner', + components: { + GlLink, + GlButton, + }, + inject: [ + 'serviceDeskCalloutSvgPath', + 'serviceDeskEmailAddress', + 'canAdminIssues', + 'canEditProjectSettings', + 'serviceDeskSettingsPath', + 'isServiceDeskEnabled', + 'serviceDeskHelpPath', + ], + i18n: { infoBannerTitle, infoBannerAdminNote, infoBannerUserNote, enableServiceDesk, learnMore }, + computed: { + canSeeEmailAddress() { + return this.canAdminIssues && this.isServiceDeskEnabled; + }, + canEnableServiceDesk() { + return this.canEditProjectSettings && !this.isServiceDeskEnabled; + }, + }, +}; +</script> + +<template> + <div class="gl-border-b gl-pb-3 gl-display-flex gl-align-items-flex-start"> + <!-- eslint-disable @gitlab/vue-require-i18n-attribute-strings --> + <img + :src="serviceDeskCalloutSvgPath" + alt="" + class="gl-display-none gl-sm-display-block gl-p-5" + /> + <!-- eslint-enable @gitlab/vue-require-i18n-attribute-strings --> + <div class="gl-mt-3 gl-ml-3"> + <h5>{{ $options.i18n.infoBannerTitle }}</h5> + <p v-if="canSeeEmailAddress"> + {{ $options.i18n.infoBannerAdminNote }} <code>{{ serviceDeskEmailAddress }}</code> + </p> + <p> + {{ $options.i18n.infoBannerUserNote }} + <gl-link :href="serviceDeskHelpPath" target="_blank">{{ $options.i18n.learnMore }}</gl-link + >. + </p> + <p v-if="canEnableServiceDesk" class="gl-mt-3"> + <gl-button :href="serviceDeskSettingsPath" variant="confirm">{{ + $options.i18n.enableServiceDesk + }}</gl-button> + </p> + </div> + </div> +</template> diff --git a/app/assets/javascripts/service_desk/components/service_desk_list_app.vue b/app/assets/javascripts/service_desk/components/service_desk_list_app.vue index e0fbc2e4748..e8b05642e7d 100644 --- a/app/assets/javascripts/service_desk/components/service_desk_list_app.vue +++ b/app/assets/javascripts/service_desk/components/service_desk_list_app.vue @@ -14,6 +14,7 @@ import { searchPlaceholder, SERVICE_DESK_BOT_USERNAME, } from '../constants'; +import InfoBanner from './info_banner.vue'; export default { i18n: { @@ -26,8 +27,16 @@ export default { components: { GlEmptyState, IssuableList, + InfoBanner, }, - inject: ['emptyStateSvgPath', 'isProject', 'isSignedIn', 'fullPath'], + inject: [ + 'emptyStateSvgPath', + 'isProject', + 'isSignedIn', + 'fullPath', + 'isServiceDeskSupported', + 'hasAnyIssues', + ], data() { return { serviceDeskIssues: [], @@ -52,12 +61,18 @@ export default { // See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106004#note_1217325202 for details notifyOnNetworkStatusChange: true, result({ data }) { + if (!data) { + return; + } this.pageInfo = data?.project.issues.pageInfo ?? {}; }, error(error) { this.issuesError = this.$options.i18n.errorFetchingIssues; Sentry.captureException(error); }, + skip() { + return !this.hasAnyIssues; + }, }, serviceDeskIssuesCounts: { query: getServiceDeskIssuesCounts, @@ -94,6 +109,9 @@ export default { [STATUS_ALL]: allIssues?.count, }; }, + isInfoBannerVisible() { + return this.isServiceDeskSupported && this.hasAnyIssues; + }, }, methods: { handleClickTab(state) { @@ -108,6 +126,7 @@ export default { <template> <section> + <info-banner v-if="isInfoBannerVisible" /> <issuable-list namespace="service-desk" recent-searches-storage-key="issues" diff --git a/app/assets/javascripts/service_desk/constants.js b/app/assets/javascripts/service_desk/constants.js index ff6128e0967..685ad738792 100644 --- a/app/assets/javascripts/service_desk/constants.js +++ b/app/assets/javascripts/service_desk/constants.js @@ -1,4 +1,4 @@ -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; export const SERVICE_DESK_BOT_USERNAME = 'support-bot'; @@ -6,3 +6,12 @@ export const errorFetchingCounts = __('An error occurred while getting issue cou export const errorFetchingIssues = __('An error occurred while loading issues'); export const noSearchNoFilterTitle = __('Please select at least one filter to see results'); export const searchPlaceholder = __('Search or filter results...'); +export const infoBannerTitle = s__( + 'ServiceDesk|Use Service Desk to connect with your users and offer customer support through email right inside GitLab', +); +export const infoBannerAdminNote = s__('ServiceDesk|Your users can send emails to this address:'); +export const infoBannerUserNote = s__( + 'ServiceDesk|Issues created from Service Desk emails will appear here. Each comment becomes part of the email conversation.', +); +export const enableServiceDesk = s__('ServiceDesk|Enable Service Desk'); +export const learnMore = __('Learn more'); diff --git a/app/assets/javascripts/service_desk/graphql.js b/app/assets/javascripts/service_desk/graphql.js new file mode 100644 index 00000000000..e01973f1e8a --- /dev/null +++ b/app/assets/javascripts/service_desk/graphql.js @@ -0,0 +1,24 @@ +import createDefaultClient, { createApolloClientWithCaching } from '~/lib/graphql'; + +let client; + +const typePolicies = { + Project: { + fields: { + issues: { + merge: true, + }, + }, + }, +}; + +export async function gqlClient() { + if (client) return client; + client = gon.features?.frontendCaching + ? await createApolloClientWithCaching( + {}, + { localCacheKey: 'service_desk_list', cacheConfig: { typePolicies } }, + ) + : createDefaultClient({}, { cacheConfig: { typePolicies } }); + return client; +} diff --git a/app/assets/javascripts/service_desk/index.js b/app/assets/javascripts/service_desk/index.js index 04fc8f2e746..a9172f96540 100644 --- a/app/assets/javascripts/service_desk/index.js +++ b/app/assets/javascripts/service_desk/index.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import { gqlClient } from '~/issues/list/graphql'; import { parseBoolean } from '~/lib/utils/common_utils'; +import { gqlClient } from './graphql'; import ServiceDeskListApp from './components/service_desk_list_app.vue'; export async function mountServiceDeskListApp() { @@ -11,7 +11,21 @@ export async function mountServiceDeskListApp() { return null; } - const { emptyStateSvgPath, fullPath, isProject, isSignedIn } = el.dataset; + const { + projectDataEmptyStateSvgPath, + projectDataFullPath, + projectDataIsProject, + projectDataIsSignedIn, + projectDataHasAnyIssues, + serviceDeskEmailAddress, + canAdminIssues, + canEditProjectSettings, + serviceDeskCalloutSvgPath, + serviceDeskSettingsPath, + serviceDeskHelpPath, + isServiceDeskSupported, + isServiceDeskEnabled, + } = el.dataset; Vue.use(VueApollo); @@ -22,10 +36,19 @@ export async function mountServiceDeskListApp() { defaultClient: await gqlClient(), }), provide: { - emptyStateSvgPath, - fullPath, - isProject: parseBoolean(isProject), - isSignedIn: parseBoolean(isSignedIn), + emptyStateSvgPath: projectDataEmptyStateSvgPath, + fullPath: projectDataFullPath, + isProject: parseBoolean(projectDataIsProject), + isSignedIn: parseBoolean(projectDataIsSignedIn), + serviceDeskEmailAddress, + canAdminIssues: parseBoolean(canAdminIssues), + canEditProjectSettings: parseBoolean(canEditProjectSettings), + serviceDeskCalloutSvgPath, + serviceDeskSettingsPath, + serviceDeskHelpPath, + isServiceDeskSupported: parseBoolean(isServiceDeskSupported), + isServiceDeskEnabled: parseBoolean(isServiceDeskEnabled), + hasAnyIssues: parseBoolean(projectDataHasAnyIssues), }, render: (createComponent) => createComponent(ServiceDeskListApp), }); diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e01e7350b12..941a480951c 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -58,7 +58,7 @@ class Projects::IssuesController < Projects::ApplicationController push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?) end - before_action only: :index do + before_action only: [:index, :service_desk] do push_frontend_feature_flag(:or_issuable_queries, project) push_frontend_feature_flag(:frontend_caching, project&.group) end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 36c09770503..2b4d0949a92 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1553,10 +1553,6 @@ class MergeRequest < ApplicationRecord %r{\Arefs/#{Repository::REF_MERGE_REQUEST}/\d+/train\z}o.match?(ref) end - def train - MergeTrains::Train.new(target_project.id, target_branch) - end - def in_locked_state lock_mr yield diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml index bd0e4b51a63..2edd9cd5592 100644 --- a/app/views/import/fogbugz/new.html.haml +++ b/app/views/import/fogbugz/new.html.haml @@ -24,4 +24,5 @@ .col-md-4 = password_field_tag :password, nil, class: 'form-control gl-form-input' .form-actions - = submit_tag _('Continue to the next step'), class: 'gl-button btn btn-confirm' + = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm) do + = _('Continue to the next step') diff --git a/app/views/projects/issues/service_desk.html.haml b/app/views/projects/issues/service_desk.html.haml index 8f2ddf8510c..9793f21e4a9 100644 --- a/app/views/projects/issues/service_desk.html.haml +++ b/app/views/projects/issues/service_desk.html.haml @@ -9,7 +9,15 @@ .js-service-desk-issues.service-desk-issues{ data: { support_bot: support_bot_attrs } } - if ::Feature.enabled?(:service_desk_vue_list, @project) - .js-service-desk-list{ data: project_issues_list_data(@project, current_user) } + .js-service-desk-list{ data: { project_data: project_issues_list_data(@project, current_user), + service_desk_email_address: @project.service_desk_address, + can_admin_issues: can?(current_user, :admin_issue, @project).to_s, + can_edit_project_settings: can?(current_user, :admin_project, @project).to_s, + service_desk_callout_svg_path: image_path('service_desk_callout.svg'), + service_desk_settings_path: edit_project_path(@project, anchor: 'js-service-desk'), + service_desk_help_path: help_page_path('user/project/service_desk'), + is_service_desk_supported: Gitlab::ServiceDesk.supported?.to_s, + is_service_desk_enabled: @project.service_desk_enabled?.to_s } } - else .top-area = render 'shared/issuable/nav', type: :issues diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index e7caebe819d..bca1ee5245c 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -422,7 +422,7 @@ Prerequisites: To set a WIP limit for a list, in an issue board: -1. On the top of the list you want to edit, select **List actions** (**{ellipsis_v}**) **> Edit list settings**. +1. On the top of the list you want to edit, select **Edit list settings** (**{settings}**). The list settings sidebar opens on the right. 1. Next to **Work in progress limit**, select **Edit**. 1. Enter the maximum number of issues. @@ -499,7 +499,7 @@ Prerequisites: To remove a list from an issue board: -1. On the top of the list you want to remove, select **List actions** (**{ellipsis_v}**). +1. On the top of the list you want to remove, select **Edit list settings** (**{settings}**). The list settings sidebar opens on the right. 1. Select **Remove list**. 1. On the confirmation dialog, select **Remove list** again. diff --git a/doc/user/project/issues/create_issues.md b/doc/user/project/issues/create_issues.md index d01a115b012..8cc9ab71ca7 100644 --- a/doc/user/project/issues/create_issues.md +++ b/doc/user/project/issues/create_issues.md @@ -100,7 +100,7 @@ To create an issue from a project issue board: 1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project. 1. Select **Plan > Issue boards**. -1. At the top of a board list, select **List actions** (**{ellipsis_v}**) **> Create new issue**. +1. At the top of a board list, select **Create new issue** (**{plus-square}**). 1. Enter the issue's title. 1. Select **Create issue**. @@ -108,7 +108,7 @@ To create an issue from a group issue board: 1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group. 1. Select **Plan > Issue boards**. -1. At the top of a board list, select **List actions** (**{ellipsis_v}**) **> Create new issue**. +1. At the top of a board list, select **Create new issue** (**{plus-square}**). 1. Enter the issue's title. 1. Under **Projects**, select the project in the group that the issue should belong to. 1. Select **Create issue**. diff --git a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml index f4a13d61ba2..b1e498a9d09 100644 --- a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.50.0' + DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.51.0' .dast-auto-deploy: image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}" diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml index c1a3daa7f5b..5a7e69b62d9 100644 --- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - AUTO_DEPLOY_IMAGE_VERSION: 'v2.50.0' + AUTO_DEPLOY_IMAGE_VERSION: 'v2.51.0' .auto-deploy: image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}" diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml index a3c7c6baf02..dac559db8d5 100644 --- a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - AUTO_DEPLOY_IMAGE_VERSION: 'v2.50.0' + AUTO_DEPLOY_IMAGE_VERSION: 'v2.51.0' .auto-deploy: image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}" diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 676d04cd34c..92fb8d868ee 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7870,9 +7870,6 @@ msgstr "" msgid "Boards|Failed to fetch blocking %{issuableType}s" msgstr "" -msgid "Boards|List actions" -msgstr "" - msgid "Boards|Move card" msgstr "" @@ -12982,6 +12979,9 @@ msgstr "" msgid "Could not restore the group" msgstr "" +msgid "Could not retrieve the list of protected branches. Use the YAML editor mode, or refresh this page later. To view the list of protected branches, go to %{boldStart}Settings - Branches%{boldEnd} and expand %{boldStart}Protected branches%{boldEnd}." +msgstr "" + msgid "Could not revoke access token %{access_token_name}." msgstr "" diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 1ea6e079104..9354050400e 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -591,8 +591,6 @@ RSpec.describe 'Project issue boards', :js, feature_category: :team_planning do def remove_list page.within(find('.board:nth-child(2)')) do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click click_button('Edit list settings') end diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 1fcea45c7ae..527415701e6 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -32,22 +32,17 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d end it 'displays new issue button' do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click expect(first('.board')).to have_button('Create new issue', count: 1) end it 'does not display new issue button in closed list' do page.within('.board:nth-child(3)') do - expect(page).not_to have_selector("[data-testid='header-list-actions']") expect(page).not_to have_button('Create new issue') end end it 'shows form when clicking button' do page.within(first('.board')) do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click click_button 'Create new issue' expect(page).to have_selector('.board-new-issue-form') @@ -56,8 +51,6 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d it 'hides form when clicking cancel' do page.within(first('.board')) do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click click_button 'Create new issue' expect(page).to have_selector('.board-new-issue-form') @@ -70,8 +63,6 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d it 'creates new issue, places it on top of the list, and opens sidebar' do page.within(first('.board')) do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click click_button 'Create new issue' end @@ -100,8 +91,6 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d it 'successfuly loads labels to be added to newly created issue' do page.within(first('.board')) do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click click_button 'Create new issue' end @@ -132,8 +121,6 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d wait_for_all_requests page.within('.board:nth-child(2)') do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click click_button('Create new issue') page.within(first('.board-new-issue-form')) do @@ -157,13 +144,11 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d end it 'does not display new issue button in open list' do - expect(page).not_to have_selector("[data-testid='header-list-actions']") expect(first('.board')).not_to have_button('Create new issue') end it 'does not display new issue button in label list' do page.within('.board:nth-child(2)') do - expect(page).not_to have_selector("[data-testid='header-list-actions']") expect(page).not_to have_button('Create new issue') end end @@ -188,7 +173,6 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d context 'when backlog does not exist' do it 'does not display new issue button in label list' do page.within('.board.is-draggable') do - expect(page).not_to have_selector("[data-testid='header-list-actions']") expect(page).not_to have_button('Create new issue') end end @@ -198,13 +182,11 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d let_it_be(:backlog_list) { create(:backlog_list, board: group_board) } it 'does not display new issue button in open list' do - expect(page).not_to have_selector("[data-testid='header-list-actions']") expect(first('.board')).not_to have_button('Create new issue') end it 'does not display new issue button in label list' do page.within('.board.is-draggable') do - expect(page).not_to have_selector("[data-testid='header-list-actions']") expect(page).not_to have_button('Create new issue') end end @@ -223,8 +205,6 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d context 'when backlog does not exist' do it 'display new issue button in label list' do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click expect(board_list_header).to have_button('Create new issue') end end @@ -234,8 +214,6 @@ RSpec.describe 'Issue Boards new issue', :js, feature_category: :team_planning d before do page.within(board_list_header) do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click click_button 'Create new issue' end diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb index 25f7d4d968c..423e8029d9c 100644 --- a/spec/features/groups/board_spec.rb +++ b/spec/features/groups/board_spec.rb @@ -25,8 +25,6 @@ RSpec.describe 'Group Boards', feature_category: :team_planning do it 'adds an issue to the backlog' do page.within(find('.board', match: :first)) do - dropdown = first("[data-testid='header-list-actions']") - dropdown.click issue_title = 'Create new issue' click_button issue_title diff --git a/spec/features/issues/service_desk_spec.rb b/spec/features/issues/service_desk_spec.rb index ba200bb9524..27e2f9fb68f 100644 --- a/spec/features/issues/service_desk_spec.rb +++ b/spec/features/issues/service_desk_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Service Desk Issue Tracker', :js, feature_category: :team_planning do +RSpec.describe 'Service Desk Issue Tracker', :js, feature_category: :service_desk do let(:project) { create(:project, :private, service_desk_enabled: true) } let_it_be(:user) { create(:user) } @@ -181,6 +181,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js, feature_category: :team_planni context 'when service_desk_vue_list feature flag is enabled' do before do stub_feature_flags(service_desk_vue_list: true) + stub_feature_flags(frontend_caching: true) end context 'when there are issues' do @@ -189,6 +190,20 @@ RSpec.describe 'Service Desk Issue Tracker', :js, feature_category: :team_planni let_it_be(:service_desk_issue) { create(:issue, project: project, title: 'Help from email', author: support_bot, service_desk_reply_to: 'service.desk@example.com') } let_it_be(:other_user_issue) { create(:issue, project: project, author: other_user) } + describe 'service desk info content' do + before do + visit service_desk_project_issues_path(project) + end + + it 'displays the small info box, documentation, a button to configure service desk, and the address' do + aggregate_failures do + expect(page).to have_link('Learn more', href: help_page_path('user/project/service_desk')) + expect(page).not_to have_link('Enable Service Desk') + expect(page).to have_content(project.service_desk_address) + end + end + end + describe 'issues list' do before do visit service_desk_project_issues_path(project) diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js index ad2674f9d3b..0c9e1b4646e 100644 --- a/spec/frontend/boards/components/board_list_header_spec.js +++ b/spec/frontend/boards/components/board_list_header_spec.js @@ -1,4 +1,4 @@ -import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui'; +import { GlButtonGroup } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import Vuex from 'vuex'; @@ -93,18 +93,17 @@ describe('Board List Header Component', () => { ...injectedProps, }, stubs: { - GlDisclosureDropdown, - GlDisclosureDropdownItem, + GlButtonGroup, }, }); }; - const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); + const findButtonGroup = () => wrapper.findComponent(GlButtonGroup); const isCollapsed = () => wrapper.vm.list.collapsed; const findTitle = () => wrapper.find('.board-title'); const findCaret = () => wrapper.findByTestId('board-title-caret'); - const findNewIssueButton = () => wrapper.findByTestId('newIssueBtn'); - const findSettingsButton = () => wrapper.findByTestId('settingsBtn'); + const findNewIssueButton = () => wrapper.findByTestId('new-issue-btn'); + const findSettingsButton = () => wrapper.findByTestId('settings-btn'); const findBoardListHeader = () => wrapper.findByTestId('board-list-header'); it('renders border when label color is present', () => { @@ -131,13 +130,13 @@ describe('Board List Header Component', () => { it.each(hasNoAddButton)('does not render dropdown when List Type is `%s`', (listType) => { createComponent({ listType }); - expect(findDropdown().exists()).toBe(false); + expect(findButtonGroup().exists()).toBe(false); }); it.each(hasAddButton)('does render when List Type is `%s`', (listType) => { createComponent({ listType }); - expect(findDropdown().exists()).toBe(true); + expect(findButtonGroup().exists()).toBe(true); expect(findNewIssueButton().exists()).toBe(true); }); @@ -146,7 +145,7 @@ describe('Board List Header Component', () => { currentUserId: null, }); - expect(findDropdown().exists()).toBe(false); + expect(findButtonGroup().exists()).toBe(false); }); }); @@ -156,20 +155,20 @@ describe('Board List Header Component', () => { it.each(hasSettings)('does render for List Type `%s`', (listType) => { createComponent({ listType }); - expect(findDropdown().exists()).toBe(true); + expect(findButtonGroup().exists()).toBe(true); expect(findSettingsButton().exists()).toBe(true); }); it('does not render dropdown when ListType `closed`', () => { createComponent({ listType: ListType.closed }); - expect(findDropdown().exists()).toBe(false); + expect(findButtonGroup().exists()).toBe(false); }); it('renders dropdown but not the Settings button when ListType `backlog`', () => { createComponent({ listType: ListType.backlog }); - expect(findDropdown().exists()).toBe(true); + expect(findButtonGroup().exists()).toBe(true); expect(findSettingsButton().exists()).toBe(false); }); }); diff --git a/spec/frontend/service_desk/components/info_banner_spec.js b/spec/frontend/service_desk/components/info_banner_spec.js new file mode 100644 index 00000000000..7487d5d8b64 --- /dev/null +++ b/spec/frontend/service_desk/components/info_banner_spec.js @@ -0,0 +1,81 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLink, GlButton } from '@gitlab/ui'; +import InfoBanner from '~/service_desk/components/info_banner.vue'; +import { infoBannerAdminNote, enableServiceDesk } from '~/service_desk/constants'; + +describe('InfoBanner', () => { + let wrapper; + + const defaultProvide = { + serviceDeskCalloutSvgPath: 'callout.svg', + serviceDeskEmailAddress: 'sd@gmail.com', + canAdminIssues: true, + canEditProjectSettings: true, + serviceDeskSettingsPath: 'path/to/project/settings', + serviceDeskHelpPath: 'path/to/documentation', + isServiceDeskEnabled: true, + }; + + const findEnableSDButton = () => wrapper.findComponent(GlButton); + + const mountComponent = (provide) => { + return shallowMount(InfoBanner, { + provide: { + ...defaultProvide, + ...provide, + }, + stubs: { + GlLink, + GlButton, + }, + }); + }; + + beforeEach(() => { + wrapper = mountComponent(); + }); + + describe('Service Desk email address', () => { + it('renders when user can admin issues and service desk is enabled', () => { + expect(wrapper.text()).toContain(infoBannerAdminNote); + expect(wrapper.text()).toContain(wrapper.vm.serviceDeskEmailAddress); + }); + + it('does not render, when user can not admin issues', () => { + wrapper = mountComponent({ canAdminIssues: false }); + + expect(wrapper.text()).not.toContain(infoBannerAdminNote); + expect(wrapper.text()).not.toContain(wrapper.vm.serviceDeskEmailAddress); + }); + + it('does not render, when service desk is not setup', () => { + wrapper = mountComponent({ isServiceDeskEnabled: false }); + + expect(wrapper.text()).not.toContain(infoBannerAdminNote); + expect(wrapper.text()).not.toContain(wrapper.vm.serviceDeskEmailAddress); + }); + }); + + describe('Link to Service Desk settings', () => { + it('renders when user can edit settings and service desk is not enabled', () => { + wrapper = mountComponent({ isServiceDeskEnabled: false }); + + expect(wrapper.text()).toContain(enableServiceDesk); + expect(findEnableSDButton().exists()).toBe(true); + }); + + it('does not render when service desk is enabled', () => { + wrapper = mountComponent(); + + expect(wrapper.text()).not.toContain(enableServiceDesk); + expect(findEnableSDButton().exists()).toBe(false); + }); + + it('does not render when user cannot edit settings', () => { + wrapper = mountComponent({ canEditProjectSettings: false }); + + expect(wrapper.text()).not.toContain(enableServiceDesk); + expect(findEnableSDButton().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/service_desk/components/service_desk_list_app_spec.js b/spec/frontend/service_desk/components/service_desk_list_app_spec.js index 14132ae1830..2ac789745aa 100644 --- a/spec/frontend/service_desk/components/service_desk_list_app_spec.js +++ b/spec/frontend/service_desk/components/service_desk_list_app_spec.js @@ -10,6 +10,7 @@ import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants'; import getServiceDeskIssuesQuery from '~/service_desk/queries/get_service_desk_issues.query.graphql'; import getServiceDeskIssuesCountsQuery from '~/service_desk/queries/get_service_desk_issues_counts.query.graphql'; import ServiceDeskListApp from '~/service_desk/components/service_desk_list_app.vue'; +import InfoBanner from '~/service_desk/components/info_banner.vue'; import { getServiceDeskIssuesQueryResponse, getServiceDeskIssuesCountsQueryResponse, @@ -27,6 +28,8 @@ describe('ServiceDeskListApp', () => { isProject: true, isSignedIn: true, fullPath: 'path/to/project', + isServiceDeskSupported: true, + hasAnyIssues: true, }; const defaultQueryResponse = getServiceDeskIssuesQueryResponse; @@ -37,6 +40,7 @@ describe('ServiceDeskListApp', () => { .mockResolvedValue(getServiceDeskIssuesCountsQueryResponse); const findIssuableList = () => wrapper.findComponent(IssuableList); + const findInfoBanner = () => wrapper.findComponent(InfoBanner); const mountComponent = ({ provide = {}, @@ -98,7 +102,20 @@ describe('ServiceDeskListApp', () => { }); }); - describe('events', () => { + describe('InfoBanner', () => { + it('renders when Service Desk is supported and has any number of issues', () => { + expect(findInfoBanner().exists()).toBe(true); + }); + + it('does not render, when there are no issues', async () => { + wrapper = mountComponent({ provide: { hasAnyIssues: false } }); + await waitForPromises(); + + expect(findInfoBanner().exists()).toBe(false); + }); + }); + + describe('Events', () => { describe('when "click-tab" event is emitted by IssuableList', () => { beforeEach(() => { mountComponent(); @@ -112,7 +129,7 @@ describe('ServiceDeskListApp', () => { }); }); - describe('errors', () => { + describe('Errors', () => { describe.each` error | mountOption | message ${'fetching issues'} | ${'serviceDeskIssuesQueryResponse'} | ${ServiceDeskListApp.i18n.errorFetchingIssues} |