Commit fa8589c4 authored by Scott Hampton's avatar Scott Hampton

Merge branch '247555-auto-fix-mrs-link' into 'master'

Link to auto-fix MRs in PSD widget

See merge request gitlab-org/gitlab!45927
parents d6ba2a8b fb460967
...@@ -34,11 +34,6 @@ export default { ...@@ -34,11 +34,6 @@ export default {
required: false, required: false,
default: () => ({}), default: () => ({}),
}, },
projectFullPath: {
type: String,
required: false,
default: '',
},
hasVulnerabilities: { hasVulnerabilities: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -86,14 +81,13 @@ export default { ...@@ -86,14 +81,13 @@ export default {
<csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" /> <csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" />
</div> </div>
<project-pipeline-status :pipeline="pipeline" /> <project-pipeline-status :pipeline="pipeline" />
<vulnerabilities-count-list :project-full-path="projectFullPath" :filters="filters" /> <vulnerabilities-count-list :filters="filters" />
</template> </template>
<template #sticky> <template #sticky>
<filters @filterChange="handleFilterChange" /> <filters @filterChange="handleFilterChange" />
</template> </template>
<project-vulnerabilities-app <project-vulnerabilities-app
:dashboard-documentation="dashboardDocumentation" :dashboard-documentation="dashboardDocumentation"
:project-full-path="projectFullPath"
:filters="filters" :filters="filters"
/> />
</security-dashboard-layout> </security-dashboard-layout>
......
<script> <script>
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { __ } from '~/locale'; import { __, s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import PipelineStatusBadge from './pipeline_status_badge.vue'; import PipelineStatusBadge from './pipeline_status_badge.vue';
import projectAutoFixMrsCountQuery from '../graphql/project_auto_fix_mrs_count.query.graphql';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default { export default {
components: { components: {
...@@ -10,6 +12,24 @@ export default { ...@@ -10,6 +12,24 @@ export default {
TimeAgoTooltip, TimeAgoTooltip,
PipelineStatusBadge, PipelineStatusBadge,
}, },
mixins: [glFeatureFlagsMixin()],
inject: ['projectFullPath', 'autoFixMrsPath'],
apollo: {
autoFixMrsCount: {
query: projectAutoFixMrsCountQuery,
variables() {
return {
fullPath: this.projectFullPath,
};
},
update(data) {
return data?.project?.mergeRequests?.count || 0;
},
skip() {
return !this.glFeatures.securityAutoFix;
},
},
},
props: { props: {
pipeline: { type: Object, required: true }, pipeline: { type: Object, required: true },
}, },
...@@ -22,7 +42,9 @@ export default { ...@@ -22,7 +42,9 @@ export default {
title: __( title: __(
'The Security Dashboard shows the results of the last successful pipeline run on the default branch.', 'The Security Dashboard shows the results of the last successful pipeline run on the default branch.',
), ),
label: __('Last updated'), lastUpdated: __('Last updated'),
autoFixSolutions: s__('AutoRemediation|Auto-fix solutions'),
autoFixMrsLink: s__('AutoRemediation|%{mrsCount} ready for review'),
}, },
}; };
</script> </script>
...@@ -33,10 +55,20 @@ export default { ...@@ -33,10 +55,20 @@ export default {
<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"
> >
<span class="gl-font-weight-bold">{{ $options.i18n.label }}</span> <div class="gl-mr-6">
<time-ago-tooltip class="gl-px-3" :time="pipeline.createdAt" /> <span class="gl-font-weight-bold gl-mr-3">{{ $options.i18n.lastUpdated }}</span>
<gl-link :href="pipeline.path" target="_blank">#{{ pipeline.id }}</gl-link> <span class="gl-white-space-nowrap">
<pipeline-status-badge :pipeline="pipeline" class="gl-ml-3" /> <time-ago-tooltip class="gl-pr-3" :time="pipeline.createdAt" />
<gl-link :href="pipeline.path" target="_blank">#{{ pipeline.id }}</gl-link>
<pipeline-status-badge :pipeline="pipeline" class="gl-ml-3" />
</span>
</div>
<div v-if="autoFixMrsCount" data-testid="auto-fix-mrs-link">
<span class="gl-font-weight-bold gl-mr-3">{{ $options.i18n.autoFixSolutions }}</span>
<gl-link :href="autoFixMrsPath" target="_blank" class="gl-white-space-nowrap">{{
sprintf($options.i18n.autoFixMrsLink, { mrsCount: autoFixMrsCount })
}}</gl-link>
</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -16,11 +16,8 @@ export default { ...@@ -16,11 +16,8 @@ export default {
GlIntersectionObserver, GlIntersectionObserver,
VulnerabilityList, VulnerabilityList,
}, },
inject: ['projectFullPath'],
props: { props: {
projectFullPath: {
type: String,
required: true,
},
filters: { filters: {
type: Object, type: Object,
required: false, required: false,
......
...@@ -6,11 +6,8 @@ export default { ...@@ -6,11 +6,8 @@ export default {
components: { components: {
VulnerabilityCountListLayout, VulnerabilityCountListLayout,
}, },
inject: ['projectFullPath'],
props: { props: {
projectFullPath: {
type: String,
required: true,
},
filters: { filters: {
type: Object, type: Object,
required: false, required: false,
......
...@@ -55,8 +55,9 @@ export default (el, dashboardType) => { ...@@ -55,8 +55,9 @@ export default (el, dashboardType) => {
securityBuildsFailedCount: Number(securityBuildsFailedCount), securityBuildsFailedCount: Number(securityBuildsFailedCount),
securityBuildsFailedPath, securityBuildsFailedPath,
}; };
props.projectFullPath = el.dataset.projectFullPath; provide.projectFullPath = el.dataset.projectFullPath;
provide.autoFixDocumentation = el.dataset.autoFixDocumentation; provide.autoFixDocumentation = el.dataset.autoFixDocumentation;
provide.autoFixMrsPath = el.dataset.autoFixMrsPath;
} 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;
......
query autoFixMrsCount($fullPath: ID!) {
project(fullPath: $fullPath) {
mergeRequests(labels: "GitLab-auto-fix", state: opened) {
count
}
}
}
...@@ -208,7 +208,8 @@ module EE ...@@ -208,7 +208,8 @@ module EE
not_enabled_scanners_help_path: help_page_path('user/application_security/index', anchor: 'quick-start'), not_enabled_scanners_help_path: help_page_path('user/application_security/index', anchor: 'quick-start'),
no_pipeline_run_scanners_help_path: new_project_pipeline_path(project), no_pipeline_run_scanners_help_path: new_project_pipeline_path(project),
security_dashboard_help_path: help_page_path('user/application_security/security_dashboard/index'), security_dashboard_help_path: help_page_path('user/application_security/security_dashboard/index'),
auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests') auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests'),
auto_fix_mrs_path: project_merge_requests_path(@project, label_name: 'GitLab-auto-fix')
}.merge!(security_dashboard_pipeline_data(project)) }.merge!(security_dashboard_pipeline_data(project))
end end
end end
......
...@@ -19,12 +19,12 @@ const props = { ...@@ -19,12 +19,12 @@ const props = {
id: '214', id: '214',
path: '/mixed-vulnerabilities/dependency-list-test-01/-/pipelines/214', path: '/mixed-vulnerabilities/dependency-list-test-01/-/pipelines/214',
}, },
projectFullPath: '/group/project',
securityDashboardHelpPath: '/security/dashboard/help-path', securityDashboardHelpPath: '/security/dashboard/help-path',
vulnerabilitiesExportEndpoint: '/vulnerabilities/exports', vulnerabilitiesExportEndpoint: '/vulnerabilities/exports',
}; };
const provide = { const provide = {
projectFullPath: '/group/project',
dashboardDocumentation: '/help/docs', dashboardDocumentation: '/help/docs',
autoFixDocumentation: '/auto/fix/documentation', autoFixDocumentation: '/auto/fix/documentation',
emptyStateSvgPath: '/svgs/empty/svg', emptyStateSvgPath: '/svgs/empty/svg',
......
import { merge } from 'lodash';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import ProjectPipelineStatus from 'ee/security_dashboard/components/project_pipeline_status.vue'; import ProjectPipelineStatus from 'ee/security_dashboard/components/project_pipeline_status.vue';
...@@ -18,12 +20,29 @@ describe('Project Pipeline Status Component', () => { ...@@ -18,12 +20,29 @@ describe('Project Pipeline Status Component', () => {
const findPipelineStatusBadge = () => wrapper.find(PipelineStatusBadge); const findPipelineStatusBadge = () => wrapper.find(PipelineStatusBadge);
const findTimeAgoTooltip = () => wrapper.find(TimeAgoTooltip); const findTimeAgoTooltip = () => wrapper.find(TimeAgoTooltip);
const findLink = () => wrapper.find(GlLink); const findLink = () => wrapper.find(GlLink);
const findAutoFixMrsLink = () => wrapper.findByTestId('auto-fix-mrs-link');
const createWrapper = ({ props = {}, options = {} } = {}) => { const createWrapper = (options = {}) => {
return shallowMount(ProjectPipelineStatus, { return extendedWrapper(
propsData: { ...DEFAULT_PROPS, ...props }, shallowMount(
...options, ProjectPipelineStatus,
}); merge(
{},
{
propsData: DEFAULT_PROPS,
provide: {
projectFullPath: '/group/project',
glFeatures: { securityAutoFix: true },
autoFixMrsPath: '/merge_requests?label_name=GitLab-auto-fix',
},
data() {
return { autoFixMrsCount: 0 };
},
},
options,
),
),
);
}; };
afterEach(() => { afterEach(() => {
...@@ -56,7 +75,7 @@ describe('Project Pipeline Status Component', () => { ...@@ -56,7 +75,7 @@ describe('Project Pipeline Status Component', () => {
describe('when no pipeline has run', () => { describe('when no pipeline has run', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper({ props: { pipeline: { path: '' } } }); wrapper = createWrapper({ propsData: { pipeline: { path: '' } } });
}); });
it('should not show the project_pipeline_status component', () => { it('should not show the project_pipeline_status component', () => {
...@@ -65,4 +84,32 @@ describe('Project Pipeline Status Component', () => { ...@@ -65,4 +84,32 @@ describe('Project Pipeline Status Component', () => {
expect(findPipelineStatusBadge().exists()).toBe(false); expect(findPipelineStatusBadge().exists()).toBe(false);
}); });
}); });
describe('auto-fix MRs', () => {
describe('when there are auto-fix MRs', () => {
beforeEach(() => {
wrapper = createWrapper({
data() {
return { autoFixMrsCount: 12 };
},
});
});
it('renders the auto-fix container', () => {
expect(findAutoFixMrsLink().exists()).toBe(true);
});
it('renders a link to open auto-fix MRs if any', () => {
const link = findAutoFixMrsLink().find(GlLink);
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe('/merge_requests?label_name=GitLab-auto-fix');
});
});
it('does not render the link if there are no open auto-fix MRs', () => {
wrapper = createWrapper();
expect(findAutoFixMrsLink().exists()).toBe(false);
});
});
}); });
...@@ -12,10 +12,12 @@ describe('Vulnerabilities app component', () => { ...@@ -12,10 +12,12 @@ describe('Vulnerabilities app component', () => {
const createWrapper = ({ props = {}, $apollo = apolloMock } = {}, options = {}) => { const createWrapper = ({ props = {}, $apollo = apolloMock } = {}, options = {}) => {
wrapper = shallowMount(ProjectVulnerabilitiesApp, { wrapper = shallowMount(ProjectVulnerabilitiesApp, {
provide: {
projectFullPath: '#',
},
propsData: { propsData: {
dashboardDocumentation: '#', dashboardDocumentation: '#',
emptyStateSvgPath: '#', emptyStateSvgPath: '#',
projectFullPath: '#',
...props, ...props,
}, },
mocks: { mocks: {
......
...@@ -9,7 +9,7 @@ describe('Vulnerabilities count list component', () => { ...@@ -9,7 +9,7 @@ describe('Vulnerabilities count list component', () => {
const createWrapper = ({ query } = {}) => { const createWrapper = ({ query } = {}) => {
return shallowMount(VulnerabilityCountList, { return shallowMount(VulnerabilityCountList, {
propsData: { provide: {
projectFullPath: '/root/security-project', projectFullPath: '/root/security-project',
}, },
mocks: { mocks: {
......
...@@ -155,7 +155,8 @@ RSpec.describe ProjectsHelper do ...@@ -155,7 +155,8 @@ RSpec.describe ProjectsHelper do
security_dashboard_help_path: '/help/user/application_security/security_dashboard/index', security_dashboard_help_path: '/help/user/application_security/security_dashboard/index',
not_enabled_scanners_help_path: help_page_path('user/application_security/index', anchor: 'quick-start'), not_enabled_scanners_help_path: help_page_path('user/application_security/index', anchor: 'quick-start'),
no_pipeline_run_scanners_help_path: "/#{project.full_path}/-/pipelines/new", no_pipeline_run_scanners_help_path: "/#{project.full_path}/-/pipelines/new",
auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests') auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests'),
auto_fix_mrs_path: end_with('/merge_requests?label_name=GitLab-auto-fix')
} }
end end
......
...@@ -3952,6 +3952,12 @@ msgstr "" ...@@ -3952,6 +3952,12 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found." msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found."
msgstr "" msgstr ""
msgid "AutoRemediation|%{mrsCount} ready for review"
msgstr ""
msgid "AutoRemediation|Auto-fix solutions"
msgstr ""
msgid "AutoRemediation|If you're using dependency and/or container scanning, and auto-fix is enabled, auto-fix automatically creates merge requests with fixes to vulnerabilities." msgid "AutoRemediation|If you're using dependency and/or container scanning, and auto-fix is enabled, auto-fix automatically creates merge requests with fixes to vulnerabilities."
msgstr "" msgstr ""
......
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