diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue index 558bf812fef..29ee73a2a6f 100644 --- a/app/assets/javascripts/pipelines/components/graph/action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue @@ -32,26 +32,38 @@ export default { required: true, }, - buttonDisabled: { + requestFinishedFor: { type: String, required: false, - default: null, + default: '', }, }, + data() { + return { + isDisabled: false, + linkRequested: '', + }; + }, + computed: { cssClass() { const actionIconDash = dasherize(this.actionIcon); return `${actionIconDash} js-icon-${actionIconDash}`; }, - isDisabled() { - return this.buttonDisabled === this.link; + }, + watch: { + requestFinishedFor() { + if (this.requestFinishedFor === this.linkRequested) { + this.isDisabled = false; + } }, }, - methods: { onClickAction() { $(this.$el).tooltip('hide'); eventHub.$emit('graphAction', this.link); + this.linkRequested = this.link; + this.isDisabled = true; }, }, }; @@ -62,7 +74,8 @@ export default { @click="onClickAction" v-tooltip :title="tooltipText" - class="js-ci-action btn btn-blank btn-transparent ci-action-icon-container ci-action-icon-wrapper" + class="js-ci-action btn btn-blank +btn-transparent ci-action-icon-container ci-action-icon-wrapper" :class="cssClass" data-container="body" :disabled="isDisabled" diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue index 30e40259146..9adc8881831 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue @@ -1,77 +1,83 @@ diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index f6e6569e15b..5461fdbbadd 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -29,10 +29,11 @@ export default { required: false, default: '', }, - actionDisabled: { + + requestFinishedFor: { type: String, required: false, - default: null, + default: '', }, }, @@ -74,12 +75,12 @@ export default { v-if="job.size === 1" :job="job" css-class-job-name="build-content" - :action-disabled="actionDisabled" /> diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index 900eb7855f4..6584f96130b 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -25,7 +25,7 @@ export default () => { data() { return { mediator, - actionDisabled: null, + requestFinishedFor: null, }; }, created() { @@ -36,15 +36,17 @@ export default () => { }, methods: { postAction(action) { - this.actionDisabled = action; + // Click was made, reset this variable + this.requestFinishedFor = null; - this.mediator.service.postAction(action) + this.mediator.service + .postAction(action) .then(() => { this.mediator.refreshPipeline(); - this.actionDisabled = null; + this.requestFinishedFor = action; }) .catch(() => { - this.actionDisabled = null; + this.requestFinishedFor = action; Flash(__('An error occurred while making the request.')); }); }, @@ -54,7 +56,7 @@ export default () => { props: { isLoading: this.mediator.state.isLoading, pipeline: this.mediator.store.state.pipeline, - actionDisabled: this.actionDisabled, + requestFinishedFor: this.requestFinishedFor, }, }); }, @@ -79,7 +81,8 @@ export default () => { }, methods: { postAction(action) { - this.mediator.service.postAction(action.path) + this.mediator.service + .postAction(action.path) .then(() => this.mediator.refreshPipeline()) .catch(() => Flash(__('An error occurred while making the request.'))); }, diff --git a/changelogs/unreleased/33697-remove-ujs-action-big-graph.yml b/changelogs/unreleased/33697-remove-ujs-action-big-graph.yml new file mode 100644 index 00000000000..43ce52c1209 --- /dev/null +++ b/changelogs/unreleased/33697-remove-ujs-action-big-graph.yml @@ -0,0 +1,5 @@ +--- +title: Prevent pipeline actions in dropdown to redirct to a new page +merge_request: +author: +type: fixed diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js index 581209f215d..3de10392472 100644 --- a/spec/javascripts/pipelines/graph/action_component_spec.js +++ b/spec/javascripts/pipelines/graph/action_component_spec.js @@ -6,7 +6,7 @@ import mountComponent from '../../helpers/vue_mount_component_helper'; describe('pipeline graph action component', () => { let component; - beforeEach((done) => { + beforeEach(done => { const ActionComponent = Vue.extend(actionComponent); component = mountComponent(ActionComponent, { tooltipText: 'bar', @@ -22,7 +22,7 @@ describe('pipeline graph action component', () => { }); it('should emit an event with the provided link', () => { - eventHub.$on('graphAction', (link) => { + eventHub.$on('graphAction', link => { expect(link).toEqual('foo'); }); }); @@ -31,7 +31,7 @@ describe('pipeline graph action component', () => { expect(component.$el.getAttribute('data-original-title')).toEqual('bar'); }); - it('should update bootstrap tooltip when title changes', (done) => { + it('should update bootstrap tooltip when title changes', done => { component.tooltipText = 'changed'; setTimeout(() => { @@ -44,4 +44,45 @@ describe('pipeline graph action component', () => { expect(component.$el.querySelector('.ci-action-icon-wrapper')).toBeDefined(); expect(component.$el.querySelector('svg')).toBeDefined(); }); + + it('disables the button when clicked', done => { + component.$el.click(); + + component.$nextTick(() => { + expect(component.$el.getAttribute('disabled')).toEqual('disabled'); + done(); + }); + }); + + it('re-enabled the button when `requestFinishedFor` matches `linkRequested`', done => { + component.$el.click(); + + component + .$nextTick() + .then(() => { + expect(component.$el.getAttribute('disabled')).toEqual('disabled'); + component.requestFinishedFor = 'foo'; + }) + .then(() => { + expect(component.$el.getAttribute('disabled')).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + + it('does not re-enable the button when `requestFinishedFor` does not matches `linkRequested`', done => { + component.$el.click(); + + component + .$nextTick() + .then(() => { + expect(component.$el.getAttribute('disabled')).toEqual('disabled'); + component.requestFinishedFor = 'bar'; + }) + .then(() => { + expect(component.$el.getAttribute('disabled')).toEqual('disabled'); + }) + .then(done) + .catch(done.fail); + }); });