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/work_items/components/work_item_notes.vue')
-rw-r--r--app/assets/javascripts/work_items/components/work_item_notes.vue258
1 files changed, 180 insertions, 78 deletions
diff --git a/app/assets/javascripts/work_items/components/work_item_notes.vue b/app/assets/javascripts/work_items/components/work_item_notes.vue
index 02b94c5331c..092b90a5731 100644
--- a/app/assets/javascripts/work_items/components/work_item_notes.vue
+++ b/app/assets/javascripts/work_items/components/work_item_notes.vue
@@ -1,21 +1,36 @@
<script>
import { GlSkeletonLoader, GlModal } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
-import { s__, __ } from '~/locale';
+import { uniqueId } from 'lodash';
+import { __ } from '~/locale';
+import { scrollToTargetOnResize } from '~/lib/utils/resize_observer';
import { TYPENAME_DISCUSSION, TYPENAME_NOTE } from '~/graphql_shared/constants';
import SystemNote from '~/work_items/components/notes/system_note.vue';
-import ActivityFilter from '~/work_items/components/notes/activity_filter.vue';
-import { i18n, DEFAULT_PAGE_SIZE_NOTES } from '~/work_items/constants';
+import WorkItemNotesActivityHeader from '~/work_items/components/notes/work_item_notes_activity_header.vue';
+import {
+ i18n,
+ DEFAULT_PAGE_SIZE_NOTES,
+ WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS,
+ WORK_ITEM_NOTES_FILTER_ONLY_HISTORY,
+} from '~/work_items/constants';
import { ASC, DESC } from '~/notes/constants';
-import { getWorkItemNotesQuery } from '~/work_items/utils';
+import { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils';
+import {
+ updateCacheAfterCreatingNote,
+ updateCacheAfterDeletingNote,
+} from '~/work_items/graphql/cache_utils';
+import { getLocationHash } from '~/lib/utils/url_utility';
import WorkItemDiscussion from '~/work_items/components/notes/work_item_discussion.vue';
+import WorkItemHistoryOnlyFilterNote from '~/work_items/components/notes/work_item_history_only_filter_note.vue';
+import workItemNoteCreatedSubscription from '~/work_items/graphql/notes/work_item_note_created.subscription.graphql';
+import workItemNoteUpdatedSubscription from '~/work_items/graphql/notes/work_item_note_updated.subscription.graphql';
+import workItemNoteDeletedSubscription from '~/work_items/graphql/notes/work_item_note_deleted.subscription.graphql';
import deleteNoteMutation from '../graphql/notes/delete_work_item_notes.mutation.graphql';
+import workItemNotesByIidQuery from '../graphql/notes/work_item_notes_by_iid.query.graphql';
import WorkItemAddNote from './notes/work_item_add_note.vue';
export default {
- i18n: {
- ACTIVITY_LABEL: s__('WorkItem|Activity'),
- },
loader: {
repeat: 10,
width: 1000,
@@ -24,21 +39,19 @@ export default {
components: {
GlSkeletonLoader,
GlModal,
- ActivityFilter,
SystemNote,
WorkItemAddNote,
WorkItemDiscussion,
+ WorkItemNotesActivityHeader,
+ WorkItemHistoryOnlyFilterNote,
},
+ inject: ['fullPath'],
props: {
workItemId: {
type: String,
required: true,
},
- queryVariables: {
- type: Object,
- required: true,
- },
- fullPath: {
+ workItemIid: {
type: String,
required: true,
},
@@ -46,18 +59,33 @@ export default {
type: String,
required: true,
},
- fetchByIid: {
+ isModal: {
type: Boolean,
required: false,
default: false,
},
+ assignees: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ canSetWorkItemMetadata: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ reportAbusePath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
isLoadingMore: false,
- perPage: DEFAULT_PAGE_SIZE_NOTES,
sortOrder: ASC,
noteToDelete: null,
+ discussionFilter: WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ addNoteKey: uniqueId(`work-item-add-note-${this.workItemId}`),
};
},
computed: {
@@ -73,70 +101,135 @@ export default {
hasNextPage() {
return this.pageInfo?.hasNextPage;
},
- showLoadingMoreSkeleton() {
- return this.isLoadingMore && !this.changeNotesSortOrderAfterLoading;
- },
- disableActivityFilter() {
+ disableActivityFilterSort() {
return this.initialLoading || this.isLoadingMore;
},
formAtTop() {
return this.sortOrder === DESC;
},
+ markdownPreviewPath() {
+ return markdownPreviewPath(this.fullPath, this.workItemIid);
+ },
+ autocompleteDataSources() {
+ return autocompleteDataSources(this.fullPath, this.workItemIid);
+ },
workItemCommentFormProps() {
return {
- queryVariables: this.queryVariables,
fullPath: this.fullPath,
workItemId: this.workItemId,
- fetchByIid: this.fetchByIid,
+ workItemIid: this.workItemIid,
workItemType: this.workItemType,
sortOrder: this.sortOrder,
+ isNewDiscussion: true,
+ markdownPreviewPath: this.markdownPreviewPath,
+ autocompleteDataSources: this.autocompleteDataSources,
};
},
notesArray() {
const notes = this.workItemNotes?.nodes || [];
+ const visibleNotes = notes.filter((note) => {
+ const isSystemNote = this.isSystemNote(note);
+
+ if (this.discussionFilter === WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS && isSystemNote) {
+ return false;
+ }
+
+ if (this.discussionFilter === WORK_ITEM_NOTES_FILTER_ONLY_HISTORY && !isSystemNote) {
+ return false;
+ }
+
+ return true;
+ });
+
if (this.sortOrder === DESC) {
- return [...notes].reverse();
+ return [...visibleNotes].reverse();
}
- return notes;
+ return visibleNotes;
+ },
+ commentsDisabled() {
+ return this.discussionFilter === WORK_ITEM_NOTES_FILTER_ONLY_HISTORY;
+ },
+ targetNoteHash() {
+ return getLocationHash();
},
},
apollo: {
workItemNotes: {
- query() {
- return getWorkItemNotesQuery(this.fetchByIid);
- },
+ query: workItemNotesByIidQuery,
context: {
isSingleRequest: true,
},
variables() {
return {
- ...this.queryVariables,
+ fullPath: this.fullPath,
+ iid: this.workItemIid,
after: this.after,
pageSize: DEFAULT_PAGE_SIZE_NOTES,
};
},
update(data) {
- const workItemWidgets = this.fetchByIid
- ? data.workspace?.workItems?.nodes[0]?.widgets
- : data.workItem?.widgets;
- const discussionNodes =
- workItemWidgets.find((widget) => widget.type === 'NOTES')?.discussions || [];
- return discussionNodes;
+ const widgets = data.workspace?.workItems?.nodes[0]?.widgets;
+ return widgets?.find((widget) => widget.type === 'NOTES')?.discussions || [];
},
skip() {
- return !this.queryVariables.id && !this.queryVariables.iid;
+ return !this.workItemIid;
},
error() {
this.$emit('error', i18n.fetchError);
},
result() {
- this.updateSortingOrderIfApplicable();
-
if (this.hasNextPage) {
this.fetchMoreNotes();
+ } else if (this.targetNoteHash) {
+ if (this.isModal) {
+ this.$emit('has-notes');
+ } else {
+ scrollToTargetOnResize();
+ }
}
},
+ subscribeToMore: [
+ {
+ document: workItemNoteCreatedSubscription,
+ updateQuery(previousResult, { subscriptionData }) {
+ return updateCacheAfterCreatingNote(previousResult, subscriptionData);
+ },
+ variables() {
+ return {
+ noteableId: this.workItemId,
+ };
+ },
+ skip() {
+ return !this.workItemId || this.hasNextPage;
+ },
+ },
+ {
+ document: workItemNoteDeletedSubscription,
+ updateQuery(previousResult, { subscriptionData }) {
+ return updateCacheAfterDeletingNote(previousResult, subscriptionData);
+ },
+ variables() {
+ return {
+ noteableId: this.workItemId,
+ };
+ },
+ skip() {
+ return !this.workItemId || this.hasNextPage;
+ },
+ },
+ {
+ document: workItemNoteUpdatedSubscription,
+ variables() {
+ return {
+ noteableId: this.workItemId,
+ };
+ },
+ skip() {
+ return !this.workItemId;
+ },
+ },
+ ],
},
},
methods: {
@@ -148,30 +241,25 @@ export default {
isSystemNote(note) {
return note.notes.nodes[0].system;
},
- updateSortingOrderIfApplicable() {
- // when the sort order is DESC in local storage and there is only a single page, call
- // changeSortOrder manually
- if (
- this.changeNotesSortOrderAfterLoading &&
- this.perPage === DEFAULT_PAGE_SIZE_NOTES &&
- !this.hasNextPage
- ) {
- this.changeNotesSortOrder(DESC);
- }
- },
changeNotesSortOrder(direction) {
this.sortOrder = direction;
},
+ filterDiscussions(filterValue) {
+ this.discussionFilter = filterValue;
+ },
+ updateKey() {
+ this.addNoteKey = uniqueId(`work-item-add-note-${this.workItemId}`);
+ },
+ reportAbuse(isOpen, reply = {}) {
+ this.$emit('openReportAbuse', reply);
+ },
async fetchMoreNotes() {
this.isLoadingMore = true;
- // copied from discussions batch logic - every fetchMore call has a higher
- // amount of page size than the previous one with the limit being 100
- this.perPage = Math.min(Math.round(this.perPage * 1.5), 100);
await this.$apollo.queries.workItemNotes
.fetchMore({
variables: {
- ...this.queryVariables,
- pageSize: this.perPage,
+ fullPath: this.fullPath,
+ iid: this.workItemIid,
after: this.pageInfo?.endCursor,
},
})
@@ -223,17 +311,14 @@ export default {
<template>
<div class="gl-border-t gl-mt-5 work-item-notes">
- <div class="gl-display-flex gl-justify-content-space-between gl-flex-wrap">
- <label class="gl-mb-0">{{ $options.i18n.ACTIVITY_LABEL }}</label>
- <activity-filter
- class="gl-min-h-5 gl-pb-3"
- :loading="disableActivityFilter"
- :sort-order="sortOrder"
- :work-item-type="workItemType"
- @changeSortOrder="changeNotesSortOrder"
- @updateSavedSortOrder="changeNotesSortOrder"
- />
- </div>
+ <work-item-notes-activity-header
+ :sort-order="sortOrder"
+ :disable-activity-filter-sort="disableActivityFilterSort"
+ :work-item-type="workItemType"
+ :discussion-filter="discussionFilter"
+ @changeSort="changeNotesSortOrder"
+ @changeFilter="filterDiscussions"
+ />
<div v-if="initialLoading" class="gl-mt-5">
<gl-skeleton-loader
v-for="index in $options.loader.repeat"
@@ -248,13 +333,17 @@ export default {
</div>
<div v-else class="issuable-discussion gl-mb-5 gl-clearfix!">
<template v-if="!initialLoading">
- <ul class="notes main-notes-list timeline gl-clearfix!">
- <work-item-add-note
- v-if="formAtTop"
- v-bind="workItemCommentFormProps"
- @error="$emit('error', $event)"
- />
-
+ <div v-if="formAtTop && !commentsDisabled" class="js-comment-form">
+ <ul class="notes notes-form timeline">
+ <work-item-add-note
+ v-bind="workItemCommentFormProps"
+ :key="addNoteKey"
+ @cancelEditing="updateKey"
+ @error="$emit('error', $event)"
+ />
+ </ul>
+ </div>
+ <ul class="notes main-notes-list timeline">
<template v-for="discussion in notesArray">
<system-note
v-if="isSystemNote(discussion)"
@@ -265,26 +354,39 @@ export default {
<work-item-discussion
:key="getDiscussionKey(discussion)"
:discussion="discussion.notes.nodes"
- :query-variables="queryVariables"
- :full-path="fullPath"
:work-item-id="workItemId"
- :fetch-by-iid="fetchByIid"
+ :work-item-iid="workItemIid"
:work-item-type="workItemType"
+ :is-modal="isModal"
+ :autocomplete-data-sources="autocompleteDataSources"
+ :markdown-preview-path="markdownPreviewPath"
+ :assignees="assignees"
+ :can-set-work-item-metadata="canSetWorkItemMetadata"
@deleteNote="showDeleteNoteModal($event, discussion)"
+ @reportAbuse="reportAbuse(true, $event)"
@error="$emit('error', $event)"
/>
</template>
</template>
- <work-item-add-note
- v-if="!formAtTop"
- v-bind="workItemCommentFormProps"
- @error="$emit('error', $event)"
+ <work-item-history-only-filter-note
+ v-if="commentsDisabled"
+ @changeFilter="filterDiscussions"
/>
</ul>
+ <div v-if="!formAtTop && !commentsDisabled" class="js-comment-form">
+ <ul class="notes notes-form timeline">
+ <work-item-add-note
+ v-bind="workItemCommentFormProps"
+ :key="addNoteKey"
+ @cancelEditing="updateKey"
+ @error="$emit('error', $event)"
+ />
+ </ul>
+ </div>
</template>
- <template v-if="showLoadingMoreSkeleton">
+ <template v-if="isLoadingMore">
<gl-skeleton-loader
v-for="index in $options.loader.repeat"
:key="index"