diff options
author | Jose Ivan Vargas <jvargas@gitlab.com> | 2018-01-29 15:46:01 -0600 |
---|---|---|
committer | Jose Ivan Vargas <jvargas@gitlab.com> | 2018-02-06 17:12:28 -0600 |
commit | 70e1d0e84f691d625815c53753bd27849fd61272 (patch) | |
tree | 675faa94c808b499e418c351289e4fb78caa88e8 | |
parent | d701ffc945fbf61d9f7b519d03c580999ff22d03 (diff) | |
download | gitlab-ce-41313-new-design-for-project-label-deletion-confirmation.tar.gz |
Changed vue logic to only add one instance41313-new-design-for-project-label-deletion-confirmation
-rw-r--r-- | app/assets/javascripts/dispatcher.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/pages/labels/components/delete_label_modal.vue | 49 | ||||
-rw-r--r-- | app/assets/javascripts/pages/labels/event_hub.js | 3 | ||||
-rw-r--r-- | app/assets/javascripts/pages/labels/index.js | 94 | ||||
-rw-r--r-- | app/views/shared/_delete_label_modal.html.haml | 20 | ||||
-rw-r--r-- | app/views/shared/_label.html.haml | 19 | ||||
-rw-r--r-- | spec/features/projects/labels/remove_labels_spec.rb | 26 | ||||
-rw-r--r-- | spec/javascripts/pages/labels/components/delete_label_modal_component_spec.js | 26 |
8 files changed, 158 insertions, 83 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 999db292d39..492fe68ad0e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -406,7 +406,9 @@ var Dispatcher; import('./pages/projects/labels/index') .then(callDefault) .catch(fail); - import('./pages/labels/').then(callDefault).catch(fail); + import('./pages/labels/') + .then(callDefault) + .catch(fail); break; case 'projects:network:show': // Ensure we don't create a particular shortcut handler here. This is diff --git a/app/assets/javascripts/pages/labels/components/delete_label_modal.vue b/app/assets/javascripts/pages/labels/components/delete_label_modal.vue index 2808476929b..c6e91ab8c81 100644 --- a/app/assets/javascripts/pages/labels/components/delete_label_modal.vue +++ b/app/assets/javascripts/pages/labels/components/delete_label_modal.vue @@ -2,7 +2,9 @@ import axios from '~/lib/utils/axios_utils'; import modal from '~/vue_shared/components/modal.vue'; import { redirectTo } from '~/lib/utils/url_utility'; + import { s__, sprintf } from '~/locale'; import Flash from '~/flash'; + import eventHub from '../event_hub'; export default { components: { @@ -13,11 +15,11 @@ type: String, required: true, }, - labelOpenMergeRequestsCount: { + openMergeRequestCount: { type: Number, required: true, }, - labelOpenIssuesCount: { + openIssuesCount: { type: Number, required: true, }, @@ -28,26 +30,37 @@ }, computed: { modalTitle() { - return `Delete label ‘${this.labelTitle}’?`; + return sprintf(s__('Labels|Delete label ‘%{labelTitle}’?'), { labelTitle: this.labelTitle }); }, modalDescription() { - return `You’re about to permanently delete the label ${this.labelTitle} from this project and remove it from - ${this.labelOpenIssuesCount}, ${this.labelOpenMergeRequestsCount} issues and merge requests. - Once deleted, it cannot be undone or recovered.`; + return sprintf(s__(`Labels|You’re about to permanently delete the label <strong>%{labelTitle}</strong> + from this project and remove it from %{openIssuesCount} issues and %{openMergeRequestCount} merge requests. + Once deleted, it cannot be undone or recovered.`), { + labelTitle: this.labelTitle, + openIssuesCount: this.openIssuesCount, + openMergeRequestCount: this.openMergeRequestCount, + }); }, primaryButtonText() { - return 'Delete Label'; + return s__('Labels|Delete Label'); }, }, methods: { onSubmit() { + eventHub.$emit('deleteLabelModal.requestStarted', this.url); return axios.delete(this.url) - .then((resp) => { - redirectTo(resp.request.responseURL); - }) - .catch((err) => { - Flash(err); - }); + .then((response) => { + eventHub.$emit('deleteLabelModal.requestFinished', { labelUrl: this.url, successful: true }); + + redirectTo(response.request.responseURL); + }) + .catch((error) => { + eventHub.$emit('deleteLabelModal.requestFinished', { labelUrl: this.url, successful: false }); + + Flash(error); + + throw error; + }); }, }, }; @@ -59,6 +72,12 @@ :text="modalDescription" kind="danger" :primary-button-label="primaryButtonText" - @submit="onSubmit" - /> + @submit="onSubmit"> + + <template + slot="body" + slot-scope="props"> + <p v-html="props.text"></p> + </template> + </modal> </template> diff --git a/app/assets/javascripts/pages/labels/event_hub.js b/app/assets/javascripts/pages/labels/event_hub.js new file mode 100644 index 00000000000..0948c2e5352 --- /dev/null +++ b/app/assets/javascripts/pages/labels/event_hub.js @@ -0,0 +1,3 @@ +import Vue from 'vue'; + +export default new Vue(); diff --git a/app/assets/javascripts/pages/labels/index.js b/app/assets/javascripts/pages/labels/index.js index 41abecd51c9..4e9464662c1 100644 --- a/app/assets/javascripts/pages/labels/index.js +++ b/app/assets/javascripts/pages/labels/index.js @@ -1,40 +1,88 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; import deleteLabelModal from './components/delete_label_modal.vue'; +import eventHub from './event_hub'; -Vue.use(Translate); +export default () => { + Vue.use(Translate); + + const onRequestFinished = ({ labelUrl, successful }) => { + const button = document.querySelector(`.js-delete-project-label[data-url="${labelUrl}"]`); + + if (!successful) { + button.removeAttribute('disabled'); + } + }; + + const onRequestStarted = (labelUrl) => { + const button = document.querySelector(`.js-delete-project-label[data-url="${labelUrl}"]`); + button.setAttribute('disabled', ''); + eventHub.$once('deleteLabelModal.requestFinished', onRequestFinished); + }; + + const onDeleteButtonClick = (event) => { + const button = event.currentTarget; + const modalProps = { + labelTitle: button.dataset.labelTitle, + openMergeRequestCount: parseInt(button.dataset.labelOpenMergeRequestsCount, 10), + openIssuesCount: parseInt(button.dataset.labelOpenIssuesCount, 10), + url: button.dataset.url, + }; + eventHub.$once('deleteLabelModal.requestStarted', onRequestStarted); + eventHub.$emit('deleteLabelModal.props', modalProps); + }; + + const deleteLabelButtons = document.querySelectorAll('.js-delete-project-label'); + for (let i = 0; i < deleteLabelButtons.length; i += 1) { + const button = deleteLabelButtons[i]; + button.addEventListener('click', onDeleteButtonClick); + } -function createDeleteLabelModal(props) { - // eslint-disable-next-line no-new - new Vue({ + eventHub.$once('deleteLabelModal.mounted', () => { + for (let i = 0; i < deleteLabelButtons.length; i += 1) { + const button = deleteLabelButtons[i]; + button.removeAttribute('disabled'); + } + }); + + const labelComponent = new Vue({ el: '#delete-label-modal', components: { deleteLabelModal, }, + data() { + return { + modalProps: { + labelTitle: '', + openMergeRequestCount: -1, + openIssuesCount: -1, + url: '', + }, + }; + }, + mounted() { + eventHub.$on('deleteLabelModal.props', this.setModalProps); + eventHub.$emit('deleteLabelModal.mounted'); + }, + beforeDestroy() { + eventHub.$off('deleteLabelModal.props', this.setModalProps); + }, + methods: { + setModalProps(modalProps) { + this.modalProps = modalProps; + }, + }, render(createElement) { return createElement('delete-label-modal', { - props, + props: this.modalProps, }); }, }); -} - -function getProps(event) { - const button = event.currentTarget; - const props = { - labelTitle: button.dataset.labelTitle, - labelOpenIssuesCount: parseInt(button.dataset.labelOpenIssuesCount, 10), - labelOpenMergeRequestsCount: parseInt(button.dataset.labelOpenMergeRequestsCount, 10), - url: button.dataset.url, - }; - - createDeleteLabelModal(props); -} -export default () => { - const deleteLabelButtons = document.querySelectorAll('.js-delete-project-label'); - for (let i = 0; i < deleteLabelButtons.length; i += 1) { - const button = deleteLabelButtons[i]; - button.onclick = getProps; + const labelModal = document.getElementById('delete-label-modal'); + let withLabel; + if (labelModal != null) { + withLabel = labelComponent; } + return withLabel; }; diff --git a/app/views/shared/_delete_label_modal.html.haml b/app/views/shared/_delete_label_modal.html.haml deleted file mode 100644 index 01effefc34d..00000000000 --- a/app/views/shared/_delete_label_modal.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -.modal{ id: "modal-delete-label-#{label.id}", tabindex: -1 } - .modal-dialog - .modal-content - .modal-header - %button.close{ data: {dismiss: 'modal' } } × - %h3.page-title Delete #{render_colored_label(label, tooltip: false)} ? - - .modal-body - %p - %strong= label.name - %span will be permanently deleted from #{label.is_a?(ProjectLabel)? label.project.name : label.group.name}. This cannot be undone. - - .modal-footer - %a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel - - = link_to 'Delete label', - destroy_label_path(label), - title: 'Delete', - method: :delete, - class: 'btn btn-remove' diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 41312e8c569..adf008a6fa1 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -58,15 +58,16 @@ = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do %span.sr-only Edit = icon('pencil-square-o') - %a.js-delete-project-label.btn.btn-transparent.btn-action.remove-row.has-tooltip{ title: _('Delete'), - data: { url: destroy_label_path(label), - label_title: label.title, - label_open_merge_requests_count: label.open_merge_requests_count, - label_open_issues_count: label.open_issues_count, - target: '#delete-label-modal', - container: 'body', - toggle: "modal" } } - = icon('trash-o') + %button.js-delete-project-label.btn.btn-transparent.btn-action.remove-row.has-tooltip{ title: _('Delete'), + data: { url: destroy_label_path(label), + label_title: label.title, + label_open_merge_requests_count: label.open_merge_requests_count, + label_open_issues_count: label.open_issues_count, + target: '#delete-label-modal', + container: 'body', + toggle: 'modal' }, + disabled: true } + = sprite_icon('remove') - if current_user .label-subscription.inline - if can_subscribe_to_label_in_different_levels?(label) diff --git a/spec/features/projects/labels/remove_labels_spec.rb b/spec/features/projects/labels/remove_labels_spec.rb index 22f5d906845..95ec3ba775d 100644 --- a/spec/features/projects/labels/remove_labels_spec.rb +++ b/spec/features/projects/labels/remove_labels_spec.rb @@ -5,7 +5,7 @@ describe 'Removing labels', :js do let(:group) { create(:group) } let(:project) { create(:project, :public, namespace: group) } let!(:bug) { create(:label, project: project, title: 'bug') } - let!(:test) { create(:label, project: project, title: 'test') } + let!(:test_label) { create(:label, project: project, title: 'test') } before do project.add_master(user) @@ -15,17 +15,23 @@ describe 'Removing labels', :js do context 'with a label list' do before do visit project_labels_path(project) - wait_for_all_requests end - it 'Deletes a label' do - page.within '.labels' do - first('.remove-row').click - sleep 2 - page.execute_script('document.querySelector(".js-primary-button").click()') - wait_for_requests - expect(page.all('.remove-row').length).to eq 1 - end + it 'Deletes all labels' do + delete_label(bug.title) + wait_for_requests + delete_label(test_label.title) + wait_for_requests + expect(find('.empty-state.labels')).not_to be_nil + end + end + + def delete_label(label_title) + find('#delete-label-modal.modal', visible: false) # wait for Vue component to be loaded + find(".js-delete-project-label[data-label-title=\"#{label_title}\"]" % { label_title: label_title }).click + + page.within '#delete-label-modal' do + click_on 'Delete Label' end end end diff --git a/spec/javascripts/pages/labels/components/delete_label_modal_component_spec.js b/spec/javascripts/pages/labels/components/delete_label_modal_component_spec.js index 53c2a6d1201..2faf1f8f86a 100644 --- a/spec/javascripts/pages/labels/components/delete_label_modal_component_spec.js +++ b/spec/javascripts/pages/labels/components/delete_label_modal_component_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import deleteLabelModal from '~/pages/labels/components/delete_label_modal.vue'; +import eventHub from '~/pages/labels/event_hub'; import axios from '~/lib/utils/axios_utils'; import * as urlUtility from '~/lib/utils/url_utility'; import mountComponent from '../../../helpers/vue_mount_component_helper'; @@ -9,9 +10,9 @@ describe('Delete label modal component', () => { let Component; const labelMockData = { labelTitle: 'Test', - labelOpenMergeRequestsCount: 1, - labelOpenIssuesCount: 2, - url: '/dummy/endpoint', + openMergeRequestCount: 1, + openIssuesCount: 2, + url: `${gl.TEST_HOST}/dummy/endpoint`, }; beforeEach(() => { @@ -35,16 +36,28 @@ describe('Delete label modal component', () => { it('modalDescription', () => { expect(vm.modalDescription).toContain(labelMockData.labelTitle); - expect(vm.modalDescription).toContain(labelMockData.labelOpenMergeRequestsCount); - expect(vm.modalDescription).toContain(labelMockData.labelOpenIssuesCount); + expect(vm.modalDescription).toContain(labelMockData.openMergeRequestCount); + expect(vm.modalDescription).toContain(labelMockData.openIssuesCount); }); }); describe('When requesting a label delete', () => { + beforeEach(() => { + vm = mountComponent(Component, { + ...labelMockData, + }); + spyOn(eventHub, '$emit'); + }); + + afterEach(() => { + vm.$destroy(); + }); + it('should redirect when a label is deleted', (done) => { const responseURL = `${gl.TEST_HOST}/dummy/endpoint`; spyOn(axios, 'delete').and.callFake((url) => { expect(url).toBe(labelMockData.url); + expect(eventHub.$emit).toHaveBeenCalledWith('deleteLabelModal.requestStarted', labelMockData.url); return Promise.resolve({ request: { responseURL, @@ -56,6 +69,7 @@ describe('Delete label modal component', () => { vm.onSubmit() .then(() => { expect(redirectSpy).toHaveBeenCalledWith(responseURL); + expect(eventHub.$emit).toHaveBeenCalledWith('deleteLabelModal.requestFinished', { labelUrl: labelMockData.url, successful: true }); }) .then(done) .catch(done.fail); @@ -66,6 +80,7 @@ describe('Delete label modal component', () => { dummyError.response = { status: 500 }; spyOn(axios, 'delete').and.callFake((url) => { expect(url).toBe(labelMockData.url); + expect(eventHub.$emit).toHaveBeenCalledWith('deleteLabelModal.requestStarted', labelMockData.url); return Promise.reject(dummyError); }); const redirectSpy = spyOn(urlUtility, 'redirectTo'); @@ -74,6 +89,7 @@ describe('Delete label modal component', () => { .catch((error) => { expect(error).toBe(dummyError); expect(redirectSpy).not.toHaveBeenCalled(); + expect(eventHub.$emit).toHaveBeenCalledWith('deleteLabelModal.requestFinished', { labelUrl: labelMockData.url, successful: false }); }) .then(done) .catch(done.fail); |