summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/issues
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/issues')
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue7
-rw-r--r--app/assets/javascripts/issues/list/constants.js10
-rw-r--r--app/assets/javascripts/issues/show/components/edited.vue33
-rw-r--r--app/assets/javascripts/issues/show/components/fields/description.vue31
-rw-r--r--app/assets/javascripts/issues/show/components/form.vue79
-rw-r--r--app/assets/javascripts/issues/show/components/header_actions.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue5
-rw-r--r--app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue7
-rw-r--r--app/assets/javascripts/issues/show/index.js1
9 files changed, 109 insertions, 66 deletions
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index 0b424d105b9..acb6aa93f0f 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -247,8 +247,8 @@ export default {
},
defaultWorkItemTypes() {
return this.isWorkItemsEnabled
- ? defaultWorkItemTypes.concat(WORK_ITEM_TYPE_ENUM_TASK)
- : defaultWorkItemTypes;
+ ? defaultWorkItemTypes
+ : defaultWorkItemTypes.filter((type) => type !== WORK_ITEM_TYPE_ENUM_TASK);
},
typeTokenOptions() {
return this.isWorkItemsEnabled
@@ -563,7 +563,8 @@ export default {
if (!this.hasInitBulkEdit) {
const bulkUpdateSidebar = await import('~/issuable/bulk_update_sidebar');
bulkUpdateSidebar.initBulkUpdateSidebar('issuable_');
- bulkUpdateSidebar.initIssueStatusSelect();
+ bulkUpdateSidebar.initStatusDropdown();
+ bulkUpdateSidebar.initSubscriptionsDropdown();
const usersSelect = await import('~/users_select');
const UsersSelect = usersSelect.default;
diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js
index 27738d7a3e6..9fe8899ab39 100644
--- a/app/assets/javascripts/issues/list/constants.js
+++ b/app/assets/javascripts/issues/list/constants.js
@@ -7,11 +7,13 @@ import {
FILTER_UPCOMING,
OPERATOR_IS,
OPERATOR_IS_NOT,
+ TOKEN_TYPE_HEALTH,
} from '~/vue_shared/components/filtered_search_bar/constants';
import {
WORK_ITEM_TYPE_ENUM_INCIDENT,
WORK_ITEM_TYPE_ENUM_ISSUE,
WORK_ITEM_TYPE_ENUM_TEST_CASE,
+ WORK_ITEM_TYPE_ENUM_TASK,
} from '~/work_items/constants';
export const i18n = {
@@ -147,14 +149,16 @@ export const TOKEN_TYPE_EPIC = 'epic_id';
export const TOKEN_TYPE_WEIGHT = 'weight';
export const TOKEN_TYPE_CONTACT = 'crm_contact';
export const TOKEN_TYPE_ORGANIZATION = 'crm_organization';
-export const TOKEN_TYPE_HEALTH = 'health_status';
-export const TYPE_TOKEN_TASK_OPTION = { icon: 'task-done', title: 'task', value: 'task' };
+export const TYPE_TOKEN_TASK_OPTION = { icon: 'issue-type-task', title: 'task', value: 'task' };
+// This should be consistent with Issue::TYPES_FOR_LIST in the backend
+// https://gitlab.com/gitlab-org/gitlab/-/blob/1379c2d7bffe2a8d809f23ac5ef9b4114f789c07/app/models/issue.rb#L48
export const defaultWorkItemTypes = [
WORK_ITEM_TYPE_ENUM_ISSUE,
WORK_ITEM_TYPE_ENUM_INCIDENT,
WORK_ITEM_TYPE_ENUM_TEST_CASE,
+ WORK_ITEM_TYPE_ENUM_TASK,
];
export const defaultTypeTokenOptions = [
@@ -327,10 +331,12 @@ export const filters = {
[TOKEN_TYPE_HEALTH]: {
[API_PARAM]: {
[NORMAL_FILTER]: 'healthStatus',
+ [SPECIAL_FILTER]: 'healthStatus',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
[NORMAL_FILTER]: 'health_status',
+ [SPECIAL_FILTER]: 'health_status',
},
},
},
diff --git a/app/assets/javascripts/issues/show/components/edited.vue b/app/assets/javascripts/issues/show/components/edited.vue
index 4c5f783cd66..5138a4530e9 100644
--- a/app/assets/javascripts/issues/show/components/edited.vue
+++ b/app/assets/javascripts/issues/show/components/edited.vue
@@ -1,10 +1,11 @@
<script>
-/* eslint-disable @gitlab/vue-require-i18n-strings */
+import { GlSprintf } from '@gitlab/ui';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
components: {
TimeAgoTooltip,
+ GlSprintf,
},
props: {
updatedAt: {
@@ -33,13 +34,27 @@ export default {
<template>
<small class="edited-text js-issue-widgets">
- Edited
- <time-ago-tooltip v-if="updatedAt" :time="updatedAt" tooltip-placement="bottom" />
- <span v-if="hasUpdatedBy">
- by
- <a :href="updatedByPath" class="author-link">
- <span>{{ updatedByName }}</span>
- </a>
- </span>
+ <gl-sprintf v-if="!hasUpdatedBy" :message="__('Edited %{timeago}')">
+ <template #timeago>
+ <time-ago-tooltip :time="updatedAt" tooltip-placement="bottom" />
+ </template>
+ </gl-sprintf>
+ <gl-sprintf v-else-if="!updatedAt" :message="__('Edited by %{author}')">
+ <template #author>
+ <a :href="updatedByPath" class="author-link gl-hover-text-decoration-underline">
+ <span>{{ updatedByName }}</span>
+ </a>
+ </template>
+ </gl-sprintf>
+ <gl-sprintf v-else :message="__('Edited %{timeago} by %{author}')">
+ <template #timeago>
+ <time-ago-tooltip :time="updatedAt" tooltip-placement="bottom" />
+ </template>
+ <template #author>
+ <a :href="updatedByPath" class="author-link gl-hover-text-decoration-underline">
+ <span>{{ updatedByName }}</span>
+ </a>
+ </template>
+ </gl-sprintf>
</small>
</template>
diff --git a/app/assets/javascripts/issues/show/components/fields/description.vue b/app/assets/javascripts/issues/show/components/fields/description.vue
index c2ab7c4f298..dbe634e7295 100644
--- a/app/assets/javascripts/issues/show/components/fields/description.vue
+++ b/app/assets/javascripts/issues/show/components/fields/description.vue
@@ -1,13 +1,16 @@
<script>
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
+import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
+import glFeaturesFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import updateMixin from '../../mixins/update';
export default {
components: {
MarkdownField,
+ MarkdownEditor,
},
- mixins: [updateMixin],
+ mixins: [updateMixin, glFeaturesFlagMixin()],
props: {
value: {
type: String,
@@ -38,7 +41,12 @@ export default {
},
},
mounted() {
- this.$refs.textarea.focus();
+ this.focus();
+ },
+ methods: {
+ focus() {
+ this.$refs.textarea?.focus();
+ },
},
};
</script>
@@ -46,7 +54,26 @@ export default {
<template>
<div class="common-note-form">
<label class="sr-only" for="issue-description">{{ __('Description') }}</label>
+ <markdown-editor
+ v-if="glFeatures.contentEditorOnIssues"
+ class="gl-mt-3"
+ :value="value"
+ :render-markdown-path="markdownPreviewPath"
+ :markdown-docs-path="markdownDocsPath"
+ :form-field-aria-label="__('Description')"
+ :form-field-placeholder="__('Write a comment or drag your files hereā€¦')"
+ form-field-id="issue-description"
+ form-field-name="issue-description"
+ :quick-actions-docs-path="quickActionsDocsPath"
+ :enable-autocomplete="enableAutocomplete"
+ supports-quick-actions
+ init-on-autofocus
+ @input="$emit('input', $event)"
+ @keydown.meta.enter="updateIssuable"
+ @keydown.ctrl.enter="updateIssuable"
+ />
<markdown-field
+ v-else
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
diff --git a/app/assets/javascripts/issues/show/components/form.vue b/app/assets/javascripts/issues/show/components/form.vue
index f479c8ae78d..0c6b61fb893 100644
--- a/app/assets/javascripts/issues/show/components/form.vue
+++ b/app/assets/javascripts/issues/show/components/form.vue
@@ -1,7 +1,6 @@
<script>
import { GlAlert } from '@gitlab/ui';
-import $ from 'jquery';
-import Autosave from '~/autosave';
+import { getDraft, updateDraft, getLockVersion, clearDraft } from '~/lib/utils/autosave';
import { IssuableType } from '~/issues/constants';
import eventHub from '../event_hub';
import EditActions from './edit_actions.vue';
@@ -76,10 +75,17 @@ export default {
},
},
data() {
+ const autosaveKey = [document.location.pathname, document.location.search];
+ const descriptionAutosaveKey = [...autosaveKey, 'description'];
+ const titleAutosaveKey = [...autosaveKey, 'title'];
+
return {
+ titleAutosaveKey,
+ descriptionAutosaveKey,
+ autosaveReset: false,
formData: {
- title: this.formState.title,
- description: this.formState.description,
+ title: getDraft(titleAutosaveKey) || this.formState.title,
+ description: getDraft(descriptionAutosaveKey) || this.formState.description,
},
showOutdatedDescriptionWarning: false,
};
@@ -118,58 +124,40 @@ export default {
},
methods: {
initAutosave() {
- const {
- description: {
- $refs: { textarea },
- },
- title: {
- $refs: { input },
- },
- } = this.$refs;
-
- this.autosaveDescription = new Autosave(
- $(textarea),
- [document.location.pathname, document.location.search, 'description'],
- null,
- this.formState.lock_version,
- );
-
- const savedLockVersion = this.autosaveDescription.getSavedLockVersion();
+ const savedLockVersion = getLockVersion(this.descriptionAutosaveKey);
this.showOutdatedDescriptionWarning =
savedLockVersion && String(this.formState.lock_version) !== savedLockVersion;
-
- this.autosaveTitle = new Autosave($(input), [
- document.location.pathname,
- document.location.search,
- 'title',
- ]);
},
resetAutosave() {
- this.autosaveDescription.reset();
- this.autosaveTitle.reset();
+ this.autosaveReset = true;
+ clearDraft(this.descriptionAutosaveKey);
+ clearDraft(this.titleAutosaveKey);
},
keepAutosave() {
- const {
- description: {
- $refs: { textarea },
- },
- } = this.$refs;
-
- textarea.focus();
+ this.$refs.description.focus();
this.showOutdatedDescriptionWarning = false;
},
discardAutosave() {
- const {
- description: {
- $refs: { textarea },
- },
- } = this.$refs;
-
- textarea.value = this.initialDescriptionText;
- textarea.focus();
+ this.formData.description = this.initialDescriptionText;
+ clearDraft(this.descriptionAutosaveKey);
+ this.$refs.description.focus();
this.showOutdatedDescriptionWarning = false;
},
+ updateTitleDraft(title) {
+ updateDraft(this.titleAutosaveKey, title);
+ },
+ updateDescriptionDraft(description) {
+ /*
+ * This conditional statement prevents a race-condition
+ * between clearing the draft and submitting a new draft
+ * update while the user is typing. It happens when saving
+ * using the cmd + enter keyboard shortcut.
+ */
+ if (!this.autosaveReset) {
+ updateDraft(this.descriptionAutosaveKey, description, this.formState.lock_version);
+ }
+ },
},
};
</script>
@@ -194,7 +182,7 @@ export default {
>
<div class="row gl-mb-3">
<div class="col-12">
- <issuable-title-field ref="title" v-model="formData.title" />
+ <issuable-title-field ref="title" v-model="formData.title" @input="updateTitleDraft" />
</div>
</div>
<div class="row">
@@ -220,6 +208,7 @@ export default {
:markdown-docs-path="markdownDocsPath"
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
+ @input="updateDescriptionDraft"
/>
<edit-actions :endpoint="endpoint" :form-state="formState" :issuable-type="issuableType" />
diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue
index adf449aca7b..74d166f82bb 100644
--- a/app/assets/javascripts/issues/show/components/header_actions.vue
+++ b/app/assets/javascripts/issues/show/components/header_actions.vue
@@ -229,7 +229,7 @@ export default {
</script>
<template>
- <div class="detail-page-header-actions gl-display-flex">
+ <div class="detail-page-header-actions gl-display-flex gl-align-self-start">
<gl-dropdown
v-if="hasMobileDropdown"
class="gl-sm-display-none! w-100"
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 dd84a1d7d67..5725d0f8d6a 100644
--- a/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue
+++ b/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue
@@ -52,9 +52,6 @@ export default {
loading() {
return this.$apollo.queries.alert.loading;
},
- incidentTabEnabled() {
- return this.glFeatures.incidentTimeline;
- },
},
mounted() {
this.trackPageViews();
@@ -112,7 +109,7 @@ export default {
>
<alert-details-table :alert="alert" :loading="loading" />
</gl-tab>
- <timeline-tab v-if="incidentTabEnabled" />
+ <timeline-tab />
</gl-tabs>
</div>
</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 b7ae18372ab..55cd8b5f606 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
@@ -74,6 +74,9 @@ export default {
return utcDate.toISOString();
},
+ hasTimelineText() {
+ return this.timelineText.length > 0;
+ },
},
mounted() {
this.focusDate();
@@ -167,6 +170,8 @@ export default {
variant="confirm"
category="primary"
class="gl-mr-3"
+ data-testid="save-button"
+ :disabled="!hasTimelineText"
:loading="isEventProcessed"
@click="handleSave(false)"
>
@@ -177,6 +182,8 @@ export default {
variant="confirm"
category="secondary"
class="gl-mr-3 gl-ml-n2"
+ data-testid="save-and-add-button"
+ :disabled="!hasTimelineText"
:loading="isEventProcessed"
@click="handleSave(true)"
>
diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js
index e5eed9f6b79..3cb5007ab0d 100644
--- a/app/assets/javascripts/issues/show/index.js
+++ b/app/assets/javascripts/issues/show/index.js
@@ -60,6 +60,7 @@ export function initIncidentApp(issueData = {}) {
projectId,
slaFeatureAvailable: parseBoolean(slaFeatureAvailable),
uploadMetricsFeatureAvailable: parseBoolean(uploadMetricsFeatureAvailable),
+ contentEditorOnIssues: gon.features.contentEditorOnIssues,
},
render(createElement) {
return createElement(IssueApp, {