Commit 52716b39 authored by Fabio Huser's avatar Fabio Huser

refactor(pipelines): rework pipeline deletion to use AJAX

parent 93d3a4d2
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlModal } from '@gitlab/ui';
import ciHeader from '../../vue_shared/components/header_ci_component.vue';
import eventHub from '../event_hub';
import { __ } from '~/locale';
......@@ -9,6 +9,7 @@ export default {
components: {
ciHeader,
GlLoadingIcon,
GlModal,
},
props: {
pipeline: {
......@@ -49,6 +50,13 @@ export default {
eventHub.$emit('headerPostAction', action);
},
deletePipeline() {
const index = this.actions.findIndex(action => action.modal === 'pipeline-delete-modal');
this.$set(this.actions[index], 'isLoading', true);
eventHub.$emit('headerDeleteAction', this.actions[index]);
},
getActions() {
const actions = [];
......@@ -77,10 +85,9 @@ export default {
actions.push({
label: __('Delete'),
path: this.pipeline.delete_path,
method: 'delete',
confirm: __('Are you sure you want to delete this pipeline?'),
modal: 'pipeline-delete-modal',
cssClass: 'js-btn-delete-pipeline btn btn-danger btn-inverted',
type: 'ujs-link',
type: 'modal-button',
isLoading: false,
});
}
......@@ -102,6 +109,23 @@ export default {
item-name="Pipeline"
@actionClicked="postAction"
/>
<gl-loading-icon v-if="isLoading" :size="2" class="prepend-top-default append-bottom-default" />
<gl-modal
modal-id="pipeline-delete-modal"
:title="__('Delete pipeline')"
:ok-title="__('Delete pipeline')"
ok-variant="danger"
@ok="deletePipeline()"
>
<p>
{{
__(
'Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts and triggers. This action cannot be undone.',
)
}}
</p>
</gl-modal>
</div>
</template>
import Vue from 'vue';
import Visibility from 'visibilityjs';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlToast } from '@gitlab/ui';
import { doesHashExistInUrl } from '~/lib/utils/url_utility';
import { historyPushState, buildUrlWithCurrentLocation } from '~/lib/utils/common_utils';
import { __ } from '../../locale';
import createFlash from '../../flash';
import Poll from '../../lib/utils/poll';
......@@ -9,6 +12,8 @@ import PipelinesTableComponent from '../components/pipelines_table.vue';
import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
Vue.use(GlToast);
export default {
components: {
PipelinesTableComponent,
......@@ -57,6 +62,11 @@ export default {
}
});
if (doesHashExistInUrl('delete_success')) {
this.$toast.show(__('The pipeline has been deleted'));
historyPushState(buildUrlWithCurrentLocation());
}
eventHub.$on('postAction', this.postAction);
eventHub.$on('retryPipeline', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
......
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import Flash from '~/flash';
import Translate from '~/vue_shared/translate';
import { __ } from '~/locale';
import { setUrlFragment, redirectTo } from '~/lib/utils/url_utility';
import pipelineGraph from './components/graph/graph_component.vue';
import GraphBundleMixin from './mixins/graph_pipeline_bundle_mixin';
import PipelinesMediator from './pipeline_details_mediator';
......@@ -62,9 +63,11 @@ export default () => {
},
created() {
eventHub.$on('headerPostAction', this.postAction);
eventHub.$on('headerDeleteAction', this.deleteAction);
},
beforeDestroy() {
eventHub.$off('headerPostAction', this.postAction);
eventHub.$off('headerDeleteAction', this.deleteAction);
},
methods: {
postAction(action) {
......@@ -73,6 +76,15 @@ export default () => {
.then(() => this.mediator.refreshPipeline())
.catch(() => Flash(__('An error occurred while making the request.')));
},
deleteAction(action) {
this.mediator.stopPipelinePoll();
this.mediator.service
.deleteAction(action.path)
.then(response =>
redirectTo(setUrlFragment(response.request.responseURL, 'delete_success')),
)
.catch(() => Flash(__('An error occurred while deleting the pipeline.')));
},
},
render(createElement) {
return createElement('pipeline-header', {
......
......@@ -64,6 +64,10 @@ export default class pipelinesMediator {
);
}
stopPipelinePoll() {
this.poll.stop();
}
/**
* Backend expects paramets in the following format: `expanded[]=id&expanded[]=id`
*/
......
......@@ -9,6 +9,11 @@ export default class PipelineService {
return axios.get(this.pipeline, { params });
}
// eslint-disable-next-line class-methods-use-this
deleteAction(endpoint) {
return axios.delete(`${endpoint}.json`);
}
// eslint-disable-next-line class-methods-use-this
postAction(endpoint) {
return axios.post(`${endpoint}.json`);
......
<script>
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { GlTooltipDirective, GlLink, GlButton, GlModalDirective } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import CiIconBadge from './ci_badge_link.vue';
import TimeagoTooltip from './time_ago_tooltip.vue';
......@@ -24,6 +24,7 @@ export default {
},
directives: {
GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
},
props: {
status: {
......@@ -117,36 +118,26 @@ export default {
<section v-if="actions.length" class="header-action-buttons">
<template v-for="(action, i) in actions">
<gl-link
v-if="action.type === 'link'"
:key="i"
:href="action.path"
:class="action.cssClass"
>
{{ action.label }}
</gl-link>
<gl-link
v-else-if="action.type === 'ujs-link'"
<loading-button
v-if="action.type === 'button'"
:key="i"
:href="action.path"
:loading="action.isLoading"
:disabled="action.isLoading"
:class="action.cssClass"
:data-method="action.method"
:data-confirm="action.confirm"
rel="nofollow"
>
{{ action.label }}
</gl-link>
container-class="d-inline"
:label="action.label"
@click="onClickAction(action)"
/>
<loading-button
v-else-if="action.type === 'button'"
v-else-if="action.type === 'modal-button'"
:key="i"
v-gl-modal="action.modal"
:loading="action.isLoading"
:disabled="action.isLoading"
:class="action.cssClass"
container-class="d-inline"
:label="action.label"
@click="onClickAction(action)"
/>
</template>
</section>
......
......@@ -83,9 +83,7 @@ class Projects::PipelinesController < Projects::ApplicationController
def destroy
::Ci::DestroyPipelineService.new(project, current_user).execute(pipeline)
redirect_to project_pipelines_path(project),
status: :found,
notice: _("Pipeline has been successfully deleted!")
redirect_to project_pipelines_path(project), status: :see_other
end
def builds
......
......@@ -1631,6 +1631,9 @@ msgstr ""
msgid "An error occurred while deleting the comment"
msgstr ""
msgid "An error occurred while deleting the pipeline."
msgstr ""
msgid "An error occurred while detecting host keys"
msgstr ""
......@@ -2092,7 +2095,7 @@ msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
msgid "Are you sure you want to delete this pipeline?"
msgid "Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts and triggers. This action cannot be undone."
msgstr ""
msgid "Are you sure you want to erase this build?"
......@@ -5735,6 +5738,9 @@ msgstr ""
msgid "Delete list"
msgstr ""
msgid "Delete pipeline"
msgstr ""
msgid "Delete snippet"
msgstr ""
......@@ -12870,9 +12876,6 @@ msgstr ""
msgid "Pipeline Schedules"
msgstr ""
msgid "Pipeline has been successfully deleted!"
msgstr ""
msgid "Pipeline minutes quota"
msgstr ""
......@@ -18083,6 +18086,9 @@ msgstr ""
msgid "The phase of the development lifecycle."
msgstr ""
msgid "The pipeline has been deleted"
msgstr ""
msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user."
msgstr ""
......
......@@ -754,7 +754,7 @@ describe Projects::PipelinesController do
it 'deletes pipeline and redirects' do
delete_pipeline
expect(response).to have_gitlab_http_status(302)
expect(response).to have_gitlab_http_status(303)
expect { build.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
......
......@@ -64,11 +64,12 @@ describe('Pipeline details header', () => {
vm.$el.querySelector('.js-retry-button').click();
});
it('should contain delete button with proper path and method', () => {
const link = vm.$el.querySelector('.js-btn-delete-pipeline');
it('should fire modal event when delete button action is clicked', () => {
vm.$root.$on('bv::modal::show', action => {
expect(action.componentId).toEqual('pipeline-delete-modal');
});
expect(link.getAttribute('href')).toContain('path');
expect(link.getAttribute('data-method')).toContain('delete');
vm.$el.querySelector('.js-btn-delete-pipeline').click();
});
});
});
......@@ -35,20 +35,12 @@ describe('Header CI Component', () => {
cssClass: 'btn',
isLoading: false,
},
{
label: 'Go',
path: 'path',
type: 'link',
cssClass: 'link',
isLoading: false,
},
{
label: 'Delete',
path: 'path',
type: 'ujs-link',
cssClass: 'ujs-link',
method: 'delete',
confirm: 'Are you sure?',
type: 'modal-button',
cssClass: 'btn-modal',
isLoading: false,
},
],
hasSidebarButton: true,
......@@ -86,19 +78,12 @@ describe('Header CI Component', () => {
it('should render provided actions', () => {
const btn = vm.$el.querySelector('.btn');
const link = vm.$el.querySelector('.link');
const ujsLink = vm.$el.querySelector('.ujs-link');
const btnModal = vm.$el.querySelector('.btn-modal');
expect(btn.tagName).toEqual('BUTTON');
expect(btn.textContent.trim()).toEqual(props.actions[0].label);
expect(link.tagName).toEqual('A');
expect(link.textContent.trim()).toEqual(props.actions[1].label);
expect(link.getAttribute('href')).toEqual(props.actions[0].path);
expect(ujsLink.tagName).toEqual('A');
expect(ujsLink.textContent.trim()).toEqual(props.actions[2].label);
expect(ujsLink.getAttribute('href')).toEqual(props.actions[2].path);
expect(ujsLink.getAttribute('data-method')).toEqual(props.actions[2].method);
expect(ujsLink.getAttribute('data-confirm')).toEqual(props.actions[2].confirm);
expect(btnModal.tagName).toEqual('BUTTON');
expect(btnModal.textContent.trim()).toEqual(props.actions[1].label);
});
it('should show loading icon', done => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment