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]);