summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/work_items/components/work_item_notes.vue
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.vue158
1 files changed, 142 insertions, 16 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 91e90589a93..a59767d8b70 100644
--- a/app/assets/javascripts/work_items/components/work_item_notes.vue
+++ b/app/assets/javascripts/work_items/components/work_item_notes.vue
@@ -2,8 +2,12 @@
import { GlSkeletonLoader } from '@gitlab/ui';
import { s__ } from '~/locale';
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 { ASC, DESC } from '~/notes/constants';
import { getWorkItemNotesQuery } from '~/work_items/utils';
+import WorkItemNote from '~/work_items/components/notes/work_item_note.vue';
+import WorkItemCommentForm from './work_item_comment_form.vue';
export default {
i18n: {
@@ -15,8 +19,11 @@ export default {
height: 40,
},
components: {
- SystemNote,
GlSkeletonLoader,
+ ActivityFilter,
+ SystemNote,
+ WorkItemCommentForm,
+ WorkItemNote,
},
props: {
workItemId: {
@@ -31,22 +38,50 @@ export default {
type: String,
required: true,
},
+ workItemType: {
+ type: String,
+ required: true,
+ },
fetchByIid: {
type: Boolean,
required: false,
default: false,
},
},
+ data() {
+ return {
+ notesArray: [],
+ isLoadingMore: false,
+ perPage: DEFAULT_PAGE_SIZE_NOTES,
+ sortOrder: ASC,
+ changeNotesSortOrderAfterLoading: false,
+ };
+ },
computed: {
- areNotesLoading() {
- return this.$apollo.queries.workItemNotes.loading;
- },
- notes() {
- return this.workItemNotes?.nodes;
+ initialLoading() {
+ return this.$apollo.queries.workItemNotes.loading && !this.isLoadingMore;
},
pageInfo() {
return this.workItemNotes?.pageInfo;
},
+ avatarUrl() {
+ return window.gon.current_user_avatar_url;
+ },
+ hasNextPage() {
+ return this.pageInfo?.hasNextPage;
+ },
+ showInitialLoader() {
+ return this.initialLoading || this.changeNotesSortOrderAfterLoading;
+ },
+ showTimeline() {
+ return !this.changeNotesSortOrderAfterLoading;
+ },
+ showLoadingMoreSkeleton() {
+ return this.isLoadingMore && !this.changeNotesSortOrderAfterLoading;
+ },
+ disableActivityFilter() {
+ return this.initialLoading || this.isLoadingMore;
+ },
},
apollo: {
workItemNotes: {
@@ -59,6 +94,7 @@ export default {
variables() {
return {
...this.queryVariables,
+ after: this.after,
pageSize: DEFAULT_PAGE_SIZE_NOTES,
};
},
@@ -66,7 +102,11 @@ export default {
const workItemWidgets = this.fetchByIid
? data.workspace?.workItems?.nodes[0]?.widgets
: data.workItem?.widgets;
- return workItemWidgets.find((widget) => widget.type === 'NOTES').discussions || [];
+ const discussionNodes =
+ workItemWidgets.find((widget) => widget.type === 'NOTES')?.discussions || [];
+ this.notesArray = discussionNodes?.nodes || [];
+ this.updateSortingOrderIfApplicable();
+ return discussionNodes;
},
skip() {
return !this.queryVariables.id && !this.queryVariables.iid;
@@ -74,6 +114,58 @@ export default {
error() {
this.$emit('error', i18n.fetchError);
},
+ result() {
+ if (this.hasNextPage) {
+ this.fetchMoreNotes();
+ }
+ },
+ },
+ },
+ methods: {
+ 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);
+ }
+ },
+ updateInitialSortedOrder(direction) {
+ this.sortOrder = direction;
+ // when the direction is reverse , we need to load all since the sorting is on the frontend
+ if (direction === DESC) {
+ this.changeNotesSortOrderAfterLoading = true;
+ }
+ },
+ changeNotesSortOrder(direction) {
+ this.sortOrder = direction;
+ this.notesArray = [...this.notesArray].reverse();
+ this.changeNotesSortOrderAfterLoading = false;
+ },
+ 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,
+ after: this.pageInfo?.endCursor,
+ },
+ })
+ .catch((error) => this.$emit('error', error.message));
+ this.isLoadingMore = false;
+ if (this.changeNotesSortOrderAfterLoading && !this.hasNextPage) {
+ this.changeNotesSortOrder(this.sortOrder);
+ }
},
},
};
@@ -81,8 +173,18 @@ export default {
<template>
<div class="gl-border-t gl-mt-5">
- <label class="gl-mb-0">{{ $options.i18n.ACTIVITY_LABEL }}</label>
- <div v-if="areNotesLoading" class="gl-mt-5">
+ <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="updateInitialSortedOrder"
+ />
+ </div>
+ <div v-if="showInitialLoader" class="gl-mt-5">
<gl-skeleton-loader
v-for="index in $options.loader.repeat"
:key="index"
@@ -94,16 +196,40 @@ export default {
<rect width="500" x="45" y="15" height="10" rx="4" />
</gl-skeleton-loader>
</div>
- <div v-else class="issuable-discussion gl-mb-5 work-item-notes">
- <template v-if="notes && notes.length">
- <ul class="notes main-notes-list timeline">
- <system-note
- v-for="note in notes"
- :key="note.notes.nodes[0].id"
- :note="note.notes.nodes[0]"
+ <div v-else class="issuable-discussion gl-mb-5 gl-clearfix!">
+ <template v-if="showTimeline">
+ <ul class="notes main-notes-list timeline gl-clearfix!">
+ <template v-for="note in notesArray">
+ <system-note
+ v-if="isSystemNote(note)"
+ :key="note.notes.nodes[0].id"
+ :note="note.notes.nodes[0]"
+ />
+ <work-item-note v-else :key="note.notes.nodes[0].id" :note="note.notes.nodes[0]" />
+ </template>
+
+ <work-item-comment-form
+ :query-variables="queryVariables"
+ :full-path="fullPath"
+ :work-item-id="workItemId"
+ :fetch-by-iid="fetchByIid"
+ @error="$emit('error', $event)"
/>
</ul>
</template>
+
+ <template v-if="showLoadingMoreSkeleton">
+ <gl-skeleton-loader
+ v-for="index in $options.loader.repeat"
+ :key="index"
+ :width="$options.loader.width"
+ :height="$options.loader.height"
+ preserve-aspect-ratio="xMinYMax meet"
+ >
+ <circle cx="20" cy="20" r="16" />
+ <rect width="500" x="45" y="15" height="10" rx="4" />
+ </gl-skeleton-loader>
+ </template>
</div>
</div>
</template>