diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-18 08:17:02 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-18 08:17:02 +0000 |
commit | b39512ed755239198a9c294b6a45e65c05900235 (patch) | |
tree | d234a3efade1de67c46b9e5a38ce813627726aa7 /app/assets/javascripts/issues/show/components/incidents | |
parent | d31474cf3b17ece37939d20082b07f6657cc79a9 (diff) | |
download | gitlab-ce-15.3.0-rc42.tar.gz |
Add latest changes from gitlab-org/gitlab@15-3-stable-eev15.3.0-rc42
Diffstat (limited to 'app/assets/javascripts/issues/show/components/incidents')
-rw-r--r-- | app/assets/javascripts/issues/show/components/incidents/constants.js | 5 | ||||
-rw-r--r-- | app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue | 117 | ||||
-rw-r--r-- | app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue | 157 | ||||
-rw-r--r-- | app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue (renamed from app/assets/javascripts/issues/show/components/incidents/timeline_events_list_item.vue) | 12 | ||||
-rw-r--r-- | app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue | 27 | ||||
-rw-r--r-- | app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue | 15 | ||||
-rw-r--r-- | app/assets/javascripts/issues/show/components/incidents/utils.js | 1 |
7 files changed, 200 insertions, 134 deletions
diff --git a/app/assets/javascripts/issues/show/components/incidents/constants.js b/app/assets/javascripts/issues/show/components/incidents/constants.js index 9fc5027d457..77d13fe085a 100644 --- a/app/assets/javascripts/issues/show/components/incidents/constants.js +++ b/app/assets/javascripts/issues/show/components/incidents/constants.js @@ -1,4 +1,4 @@ -import { s__ } from '~/locale'; +import { __, s__ } from '~/locale'; export const timelineTabI18n = Object.freeze({ title: s__('Incident|Timeline'), @@ -12,6 +12,9 @@ export const timelineFormI18n = Object.freeze({ 'Incident|Something went wrong while creating the incident timeline event.', ), areaPlaceholder: s__('Incident|Timeline text...'), + save: __('Save'), + cancel: __('Cancel'), + description: __('Description'), saveAndAdd: s__('Incident|Save and add another event'), areaLabel: s__('Incident|Timeline text'), }); 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 new file mode 100644 index 00000000000..c902895702e --- /dev/null +++ b/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue @@ -0,0 +1,117 @@ +<script> +import { produce } from 'immer'; +import { sortBy } from 'lodash'; +import { sprintf } from '~/locale'; +import { createAlert } from '~/flash'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_ISSUE } from '~/graphql_shared/constants'; +import { timelineFormI18n } from './constants'; +import TimelineEventsForm from './timeline_events_form.vue'; + +import CreateTimelineEvent from './graphql/queries/create_timeline_event.mutation.graphql'; +import getTimelineEvents from './graphql/queries/get_timeline_events.query.graphql'; + +export default { + name: 'CreateTimelineEvent', + i18n: timelineFormI18n, + components: { + TimelineEventsForm, + }, + inject: ['fullPath', 'issuableId'], + props: { + hasTimelineEvents: { + type: Boolean, + required: true, + }, + }, + data() { + return { createTimelineEventActive: false }; + }, + methods: { + clearForm() { + this.$refs.eventForm.clear(); + }, + focusDate() { + this.$refs.eventForm.focusDate(); + }, + updateCache(store, { data }) { + const { timelineEvent: event, errors } = data?.timelineEventCreate || {}; + + if (errors.length) { + return; + } + + const variables = { + incidentId: convertToGraphQLId(TYPE_ISSUE, this.issuableId), + fullPath: this.fullPath, + }; + + const sourceData = store.readQuery({ + query: getTimelineEvents, + variables, + }); + + const newData = produce(sourceData, (draftData) => { + const { nodes: draftEventList } = draftData.project.incidentManagementTimelineEvents; + draftEventList.push(event); + // ISOStrings sort correctly in lexical order + const sortedEvents = sortBy(draftEventList, 'occurredAt'); + draftData.project.incidentManagementTimelineEvents.nodes = sortedEvents; + }); + + store.writeQuery({ + query: getTimelineEvents, + variables, + data: newData, + }); + }, + createIncidentTimelineEvent(eventDetails, addAnotherEvent = false) { + this.createTimelineEventActive = true; + return this.$apollo + .mutate({ + mutation: CreateTimelineEvent, + variables: { + input: { + incidentId: convertToGraphQLId(TYPE_ISSUE, this.issuableId), + note: eventDetails.note, + occurredAt: eventDetails.occurredAt, + }, + }, + update: this.updateCache, + }) + .then(({ data = {} }) => { + this.createTimelineEventActive = false; + const errors = data.timelineEventCreate?.errors; + if (errors.length) { + createAlert({ + message: sprintf(this.$options.i18n.createError, { error: errors.join('. ') }, false), + }); + return; + } + if (addAnotherEvent) { + this.$refs.eventForm.clear(); + } else { + this.$emit('hide-new-timeline-events-form'); + } + }) + .catch((error) => { + createAlert({ + message: this.$options.i18n.createErrorGeneric, + captureError: true, + error, + }); + }); + }, + }, +}; +</script> + +<template> + <timeline-events-form + ref="eventForm" + :is-event-processed="createTimelineEventActive" + :has-timeline-events="hasTimelineEvents" + @save-event="createIncidentTimelineEvent" + @cancel="$emit('hide-new-timeline-events-form')" + /> +</template> diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue index 36ec6362a22..0d84fabb1be 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue @@ -1,21 +1,12 @@ <script> import { GlDatepicker, GlFormInput, GlFormGroup, GlButton, GlIcon } from '@gitlab/ui'; -import { produce } from 'immer'; -import { sortBy } from 'lodash'; -import { convertToGraphQLId } from '~/graphql_shared/utils'; -import { TYPE_ISSUE } from '~/graphql_shared/constants'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; -import { createAlert } from '~/flash'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; -import { sprintf } from '~/locale'; -import { getUtcShiftedDateNow } from './utils'; import { timelineFormI18n } from './constants'; - -import CreateTimelineEvent from './graphql/queries/create_timeline_event.mutation.graphql'; -import getTimelineEvents from './graphql/queries/get_timeline_events.query.graphql'; +import { getUtcShiftedDateNow } from './utils'; export default { - name: 'IncidentTimelineEventForm', + name: 'TimelineEventsForm', restrictedToolBarItems: [ 'quote', 'strikethrough', @@ -38,112 +29,55 @@ export default { directives: { autofocusonshow, }, - inject: ['fullPath', 'issuableId'], props: { hasTimelineEvents: { type: Boolean, required: true, }, + isEventProcessed: { + type: Boolean, + required: true, + }, }, data() { - // Create shifted date to force the datepicker to format in UTC - const utcShiftedDate = getUtcShiftedDateNow(); + // if occurredAt is undefined, returns "now" in UTC + const placeholderDate = getUtcShiftedDateNow(); + return { - currentDate: utcShiftedDate, - currentHour: utcShiftedDate.getHours(), - currentMinute: utcShiftedDate.getMinutes(), timelineText: '', - createTimelineEventActive: false, + placeholderDate, + hourPickerInput: placeholderDate.getHours(), + minutePickerInput: placeholderDate.getMinutes(), datepickerTextInput: null, }; }, + computed: { + occurredAt() { + const [years, months, days] = this.datepickerTextInput.split('-'); + const utcDate = new Date( + Date.UTC(years, months - 1, days, this.hourPickerInput, this.minutePickerInput), + ); + + return utcDate.toISOString(); + }, + }, methods: { clear() { - const utcShiftedDate = getUtcShiftedDateNow(); - this.currentDate = utcShiftedDate; - this.currentHour = utcShiftedDate.getHours(); - this.currentMinute = utcShiftedDate.getMinutes(); - }, - hideIncidentTimelineEventForm() { - this.$emit('hide-incident-timeline-event-form'); + const utcShiftedDateNow = getUtcShiftedDateNow(); + this.placeholderDate = utcShiftedDateNow; + this.hourPickerInput = utcShiftedDateNow.getHours(); + this.minutePickerInput = utcShiftedDateNow.getMinutes(); + this.timelineText = ''; }, focusDate() { this.$refs.datepicker.$el.focus(); }, - updateCache(store, { data }) { - const { timelineEvent: event, errors } = data?.timelineEventCreate || {}; - - if (errors.length) { - return; - } - - const variables = { - incidentId: convertToGraphQLId(TYPE_ISSUE, this.issuableId), - fullPath: this.fullPath, + handleSave(addAnotherEvent) { + const eventDetails = { + note: this.timelineText, + occurredAt: this.occurredAt, }; - - const sourceData = store.readQuery({ - query: getTimelineEvents, - variables, - }); - - const newData = produce(sourceData, (draftData) => { - const { nodes: draftEventList } = draftData.project.incidentManagementTimelineEvents; - draftEventList.push(event); - // ISOStrings sort correctly in lexical order - const sortedEvents = sortBy(draftEventList, 'occurredAt'); - draftData.project.incidentManagementTimelineEvents.nodes = sortedEvents; - }); - - store.writeQuery({ - query: getTimelineEvents, - variables, - data: newData, - }); - }, - createIncidentTimelineEvent(addOneEvent) { - this.createTimelineEventActive = true; - return this.$apollo - .mutate({ - mutation: CreateTimelineEvent, - variables: { - input: { - incidentId: convertToGraphQLId(TYPE_ISSUE, this.issuableId), - note: this.timelineText, - occurredAt: this.createDateString(), - }, - }, - update: this.updateCache, - }) - .then(({ data = {} }) => { - const errors = data.timelineEventCreate?.errors; - if (errors.length) { - createAlert({ - message: sprintf(this.$options.i18n.createError, { error: errors.join('. ') }, false), - }); - } - }) - .catch((error) => { - createAlert({ - message: this.$options.i18n.createErrorGeneric, - captureError: true, - error, - }); - }) - .finally(() => { - this.createTimelineEventActive = false; - this.timelineText = ''; - if (addOneEvent) { - this.hideIncidentTimelineEventForm(); - } - }); - }, - createDateString() { - const [years, months, days] = this.datepickerTextInput.split('-'); - const utcDate = new Date( - Date.UTC(years, months - 1, days, this.currentHour, this.currentMinute), - ); - return utcDate.toISOString(); + this.$emit('save-event', eventDetails, addAnotherEvent); }, }, }; @@ -165,7 +99,7 @@ export default { class="gl-display-flex gl-flex-direction-column gl-sm-flex-direction-row datetime-picker" > <gl-form-group :label="__('Date')" class="gl-mt-5 gl-mr-5"> - <gl-datepicker id="incident-date" #default="{ formattedDate }" v-model="currentDate"> + <gl-datepicker id="incident-date" #default="{ formattedDate }" v-model="placeholderDate"> <gl-form-input id="incident-date" ref="datepicker" @@ -184,7 +118,7 @@ export default { <label label-for="timeline-input-hours" class="sr-only"></label> <gl-form-input id="timeline-input-hours" - v-model="currentHour" + v-model="hourPickerInput" data-testid="input-hours" size="xs" type="number" @@ -194,7 +128,7 @@ export default { <label label-for="timeline-input-minutes" class="sr-only"></label> <gl-form-input id="timeline-input-minutes" - v-model="currentMinute" + v-model="minutePickerInput" class="gl-ml-3" data-testid="input-minutes" size="xs" @@ -223,9 +157,10 @@ export default { <textarea v-model="timelineText" class="note-textarea js-gfm-input js-autosize markdown-area" + data-testid="input-note" dir="auto" data-supports-quick-actions="false" - :aria-label="__('Description')" + :aria-label="$options.i18n.description" :placeholder="$options.i18n.areaPlaceholder" > </textarea> @@ -238,26 +173,22 @@ export default { variant="confirm" category="primary" class="gl-mr-3" - :loading="createTimelineEventActive" - @click="createIncidentTimelineEvent(true)" + :loading="isEventProcessed" + @click="handleSave(false)" > - {{ __('Save') }} + {{ $options.i18n.save }} </gl-button> <gl-button variant="confirm" category="secondary" class="gl-mr-3 gl-ml-n2" - :loading="createTimelineEventActive" - @click="createIncidentTimelineEvent(false)" + :loading="isEventProcessed" + @click="handleSave(true)" > {{ $options.i18n.saveAndAdd }} </gl-button> - <gl-button - class="gl-ml-n2" - :disabled="createTimelineEventActive" - @click="hideIncidentTimelineEventForm" - > - {{ __('Cancel') }} + <gl-button class="gl-ml-n2" :disabled="isEventProcessed" @click="$emit('cancel')"> + {{ $options.i18n.cancel }} </gl-button> <div class="gl-border-b gl-pt-5"></div> </gl-form-group> diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list_item.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue index 62ccd696ef6..6175c9969ec 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list_item.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue @@ -1,5 +1,12 @@ <script> -import { GlDropdown, GlDropdownItem, GlIcon, GlSafeHtmlDirective, GlSprintf } from '@gitlab/ui'; +import { + GlButton, + GlDropdown, + GlDropdownItem, + GlIcon, + GlSafeHtmlDirective, + GlSprintf, +} from '@gitlab/ui'; import { formatDate } from '~/lib/utils/datetime_utility'; import { __ } from '~/locale'; import { getEventIcon } from './utils'; @@ -12,6 +19,7 @@ export default { timeUTC: __('%{time} UTC'), }, components: { + GlButton, GlDropdown, GlDropdownItem, GlIcon, @@ -83,7 +91,7 @@ export default { no-caret > <gl-dropdown-item @click="$emit('delete')"> - {{ $options.i18n.delete }} + <gl-button>{{ $options.i18n.delete }}</gl-button> </gl-dropdown-item> </gl-dropdown> </div> diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue index 519c0d402a0..80ac1c372cd 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue @@ -4,7 +4,7 @@ import { createAlert } from '~/flash'; import { sprintf } from '~/locale'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import { ignoreWhilePending } from '~/lib/utils/ignore_while_pending'; -import IncidentTimelineEventListItem from './timeline_events_list_item.vue'; +import IncidentTimelineEventItem from './timeline_events_item.vue'; import deleteTimelineEvent from './graphql/queries/delete_timeline_event.mutation.graphql'; import { timelineListI18n } from './constants'; @@ -12,7 +12,7 @@ export default { name: 'IncidentTimelineEventList', i18n: timelineListI18n, components: { - IncidentTimelineEventListItem, + IncidentTimelineEventItem, }, props: { timelineEventLoading: { @@ -99,16 +99,21 @@ export default { <div class="gl-pb-3 gl-border-gray-50 gl-border-1 gl-border-b-solid"> <strong class="gl-font-size-h2" data-testid="event-date">{{ eventDate }}</strong> </div> - <ul class="notes main-notes-list gl-pl-n3"> - <incident-timeline-event-list-item + <ul class="notes main-notes-list"> + <li v-for="(event, eventIndex) in events" - :key="event.id" - :action="event.action" - :occurred-at="event.occurredAt" - :note-html="event.noteHtml" - :is-last-item="isLastItem(dateGroupedEvents, groupIndex, events, eventIndex)" - @delete="handleDelete(event)" - /> + :key="eventIndex" + class="timeline-entry-vertical-line note system-note note-wrapper gl-my-2! gl-pr-0!" + > + <incident-timeline-event-item + :key="event.id" + :action="event.action" + :occurred-at="event.occurredAt" + :note-html="event.noteHtml" + :is-last-item="isLastItem(dateGroupedEvents, groupIndex, events, eventIndex)" + @delete="handleDelete(event)" + /> + </li> </ul> </div> </div> diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue index e1946ef4d07..7c2a7878c58 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue @@ -7,7 +7,7 @@ import getTimelineEvents from './graphql/queries/get_timeline_events.query.graph import { displayAndLogError } from './utils'; import { timelineTabI18n } from './constants'; -import IncidentTimelineEventForm from './timeline_events_form.vue'; +import CreateTimelineEvent from './create_timeline_event.vue'; import IncidentTimelineEventsList from './timeline_events_list.vue'; export default { @@ -16,7 +16,7 @@ export default { GlEmptyState, GlLoadingIcon, GlTab, - IncidentTimelineEventForm, + CreateTimelineEvent, IncidentTimelineEventsList, }, i18n: timelineTabI18n, @@ -61,10 +61,10 @@ export default { this.isEventFormVisible = false; }, async showEventForm() { - this.$refs.eventForm.clear(); + this.$refs.createEventForm.clearForm(); this.isEventFormVisible = true; await this.$nextTick(); - this.$refs.eventForm.focusDate(); + this.$refs.createEventForm.focusDate(); }, }, }; @@ -82,14 +82,15 @@ export default { v-if="hasTimelineEvents" :timeline-event-loading="timelineEventLoading" :timeline-events="timelineEvents" + @hide-new-timeline-events-form="hideEventForm" /> - <incident-timeline-event-form + <create-timeline-event v-show="isEventFormVisible" - ref="eventForm" + ref="createEventForm" :has-timeline-events="hasTimelineEvents" class="timeline-event-note timeline-event-note-form" :class="{ 'gl-pl-0': !hasTimelineEvents }" - @hide-incident-timeline-event-form="hideEventForm" + @hide-new-timeline-events-form="hideEventForm" /> <gl-button v-if="canUpdate" variant="default" class="gl-mb-3 gl-mt-7" @click="showEventForm"> {{ $options.i18n.addEventButton }} diff --git a/app/assets/javascripts/issues/show/components/incidents/utils.js b/app/assets/javascripts/issues/show/components/incidents/utils.js index 256e3025f19..cf790a11b67 100644 --- a/app/assets/javascripts/issues/show/components/incidents/utils.js +++ b/app/assets/javascripts/issues/show/components/incidents/utils.js @@ -11,6 +11,7 @@ export const displayAndLogError = (error) => const EVENT_ICONS = { comment: 'comment', issues: 'issues', + label: 'label', status: 'status', default: 'comment', }; |