diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-20 14:18:08 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-20 14:18:08 +0300 |
commit | 5afcbe03ead9ada87621888a31a62652b10a7e4f (patch) | |
tree | 9918b67a0d0f0bafa6542e839a8be37adf73102d /app/assets/javascripts/issues/show | |
parent | c97c0201564848c1f53226fe19d71fdcc472f7d0 (diff) |
Add latest changes from gitlab-org/gitlab@16-4-stable-eev16.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/issues/show')
9 files changed, 284 insertions, 390 deletions
diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue index 26c3db647a3..d59692d2a28 100644 --- a/app/assets/javascripts/issues/show/components/app.vue +++ b/app/assets/javascripts/issues/show/components/app.vue @@ -1,49 +1,36 @@ <script> -import { GlIcon, GlBadge, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui'; import Visibility from 'visibilityjs'; import { createAlert } from '~/alert'; -import { - issuableStatusText, - STATUS_CLOSED, - TYPE_EPIC, - TYPE_INCIDENT, - TYPE_ISSUE, - WORKSPACE_PROJECT, -} from '~/issues/constants'; +import { TYPE_EPIC, TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants'; +import updateDescription from '~/issues/show/utils/update_description'; +import { sanitize } from '~/lib/dompurify'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import Poll from '~/lib/utils/poll'; +import { containsSensitiveToken, confirmSensitiveAction, i18n } from '~/lib/utils/secret_detection'; 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 { ISSUE_TYPE_PATH, INCIDENT_TYPE_PATH, POLLING_DELAY } from '../constants'; import eventHub from '../event_hub'; import getIssueStateQuery from '../queries/get_issue_state.query.graphql'; import Service from '../services/index'; -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 StickyHeader from './sticky_header.vue'; import TitleComponent from './title.vue'; export default { - WORKSPACE_PROJECT, components: { - GlIcon, - GlBadge, - GlIntersectionObserver, HeaderActions, IssueHeader, TitleComponent, EditedComponent, FormComponent, PinnedLinks, - ConfidentialityBadge, - }, - directives: { - GlTooltip: GlTooltipDirective, + StickyHeader, }, props: { author: { @@ -234,21 +221,26 @@ export default { }, }, data() { - const store = new Store({ - titleHtml: this.initialTitleHtml, - titleText: this.initialTitleText, - descriptionHtml: this.initialDescriptionHtml, - descriptionText: this.initialDescriptionText, - updatedAt: this.updatedAt, - updatedByName: this.updatedByName, - updatedByPath: this.updatedByPath, - taskCompletionStatus: this.initialTaskCompletionStatus, - lock_version: this.lockVersion, - }); - return { - store, - state: store.state, + formState: { + title: '', + description: '', + lockedWarningVisible: false, + updateLoading: false, + lock_version: 0, + issuableTemplates: {}, + }, + state: { + titleHtml: this.initialTitleHtml, + titleText: this.initialTitleText, + descriptionHtml: this.initialDescriptionHtml, + descriptionText: this.initialDescriptionText, + updatedAt: this.updatedAt, + updatedByName: this.updatedByName, + updatedByPath: this.updatedByPath, + taskCompletionStatus: this.initialTaskCompletionStatus, + lock_version: this.lockVersion, + }, showForm: false, templatesRequested: false, isStickyHeaderShowing: false, @@ -264,17 +256,9 @@ export default { headerClasses() { return this.issuableType === TYPE_INCIDENT ? 'gl-mb-3' : 'gl-mb-6'; }, - issuableTemplates() { - return this.store.formState.issuableTemplates; - }, - formState() { - return this.store.formState; - }, issueChanged() { const { - store: { - formState: { description, title }, - }, + formState: { description, title }, initialDescriptionText, initialTitleText, } = this; @@ -292,26 +276,13 @@ export default { defaultErrorMessage() { return sprintf(__('Error updating %{issuableType}'), { issuableType: this.issuableType }); }, - isClosed() { - return this.issuableStatus === STATUS_CLOSED; - }, + pinnedLinkClasses() { return this.showTitleBorder ? 'gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-mb-6' : ''; }, - statusIcon() { - if (this.issuableType === TYPE_EPIC) { - return this.isClosed ? 'epic-closed' : 'epic'; - } - return this.isClosed ? 'issue-closed' : 'issues'; - }, - statusVariant() { - return this.isClosed ? 'info' : 'success'; - }, - statusText() { - return issuableStatusText[this.issuableStatus]; - }, + shouldShowStickyHeader() { return [TYPE_INCIDENT, TYPE_ISSUE, TYPE_EPIC].includes(this.issuableType); }, @@ -322,7 +293,7 @@ export default { this.poll = new Poll({ resource: this.service, method: 'getData', - successCallback: (res) => this.store.updateState(res.data), + successCallback: (res) => this.updateState(res.data), errorCallback(err) { throw new Error(err); }, @@ -360,23 +331,37 @@ export default { } return undefined; }, + updateState(data) { + const stateShouldUpdate = + this.state.titleText !== data.title_text || + this.state.descriptionText !== data.description_text; - updateStoreState() { + if (stateShouldUpdate) { + this.formState.lockedWarningVisible = true; + } + + Object.assign(this.state, convertObjectPropsToCamelCase(data)); + // find if there is an open details node inside of the issue description. + const descriptionSection = document.body.querySelector( + '.detail-page-description.content-block', + ); + const details = + descriptionSection != null && descriptionSection.getElementsByTagName('details'); + + this.state.descriptionHtml = updateDescription(sanitize(data.description), details); + this.state.titleHtml = sanitize(data.title); + this.state.lock_version = data.lock_version; + }, + refetchData() { return this.service .getData() .then((res) => res.data) - .then((data) => { - this.store.updateState(data); - }) - .catch(() => { - createAlert({ - message: this.defaultErrorMessage, - }); - }); + .then(this.updateState) + .catch(() => createAlert({ message: this.defaultErrorMessage })); }, setFormState(state) { - this.store.setFormState(state); + this.formState = { ...this.formState, ...state }; }, updateFormState(templates = {}) { @@ -416,7 +401,7 @@ export default { this.templatesRequested = true; this.requestTemplatesAndShowForm(); } else { - this.updateAndShowForm(this.issuableTemplates); + this.updateAndShowForm(this.formState.issuableTemplates); } }, @@ -427,10 +412,7 @@ export default { async updateIssuable() { this.setFormState({ updateLoading: true }); - const { - store: { formState }, - issueState, - } = this; + const { formState, issueState } = this; const issuablePayload = issueState.isDirty ? { ...formState, issue_type: issueState.issueType } : formState; @@ -464,7 +446,7 @@ export default { visitUrl(URI); } }) - .then(this.updateStoreState) + .then(this.refetchData) .then(() => { eventHub.$emit('close.form'); }) @@ -518,7 +500,7 @@ export default { this.poll.enable(); this.poll.makeDelayedRequest(POLLING_DELAY); - this.updateStoreState(); + this.refetchData(); }, }, }; @@ -531,7 +513,7 @@ export default { :endpoint="endpoint" :form-state="formState" :initial-description-text="initialDescriptionText" - :issuable-templates="issuableTemplates" + :issuable-templates="formState.issuableTemplates" :markdown-docs-path="markdownDocsPath" :markdown-preview-path="markdownPreviewPath" :project-path="projectPath" @@ -559,61 +541,19 @@ export default { </template> </title-component> - <gl-intersection-observer + <sticky-header v-if="shouldShowStickyHeader" - @appear="hideStickyHeader" - @disappear="showStickyHeader" - > - <transition name="issuable-header-slide"> - <div - v-if="isStickyHeaderShowing" - class="issue-sticky-header gl-fixed gl-z-index-3 gl-bg-white gl-border-1 gl-border-b-solid gl-border-b-gray-100 gl-py-3" - data-testid="issue-sticky-header" - > - <div - class="issue-sticky-header-text gl-display-flex gl-align-items-center gl-mx-auto gl-px-5" - > - <gl-badge :variant="statusVariant" class="gl-mr-2"> - <gl-icon :name="statusIcon" /> - <span class="gl-display-none gl-sm-display-block gl-ml-2">{{ - statusText - }}</span></gl-badge - > - <span - v-if="isLocked" - v-gl-tooltip.bottom - data-testid="locked" - class="issuable-warning-icon" - :title="__('This issue is locked. Only project members can comment.')" - > - <gl-icon name="lock" :aria-label="__('Locked')" /> - </span> - <confidentiality-badge - v-if="isConfidential" - data-testid="confidential" - :workspace-type="$options.WORKSPACE_PROJECT" - :issuable-type="issuableType" - /> - <span - v-if="isHidden" - v-gl-tooltip.bottom - :title="__('This issue is hidden because its author has been banned')" - data-testid="hidden" - class="issuable-warning-icon" - > - <gl-icon name="spam" /> - </span> - <a - href="#top" - class="gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0 gl-text-black-normal" - :title="state.titleText" - > - {{ state.titleText }} - </a> - </div> - </div> - </transition> - </gl-intersection-observer> + :is-confidential="isConfidential" + :is-hidden="isHidden" + :is-locked="isLocked" + :issuable-status="issuableStatus" + :issuable-type="issuableType" + :show="isStickyHeaderShowing" + :title="state.titleText" + :title-html="state.titleHtml" + @hide="hideStickyHeader" + @show="showStickyHeader" + /> <slot name="header"> <issue-header diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue index 90f01603f96..acbba216601 100644 --- a/app/assets/javascripts/issues/show/components/description.vue +++ b/app/assets/javascripts/issues/show/components/description.vue @@ -307,7 +307,8 @@ export default { ); taskListItems?.forEach((item) => { - const dropdown = this.createTaskListItemActions({ canUpdate: this.canUpdate }); + const provide = { canUpdate: this.canUpdate, issuableType: this.issuableType }; + const dropdown = this.createTaskListItemActions(provide); this.insertNextToTaskListItemText(dropdown, item); this.addPointerEventListeners(item, '.task-list-item-actions'); this.hasTaskListItemActions = true; diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue index 1ade5e654e9..81e5c30a264 100644 --- a/app/assets/javascripts/issues/show/components/header_actions.vue +++ b/app/assets/javascripts/issues/show/components/header_actions.vue @@ -79,62 +79,25 @@ export default { GlTooltip: GlTooltipDirective, }, mixins: [trackingMixin, glFeatureFlagMixin()], - inject: { - canCreateIssue: { - default: false, - }, - canDestroyIssue: { - default: false, - }, - canPromoteToEpic: { - default: false, - }, - canReopenIssue: { - default: false, - }, - canReportSpam: { - default: false, - }, - canUpdateIssue: { - default: false, - }, - iid: { - default: '', - }, - issuableId: { - default: '', - }, - isIssueAuthor: { - default: false, - }, - issuePath: { - default: '', - }, - issueType: { - default: TYPE_ISSUE, - }, - newIssuePath: { - default: '', - }, - projectPath: { - default: '', - }, - submitAsSpamPath: { - default: '', - }, - reportedUserId: { - default: '', - }, - reportedFromUrl: { - default: '', - }, - issuableEmailAddress: { - default: '', - }, - fullPath: { - default: '', - }, - }, + inject: [ + 'canCreateIssue', + 'canDestroyIssue', + 'canPromoteToEpic', + 'canReopenIssue', + 'canReportSpam', + 'canUpdateIssue', + 'iid', + 'isIssueAuthor', + 'issuePath', + 'issueType', + 'newIssuePath', + 'projectPath', + 'submitAsSpamPath', + 'reportedUserId', + 'reportedFromUrl', + 'issuableEmailAddress', + 'fullPath', + ], data() { return { isReportAbuseDrawerOpen: false, @@ -256,7 +219,7 @@ export default { mutation: updateIssueMutation, variables: { input: { - iid: this.iid.toString(), + iid: String(this.iid), projectPath: this.projectPath, stateEvent: this.isClosed ? ISSUE_STATE_EVENT_REOPEN : ISSUE_STATE_EVENT_CLOSE, }, @@ -501,7 +464,7 @@ export default { >{{ copyMailAddressText }}</gl-dropdown-item > </template> - <gl-dropdown-divider v-if="showToggleIssueStateButton || canDestroyIssue || canReportSpam" /> + <gl-dropdown-divider v-if="canDestroyIssue || canReportSpam || !isIssueAuthor" /> <gl-dropdown-item v-if="canReportSpam" :href="submitAsSpamPath" diff --git a/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue b/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue index ac64c35bf15..ab1bb9253f4 100644 --- a/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue +++ b/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue @@ -107,10 +107,7 @@ export default { </script> <template> - <div - class="create-timeline-event gl-relative gl-display-flex gl-align-items-start" - :class="{ 'timeline-entry-vertical-line': hasTimelineEvents }" - > + <div class="create-timeline-event gl-relative gl-display-flex gl-align-items-start"> <div v-if="hasTimelineEvents" class="gl-display-flex gl-align-items-center gl-justify-content-center gl-align-self-start gl-bg-white gl-text-gray-200 gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-full gl-mt-2 gl-w-8 gl-h-8 gl-flex-shrink-0 gl-p-3 gl-z-index-1" diff --git a/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue b/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue index 4ec64ef838d..2909a4d2666 100644 --- a/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue +++ b/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue @@ -43,7 +43,7 @@ export default { variables() { return { fullPath: this.fullPath, - iid: this.iid, + iid: String(this.iid), }; }, update(data) { diff --git a/app/assets/javascripts/issues/show/components/sticky_header.vue b/app/assets/javascripts/issues/show/components/sticky_header.vue new file mode 100644 index 00000000000..bcf10ee92bb --- /dev/null +++ b/app/assets/javascripts/issues/show/components/sticky_header.vue @@ -0,0 +1,130 @@ +<script> +import { GlBadge, GlIcon, GlIntersectionObserver, GlTooltipDirective } from '@gitlab/ui'; +import { + issuableStatusText, + STATUS_CLOSED, + TYPE_EPIC, + WORKSPACE_PROJECT, +} from '~/issues/constants'; +import SafeHtml from '~/vue_shared/directives/safe_html'; +import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue'; + +export default { + WORKSPACE_PROJECT, + components: { + ConfidentialityBadge, + GlBadge, + GlIcon, + GlIntersectionObserver, + }, + directives: { + GlTooltip: GlTooltipDirective, + SafeHtml, + }, + props: { + isConfidential: { + type: Boolean, + required: false, + default: false, + }, + isHidden: { + type: Boolean, + required: false, + default: false, + }, + isLocked: { + type: Boolean, + required: false, + default: false, + }, + issuableStatus: { + type: String, + required: true, + }, + issuableType: { + type: String, + required: true, + }, + show: { + type: Boolean, + required: false, + default: false, + }, + title: { + type: String, + required: true, + }, + titleHtml: { + type: String, + required: true, + }, + }, + computed: { + isClosed() { + return this.issuableStatus === STATUS_CLOSED; + }, + statusIcon() { + if (this.issuableType === TYPE_EPIC) { + return this.isClosed ? 'epic-closed' : 'epic'; + } + return this.isClosed ? 'issue-closed' : 'issues'; + }, + statusText() { + return issuableStatusText[this.issuableStatus]; + }, + statusVariant() { + return this.isClosed ? 'info' : 'success'; + }, + }, +}; +</script> + +<template> + <gl-intersection-observer @appear="$emit('hide')" @disappear="$emit('show')"> + <transition name="issuable-header-slide"> + <div + v-if="show" + class="issue-sticky-header gl-fixed gl-z-index-3 gl-bg-white gl-border-1 gl-border-b-solid gl-border-b-gray-100 gl-py-3" + data-testid="issue-sticky-header" + > + <div + class="issue-sticky-header-text gl-display-flex gl-align-items-center gl-gap-2 gl-mx-auto gl-px-5" + > + <gl-badge :variant="statusVariant"> + <gl-icon :name="statusIcon" /> + <span class="gl-display-none gl-sm-display-block gl-ml-2">{{ statusText }}</span> + </gl-badge> + <span + v-if="isLocked" + v-gl-tooltip.bottom + data-testid="locked" + class="issuable-warning-icon" + :title="__('This issue is locked. Only project members can comment.')" + > + <gl-icon name="lock" :aria-label="__('Locked')" /> + </span> + <confidentiality-badge + v-if="isConfidential" + :issuable-type="issuableType" + :workspace-type="$options.WORKSPACE_PROJECT" + /> + <span + v-if="isHidden" + v-gl-tooltip.bottom + :title="__('This issue is hidden because its author has been banned')" + data-testid="hidden" + class="issuable-warning-icon" + > + <gl-icon name="spam" /> + </span> + <a + v-safe-html="titleHtml || title" + href="#top" + class="gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0 gl-text-black-normal" + > + </a> + </div> + </div> + </transition> + </gl-intersection-observer> +</template> diff --git a/app/assets/javascripts/issues/show/components/task_list_item_actions.vue b/app/assets/javascripts/issues/show/components/task_list_item_actions.vue index 64b916caddb..55e2e857050 100644 --- a/app/assets/javascripts/issues/show/components/task_list_item_actions.vue +++ b/app/assets/javascripts/issues/show/components/task_list_item_actions.vue @@ -1,5 +1,6 @@ <script> import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui'; +import { TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants'; import { __, s__ } from '~/locale'; import eventHub from '../event_hub'; @@ -13,7 +14,12 @@ export default { GlDisclosureDropdown, GlDisclosureDropdownItem, }, - inject: ['canUpdate'], + inject: ['canUpdate', 'issuableType'], + computed: { + showConvertToTaskItem() { + return [TYPE_INCIDENT, TYPE_ISSUE].includes(this.issuableType); + }, + }, methods: { convertToTask() { eventHub.$emit('convert-task-list-item', this.$el.closest('li').dataset.sourcepos); @@ -37,12 +43,17 @@ export default { text-sr-only toggle-class="task-list-item-actions gl-opacity-0 gl-p-2! " > - <gl-disclosure-dropdown-item class="gl-ml-2!" @action="convertToTask"> + <gl-disclosure-dropdown-item + v-if="showConvertToTaskItem" + class="gl-ml-2!" + data-testid="convert" + @action="convertToTask" + > <template #list-item> {{ $options.i18n.convertToTask }} </template> </gl-disclosure-dropdown-item> - <gl-disclosure-dropdown-item class="gl-ml-2!" @action="deleteTaskListItem"> + <gl-disclosure-dropdown-item class="gl-ml-2!" data-testid="delete" @action="deleteTaskListItem"> <template #list-item> <span class="gl-text-red-500!">{{ $options.i18n.delete }}</span> </template> diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js index a27f86bd9c3..b94f88f690e 100644 --- a/app/assets/javascripts/issues/show/index.js +++ b/app/assets/javascripts/issues/show/index.js @@ -6,13 +6,15 @@ import { apolloProvider } from '~/graphql_shared/issuable_client'; import { TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants'; import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; import { scrollToTargetOnResize } from '~/lib/utils/resize_observer'; +import initLinkedResources from '~/linked_resources'; import IssueApp from './components/app.vue'; -import HeaderActions from './components/header_actions.vue'; +import DescriptionComponent from './components/description.vue'; import IncidentTabs from './components/incidents/incident_tabs.vue'; import SentryErrorStackTrace from './components/sentry_error_stack_trace.vue'; import { issueState } from './constants'; import getIssueStateQuery from './queries/get_issue_state.query.graphql'; import createRouter from './components/incidents/router'; +import { parseIssuableData } from './utils/parse_data'; const bootstrapApollo = (state = {}) => { return apolloProvider.clients.defaultClient.cache.writeQuery({ @@ -23,14 +25,15 @@ const bootstrapApollo = (state = {}) => { }); }; -export function initIncidentApp(issueData = {}, store) { +export function initIssuableApp(store) { const el = document.getElementById('js-issuable-app'); if (!el) { return undefined; } - bootstrapApollo({ ...issueState, issueType: TYPE_INCIDENT }); + const issuableData = parseIssuableData(el); + const headerActionsData = convertObjectPropsToCamelCase(JSON.parse(el.dataset.headerActionsData)); const { authorId, @@ -38,137 +41,72 @@ export function initIncidentApp(issueData = {}, store) { authorUsername, authorWebUrl, canCreateIncident, - canUpdate, - canUpdateTimelineEvent, + fullPath, iid, issuableId, + issueType, + hasIterationsFeature, + // for issue + registerPath, + signInPath, + // for incident + canUpdate, + canUpdateTimelineEvent, currentPath, currentTab, - projectNamespace, - projectPath, - projectId, hasLinkedAlerts, + projectId, slaFeatureAvailable, uploadMetricsFeatureAvailable, - } = issueData; - const headerActionsData = convertObjectPropsToCamelCase(JSON.parse(el.dataset.headerActionsData)); - - const fullPath = `${projectNamespace}/${projectPath}`; - const router = createRouter(currentPath, currentTab); - - return new Vue({ - el, - name: 'DescriptionRoot', - apolloProvider, - store, - router, - provide: { - issueType: TYPE_INCIDENT, - canCreateIncident, - canUpdateTimelineEvent, - canUpdate, - fullPath, - iid, - issuableId, - projectId, - hasLinkedAlerts: parseBoolean(hasLinkedAlerts), - slaFeatureAvailable: parseBoolean(slaFeatureAvailable), - uploadMetricsFeatureAvailable: parseBoolean(uploadMetricsFeatureAvailable), - contentEditorOnIssues: gon.features.contentEditorOnIssues, - // for HeaderActions component - canCreateIssue: parseBoolean(headerActionsData.canCreateIncident), - canDestroyIssue: parseBoolean(headerActionsData.canDestroyIssue), - canPromoteToEpic: parseBoolean(headerActionsData.canPromoteToEpic), - canReopenIssue: parseBoolean(headerActionsData.canReopenIssue), - canReportSpam: parseBoolean(headerActionsData.canReportSpam), - canUpdateIssue: parseBoolean(headerActionsData.canUpdateIssue), - isIssueAuthor: parseBoolean(headerActionsData.isIssueAuthor), - issuePath: headerActionsData.issuePath, - newIssuePath: headerActionsData.newIssuePath, - projectPath: headerActionsData.projectPath, - reportAbusePath: headerActionsData.reportAbusePath, - reportedUserId: headerActionsData.reportedUserId, - reportedFromUrl: headerActionsData.reportedFromUrl, - submitAsSpamPath: headerActionsData.submitAsSpamPath, - issuableEmailAddress: headerActionsData.issuableEmailAddress, - }, - computed: { - ...mapGetters(['getNoteableData']), - }, - render(createElement) { - return createElement(IssueApp, { - props: { - ...issueData, - author: { - id: authorId, - name: authorName, - username: authorUsername, - webUrl: authorWebUrl, - }, - issueId: Number(issuableId), - issuableStatus: this.getNoteableData?.state, - issuableType: TYPE_INCIDENT, - descriptionComponent: IncidentTabs, - showTitleBorder: false, - isConfidential: this.getNoteableData?.confidential, - }, - }); - }, - }); -} - -export function initIssueApp(issueData, store) { - const el = document.getElementById('js-issuable-app'); + } = issuableData; - if (!el) { - return undefined; - } + const issueProvideData = { registerPath, signInPath }; + const incidentProvideData = { + canUpdate, + canUpdateTimelineEvent, + hasLinkedAlerts: parseBoolean(hasLinkedAlerts), + projectId, + slaFeatureAvailable: parseBoolean(slaFeatureAvailable), + uploadMetricsFeatureAvailable: parseBoolean(uploadMetricsFeatureAvailable), + }; - const { fullPath, registerPath, signInPath } = el.dataset; - const headerActionsData = convertObjectPropsToCamelCase(JSON.parse(el.dataset.headerActionsData)); + bootstrapApollo({ ...issueState, issueType }); scrollToTargetOnResize(); - bootstrapApollo({ ...issueState, issueType: TYPE_ISSUE }); - - const { - authorId, - authorName, - authorUsername, - authorWebUrl, - canCreateIncident, - hasIssueWeightsFeature, - hasIterationsFeature, - ...issueProps - } = issueData; + if (issueType === TYPE_INCIDENT) { + initLinkedResources(); + } return new Vue({ el, name: 'DescriptionRoot', apolloProvider, store, + router: issueType === TYPE_INCIDENT ? createRouter(currentPath, currentTab) : undefined, provide: { canCreateIncident, fullPath, - registerPath, - signInPath, - hasIssueWeightsFeature, + iid, + issuableId, + issueType, hasIterationsFeature, + ...(issueType === TYPE_ISSUE && issueProvideData), + ...(issueType === TYPE_INCIDENT && incidentProvideData), // for HeaderActions component - canCreateIssue: parseBoolean(headerActionsData.canCreateIssue), + canCreateIssue: + issueType === TYPE_INCIDENT + ? parseBoolean(headerActionsData.canCreateIncident) + : 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, @@ -181,67 +119,27 @@ export function initIssueApp(issueData, store) { render(createElement) { return createElement(IssueApp, { props: { - ...issueProps, + ...issuableData, author: { id: authorId, name: authorName, username: authorUsername, webUrl: authorWebUrl, }, + descriptionComponent: issueType === TYPE_INCIDENT ? IncidentTabs : DescriptionComponent, isConfidential: this.getNoteableData?.confidential, isLocked: this.getNoteableData?.discussion_locked, issuableStatus: this.getNoteableData?.state, + issuableType: issueType, issueId: this.getNoteableData?.id, issueIid: this.getNoteableData?.iid, + showTitleBorder: issueType !== TYPE_INCIDENT, }, }); }, }); } -export function initHeaderActions(store, type = '') { - const el = document.querySelector('.js-issue-header-actions'); - - if (!el) { - return undefined; - } - - bootstrapApollo({ ...issueState, issueType: el.dataset.issueType }); - - const canCreate = - type === TYPE_INCIDENT ? el.dataset.canCreateIncident : el.dataset.canCreateIssue; - - return new Vue({ - el, - name: 'HeaderActionsRoot', - apolloProvider, - store, - provide: { - canCreateIssue: parseBoolean(canCreate), - canDestroyIssue: parseBoolean(el.dataset.canDestroyIssue), - canPromoteToEpic: parseBoolean(el.dataset.canPromoteToEpic), - canReopenIssue: parseBoolean(el.dataset.canReopenIssue), - canReportSpam: parseBoolean(el.dataset.canReportSpam), - canUpdateIssue: parseBoolean(el.dataset.canUpdateIssue), - iid: el.dataset.iid, - issuableId: el.dataset.issuableId, - isIssueAuthor: parseBoolean(el.dataset.isIssueAuthor), - issuePath: el.dataset.issuePath, - issueType: el.dataset.issueType, - newIssuePath: el.dataset.newIssuePath, - projectPath: el.dataset.projectPath, - projectId: el.dataset.projectId, - reportAbusePath: el.dataset.reportAbusePath, - reportedUserId: parseInt(el.dataset.reportedUserId, 10), - reportedFromUrl: el.dataset.reportedFromUrl, - submitAsSpamPath: el.dataset.submitAsSpamPath, - issuableEmailAddress: el.dataset.issuableEmailAddress, - fullPath: el.dataset.projectPath, - }, - render: (createElement) => createElement(HeaderActions), - }); -} - export function initSentryErrorStackTrace() { const el = document.querySelector('#js-sentry-error-stack-trace'); diff --git a/app/assets/javascripts/issues/show/stores/index.js b/app/assets/javascripts/issues/show/stores/index.js deleted file mode 100644 index a50913d3455..00000000000 --- a/app/assets/javascripts/issues/show/stores/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import { sanitize } from '~/lib/dompurify'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import updateDescription from '../utils/update_description'; - -export default class Store { - constructor(initialState) { - this.state = initialState; - this.formState = { - title: '', - description: '', - lockedWarningVisible: false, - updateLoading: false, - lock_version: 0, - issuableTemplates: {}, - }; - } - - updateState(data) { - if (this.stateShouldUpdate(data)) { - this.formState.lockedWarningVisible = true; - } - - Object.assign(this.state, convertObjectPropsToCamelCase(data)); - // find if there is an open details node inside of the issue description. - const descriptionSection = document.body.querySelector( - '.detail-page-description.content-block', - ); - const details = - descriptionSection != null && descriptionSection.getElementsByTagName('details'); - - this.state.descriptionHtml = updateDescription(sanitize(data.description), details); - this.state.titleHtml = sanitize(data.title); - this.state.lock_version = data.lock_version; - } - - stateShouldUpdate(data) { - return ( - this.state.titleText !== data.title_text || - this.state.descriptionText !== data.description_text - ); - } - - setFormState(state) { - this.formState = Object.assign(this.formState, state); - } -} |