diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index 9372618fb2a62c2b6509dced423a77dbd885db9b..db3008c17a4570a3d670455842f1cf0a41133c49 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -106,6 +106,7 @@ import { getSortOptions, isSortKey, mapWorkItemWidgetsToIssueFields, + updateUpvotesCount, } from '../utils'; import { hasNewIssueDropdown } from '../has_new_issue_dropdown_mixin'; import EmptyStateWithAnyIssues from './empty_state_with_any_issues.vue'; @@ -885,6 +886,17 @@ export default { Sentry.captureException(error); }); }, + updateIssuableEmojis(workItem) { + const client = this.$apollo.provider.clients.defaultClient; + const issuesList = client.readQuery({ + query: getIssuesQuery, + variables: this.queryVariables, + }); + + const data = updateUpvotesCount(issuesList, workItem); + + client.writeQuery({ query: getIssuesQuery, variables: this.queryVariables, data }); + }, }, }; </script> @@ -908,6 +920,7 @@ export default { :key="activeIssuable.iid" :work-item-iid="activeIssuable.iid" @work-item-updated="updateIssuablesCache" + @work-item-emoji-updated="updateIssuableEmojis" @addChild="refetchIssuables" @deleteWorkItem="deleteIssuable" @promotedToObjective="promoteToObjective" diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js index 8859021b16ca272db9c72c1ceb4e5bf2a06856bb..ad88182da1d22ace854b24af35c24fac86a9435f 100644 --- a/app/assets/javascripts/issues/list/utils.js +++ b/app/assets/javascripts/issues/list/utils.js @@ -364,19 +364,6 @@ export function mapWorkItemWidgetsToIssueFields(issuesList, workItem) { activeItem[property] = { __persist: true, ...currentWidget[property] }; return; } - - // handling emojis - if (property === WORK_ITEM_TO_ISSUE_MAP[WIDGET_TYPE_AWARD_EMOJI]) { - const upvotesCount = - currentWidget[property].nodes.filter((emoji) => emoji.name === EMOJI_THUMBSUP)?.length ?? - 0; - const downvotesCount = - currentWidget[property].nodes.filter((emoji) => emoji.name === EMOJI_THUMBSDOWN) - ?.length ?? 0; - activeItem.upvotes = upvotesCount; - activeItem.downvotes = downvotesCount; - return; - } activeItem[property] = currentWidget[property]; }); @@ -384,3 +371,24 @@ export function mapWorkItemWidgetsToIssueFields(issuesList, workItem) { activeItem.confidential = workItem.confidential; }); } + +export function updateUpvotesCount(issuesList, workItem) { + const type = WIDGET_TYPE_AWARD_EMOJI; + const property = WORK_ITEM_TO_ISSUE_MAP[type]; + + return produce(issuesList, (draftData) => { + const activeItem = draftData.project.issues.nodes.find((issue) => issue.iid === workItem.iid); + + const currentWidget = findWidget(type, workItem); + if (!currentWidget) { + return; + } + + const upvotesCount = + currentWidget[property].nodes.filter((emoji) => emoji.name === EMOJI_THUMBSUP)?.length ?? 0; + const downvotesCount = + currentWidget[property].nodes.filter((emoji) => emoji.name === EMOJI_THUMBSDOWN)?.length ?? 0; + activeItem.upvotes = upvotesCount; + activeItem.downvotes = downvotesCount; + }); +} diff --git a/app/assets/javascripts/work_items/components/work_item_award_emoji.vue b/app/assets/javascripts/work_items/components/work_item_award_emoji.vue index 1b1d308f9ca1d62482ccbfac1da1d086414c1440..44bd17b59a2a7eec8dca963eaf5b6132b2152f9c 100644 --- a/app/assets/javascripts/work_items/components/work_item_award_emoji.vue +++ b/app/assets/javascripts/work_items/components/work_item_award_emoji.vue @@ -91,12 +91,15 @@ export default { skip() { return !this.workItemIid; }, - result() { + result({ data }) { if (this.hasNextPage) { this.fetchAwardEmojis(); } else { this.isLoading = false; } + if (data) { + this.$emit('emoji-updated', data.workspace?.workItems?.nodes[0]); + } }, error() { this.$emit('error', I18N_WORK_ITEM_FETCH_AWARD_EMOJI_ERROR); diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 13c4aff09da959c725ad7c5d8ccbc651cda84a58..04e7814e6507dce3365e404a9fe18023e5c303ea 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -576,6 +576,7 @@ export default { :award-emoji="workItemAwardEmoji.awardEmoji" :work-item-iid="workItemIid" @error="updateError = $event" + @emoji-updated="$emit('work-item-emoji-updated', $event)" /> <work-item-tree v-if="workItemType === $options.WORK_ITEM_TYPE_VALUE_OBJECTIVE" diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index f8f57934303899d35e9ac8febd3f6e2ae4c59b15..8bfad64c369023ff8657f2fa07b06f0cd705e69a 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,6 +1,7 @@ - page_title _('Issues') - add_page_specific_style 'page_bundles/issuable_list' - add_page_specific_style 'page_bundles/issues_list' +- add_page_specific_style 'page_bundles/work_items' = content_for :meta_tags do = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues") diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js index 31c6df953c6ce9b7ba0a526cedd0d4b6d7ec6ef8..2a0b002ae1ff7847b3d137584fa2324fa2effe03 100644 --- a/spec/frontend/issues/list/components/issues_list_app_spec.js +++ b/spec/frontend/issues/list/components/issues_list_app_spec.js @@ -74,6 +74,9 @@ import WorkItemDetail from '~/work_items/components/work_item_detail.vue'; import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql'; import { workItemResponseFactory, + workItemByIidResponseFactory, + mockAwardEmojiThumbsUp, + mockAwardsWidget, mockAssignees, mockLabels, mockMilestone, @@ -1137,6 +1140,22 @@ describe('CE IssuesListApp component', () => { ); }); + it('updates the upvotes count of active issuable', async () => { + const workItem = workItemByIidResponseFactory({ + iid: '789', + awardEmoji: { + ...mockAwardsWidget, + nodes: [mockAwardEmojiThumbsUp], + }, + }).data.workspace.workItems.nodes[0]; + + findDrawerWorkItem().vm.$emit('work-item-emoji-updated', workItem); + + await waitForPromises(); + + expect(findIssuableList().props('issuables')[0].upvotes).toBe(1); + }); + it('updates the milestone field of active issuable', async () => { const { data: { workItem },