Commit 9b046ea8 authored by Daniel Tian's avatar Daniel Tian Committed by Kushal Pandya

Improve security dashboard/vulnerability report empty state messages

Updates the text and standardizes the styling of the "not configured"
empty state messages for the project, group, and instance-level security
dashboard and vulnerability report pages.
parent 2f21b551
...@@ -136,10 +136,6 @@ bar at the top of the page. Under **More**, select **Security**. ...@@ -136,10 +136,6 @@ bar at the top of the page. Under **More**, select **Security**.
![Security Center navigation link](img/security_center_dashboard_link_v12_4.png) ![Security Center navigation link](img/security_center_dashboard_link_v12_4.png)
The dashboard and vulnerability report are empty before you add projects.
![Uninitialized Security Center](img/security_center_dashboard_empty_v13_4.png)
### Adding projects to the Security Center ### Adding projects to the Security Center
To add projects to the Security Center: To add projects to the Security Center:
......
<script> <script>
import { GlEmptyState } from '@gitlab/ui'; import { GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
export default { export default {
components: { components: {
GlEmptyState, GlEmptyState,
}, },
inject: ['emptyStateSvgPath', 'dashboardDocumentation'], inject: ['emptyStateSvgPath', 'dashboardDocumentation'],
i18n: {
title: s__('SecurityReports|No vulnerabilities found'),
primaryButtonText: s__('SecurityReports|Learn more about setting up your dashboard'),
description: s__(
`SecurityReports|Although it's rare to have no vulnerabilities, it can happen. Check your settings to make sure you've set up your dashboard correctly.`,
),
},
}; };
</script> </script>
<template> <template>
<gl-empty-state <gl-empty-state
:title="s__(`SecurityReports|No vulnerabilities found`)" :title="$options.i18n.title"
:svg-path="emptyStateSvgPath" :svg-path="emptyStateSvgPath"
:primary-button-link="dashboardDocumentation" :primary-button-link="dashboardDocumentation"
:primary-button-text="s__('SecurityReports|Learn more about setting up your dashboard')" :primary-button-text="$options.i18n.primaryButtonText"
:description=" :description="$options.i18n.description"
s__(
`SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly.`,
)
"
/> />
</template> </template>
<script> <script>
import { GlEmptyState } from '@gitlab/ui'; import { GlEmptyState, GlButton } from '@gitlab/ui';
import { s__, __ } from '~/locale';
export default { export default {
components: { components: {
GlEmptyState, GlEmptyState,
GlButton,
}, },
inject: ['dashboardDocumentation', 'emptyStateSvgPath'], inject: ['dashboardDocumentation', 'emptyStateSvgPath'],
i18n: {
title: s__('SecurityReports|Monitor vulnerabilities in your group'),
description: s__(
'SecurityReports|Manage and track vulnerabilities identified in projects within your group. Vulnerabilities in projects are shown here when security testing is configured.',
),
secondaryButtonText: __('Learn more'),
},
}; };
</script> </script>
<template> <template>
<gl-empty-state <gl-empty-state
:title="s__('SecurityReports|Add projects to your group')" :title="$options.i18n.title"
:description="
s__(
'SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Add projects to your group to view their vulnerabilities here.',
)
"
:primary-button-link="dashboardDocumentation"
:primary-button-text="s__('SecurityReports|Learn more about setting up your dashboard')"
:svg-path="emptyStateSvgPath" :svg-path="emptyStateSvgPath"
/> :description="$options.i18n.description"
>
<template #actions>
<gl-button :href="dashboardDocumentation">{{ $options.i18n.secondaryButtonText }}</gl-button>
</template>
</gl-empty-state>
</template> </template>
<script> <script>
import { GlButton, GlEmptyState, GlLink } from '@gitlab/ui'; import { GlEmptyState } from '@gitlab/ui';
import { s__, __ } from '~/locale';
export default { export default {
components: { components: {
GlEmptyState, GlEmptyState,
GlButton,
GlLink,
}, },
inject: ['dashboardDocumentation', 'emptyStateSvgPath', 'instanceDashboardSettingsPath'], inject: ['dashboardDocumentation', 'emptyStateSvgPath', 'instanceDashboardSettingsPath'],
i18n: {
title: s__('SecurityReports|Monitor vulnerabilities in all of your projects'),
description: s__(
'SecurityReports|Manage and track vulnerabilities identified in your selected projects. Vulnerabilities for selected projects with security testing configured are shown here.',
),
primaryButtonText: s__('Add projects'),
secondaryButtonText: __('Learn more'),
},
}; };
</script> </script>
<template> <template>
<gl-empty-state <gl-empty-state
:title="s__('SecurityReports|Add a project to your dashboard')" :title="$options.i18n.title"
:svg-path="emptyStateSvgPath" :svg-path="emptyStateSvgPath"
> :description="$options.i18n.description"
<template #description> :primary-button-text="$options.i18n.primaryButtonText"
{{ :primary-button-link="instanceDashboardSettingsPath"
s__( :secondary-button-text="$options.i18n.secondaryButtonText"
'SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Select "Edit dashboard" to add and remove projects.', :secondary-button-link="dashboardDocumentation"
) />
}}
<gl-link :href="dashboardDocumentation">{{
s__('SecurityReports|More information')
}}</gl-link>
</template>
<template #actions>
<gl-button variant="success" :href="instanceDashboardSettingsPath">
{{ s__('SecurityReports|Add projects') }}
</gl-button>
</template>
</gl-empty-state>
</template> </template>
<script> <script>
import { GlEmptyState } from '@gitlab/ui'; import { GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__, __ } from '~/locale';
export default { export default {
components: { components: {
GlEmptyState, GlEmptyState,
}, },
inject: ['emptyStateSvgPath'], inject: ['emptyStateSvgPath', 'securityConfigurationPath'],
props: { props: {
helpPath: { helpPath: {
type: String, type: String,
required: true, required: true,
}, },
}, },
DESCRIPTION: s__( i18n: {
`SecurityReports|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities.`, title: s__('SecurityReports|Monitor vulnerabilities in your project'),
description: s__(
'SecurityReports|Manage and track vulnerabilities identified in your project. Vulnerabilities are shown here when security testing is configured.',
), ),
primaryButtonText: s__('SecurityReports|Configure security testing'),
secondaryButtonText: __('Learn more'),
},
}; };
</script> </script>
<template> <template>
<gl-empty-state <gl-empty-state
:title="s__('SecurityReports|Monitor vulnerabilities in your code')" :title="$options.i18n.title"
:svg-path="emptyStateSvgPath" :svg-path="emptyStateSvgPath"
:description="$options.DESCRIPTION" :description="$options.i18n.description"
:primary-button-link="helpPath" :primary-button-text="$options.i18n.primaryButtonText"
:primary-button-text="__('Learn more')" :primary-button-link="securityConfigurationPath"
:secondary-button-text="$options.i18n.secondaryButtonText"
:secondary-button-link="helpPath"
/> />
</template> </template>
...@@ -54,7 +54,7 @@ export default { ...@@ -54,7 +54,7 @@ export default {
}; };
}, },
computed: { computed: {
isNotYetConfigured() { hasNoProjects() {
return this.projects.length === 0 && this.projectsWereFetched; return this.projects.length === 0 && this.projectsWereFetched;
}, },
}, },
...@@ -70,10 +70,9 @@ export default { ...@@ -70,10 +70,9 @@ export default {
<template> <template>
<div> <div>
<gl-loading-icon v-if="!projectsWereFetched" size="lg" class="gl-mt-6" /> <gl-loading-icon v-if="!projectsWereFetched" size="lg" class="gl-mt-6" />
<dashboard-not-configured v-if="isNotYetConfigured" /> <dashboard-not-configured v-else-if="hasNoProjects" />
<security-dashboard-layout v-else :class="{ 'gl-display-none': !projectsWereFetched }"> <security-dashboard-layout v-else>
<template #header> <template #header>
<div>
<header class="gl-my-6 gl-display-flex gl-align-items-center"> <header class="gl-my-6 gl-display-flex gl-align-items-center">
<h2 class="gl-flex-grow-1 gl-my-0"> <h2 class="gl-flex-grow-1 gl-my-0">
{{ s__('SecurityReports|Vulnerability Report') }} {{ s__('SecurityReports|Vulnerability Report') }}
...@@ -85,7 +84,6 @@ export default { ...@@ -85,7 +84,6 @@ export default {
:full-path="groupFullPath" :full-path="groupFullPath"
:filters="filters" :filters="filters"
/> />
</div>
</template> </template>
<template #sticky> <template #sticky>
<filters :projects="projects" @filterChange="handleFilterChange" /> <filters :projects="projects" @filterChange="handleFilterChange" />
......
...@@ -67,19 +67,17 @@ export default { ...@@ -67,19 +67,17 @@ export default {
<template> <template>
<security-dashboard-layout> <security-dashboard-layout>
<dashboard-not-configured v-if="shouldShowEmptyState" />
<template #header> <template #header>
<div> <div v-if="shouldShowDashboard">
<header class="gl-my-6 gl-display-flex gl-align-items-center"> <header class="gl-my-6 gl-display-flex gl-align-items-center" data-testid="header">
<h2 class="gl-flex-grow-1 gl-my-0"> <h2 class="gl-flex-grow-1 gl-my-0">
{{ s__('SecurityReports|Vulnerability Report') }} {{ s__('SecurityReports|Vulnerability Report') }}
</h2> </h2>
<csv-export-button <csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" />
v-if="shouldShowDashboard"
:vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint"
/>
</header> </header>
<vulnerabilities-count-list <vulnerabilities-count-list
v-if="shouldShowDashboard"
:scope="$options.vulnerabilitiesSeverityCountScopes.instance" :scope="$options.vulnerabilitiesSeverityCountScopes.instance"
:filters="filters" :filters="filters"
/> />
...@@ -93,6 +91,5 @@ export default { ...@@ -93,6 +91,5 @@ export default {
:projects="projects" :projects="projects"
:filters="filters" :filters="filters"
/> />
<dashboard-not-configured v-else-if="shouldShowEmptyState" />
</security-dashboard-layout> </security-dashboard-layout>
</template> </template>
...@@ -134,7 +134,7 @@ export default { ...@@ -134,7 +134,7 @@ export default {
<template v-if="shouldShowEmptyState" #empty-state> <template v-if="shouldShowEmptyState" #empty-state>
<dashboard-not-configured :help-path="helpPath" /> <dashboard-not-configured :help-path="helpPath" />
</template> </template>
<template v-else-if="shouldShowCharts"> <template v-else-if="shouldShowCharts" #default>
<gl-line-chart <gl-line-chart
class="gl-mt-6" class="gl-mt-6"
:width="chartWidth" :width="chartWidth"
......
...@@ -9,12 +9,16 @@ export default { ...@@ -9,12 +9,16 @@ export default {
</script> </script>
<template> <template>
<div data-testid="security-charts-layout"> <div>
<h2>{{ $options.i18n.title }}</h2>
<slot name="loading"></slot> <slot name="loading"></slot>
<template v-if="$slots.default">
<h2 data-testid="title">{{ $options.i18n.title }}</h2>
<div class="security-charts gl-display-flex gl-flex-wrap"> <div class="security-charts gl-display-flex gl-flex-wrap">
<slot></slot> <slot></slot>
</div> </div>
</template>
<slot name="empty-state"></slot> <slot name="empty-state"></slot>
</div> </div>
</template> </template>
...@@ -38,6 +38,7 @@ export default (el, dashboardType) => { ...@@ -38,6 +38,7 @@ export default (el, dashboardType) => {
pipelineSecurityBuildsFailedCount, pipelineSecurityBuildsFailedCount,
pipelineSecurityBuildsFailedPath, pipelineSecurityBuildsFailedPath,
hasJiraVulnerabilitiesIntegrationEnabled, hasJiraVulnerabilitiesIntegrationEnabled,
securityConfigurationPath,
} = el.dataset; } = el.dataset;
if (isUnavailable) { if (isUnavailable) {
...@@ -60,6 +61,7 @@ export default (el, dashboardType) => { ...@@ -60,6 +61,7 @@ export default (el, dashboardType) => {
emptyStateSvgPath, emptyStateSvgPath,
notEnabledScannersHelpPath, notEnabledScannersHelpPath,
noPipelineRunScannersHelpPath, noPipelineRunScannersHelpPath,
securityConfigurationPath,
hasVulnerabilities: parseBoolean(hasVulnerabilities), hasVulnerabilities: parseBoolean(hasVulnerabilities),
scanners: scanners ? JSON.parse(scanners) : [], scanners: scanners ? JSON.parse(scanners) : [],
hasJiraVulnerabilitiesIntegrationEnabled: parseBoolean( hasJiraVulnerabilitiesIntegrationEnabled: parseBoolean(
......
...@@ -32,6 +32,7 @@ export default (el, dashboardType) => { ...@@ -32,6 +32,7 @@ export default (el, dashboardType) => {
const provide = { const provide = {
dashboardDocumentation: el.dataset.dashboardDocumentation, dashboardDocumentation: el.dataset.dashboardDocumentation,
emptyStateSvgPath: el.dataset.emptyStateSvgPath, emptyStateSvgPath: el.dataset.emptyStateSvgPath,
securityConfigurationPath: el.dataset.securityConfigurationPath,
}; };
let component; let component;
......
...@@ -258,7 +258,8 @@ module EE ...@@ -258,7 +258,8 @@ module EE
empty_state_svg_path: image_path('illustrations/security-dashboard_empty.svg'), empty_state_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
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'),
no_vulnerabilities_svg_path: image_path('illustrations/issues.svg'), no_vulnerabilities_svg_path: image_path('illustrations/issues.svg'),
project_full_path: project.full_path project_full_path: project.full_path,
security_configuration_path: project_security_configuration_path(@project)
}.merge!(security_dashboard_pipeline_data(project)) }.merge!(security_dashboard_pipeline_data(project))
else else
{ {
......
---
title: Improve security dashboard/vulnerability report empty state messages
merge_request: 55489
author:
type: changed
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`dashboard has no vulnerabilities empty state matches snapshot 1`] = `
"<section class=\\"row empty-state text-center\\">
<div class=\\"col-12\\">
<div class=\\"svg-250 svg-content\\"><img src=\\"/placeholder.svg\\" alt=\\"\\" class=\\"gl-max-w-full\\"></div>
</div>
<div class=\\"col-12\\">
<div class=\\"text-content gl-mx-auto gl-my-0 gl-p-5\\">
<h1 class=\\"h4\\">No vulnerabilities found</h1>
<p>
Although it's rare to have no vulnerabilities, it can happen. Check your settings to make sure you've set up your dashboard correctly.
</p>
<div><a href=\\"/path/to/dashboard/documentation\\" class=\\"btn btn-success btn-md gl-button\\">
<!---->
<!----> <span class=\\"gl-button-text\\">Learn more about setting up your dashboard</span></a>
<!---->
</div>
</div>
</div>
</section>"
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`first class group security dashboard empty state matches snapshot 1`] = `
"<gl-empty-state-stub title=\\"Monitor vulnerabilities in your group\\" svgpath=\\"/placeholder.svg\\" description=\\"Manage and track vulnerabilities identified in projects within your group. Vulnerabilities in projects are shown here when security testing is configured.\\">
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" href=\\"/path/to/dashboard/documentation\\">Learn more</gl-button-stub>
</gl-empty-state-stub>"
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`first class instance security dashboard empty state matches snapshot 1`] = `"<gl-empty-state-stub title=\\"Monitor vulnerabilities in all of your projects\\" svgpath=\\"/placeholder.svg\\" description=\\"Manage and track vulnerabilities identified in your selected projects. Vulnerabilities for selected projects with security testing configured are shown here.\\" primarybuttonlink=\\"/path/to/dashboard/settings\\" primarybuttontext=\\"Add projects\\" secondarybuttonlink=\\"/path/to/dashboard/documentation\\" secondarybuttontext=\\"Learn more\\"></gl-empty-state-stub>"`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`reports not configured empty state matches snapshot 1`] = `"<gl-empty-state-stub title=\\"Monitor vulnerabilities in your project\\" svgpath=\\"/placeholder.svg\\" description=\\"Manage and track vulnerabilities identified in your project. Vulnerabilities are shown here when security testing is configured.\\" primarybuttonlink=\\"/configuration\\" primarybuttontext=\\"Configure security testing\\" secondarybuttonlink=\\"/help\\" secondarybuttontext=\\"Learn more\\"></gl-empty-state-stub>"`;
import { GlEmptyState, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import DashboardHasNoVulnerabilities from 'ee/security_dashboard/components/empty_states/dashboard_has_no_vulnerabilities.vue'; import DashboardHasNoVulnerabilities from 'ee/security_dashboard/components/empty_states/dashboard_has_no_vulnerabilities.vue';
...@@ -15,10 +14,6 @@ describe('dashboard has no vulnerabilities empty state', () => { ...@@ -15,10 +14,6 @@ describe('dashboard has no vulnerabilities empty state', () => {
}, },
}); });
const findGlEmptyState = () => wrapper.find(GlEmptyState);
const findButton = () => wrapper.find(GlButton);
const findLink = () => wrapper.find('a');
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper(); wrapper = createWrapper();
}); });
...@@ -27,26 +22,7 @@ describe('dashboard has no vulnerabilities empty state', () => { ...@@ -27,26 +22,7 @@ describe('dashboard has no vulnerabilities empty state', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('contains a GlEmptyState', () => { it('matches snapshot', () => {
expect(findGlEmptyState().exists()).toBe(true); expect(wrapper.html()).toMatchSnapshot();
expect(findGlEmptyState().props('svgPath')).toBe(emptyStateSvgPath);
});
it('contains a GlLink with href attribute equal to dashboardDocumentation', () => {
expect(findLink().attributes('href')).toBe(dashboardDocumentation);
});
it('contains a GlButton', () => {
expect(findButton().exists()).toBe(true);
});
it('has the correct message', () => {
expect(findGlEmptyState().text()).toContain(
"While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly.",
);
});
it('has the correct title', () => {
expect(findGlEmptyState().text()).toContain('No vulnerabilities found');
}); });
}); });
import { GlEmptyState, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/group_dashboard_not_configured.vue'; import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/group_dashboard_not_configured.vue';
describe('first class group security dashboard empty state', () => { describe('first class group security dashboard empty state', () => {
...@@ -8,17 +7,13 @@ describe('first class group security dashboard empty state', () => { ...@@ -8,17 +7,13 @@ describe('first class group security dashboard empty state', () => {
const emptyStateSvgPath = '/placeholder.svg'; const emptyStateSvgPath = '/placeholder.svg';
const createWrapper = () => const createWrapper = () =>
mount(DashboardNotConfigured, { shallowMount(DashboardNotConfigured, {
provide: { provide: {
dashboardDocumentation, dashboardDocumentation,
emptyStateSvgPath, emptyStateSvgPath,
}, },
}); });
const findGlEmptyState = () => wrapper.find(GlEmptyState);
const findButton = () => wrapper.find(GlButton);
const findLink = () => wrapper.find('a');
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper(); wrapper = createWrapper();
}); });
...@@ -27,26 +22,7 @@ describe('first class group security dashboard empty state', () => { ...@@ -27,26 +22,7 @@ describe('first class group security dashboard empty state', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('contains a GlEmptyState', () => { it('matches snapshot', () => {
expect(findGlEmptyState().exists()).toBe(true); expect(wrapper.html()).toMatchSnapshot();
expect(findGlEmptyState().props('svgPath')).toBe(emptyStateSvgPath);
});
it('contains a GlLink with href attribute equal to dashboardDocumentation', () => {
expect(findLink().attributes('href')).toBe(dashboardDocumentation);
});
it('contains a GlButton', () => {
expect(findButton().exists()).toBe(true);
});
it('has the correct message', () => {
expect(findGlEmptyState().text()).toContain(
'The security dashboard displays the latest security findings for projects you wish to monitor. Add projects to your group to view their vulnerabilities here.',
);
});
it('has the correct title', () => {
expect(findGlEmptyState().text()).toContain('Add projects to your group');
}); });
}); });
import { GlEmptyState, GlButton, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/instance_dashboard_not_configured.vue'; import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/instance_dashboard_not_configured.vue';
describe('first class instance security dashboard empty state', () => { describe('first class instance security dashboard empty state', () => {
...@@ -9,7 +8,7 @@ describe('first class instance security dashboard empty state', () => { ...@@ -9,7 +8,7 @@ describe('first class instance security dashboard empty state', () => {
const emptyStateSvgPath = '/placeholder.svg'; const emptyStateSvgPath = '/placeholder.svg';
const createWrapper = () => const createWrapper = () =>
mount(DashboardNotConfigured, { shallowMount(DashboardNotConfigured, {
provide: { provide: {
dashboardDocumentation, dashboardDocumentation,
emptyStateSvgPath, emptyStateSvgPath,
...@@ -17,10 +16,6 @@ describe('first class instance security dashboard empty state', () => { ...@@ -17,10 +16,6 @@ describe('first class instance security dashboard empty state', () => {
}, },
}); });
const findGlEmptyState = () => wrapper.find(GlEmptyState);
const findButton = () => wrapper.find(GlButton);
const findLink = () => wrapper.find(GlLink);
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper(); wrapper = createWrapper();
}); });
...@@ -29,16 +24,7 @@ describe('first class instance security dashboard empty state', () => { ...@@ -29,16 +24,7 @@ describe('first class instance security dashboard empty state', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('contains a GlEmptyState', () => { it('matches snapshot', () => {
expect(findGlEmptyState().exists()).toBe(true); expect(wrapper.html()).toMatchSnapshot();
expect(findGlEmptyState().props('svgPath')).toBe(emptyStateSvgPath);
});
it('contains a GlLink with href attribute equal to dashboardDocumentation', () => {
expect(findLink().attributes('href')).toBe(dashboardDocumentation);
});
it('contains a GlButton with a link to settings page', () => {
expect(findButton().attributes('href')).toBe(instanceDashboardSettingsPath);
}); });
}); });
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/reports_not_configured.vue'; import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/reports_not_configured.vue';
...@@ -6,29 +5,23 @@ describe('reports not configured empty state', () => { ...@@ -6,29 +5,23 @@ describe('reports not configured empty state', () => {
let wrapper; let wrapper;
const helpPath = '/help'; const helpPath = '/help';
const emptyStateSvgPath = '/placeholder.svg'; const emptyStateSvgPath = '/placeholder.svg';
const securityConfigurationPath = '/configuration';
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(ReportsNotConfigured, { wrapper = shallowMount(ReportsNotConfigured, {
provide: { provide: {
emptyStateSvgPath, emptyStateSvgPath,
securityConfigurationPath,
}, },
propsData: { helpPath }, propsData: { helpPath },
}); });
}; };
const findEmptyState = () => wrapper.find(GlEmptyState);
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
}); });
it.each` it('matches snapshot', () => {
prop | data expect(wrapper.html()).toMatchSnapshot();
${'title'} | ${'Monitor vulnerabilities in your code'}
${'svgPath'} | ${emptyStateSvgPath}
${'description'} | ${'The security dashboard displays the latest security report. Use it to find and fix vulnerabilities.'}
${'primaryButtonLink'} | ${helpPath}
${'primaryButtonText'} | ${'Learn more'}
`('passes the correct data to the $prop prop', ({ prop, data }) => {
expect(findEmptyState().props(prop)).toBe(data);
}); });
}); });
...@@ -52,10 +52,6 @@ describe('First Class Group Dashboard Component', () => { ...@@ -52,10 +52,6 @@ describe('First Class Group Dashboard Component', () => {
expect(findLoadingIcon().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(true);
}); });
it('dashboard should have display none because it needs to fetch the projects', () => {
expect(findDashboardLayout().attributes('class')).toEqual('gl-display-none');
});
it('should not display the dashboard not configured component', () => { it('should not display the dashboard not configured component', () => {
expect(findEmptyState().exists()).toBe(false); expect(findEmptyState().exists()).toBe(false);
}); });
...@@ -103,10 +99,6 @@ describe('First Class Group Dashboard Component', () => { ...@@ -103,10 +99,6 @@ describe('First Class Group Dashboard Component', () => {
expect(findLoadingIcon().exists()).toBe(false); expect(findLoadingIcon().exists()).toBe(false);
}); });
it('dashboard should no more have display none', () => {
expect(findDashboardLayout().attributes('class')).toEqual('');
});
it('should not display the dashboard not configured component', () => { it('should not display the dashboard not configured component', () => {
expect(findEmptyState().exists()).toBe(false); expect(findEmptyState().exists()).toBe(false);
}); });
......
import { within } from '@testing-library/dom';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue'; import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue';
import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/instance_dashboard_not_configured.vue'; import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/instance_dashboard_not_configured.vue';
...@@ -22,6 +21,7 @@ describe('First Class Instance Dashboard Component', () => { ...@@ -22,6 +21,7 @@ describe('First Class Instance Dashboard Component', () => {
const findEmptyState = () => wrapper.find(DashboardNotConfigured); const findEmptyState = () => wrapper.find(DashboardNotConfigured);
const findFilters = () => wrapper.find(Filters); const findFilters = () => wrapper.find(Filters);
const findVulnerabilitiesCountList = () => wrapper.find(VulnerabilitiesCountList); const findVulnerabilitiesCountList = () => wrapper.find(VulnerabilitiesCountList);
const findHeader = () => wrapper.find('[data-testid="header"]');
const createWrapper = ({ data = {}, stubs, mocks = defaultMocks() }) => { const createWrapper = ({ data = {}, stubs, mocks = defaultMocks() }) => {
return shallowMount(FirstClassInstanceDashboard, { return shallowMount(FirstClassInstanceDashboard, {
...@@ -53,6 +53,10 @@ describe('First Class Instance Dashboard Component', () => { ...@@ -53,6 +53,10 @@ describe('First Class Instance Dashboard Component', () => {
}); });
}); });
it('should show the header', () => {
expect(findHeader().exists()).toBe(true);
});
it('should render the vulnerabilities', () => { it('should render the vulnerabilities', () => {
expect(findInstanceVulnerabilities().props()).toEqual({ expect(findInstanceVulnerabilities().props()).toEqual({
filters: {}, filters: {},
...@@ -96,12 +100,10 @@ describe('First Class Instance Dashboard Component', () => { ...@@ -96,12 +100,10 @@ describe('First Class Instance Dashboard Component', () => {
}); });
}); });
it('does not render the export button', () => { it('does not render the export button, vulnerabilities count list, or header', () => {
expect(findCsvExportButton().exists()).toBe(false); expect(findCsvExportButton().exists()).toBe(false);
});
it('does not render the vulnerabilities count list', () => {
expect(findVulnerabilitiesCountList().exists()).toBe(false); expect(findVulnerabilitiesCountList().exists()).toBe(false);
expect(findHeader().exists()).toBe(false);
}); });
}); });
...@@ -114,36 +116,13 @@ describe('First Class Instance Dashboard Component', () => { ...@@ -114,36 +116,13 @@ describe('First Class Instance Dashboard Component', () => {
}); });
}); });
it('renders the empty state', () => { it('only renders the empty state', () => {
expect(findEmptyState().props()).toEqual({}); expect(findEmptyState().exists()).toBe(true);
});
it('does not render the export button', () => {
expect(findCsvExportButton().exists()).toBe(false); expect(findCsvExportButton().exists()).toBe(false);
});
it('does not render the vulnerability list', () => {
expect(findInstanceVulnerabilities().exists()).toBe(false); expect(findInstanceVulnerabilities().exists()).toBe(false);
});
it('has no filters', () => {
expect(findFilters().exists()).toBe(false); expect(findFilters().exists()).toBe(false);
});
it('does not render the vulnerabilities count list', () => {
expect(findVulnerabilitiesCountList().exists()).toBe(false); expect(findVulnerabilitiesCountList().exists()).toBe(false);
}); expect(findHeader().exists()).toBe(false);
});
describe('always', () => {
beforeEach(() => {
wrapper = createWrapper({});
});
it('has the security dashboard title', () => {
expect(
within(wrapper.element).getByRole('heading', { name: 'Vulnerability Report' }),
).not.toBe(null);
}); });
}); });
}); });
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import SecurityChartsLayout from 'ee/security_dashboard/components/security_charts_layout.vue'; import SecurityChartsLayout from 'ee/security_dashboard/components/security_charts_layout.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
describe('Security Charts Layout component', () => { describe('Security Charts Layout component', () => {
let wrapper; let wrapper;
const DummyComponent1 = { const DummyComponent = {
name: 'dummy-component-1', name: 'dummy-component-1',
template: '<p>dummy component 1</p>', template: '<p>dummy component 1</p>',
}; };
const DummyComponent2 = {
name: 'dummy-component-2',
template: '<p>dummy component 2</p>',
};
const findSlot = () => wrapper.find(`[data-testid="security-charts-layout"]`); const findDummyComponent = () => wrapper.findComponent(DummyComponent);
const findTitle = () => wrapper.findByTestId('title');
const createWrapper = (slots) => { const createWrapper = (slots) => {
wrapper = shallowMount(SecurityChartsLayout, { slots }); wrapper = extendedWrapper(shallowMount(SecurityChartsLayout, { slots }));
}; };
beforeEach(() => {
createWrapper({ default: DummyComponent1, 'empty-state': DummyComponent2 });
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('should render the default slot', () => { it('should render the default slot', () => {
const slot = findSlot(); createWrapper({ default: DummyComponent });
expect(slot.find(DummyComponent1).exists()).toBe(true);
expect(findDummyComponent().exists()).toBe(true);
expect(findTitle().exists()).toBe(true);
}); });
it('should render the empty-state slot', () => { it('should render the empty-state slot', () => {
const slot = findSlot(); createWrapper({ 'empty-state': DummyComponent });
expect(slot.find(DummyComponent2).exists()).toBe(true);
expect(findDummyComponent().exists()).toBe(true);
expect(findTitle().exists()).toBe(false);
});
it('should render the loading slot', () => {
createWrapper({ loading: DummyComponent });
expect(findDummyComponent().exists()).toBe(true);
expect(findTitle().exists()).toBe(false);
}); });
}); });
...@@ -2,12 +2,7 @@ ...@@ -2,12 +2,7 @@
exports[`Security Charts default states sets up group-level 1`] = ` exports[`Security Charts default states sets up group-level 1`] = `
<div> <div>
<div <div>
data-testid="security-charts-layout"
>
<h2>
Security Dashboard
</h2>
<div <div
class="gl-spinner-container gl-mt-6" class="gl-spinner-container gl-mt-6"
> >
...@@ -16,21 +11,13 @@ exports[`Security Charts default states sets up group-level 1`] = ` ...@@ -16,21 +11,13 @@ exports[`Security Charts default states sets up group-level 1`] = `
class="align-text-bottom gl-spinner gl-spinner-dark gl-spinner-lg" class="align-text-bottom gl-spinner gl-spinner-dark gl-spinner-lg"
/> />
</div> </div>
<div
class="security-charts gl-display-flex gl-flex-wrap"
/>
</div> </div>
</div> </div>
`; `;
exports[`Security Charts default states sets up instance-level 1`] = ` exports[`Security Charts default states sets up instance-level 1`] = `
<div> <div>
<div <div>
data-testid="security-charts-layout"
>
<h2>
Security Dashboard
</h2>
<div <div
class="gl-spinner-container gl-mt-6" class="gl-spinner-container gl-mt-6"
> >
...@@ -39,9 +26,6 @@ exports[`Security Charts default states sets up instance-level 1`] = ` ...@@ -39,9 +26,6 @@ exports[`Security Charts default states sets up instance-level 1`] = `
class="align-text-bottom gl-spinner gl-spinner-dark gl-spinner-lg" class="align-text-bottom gl-spinner gl-spinner-dark gl-spinner-lg"
/> />
</div> </div>
<div
class="security-charts gl-display-flex gl-flex-wrap"
/>
</div> </div>
</div> </div>
`; `;
......
...@@ -137,7 +137,8 @@ RSpec.describe ProjectsHelper do ...@@ -137,7 +137,8 @@ RSpec.describe ProjectsHelper do
empty_state_svg_path: start_with('/assets/illustrations/security-dashboard_empty'), empty_state_svg_path: start_with('/assets/illustrations/security-dashboard_empty'),
security_dashboard_help_path: '/help/user/application_security/security_dashboard/index', security_dashboard_help_path: '/help/user/application_security/security_dashboard/index',
project_full_path: project.full_path, project_full_path: project.full_path,
no_vulnerabilities_svg_path: start_with('/assets/illustrations/issues-') no_vulnerabilities_svg_path: start_with('/assets/illustrations/issues-'),
security_configuration_path: end_with('/configuration')
} }
end end
......
...@@ -1935,6 +1935,9 @@ msgstr "" ...@@ -1935,6 +1935,9 @@ msgstr ""
msgid "Add previously merged commits" msgid "Add previously merged commits"
msgstr "" msgstr ""
msgid "Add projects"
msgstr ""
msgid "Add reaction" msgid "Add reaction"
msgstr "" msgstr ""
...@@ -26812,19 +26815,16 @@ msgstr "" ...@@ -26812,19 +26815,16 @@ msgstr ""
msgid "SecurityReports|%{firstProject}, %{secondProject}, and %{rest}" msgid "SecurityReports|%{firstProject}, %{secondProject}, and %{rest}"
msgstr "" msgstr ""
msgid "SecurityReports|Add a project to your dashboard"
msgstr ""
msgid "SecurityReports|Add or remove projects to monitor in the security area. Projects included in this list will have their results displayed in the security dashboard and vulnerability report." msgid "SecurityReports|Add or remove projects to monitor in the security area. Projects included in this list will have their results displayed in the security dashboard and vulnerability report."
msgstr "" msgstr ""
msgid "SecurityReports|Add projects" msgid "SecurityReports|Add projects"
msgstr "" msgstr ""
msgid "SecurityReports|Add projects to your group" msgid "SecurityReports|All"
msgstr "" msgstr ""
msgid "SecurityReports|All" msgid "SecurityReports|Although it's rare to have no vulnerabilities, it can happen. Check your settings to make sure you've set up your dashboard correctly."
msgstr "" msgstr ""
msgid "SecurityReports|Change status" msgid "SecurityReports|Change status"
...@@ -26839,6 +26839,9 @@ msgstr "" ...@@ -26839,6 +26839,9 @@ msgstr ""
msgid "SecurityReports|Comment edited on '%{vulnerabilityName}'" msgid "SecurityReports|Comment edited on '%{vulnerabilityName}'"
msgstr "" msgstr ""
msgid "SecurityReports|Configure security testing"
msgstr ""
msgid "SecurityReports|Create Jira issue" msgid "SecurityReports|Create Jira issue"
msgstr "" msgstr ""
...@@ -26896,16 +26899,28 @@ msgstr "" ...@@ -26896,16 +26899,28 @@ msgstr ""
msgid "SecurityReports|Learn more about setting up your dashboard" msgid "SecurityReports|Learn more about setting up your dashboard"
msgstr "" msgstr ""
msgid "SecurityReports|Monitor vulnerabilities in your code" msgid "SecurityReports|Manage and track vulnerabilities identified in projects within your group. Vulnerabilities in projects are shown here when security testing is configured."
msgstr "" msgstr ""
msgid "SecurityReports|Monitored projects" msgid "SecurityReports|Manage and track vulnerabilities identified in your project. Vulnerabilities are shown here when security testing is configured."
msgstr "" msgstr ""
msgid "SecurityReports|More info" msgid "SecurityReports|Manage and track vulnerabilities identified in your selected projects. Vulnerabilities for selected projects with security testing configured are shown here."
msgstr ""
msgid "SecurityReports|Monitor vulnerabilities in all of your projects"
msgstr ""
msgid "SecurityReports|Monitor vulnerabilities in your group"
msgstr "" msgstr ""
msgid "SecurityReports|More information" msgid "SecurityReports|Monitor vulnerabilities in your project"
msgstr ""
msgid "SecurityReports|Monitored projects"
msgstr ""
msgid "SecurityReports|More info"
msgstr "" msgstr ""
msgid "SecurityReports|No activity" msgid "SecurityReports|No activity"
...@@ -26971,15 +26986,6 @@ msgstr "" ...@@ -26971,15 +26986,6 @@ msgstr ""
msgid "SecurityReports|Status" msgid "SecurityReports|Status"
msgstr "" msgstr ""
msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Add projects to your group to view their vulnerabilities here."
msgstr ""
msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
msgstr ""
msgid "SecurityReports|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
msgstr ""
msgid "SecurityReports|There was an error adding the comment." msgid "SecurityReports|There was an error adding the comment."
msgstr "" msgstr ""
...@@ -27031,9 +27037,6 @@ msgstr "" ...@@ -27031,9 +27037,6 @@ msgstr ""
msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully." msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully."
msgstr "" msgstr ""
msgid "SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
msgid "SecurityReports|With issues" msgid "SecurityReports|With issues"
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