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:
Diffstat (limited to 'app/assets/javascripts/issues/show/components/incidents')
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/constants.js11
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue31
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue47
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/graphql/queries/edit_timeline_event.mutation.graphql13
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue242
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_item.vue94
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_list.vue66
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue27
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/utils.js11
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;
};