Commit 8ded239f authored by Alexander Turinske's avatar Alexander Turinske

Simplify pipeline failed jobs widget via props

- remove provide/inject in lieu of props
- update tests
- update changelog
parent fb41b14e
...@@ -59,11 +59,6 @@ export default { ...@@ -59,11 +59,6 @@ export default {
}; };
}, },
inject: ['dashboardDocumentation', 'autoFixDocumentation'], inject: ['dashboardDocumentation', 'autoFixDocumentation'],
computed: {
shouldShowPipelineStatus() {
return Object.values(this.pipeline).every(Boolean);
},
},
methods: { methods: {
handleFilterChange(filters) { handleFilterChange(filters) {
this.filters = filters; this.filters = filters;
...@@ -90,7 +85,7 @@ export default { ...@@ -90,7 +85,7 @@ export default {
<h4 class="flex-grow mt-0 mb-0">{{ __('Vulnerabilities') }}</h4> <h4 class="flex-grow mt-0 mb-0">{{ __('Vulnerabilities') }}</h4>
<csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" /> <csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" />
</div> </div>
<project-pipeline-status v-if="shouldShowPipelineStatus" :pipeline="pipeline" /> <project-pipeline-status :pipeline="pipeline" />
<vulnerabilities-count-list :project-full-path="projectFullPath" :filters="filters" /> <vulnerabilities-count-list :project-full-path="projectFullPath" :filters="filters" />
</template> </template>
<template #sticky> <template #sticky>
......
...@@ -3,18 +3,29 @@ import { GlBadge, GlIcon } from '@gitlab/ui'; ...@@ -3,18 +3,29 @@ import { GlBadge, GlIcon } from '@gitlab/ui';
export default { export default {
components: { GlBadge, GlIcon }, components: { GlBadge, GlIcon },
inject: { props: {
pipelineSecurityBuildsFailedCount: { default: 0 }, pipeline: {
pipelineSecurityBuildsFailedPath: { default: '' }, type: Object,
required: true,
},
},
computed: {
failedCount() {
return this.pipeline.securityBuildsFailedCount || 0;
},
failedPath() {
return this.pipeline.securityBuildsFailedPath || '';
},
shouldShow() {
return this.failedCount > 0;
},
}, },
}; };
</script> </script>
<template> <template>
<gl-badge variant="danger" :href="pipelineSecurityBuildsFailedPath"> <gl-badge v-if="shouldShow" variant="danger" :href="failedPath">
<gl-icon name="status_failed" class="gl-mr-2" /> <gl-icon name="status_failed" class="gl-mr-2" />
{{ {{ n__('%d failed security job', '%d failed security jobs', failedCount) }}
n__('%d failed security job', '%d failed security jobs', pipelineSecurityBuildsFailedCount)
}}
</gl-badge> </gl-badge>
</template> </template>
...@@ -13,12 +13,9 @@ export default { ...@@ -13,12 +13,9 @@ export default {
props: { props: {
pipeline: { type: Object, required: true }, pipeline: { type: Object, required: true },
}, },
inject: {
pipelineSecurityBuildsFailedCount: { type: Number, default: 0 },
},
computed: { computed: {
shouldShowPipelineStatusBadge() { shouldShowPipelineStatus() {
return this.pipelineSecurityBuildsFailedCount > 0; return this.pipeline.createdAt && this.pipeline.id && this.pipeline.path;
}, },
}, },
i18n: { i18n: {
...@@ -31,7 +28,7 @@ export default { ...@@ -31,7 +28,7 @@ export default {
</script> </script>
<template> <template>
<div> <div v-if="shouldShowPipelineStatus">
<h6 class="gl-font-weight-normal">{{ $options.i18n.title }}</h6> <h6 class="gl-font-weight-normal">{{ $options.i18n.title }}</h6>
<div <div
class="gl-display-flex gl-align-items-center gl-border-solid gl-border-1 gl-border-gray-100 gl-p-6" class="gl-display-flex gl-align-items-center gl-border-solid gl-border-1 gl-border-gray-100 gl-p-6"
...@@ -39,7 +36,7 @@ export default { ...@@ -39,7 +36,7 @@ export default {
<span class="gl-font-weight-bold">{{ $options.i18n.label }}</span> <span class="gl-font-weight-bold">{{ $options.i18n.label }}</span>
<time-ago-tooltip class="gl-px-3" :time="pipeline.createdAt" /> <time-ago-tooltip class="gl-px-3" :time="pipeline.createdAt" />
<gl-link :href="pipeline.path" target="_blank">#{{ pipeline.id }}</gl-link> <gl-link :href="pipeline.path" target="_blank">#{{ pipeline.id }}</gl-link>
<pipeline-status-badge v-if="shouldShowPipelineStatusBadge" class="gl-ml-3" /> <pipeline-status-badge :pipeline="pipeline" class="gl-ml-3" />
</div> </div>
</div> </div>
</template> </template>
...@@ -41,14 +41,22 @@ export default (el, dashboardType) => { ...@@ -41,14 +41,22 @@ export default (el, dashboardType) => {
if (dashboardType === DASHBOARD_TYPES.PROJECT) { if (dashboardType === DASHBOARD_TYPES.PROJECT) {
component = FirstClassProjectSecurityDashboard; component = FirstClassProjectSecurityDashboard;
const { pipelineCreatedAt: createdAt, pipelineId: id, pipelinePath: path } = el.dataset; const {
props.pipeline = { createdAt, id, path }; pipelineCreatedAt: createdAt,
pipelineId: id,
pipelinePath: path,
pipelineSecurityBuildsFailedCount: securityBuildsFailedCount,
pipelineSecurityBuildsFailedPath: securityBuildsFailedPath,
} = el.dataset;
props.pipeline = {
createdAt,
id,
path,
securityBuildsFailedCount: Number(securityBuildsFailedCount),
securityBuildsFailedPath,
};
props.projectFullPath = el.dataset.projectFullPath; props.projectFullPath = el.dataset.projectFullPath;
provide.autoFixDocumentation = el.dataset.autoFixDocumentation; provide.autoFixDocumentation = el.dataset.autoFixDocumentation;
provide.pipelineSecurityBuildsFailedPath = el.dataset.pipelineSecurityBuildsFailedPath;
provide.pipelineSecurityBuildsFailedCount = Number(
el.dataset.pipelineSecurityBuildsFailedCount,
);
} else if (dashboardType === DASHBOARD_TYPES.GROUP) { } else if (dashboardType === DASHBOARD_TYPES.GROUP) {
component = FirstClassGroupSecurityDashboard; component = FirstClassGroupSecurityDashboard;
props.groupFullPath = el.dataset.groupFullPath; props.groupFullPath = el.dataset.groupFullPath;
......
--- ---
title: Integrate the status badge in the pipeline widget title: Add security status badge to the project pipeline widget
merge_request: 45987 merge_request: 45987
author: author:
type: added type: added
...@@ -168,7 +168,6 @@ describe('First class Project Security Dashboard component', () => { ...@@ -168,7 +168,6 @@ describe('First class Project Security Dashboard component', () => {
createComponent({ createComponent({
props: { props: {
hasVulnerabilities: true, hasVulnerabilities: true,
pipeline: { id: '214' },
}, },
data() { data() {
return { filters }; return { filters };
...@@ -193,23 +192,5 @@ describe('First class Project Security Dashboard component', () => { ...@@ -193,23 +192,5 @@ describe('First class Project Security Dashboard component', () => {
it('displays the unconfigured state', () => { it('displays the unconfigured state', () => {
expect(findUnconfiguredState().exists()).toBe(true); expect(findUnconfiguredState().exists()).toBe(true);
}); });
it('does not display the project pipeline status', () => {
expect(findProjectPipelineStatus().exists()).toBe(false);
});
});
describe('when there is no pipeline data', () => {
beforeEach(() => {
createComponent({
props: {
pipeline: undefined,
},
});
});
it('does not display the project pipeline status', () => {
expect(findProjectPipelineStatus().exists()).toBe(false);
});
}); });
}); });
import { GlBadge } from '@gitlab/ui'; import { GlBadge, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import PipelineStatusBadge from 'ee/security_dashboard/components/pipeline_status_badge.vue'; import PipelineStatusBadge from 'ee/security_dashboard/components/pipeline_status_badge.vue';
describe('Pipeline status badge', () => { describe('Pipeline status badge', () => {
const pipelineSecurityBuildsFailedPath = '/some/path/to/failed/jobs';
let wrapper; let wrapper;
const createWrapper = ({ pipelineSecurityBuildsFailedCount }) => { const securityBuildsFailedPath = '/some/path/to/failed/jobs';
const DEFAULT_PROPS = {
pipeline: {
securityBuildsFailedCount: 5,
securityBuildsFailedPath,
},
};
const findGlBadge = () => wrapper.find(GlBadge);
const findGlIcon = () => wrapper.find(GlIcon);
const createProps = securityBuildsFailedCount => ({ pipeline: { securityBuildsFailedCount } });
const createWrapper = ({ props = {} } = {}) => {
wrapper = shallowMount(PipelineStatusBadge, { wrapper = shallowMount(PipelineStatusBadge, {
provide: { propsData: { ...DEFAULT_PROPS, ...props },
pipelineSecurityBuildsFailedCount,
pipelineSecurityBuildsFailedPath,
},
stubs: { GlBadge },
}); });
}; };
...@@ -22,18 +29,36 @@ describe('Pipeline status badge', () => { ...@@ -22,18 +29,36 @@ describe('Pipeline status badge', () => {
wrapper = null; wrapper = null;
}); });
it('displays correct message for 5 failed jobs', () => { describe('when there are more than 0 failed jobs', () => {
createWrapper({ pipelineSecurityBuildsFailedCount: 5 }); beforeEach(() => {
expect(wrapper.text()).toBe('5 failed security jobs'); createWrapper();
}); });
it('displays correct message for 1 failed job', () => { it('displays correct message for 5 failed jobs', () => {
createWrapper({ pipelineSecurityBuildsFailedCount: 1 }); expect(wrapper.text()).toBe('5 failed security jobs');
expect(wrapper.text()).toBe('1 failed security job'); });
it('links to the correct path', () => {
expect(findGlBadge().attributes('href')).toBe(securityBuildsFailedPath);
});
it('displays correct message for 1 failed job', () => {
createWrapper({ props: createProps(1) });
expect(wrapper.text()).toBe('1 failed security job');
});
}); });
it('links to the correct path', () => { describe('when there are not more than 0 failed jobs', () => {
createWrapper({ pipelineSecurityBuildsFailedCount: 5 }); it('does not display when there are 0 failed jobs', () => {
expect(wrapper.find(GlBadge).attributes('href')).toBe(pipelineSecurityBuildsFailedPath); createWrapper({ props: createProps(0) });
expect(findGlBadge().exists()).toBe(false);
expect(findGlIcon().exists()).toBe(false);
});
it('does not display when there is no failed jobs count', () => {
createWrapper({ props: createProps(undefined) });
expect(findGlBadge().exists()).toBe(false);
expect(findGlIcon().exists()).toBe(false);
});
}); });
}); });
...@@ -7,7 +7,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; ...@@ -7,7 +7,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
describe('Project Pipeline Status Component', () => { describe('Project Pipeline Status Component', () => {
let wrapper; let wrapper;
const propsData = { const DEFAULT_PROPS = {
pipeline: { pipeline: {
createdAt: '2020-10-06T20:08:07Z', createdAt: '2020-10-06T20:08:07Z',
id: '214', id: '214',
...@@ -19,9 +19,9 @@ describe('Project Pipeline Status Component', () => { ...@@ -19,9 +19,9 @@ describe('Project Pipeline Status Component', () => {
const findTimeAgoTooltip = () => wrapper.find(TimeAgoTooltip); const findTimeAgoTooltip = () => wrapper.find(TimeAgoTooltip);
const findLink = () => wrapper.find(GlLink); const findLink = () => wrapper.find(GlLink);
const createWrapper = options => { const createWrapper = ({ props = {}, options = {} } = {}) => {
return shallowMount(ProjectPipelineStatus, { return shallowMount(ProjectPipelineStatus, {
propsData, propsData: { ...DEFAULT_PROPS, ...props },
...options, ...options,
}); });
}; };
...@@ -40,7 +40,7 @@ describe('Project Pipeline Status Component', () => { ...@@ -40,7 +40,7 @@ describe('Project Pipeline Status Component', () => {
const TimeComponent = findTimeAgoTooltip(); const TimeComponent = findTimeAgoTooltip();
expect(TimeComponent.exists()).toBeTruthy(); expect(TimeComponent.exists()).toBeTruthy();
expect(TimeComponent.props()).toStrictEqual({ expect(TimeComponent.props()).toStrictEqual({
time: propsData.pipeline.createdAt, time: DEFAULT_PROPS.pipeline.createdAt,
cssClass: '', cssClass: '',
tooltipPlacement: 'top', tooltipPlacement: 'top',
}); });
...@@ -49,27 +49,19 @@ describe('Project Pipeline Status Component', () => { ...@@ -49,27 +49,19 @@ describe('Project Pipeline Status Component', () => {
it('should show the link component', () => { it('should show the link component', () => {
const GlLinkComponent = findLink(); const GlLinkComponent = findLink();
expect(GlLinkComponent.exists()).toBeTruthy(); expect(GlLinkComponent.exists()).toBeTruthy();
expect(GlLinkComponent.text()).toBe(`#${propsData.pipeline.id}`); expect(GlLinkComponent.text()).toBe(`#${DEFAULT_PROPS.pipeline.id}`);
expect(GlLinkComponent.attributes('href')).toBe(propsData.pipeline.path); expect(GlLinkComponent.attributes('href')).toBe(DEFAULT_PROPS.pipeline.path);
}); });
}); });
describe('when there are more than 0 failed jobs', () => { describe('when no pipeline has run', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper({ provide: { pipelineSecurityBuildsFailedCount: 5 } }); wrapper = createWrapper({ props: { pipeline: { path: '' } } });
}); });
it('should show the pipeline status badge', () => { it('should not show the project_pipeline_status component', () => {
expect(findPipelineStatusBadge().exists()).toBe(true); expect(findLink().exists()).toBe(false);
}); expect(findTimeAgoTooltip().exists()).toBe(false);
});
describe('when there are 0 failed jobs', () => {
beforeEach(() => {
wrapper = createWrapper({ provide: { pipelineSecurityBuildsFailedCount: 0 } });
});
it('should show the pipeline status badge', () => {
expect(findPipelineStatusBadge().exists()).toBe(false); expect(findPipelineStatusBadge().exists()).toBe(false);
}); });
}); });
......
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