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')
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue9
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/constants.js5
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue117
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue157
-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.vue27
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_tab.vue15
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/utils.js1
8 files changed, 206 insertions, 137 deletions
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index 449da394841..a6747d67611 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -133,7 +133,7 @@ export default {
},
computed: {
workItemsEnabled() {
- return this.glFeatures.workItems;
+ return this.glFeatures.workItemsCreateFromMarkdown;
},
taskWorkItemType() {
return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id;
@@ -302,7 +302,9 @@ export default {
if (taskRegexMatches) {
$tasks.text(this.taskStatus);
$tasksShort.text(
- `${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`,
+ `${taskRegexMatches[1]}/${taskRegexMatches[2]} checklist item${
+ taskRegexMatches[2] > 1 ? 's' : ''
+ }`,
);
} else {
$tasks.text('');
@@ -315,7 +317,7 @@ export default {
}
this.taskButtons = [];
- const taskListFields = this.$el.querySelectorAll('.task-list-item');
+ const taskListFields = this.$el.querySelectorAll('.task-list-item:not(.inapplicable)');
taskListFields.forEach((item, index) => {
const taskLink = item.querySelector('.gfm-issue');
@@ -326,6 +328,7 @@ export default {
}
const workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue);
this.addHoverListeners(taskLink, workItemId);
+ taskLink.classList.add('gl-link');
taskLink.addEventListener('click', (e) => {
e.preventDefault();
this.openWorkItemDetailModal(taskLink);
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',
};