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>2020-11-03 18:09:05 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-03 18:09:05 +0300
commitff8eb438401fc82b883fc4ae69626f0035b69236 (patch)
tree044a7195c0338c2b31d55dd21a5638068a5722ea /app/assets/javascripts/issue_show
parent2ac811ce685f906d3e54e78b23f61495c19ad595 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/issue_show')
-rw-r--r--app/assets/javascripts/issue_show/components/header_actions.vue187
-rw-r--r--app/assets/javascripts/issue_show/constants.js5
-rw-r--r--app/assets/javascripts/issue_show/issue.js39
-rw-r--r--app/assets/javascripts/issue_show/queries/update_issue.mutation.graphql5
4 files changed, 235 insertions, 1 deletions
diff --git a/app/assets/javascripts/issue_show/components/header_actions.vue b/app/assets/javascripts/issue_show/components/header_actions.vue
new file mode 100644
index 00000000000..76fc7d0cb47
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/header_actions.vue
@@ -0,0 +1,187 @@
+<script>
+import { GlButton, GlDropdown, GlDropdownItem, GlIcon, GlLink, GlModal } from '@gitlab/ui';
+import { mapGetters } from 'vuex';
+import createFlash from '~/flash';
+import { IssuableStatus, IssueStateEvent } from '~/issue_show/constants';
+import { __ } from '~/locale';
+import updateIssueMutation from '../queries/update_issue.mutation.graphql';
+
+export default {
+ components: {
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlIcon,
+ GlLink,
+ GlModal,
+ },
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: __('Yes, close issue'),
+ attributes: [{ variant: 'warning' }],
+ },
+ inject: [
+ 'canCreateIssue',
+ 'canReopenIssue',
+ 'canReportSpam',
+ 'canUpdateIssue',
+ 'iid',
+ 'isIssueAuthor',
+ 'newIssuePath',
+ 'projectPath',
+ 'reportAbusePath',
+ 'submitAsSpamPath',
+ ],
+ data() {
+ return {
+ isUpdatingState: false,
+ };
+ },
+ computed: {
+ ...mapGetters(['getNoteableData']),
+ isClosed() {
+ return this.getNoteableData.state === IssuableStatus.Closed;
+ },
+ buttonText() {
+ return this.isClosed ? __('Reopen issue') : __('Close issue');
+ },
+ buttonVariant() {
+ return this.isClosed ? 'default' : 'warning';
+ },
+ showToggleIssueButton() {
+ const canClose = !this.isClosed && this.canUpdateIssue;
+ const canReopen = this.isClosed && this.canReopenIssue;
+ return canClose || canReopen;
+ },
+ },
+ methods: {
+ toggleIssueState() {
+ if (!this.isClosed && this.getNoteableData?.blocked_by_issues?.length) {
+ this.$refs.blockedByIssuesModal.show();
+ return;
+ }
+
+ this.invokeUpdateIssueMutation();
+ },
+ invokeUpdateIssueMutation() {
+ this.isUpdatingState = true;
+
+ this.$apollo
+ .mutate({
+ mutation: updateIssueMutation,
+ variables: {
+ input: {
+ iid: this.iid.toString(),
+ projectPath: this.projectPath,
+ stateEvent: this.isClosed ? IssueStateEvent.Reopen : IssueStateEvent.Close,
+ },
+ },
+ })
+ .then(({ data }) => {
+ if (data.updateIssue.errors.length) {
+ createFlash(data.updateIssue.errors.join('. '));
+ return;
+ }
+
+ const payload = {
+ detail: {
+ data: { id: this.iid },
+ isClosed: !this.isClosed,
+ },
+ };
+
+ // Dispatch event which updates open/close state, shared among the issue show page
+ document.dispatchEvent(new CustomEvent('issuable_vue_app:change', payload));
+ })
+ .catch(() => createFlash(__('Update failed. Please try again.')))
+ .finally(() => {
+ this.isUpdatingState = false;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="detail-page-header-actions">
+ <gl-dropdown class="gl-display-block gl-display-sm-none!" block :text="__('Issue actions')">
+ <gl-dropdown-item
+ v-if="showToggleIssueButton"
+ :disabled="isUpdatingState"
+ @click="toggleIssueState"
+ >
+ {{ buttonText }}
+ </gl-dropdown-item>
+ <gl-dropdown-item v-if="canCreateIssue" :href="newIssuePath">
+ {{ __('New issue') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item v-if="!isIssueAuthor" :href="reportAbusePath">
+ {{ __('Report abuse') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item
+ v-if="canReportSpam"
+ :href="submitAsSpamPath"
+ data-method="post"
+ rel="nofollow"
+ >
+ {{ __('Submit as spam') }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+
+ <gl-button
+ v-if="showToggleIssueButton"
+ class="gl-display-none gl-display-sm-inline-flex!"
+ category="secondary"
+ :loading="isUpdatingState"
+ :variant="buttonVariant"
+ @click="toggleIssueState"
+ >
+ {{ buttonText }}
+ </gl-button>
+
+ <gl-dropdown
+ class="gl-display-none gl-display-sm-inline-flex!"
+ toggle-class="gl-border-0! gl-shadow-none!"
+ no-caret
+ right
+ >
+ <template #button-content>
+ <gl-icon name="ellipsis_v" aria-hidden="true" />
+ <span class="gl-sr-only">{{ __('Actions') }}</span>
+ </template>
+
+ <gl-dropdown-item v-if="canCreateIssue" :href="newIssuePath">
+ {{ __('New issue') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item v-if="!isIssueAuthor" :href="reportAbusePath">
+ {{ __('Report abuse') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item
+ v-if="canReportSpam"
+ :href="submitAsSpamPath"
+ data-method="post"
+ rel="nofollow"
+ >
+ {{ __('Submit as spam') }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+
+ <gl-modal
+ ref="blockedByIssuesModal"
+ modal-id="blocked-by-issues-modal"
+ :action-cancel="$options.actionCancel"
+ :action-primary="$options.actionPrimary"
+ :title="__('Are you sure you want to close this blocked issue?')"
+ @primary="invokeUpdateIssueMutation"
+ >
+ <p>{{ __('This issue is currently blocked by the following issues:') }}</p>
+ <ul>
+ <li v-for="issue in getNoteableData.blocked_by_issues" :key="issue.iid">
+ <gl-link :href="issue.web_url">#{{ issue.iid }}</gl-link>
+ </li>
+ </ul>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issue_show/constants.js b/app/assets/javascripts/issue_show/constants.js
index 576cf793717..a5ca91dffd4 100644
--- a/app/assets/javascripts/issue_show/constants.js
+++ b/app/assets/javascripts/issue_show/constants.js
@@ -18,5 +18,10 @@ export const IssuableType = {
MergeRequest: 'merge_request',
};
+export const IssueStateEvent = {
+ Close: 'CLOSE',
+ Reopen: 'REOPEN',
+};
+
export const STATUS_PAGE_PUBLISHED = __('Published on status page');
export const JOIN_ZOOM_MEETING = __('Join Zoom meeting');
diff --git a/app/assets/javascripts/issue_show/issue.js b/app/assets/javascripts/issue_show/issue.js
index 4af2577e33b..fc9e8e051bb 100644
--- a/app/assets/javascripts/issue_show/issue.js
+++ b/app/assets/javascripts/issue_show/issue.js
@@ -1,8 +1,12 @@
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import { mapGetters } from 'vuex';
+import createDefaultClient from '~/lib/graphql';
+import { parseBoolean } from '~/lib/utils/common_utils';
import IssuableApp from './components/app.vue';
+import HeaderActions from './components/header_actions.vue';
-export default function initIssuableApp(issuableData, store) {
+export function initIssuableApp(issuableData, store) {
return new Vue({
el: document.getElementById('js-issuable-app'),
store,
@@ -19,3 +23,36 @@ export default function initIssuableApp(issuableData, store) {
},
});
}
+
+export function initIssueHeaderActions(store) {
+ const el = document.querySelector('.js-issue-header-actions');
+
+ if (!el) {
+ return undefined;
+ }
+
+ Vue.use(VueApollo);
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ store,
+ provide: {
+ canCreateIssue: parseBoolean(el.dataset.canCreateIssue),
+ canReopenIssue: parseBoolean(el.dataset.canReopenIssue),
+ canReportSpam: parseBoolean(el.dataset.canReportSpam),
+ canUpdateIssue: parseBoolean(el.dataset.canUpdateIssue),
+ iid: el.dataset.iid,
+ isIssueAuthor: parseBoolean(el.dataset.isIssueAuthor),
+ newIssuePath: el.dataset.newIssuePath,
+ projectPath: el.dataset.projectPath,
+ reportAbusePath: el.dataset.reportAbusePath,
+ submitAsSpamPath: el.dataset.submitAsSpamPath,
+ },
+ render: createElement => createElement(HeaderActions),
+ });
+}
diff --git a/app/assets/javascripts/issue_show/queries/update_issue.mutation.graphql b/app/assets/javascripts/issue_show/queries/update_issue.mutation.graphql
new file mode 100644
index 00000000000..9c28fdded21
--- /dev/null
+++ b/app/assets/javascripts/issue_show/queries/update_issue.mutation.graphql
@@ -0,0 +1,5 @@
+mutation updateIssue($input: UpdateIssueInput!) {
+ updateIssue(input: $input) {
+ errors
+ }
+}