diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2018-02-09 20:07:20 +0300 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2018-02-09 21:56:41 +0300 |
commit | d637f87f88b40704291d7a01032af09853afb4e4 (patch) | |
tree | 40becbcd8457096386ec19621ee0010847cb26d6 /app | |
parent | a4a47cfba87a618698eab36bd7b6427f4f27d0de (diff) |
Makes close/reopen issue request to inside the vue app
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/issue.js | 75 | ||||
-rw-r--r-- | app/assets/javascripts/notes/components/comment_form.vue | 28 | ||||
-rw-r--r-- | app/assets/javascripts/notes/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/notes/services/notes_service.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/actions.js | 33 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/getters.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/mutation_types.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/notes/stores/mutations.js | 8 | ||||
-rw-r--r-- | app/views/projects/issues/_discussion.html.haml | 2 |
9 files changed, 116 insertions, 40 deletions
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index ff65ea99e9a..1bf3dc1da91 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -25,6 +25,51 @@ export default class Issue { if (Issue.createMrDropdownWrap) { this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap); } + + // Listen to state changes in the Vue app + document.addEventListener('issuable_vue_app:change', (event) => { + this.updateTopState(event.detail.isClosed, event.detail.data); + }); + } + + /** + * This method updates the top area of the issue. + * + * Once the issue state changes, either through a click on the top area (jquery) + * or a click on the bottom area (Vue) we need to update the top area. + * + * @param {Boolean} isClosed + * @param {Array} data + * @param {String} issueFailMessage + */ + updateTopState(isClosed, data, issueFailMessage = 'Unable to update this issue at this time.') { + if ('id' in data) { + const isClosedBadge = $('div.status-box-issue-closed'); + const isOpenBadge = $('div.status-box-open'); + const projectIssuesCounter = $('.issue_counter'); + + isClosedBadge.toggleClass('hidden', !isClosed); + isOpenBadge.toggleClass('hidden', isClosed); + + $(document).trigger('issuable:change', isClosed); + this.toggleCloseReopenButton(isClosed); + + let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, '')); + numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1; + projectIssuesCounter.text(addDelimiter(numProjectIssues)); + + if (this.createMergeRequestDropdown) { + if (isClosed) { + this.createMergeRequestDropdown.unavailable(); + this.createMergeRequestDropdown.disable(); + } else { + // We should check in case a branch was created in another tab + this.createMergeRequestDropdown.checkAbilityToCreateBranch(); + } + } + } else { + flash(issueFailMessage); + } } initIssueBtnEventListeners() { @@ -45,34 +90,8 @@ export default class Issue { url = $button.attr('href'); return axios.put(url) .then(({ data }) => { - const isClosedBadge = $('div.status-box-issue-closed'); - const isOpenBadge = $('div.status-box-open'); - const projectIssuesCounter = $('.issue_counter'); - - if ('id' in data) { - const isClosed = $button.hasClass('btn-close'); - isClosedBadge.toggleClass('hidden', !isClosed); - isOpenBadge.toggleClass('hidden', isClosed); - - $(document).trigger('issuable:change', isClosed); - this.toggleCloseReopenButton(isClosed); - - let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, '')); - numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1; - projectIssuesCounter.text(addDelimiter(numProjectIssues)); - - if (this.createMergeRequestDropdown) { - if (isClosed) { - this.createMergeRequestDropdown.unavailable(); - this.createMergeRequestDropdown.disable(); - } else { - // We should check in case a branch was created in another tab - this.createMergeRequestDropdown.checkAbilityToCreateBranch(); - } - } - } else { - flash(issueFailMessage); - } + const isClosed = $button.hasClass('btn-close'); + this.updateTopState(isClosed, data); }) .catch(() => flash(issueFailMessage)) .then(() => { diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 3c8452ac808..7b54edb0c2a 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -2,6 +2,7 @@ import { mapActions, mapGetters } from 'vuex'; import _ from 'underscore'; import Autosize from 'autosize'; + import { __ } from '~/locale'; import Flash from '../../flash'; import Autosave from '../../autosave'; import TaskList from '../../task_list'; @@ -30,9 +31,6 @@ return { note: '', noteType: constants.COMMENT, - // Can't use mapGetters, - // this needs to be in the data object because it belongs to the state - issueState: this.$store.getters.getNoteableData.state, isSubmitting: false, isSubmitButtonDisabled: true, }; @@ -43,7 +41,11 @@ 'getUserData', 'getNoteableData', 'getNotesData', + 'getIssueState', ]), + issueState() { + return this.getIssueState; + }, isLoggedIn() { return this.getUserData.id; }, @@ -71,8 +73,6 @@ return { 'btn-reopen': !this.isIssueOpen, 'btn-close': this.isIssueOpen, - 'js-note-target-close': this.isIssueOpen, - 'js-note-target-reopen': !this.isIssueOpen, }; }, markdownDocsPath() { @@ -105,7 +105,7 @@ mounted() { // jQuery is needed here because it is a custom event being dispatched with jQuery. $(document).on('issuable:change', (e, isClosed) => { - this.issueState = isClosed ? constants.CLOSED : constants.REOPENED; + this.toggleIssueLocalState(isClosed ? constants.CLOSED : constants.REOPENED); }); this.initAutoSave(); @@ -117,6 +117,9 @@ 'stopPolling', 'restartPolling', 'removePlaceholderNotes', + 'closeIssue', + 'reopenIssue', + 'toggleIssueLocalState', ]), setIsSubmitButtonDisabled(note, isSubmitting) { if (!_.isEmpty(note) && !isSubmitting) { @@ -185,12 +188,13 @@ Please check your network connection and try again.`; } }, toggleIssueState() { - this.issueState = this.isIssueOpen ? constants.CLOSED : constants.REOPENED; - - // This is out of scope for the Notes Vue component. - // It was the shortest path to update the issue state and relevant places. - const btnClass = this.isIssueOpen ? 'btn-reopen' : 'btn-close'; - $(`.js-btn-issue-action.${btnClass}:visible`).trigger('click'); + if (this.isIssueOpen) { + this.closeIssue() + .catch(() => Flash(__('Something went wrong while closing the issue. Please try again later'))); + } else { + this.reopenIssue() + .catch(() => Flash(__('Something went wrong while reopening the issue. Please try again later'))); + } }, discard(shouldClear = true) { // `blur` is needed to clear slash commands autocomplete cache if event fired. diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index d250dd8d25b..48e7cfddb63 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -28,6 +28,8 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ notesPath: notesDataset.notesPath, markdownDocsPath: notesDataset.markdownDocsPath, quickActionsDocsPath: notesDataset.quickActionsDocsPath, + closeIssuePath: notesDataset.closeIssuePath, + reopenIssuePath: notesDataset.reopenIssuePath, }, }; }, diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js index b51b0cb2013..b8e7ffc8c46 100644 --- a/app/assets/javascripts/notes/services/notes_service.js +++ b/app/assets/javascripts/notes/services/notes_service.js @@ -32,4 +32,7 @@ export default { toggleAward(endpoint, data) { return Vue.http.post(endpoint, data, { emulateJSON: true }); }, + toggleIssueState(endpoint, data) { + return Vue.http.put(endpoint, data); + }, }; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 085b18642ba..1877d5bf959 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -61,6 +61,39 @@ export const createNewNote = ({ commit }, { endpoint, data }) => service export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES); +export const closeIssue = ({ commit, dispatch, state }) => service + .toggleIssueState(state.notesData.closeIssuePath) + .then(res => res.json()) + .then((data) => { + commit(types.CLOSE_ISSUE); + dispatch('emitStateChangedEvent', data); + }); + +export const reopenIssue = ({ commit, dispatch, state }) => service + .toggleIssueState(state.notesData.reopenIssuePath) + .then(res => res.json()) + .then((data) => { + commit(types.REOPEN_ISSUE); + dispatch('emitStateChangedEvent', data); + }); + +export const emitStateChangedEvent = ({ commit }, data) => { + const event = new CustomEvent('issuable_vue_app:change', { detail: { + data, + isClosed: data.state === constants.CLOSED, + } }); + + document.dispatchEvent(event); +}; + +export const toggleIssueLocalState = ({ commit }, newState) => { + if (newState === constants.CLOSED) { + commit(types.CLOSE_ISSUE); + } else if (newState === constants.REOPENED) { + commit(types.REOPEN_ISSUE); + } +}; + export const saveNote = ({ commit, dispatch }, noteData) => { const { note } = noteData.data.note; let placeholderText = note; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index e18b277119e..38b48c3bbe4 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -8,6 +8,7 @@ export const getNotesDataByProp = state => prop => state.notesData[prop]; export const getNoteableData = state => state.noteableData; export const getNoteableDataByProp = state => prop => state.noteableData[prop]; +export const getIssueState = state => state.noteableData.state; export const getUserData = state => state.userData || {}; export const getUserDataByProp = state => prop => state.userData && state.userData[prop]; diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index d520c197407..6d7c3bbae0f 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -12,3 +12,7 @@ export const SHOW_PLACEHOLDER_NOTE = 'SHOW_PLACEHOLDER_NOTE'; export const TOGGLE_AWARD = 'TOGGLE_AWARD'; export const TOGGLE_DISCUSSION = 'TOGGLE_DISCUSSION'; export const UPDATE_NOTE = 'UPDATE_NOTE'; + +// Issue +export const CLOSE_ISSUE = 'CLOSE_ISSUE'; +export const REOPEN_ISSUE = 'REOPEN_ISSUE'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 20f81a430c2..b3f66578c9a 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -152,4 +152,12 @@ export default { noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note); } }, + + [types.CLOSE_ISSUE](state) { + Object.assign(state.noteableData, { state: constants.CLOSED }); + }, + + [types.REOPEN_ISSUE](state) { + Object.assign(state.noteableData, { state: constants.REOPENED }); + }, }; diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 9779c1985d5..11b5e02f1e0 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -12,6 +12,8 @@ markdown_docs_path: help_page_path('user/markdown'), quick_actions_docs_path: help_page_path('user/project/quick_actions'), notes_path: notes_url, + close_issue_path: issue_path(@issue, issue: { state_event: :close }, format: 'json'), + reopen_issue_path: issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), last_fetched_at: Time.now.to_i, noteable_data: serialize_issuable(@issue), current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } } |