diff --git a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue index cd1aae155d29db5d80d653fd91ea7269c074d860..031de6694890f446eced5cd15b46d805cd12324e 100644 --- a/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue +++ b/app/assets/javascripts/sidebar/components/attention_requested_toggle.vue @@ -47,6 +47,23 @@ export default { return this.$options.i18n.noAttentionRequestedNoPermission; }, + request() { + const state = { + variant: 'default', + icon: 'attention', + direction: 'add', + }; + + if (this.user.attention_requested) { + Object.assign(state, { + variant: 'warning', + icon: 'attention-solid', + direction: 'remove', + }); + } + + return state; + }, }, methods: { toggleAttentionRequired() { @@ -57,6 +74,7 @@ export default { this.$emit('toggle-attention-requested', { user: this.user, callback: this.toggleAttentionRequiredComplete, + direction: this.request.direction, }); }, toggleAttentionRequiredComplete() { @@ -74,8 +92,8 @@ export default { > this.service.requestAttention(id), + remove: (id) => this.service.removeAttentionRequest(id), + }; + try { const isReviewer = type === 'reviewer'; const reviewerOrAssignee = isReviewer ? this.store.findReviewer(user) : this.store.findAssignee(user); - await this.service.toggleAttentionRequested(user.id); + await mutations[direction]?.(user.id); if (reviewerOrAssignee.attention_requested) { toast( @@ -138,7 +143,7 @@ export default class SidebarMediator { captureError: true, actionConfig: { title: __('Try again'), - clickHandler: () => this.toggleAttentionRequired(type, { user, callback }), + clickHandler: () => this.toggleAttentionRequired(type, { user, callback, direction }), }, }); } diff --git a/spec/frontend/sidebar/components/attention_requested_toggle_spec.js b/spec/frontend/sidebar/components/attention_requested_toggle_spec.js index e3eabc6a0f6ab86070e1cdb94c810baf1fbccd86..959fa799eb737ce83e8882eef2c7755741ea3ca8 100644 --- a/spec/frontend/sidebar/components/attention_requested_toggle_spec.js +++ b/spec/frontend/sidebar/components/attention_requested_toggle_spec.js @@ -68,6 +68,7 @@ describe('Attention require toggle', () => { { user: { attention_requested: true, can_update_merge_request: true }, callback: expect.anything(), + direction: 'remove', }, ]); }); diff --git a/spec/frontend/sidebar/sidebar_mediator_spec.js b/spec/frontend/sidebar/sidebar_mediator_spec.js index c472a98bf0b11cbe4440bd595b41584be32db3de..82fb10ab1d27bca180b487c7d292e5cc6cff60a8 100644 --- a/spec/frontend/sidebar/sidebar_mediator_spec.js +++ b/spec/frontend/sidebar/sidebar_mediator_spec.js @@ -1,4 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import * as urlUtility from '~/lib/utils/url_utility'; import SidebarService, { gqClient } from '~/sidebar/services/sidebar_service'; @@ -8,6 +9,7 @@ import toast from '~/vue_shared/plugins/global_toast'; import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import Mock from './mock_data'; +jest.mock('~/flash'); jest.mock('~/vue_shared/plugins/global_toast'); jest.mock('~/commons/nav/user_merge_requests'); @@ -122,25 +124,39 @@ describe('Sidebar mediator', () => { }); describe('toggleAttentionRequested', () => { - let attentionRequiredService; + let requestAttentionMock; + let removeAttentionRequestMock; beforeEach(() => { - attentionRequiredService = jest - .spyOn(mediator.service, 'toggleAttentionRequested') + requestAttentionMock = jest.spyOn(mediator.service, 'requestAttention').mockResolvedValue(); + removeAttentionRequestMock = jest + .spyOn(mediator.service, 'removeAttentionRequest') .mockResolvedValue(); }); - it('calls attentionRequired service method', async () => { - mediator.store.reviewers = [{ id: 1, attention_requested: false, username: 'root' }]; + it.each` + attentionIsCurrentlyRequested | serviceMethod + ${true} | ${'remove'} + ${false} | ${'add'} + `( + "calls the $serviceMethod service method when the user's attention request is set to $attentionIsCurrentlyRequested", + async ({ serviceMethod }) => { + const methods = { + add: requestAttentionMock, + remove: removeAttentionRequestMock, + }; + mediator.store.reviewers = [{ id: 1, attention_requested: false, username: 'root' }]; - await mediator.toggleAttentionRequested('reviewer', { - user: { id: 1, username: 'root' }, - callback: jest.fn(), - }); + await mediator.toggleAttentionRequested('reviewer', { + user: { id: 1, username: 'root' }, + callback: jest.fn(), + direction: serviceMethod, + }); - expect(attentionRequiredService).toHaveBeenCalledWith(1); - expect(refreshUserMergeRequestCounts).toHaveBeenCalled(); - }); + expect(methods[serviceMethod]).toHaveBeenCalledWith(1); + expect(refreshUserMergeRequestCounts).toHaveBeenCalled(); + }, + ); it.each` type | method @@ -172,5 +188,27 @@ describe('Sidebar mediator', () => { expect(toast).toHaveBeenCalledWith(toastMessage); }, ); + + describe('errors', () => { + beforeEach(() => { + jest + .spyOn(mediator.service, 'removeAttentionRequest') + .mockRejectedValueOnce(new Error('Something went wrong')); + }); + + it('shows an error message', async () => { + await mediator.toggleAttentionRequested('reviewer', { + user: { id: 1, username: 'root' }, + callback: jest.fn(), + direction: 'remove', + }); + + expect(createFlash).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Updating the attention request for root failed.', + }), + ); + }); + }); }); });