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 {
required: false,
default: () => ({}),
},
projectFullPath: {
type: String,
required: false,
default: '',
},
hasVulnerabilities: {
type: Boolean,
required: false,
......@@ -86,14 +81,13 @@ export default {
<csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" />
</div>
<project-pipeline-status :pipeline="pipeline" />
<vulnerabilities-count-list :project-full-path="projectFullPath" :filters="filters" />
<vulnerabilities-count-list :filters="filters" />
</template>
<template #sticky>
<filters @filterChange="handleFilterChange" />
</template>
<project-vulnerabilities-app
:dashboard-documentation="dashboardDocumentation"
:project-full-path="projectFullPath"
:filters="filters"
/>
</security-dashboard-layout>
......
<script>
import { GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
import { __, s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.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 {
components: {
......@@ -10,6 +12,24 @@ export default {
TimeAgoTooltip,
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: {
pipeline: { type: Object, required: true },
},
......@@ -22,7 +42,9 @@ export default {
title: __(
'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>
......@@ -33,10 +55,20 @@ export default {
<div
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>
<time-ago-tooltip class="gl-px-3" :time="pipeline.createdAt" />
<div class="gl-mr-6">
<span class="gl-font-weight-bold gl-mr-3">{{ $options.i18n.lastUpdated }}</span>
<span class="gl-white-space-nowrap">
<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>
</template>
......@@ -16,11 +16,8 @@ export default {
GlIntersectionObserver,
VulnerabilityList,
},
inject: ['projectFullPath'],
props: {
projectFullPath: {
type: String,
required: true,
},
filters: {
type: Object,
required: false,
......
......@@ -6,11 +6,8 @@ export default {
components: {
VulnerabilityCountListLayout,
},
inject: ['projectFullPath'],
props: {
projectFullPath: {
type: String,
required: true,
},
filters: {
type: Object,
required: false,
......
......@@ -55,8 +55,9 @@ export default (el, dashboardType) => {
securityBuildsFailedCount: Number(securityBuildsFailedCount),
securityBuildsFailedPath,
};
props.projectFullPath = el.dataset.projectFullPath;
provide.projectFullPath = el.dataset.projectFullPath;
provide.autoFixDocumentation = el.dataset.autoFixDocumentation;
provide.autoFixMrsPath = el.dataset.autoFixMrsPath;
} else if (dashboardType === DASHBOARD_TYPES.GROUP) {
component = FirstClassGroupSecurityDashboard;
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
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),
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))
end
end
......
......@@ -19,12 +19,12 @@ const props = {
id: '214',
path: '/mixed-vulnerabilities/dependency-list-test-01/-/pipelines/214',
},
projectFullPath: '/group/project',
securityDashboardHelpPath: '/security/dashboard/help-path',
vulnerabilitiesExportEndpoint: '/vulnerabilities/exports',
};
const provide = {
projectFullPath: '/group/project',
dashboardDocumentation: '/help/docs',
autoFixDocumentation: '/auto/fix/documentation',
emptyStateSvgPath: '/svgs/empty/svg',
......
import { merge } from 'lodash';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import ProjectPipelineStatus from 'ee/security_dashboard/components/project_pipeline_status.vue';
......@@ -18,12 +20,29 @@ describe('Project Pipeline Status Component', () => {
const findPipelineStatusBadge = () => wrapper.find(PipelineStatusBadge);
const findTimeAgoTooltip = () => wrapper.find(TimeAgoTooltip);
const findLink = () => wrapper.find(GlLink);
const findAutoFixMrsLink = () => wrapper.findByTestId('auto-fix-mrs-link');
const createWrapper = ({ props = {}, options = {} } = {}) => {
return shallowMount(ProjectPipelineStatus, {
propsData: { ...DEFAULT_PROPS, ...props },
...options,
});
const createWrapper = (options = {}) => {
return extendedWrapper(
shallowMount(
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(() => {
......@@ -56,7 +75,7 @@ describe('Project Pipeline Status Component', () => {
describe('when no pipeline has run', () => {
beforeEach(() => {
wrapper = createWrapper({ props: { pipeline: { path: '' } } });
wrapper = createWrapper({ propsData: { pipeline: { path: '' } } });
});
it('should not show the project_pipeline_status component', () => {
......@@ -65,4 +84,32 @@ describe('Project Pipeline Status Component', () => {
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', () => {
const createWrapper = ({ props = {}, $apollo = apolloMock } = {}, options = {}) => {
wrapper = shallowMount(ProjectVulnerabilitiesApp, {
provide: {
projectFullPath: '#',
},
propsData: {
dashboardDocumentation: '#',
emptyStateSvgPath: '#',
projectFullPath: '#',
...props,
},
mocks: {
......
......@@ -9,7 +9,7 @@ describe('Vulnerabilities count list component', () => {
const createWrapper = ({ query } = {}) => {
return shallowMount(VulnerabilityCountList, {
propsData: {
provide: {
projectFullPath: '/root/security-project',
},
mocks: {
......
......@@ -155,7 +155,8 @@ RSpec.describe ProjectsHelper do
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'),
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
......
......@@ -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."
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."
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