Commit f82c3f2d authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'move-pipeline-header-actions-to-graphql' into 'master'

Refactor pipeline header to use GraphQL mutations

See merge request gitlab-org/gitlab!47011
parents c982e3c3 0b45caac
<script>
import { GlAlert, GlButton, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import ciHeader from '~/vue_shared/components/header_ci_component.vue';
import { setUrlFragment, redirectTo } from '~/lib/utils/url_utility';
import getPipelineQuery from '../graphql/queries/get_pipeline_header_data.query.graphql';
import deletePipelineMutation from '../graphql/mutations/delete_pipeline.mutation.graphql';
import retryPipelineMutation from '../graphql/mutations/retry_pipeline.mutation.graphql';
import cancelPipelineMutation from '../graphql/mutations/cancel_pipeline.mutation.graphql';
import { LOAD_FAILURE, POST_FAILURE, DELETE_FAILURE, DEFAULT } from '../constants';
const DELETE_MODAL_ID = 'pipeline-delete-modal';
const POLL_INTERVAL = 10000;
export default {
name: 'PipelineHeaderSection',
pipelineCancel: 'pipelineCancel',
pipelineRetry: 'pipelineRetry',
components: {
ciHeader,
GlAlert,
......@@ -28,7 +33,7 @@ export default {
[DEFAULT]: __('An unknown error occurred.'),
},
inject: {
// Receive `cancel`, `delete`, `fullProject` and `retry`
// Receive `fullProject` and `pipelinesPath`
paths: {
default: {},
},
......@@ -52,7 +57,7 @@ export default {
error() {
this.reportFailure(LOAD_FAILURE);
},
pollInterval: 10000,
pollInterval: POLL_INTERVAL,
watchLoading(isLoading) {
if (!isLoading) {
// To ensure apollo has updated the cache,
......@@ -122,31 +127,58 @@ export default {
reportFailure(errorType) {
this.failureType = errorType;
},
async postAction(path) {
async postPipelineAction(name, mutation) {
try {
await axios.post(path);
this.$apollo.queries.pipeline.refetch();
const {
data: {
[name]: { errors },
},
} = await this.$apollo.mutate({
mutation,
variables: { id: this.pipeline.id },
});
if (errors.length > 0) {
this.reportFailure(POST_FAILURE);
} else {
this.$apollo.queries.pipeline.refetch();
}
} catch {
this.reportFailure(POST_FAILURE);
}
},
async cancelPipeline() {
cancelPipeline() {
this.isCanceling = true;
this.postAction(this.paths.cancel);
this.postPipelineAction(this.$options.pipelineCancel, cancelPipelineMutation);
},
async retryPipeline() {
retryPipeline() {
this.isRetrying = true;
this.postAction(this.paths.retry);
this.postPipelineAction(this.$options.pipelineRetry, retryPipelineMutation);
},
async deletePipeline() {
this.isDeleting = true;
this.$apollo.queries.pipeline.stopPolling();
try {
const { request } = await axios.delete(this.paths.delete);
redirectTo(setUrlFragment(request.responseURL, 'delete_success'));
const {
data: {
pipelineDestroy: { errors },
},
} = await this.$apollo.mutate({
mutation: deletePipelineMutation,
variables: {
id: this.pipeline.id,
},
});
if (errors.length > 0) {
this.reportFailure(DELETE_FAILURE);
this.isDeleting = false;
} else {
redirectTo(setUrlFragment(this.paths.pipelinesPath, 'delete_success'));
}
} catch {
this.$apollo.queries.pipeline.startPolling();
this.$apollo.queries.pipeline.startPolling(POLL_INTERVAL);
this.reportFailure(DELETE_FAILURE);
this.isDeleting = false;
}
......
mutation cancelPipeline($id: CiPipelineID!) {
pipelineCancel(input: { id: $id }) {
errors
}
}
mutation deletePipeline($id: CiPipelineID!) {
pipelineDestroy(input: { id: $id }) {
errors
}
}
mutation retryPipeline($id: CiPipelineID!) {
pipelineRetry(input: { id: $id }) {
errors
}
}
......@@ -16,7 +16,7 @@ export const createPipelineHeaderApp = elSelector => {
return;
}
const { cancelPath, deletePath, fullPath, pipelineId, pipelineIid, retryPath } = el?.dataset;
const { fullPath, pipelineId, pipelineIid, pipelinesPath } = el?.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
......@@ -26,10 +26,8 @@ export const createPipelineHeaderApp = elSelector => {
apolloProvider,
provide: {
paths: {
cancel: cancelPath,
delete: deletePath,
fullProject: fullPath,
retry: retryPath,
pipelinesPath,
},
pipelineId,
pipelineIid,
......
......@@ -7,7 +7,7 @@
- add_page_specific_style 'page_bundles/ci_status'
.js-pipeline-container{ data: { controller_action: "#{controller.action_name}" } }
#js-pipeline-header-vue.pipeline-header-container{ data: {full_path: @project.full_path, retry_path: retry_project_pipeline_path(@pipeline.project, @pipeline), cancel_path: cancel_project_pipeline_path(@pipeline.project, @pipeline), delete_path: project_pipeline_path(@pipeline.project, @pipeline), pipeline_iid: @pipeline.iid, pipeline_id: @pipeline.id} }
#js-pipeline-header-vue.pipeline-header-container{ data: { full_path: @project.full_path, pipeline_iid: @pipeline.iid, pipeline_id: @pipeline.id, pipelines_path: project_pipelines_path(@project) } }
- if @pipeline.commit.present?
= render "projects/pipelines/info", commit: @pipeline.commit
......
import { shallowMount } from '@vue/test-utils';
import { GlModal, GlLoadingIcon } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import {
mockCancelledPipelineHeader,
mockFailedPipelineHeader,
mockRunningPipelineHeader,
mockSuccessfulPipelineHeader,
} from './mock_data';
import axios from '~/lib/utils/axios_utils';
import HeaderComponent from '~/pipelines/components/header_component.vue';
import deletePipelineMutation from '~/pipelines/graphql/mutations/delete_pipeline.mutation.graphql';
import retryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql';
import cancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql';
describe('Pipeline details header', () => {
let wrapper;
let glModalDirective;
let mockAxios;
const findDeleteModal = () => wrapper.find(GlModal);
const findRetryButton = () => wrapper.find('[data-testid="retryPipeline"]');
......@@ -25,9 +25,7 @@ describe('Pipeline details header', () => {
pipelineId: 14,
pipelineIid: 1,
paths: {
retry: '/retry',
cancel: '/cancel',
delete: '/delete',
pipelinesPath: '/namespace/my-project/-/pipelines',
fullProject: '/namespace/my-project',
},
};
......@@ -43,6 +41,7 @@ describe('Pipeline details header', () => {
startPolling: jest.fn(),
},
},
mutate: jest.fn(),
};
return shallowMount(HeaderComponent, {
......@@ -65,16 +64,9 @@ describe('Pipeline details header', () => {
});
};
beforeEach(() => {
mockAxios = new MockAdapter(axios);
mockAxios.onGet('*').replyOnce(200);
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
mockAxios.restore();
});
describe('initial loading', () => {
......@@ -111,13 +103,13 @@ describe('Pipeline details header', () => {
wrapper = createComponent(mockCancelledPipelineHeader);
});
it('should call axios with the right path when retry button is clicked', async () => {
jest.spyOn(axios, 'post');
it('should call retryPipeline Mutation with pipeline id', () => {
findRetryButton().vm.$emit('click');
await wrapper.vm.$nextTick();
expect(axios.post).toHaveBeenCalledWith(defaultProvideOptions.paths.retry);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: retryPipelineMutation,
variables: { id: mockCancelledPipelineHeader.id },
});
});
});
......@@ -126,13 +118,13 @@ describe('Pipeline details header', () => {
wrapper = createComponent(mockRunningPipelineHeader);
});
it('should call axios with the right path when cancel button is clicked', async () => {
jest.spyOn(axios, 'post');
it('should call cancelPipeline Mutation with pipeline id', () => {
findCancelButton().vm.$emit('click');
await wrapper.vm.$nextTick();
expect(axios.post).toHaveBeenCalledWith(defaultProvideOptions.paths.cancel);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: cancelPipelineMutation,
variables: { id: mockRunningPipelineHeader.id },
});
});
});
......@@ -141,24 +133,21 @@ describe('Pipeline details header', () => {
wrapper = createComponent(mockFailedPipelineHeader);
});
it('displays delete modal when clicking on delete and does not call the delete action', async () => {
jest.spyOn(axios, 'delete');
it('displays delete modal when clicking on delete and does not call the delete action', () => {
findDeleteButton().vm.$emit('click');
await wrapper.vm.$nextTick();
expect(findDeleteModal().props('modalId')).toBe(wrapper.vm.$options.DELETE_MODAL_ID);
expect(glModalDirective).toHaveBeenCalledWith(wrapper.vm.$options.DELETE_MODAL_ID);
expect(axios.delete).not.toHaveBeenCalled();
expect(wrapper.vm.$apollo.mutate).not.toHaveBeenCalled();
});
it('should call delete path when modal is submitted', async () => {
jest.spyOn(axios, 'delete');
it('should call deletePipeline Mutation with pipeline id when modal is submitted', () => {
findDeleteModal().vm.$emit('ok');
await wrapper.vm.$nextTick();
expect(axios.delete).toHaveBeenCalledWith(defaultProvideOptions.paths.delete);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: deletePipelineMutation,
variables: { id: mockFailedPipelineHeader.id },
});
});
});
});
......
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