Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipelines/graph')
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js27
-rw-r--r--spec/frontend/pipelines/graph/job_item_spec.js184
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js336
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js1
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_mock_data.js5
-rw-r--r--spec/frontend/pipelines/graph/mock_data.js122
6 files changed, 506 insertions, 169 deletions
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index cb7073fb5f5..49d64c6eac0 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -16,7 +16,7 @@ import {
} from '~/performance/constants';
import * as perfUtils from '~/performance/utils';
import {
- IID_FAILURE,
+ ACTION_FAILURE,
LAYER_VIEW,
STAGE_VIEW,
VIEW_TYPE_KEY,
@@ -188,7 +188,9 @@ describe('Pipeline graph wrapper', () => {
it('displays the no iid alert', () => {
expect(getAlert().exists()).toBe(true);
- expect(getAlert().text()).toBe(wrapper.vm.$options.errorTexts[IID_FAILURE]);
+ expect(getAlert().text()).toBe(
+ 'The data in this pipeline is too old to be rendered as a graph. Please check the Jobs tab to access historical data.',
+ );
});
it('does not display the graph', () => {
@@ -196,6 +198,27 @@ describe('Pipeline graph wrapper', () => {
});
});
+ describe('when there is an error with an action in the graph', () => {
+ beforeEach(async () => {
+ createComponentWithApollo();
+ await waitForPromises();
+ await getGraph().vm.$emit('error', { type: ACTION_FAILURE });
+ });
+
+ it('does not display the loading icon', () => {
+ expect(getLoadingIcon().exists()).toBe(false);
+ });
+
+ it('displays the action error alert', () => {
+ expect(getAlert().exists()).toBe(true);
+ expect(getAlert().text()).toBe('An error occurred while performing this action.');
+ });
+
+ it('displays the graph', () => {
+ expect(getGraph().exists()).toBe(true);
+ });
+ });
+
describe('when refresh action is emitted', () => {
beforeEach(async () => {
createComponentWithApollo();
diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
index 23e7ed7ebb4..4f0da09fec6 100644
--- a/spec/frontend/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -1,89 +1,34 @@
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { GlBadge } from '@gitlab/ui';
import JobItem from '~/pipelines/components/graph/job_item.vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import {
+ delayedJob,
+ mockJob,
+ mockJobWithoutDetails,
+ mockJobWithUnauthorizedAction,
+ triggerJob,
+} from './mock_data';
describe('pipeline graph job item', () => {
let wrapper;
- const findJobWithoutLink = () => wrapper.find('[data-testid="job-without-link"]');
- const findJobWithLink = () => wrapper.find('[data-testid="job-with-link"]');
- const findActionComponent = () => wrapper.find('[data-testid="ci-action-component"]');
+ const findJobWithoutLink = () => wrapper.findByTestId('job-without-link');
+ const findJobWithLink = () => wrapper.findByTestId('job-with-link');
+ const findActionComponent = () => wrapper.findByTestId('ci-action-component');
+ const findBadge = () => wrapper.findComponent(GlBadge);
const createWrapper = (propsData) => {
- wrapper = mount(JobItem, {
- propsData,
- });
+ wrapper = extendedWrapper(
+ mount(JobItem, {
+ propsData,
+ }),
+ );
};
const triggerActiveClass = 'gl-shadow-x0-y0-b3-s1-blue-500';
- const delayedJob = {
- __typename: 'CiJob',
- name: 'delayed job',
- scheduledAt: '2015-07-03T10:01:00.000Z',
- needs: [],
- status: {
- __typename: 'DetailedStatus',
- icon: 'status_scheduled',
- tooltip: 'delayed manual action (%{remainingTime})',
- hasDetails: true,
- detailsPath: '/root/kinder-pipe/-/jobs/5339',
- group: 'scheduled',
- action: {
- __typename: 'StatusAction',
- icon: 'time-out',
- title: 'Unschedule',
- path: '/frontend-fixtures/builds-project/-/jobs/142/unschedule',
- buttonTitle: 'Unschedule job',
- },
- },
- };
-
- const mockJob = {
- id: 4256,
- name: 'test',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- tooltip: 'passed',
- group: 'success',
- detailsPath: '/root/ci-mock/builds/4256',
- hasDetails: true,
- action: {
- icon: 'retry',
- title: 'Retry',
- path: '/root/ci-mock/builds/4256/retry',
- method: 'post',
- },
- },
- };
- const mockJobWithoutDetails = {
- id: 4257,
- name: 'job_without_details',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- detailsPath: '/root/ci-mock/builds/4257',
- hasDetails: false,
- },
- };
- const mockJobWithUnauthorizedAction = {
- id: 4258,
- name: 'stop-environment',
- status: {
- icon: 'status_manual',
- label: 'manual stop action (not allowed)',
- tooltip: 'manual action',
- group: 'manual',
- detailsPath: '/root/ci-mock/builds/4258',
- hasDetails: true,
- action: null,
- },
- };
-
afterEach(() => {
wrapper.destroy();
});
@@ -148,13 +93,25 @@ describe('pipeline graph job item', () => {
});
});
- it('should render provided class name', () => {
- createWrapper({
- job: mockJob,
- cssClassJobName: 'css-class-job-name',
+ describe('job style', () => {
+ beforeEach(() => {
+ createWrapper({
+ job: mockJob,
+ cssClassJobName: 'css-class-job-name',
+ });
+ });
+
+ it('should render provided class name', () => {
+ expect(wrapper.find('a').classes()).toContain('css-class-job-name');
+ });
+
+ it('does not show a badge on the job item', () => {
+ expect(findBadge().exists()).toBe(false);
});
- expect(wrapper.find('a').classes()).toContain('css-class-job-name');
+ it('does not apply the trigger job class', () => {
+ expect(findJobWithLink().classes()).not.toContain('gl-rounded-lg');
+ });
});
describe('status label', () => {
@@ -201,34 +158,51 @@ describe('pipeline graph job item', () => {
});
});
- describe('trigger job highlighting', () => {
- it.each`
- job | jobName | expanded | link
- ${mockJob} | ${mockJob.name} | ${true} | ${true}
- ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${true} | ${false}
- `(
- `trigger job should stay highlighted when downstream is expanded`,
- ({ job, jobName, expanded, link }) => {
- createWrapper({ job, pipelineExpanded: { jobName, expanded } });
- const findJobEl = link ? findJobWithLink : findJobWithoutLink;
-
- expect(findJobEl().classes()).toContain(triggerActiveClass);
- },
- );
+ describe('trigger job', () => {
+ describe('card', () => {
+ beforeEach(() => {
+ createWrapper({ job: triggerJob });
+ });
- it.each`
- job | jobName | expanded | link
- ${mockJob} | ${mockJob.name} | ${false} | ${true}
- ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${false} | ${false}
- `(
- `trigger job should not be highlighted when downstream is not expanded`,
- ({ job, jobName, expanded, link }) => {
- createWrapper({ job, pipelineExpanded: { jobName, expanded } });
- const findJobEl = link ? findJobWithLink : findJobWithoutLink;
-
- expect(findJobEl().classes()).not.toContain(triggerActiveClass);
- },
- );
+ it('shows a badge on the job item', () => {
+ expect(findBadge().exists()).toBe(true);
+ expect(findBadge().text()).toBe('Trigger job');
+ });
+
+ it('applies a rounded corner style instead of the usual pill shape', () => {
+ expect(findJobWithoutLink().classes()).toContain('gl-rounded-lg');
+ });
+ });
+
+ describe('highlighting', () => {
+ it.each`
+ job | jobName | expanded | link
+ ${mockJob} | ${mockJob.name} | ${true} | ${true}
+ ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${true} | ${false}
+ `(
+ `trigger job should stay highlighted when downstream is expanded`,
+ ({ job, jobName, expanded, link }) => {
+ createWrapper({ job, pipelineExpanded: { jobName, expanded } });
+ const findJobEl = link ? findJobWithLink : findJobWithoutLink;
+
+ expect(findJobEl().classes()).toContain(triggerActiveClass);
+ },
+ );
+
+ it.each`
+ job | jobName | expanded | link
+ ${mockJob} | ${mockJob.name} | ${false} | ${true}
+ ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${false} | ${false}
+ `(
+ `trigger job should not be highlighted when downstream is not expanded`,
+ ({ job, jobName, expanded, link }) => {
+ createWrapper({ job, pipelineExpanded: { jobName, expanded } });
+ const findJobEl = link ? findJobWithLink : findJobWithoutLink;
+
+ expect(findJobEl().classes()).not.toContain(triggerActiveClass);
+ },
+ );
+ });
});
describe('job classes', () => {
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index d800a8c341e..06fd970778c 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -1,11 +1,21 @@
-import { GlButton, GlLoadingIcon } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlButton, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
-import { UPSTREAM, DOWNSTREAM } from '~/pipelines/components/graph/constants';
+import { ACTION_FAILURE, UPSTREAM, DOWNSTREAM } from '~/pipelines/components/graph/constants';
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
+import { PIPELINE_GRAPHQL_TYPE } from '~/pipelines/constants';
+import CancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql';
+import RetryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
import mockPipeline from './linked_pipelines_mock_data';
+Vue.use(VueApollo);
+
describe('Linked pipeline', () => {
let wrapper;
@@ -27,22 +37,30 @@ describe('Linked pipeline', () => {
};
const findButton = () => wrapper.find(GlButton);
- const findDownstreamPipelineTitle = () => wrapper.find('[data-testid="downstream-title"]');
- const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]');
+ const findCancelButton = () => wrapper.findByLabelText('Cancel downstream pipeline');
+ const findCardTooltip = () => wrapper.findComponent(GlTooltip);
+ const findDownstreamPipelineTitle = () => wrapper.findByTestId('downstream-title');
+ const findExpandButton = () => wrapper.findByTestId('expand-pipeline-button');
const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
- const findPipelineLink = () => wrapper.find('[data-testid="pipelineLink"]');
- const findExpandButton = () => wrapper.find('[data-testid="expand-pipeline-button"]');
-
- const createWrapper = (propsData, data = []) => {
- wrapper = mount(LinkedPipelineComponent, {
- propsData,
- data() {
- return {
- ...data,
- };
- },
- });
+ const findPipelineLabel = () => wrapper.findByTestId('downstream-pipeline-label');
+ const findPipelineLink = () => wrapper.findByTestId('pipelineLink');
+ const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline');
+
+ const createWrapper = ({ propsData, downstreamRetryAction = false }) => {
+ const mockApollo = createMockApollo();
+
+ wrapper = extendedWrapper(
+ mount(LinkedPipelineComponent, {
+ propsData,
+ provide: {
+ glFeatures: {
+ downstreamRetryAction,
+ },
+ },
+ apolloProvider: mockApollo,
+ }),
+ );
};
afterEach(() => {
@@ -59,7 +77,7 @@ describe('Linked pipeline', () => {
};
beforeEach(() => {
- createWrapper(props);
+ createWrapper({ propsData: props });
});
it('should render the project name', () => {
@@ -84,18 +102,13 @@ describe('Linked pipeline', () => {
expect(wrapper.text()).toContain(`#${props.pipeline.id}`);
});
- it('should correctly compute the tooltip text', () => {
- expect(wrapper.vm.tooltipText).toContain(mockPipeline.project.name);
- expect(wrapper.vm.tooltipText).toContain(mockPipeline.status.label);
- expect(wrapper.vm.tooltipText).toContain(mockPipeline.sourceJob.name);
- expect(wrapper.vm.tooltipText).toContain(mockPipeline.id);
- });
+ it('adds the card tooltip text to the DOM', () => {
+ expect(findCardTooltip().exists()).toBe(true);
- it('should render the tooltip text as the title attribute', () => {
- const titleAttr = findLinkedPipeline().attributes('title');
-
- expect(titleAttr).toContain(mockPipeline.project.name);
- expect(titleAttr).toContain(mockPipeline.status.label);
+ expect(findCardTooltip().text()).toContain(mockPipeline.project.name);
+ expect(findCardTooltip().text()).toContain(mockPipeline.status.label);
+ expect(findCardTooltip().text()).toContain(mockPipeline.sourceJob.name);
+ expect(findCardTooltip().text()).toContain(mockPipeline.id);
});
it('should display multi-project label when pipeline project id is not the same as triggered pipeline project id', () => {
@@ -105,7 +118,7 @@ describe('Linked pipeline', () => {
describe('upstream pipelines', () => {
beforeEach(() => {
- createWrapper(upstreamProps);
+ createWrapper({ propsData: upstreamProps });
});
it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
@@ -123,45 +136,246 @@ describe('Linked pipeline', () => {
});
describe('downstream pipelines', () => {
- beforeEach(() => {
- createWrapper(downstreamProps);
- });
-
- it('parent/child label container should exist', () => {
- expect(findPipelineLabel().exists()).toBe(true);
- });
-
- it('should display child label when pipeline project id is the same as triggered pipeline project id', () => {
- expect(findPipelineLabel().exists()).toBe(true);
- });
-
- it('should have the name of the trigger job on the card when it is a child pipeline', () => {
- expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.sourceJob.name);
- });
-
- it('downstream pipeline should contain the correct link', () => {
- expect(findPipelineLink().attributes('href')).toBe(downstreamProps.pipeline.path);
+ describe('styling', () => {
+ beforeEach(() => {
+ createWrapper({ propsData: downstreamProps });
+ });
+
+ it('parent/child label container should exist', () => {
+ expect(findPipelineLabel().exists()).toBe(true);
+ });
+
+ it('should display child label when pipeline project id is the same as triggered pipeline project id', () => {
+ expect(findPipelineLabel().exists()).toBe(true);
+ });
+
+ it('should have the name of the trigger job on the card when it is a child pipeline', () => {
+ expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.sourceJob.name);
+ });
+
+ it('downstream pipeline should contain the correct link', () => {
+ expect(findPipelineLink().attributes('href')).toBe(downstreamProps.pipeline.path);
+ });
+
+ it('applies the flex-row css class to the card', () => {
+ expect(findLinkedPipeline().classes()).toContain('gl-flex-direction-row');
+ expect(findLinkedPipeline().classes()).not.toContain('gl-flex-direction-row-reverse');
+ });
});
- it('applies the flex-row css class to the card', () => {
- expect(findLinkedPipeline().classes()).toContain('gl-flex-direction-row');
- expect(findLinkedPipeline().classes()).not.toContain('gl-flex-direction-row-reverse');
+ describe('action button', () => {
+ describe('with the `downstream_retry_action` flag on', () => {
+ describe('with permissions', () => {
+ describe('on an upstream', () => {
+ describe('when retryable', () => {
+ beforeEach(() => {
+ const retryablePipeline = {
+ ...upstreamProps,
+ pipeline: { ...mockPipeline, retryable: true },
+ };
+
+ createWrapper({ propsData: retryablePipeline, downstreamRetryAction: true });
+ });
+
+ it('does not show the retry or cancel button', () => {
+ expect(findCancelButton().exists()).toBe(false);
+ expect(findRetryButton().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('on a downstream', () => {
+ describe('when retryable', () => {
+ beforeEach(() => {
+ const retryablePipeline = {
+ ...downstreamProps,
+ pipeline: { ...mockPipeline, retryable: true },
+ };
+
+ createWrapper({ propsData: retryablePipeline, downstreamRetryAction: true });
+ });
+
+ it('shows only the retry button', () => {
+ expect(findCancelButton().exists()).toBe(false);
+ expect(findRetryButton().exists()).toBe(true);
+ });
+
+ it('hides the card tooltip when the action button tooltip is hovered', async () => {
+ expect(findCardTooltip().exists()).toBe(true);
+
+ await findRetryButton().trigger('mouseover');
+
+ expect(findCardTooltip().exists()).toBe(false);
+ });
+
+ describe('and the retry button is clicked', () => {
+ describe('on success', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
+ jest.spyOn(wrapper.vm, '$emit');
+ await findRetryButton().trigger('click');
+ });
+
+ it('calls the retry mutation ', () => {
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: RetryPipelineMutation,
+ variables: {
+ id: convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, mockPipeline.id),
+ },
+ });
+ });
+
+ it('emits the refreshPipelineGraph event', () => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph');
+ });
+ });
+
+ describe('on failure', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] });
+ jest.spyOn(wrapper.vm, '$emit');
+ await findRetryButton().trigger('click');
+ });
+
+ it('emits an error event', () => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', {
+ type: ACTION_FAILURE,
+ });
+ });
+ });
+ });
+ });
+
+ describe('when cancelable', () => {
+ beforeEach(() => {
+ const cancelablePipeline = {
+ ...downstreamProps,
+ pipeline: { ...mockPipeline, cancelable: true },
+ };
+
+ createWrapper({ propsData: cancelablePipeline, downstreamRetryAction: true });
+ });
+
+ it('shows only the cancel button ', () => {
+ expect(findCancelButton().exists()).toBe(true);
+ expect(findRetryButton().exists()).toBe(false);
+ });
+
+ it('hides the card tooltip when the action button tooltip is hovered', async () => {
+ expect(findCardTooltip().exists()).toBe(true);
+
+ await findCancelButton().trigger('mouseover');
+
+ expect(findCardTooltip().exists()).toBe(false);
+ });
+
+ describe('and the cancel button is clicked', () => {
+ describe('on success', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
+ jest.spyOn(wrapper.vm, '$emit');
+ await findCancelButton().trigger('click');
+ });
+
+ it('calls the cancel mutation', () => {
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: CancelPipelineMutation,
+ variables: {
+ id: convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, mockPipeline.id),
+ },
+ });
+ });
+ it('emits the refreshPipelineGraph event', () => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph');
+ });
+ });
+ describe('on failure', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] });
+ jest.spyOn(wrapper.vm, '$emit');
+ await findCancelButton().trigger('click');
+ });
+ it('emits an error event', () => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', {
+ type: ACTION_FAILURE,
+ });
+ });
+ });
+ });
+ });
+
+ describe('when both cancellable and retryable', () => {
+ beforeEach(() => {
+ const pipelineWithTwoActions = {
+ ...downstreamProps,
+ pipeline: { ...mockPipeline, cancelable: true, retryable: true },
+ };
+
+ createWrapper({ propsData: pipelineWithTwoActions, downstreamRetryAction: true });
+ });
+
+ it('only shows the cancel button', () => {
+ expect(findRetryButton().exists()).toBe(false);
+ expect(findCancelButton().exists()).toBe(true);
+ });
+ });
+ });
+ });
+
+ describe('without permissions', () => {
+ beforeEach(() => {
+ const pipelineWithTwoActions = {
+ ...downstreamProps,
+ pipeline: {
+ ...mockPipeline,
+ cancelable: true,
+ retryable: true,
+ userPermissions: { updatePipeline: false },
+ },
+ };
+
+ createWrapper({ propsData: pipelineWithTwoActions });
+ });
+
+ it('does not show any action button', () => {
+ expect(findRetryButton().exists()).toBe(false);
+ expect(findCancelButton().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('with the `downstream_retry_action` flag off', () => {
+ beforeEach(() => {
+ const pipelineWithTwoActions = {
+ ...downstreamProps,
+ pipeline: { ...mockPipeline, cancelable: true, retryable: true },
+ };
+
+ createWrapper({ propsData: pipelineWithTwoActions });
+ });
+ it('does not show any action button', () => {
+ expect(findRetryButton().exists()).toBe(false);
+ expect(findCancelButton().exists()).toBe(false);
+ });
+ });
});
});
describe('expand button', () => {
it.each`
- pipelineType | anglePosition | borderClass | expanded
- ${downstreamProps} | ${'angle-right'} | ${'gl-border-l-1!'} | ${false}
- ${downstreamProps} | ${'angle-left'} | ${'gl-border-l-1!'} | ${true}
- ${upstreamProps} | ${'angle-left'} | ${'gl-border-r-1!'} | ${false}
- ${upstreamProps} | ${'angle-right'} | ${'gl-border-r-1!'} | ${true}
+ pipelineType | anglePosition | buttonBorderClasses | expanded
+ ${downstreamProps} | ${'angle-right'} | ${'gl-border-l-0!'} | ${false}
+ ${downstreamProps} | ${'angle-left'} | ${'gl-border-l-0!'} | ${true}
+ ${upstreamProps} | ${'angle-left'} | ${'gl-border-r-0!'} | ${false}
+ ${upstreamProps} | ${'angle-right'} | ${'gl-border-r-0!'} | ${true}
`(
- '$pipelineType.columnTitle pipeline button icon should be $anglePosition with $borderClass if expanded state is $expanded',
- ({ pipelineType, anglePosition, borderClass, expanded }) => {
- createWrapper({ ...pipelineType, expanded });
+ '$pipelineType.columnTitle pipeline button icon should be $anglePosition with $buttonBorderClasses if expanded state is $expanded',
+ ({ pipelineType, anglePosition, buttonBorderClasses, expanded }) => {
+ createWrapper({ propsData: { ...pipelineType, expanded } });
expect(findExpandButton().props('icon')).toBe(anglePosition);
- expect(findExpandButton().classes()).toContain(borderClass);
+ expect(findExpandButton().classes()).toContain(buttonBorderClasses);
},
);
});
@@ -176,7 +390,7 @@ describe('Linked pipeline', () => {
};
beforeEach(() => {
- createWrapper(props);
+ createWrapper({ propsData: props });
});
it('loading icon is visible', () => {
@@ -194,7 +408,7 @@ describe('Linked pipeline', () => {
};
beforeEach(() => {
- createWrapper(props);
+ createWrapper({ propsData: props });
});
it('emits `pipelineClicked` event', () => {
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index 1673065e09c..46000711110 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -67,7 +67,6 @@ describe('Linked Pipelines Column', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('it renders correctly', () => {
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js b/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js
index 955b70cbd3b..f7f5738e46d 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js
@@ -2,6 +2,11 @@ export default {
__typename: 'Pipeline',
id: 195,
iid: '5',
+ retryable: false,
+ cancelable: false,
+ userPermissions: {
+ updatePipeline: true,
+ },
path: '/root/elemenohpee/-/pipelines/195',
status: {
__typename: 'DetailedStatus',
diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js
index 0cf7dc507f4..6124d67af09 100644
--- a/spec/frontend/pipelines/graph/mock_data.js
+++ b/spec/frontend/pipelines/graph/mock_data.js
@@ -1,4 +1,5 @@
import { unwrapPipelineData } from '~/pipelines/components/graph/utils';
+import { BUILD_KIND, BRIDGE_KIND } from '~/pipelines/components/graph/constants';
export const mockPipelineResponse = {
data: {
@@ -50,6 +51,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '6',
+ kind: BUILD_KIND,
name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl',
scheduledAt: null,
status: {
@@ -101,6 +103,7 @@ export const mockPipelineResponse = {
__typename: 'CiJob',
id: '11',
name: 'build_b',
+ kind: BUILD_KIND,
scheduledAt: null,
status: {
__typename: 'DetailedStatus',
@@ -151,6 +154,7 @@ export const mockPipelineResponse = {
__typename: 'CiJob',
id: '16',
name: 'build_c',
+ kind: BUILD_KIND,
scheduledAt: null,
status: {
__typename: 'DetailedStatus',
@@ -200,6 +204,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '21',
+ kind: BUILD_KIND,
name: 'build_d 1/3',
scheduledAt: null,
status: {
@@ -232,6 +237,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '24',
+ kind: BUILD_KIND,
name: 'build_d 2/3',
scheduledAt: null,
status: {
@@ -264,6 +270,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '27',
+ kind: BUILD_KIND,
name: 'build_d 3/3',
scheduledAt: null,
status: {
@@ -329,6 +336,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '34',
+ kind: BUILD_KIND,
name: 'test_a',
scheduledAt: null,
status: {
@@ -413,6 +421,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '42',
+ kind: BUILD_KIND,
name: 'test_b 1/2',
scheduledAt: null,
status: {
@@ -499,6 +508,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '67',
+ kind: BUILD_KIND,
name: 'test_b 2/2',
scheduledAt: null,
status: {
@@ -603,6 +613,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '59',
+ kind: BUILD_KIND,
name: 'test_c',
scheduledAt: null,
status: {
@@ -646,6 +657,7 @@ export const mockPipelineResponse = {
{
__typename: 'CiJob',
id: '53',
+ kind: BUILD_KIND,
name: 'test_d',
scheduledAt: null,
status: {
@@ -699,6 +711,11 @@ export const downstream = {
id: 175,
iid: '31',
path: '/root/elemenohpee/-/pipelines/175',
+ retryable: true,
+ cancelable: false,
+ userPermissions: {
+ updatePipeline: true,
+ },
status: {
id: '70',
group: 'success',
@@ -724,6 +741,11 @@ export const downstream = {
id: 181,
iid: '27',
path: '/root/abcd-dag/-/pipelines/181',
+ retryable: true,
+ cancelable: false,
+ userPermissions: {
+ updatePipeline: true,
+ },
status: {
id: '72',
group: 'success',
@@ -752,6 +774,11 @@ export const upstream = {
id: 161,
iid: '24',
path: '/root/abcd-dag/-/pipelines/161',
+ retryable: true,
+ cancelable: false,
+ userPermissions: {
+ updatePipeline: true,
+ },
status: {
id: '74',
group: 'success',
@@ -786,6 +813,11 @@ export const wrappedPipelineReturn = {
updatePipeline: true,
},
downstream: {
+ retryable: true,
+ cancelable: false,
+ userPermissions: {
+ updatePipeline: true,
+ },
__typename: 'PipelineConnection',
nodes: [],
},
@@ -793,6 +825,11 @@ export const wrappedPipelineReturn = {
id: 'gid://gitlab/Ci::Pipeline/174',
iid: '37',
path: '/root/elemenohpee/-/pipelines/174',
+ retryable: true,
+ cancelable: false,
+ userPermissions: {
+ updatePipeline: true,
+ },
__typename: 'Pipeline',
status: {
__typename: 'DetailedStatus',
@@ -846,6 +883,7 @@ export const wrappedPipelineReturn = {
{
__typename: 'CiJob',
id: '83',
+ kind: BUILD_KIND,
name: 'build_n',
scheduledAt: null,
needs: {
@@ -916,3 +954,87 @@ export const mockCalloutsResponse = (mappedCallouts) => ({
},
},
});
+
+export const delayedJob = {
+ __typename: 'CiJob',
+ kind: BUILD_KIND,
+ name: 'delayed job',
+ scheduledAt: '2015-07-03T10:01:00.000Z',
+ needs: [],
+ status: {
+ __typename: 'DetailedStatus',
+ icon: 'status_scheduled',
+ tooltip: 'delayed manual action (%{remainingTime})',
+ hasDetails: true,
+ detailsPath: '/root/kinder-pipe/-/jobs/5339',
+ group: 'scheduled',
+ action: {
+ __typename: 'StatusAction',
+ icon: 'time-out',
+ title: 'Unschedule',
+ path: '/frontend-fixtures/builds-project/-/jobs/142/unschedule',
+ buttonTitle: 'Unschedule job',
+ },
+ },
+};
+
+export const mockJob = {
+ id: 4256,
+ name: 'test',
+ kind: BUILD_KIND,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ tooltip: 'passed',
+ group: 'success',
+ detailsPath: '/root/ci-mock/builds/4256',
+ hasDetails: true,
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/ci-mock/builds/4256/retry',
+ method: 'post',
+ },
+ },
+};
+
+export const mockJobWithoutDetails = {
+ id: 4257,
+ name: 'job_without_details',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ detailsPath: '/root/ci-mock/builds/4257',
+ hasDetails: false,
+ },
+};
+
+export const mockJobWithUnauthorizedAction = {
+ id: 4258,
+ name: 'stop-environment',
+ status: {
+ icon: 'status_manual',
+ label: 'manual stop action (not allowed)',
+ tooltip: 'manual action',
+ group: 'manual',
+ detailsPath: '/root/ci-mock/builds/4258',
+ hasDetails: true,
+ action: null,
+ },
+};
+
+export const triggerJob = {
+ id: 4259,
+ name: 'trigger',
+ kind: BRIDGE_KIND,
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ action: null,
+ },
+};