diff options
Diffstat (limited to 'spec/frontend/pipelines/graph')
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, + }, +}; |