From 3beb10c1c88849c25177f3213d543cd1df8119dc Mon Sep 17 00:00:00 2001 From: Coung Ngo <cngo@gitlab.com> Date: Tue, 5 Dec 2023 08:12:52 +0000 Subject: [PATCH] Fix showing discussions in group work items Behind feature flag namespace_level_work_items --- .../notes/work_item_note_actions.vue | 2 + .../notes/work_item_note_awards_list.vue | 2 + .../work_items/components/work_item_notes.vue | 6 ++- ...group_work_item_notes_by_iid.query.graphql | 32 ++++++++++++ .../work_items/notes/award_utils.js | 5 +- .../notes/work_item_note_actions_spec.js | 1 + .../notes/work_item_note_awards_list_spec.js | 51 +++++++++++-------- .../components/work_item_notes_spec.js | 27 ++++++++++ .../work_items/notes/award_utils_spec.js | 18 +++++++ 9 files changed, 121 insertions(+), 23 deletions(-) create mode 100644 app/assets/javascripts/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue b/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue index cb9a560f9e1bf..445938ad56a1f 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_note_actions.vue @@ -33,6 +33,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + inject: ['isGroup'], props: { fullPath: { type: String, @@ -152,6 +153,7 @@ export default { note: this.note, name, fullPath: this.fullPath, + isGroup: this.isGroup, workItemIid: this.workItemIid, }), }); diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note_awards_list.vue b/app/assets/javascripts/work_items/components/notes/work_item_note_awards_list.vue index 75a8a7b29c0d0..c3b3c0e6db7f7 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_note_awards_list.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_note_awards_list.vue @@ -8,6 +8,7 @@ export default { components: { AwardsList, }, + inject: ['isGroup'], props: { fullPath: { type: String, @@ -73,6 +74,7 @@ export default { note: this.note, name, fullPath: this.fullPath, + isGroup: this.isGroup, workItemIid: this.workItemIid, }), }); 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 6756acd44956a..83958ee4ef36d 100644 --- a/app/assets/javascripts/work_items/components/work_item_notes.vue +++ b/app/assets/javascripts/work_items/components/work_item_notes.vue @@ -28,6 +28,7 @@ import workItemNoteCreatedSubscription from '~/work_items/graphql/notes/work_ite 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 groupWorkItemNotesByIidQuery from '../graphql/notes/group_work_item_notes_by_iid.query.graphql'; import workItemNotesByIidQuery from '../graphql/notes/work_item_notes_by_iid.query.graphql'; import WorkItemAddNote from './notes/work_item_add_note.vue'; @@ -46,6 +47,7 @@ export default { WorkItemNotesActivityHeader, WorkItemHistoryOnlyFilterNote, }, + inject: ['isGroup'], props: { fullPath: { type: String, @@ -169,7 +171,9 @@ export default { }, apollo: { workItemNotes: { - query: workItemNotesByIidQuery, + query() { + return this.isGroup ? groupWorkItemNotesByIidQuery : workItemNotesByIidQuery; + }, variables() { return { fullPath: this.fullPath, diff --git a/app/assets/javascripts/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql b/app/assets/javascripts/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql new file mode 100644 index 0000000000000..f86176b28365c --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql @@ -0,0 +1,32 @@ +#import "~/graphql_shared/fragments/page_info.fragment.graphql" +#import "ee_else_ce/work_items/graphql/notes/work_item_note.fragment.graphql" + +query groupWorkItemNotesByIid($fullPath: ID!, $iid: String, $after: String, $pageSize: Int) { + workspace: group(fullPath: $fullPath) { + id + workItems(iid: $iid) { + nodes { + id + iid + widgets { + ... on WorkItemWidgetNotes { + type + discussions(first: $pageSize, after: $after, filter: ALL_NOTES) { + pageInfo { + ...PageInfo + } + nodes { + id + notes { + nodes { + ...WorkItemNote + } + } + } + } + } + } + } + } + } +} diff --git a/app/assets/javascripts/work_items/notes/award_utils.js b/app/assets/javascripts/work_items/notes/award_utils.js index 5351a22d5934c..4f35b06a68568 100644 --- a/app/assets/javascripts/work_items/notes/award_utils.js +++ b/app/assets/javascripts/work_items/notes/award_utils.js @@ -5,6 +5,7 @@ import { updateCacheAfterAddingAwardEmojiToNote, updateCacheAfterRemovingAwardEmojiFromNote, } from '~/work_items/graphql/cache_utils'; +import groupWorkItemNotesByIidQuery from '../graphql/notes/group_work_item_notes_by_iid.query.graphql'; import workItemNotesByIidQuery from '../graphql/notes/work_item_notes_by_iid.query.graphql'; import addAwardEmojiMutation from '../graphql/notes/work_item_note_add_award_emoji.mutation.graphql'; import removeAwardEmojiMutation from '../graphql/notes/work_item_note_remove_award_emoji.mutation.graphql'; @@ -32,7 +33,7 @@ export function getMutation({ note, name }) { }; } -export function optimisticAwardUpdate({ note, name, fullPath, workItemIid }) { +export function optimisticAwardUpdate({ note, name, fullPath, isGroup, workItemIid }) { const { mutation } = getMutation({ note, name }); const currentUserId = window.gon.current_user_id; @@ -40,7 +41,7 @@ export function optimisticAwardUpdate({ note, name, fullPath, workItemIid }) { return (store) => { store.updateQuery( { - query: workItemNotesByIidQuery, + query: isGroup ? groupWorkItemNotesByIidQuery : workItemNotesByIidQuery, variables: { fullPath, iid: workItemIid }, }, (sourceData) => { diff --git a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js index 596283a959099..c820c60fe13de 100644 --- a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js @@ -64,6 +64,7 @@ describe('Work Item Note Actions', () => { projectName, }, provide: { + isGroup: false, glFeatures: { workItemsMvc2: true, }, diff --git a/spec/frontend/work_items/components/notes/work_item_note_awards_list_spec.js b/spec/frontend/work_items/components/notes/work_item_note_awards_list_spec.js index ce9156359465f..939dd3e870ba3 100644 --- a/spec/frontend/work_items/components/notes/work_item_note_awards_list_spec.js +++ b/spec/frontend/work_items/components/notes/work_item_note_awards_list_spec.js @@ -9,6 +9,7 @@ import AwardsList from '~/vue_shared/components/awards_list.vue'; import WorkItemNoteAwardsList from '~/work_items/components/notes/work_item_note_awards_list.vue'; import addAwardEmojiMutation from '~/work_items/graphql/notes/work_item_note_add_award_emoji.mutation.graphql'; import removeAwardEmojiMutation from '~/work_items/graphql/notes/work_item_note_remove_award_emoji.mutation.graphql'; +import groupWorkItemNotesByIidQuery from '~/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql'; import workItemNotesByIidQuery from '~/work_items/graphql/notes/work_item_notes_by_iid.query.graphql'; import { mockWorkItemNotesResponseWithComments, @@ -45,6 +46,7 @@ describe('Work Item Note Awards List', () => { const findAwardsList = () => wrapper.findComponent(AwardsList); const createComponent = ({ + isGroup = false, note = firstNote, addAwardEmojiMutationHandler = addAwardEmojiMutationSuccessHandler, removeAwardEmojiMutationHandler = removeAwardEmojiMutationSuccessHandler, @@ -55,12 +57,15 @@ describe('Work Item Note Awards List', () => { ]); apolloProvider.clients.defaultClient.writeQuery({ - query: workItemNotesByIidQuery, + query: isGroup ? groupWorkItemNotesByIidQuery : workItemNotesByIidQuery, variables: { fullPath, iid: workItemIid }, ...mockWorkItemNotesResponseWithComments, }); wrapper = shallowMount(WorkItemNoteAwardsList, { + provide: { + isGroup, + }, propsData: { fullPath, workItemIid, @@ -89,17 +94,20 @@ describe('Work Item Note Awards List', () => { expect(findAwardsList().props('canAwardEmoji')).toBe(hasAwardEmojiPermission); }); - it('adds award if not already awarded', async () => { - createComponent(); - await waitForPromises(); + it.each([true, false])( + 'adds award if not already awarded in both group and project contexts', + async (isGroup) => { + createComponent({ isGroup }); + await waitForPromises(); - findAwardsList().vm.$emit('award', EMOJI_THUMBSUP); + findAwardsList().vm.$emit('award', EMOJI_THUMBSUP); - expect(addAwardEmojiMutationSuccessHandler).toHaveBeenCalledWith({ - awardableId: firstNote.id, - name: EMOJI_THUMBSUP, - }); - }); + expect(addAwardEmojiMutationSuccessHandler).toHaveBeenCalledWith({ + awardableId: firstNote.id, + name: EMOJI_THUMBSUP, + }); + }, + ); it('emits error if awarding emoji fails', async () => { createComponent({ @@ -114,20 +122,23 @@ describe('Work Item Note Awards List', () => { expect(wrapper.emitted('error')).toEqual([[__('Failed to add emoji. Please try again')]]); }); - it('removes award if already awarded', async () => { - const removeAwardEmojiMutationHandler = removeAwardEmojiMutationSuccessHandler; + it.each([true, false])( + 'removes award if already awarded in both group and project contexts', + async (isGroup) => { + const removeAwardEmojiMutationHandler = removeAwardEmojiMutationSuccessHandler; - createComponent({ removeAwardEmojiMutationHandler }); + createComponent({ isGroup, removeAwardEmojiMutationHandler }); - findAwardsList().vm.$emit('award', EMOJI_THUMBSDOWN); + findAwardsList().vm.$emit('award', EMOJI_THUMBSDOWN); - await waitForPromises(); + await waitForPromises(); - expect(removeAwardEmojiMutationHandler).toHaveBeenCalledWith({ - awardableId: firstNote.id, - name: EMOJI_THUMBSDOWN, - }); - }); + expect(removeAwardEmojiMutationHandler).toHaveBeenCalledWith({ + awardableId: firstNote.id, + name: EMOJI_THUMBSDOWN, + }); + }, + ); it('restores award if remove fails', async () => { createComponent({ diff --git a/spec/frontend/work_items/components/work_item_notes_spec.js b/spec/frontend/work_items/components/work_item_notes_spec.js index 9e02e0708d48b..2620242000e78 100644 --- a/spec/frontend/work_items/components/work_item_notes_spec.js +++ b/spec/frontend/work_items/components/work_item_notes_spec.js @@ -10,6 +10,7 @@ import WorkItemNotes from '~/work_items/components/work_item_notes.vue'; import WorkItemDiscussion from '~/work_items/components/notes/work_item_discussion.vue'; import WorkItemAddNote from '~/work_items/components/notes/work_item_add_note.vue'; import WorkItemNotesActivityHeader from '~/work_items/components/notes/work_item_notes_activity_header.vue'; +import groupWorkItemNotesByIidQuery from '~/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql'; import workItemNotesByIidQuery from '~/work_items/graphql/notes/work_item_notes_by_iid.query.graphql'; import deleteWorkItemNoteMutation from '~/work_items/graphql/notes/delete_work_item_notes.mutation.graphql'; import workItemNoteCreatedSubscription from '~/work_items/graphql/notes/work_item_note_created.subscription.graphql'; @@ -63,6 +64,9 @@ describe('WorkItemNotes component', () => { const findWorkItemCommentNoteAtIndex = (index) => findAllWorkItemCommentNotes().at(index); const findDeleteNoteModal = () => wrapper.findComponent(GlModal); + const groupWorkItemNotesQueryHandler = jest + .fn() + .mockResolvedValue(mockWorkItemNotesByIidResponse); const workItemNotesQueryHandler = jest.fn().mockResolvedValue(mockWorkItemNotesByIidResponse); const workItemMoreNotesQueryHandler = jest.fn().mockResolvedValue(mockMoreWorkItemNotesResponse); const workItemNotesWithCommentsQueryHandler = jest @@ -87,17 +91,22 @@ describe('WorkItemNotes component', () => { workItemIid = mockWorkItemIid, defaultWorkItemNotesQueryHandler = workItemNotesQueryHandler, deleteWINoteMutationHandler = deleteWorkItemNoteMutationSuccessHandler, + isGroup = false, isModal = false, isWorkItemConfidential = false, } = {}) => { wrapper = shallowMount(WorkItemNotes, { apolloProvider: createMockApollo([ [workItemNotesByIidQuery, defaultWorkItemNotesQueryHandler], + [groupWorkItemNotesByIidQuery, groupWorkItemNotesQueryHandler], [deleteWorkItemNoteMutation, deleteWINoteMutationHandler], [workItemNoteCreatedSubscription, notesCreateSubscriptionHandler], [workItemNoteUpdatedSubscription, notesUpdateSubscriptionHandler], [workItemNoteDeletedSubscription, notesDeleteSubscriptionHandler], ]), + provide: { + isGroup, + }, propsData: { fullPath: 'test-path', workItemId, @@ -354,4 +363,22 @@ describe('WorkItemNotes component', () => { expect(findWorkItemCommentNoteAtIndex(0).props('isWorkItemConfidential')).toBe(true); }); + + describe('when project context', () => { + it('calls the project work item query', async () => { + createComponent(); + await waitForPromises(); + + expect(workItemNotesQueryHandler).toHaveBeenCalled(); + }); + }); + + describe('when group context', () => { + it('calls the group work item query', async () => { + createComponent({ isGroup: true }); + await waitForPromises(); + + expect(groupWorkItemNotesQueryHandler).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/work_items/notes/award_utils_spec.js b/spec/frontend/work_items/notes/award_utils_spec.js index 8ae32ce5f4081..43eceb13b67c2 100644 --- a/spec/frontend/work_items/notes/award_utils_spec.js +++ b/spec/frontend/work_items/notes/award_utils_spec.js @@ -2,6 +2,7 @@ import { getMutation, optimisticAwardUpdate } from '~/work_items/notes/award_uti import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import mockApollo from 'helpers/mock_apollo_helper'; import { __ } from '~/locale'; +import groupWorkItemNotesByIidQuery from '~/work_items/graphql/notes/group_work_item_notes_by_iid.query.graphql'; import workItemNotesByIidQuery from '~/work_items/graphql/notes/work_item_notes_by_iid.query.graphql'; import addAwardEmojiMutation from '~/work_items/graphql/notes/work_item_note_add_award_emoji.mutation.graphql'; import removeAwardEmojiMutation from '~/work_items/graphql/notes/work_item_note_remove_award_emoji.mutation.graphql'; @@ -105,5 +106,22 @@ describe('Work item note award utils', () => { expect(updatedNote.awardEmoji.nodes).toEqual([]); }); + + it.each` + description | isGroup | query + ${'calls project query when in project context'} | ${false} | ${workItemNotesByIidQuery} + ${'calls group query when in group context'} | ${true} | ${groupWorkItemNotesByIidQuery} + `('$description', ({ isGroup, query }) => { + const note = firstNote; + const { name } = mockAwardEmojiThumbsUp; + const cacheSpy = { updateQuery: jest.fn() }; + + optimisticAwardUpdate({ note, name, fullPath, isGroup, workItemIid })(cacheSpy); + + expect(cacheSpy.updateQuery).toHaveBeenCalledWith( + { query, variables: { fullPath, iid: workItemIid } }, + expect.any(Function), + ); + }); }); }); -- GitLab