diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-08 18:06:56 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-08 18:06:56 +0300 |
commit | bfb24e1685fb574d3144865da29a21b38cb52883 (patch) | |
tree | d694d329da73d9a312a6f819edaebebc3b081491 /app/assets/javascripts/issues | |
parent | e44c3e4832e43c77e9c29fad6e49f8d6066d7f5c (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/issues')
6 files changed, 239 insertions, 9 deletions
diff --git a/app/assets/javascripts/issues/index.js b/app/assets/javascripts/issues/index.js index 4d2df9e3602..e266966c665 100644 --- a/app/assets/javascripts/issues/index.js +++ b/app/assets/javascripts/issues/index.js @@ -63,7 +63,6 @@ export function initShow({ notesParams } = {}) { initRelatedIssues(TYPE_INCIDENT); } else { initIssueApp(issuableData, store); - initHeaderActions(store); } new Issue(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue index fcdf1f7741b..633f336b0c0 100644 --- a/app/assets/javascripts/issues/show/components/app.vue +++ b/app/assets/javascripts/issues/show/components/app.vue @@ -15,6 +15,7 @@ import { visitUrl } from '~/lib/utils/url_utility'; import { __, sprintf } from '~/locale'; import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; import { containsSensitiveToken, confirmSensitiveAction, i18n } from '~/lib/utils/secret_detection'; +import { WORK_ITEM_TYPE_VALUE_ISSUE } from '~/work_items/constants'; import { ISSUE_TYPE_PATH, INCIDENT_TYPE_PATH, POLLING_DELAY } from '../constants'; import eventHub from '../event_hub'; import getIssueStateQuery from '../queries/get_issue_state.query.graphql'; @@ -23,6 +24,8 @@ import Store from '../stores'; import DescriptionComponent from './description.vue'; import EditedComponent from './edited.vue'; import FormComponent from './form.vue'; +import HeaderActions from './header_actions.vue'; +import IssueHeader from './issue_header.vue'; import PinnedLinks from './pinned_links.vue'; import TitleComponent from './title.vue'; @@ -32,6 +35,8 @@ export default { GlIcon, GlBadge, GlIntersectionObserver, + HeaderActions, + IssueHeader, TitleComponent, EditedComponent, FormComponent, @@ -42,6 +47,11 @@ export default { GlTooltip: GlTooltipDirective, }, props: { + author: { + type: Object, + required: false, + default: () => ({}), + }, endpoint: { required: true, type: String, @@ -54,6 +64,11 @@ export default { required: true, type: Boolean, }, + createdAt: { + type: String, + required: false, + default: '', + }, enableAutocomplete: { type: Boolean, required: false, @@ -193,6 +208,36 @@ export default { required: false, default: null, }, + duplicatedToIssueUrl: { + type: String, + required: false, + default: '', + }, + movedToIssueUrl: { + type: String, + required: false, + default: '', + }, + promotedToEpicUrl: { + type: String, + required: false, + default: '', + }, + isFirstContribution: { + type: Boolean, + required: false, + default: false, + }, + serviceDeskReplyTo: { + type: String, + required: false, + default: '', + }, + workItemType: { + type: String, + required: false, + default: '', + }, }, data() { const store = new Store({ @@ -222,6 +267,9 @@ export default { }, }, computed: { + isIssue() { + return this.workItemType === WORK_ITEM_TYPE_VALUE_ISSUE; + }, issuableTemplates() { return this.store.formState.issuableTemplates; }, @@ -509,7 +557,13 @@ export default { :can-update="canUpdate" :title-html="state.titleHtml" :title-text="state.titleText" - /> + > + <template #actions> + <slot name="actions"> + <header-actions v-if="isIssue" /> + </slot> + </template> + </title-component> <gl-intersection-observer v-if="shouldShowStickyHeader" @@ -567,6 +621,23 @@ export default { </transition> </gl-intersection-observer> + <slot name="header"> + <issue-header + v-if="isIssue" + :author="author" + :confidential="isConfidential" + :created-at="createdAt" + :duplicated-to-issue-url="duplicatedToIssueUrl" + :is-first-contribution="isFirstContribution" + :is-hidden="isHidden" + :is-locked="isLocked" + :issuable-state="issuableStatus" + :moved-to-issue-url="movedToIssueUrl" + :promoted-to-epic-url="promotedToEpicUrl" + :service-desk-reply-to="serviceDeskReplyTo" + /> + </slot> + <pinned-links :zoom-meeting-url="zoomMeetingUrl" :published-incident-url="publishedIncidentUrl" diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue index 321b12a0050..d05311cb1db 100644 --- a/app/assets/javascripts/issues/show/components/header_actions.vue +++ b/app/assets/javascripts/issues/show/components/header_actions.vue @@ -146,7 +146,7 @@ export default { variables() { return { fullPath: this.fullPath, - iid: this.iid, + iid: String(this.iid), }; }, update(data) { @@ -289,7 +289,7 @@ export default { mutation: promoteToEpicMutation, variables: { input: { - iid: this.iid, + iid: String(this.iid), projectPath: this.projectPath, }, }, diff --git a/app/assets/javascripts/issues/show/components/issue_header.vue b/app/assets/javascripts/issues/show/components/issue_header.vue new file mode 100644 index 00000000000..771b438f0da --- /dev/null +++ b/app/assets/javascripts/issues/show/components/issue_header.vue @@ -0,0 +1,126 @@ +<script> +import { GlLink, GlSprintf } from '@gitlab/ui'; +import { STATUS_OPEN, STATUS_REOPENED, TYPE_ISSUE, WORKSPACE_PROJECT } from '~/issues/constants'; +import { __, s__ } from '~/locale'; +import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue'; + +export default { + TYPE_ISSUE, + WORKSPACE_PROJECT, + components: { + GlLink, + GlSprintf, + IssuableHeader, + }, + props: { + author: { + type: Object, + required: true, + }, + confidential: { + type: Boolean, + required: true, + }, + createdAt: { + type: String, + required: true, + }, + duplicatedToIssueUrl: { + type: String, + required: true, + }, + isFirstContribution: { + type: Boolean, + required: true, + }, + isHidden: { + type: Boolean, + required: true, + }, + isLocked: { + type: Boolean, + required: true, + }, + issuableState: { + type: String, + required: true, + }, + movedToIssueUrl: { + type: String, + required: true, + }, + promotedToEpicUrl: { + type: String, + required: true, + }, + serviceDeskReplyTo: { + type: String, + required: false, + default: '', + }, + }, + computed: { + closedStatusLink() { + return this.duplicatedToIssueUrl || this.movedToIssueUrl || this.promotedToEpicUrl; + }, + closedStatusText() { + if (this.duplicatedToIssueUrl) { + return s__('IssuableStatus|duplicated'); + } + if (this.movedToIssueUrl) { + return s__('IssuableStatus|moved'); + } + if (this.promotedToEpicUrl) { + return s__('IssuableStatus|promoted'); + } + return ''; + }, + isOpen() { + return this.issuableState === STATUS_OPEN || this.issuableState === STATUS_REOPENED; + }, + statusIcon() { + return this.isOpen ? 'issues' : 'issue-closed'; + }, + statusText() { + if (this.isOpen) { + return __('Open'); + } + if (this.closedStatusLink) { + return s__('IssuableStatus|Closed (%{link})'); + } + return s__('IssuableStatus|Closed'); + }, + }, +}; +</script> + +<template> + <issuable-header + class="gl-p-0 gl-mb-6 gl-mt-2 gl-sm-mt-0" + :author="author" + :blocked="isLocked" + :confidential="confidential" + :created-at="createdAt" + :is-first-contribution="isFirstContribution" + :is-hidden="isHidden" + :issuable-state="issuableState" + :issuable-type="$options.TYPE_ISSUE" + :service-desk-reply-to="serviceDeskReplyTo" + show-work-item-type-icon + :status-icon="statusIcon" + :workspace-type="$options.WORKSPACE_PROJECT" + > + <template #status-badge> + <gl-sprintf v-if="closedStatusLink" :message="statusText"> + <template #link> + <gl-link + class="gl-reset-color! gl-reset-font-size gl-text-decoration-underline" + :href="closedStatusLink" + >{{ closedStatusText }}</gl-link + > + </template> + </gl-sprintf> + <template v-else>{{ statusText }}</template> + </template> + </issuable-header> +</template> diff --git a/app/assets/javascripts/issues/show/components/title.vue b/app/assets/javascripts/issues/show/components/title.vue index 197a2594f4d..8c9e43b729b 100644 --- a/app/assets/javascripts/issues/show/components/title.vue +++ b/app/assets/javascripts/issues/show/components/title.vue @@ -53,16 +53,19 @@ export default { </script> <template> - <div class="title-container"> + <div + class="gl-display-flex gl-align-items-flex-start gl-flex-direction-column gl-sm-flex-direction-row gl-pt-3" + > <h1 v-safe-html="titleHtml" :class="{ 'issue-realtime-pre-pulse': preAnimation, 'issue-realtime-trigger-pulse': pulseAnimation, }" - class="title gl-font-size-h-display" + class="title gl-font-size-h-display gl-m-0!" data-testid="issue-title" dir="auto" ></h1> + <slot name="actions"></slot> </div> </template> diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js index bc4284457f6..41e6daea4c1 100644 --- a/app/assets/javascripts/issues/show/index.js +++ b/app/assets/javascripts/issues/show/index.js @@ -2,8 +2,8 @@ import Vue from 'vue'; import { mapGetters } from 'vuex'; import errorTrackingStore from '~/error_tracking/store'; import { apolloProvider } from '~/graphql_shared/issuable_client'; -import { TYPE_INCIDENT } from '~/issues/constants'; -import { parseBoolean } from '~/lib/utils/common_utils'; +import { TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants'; +import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; import { scrollToTargetOnResize } from '~/lib/utils/resize_observer'; import IssueApp from './components/app.vue'; import HeaderActions from './components/header_actions.vue'; @@ -97,12 +97,17 @@ export function initIssueApp(issueData, store) { } const { fullPath, registerPath, signInPath } = el.dataset; + const headerActionsData = convertObjectPropsToCamelCase(JSON.parse(el.dataset.headerActionsData)); scrollToTargetOnResize(); - bootstrapApollo({ ...issueState, issueType: el.dataset.issueType }); + bootstrapApollo({ ...issueState, issueType: TYPE_ISSUE }); const { + authorId, + authorName, + authorUsername, + authorWebUrl, canCreateIncident, hasIssueWeightsFeature, hasIterationsFeature, @@ -121,6 +126,26 @@ export function initIssueApp(issueData, store) { signInPath, hasIssueWeightsFeature, hasIterationsFeature, + // for HeaderActions component + canCreateIssue: parseBoolean(headerActionsData.canCreateIssue), + canDestroyIssue: parseBoolean(headerActionsData.canDestroyIssue), + canPromoteToEpic: parseBoolean(headerActionsData.canPromoteToEpic), + canReopenIssue: parseBoolean(headerActionsData.canReopenIssue), + canReportSpam: parseBoolean(headerActionsData.canReportSpam), + canUpdateIssue: parseBoolean(headerActionsData.canUpdateIssue), + iid: headerActionsData.iid, + issuableId: headerActionsData.issuableId, + isIssueAuthor: parseBoolean(headerActionsData.isIssueAuthor), + issuePath: headerActionsData.issuePath, + issueType: headerActionsData.issueType, + newIssuePath: headerActionsData.newIssuePath, + projectPath: headerActionsData.projectPath, + projectId: headerActionsData.projectId, + reportAbusePath: headerActionsData.reportAbusePath, + reportedUserId: headerActionsData.reportedUserId, + reportedFromUrl: headerActionsData.reportedFromUrl, + submitAsSpamPath: headerActionsData.submitAsSpamPath, + issuableEmailAddress: headerActionsData.issuableEmailAddress, }, computed: { ...mapGetters(['getNoteableData']), @@ -129,6 +154,12 @@ export function initIssueApp(issueData, store) { return createElement(IssueApp, { props: { ...issueProps, + author: { + id: authorId, + name: authorName, + username: authorUsername, + webUrl: authorWebUrl, + }, isConfidential: this.getNoteableData?.confidential, isLocked: this.getNoteableData?.discussion_locked, issuableStatus: this.getNoteableData?.state, |