Commit 23795036 authored by Nathan Friend's avatar Nathan Friend

Merge branch 'project-sec-dashboard-no-pipeline' into 'master'

Add a first class dashboard unconfigured state

See merge request gitlab-org/gitlab!28344
parents a132a3ac d5dd2222
<script>
import { GlEmptyState } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
components: {
GlEmptyState,
},
props: {
helpPath: {
type: String,
required: true,
},
svgPath: {
type: String,
required: true,
},
},
DESCRIPTION: s__(
`SecurityDashboard|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities.`,
),
};
</script>
<template>
<gl-empty-state
:title="s__('SecurityDashboard|Monitor vulnerabilities in your code')"
:svg-path="svgPath"
:description="$options.DESCRIPTION"
:primary-button-link="helpPath"
:primary-button-text="__('Learn more')"
/>
</template>
<script>
import ProjectVulnerabilitiesApp from 'ee/vulnerabilities/components/project_vulnerabilities_app.vue';
import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/reports_not_configured.vue';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import VulnerabilitiesCountList from 'ee/security_dashboard/components/vulnerability_count_list.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import ProjectVulnerabilitiesApp from 'ee/vulnerabilities/components/project_vulnerabilities_app.vue';
export default {
components: {
SecurityDashboardLayout,
ProjectVulnerabilitiesApp,
ReportsNotConfigured,
SecurityDashboardLayout,
VulnerabilitiesCountList,
Filters,
},
props: {
dashboardDocumentation: {
emptyStateSvgPath: {
type: String,
required: true,
},
emptyStateSvgPath: {
securityDashboardHelpPath: {
type: String,
required: true,
},
projectFullPath: {
type: String,
required: true,
required: false,
default: '',
},
dashboardDocumentation: {
type: String,
required: false,
default: '',
},
hasPipelineData: {
type: Boolean,
required: false,
default: false,
},
},
data() {
......@@ -39,6 +52,8 @@ export default {
</script>
<template>
<div>
<template v-if="hasPipelineData">
<security-dashboard-layout>
<template #header>
<vulnerabilities-count-list :project-full-path="projectFullPath" />
......@@ -51,4 +66,11 @@ export default {
:filters="filters"
/>
</security-dashboard-layout>
</template>
<reports-not-configured
v-else
:svg-path="emptyStateSvgPath"
:help-path="securityDashboardHelpPath"
/>
</div>
</template>
<script>
import { isUndefined } from 'lodash';
import { GlEmptyState, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import ReportsNotConfigured from './empty_states/reports_not_configured.vue';
import SecurityDashboard from './security_dashboard_vuex.vue';
export default {
......@@ -12,10 +12,11 @@ export default {
components: {
GlEmptyState,
GlSprintf,
UserAvatarLink,
Icon,
TimeagoTooltip,
ReportsNotConfigured,
SecurityDashboard,
TimeagoTooltip,
UserAvatarLink,
},
props: {
hasPipelineData: {
......@@ -23,11 +24,6 @@ export default {
required: false,
default: false,
},
emptyStateIllustrationPath: {
type: String,
required: false,
default: null,
},
securityDashboardHelpPath: {
type: String,
required: false,
......@@ -84,15 +80,6 @@ export default {
default: null,
},
},
computed: {
emptyStateDescription() {
return s__(
`SecurityDashboard|
The security dashboard displays the latest security report.
Use it to find and fix vulnerabilities.`,
).trim();
},
},
};
</script>
<template>
......@@ -157,13 +144,10 @@ export default {
</template>
</security-dashboard>
</template>
<gl-empty-state
<reports-not-configured
v-else
:title="s__('SecurityDashboard|Monitor vulnerabilities in your code')"
:svg-path="emptyStateIllustrationPath"
:description="emptyStateDescription"
:primary-button-link="securityDashboardHelpPath"
:primary-button-text="__('Learn more')"
:svg-path="emptyStateSvgPath"
:help-path="securityDashboardHelpPath"
/>
</div>
</template>
......@@ -20,10 +20,17 @@ export default (
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
const { dashboardDocumentation, emptyStateSvgPath } = el.dataset;
const {
dashboardDocumentation,
emptyStateSvgPath,
hasPipelineData,
securityDashboardHelpPath,
} = el.dataset;
const props = {
emptyStateSvgPath,
dashboardDocumentation,
hasPipelineData: Boolean(hasPipelineData),
securityDashboardHelpPath,
};
let component;
......
......@@ -10,40 +10,38 @@ export default () => {
const securityTab = document.getElementById('js-security-report-app');
const {
hasPipelineData,
userPath,
userAvatarPath,
pipelineCreated,
pipelinePath,
userName,
commitId,
commitPath,
refId,
refPath,
dashboardDocumentation,
emptyStateSvgPath,
hasPipelineData,
pipelineCreated,
pipelineId,
pipelinePath,
projectId,
projectName,
dashboardDocumentation,
emptyStateSvgPath,
refId,
refPath,
securityDashboardHelpPath,
userAvatarPath,
userName,
userPath,
vulnerabilitiesEndpoint,
vulnerabilitiesSummaryEndpoint,
vulnerabilityFeedbackHelpPath,
securityDashboardHelpPath,
emptyStateIllustrationPath,
} = securityTab.dataset;
const parsedPipelineId = parseInt(pipelineId, 10);
const parsedHasPipelineData = parseBoolean(hasPipelineData);
let props = {
hasPipelineData: parsedHasPipelineData,
dashboardDocumentation,
emptyStateSvgPath,
hasPipelineData: parsedHasPipelineData,
securityDashboardHelpPath,
vulnerabilitiesEndpoint,
vulnerabilitiesSummaryEndpoint,
vulnerabilityFeedbackHelpPath,
securityDashboardHelpPath,
emptyStateIllustrationPath,
};
if (parsedHasPipelineData) {
props = {
......
......@@ -110,7 +110,7 @@ export default {
<gl-empty-state
:title="s__(`No vulnerabilities found for this project`)"
:svg-path="emptyStateSvgPath"
:description="$options.emptyStateDecription"
:description="$options.emptyStateDescription"
:primary-button-link="dashboardDocumentation"
:primary-button-text="s__('Security Reports|Learn more about setting up your dashboard')"
/>
......
......@@ -213,9 +213,8 @@ module EE
def project_security_dashboard_config(project, pipeline)
if pipeline.nil?
{
empty_state_illustration_path: image_path('illustrations/security-dashboard_empty.svg'),
security_dashboard_help_path: help_page_path('user/application_security/security_dashboard/index'),
has_pipeline_data: "false"
empty_state_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
security_dashboard_help_path: help_page_path('user/application_security/security_dashboard/index')
}
else
{
......
......@@ -40,7 +40,7 @@ describe Projects::Security::DashboardController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:index)
expect(response.body).to have_css("div#js-security-report-app[data-has-pipeline-data=true]")
expect(response.body).to have_css("div#js-security-report-app[data-has-pipeline-data]")
end
end
......@@ -54,7 +54,7 @@ describe Projects::Security::DashboardController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:index)
expect(response.body).to have_css("div#js-security-report-app[data-has-pipeline-data=true]")
expect(response.body).to have_css("div#js-security-report-app[data-has-pipeline-data]")
end
end
......@@ -64,7 +64,7 @@ describe Projects::Security::DashboardController do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:index)
expect(response.body).to have_css("div#js-security-report-app[data-has-pipeline-data=false]")
expect(response.body).not_to have_css("div#js-security-report-app[data-has-pipeline-data]")
end
end
end
......
import { shallowMount } from '@vue/test-utils';
import { GlEmptyState } from '@gitlab/ui';
import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/reports_not_configured.vue';
describe('reports not configured empty state', () => {
let wrapper;
const helpPath = '/help';
const svgPath = '/placeholder.svg';
const createComponent = () => {
wrapper = shallowMount(ReportsNotConfigured, {
propsData: { helpPath, svgPath },
});
};
const findEmptyState = () => wrapper.find(GlEmptyState);
beforeEach(() => {
createComponent();
});
it.each`
prop | data
${'title'} | ${'Monitor vulnerabilities in your code'}
${'svgPath'} | ${svgPath}
${'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);
});
});
......@@ -3,11 +3,13 @@ import FirstClassProjectSecurityDashboard from 'ee/security_dashboard/components
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import ProjectVulnerabilitiesApp from 'ee/vulnerabilities/components/project_vulnerabilities_app.vue';
import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/reports_not_configured.vue';
const drilledProps = {
const props = {
dashboardDocumentation: '/help/docs',
emptyStateSvgPath: '/svgs/empty/svg',
projectFullPath: '/group/project',
securityDashboardHelpPath: '/security/dashboard/help-path',
};
const filters = { foo: 'bar' };
......@@ -16,10 +18,14 @@ describe('First class Project Security Dashboard component', () => {
const findFilters = () => wrapper.find(Filters);
const findVulnerabilities = () => wrapper.find(ProjectVulnerabilitiesApp);
const findUnconfiguredState = () => wrapper.find(ReportsNotConfigured);
const createComponent = options => {
wrapper = shallowMount(FirstClassProjectSecurityDashboard, {
propsData: drilledProps,
propsData: {
...props,
...options.props,
},
stubs: { SecurityDashboardLayout },
...options,
});
......@@ -29,30 +35,38 @@ describe('First class Project Security Dashboard component', () => {
wrapper.destroy();
});
describe('on render', () => {
describe('on render when pipeline has data', () => {
beforeEach(() => {
createComponent();
createComponent({ props: { hasPipelineData: true } });
});
it('should render the vulnerabilities', () => {
expect(findVulnerabilities().exists()).toBe(true);
});
it.each(Object.entries(drilledProps))(
'should pass down the %s prop to the vulnerabilities',
(key, value) => {
expect(findVulnerabilities().props(key)).toBe(value);
},
it('should pass down the %s prop to the vulnerabilities', () => {
expect(findVulnerabilities().props('dashboardDocumentation')).toBe(
props.dashboardDocumentation,
);
expect(findVulnerabilities().props('emptyStateSvgPath')).toBe(props.emptyStateSvgPath);
expect(findVulnerabilities().props('projectFullPath')).toBe(props.projectFullPath);
});
it('should render the filters component', () => {
expect(findFilters().exists()).toBe(true);
});
it('does not display the unconfigured state', () => {
expect(findUnconfiguredState().exists()).toBe(false);
});
});
describe('with filter data', () => {
beforeEach(() => {
createComponent({
props: {
hasPipelineData: true,
},
data() {
return { filters };
},
......@@ -63,4 +77,18 @@ describe('First class Project Security Dashboard component', () => {
expect(findVulnerabilities().props().filters).toEqual(filters);
});
});
describe('when pipeline has no data', () => {
beforeEach(() => {
createComponent({
props: {
hasPipelineData: false,
},
});
});
it('displays the unconfigured state', () => {
expect(findUnconfiguredState().exists()).toBe(true);
});
});
});
......@@ -24,7 +24,7 @@ describe('Project Security Dashboard component', () => {
stubs: ['security-dashboard-table'],
propsData: {
hasPipelineData: true,
emptyStateIllustrationPath: `${TEST_HOST}/img`,
emptyStateSvgPath: `${TEST_HOST}/img`,
securityDashboardHelpPath: `${TEST_HOST}/help_dashboard`,
commit: {
id: '1234adf',
......
......@@ -96,8 +96,10 @@ describe ProjectsHelper do
subject { helper.project_security_dashboard_config(project, nil) }
it 'returns simple config' do
expect(subject[:security_dashboard_help_path]).to eq '/help/user/application_security/security_dashboard/index'
expect(subject[:has_pipeline_data]).to eq 'false'
expect(subject).to match(
empty_state_svg_path: start_with('/assets/illustrations/security-dashboard_empty'),
security_dashboard_help_path: '/help/user/application_security/security_dashboard/index'
)
end
end
......
......@@ -17850,9 +17850,6 @@ msgstr ""
msgid "SecurityConfiguration|Status"
msgstr ""
msgid "SecurityDashboard| The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
msgstr ""
msgid "SecurityDashboard|%{firstProject} and %{secondProject}"
msgstr ""
......@@ -17913,6 +17910,9 @@ msgstr ""
msgid "SecurityDashboard|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
msgstr ""
msgid "SecurityDashboard|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
msgstr ""
msgid "SecurityDashboard|Unable to add %{invalidProjects}"
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