diff options
Diffstat (limited to 'app/assets/javascripts/issues/show/components/incidents')
9 files changed, 331 insertions, 211 deletions
diff --git a/app/assets/javascripts/issues/show/components/incidents/constants.js b/app/assets/javascripts/issues/show/components/incidents/constants.js index 77d13fe085a..aa7b9805b5f 100644 --- a/app/assets/javascripts/issues/show/components/incidents/constants.js +++ b/app/assets/javascripts/issues/show/components/incidents/constants.js @@ -26,4 +26,15 @@ export const timelineListI18n = Object.freeze({ 'Incident|Something went wrong while deleting the incident timeline event.', ), deleteModal: s__('Incident|Are you sure you want to delete this event?'), + editError: s__('Incident|Error updating incident timeline event: %{error}'), + editErrorGeneric: s__( + 'Incident|Something went wrong while updating the incident timeline event.', + ), +}); + +export const timelineItemI18n = Object.freeze({ + delete: __('Delete'), + edit: __('Edit'), + moreActions: __('More actions'), + timeUTC: __('%{time} UTC'), }); 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 c902895702e..6bb72e82778 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 @@ -1,6 +1,7 @@ <script> import { produce } from 'immer'; import { sortBy } from 'lodash'; +import { GlIcon } from '@gitlab/ui'; import { sprintf } from '~/locale'; import { createAlert } from '~/flash'; import { convertToGraphQLId } from '~/graphql_shared/utils'; @@ -16,6 +17,7 @@ export default { i18n: timelineFormI18n, components: { TimelineEventsForm, + GlIcon, }, inject: ['fullPath', 'issuableId'], props: { @@ -31,9 +33,6 @@ export default { clearForm() { this.$refs.eventForm.clear(); }, - focusDate() { - this.$refs.eventForm.focusDate(); - }, updateCache(store, { data }) { const { timelineEvent: event, errors } = data?.timelineEventCreate || {}; @@ -107,11 +106,23 @@ export default { </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')" - /> + <div + class="create-timeline-event gl-relative gl-display-flex gl-align-items-start" + :class="{ 'timeline-entry-vertical-line': hasTimelineEvents }" + > + <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-mr-3 gl-w-8 gl-h-8 gl-z-index-1" + > + <gl-icon name="comment" class="note-icon" /> + </div> + <timeline-events-form + ref="eventForm" + :class="{ 'gl-border-gray-50 gl-border-t': hasTimelineEvents }" + :is-event-processed="createTimelineEventActive" + show-save-and-add + @save-event="createIncidentTimelineEvent" + @cancel="$emit('hide-new-timeline-events-form')" + /> + </div> </template> diff --git a/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue b/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue new file mode 100644 index 00000000000..60fa8cb949b --- /dev/null +++ b/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue @@ -0,0 +1,47 @@ +<script> +import { GlIcon } from '@gitlab/ui'; +import TimelineEventsForm from './timeline_events_form.vue'; + +export default { + name: 'EditTimelineEvent', + components: { + TimelineEventsForm, + GlIcon, + }, + props: { + event: { + type: Object, + required: true, + validator: (item) => ['occurredAt', 'note'].every((key) => item[key]), + }, + editTimelineEventActive: { + type: Boolean, + required: true, + }, + }, + methods: { + saveEvent(eventDetails) { + this.$emit('handle-save-edit', { ...eventDetails, id: this.event.id }, false); + }, + }, +}; +</script> + +<template> + <div class="gl-relative gl-display-flex gl-align-items-center"> + <div + 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-mr-3 gl-w-8 gl-h-8 gl-z-index-1" + > + <gl-icon name="comment" class="note-icon" /> + </div> + <timeline-events-form + ref="eventForm" + class="timeline-event-border" + :is-event-processed="editTimelineEventActive" + :previous-occurred-at="event.occurredAt" + :previous-note="event.note" + @save-event="saveEvent" + @cancel="$emit('hide-edit')" + /> + </div> +</template> diff --git a/app/assets/javascripts/issues/show/components/incidents/graphql/queries/edit_timeline_event.mutation.graphql b/app/assets/javascripts/issues/show/components/incidents/graphql/queries/edit_timeline_event.mutation.graphql new file mode 100644 index 00000000000..54f036268cc --- /dev/null +++ b/app/assets/javascripts/issues/show/components/incidents/graphql/queries/edit_timeline_event.mutation.graphql @@ -0,0 +1,13 @@ +mutation UpdateTimelineEvent($input: TimelineEventUpdateInput!) { + timelineEventUpdate(input: $input) { + timelineEvent { + id + note + noteHtml + action + occurredAt + createdAt + } + errors + } +} 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 0d84fabb1be..b7ae18372ab 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,9 +1,9 @@ <script> -import { GlDatepicker, GlFormInput, GlFormGroup, GlButton, GlIcon } from '@gitlab/ui'; +import { GlDatepicker, GlFormInput, GlFormGroup, GlButton } from '@gitlab/ui'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import autofocusonshow from '~/vue_shared/directives/autofocusonshow'; import { timelineFormI18n } from './constants'; -import { getUtcShiftedDateNow } from './utils'; +import { getUtcShiftedDate } from './utils'; export default { name: 'TimelineEventsForm', @@ -15,6 +15,7 @@ export default { 'task-list', 'collapsible-section', 'table', + 'attach-file', 'full-screen', ], components: { @@ -23,175 +24,168 @@ export default { GlFormInput, GlFormGroup, GlButton, - GlIcon, }, i18n: timelineFormI18n, directives: { autofocusonshow, }, props: { - hasTimelineEvents: { + showSaveAndAdd: { type: Boolean, - required: true, + required: false, + default: false, }, isEventProcessed: { type: Boolean, required: true, }, + previousOccurredAt: { + type: String, + required: false, + default: null, + }, + previousNote: { + type: String, + required: false, + default: '', + }, }, data() { - // if occurredAt is undefined, returns "now" in UTC - const placeholderDate = getUtcShiftedDateNow(); + // if occurredAt is null, returns "now" in UTC + const placeholderDate = getUtcShiftedDate(this.previousOccurredAt); return { - timelineText: '', + timelineText: this.previousNote, placeholderDate, hourPickerInput: placeholderDate.getHours(), minutePickerInput: placeholderDate.getMinutes(), - datepickerTextInput: null, + datePickerInput: placeholderDate, }; }, computed: { - occurredAt() { - const [years, months, days] = this.datepickerTextInput.split('-'); + occurredAtString() { + const year = this.datePickerInput.getFullYear(); + const month = this.datePickerInput.getMonth(); + const day = this.datePickerInput.getDate(); + const utcDate = new Date( - Date.UTC(years, months - 1, days, this.hourPickerInput, this.minutePickerInput), + Date.UTC(year, month, day, this.hourPickerInput, this.minutePickerInput), ); return utcDate.toISOString(); }, }, + mounted() { + this.focusDate(); + }, methods: { clear() { - const utcShiftedDateNow = getUtcShiftedDateNow(); - this.placeholderDate = utcShiftedDateNow; - this.hourPickerInput = utcShiftedDateNow.getHours(); - this.minutePickerInput = utcShiftedDateNow.getMinutes(); + const newPlaceholderDate = getUtcShiftedDate(); + this.datePickerInput = newPlaceholderDate; + this.hourPickerInput = newPlaceholderDate.getHours(); + this.minutePickerInput = newPlaceholderDate.getMinutes(); this.timelineText = ''; }, focusDate() { - this.$refs.datepicker.$el.focus(); + this.$refs.datepicker.$el.querySelector('input').focus(); }, handleSave(addAnotherEvent) { - const eventDetails = { + const event = { note: this.timelineText, - occurredAt: this.occurredAt, + occurredAt: this.occurredAtString, }; - this.$emit('save-event', eventDetails, addAnotherEvent); + this.$emit('save-event', event, addAnotherEvent); }, }, }; </script> <template> - <div - class="gl-relative gl-display-flex gl-align-items-center" - :class="{ 'timeline-entry-vertical-line': hasTimelineEvents }" - > - <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-mr-3 gl-w-8 gl-h-8 gl-z-index-1" - > - <gl-icon name="comment" class="note-icon" /> - </div> - <form class="gl-flex-grow-1 gl-border-gray-50" :class="{ 'gl-border-t': hasTimelineEvents }"> - <div - 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="placeholderDate"> + <form class="gl-flex-grow-1 gl-border-gray-50"> + <div class="gl-display-flex gl-flex-direction-column gl-sm-flex-direction-row"> + <gl-form-group :label="__('Date')" class="gl-mt-5 gl-mr-5"> + <gl-datepicker id="incident-date" ref="datepicker" v-model="datePickerInput" /> + </gl-form-group> + <div class="gl-display-flex gl-mt-5"> + <gl-form-group :label="__('Time')"> + <div class="gl-display-flex"> + <label label-for="timeline-input-hours" class="sr-only"></label> <gl-form-input - id="incident-date" - ref="datepicker" - v-model="datepickerTextInput" - data-testid="input-datepicker" - class="gl-datepicker-input gl-pr-7!" - :value="formattedDate" - :placeholder="__('YYYY-MM-DD')" - @keydown.enter="onKeydown" + id="timeline-input-hours" + v-model="hourPickerInput" + data-testid="input-hours" + size="xs" + type="number" + min="00" + max="23" /> - </gl-datepicker> - </gl-form-group> - <div class="gl-display-flex gl-mt-5"> - <gl-form-group :label="__('Time')"> - <div class="gl-display-flex"> - <label label-for="timeline-input-hours" class="sr-only"></label> - <gl-form-input - id="timeline-input-hours" - v-model="hourPickerInput" - data-testid="input-hours" - size="xs" - type="number" - min="00" - max="23" - /> - <label label-for="timeline-input-minutes" class="sr-only"></label> - <gl-form-input - id="timeline-input-minutes" - v-model="minutePickerInput" - class="gl-ml-3" - data-testid="input-minutes" - size="xs" - type="number" - min="00" - max="59" - /> - </div> - </gl-form-group> - <p class="gl-ml-3 gl-align-self-end gl-line-height-32">{{ __('UTC') }}</p> - </div> - </div> - <div class="common-note-form"> - <gl-form-group class="gl-mb-3" :label="$options.i18n.areaLabel"> - <markdown-field - :can-attach-file="false" - :add-spacing-classes="false" - :show-comment-tool-bar="false" - :textarea-value="timelineText" - :restricted-tool-bar-items="$options.restrictedToolBarItems" - markdown-docs-path="" - :enable-preview="false" - class="bordered-box gl-mt-0" - > - <template #textarea> - <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="$options.i18n.description" - :placeholder="$options.i18n.areaPlaceholder" - > - </textarea> - </template> - </markdown-field> + <label label-for="timeline-input-minutes" class="sr-only"></label> + <gl-form-input + id="timeline-input-minutes" + v-model="minutePickerInput" + class="gl-ml-3" + data-testid="input-minutes" + size="xs" + type="number" + min="00" + max="59" + /> + </div> </gl-form-group> + <p class="gl-ml-3 gl-align-self-end gl-line-height-32">{{ __('UTC') }}</p> </div> - <gl-form-group class="gl-mb-0"> - <gl-button - variant="confirm" - category="primary" - class="gl-mr-3" - :loading="isEventProcessed" - @click="handleSave(false)" - > - {{ $options.i18n.save }} - </gl-button> - <gl-button - variant="confirm" - category="secondary" - class="gl-mr-3 gl-ml-n2" - :loading="isEventProcessed" - @click="handleSave(true)" + </div> + <div class="common-note-form"> + <gl-form-group class="gl-mb-3" :label="$options.i18n.areaLabel"> + <markdown-field + :can-attach-file="false" + :add-spacing-classes="false" + :show-comment-tool-bar="false" + :textarea-value="timelineText" + :restricted-tool-bar-items="$options.restrictedToolBarItems" + markdown-docs-path="" + :enable-preview="false" + class="bordered-box gl-mt-0" > - {{ $options.i18n.saveAndAdd }} - </gl-button> - <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> + <template #textarea> + <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="$options.i18n.description" + :placeholder="$options.i18n.areaPlaceholder" + > + </textarea> + </template> + </markdown-field> </gl-form-group> - </form> - </div> + </div> + <gl-form-group class="gl-mb-0"> + <gl-button + variant="confirm" + category="primary" + class="gl-mr-3" + :loading="isEventProcessed" + @click="handleSave(false)" + > + {{ $options.i18n.save }} + </gl-button> + <gl-button + v-if="showSaveAndAdd" + variant="confirm" + category="secondary" + class="gl-mr-3 gl-ml-n2" + :loading="isEventProcessed" + @click="handleSave(true)" + > + {{ $options.i18n.saveAndAdd }} + </gl-button> + <gl-button class="gl-ml-n2" :disabled="isEventProcessed" @click="$emit('cancel')"> + {{ $options.i18n.cancel }} + </gl-button> + <div class="timeline-event-bottom-border"></div> + </gl-form-group> + </form> </template> diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue index 6175c9969ec..cbf3c387fa3 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue @@ -1,25 +1,13 @@ <script> -import { - GlButton, - GlDropdown, - GlDropdownItem, - GlIcon, - GlSafeHtmlDirective, - GlSprintf, -} from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem, GlIcon, GlSafeHtmlDirective, GlSprintf } from '@gitlab/ui'; import { formatDate } from '~/lib/utils/datetime_utility'; -import { __ } from '~/locale'; +import { timelineItemI18n } from './constants'; import { getEventIcon } from './utils'; export default { name: 'IncidentTimelineEventListItem', - i18n: { - delete: __('Delete'), - moreActions: __('More actions'), - timeUTC: __('%{time} UTC'), - }, + i18n: timelineItemI18n, components: { - GlButton, GlDropdown, GlDropdownItem, GlIcon, @@ -28,12 +16,8 @@ export default { directives: { SafeHtml: GlSafeHtmlDirective, }, - inject: ['canUpdate'], + inject: ['canUpdateTimelineEvent'], props: { - isLastItem: { - type: Boolean, - required: true, - }, occurredAt: { type: String, required: true, @@ -58,43 +42,41 @@ export default { }; </script> <template> - <li - class="timeline-entry timeline-entry-vertical-line note system-note note-wrapper gl-my-2! gl-pr-0!" - > - <div class="gl-display-flex gl-align-items-center"> - <div - class="gl-display-flex gl-align-items-center gl-justify-content-center gl-bg-white gl-text-gray-200 gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-full gl-mt-n2 gl-mr-3 gl-w-8 gl-h-8 gl-p-3 gl-z-index-1" - > - <gl-icon :name="getEventIcon(action)" class="note-icon" /> + <div class="gl-display-flex gl-align-items-start"> + <div + class="gl-display-flex gl-align-items-center gl-justify-content-center gl-bg-white gl-text-gray-200 gl-border-gray-100 gl-border-1 gl-border-solid gl-rounded-full gl-mt-2 gl-mr-3 gl-w-8 gl-h-8 gl-p-3 gl-z-index-1" + > + <gl-icon :name="getEventIcon(action)" class="note-icon" /> + </div> + <div + class="timeline-event-note timeline-event-border gl-w-full gl-display-flex gl-flex-direction-row" + data-testid="event-text-container" + > + <div> + <strong class="gl-font-lg" data-testid="event-time"> + <gl-sprintf :message="$options.i18n.timeUTC"> + <template #time>{{ time }}</template> + </gl-sprintf> + </strong> + <div v-safe-html="noteHtml"></div> </div> - <div - class="timeline-event-note gl-w-full gl-display-flex gl-flex-direction-row" - :class="{ 'gl-pb-3 gl-border-gray-50 gl-border-1 gl-border-b-solid': !isLastItem }" - data-testid="event-text-container" + <gl-dropdown + v-if="canUpdateTimelineEvent" + right + class="event-note-actions gl-ml-auto gl-align-self-start" + icon="ellipsis_v" + text-sr-only + :text="$options.i18n.moreActions" + category="tertiary" + no-caret > - <div> - <strong class="gl-font-lg" data-testid="event-time"> - <gl-sprintf :message="$options.i18n.timeUTC"> - <template #time>{{ time }}</template> - </gl-sprintf> - </strong> - <div v-safe-html="noteHtml"></div> - </div> - <gl-dropdown - v-if="canUpdate" - right - class="event-note-actions gl-ml-auto gl-align-self-center" - icon="ellipsis_v" - text-sr-only - :text="$options.i18n.moreActions" - category="tertiary" - no-caret - > - <gl-dropdown-item @click="$emit('delete')"> - <gl-button>{{ $options.i18n.delete }}</gl-button> - </gl-dropdown-item> - </gl-dropdown> - </div> + <gl-dropdown-item @click="$emit('edit')"> + {{ $options.i18n.edit }} + </gl-dropdown-item> + <gl-dropdown-item @click="$emit('delete')"> + {{ $options.i18n.delete }} + </gl-dropdown-item> + </gl-dropdown> </div> - </li> + </div> </template> 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 80ac1c372cd..321b7ccc14a 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 @@ -5,7 +5,9 @@ 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 IncidentTimelineEventItem from './timeline_events_item.vue'; +import EditTimelineEvent from './edit_timeline_event.vue'; import deleteTimelineEvent from './graphql/queries/delete_timeline_event.mutation.graphql'; +import editTimelineEvent from './graphql/queries/edit_timeline_event.mutation.graphql'; import { timelineListI18n } from './constants'; export default { @@ -13,6 +15,7 @@ export default { i18n: timelineListI18n, components: { IncidentTimelineEventItem, + EditTimelineEvent, }, props: { timelineEventLoading: { @@ -26,6 +29,9 @@ export default { default: () => [], }, }, + data() { + return { eventToEdit: null, editTimelineEventActive: false }; + }, computed: { dateGroupedEvents() { const groupedEvents = new Map(); @@ -44,11 +50,12 @@ export default { }, }, methods: { - isLastItem(groups, groupIndex, events, eventIndex) { - if (groupIndex < groups.size - 1) { - return false; - } - return eventIndex === events.length - 1; + handleEditSelection(event) { + this.eventToEdit = event.id; + this.$emit('hide-new-incident-timeline-event-form'); + }, + hideEdit() { + this.eventToEdit = null; }, handleDelete: ignoreWhilePending(async function handleDelete(event) { const msg = this.$options.i18n.deleteModal; @@ -85,6 +92,38 @@ export default { createAlert({ message: this.$options.i18n.deleteErrorGeneric, captureError: true, error }); } }), + handleSaveEdit(eventDetails) { + this.editTimelineEventActive = true; + return this.$apollo + .mutate({ + mutation: editTimelineEvent, + variables: { + input: { + id: eventDetails.id, + note: eventDetails.note, + occurredAt: eventDetails.occurredAt, + }, + }, + }) + .then(({ data }) => { + this.editTimelineEventActive = false; + const errors = data.timelineEventUpdate?.errors; + if (errors.length) { + createAlert({ + message: sprintf(this.$options.i18n.editError, { error: errors.join('. ') }, false), + }); + } else { + this.hideEdit(); + } + }) + .catch((error) => { + createAlert({ + message: this.$options.i18n.editErrorGeneric, + captureError: true, + error, + }); + }); + }, }, }; </script> @@ -92,9 +131,10 @@ export default { <template> <div class="issuable-discussion incident-timeline-events"> <div - v-for="([eventDate, events], groupIndex) in dateGroupedEvents" + v-for="[eventDate, events] in dateGroupedEvents" :key="eventDate" data-testid="timeline-group" + class="timeline-group" > <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> @@ -103,15 +143,25 @@ export default { <li v-for="(event, eventIndex) in events" :key="eventIndex" - class="timeline-entry-vertical-line note system-note note-wrapper gl-my-2! gl-pr-0!" + class="timeline-entry-vertical-line timeline-entry note system-note note-wrapper gl-my-2! gl-pr-0!" > + <edit-timeline-event + v-if="eventToEdit === event.id" + :key="`edit-${event.id}`" + ref="eventForm" + :event="event" + :edit-timeline-event-active="editTimelineEventActive" + @handle-save-edit="handleSaveEdit" + @hide-edit="hideEdit()" + /> <incident-timeline-event-item + v-else :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)" + @edit="handleEditSelection(event)" /> </li> </ul> 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 7c2a7878c58..5f70d9acac9 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 @@ -3,10 +3,10 @@ import { GlButton, GlEmptyState, GlLoadingIcon, GlTab } from '@gitlab/ui'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { TYPE_ISSUE } from '~/graphql_shared/constants'; import { fetchPolicies } from '~/lib/graphql'; +import notesEventHub from '~/notes/event_hub'; import getTimelineEvents from './graphql/queries/get_timeline_events.query.graphql'; import { displayAndLogError } from './utils'; import { timelineTabI18n } from './constants'; - import CreateTimelineEvent from './create_timeline_event.vue'; import IncidentTimelineEventsList from './timeline_events_list.vue'; @@ -20,7 +20,7 @@ export default { IncidentTimelineEventsList, }, i18n: timelineTabI18n, - inject: ['canUpdate', 'fullPath', 'issuableId'], + inject: ['canUpdateTimelineEvent', 'fullPath', 'issuableId'], data() { return { isEventFormVisible: false, @@ -56,15 +56,21 @@ export default { return !this.timelineEventLoading && !this.hasTimelineEvents; }, }, + mounted() { + notesEventHub.$on('comment-promoted-to-timeline-event', this.refreshTimelineEvents); + }, + destroyed() { + notesEventHub.$off('comment-promoted-to-timeline-event', this.refreshTimelineEvents); + }, methods: { + refreshTimelineEvents() { + this.$apollo.queries.timelineEvents.refetch(); + }, hideEventForm() { this.isEventFormVisible = false; }, - async showEventForm() { - this.$refs.createEventForm.clearForm(); + showEventForm() { this.isEventFormVisible = true; - await this.$nextTick(); - this.$refs.createEventForm.focusDate(); }, }, }; @@ -85,14 +91,19 @@ export default { @hide-new-timeline-events-form="hideEventForm" /> <create-timeline-event - v-show="isEventFormVisible" + v-if="isEventFormVisible" ref="createEventForm" :has-timeline-events="hasTimelineEvents" class="timeline-event-note timeline-event-note-form" :class="{ 'gl-pl-0': !hasTimelineEvents }" @hide-new-timeline-events-form="hideEventForm" /> - <gl-button v-if="canUpdate" variant="default" class="gl-mb-3 gl-mt-7" @click="showEventForm"> + <gl-button + v-if="canUpdateTimelineEvent" + variant="default" + class="gl-mb-3 gl-mt-7" + @click="showEventForm" + > {{ $options.i18n.addEventButton }} </gl-button> </gl-tab> diff --git a/app/assets/javascripts/issues/show/components/incidents/utils.js b/app/assets/javascripts/issues/show/components/incidents/utils.js index cf790a11b67..5a009debd75 100644 --- a/app/assets/javascripts/issues/show/components/incidents/utils.js +++ b/app/assets/javascripts/issues/show/components/incidents/utils.js @@ -21,13 +21,14 @@ export const getEventIcon = (actionName) => { }; /** - * Returns a date shifted by the current timezone offset. Allows - * date.getHours() and similar to return UTC values. - * + * Returns a date shifted by the current timezone offset set to now + * by default but can accept an existing date as an ISO date string + * @param {string} ISOString * @returns {Date} */ -export const getUtcShiftedDateNow = () => { - const date = new Date(); +export const getUtcShiftedDate = (ISOString = null) => { + const date = ISOString ? new Date(ISOString) : new Date(); date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); + return date; }; |