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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-07-11 06:09:14 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-11 06:09:14 +0300
commitf4b4020ba3e083a1f3a828da755366fbfbb900ce (patch)
tree45365634493c7ce6fc37e6d46b4f93c3cc2bbc28
parent1326fc930c76c78b4943c86b0b18b939004a07ee (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/images/service_desk_callout.svg1
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue102
-rw-r--r--app/assets/javascripts/service_desk/components/info_banner.vue64
-rw-r--r--app/assets/javascripts/service_desk/components/service_desk_list_app.vue21
-rw-r--r--app/assets/javascripts/service_desk/constants.js11
-rw-r--r--app/assets/javascripts/service_desk/graphql.js24
-rw-r--r--app/assets/javascripts/service_desk/index.js35
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/views/import/fogbugz/new.html.haml3
-rw-r--r--app/views/projects/issues/service_desk.html.haml10
-rw-r--r--doc/user/project/issue_board.md4
-rw-r--r--doc/user/project/issues/create_issues.md4
-rw-r--r--lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml2
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/features/boards/boards_spec.rb2
-rw-r--r--spec/features/boards/new_issue_spec.rb22
-rw-r--r--spec/features/groups/board_spec.rb2
-rw-r--r--spec/features/issues/service_desk_spec.rb17
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js23
-rw-r--r--spec/frontend/service_desk/components/info_banner_spec.js81
-rw-r--r--spec/frontend/service_desk/components/service_desk_list_app_spec.js21
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}