Skip to content
Snippets Groups Projects
Verified Commit 845390e1 authored by Kushal Pandya's avatar Kushal Pandya :speech_balloon: Committed by GitLab
Browse files

Merge branch 'ph/454657/discardDraftComments' into 'master'

Added discard review button to review bar

See merge request !176112



Merged-by: Kushal Pandya's avatarKushal Pandya <kushal@gitlab.com>
Approved-by: Kushal Pandya's avatarKushal Pandya <kushal@gitlab.com>
Reviewed-by: default avatarGitLab Duo <gitlab-duo@gitlab.com>
Co-authored-by: default avatarPhil Hughes <me@iamphill.com>
parents b96a1479 6dda2c66
No related branches found
No related tags found
3 merge requests!181325Fix ambiguous `created_at` in project.rb,!179611Draft: Rebase CR approach for zoekt assignments,!176112Added discard review button to review bar
Pipeline #1597402723 passed
<script> <script>
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import { GlButton, GlTooltipDirective as GlTooltip, GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
import toast from '~/vue_shared/plugins/global_toast';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { SET_REVIEW_BAR_RENDERED } from '~/batch_comments/stores/modules/batch_comments/mutation_types'; import { SET_REVIEW_BAR_RENDERED } from '~/batch_comments/stores/modules/batch_comments/mutation_types';
import { REVIEW_BAR_VISIBLE_CLASS_NAME } from '../constants'; import { REVIEW_BAR_VISIBLE_CLASS_NAME } from '../constants';
...@@ -9,10 +12,21 @@ import SubmitDropdown from './submit_dropdown.vue'; ...@@ -9,10 +12,21 @@ import SubmitDropdown from './submit_dropdown.vue';
export default { export default {
components: { components: {
GlModal,
GlButton,
PreviewDropdown, PreviewDropdown,
SubmitDropdown, SubmitDropdown,
}, },
directives: {
GlTooltip,
},
mixins: [glFeatureFlagMixin()], mixins: [glFeatureFlagMixin()],
data() {
return {
discarding: false,
showDiscardModal: false,
};
},
computed: { computed: {
...mapGetters(['isNotesFetched']), ...mapGetters(['isNotesFetched']),
}, },
...@@ -31,17 +45,57 @@ export default { ...@@ -31,17 +45,57 @@ export default {
document.body.classList.remove(REVIEW_BAR_VISIBLE_CLASS_NAME); document.body.classList.remove(REVIEW_BAR_VISIBLE_CLASS_NAME);
}, },
methods: { methods: {
...mapActions('batchComments', ['expandAllDiscussions']), ...mapActions('batchComments', ['expandAllDiscussions', 'discardDrafts']),
async discardReviews() {
this.discarding = true;
try {
await this.discardDrafts();
toast(__('Review discarded'));
} finally {
this.discarding = false;
}
},
},
modal: {
cancelAction: { text: __('Keep review') },
primaryAction: { text: __('Discard review'), attributes: { variant: 'danger' } },
}, },
}; };
</script> </script>
<template> <template>
<div> <nav class="review-bar-component js-review-bar" data-testid="review_bar_component">
<nav class="review-bar-component js-review-bar" data-testid="review_bar_component"> <div class="review-bar-content gl-flex gl-justify-end" data-testid="review-bar-content">
<div class="review-bar-content gl-flex gl-justify-end" data-testid="review-bar-content"> <gl-button
<preview-dropdown /> v-gl-tooltip
<submit-dropdown /> icon="remove"
</div> variant="danger"
</nav> category="tertiary"
</div> class="gl-mr-3"
:title="__('Discard review')"
:aria-label="__('Discard review')"
:loading="discarding"
data-testid="discard-review-btn"
@click="showDiscardModal = true"
/>
<preview-dropdown />
<submit-dropdown />
</div>
<gl-modal
v-model="showDiscardModal"
modal-id="discard-review-modal"
:title="__('Discard pending review?')"
:action-primary="$options.modal.primaryAction"
:action-cancel="$options.modal.cancelAction"
data-testid="discard-review-modal"
@primary="discardReviews"
>
{{
__(
'Are you sure you want to discard your pending review comments? This action cannot be undone.',
)
}}
</gl-modal>
</nav>
</template> </template>
...@@ -16,6 +16,7 @@ export const useBatchComments = defineStore('batchComments', { ...@@ -16,6 +16,7 @@ export const useBatchComments = defineStore('batchComments', {
addDraftToDiscussion() {}, addDraftToDiscussion() {},
createNewDraft() {}, createNewDraft() {},
clearDrafts() {}, clearDrafts() {},
discardDrafts() {},
}, },
getters: { getters: {
hasDrafts() {}, hasDrafts() {},
......
...@@ -163,3 +163,18 @@ export const toggleResolveDiscussion = ({ commit }, draftId) => { ...@@ -163,3 +163,18 @@ export const toggleResolveDiscussion = ({ commit }, draftId) => {
}; };
export const clearDrafts = ({ commit }) => commit(types.CLEAR_DRAFTS); export const clearDrafts = ({ commit }) => commit(types.CLEAR_DRAFTS);
export const discardDrafts = ({ getters, commit }) => {
return service
.discard(getters.getNotesData.draftsDiscardPath)
.then(() => {
commit(types.CLEAR_DRAFTS);
})
.catch((error) =>
createAlert({
captureError: true,
error,
message: __('An error occurred while discarding your review. Please try again.'),
}),
);
};
...@@ -6262,6 +6262,9 @@ msgstr "" ...@@ -6262,6 +6262,9 @@ msgstr ""
msgid "An error occurred while disabling Service Desk." msgid "An error occurred while disabling Service Desk."
msgstr "" msgstr ""
   
msgid "An error occurred while discarding your review. Please try again."
msgstr ""
msgid "An error occurred while dismissing the alert. Refresh the page and try again." msgid "An error occurred while dismissing the alert. Refresh the page and try again."
msgstr "" msgstr ""
   
...@@ -7765,6 +7768,9 @@ msgstr "" ...@@ -7765,6 +7768,9 @@ msgstr ""
msgid "Are you sure you want to discard your changes?" msgid "Are you sure you want to discard your changes?"
msgstr "" msgstr ""
   
msgid "Are you sure you want to discard your pending review comments? This action cannot be undone."
msgstr ""
msgid "Are you sure you want to import %d repository?" msgid "Are you sure you want to import %d repository?"
msgid_plural "Are you sure you want to import %d repositories?" msgid_plural "Are you sure you want to import %d repositories?"
msgstr[0] "" msgstr[0] ""
...@@ -20290,6 +20296,12 @@ msgstr "" ...@@ -20290,6 +20296,12 @@ msgstr ""
msgid "Discard draft" msgid "Discard draft"
msgstr "" msgstr ""
   
msgid "Discard pending review?"
msgstr ""
msgid "Discard review"
msgstr ""
msgid "Discord webhook (for example, `https://discord.com/api/webhooks/…`)." msgid "Discord webhook (for example, `https://discord.com/api/webhooks/…`)."
msgstr "" msgstr ""
   
...@@ -31994,6 +32006,9 @@ msgstr "" ...@@ -31994,6 +32006,9 @@ msgstr ""
msgid "Keep divergent refs" msgid "Keep divergent refs"
msgstr "" msgstr ""
   
msgid "Keep review"
msgstr ""
msgid "Keep sidebar visible" msgid "Keep sidebar visible"
msgstr "" msgstr ""
   
...@@ -47447,6 +47462,9 @@ msgstr "" ...@@ -47447,6 +47462,9 @@ msgstr ""
msgid "Review changes" msgid "Review changes"
msgstr "" msgstr ""
   
msgid "Review discarded"
msgstr ""
msgid "Review requested" msgid "Review requested"
msgstr "" msgstr ""
   
import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ReviewBar from '~/batch_comments/components/review_bar.vue'; import ReviewBar from '~/batch_comments/components/review_bar.vue';
import { REVIEW_BAR_VISIBLE_CLASS_NAME } from '~/batch_comments/constants'; import { REVIEW_BAR_VISIBLE_CLASS_NAME } from '~/batch_comments/constants';
import toast from '~/vue_shared/plugins/global_toast';
import createStore from '../create_batch_comments_store'; import createStore from '../create_batch_comments_store';
jest.mock('~/vue_shared/plugins/global_toast');
describe('Batch comments review bar component', () => { describe('Batch comments review bar component', () => {
let store; let store;
let wrapper; let wrapper;
const findDiscardReviewButton = () => wrapper.findByTestId('discard-review-btn');
const findDiscardReviewModal = () => wrapper.findByTestId('discard-review-modal');
const createComponent = (propsData = {}) => { const createComponent = (propsData = {}) => {
store = createStore(); store = createStore();
wrapper = shallowMount(ReviewBar, { wrapper = shallowMountExtended(ReviewBar, {
store, store,
propsData, propsData,
}); });
...@@ -35,4 +42,40 @@ describe('Batch comments review bar component', () => { ...@@ -35,4 +42,40 @@ describe('Batch comments review bar component', () => {
expect(document.body.classList.contains(REVIEW_BAR_VISIBLE_CLASS_NAME)).toBe(false); expect(document.body.classList.contains(REVIEW_BAR_VISIBLE_CLASS_NAME)).toBe(false);
}); });
describe('when discarding a review', () => {
it('shows modal when clicking discard button', async () => {
createComponent();
expect(findDiscardReviewModal().props('visible')).toBe(false);
findDiscardReviewButton().vm.$emit('click');
await nextTick();
expect(findDiscardReviewModal().props('visible')).toBe(true);
});
it('calls discardReviews when primary action on modal is triggered', () => {
createComponent();
const dispatchSpy = jest.spyOn(store, 'dispatch').mockImplementation();
findDiscardReviewModal().vm.$emit('primary');
expect(dispatchSpy).toHaveBeenCalledWith('batchComments/discardDrafts', undefined);
});
it('creates a toast message when finished', async () => {
createComponent();
jest.spyOn(store, 'dispatch').mockImplementation();
findDiscardReviewModal().vm.$emit('primary');
await nextTick();
expect(toast).toHaveBeenCalledWith('Review discarded');
});
});
}); });
...@@ -357,4 +357,36 @@ describe('Batch comments store actions', () => { ...@@ -357,4 +357,36 @@ describe('Batch comments store actions', () => {
return testAction(actions.clearDrafts, null, null, [{ type: 'CLEAR_DRAFTS' }], []); return testAction(actions.clearDrafts, null, null, [{ type: 'CLEAR_DRAFTS' }], []);
}); });
}); });
describe('discardDrafts', () => {
let commit;
let getters;
beforeEach(() => {
commit = jest.fn();
getters = {
getNotesData: { draftsDiscardPath: TEST_HOST },
};
});
it('dispatches actions & commits', async () => {
mock.onAny().reply(HTTP_STATUS_OK);
await actions.discardDrafts({ commit, getters });
expect(commit.mock.calls[0]).toEqual(['CLEAR_DRAFTS']);
});
it('calls createAlert when server returns an error', async () => {
mock.onAny().reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
await actions.discardDrafts({ commit, getters });
expect(createAlert).toHaveBeenCalledWith({
error: expect.anything(),
captureError: true,
message: 'An error occurred while discarding your review. Please try again.',
});
});
});
}); });
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment