diff --git a/app/assets/javascripts/notes/components/mr_discussion_filter.vue b/app/assets/javascripts/notes/components/mr_discussion_filter.vue index c2ac95ca56eb742ec8ed24a7ff6de62538c1f744..4d78a888be70569210ab92193daaa71944a328c1 100644 --- a/app/assets/javascripts/notes/components/mr_discussion_filter.vue +++ b/app/assets/javascripts/notes/components/mr_discussion_filter.vue @@ -85,7 +85,7 @@ export default { /> diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js index 999ef8ff905784bb8375ecb87532a1053018d29c..464c1cd181ed1928dd8e257a2f9164f355da8790 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -76,6 +76,11 @@ export const MR_FILTER_OPTIONS = [ s__('IssuableEvents|unassigned'), ], }, + { + text: __('Bot comments'), + value: 'bot_comments', + bot: true, + }, { text: __('Comments'), value: 'comments', diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index 62d991c2d9e8c135ba06534c6ddea9e976ca20e7..56bbb44df5396691f12f77b2b47b38139316f1c3 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -33,13 +33,15 @@ const hideActivity = (filters, discussion) => { // the first in a discussion or a single note // If the filter option filters based on icon check against the first notes system note icon f.systemNoteIcons?.includes(firstNote.system_note_icon_name) || - // If the filter option filters based on note type user the first notes type - f.noteType?.includes(firstNote.type) || + // If the filter option filters based on note type use the first notes type + (f.noteType?.includes(firstNote.type) && !firstNote.author.bot) || // If the filter option filters based on the note text then check if it is sytem // and filter based on the text of the system note (firstNote.system && f.noteText?.some((t) => firstNote.note.includes(t))) || // For individual notes we filter if the discussion is a single note and is not a sytem - (f.individualNote === discussion.individual_note && !firstNote.system) + (f.individualNote === discussion.individual_note && !firstNote.system) || + // For bot comments we filter on the authors `bot` boolean attribute + (f.bot && firstNote.author.bot) ) { return true; } diff --git a/app/serializers/note_user_entity.rb b/app/serializers/note_user_entity.rb index c3f14fb0f9e94251d63176f3d66190a17138c569..f89203d4cf8c61367e50dfdb093ff26912a7942e 100644 --- a/app/serializers/note_user_entity.rb +++ b/app/serializers/note_user_entity.rb @@ -2,6 +2,8 @@ class NoteUserEntity < UserEntity unexpose :web_url + + expose :bot?, as: :bot end NoteUserEntity.prepend_mod_with('NoteUserEntity') diff --git a/ee/spec/fixtures/api/schemas/entities/note_user_entity.json b/ee/spec/fixtures/api/schemas/entities/note_user_entity.json index 0642579da22ced3d5be8cf5bb4d5dd5bd0fc3bfa..6a6d4518a691205d301a7fa66d36ce13b22bf96b 100644 --- a/ee/spec/fixtures/api/schemas/entities/note_user_entity.json +++ b/ee/spec/fixtures/api/schemas/entities/note_user_entity.json @@ -35,6 +35,9 @@ }, "show_status": { "type": "boolean" + }, + "bot": { + "type": "boolean" } }, "additionalProperties": false, @@ -45,7 +48,8 @@ "avatar_url", "path", "name", - "username" + "username", + "bot" ] } ] diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7595bc3785bf6031d8d7be6ba1f703094490242d..75fa4cc0a2117f5cdea426cffda2a42c339aaafd 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8539,6 +8539,9 @@ msgstr "" msgid "Bot" msgstr "" +msgid "Bot comments" +msgstr "" + msgid "Both SSH and HTTP(S)" msgstr "" diff --git a/spec/fixtures/api/schemas/entities/note_user_entity.json b/spec/fixtures/api/schemas/entities/note_user_entity.json index 16d33ada51d8199af4a4aaca091b75b9fa524445..af5e6fa53a90e1b6cd6e8af3021db53f1e8ccd2b 100644 --- a/spec/fixtures/api/schemas/entities/note_user_entity.json +++ b/spec/fixtures/api/schemas/entities/note_user_entity.json @@ -7,7 +7,8 @@ "avatar_url", "path", "name", - "username" + "username", + "bot" ], "properties": { "id": { @@ -39,6 +40,9 @@ }, "show_status": { "type": "boolean" + }, + "bot": { + "type": "boolean" } } } diff --git a/spec/frontend/notes/components/mr_discussion_filter_spec.js b/spec/frontend/notes/components/mr_discussion_filter_spec.js index 05576d2ccc6b4928491e0248bbede562bf218890..73fd6da811dfaf5faa92b331779c4ce7eba5de49 100644 --- a/spec/frontend/notes/components/mr_discussion_filter_spec.js +++ b/spec/frontend/notes/components/mr_discussion_filter_spec.js @@ -57,7 +57,7 @@ describe('Merge request discussion filter component', () => { describe('local sync sort filters', () => { it('calls setDiscussionSortDirection when mounted', () => { - localStorage.setItem('mr_activity_filters', '["comments"]'); + localStorage.setItem('mr_activity_filters_2', '["comments"]'); createComponent(); @@ -82,6 +82,7 @@ describe('Merge request discussion filter component', () => { expect(updateMergeRequestFilters).toHaveBeenCalledWith(expect.anything(), [ 'assignees_reviewers', + 'bot_comments', 'comments', 'commit_branches', 'edits', @@ -126,12 +127,12 @@ describe('Merge request discussion filter component', () => { await nextTick(); - expect(wrapper.findAll('[aria-selected="true"]')).toHaveLength(9); + expect(wrapper.findAll('[aria-selected="true"]')).toHaveLength(10); wrapper.find('[data-testid="listbox-select-all-button"]').vm.$emit('click'); await nextTick(); - expect(wrapper.findAll('[aria-selected="true"]')).toHaveLength(10); + expect(wrapper.findAll('[aria-selected="true"]')).toHaveLength(11); }); }); diff --git a/spec/frontend/notes/stores/getters_spec.js b/spec/frontend/notes/stores/getters_spec.js index 1514602d424e536739d6020d40f79547e33e5cc5..8eebfeff553b8ab393daf5bece824eaf4a1548fc 100644 --- a/spec/frontend/notes/stores/getters_spec.js +++ b/spec/frontend/notes/stores/getters_spec.js @@ -87,6 +87,19 @@ describe('Getters Notes Store', () => { const getDiscussions = () => getters.discussions(state, {}, { batchComments }); + describe('merge request filters', () => { + it('returns only bot comments', () => { + const discussion = JSON.parse(JSON.stringify(discussionMock)); + discussion.notes[0].author.bot = true; + + state.noteableData = { targetType: 'merge_request' }; + state.discussions = [discussion]; + state.mergeRequestFilters = ['bot_comments']; + + expect(getDiscussions()).toContain(discussion); + }); + }); + describe('without batchComments module', () => { it('should return all discussions in the store', () => { expect(getDiscussions()).toEqual([individualNote]);