summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/artifacts/components/job_artifacts_table.vue8
-rw-r--r--app/assets/javascripts/work_items/components/notes/activity_filter.vue112
-rw-r--r--app/assets/javascripts/work_items/components/notes/activity_sort.vue99
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_history_only_filter_note.vue61
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_notes_activity_header.vue67
-rw-r--r--app/assets/javascripts/work_items/components/work_item_notes.vue73
-rw-r--r--app/assets/javascripts/work_items/constants.js6
7 files changed, 346 insertions, 80 deletions
diff --git a/app/assets/javascripts/artifacts/components/job_artifacts_table.vue b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue
index a7331cc1fd3..1b7782c6860 100644
--- a/app/assets/javascripts/artifacts/components/job_artifacts_table.vue
+++ b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue
@@ -68,9 +68,8 @@ export default {
variables() {
return this.queryVariables;
},
- update({ project: { jobs: { nodes = [], pageInfo = {}, count = 0 } = {} } }) {
+ update({ project: { jobs: { nodes = [], pageInfo = {} } = {} } }) {
this.pageInfo = pageInfo;
- this.count = count;
return nodes
.map(mapArchivesToJobNodes)
.map(mapBooleansToJobNodes)
@@ -93,7 +92,6 @@ export default {
data() {
return {
jobArtifacts: [],
- count: 0,
pageInfo: {},
expandedJobs: [],
pagination: INITIAL_PAGINATION_STATE,
@@ -110,7 +108,9 @@ export default {
};
},
showPagination() {
- return this.count > JOBS_PER_PAGE;
+ const { hasNextPage, hasPreviousPage } = this.pageInfo;
+
+ return hasNextPage || hasPreviousPage;
},
prevPage() {
return Number(this.pageInfo.hasPreviousPage);
diff --git a/app/assets/javascripts/work_items/components/notes/activity_filter.vue b/app/assets/javascripts/work_items/components/notes/activity_filter.vue
index 71784d3a807..6d5535797ef 100644
--- a/app/assets/javascripts/work_items/components/notes/activity_filter.vue
+++ b/app/assets/javascripts/work_items/components/notes/activity_filter.vue
@@ -1,18 +1,35 @@
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { __ } from '~/locale';
+import { s__ } from '~/locale';
import Tracking from '~/tracking';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-import { ASC, DESC } from '~/notes/constants';
-import { TRACKING_CATEGORY_SHOW, WORK_ITEM_NOTES_SORT_ORDER_KEY } from '~/work_items/constants';
+import {
+ WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS,
+ WORK_ITEM_NOTES_FILTER_ONLY_HISTORY,
+ TRACKING_CATEGORY_SHOW,
+ WORK_ITEM_NOTES_FILTER_KEY,
+} from '~/work_items/constants';
-const SORT_OPTIONS = [
- { key: DESC, text: __('Newest first'), dataid: 'js-newest-first' },
- { key: ASC, text: __('Oldest first'), dataid: 'js-oldest-first' },
+const filterOptions = [
+ {
+ key: WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ text: s__('WorkItem|All activity'),
+ },
+ {
+ key: WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS,
+ text: s__('WorkItem|Comments only'),
+ testid: 'comments-activity',
+ },
+ {
+ key: WORK_ITEM_NOTES_FILTER_ONLY_HISTORY,
+ text: s__('WorkItem|History only'),
+ testid: 'history-activity',
+ },
];
export default {
- SORT_OPTIONS,
+ filterOptions,
components: {
GlDropdown,
GlDropdownItem,
@@ -20,11 +37,6 @@ export default {
},
mixins: [Tracking.mixin()],
props: {
- sortOrder: {
- type: String,
- default: ASC,
- required: false,
- },
loading: {
type: Boolean,
default: false,
@@ -34,80 +46,74 @@ export default {
type: String,
required: true,
},
- },
- data() {
- return {
- persistSortOrder: true,
- };
+ discussionFilter: {
+ type: String,
+ default: WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ required: false,
+ },
},
computed: {
tracking() {
return {
category: TRACKING_CATEGORY_SHOW,
- label: 'item_track_notes_sorting',
+ label: 'item_track_notes_filtering',
property: `type_${this.workItemType}`,
};
},
- selectedSortOption() {
- const isSortOptionValid = this.sortOrder === ASC || this.sortOrder === DESC;
- return isSortOptionValid ? SORT_OPTIONS.find(({ key }) => this.sortOrder === key) : ASC;
- },
getDropdownSelectedText() {
return this.selectedSortOption.text;
},
+ selectedSortOption() {
+ return (
+ filterOptions.find(({ key }) => this.discussionFilter === key) ||
+ WORK_ITEM_NOTES_FILTER_ALL_NOTES
+ );
+ },
},
methods: {
- setDiscussionSortDirection(direction) {
- this.$emit('updateSavedSortOrder', direction);
+ setDiscussionFilterOption(filterValue) {
+ this.$emit('changeFilter', filterValue);
},
- fetchSortedDiscussions(direction) {
- if (this.isSortDropdownItemActive(direction)) {
+ fetchFilteredDiscussions(filterValue) {
+ if (this.isSortDropdownItemActive(filterValue)) {
return;
}
- this.track('notes_sort_order_changed');
- this.$emit('changeSortOrder', direction);
+ this.track('work_item_notes_filter_changed');
+ this.$emit('changeFilter', filterValue);
},
- isSortDropdownItemActive(sortDir) {
- return sortDir === this.sortOrder;
+ isSortDropdownItemActive(discussionFilter) {
+ return discussionFilter === this.discussionFilter;
},
},
- WORK_ITEM_NOTES_SORT_ORDER_KEY,
+ WORK_ITEM_NOTES_FILTER_KEY,
};
</script>
<template>
- <div
- id="discussion-preferences"
- data-testid="discussion-preferences"
- class="gl-display-inline-block gl-vertical-align-bottom gl-w-full gl-sm-w-auto"
- >
+ <div class="gl-display-inline-block gl-vertical-align-bottom">
<local-storage-sync
- :value="sortOrder"
- :storage-key="$options.WORK_ITEM_NOTES_SORT_ORDER_KEY"
- :persist="persistSortOrder"
+ :value="discussionFilter"
+ :storage-key="$options.WORK_ITEM_NOTES_FILTER_KEY"
as-string
- @input="setDiscussionSortDirection"
+ @input="setDiscussionFilterOption"
/>
<gl-dropdown
- :id="`discussion-preferences-dropdown-${workItemType}`"
class="gl-xs-w-full"
size="small"
:text="getDropdownSelectedText"
:disabled="loading"
right
>
- <div id="discussion-sort">
- <gl-dropdown-item
- v-for="{ text, key, dataid } in $options.SORT_OPTIONS"
- :key="text"
- :data-testid="dataid"
- is-check-item
- :is-checked="isSortDropdownItemActive(key)"
- @click="fetchSortedDiscussions(key)"
- >
- {{ text }}
- </gl-dropdown-item>
- </div>
+ <gl-dropdown-item
+ v-for="{ text, key, testid } in $options.filterOptions"
+ :key="text"
+ :data-testid="testid"
+ is-check-item
+ :is-checked="isSortDropdownItemActive(key)"
+ @click="fetchFilteredDiscussions(key)"
+ >
+ {{ text }}
+ </gl-dropdown-item>
</gl-dropdown>
</div>
</template>
diff --git a/app/assets/javascripts/work_items/components/notes/activity_sort.vue b/app/assets/javascripts/work_items/components/notes/activity_sort.vue
new file mode 100644
index 00000000000..bfbb2b65346
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/notes/activity_sort.vue
@@ -0,0 +1,99 @@
+<script>
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { __ } from '~/locale';
+import Tracking from '~/tracking';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+import { ASC, DESC } from '~/notes/constants';
+import { TRACKING_CATEGORY_SHOW, WORK_ITEM_NOTES_SORT_ORDER_KEY } from '~/work_items/constants';
+
+const sortOptions = [
+ { key: DESC, text: __('Newest first'), testid: 'newest-first' },
+ { key: ASC, text: __('Oldest first') },
+];
+
+export default {
+ sortOptions,
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ LocalStorageSync,
+ },
+ mixins: [Tracking.mixin()],
+ props: {
+ sortOrder: {
+ type: String,
+ default: ASC,
+ required: false,
+ },
+ loading: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ workItemType: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ tracking() {
+ return {
+ category: TRACKING_CATEGORY_SHOW,
+ label: 'item_track_notes_sorting',
+ property: `type_${this.workItemType}`,
+ };
+ },
+ selectedSortOption() {
+ return sortOptions.find(({ key }) => this.sortOrder === key) || ASC;
+ },
+ getDropdownSelectedText() {
+ return this.selectedSortOption.text;
+ },
+ },
+ methods: {
+ setDiscussionSortDirection(direction) {
+ this.$emit('changeSort', direction);
+ },
+ fetchSortedDiscussions(direction) {
+ if (this.isSortDropdownItemActive(direction)) {
+ return;
+ }
+ this.track('work_item_notes_sort_order_changed');
+ this.$emit('changeSort', direction);
+ },
+ isSortDropdownItemActive(sortDir) {
+ return sortDir === this.sortOrder;
+ },
+ },
+ WORK_ITEM_NOTES_SORT_ORDER_KEY,
+};
+</script>
+
+<template>
+ <div class="gl-display-inline-block gl-vertical-align-bottom">
+ <local-storage-sync
+ :value="sortOrder"
+ :storage-key="$options.WORK_ITEM_NOTES_SORT_ORDER_KEY"
+ as-string
+ @input="setDiscussionSortDirection"
+ />
+ <gl-dropdown
+ class="gl-xs-w-full"
+ size="small"
+ :text="getDropdownSelectedText"
+ :disabled="loading"
+ right
+ >
+ <gl-dropdown-item
+ v-for="{ text, key, testid } in $options.sortOptions"
+ :key="text"
+ :data-testid="testid"
+ is-check-item
+ :is-checked="isSortDropdownItemActive(key)"
+ @click="fetchSortedDiscussions(key)"
+ >
+ {{ text }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/work_items/components/notes/work_item_history_only_filter_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_history_only_filter_note.vue
new file mode 100644
index 00000000000..07e25312f87
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/notes/work_item_history_only_filter_note.vue
@@ -0,0 +1,61 @@
+<script>
+import { GlButton, GlIcon, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+import {
+ WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS,
+} from '~/work_items/constants';
+
+export default {
+ WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS,
+ i18n: {
+ information: s__(
+ "WorkItem|You're only seeing %{boldStart}other activity%{boldEnd} in the feed. To add a comment, switch to one of the following options.",
+ ),
+ },
+ components: {
+ GlButton,
+ GlIcon,
+ GlSprintf,
+ },
+ methods: {
+ selectFilter(value) {
+ this.$emit('changeFilter', value);
+ },
+ },
+};
+</script>
+
+<template>
+ <li class="timeline-entry note note-wrapper discussion-filter-note">
+ <div class="timeline-icon gl-display-none gl-lg-display-flex">
+ <gl-icon name="comment" />
+ </div>
+ <div class="timeline-content gl-pl-8">
+ <gl-sprintf :message="$options.i18n.information">
+ <template #bold="{ content }">
+ <b>{{ content }}</b>
+ </template>
+ </gl-sprintf>
+
+ <div class="discussion-filter-actions">
+ <gl-button
+ class="gl-mr-2 gl-mt-3"
+ data-testid="show-all-activity"
+ @click="selectFilter($options.WORK_ITEM_NOTES_FILTER_ALL_NOTES)"
+ >
+ {{ __('Show all activity') }}
+ </gl-button>
+ <gl-button
+ class="gl-mt-3"
+ data-testid="show-comments-only"
+ @click="selectFilter($options.WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS)"
+ >
+ {{ __('Show comments only') }}
+ </gl-button>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/work_items/components/notes/work_item_notes_activity_header.vue b/app/assets/javascripts/work_items/components/notes/work_item_notes_activity_header.vue
new file mode 100644
index 00000000000..e700d5353e2
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/notes/work_item_notes_activity_header.vue
@@ -0,0 +1,67 @@
+<script>
+import ActivitySort from '~/work_items/components/notes/activity_sort.vue';
+import ActivityFilter from '~/work_items/components/notes/activity_filter.vue';
+import { s__ } from '~/locale';
+import { ASC } from '~/notes/constants';
+import { WORK_ITEM_NOTES_FILTER_ALL_NOTES } from '~/work_items/constants';
+
+export default {
+ i18n: {
+ activityLabel: s__('WorkItem|Activity'),
+ },
+ components: {
+ ActivitySort,
+ ActivityFilter,
+ },
+ props: {
+ disableActivityFilterSort: {
+ type: Boolean,
+ required: true,
+ },
+ sortOrder: {
+ type: String,
+ default: ASC,
+ required: false,
+ },
+ workItemType: {
+ type: String,
+ required: true,
+ },
+ discussionFilter: {
+ type: String,
+ default: WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ required: false,
+ },
+ },
+ methods: {
+ changeNotesSortOrder(direction) {
+ this.$emit('changeSort', direction);
+ },
+ filterDiscussions(filterValue) {
+ this.$emit('changeFilter', filterValue);
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="gl-display-flex gl-justify-content-space-between gl-flex-wrap gl-pb-3 gl-align-items-center"
+ >
+ <h3 class="gl-font-base gl-m-0">{{ $options.i18n.activityLabel }}</h3>
+ <div class="gl-display-flex gl-gap-3">
+ <activity-filter
+ :loading="disableActivityFilterSort"
+ :work-item-type="workItemType"
+ :discussion-filter="discussionFilter"
+ @changeFilter="filterDiscussions"
+ />
+ <activity-sort
+ :loading="disableActivityFilterSort"
+ :sort-order="sortOrder"
+ :work-item-type="workItemType"
+ @changeSort="changeNotesSortOrder"
+ />
+ </div>
+ </div>
+</template>
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 aa6dd9b5184..331d0b5e8d0 100644
--- a/app/assets/javascripts/work_items/components/work_item_notes.vue
+++ b/app/assets/javascripts/work_items/components/work_item_notes.vue
@@ -1,11 +1,17 @@
<script>
import { GlSkeletonLoader, GlModal } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
-import { s__, __ } from '~/locale';
+import { __ } from '~/locale';
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 {
@@ -13,6 +19,7 @@ import {
updateCacheAfterDeletingNote,
} from '~/work_items/graphql/cache_utils';
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';
@@ -20,9 +27,6 @@ import deleteNoteMutation from '../graphql/notes/delete_work_item_notes.mutation
import WorkItemAddNote from './notes/work_item_add_note.vue';
export default {
- i18n: {
- ACTIVITY_LABEL: s__('WorkItem|Activity'),
- },
loader: {
repeat: 10,
width: 1000,
@@ -31,10 +35,11 @@ export default {
components: {
GlSkeletonLoader,
GlModal,
- ActivityFilter,
SystemNote,
WorkItemAddNote,
WorkItemDiscussion,
+ WorkItemNotesActivityHeader,
+ WorkItemHistoryOnlyFilterNote,
},
props: {
workItemId: {
@@ -65,6 +70,7 @@ export default {
perPage: DEFAULT_PAGE_SIZE_NOTES,
sortOrder: ASC,
noteToDelete: null,
+ discussionFilter: WORK_ITEM_NOTES_FILTER_ALL_NOTES,
};
},
computed: {
@@ -83,7 +89,7 @@ export default {
showLoadingMoreSkeleton() {
return this.isLoadingMore && !this.changeNotesSortOrderAfterLoading;
},
- disableActivityFilter() {
+ disableActivityFilterSort() {
return this.initialLoading || this.isLoadingMore;
},
formAtTop() {
@@ -102,10 +108,27 @@ export default {
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;
},
},
apollo: {
@@ -210,6 +233,9 @@ export default {
changeNotesSortOrder(direction) {
this.sortOrder = direction;
},
+ filterDiscussions(filterValue) {
+ this.discussionFilter = filterValue;
+ },
async fetchMoreNotes() {
this.isLoadingMore = true;
// copied from discussions batch logic - every fetchMore call has a higher
@@ -271,17 +297,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"
@@ -298,7 +321,7 @@ export default {
<template v-if="!initialLoading">
<ul class="notes main-notes-list timeline gl-clearfix!">
<work-item-add-note
- v-if="formAtTop"
+ v-if="formAtTop && !commentsDisabled"
v-bind="workItemCommentFormProps"
@error="$emit('error', $event)"
/>
@@ -325,10 +348,14 @@ export default {
</template>
<work-item-add-note
- v-if="!formAtTop"
+ v-if="!formAtTop && !commentsDisabled"
v-bind="workItemCommentFormProps"
@error="$emit('error', $event)"
/>
+ <work-item-history-only-filter-note
+ v-if="commentsDisabled"
+ @changeFilter="filterDiscussions"
+ />
</ul>
</template>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index 81f9bf04bc8..b372f2d6f7b 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -176,3 +176,9 @@ export const DEFAULT_PAGE_SIZE_ASSIGNEES = 10;
export const DEFAULT_PAGE_SIZE_NOTES = 30;
export const WORK_ITEM_NOTES_SORT_ORDER_KEY = 'sort_direction_work_item';
+
+export const WORK_ITEM_NOTES_FILTER_ALL_NOTES = 'ALL_NOTES';
+export const WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS = 'ONLY_COMMENTS';
+export const WORK_ITEM_NOTES_FILTER_ONLY_HISTORY = 'ONLY_HISTORY';
+
+export const WORK_ITEM_NOTES_FILTER_KEY = 'filter_key_work_item';