diff options
-rw-r--r-- | app/assets/javascripts/notes/components/comment_form.vue | 18 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/actions.js | 15 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/index.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/mutation_types.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/mutations.js | 4 | ||||
-rw-r--r-- | changelogs/unreleased/44149-issue-comment-buttons.yml | 5 | ||||
-rw-r--r-- | spec/javascripts/notes/components/comment_form_spec.js | 14 | ||||
-rw-r--r-- | spec/javascripts/notes/stores/actions_spec.js | 16 | ||||
-rw-r--r-- | spec/javascripts/notes/stores/mutation_spec.js | 66 |
9 files changed, 135 insertions, 7 deletions
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index b85c1a6ad72..42bc383f4d2 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -1,5 +1,6 @@ <script> - import { mapActions, mapGetters } from 'vuex'; + import $ from 'jquery'; + import { mapActions, mapGetters, mapState } from 'vuex'; import _ from 'underscore'; import Autosize from 'autosize'; import { __, sprintf } from '~/locale'; @@ -52,6 +53,9 @@ 'getNotesData', 'openState', ]), + ...mapState([ + 'isToggleStateButtonLoading', + ]), noteableDisplayName() { return this.noteableType.replace(/_/g, ' '); }, @@ -142,6 +146,7 @@ 'closeIssue', 'reopenIssue', 'toggleIssueLocalState', + 'toggleStateButtonLoading', ]), setIsSubmitButtonDisabled(note, isSubmitting) { if (!_.isEmpty(note) && !isSubmitting) { @@ -169,13 +174,14 @@ if (this.noteType === constants.DISCUSSION) { noteData.data.note.type = constants.DISCUSSION_NOTE; } + this.note = ''; // Empty textarea while being requested. Repopulate in catch this.resizeTextarea(); this.stopPolling(); this.saveNote(noteData) .then((res) => { - this.isSubmitting = false; + this.enableButton(); this.restartPolling(); if (res.errors) { @@ -197,7 +203,7 @@ } }) .catch(() => { - this.isSubmitting = false; + this.enableButton(); this.discard(false); const msg = `Your comment could not be submitted! @@ -219,6 +225,7 @@ Please check your network connection and try again.`; .then(() => this.enableButton()) .catch(() => { this.enableButton(); + this.toggleStateButtonLoading(false); Flash( sprintf( __('Something went wrong while closing the %{issuable}. Please try again later'), @@ -231,6 +238,7 @@ Please check your network connection and try again.`; .then(() => this.enableButton()) .catch(() => { this.enableButton(); + this.toggleStateButtonLoading(false); Flash( sprintf( __('Something went wrong while reopening the %{issuable}. Please try again later'), @@ -418,13 +426,13 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" <loading-button v-if="canUpdateIssue" - :loading="isSubmitting" + :loading="isToggleStateButtonLoading" @click="handleSave(true)" :container-class="[ actionButtonClassNames, 'btn btn-comment btn-comment-and-close js-action-button' ]" - :disabled="isSubmitting" + :disabled="isToggleStateButtonLoading || isSubmitting" :label="issueActionButtonTitle" /> diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index fc7faf05c5f..a85686e1633 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -70,21 +70,32 @@ export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion commit(mutationType, res); }); -export const closeIssue = ({ commit, dispatch, state }) => service +export const closeIssue = ({ commit, dispatch, state }) => { + dispatch('toggleStateButtonLoading', true); + return service .toggleIssueState(state.notesData.closePath) .then(res => res.json()) .then((data) => { commit(types.CLOSE_ISSUE); dispatch('emitStateChangedEvent', data); + dispatch('toggleStateButtonLoading', false); }); +}; -export const reopenIssue = ({ commit, dispatch, state }) => service +export const reopenIssue = ({ commit, dispatch, state }) => { + dispatch('toggleStateButtonLoading', true); + return service .toggleIssueState(state.notesData.reopenPath) .then(res => res.json()) .then((data) => { commit(types.REOPEN_ISSUE); dispatch('emitStateChangedEvent', data); + dispatch('toggleStateButtonLoading', false); }); +}; + +export const toggleStateButtonLoading = ({ commit }, value) => + commit(types.TOGGLE_STATE_BUTTON_LOADING, value); export const emitStateChangedEvent = ({ commit, getters }, data) => { const event = new CustomEvent('issuable_vue_app:change', { detail: { diff --git a/app/assets/javascripts/notes/stores/index.js b/app/assets/javascripts/notes/stores/index.js index 488a9ca38d3..9ed19bf171e 100644 --- a/app/assets/javascripts/notes/stores/index.js +++ b/app/assets/javascripts/notes/stores/index.js @@ -12,6 +12,9 @@ export default new Vuex.Store({ targetNoteHash: null, lastFetchedAt: null, + // View layer + isToggleStateButtonLoading: false, + // holds endpoints and permissions provided through haml notesData: {}, userData: {}, diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index da1b5a9e51a..b455e23ecde 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -17,3 +17,4 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION'; // Issue export const CLOSE_ISSUE = 'CLOSE_ISSUE'; export const REOPEN_ISSUE = 'REOPEN_ISSUE'; +export const TOGGLE_STATE_BUTTON_LOADING = 'TOGGLE_STATE_BUTTON_LOADING'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 949628a65c0..9308daa36f1 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -199,4 +199,8 @@ export default { [types.REOPEN_ISSUE](state) { Object.assign(state.noteableData, { state: constants.REOPENED }); }, + + [types.TOGGLE_STATE_BUTTON_LOADING](state, value) { + Object.assign(state, { isToggleStateButtonLoading: value }); + }, }; diff --git a/changelogs/unreleased/44149-issue-comment-buttons.yml b/changelogs/unreleased/44149-issue-comment-buttons.yml new file mode 100644 index 00000000000..c874c0d3d66 --- /dev/null +++ b/changelogs/unreleased/44149-issue-comment-buttons.yml @@ -0,0 +1,5 @@ +--- +title: Fix broken loading state for close issue button +merge_request: +author: +type: fixed diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js index 6a7131528a3..5f1eac01e32 100644 --- a/spec/javascripts/notes/components/comment_form_spec.js +++ b/spec/javascripts/notes/components/comment_form_spec.js @@ -199,6 +199,20 @@ describe('issue_comment_form component', () => { done(); }); }); + + describe('when clicking close/reopen button', () => { + it('should disable button and show a loading spinner', (done) => { + const toggleStateButton = vm.$el.querySelector('.js-action-button'); + + toggleStateButton.click(); + Vue.nextTick(() => { + expect(toggleStateButton.disabled).toEqual(true); + expect(toggleStateButton.querySelector('.js-loading-button-icon')).not.toBeNull(); + + done(); + }); + }); + }); }); describe('issue is confidential', () => { diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index b838cc36fb3..91249b2c79e 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -88,6 +88,7 @@ describe('Actions Notes Store', () => { store.dispatch('closeIssue', { notesData: { closeIssuePath: '' } }) .then(() => { expect(store.state.noteableData.state).toEqual('closed'); + expect(store.state.isToggleStateButtonLoading).toEqual(false); done(); }) .catch(done.fail); @@ -99,6 +100,7 @@ describe('Actions Notes Store', () => { store.dispatch('reopenIssue', { notesData: { reopenIssuePath: '' } }) .then(() => { expect(store.state.noteableData.state).toEqual('reopened'); + expect(store.state.isToggleStateButtonLoading).toEqual(false); done(); }) .catch(done.fail); @@ -117,6 +119,20 @@ describe('Actions Notes Store', () => { }); }); + describe('toggleStateButtonLoading', () => { + it('should set loading as true', (done) => { + testAction(actions.toggleStateButtonLoading, true, {}, [ + { type: 'TOGGLE_STATE_BUTTON_LOADING', payload: true }, + ], done); + }); + + it('should set loading as false', (done) => { + testAction(actions.toggleStateButtonLoading, false, {}, [ + { type: 'TOGGLE_STATE_BUTTON_LOADING', payload: false }, + ], done); + }); + }); + describe('toggleIssueLocalState', () => { it('sets issue state as closed', (done) => { testAction(actions.toggleIssueLocalState, 'closed', {}, [ diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js index 34884f8968f..98f101d6bc5 100644 --- a/spec/javascripts/notes/stores/mutation_spec.js +++ b/spec/javascripts/notes/stores/mutation_spec.js @@ -228,4 +228,70 @@ describe('Notes Store mutations', () => { expect(state.notes[0].notes[0].note).toEqual('Foo'); }); }); + + describe('CLOSE_ISSUE', () => { + it('should set issue as closed', () => { + const state = { + notes: [], + targetNoteHash: null, + lastFetchedAt: null, + isToggleStateButtonLoading: false, + notesData: {}, + userData: {}, + noteableData: {}, + }; + + mutations.CLOSE_ISSUE(state); + expect(state.noteableData.state).toEqual('closed'); + }); + }); + + describe('REOPEN_ISSUE', () => { + it('should set issue as closed', () => { + const state = { + notes: [], + targetNoteHash: null, + lastFetchedAt: null, + isToggleStateButtonLoading: false, + notesData: {}, + userData: {}, + noteableData: {}, + }; + + mutations.REOPEN_ISSUE(state); + expect(state.noteableData.state).toEqual('reopened'); + }); + }); + + describe('TOGGLE_STATE_BUTTON_LOADING', () => { + it('should set isToggleStateButtonLoading as true', () => { + const state = { + notes: [], + targetNoteHash: null, + lastFetchedAt: null, + isToggleStateButtonLoading: false, + notesData: {}, + userData: {}, + noteableData: {}, + }; + + mutations.TOGGLE_STATE_BUTTON_LOADING(state, true); + expect(state.isToggleStateButtonLoading).toEqual(true); + }); + + it('should set isToggleStateButtonLoading as false', () => { + const state = { + notes: [], + targetNoteHash: null, + lastFetchedAt: null, + isToggleStateButtonLoading: true, + notesData: {}, + userData: {}, + noteableData: {}, + }; + + mutations.TOGGLE_STATE_BUTTON_LOADING(state, false); + expect(state.isToggleStateButtonLoading).toEqual(false); + }); + }); }); |