Resolve "Remove modal box confirmation when retrying a pipeline"

This commit is contained in:
Filipa Lacerda 2018-05-11 14:03:27 +00:00 committed by Phil Hughes
parent 27d0c42986
commit feb9caab90
9 changed files with 124 additions and 241 deletions

View file

@ -1,95 +0,0 @@
<script>
/* eslint-disable no-alert */
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import icon from '../../vue_shared/components/icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
components: {
loadingIcon,
icon,
},
props: {
endpoint: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
cssClass: {
type: String,
required: true,
},
pipelineId: {
type: Number,
required: true,
},
type: {
type: String,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
computed: {
buttonClass() {
return `btn ${this.cssClass}`;
},
},
created() {
// We're using eventHub to listen to the modal here instead of
// using props because it would would make the parent components
// much more complex to keep track of the loading state of each button
eventHub.$on('postAction', this.setLoading);
},
beforeDestroy() {
eventHub.$off('postAction', this.setLoading);
},
methods: {
onClick() {
eventHub.$emit('openConfirmationModal', {
pipelineId: this.pipelineId,
endpoint: this.endpoint,
type: this.type,
});
},
setLoading(endpoint) {
if (endpoint === this.endpoint) {
this.isLoading = true;
}
},
},
};
</script>
<template>
<button
v-tooltip
type="button"
@click="onClick"
:class="buttonClass"
:title="title"
:aria-label="title"
data-container="body"
data-placement="top"
:disabled="isLoading">
<icon
:name="icon"
/>
<loading-icon v-if="isLoading" />
</button>
</template>

View file

@ -1,7 +1,7 @@
<script>
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
import Modal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
import pipelinesTableRowComponent from './pipelines_table_row.vue';
import PipelinesTableRowComponent from './pipelines_table_row.vue';
import eventHub from '../event_hub';
/**
@ -11,8 +11,8 @@
*/
export default {
components: {
pipelinesTableRowComponent,
DeprecatedModal,
PipelinesTableRowComponent,
Modal,
},
props: {
pipelines: {
@ -37,31 +37,19 @@
return {
pipelineId: '',
endpoint: '',
type: '',
};
},
computed: {
modalTitle() {
return this.type === 'stop' ?
sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), {
pipelineId: `'${this.pipelineId}'`,
}, false) :
sprintf(s__('Pipeline|Retry pipeline #%{pipelineId}?'), {
pipelineId: `'${this.pipelineId}'`,
return sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), {
pipelineId: `${this.pipelineId}`,
}, false);
},
modalText() {
return this.type === 'stop' ?
sprintf(s__('Pipeline|Youre about to stop pipeline %{pipelineId}.'), {
pipelineId: `<strong>#${this.pipelineId}</strong>`,
}, false) :
sprintf(s__('Pipeline|Youre about to retry pipeline %{pipelineId}.'), {
return sprintf(s__('Pipeline|Youre about to stop pipeline %{pipelineId}.'), {
pipelineId: `<strong>#${this.pipelineId}</strong>`,
}, false);
},
primaryButtonLabel() {
return this.type === 'stop' ? s__('Pipeline|Stop pipeline') : s__('Pipeline|Retry pipeline');
},
},
created() {
eventHub.$on('openConfirmationModal', this.setModalData);
@ -73,7 +61,6 @@
setModalData(data) {
this.pipelineId = data.pipelineId;
this.endpoint = data.endpoint;
this.type = data.type;
},
onSubmit() {
eventHub.$emit('postAction', this.endpoint);
@ -120,20 +107,16 @@
:auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType"
/>
<deprecated-modal
<modal
id="confirmation-modal"
:title="modalTitle"
:text="modalText"
kind="danger"
:primary-button-label="primaryButtonLabel"
:header-title-text="modalTitle"
footer-primary-button-variant="danger"
:footer-primary-button-text="s__('Pipeline|Stop pipeline')"
@submit="onSubmit"
>
<template
slot="body"
slot-scope="props"
>
<p v-html="props.text"></p>
</template>
</deprecated-modal>
<span v-html="modalText"></span>
</modal>
</div>
</template>

View file

@ -1,13 +1,14 @@
<script>
/* eslint-disable no-param-reassign */
import asyncButtonComponent from './async_button.vue';
import pipelinesActionsComponent from './pipelines_actions.vue';
import pipelinesArtifactsComponent from './pipelines_artifacts.vue';
import ciBadge from '../../vue_shared/components/ci_badge_link.vue';
import pipelineStage from './stage.vue';
import pipelineUrl from './pipeline_url.vue';
import pipelinesTimeago from './time_ago.vue';
import commitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub';
import PipelinesActionsComponent from './pipelines_actions.vue';
import PipelinesArtifactsComponent from './pipelines_artifacts.vue';
import CiBadge from '../../vue_shared/components/ci_badge_link.vue';
import PipelineStage from './stage.vue';
import PipelineUrl from './pipeline_url.vue';
import PipelinesTimeago from './time_ago.vue';
import CommitComponent from '../../vue_shared/components/commit.vue';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
import Icon from '../../vue_shared/components/icon.vue';
/**
* Pipeline table row.
@ -16,14 +17,15 @@
*/
export default {
components: {
asyncButtonComponent,
pipelinesActionsComponent,
pipelinesArtifactsComponent,
commitComponent,
pipelineStage,
pipelineUrl,
ciBadge,
pipelinesTimeago,
PipelinesActionsComponent,
PipelinesArtifactsComponent,
CommitComponent,
PipelineStage,
PipelineUrl,
CiBadge,
PipelinesTimeago,
LoadingButton,
Icon,
},
props: {
pipeline: {
@ -44,6 +46,12 @@
required: true,
},
},
data() {
return {
isRetrying: false,
isCancelling: false,
};
},
computed: {
/**
* If provided, returns the commit tag.
@ -119,8 +127,10 @@
if (this.pipeline.ref) {
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
if (prop === 'path') {
// eslint-disable-next-line no-param-reassign
accumulator.ref_url = this.pipeline.ref[prop];
} else {
// eslint-disable-next-line no-param-reassign
accumulator[prop] = this.pipeline.ref[prop];
}
return accumulator;
@ -216,6 +226,21 @@
return this.viewType === 'child';
},
},
methods: {
handleCancelClick() {
this.isCancelling = true;
eventHub.$emit('openConfirmationModal', {
pipelineId: this.pipeline.id,
endpoint: this.pipeline.cancel_path,
});
},
handleRetryClick() {
this.isRetrying = true;
eventHub.$emit('retryPipeline', this.pipeline.retry_path);
},
},
};
</script>
<template>
@ -287,7 +312,8 @@
<div
v-if="displayPipelineActions"
class="table-section section-20 table-button-footer pipeline-actions">
class="table-section section-20 table-button-footer pipeline-actions"
>
<div class="btn-group table-action-buttons">
<pipelines-actions-component
v-if="pipeline.details.manual_actions.length"
@ -300,29 +326,27 @@
:artifacts="pipeline.details.artifacts"
/>
<async-button-component
<loading-button
v-if="pipeline.flags.retryable"
:endpoint="pipeline.retry_path"
css-class="js-pipelines-retry-button btn-default btn-retry"
title="Retry"
icon="repeat"
:pipeline-id="pipeline.id"
data-toggle="modal"
data-target="#confirmation-modal"
type="retry"
/>
@click="handleRetryClick"
container-class="js-pipelines-retry-button btn btn-default btn-retry"
:loading="isRetrying"
:disabled="isRetrying"
>
<icon name="repeat" />
</loading-button>
<async-button-component
<loading-button
v-if="pipeline.flags.cancelable"
:endpoint="pipeline.cancel_path"
css-class="js-pipelines-cancel-button btn-remove"
title="Stop"
icon="close"
:pipeline-id="pipeline.id"
@click="handleCancelClick"
data-toggle="modal"
data-target="#confirmation-modal"
type="stop"
/>
container-class="js-pipelines-cancel-button btn btn-remove"
:loading="isCancelling"
:disabled="isCancelling"
>
<icon name="close" />
</loading-button>
</div>
</div>
</div>

View file

@ -53,10 +53,12 @@ export default {
});
eventHub.$on('postAction', this.postAction);
eventHub.$on('retryPipeline', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
eventHub.$off('retryPipeline', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
},
destroyed() {

View file

@ -70,12 +70,14 @@
/>
</transition>
<transition name="fade">
<slot>
<span
v-if="label"
class="js-loading-button-label"
>
{{ label }}
</span>
</slot>
</transition>
</button>
</template>

View file

@ -0,0 +1,5 @@
---
title: Remove modalbox confirmation when retrying a pipeline
merge_request: 18879
author:
type: changed

View file

@ -125,7 +125,7 @@ describe 'Pipelines', :js do
context 'when canceling' do
before do
find('.js-pipelines-cancel-button').click
find('.js-primary-button').click
find('.js-modal-primary-action').click
wait_for_requests
end
@ -156,7 +156,6 @@ describe 'Pipelines', :js do
context 'when retrying' do
before do
find('.js-pipelines-retry-button').click
find('.js-primary-button').click
wait_for_requests
end
@ -256,7 +255,7 @@ describe 'Pipelines', :js do
context 'when canceling' do
before do
find('.js-pipelines-cancel-button').click
find('.js-primary-button').click
find('.js-modal-primary-action').click
end
it 'indicates that pipeline was canceled' do

View file

@ -1,62 +0,0 @@
import Vue from 'vue';
import asyncButtonComp from '~/pipelines/components/async_button.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Async Button', () => {
let component;
let AsyncButtonComponent;
beforeEach(() => {
AsyncButtonComponent = Vue.extend(asyncButtonComp);
component = new AsyncButtonComponent({
propsData: {
endpoint: '/foo',
title: 'Foo',
icon: 'repeat',
cssClass: 'bar',
pipelineId: 123,
type: 'explode',
},
}).$mount();
});
it('should render a button', () => {
expect(component.$el.tagName).toEqual('BUTTON');
});
it('should render svg icon', () => {
expect(component.$el.querySelector('svg')).not.toBeNull();
});
it('should render the provided title', () => {
expect(component.$el.getAttribute('data-original-title')).toContain('Foo');
expect(component.$el.getAttribute('aria-label')).toContain('Foo');
});
it('should render the provided cssClass', () => {
expect(component.$el.getAttribute('class')).toContain('bar');
});
describe('With confirm dialog', () => {
it('should call the service when confimation is positive', () => {
eventHub.$on('openConfirmationModal', (data) => {
expect(data.pipelineId).toEqual(123);
expect(data.type).toEqual('explode');
});
component = new AsyncButtonComponent({
propsData: {
endpoint: '/foo',
title: 'Foo',
icon: 'fa fa-foo',
cssClass: 'bar',
pipelineId: 123,
type: 'explode',
},
}).$mount();
component.$el.click();
});
});
});

View file

@ -1,5 +1,6 @@
import Vue from 'vue';
import tableRowComp from '~/pipelines/components/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
@ -151,13 +152,37 @@ describe('Pipelines Table Row', () => {
describe('actions column', () => {
beforeEach(() => {
component = buildComponent(pipeline);
const withActions = Object.assign({}, pipeline);
withActions.flags.cancelable = true;
withActions.flags.retryable = true;
withActions.cancel_path = '/cancel';
withActions.retry_path = '/retry';
component = buildComponent(withActions);
});
it('should render the provided actions', () => {
expect(
component.$el.querySelectorAll('.table-section:nth-child(6) ul li').length,
).toEqual(pipeline.details.manual_actions.length);
expect(component.$el.querySelector('.js-pipelines-retry-button')).not.toBeNull();
expect(component.$el.querySelector('.js-pipelines-cancel-button')).not.toBeNull();
});
it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => {
eventHub.$on('retryPipeline', (endpoint) => {
expect(endpoint).toEqual('/retry');
});
component.$el.querySelector('.js-pipelines-retry-button').click();
expect(component.isRetrying).toEqual(true);
});
it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => {
eventHub.$on('openConfirmationModal', (data) => {
expect(data.endpoint).toEqual('/cancel');
expect(data.pipelineId).toEqual(pipeline.id);
});
component.$el.querySelector('.js-pipelines-cancel-button').click();
expect(component.isCancelling).toEqual(true);
});
});
});