Commit 6ee5d266 authored by Savas Vedova's avatar Savas Vedova

Merge branch '321775-move-project-dashboard-to-vulnerability-reports' into 'master'

Move project level vulnerabilities to vulnerability report component

See merge request gitlab-org/gitlab!59586
parents de948242 04752cbf
...@@ -6,13 +6,7 @@ export default { ...@@ -6,13 +6,7 @@ export default {
components: { components: {
GlEmptyState, GlEmptyState,
}, },
inject: ['emptyStateSvgPath', 'securityConfigurationPath'], inject: ['emptyStateSvgPath', 'securityConfigurationPath', 'securityDashboardHelpPath'],
props: {
helpPath: {
type: String,
required: true,
},
},
i18n: { i18n: {
title: s__('SecurityReports|Monitor vulnerabilities in your project'), title: s__('SecurityReports|Monitor vulnerabilities in your project'),
description: s__( description: s__(
...@@ -32,6 +26,6 @@ export default { ...@@ -32,6 +26,6 @@ export default {
:primary-button-text="$options.i18n.primaryButtonText" :primary-button-text="$options.i18n.primaryButtonText"
:primary-button-link="securityConfigurationPath" :primary-button-link="securityConfigurationPath"
:secondary-button-text="$options.i18n.secondaryButtonText" :secondary-button-text="$options.i18n.secondaryButtonText"
:secondary-button-link="helpPath" :secondary-button-link="securityDashboardHelpPath"
/> />
</template> </template>
<script>
import Cookies from 'js-cookie';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import AutoFixUserCallout from './auto_fix_user_callout.vue';
import CsvExportButton from './csv_export_button.vue';
import ReportsNotConfigured from './empty_states/reports_not_configured.vue';
import Filters from './first_class_vulnerability_filters.vue';
import ProjectPipelineStatus from './project_pipeline_status.vue';
import ProjectVulnerabilitiesApp from './project_vulnerabilities.vue';
import SecurityDashboardLayout from './security_dashboard_layout.vue';
import SurveyRequestBanner from './survey_request_banner.vue';
import VulnerabilitiesCountList from './vulnerability_count_list.vue';
export default {
components: {
AutoFixUserCallout,
ProjectPipelineStatus,
ProjectVulnerabilitiesApp,
ReportsNotConfigured,
SecurityDashboardLayout,
VulnerabilitiesCountList,
CsvExportButton,
Filters,
SurveyRequestBanner,
},
mixins: [glFeatureFlagsMixin()],
inject: ['dashboardDocumentation', 'autoFixDocumentation', 'projectFullPath'],
props: {
securityDashboardHelpPath: {
type: String,
required: true,
},
pipeline: {
type: Object,
required: false,
default: () => ({}),
},
},
data() {
const shouldShowAutoFixUserCallout =
this.glFeatures.securityAutoFix && !Cookies.get('auto_fix_user_callout_dismissed');
return {
filters: null,
shouldShowAutoFixUserCallout,
};
},
methods: {
handleFilterChange(filters) {
this.filters = filters;
},
handleAutoFixUserCalloutClose() {
Cookies.set('auto_fix_user_callout_dismissed', 'true');
this.shouldShowAutoFixUserCallout = false;
},
},
};
</script>
<template>
<div>
<survey-request-banner class="gl-mt-5" />
<template v-if="pipeline.id">
<auto-fix-user-callout
v-if="shouldShowAutoFixUserCallout"
:help-page-path="autoFixDocumentation"
@close="handleAutoFixUserCalloutClose"
/>
<security-dashboard-layout>
<template #header>
<div class="gl-mt-6 gl-display-flex">
<h4 class="gl-flex-grow-1 gl-my-0">
{{ s__('SecurityReports|Vulnerability Report') }}
</h4>
<csv-export-button />
</div>
<project-pipeline-status :pipeline="pipeline" />
<vulnerabilities-count-list
class="gl-mt-6"
:full-path="projectFullPath"
:filters="filters"
/>
</template>
<template #sticky>
<filters @filterChange="handleFilterChange" />
</template>
<project-vulnerabilities-app
:dashboard-documentation="dashboardDocumentation"
:filters="filters"
/>
</security-dashboard-layout>
</template>
<reports-not-configured v-else :help-path="securityDashboardHelpPath" />
</div>
</template>
...@@ -40,10 +40,6 @@ export default { ...@@ -40,10 +40,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
helpPath: {
type: String,
required: true,
},
}, },
apollo: { apollo: {
trendsByDay: { trendsByDay: {
...@@ -180,7 +176,7 @@ export default { ...@@ -180,7 +176,7 @@ export default {
<template> <template>
<security-charts-layout ref="layout"> <security-charts-layout ref="layout">
<template v-if="shouldShowEmptyState" #empty-state> <template v-if="shouldShowEmptyState" #empty-state>
<dashboard-not-configured :help-path="helpPath" /> <dashboard-not-configured />
</template> </template>
<template v-else-if="shouldShowCharts" #default> <template v-else-if="shouldShowCharts" #default>
<gl-line-chart <gl-line-chart
......
<script> <script>
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import GroupSecurityVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue'; import Cookies from 'js-cookie';
import InstanceSecurityVulnerabilities from 'ee/security_dashboard/components/first_class_instance_security_dashboard_vulnerabilities.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants'; import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import vulnerableProjectsGroupQuery from '../graphql/queries/vulnerable_projects_group.query.graphql'; import vulnerableProjectsGroupQuery from '../graphql/queries/vulnerable_projects_group.query.graphql';
import vulnerableProjectsInstanceQuery from '../graphql/queries/vulnerable_projects_instance.query.graphql'; import vulnerableProjectsInstanceQuery from '../graphql/queries/vulnerable_projects_instance.query.graphql';
import AutoFixUserCallout from './auto_fix_user_callout.vue';
import CsvExportButton from './csv_export_button.vue'; import CsvExportButton from './csv_export_button.vue';
import DashboardNotConfiguredGroup from './empty_states/group_dashboard_not_configured.vue'; import DashboardNotConfiguredGroup from './empty_states/group_dashboard_not_configured.vue';
import DashboardNotConfiguredInstance from './empty_states/instance_dashboard_not_configured.vue'; import DashboardNotConfiguredInstance from './empty_states/instance_dashboard_not_configured.vue';
import DashboardNotConfiguredProject from './empty_states/reports_not_configured.vue';
import GroupSecurityVulnerabilities from './first_class_group_security_dashboard_vulnerabilities.vue';
import InstanceSecurityVulnerabilities from './first_class_instance_security_dashboard_vulnerabilities.vue';
import ProjectPipelineStatus from './project_pipeline_status.vue';
import ProjectSecurityVulnerabilities from './project_vulnerabilities.vue';
import SurveyRequestBanner from './survey_request_banner.vue'; import SurveyRequestBanner from './survey_request_banner.vue';
import VulnerabilitiesCountList from './vulnerability_count_list.vue'; import VulnerabilitiesCountList from './vulnerability_count_list.vue';
export default { export default {
components: { components: {
AutoFixUserCallout,
SecurityDashboardLayout, SecurityDashboardLayout,
GroupSecurityVulnerabilities, GroupSecurityVulnerabilities,
InstanceSecurityVulnerabilities, InstanceSecurityVulnerabilities,
ProjectSecurityVulnerabilities,
Filters, Filters,
CsvExportButton, CsvExportButton,
SurveyRequestBanner, SurveyRequestBanner,
DashboardNotConfiguredGroup, DashboardNotConfiguredGroup,
DashboardNotConfiguredInstance, DashboardNotConfiguredInstance,
DashboardNotConfiguredProject,
ProjectPipelineStatus,
GlLoadingIcon, GlLoadingIcon,
VulnerabilitiesCountList, VulnerabilitiesCountList,
}, },
inject: ['groupFullPath', 'dashboardType'], mixins: [glFeatureFlagsMixin()],
inject: [
'groupFullPath',
'projectFullPath',
'dashboardType',
'dashboardDocumentation',
'autoFixDocumentation',
'projectFullPath',
],
props: {
pipeline: {
type: Object,
required: false,
default: () => ({}),
},
},
queries: { queries: {
[DASHBOARD_TYPES.GROUP]: vulnerableProjectsGroupQuery, [DASHBOARD_TYPES.GROUP]: vulnerableProjectsGroupQuery,
[DASHBOARD_TYPES.INSTANCE]: vulnerableProjectsInstanceQuery, [DASHBOARD_TYPES.INSTANCE]: vulnerableProjectsInstanceQuery,
...@@ -51,30 +76,49 @@ export default { ...@@ -51,30 +76,49 @@ export default {
}, },
}, },
data() { data() {
const shouldShowAutoFixUserCallout =
this.dashboardType === DASHBOARD_TYPES.PROJECT &&
this.glFeatures.securityAutoFix &&
!Cookies.get(this.$options.autoFixUserCalloutCookieName);
return { return {
filters: null, filters: null,
projects: [], projects: [],
shouldShowAutoFixUserCallout,
}; };
}, },
computed: { computed: {
projectsWereFetched() { projectsWereFetched() {
return !this.$apollo.queries.projects?.loading; return !this.$apollo.queries.projects?.loading;
}, },
fullPath() {
return this.groupFullPath || this.projectFullPath;
},
isGroup() { isGroup() {
return this.dashboardType === DASHBOARD_TYPES.GROUP; return this.dashboardType === DASHBOARD_TYPES.GROUP;
}, },
isInstance() { isInstance() {
return this.dashboardType === DASHBOARD_TYPES.INSTANCE; return this.dashboardType === DASHBOARD_TYPES.INSTANCE;
}, },
hasNoProjects() { isProject() {
return this.projects.length === 0 && this.projectsWereFetched; return this.dashboardType === DASHBOARD_TYPES.PROJECT;
},
isDashboardConfigured() {
return this.isProject
? Boolean(this.pipeline?.id)
: this.projects.length > 0 && this.projectsWereFetched;
}, },
}, },
methods: { methods: {
handleFilterChange(filters) { handleFilterChange(filters) {
this.filters = filters; this.filters = filters;
}, },
handleAutoFixUserCalloutClose() {
Cookies.set(this.$options.autoFixUserCalloutCookieName, 'true');
this.shouldShowAutoFixUserCallout = false;
},
}, },
autoFixUserCalloutCookieName: 'auto_fix_user_callout_dismissed',
i18n: { i18n: {
title: s__('SecurityReports|Vulnerability Report'), title: s__('SecurityReports|Vulnerability Report'),
}, },
...@@ -84,27 +128,37 @@ export default { ...@@ -84,27 +128,37 @@ 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" />
<template v-else-if="hasNoProjects"> <template v-else-if="!isDashboardConfigured">
<survey-request-banner class="gl-mt-5" /> <survey-request-banner class="gl-mt-5" />
<dashboard-not-configured-group v-if="isGroup" /> <dashboard-not-configured-group v-if="isGroup" />
<dashboard-not-configured-instance v-else-if="isInstance" /> <dashboard-not-configured-instance v-else-if="isInstance" />
<dashboard-not-configured-project v-else-if="isProject" />
</template>
<template v-else>
<auto-fix-user-callout
v-if="shouldShowAutoFixUserCallout"
:help-page-path="autoFixDocumentation"
@close="handleAutoFixUserCalloutClose"
/>
<security-dashboard-layout>
<template #header>
<survey-request-banner class="gl-mt-5" />
<header class="gl-my-6 gl-display-flex gl-align-items-center">
<h2 class="gl-flex-grow-1 gl-my-0">
{{ $options.i18n.title }}
</h2>
<csv-export-button />
</header>
<project-pipeline-status v-if="isProject" class="gl-mb-6" :pipeline="pipeline" />
<vulnerabilities-count-list :full-path="fullPath" :filters="filters" />
</template>
<template #sticky>
<filters :projects="projects" @filterChange="handleFilterChange" />
</template>
<group-security-vulnerabilities v-if="isGroup" :filters="filters" />
<instance-security-vulnerabilities v-else-if="isInstance" :filters="filters" />
<project-security-vulnerabilities v-else-if="isProject" :filters="filters" />
</security-dashboard-layout>
</template> </template>
<security-dashboard-layout v-else>
<template #header>
<survey-request-banner class="gl-mt-5" />
<header class="gl-my-6 gl-display-flex gl-align-items-center">
<h2 class="gl-flex-grow-1 gl-my-0">
{{ $options.i18n.title }}
</h2>
<csv-export-button />
</header>
<vulnerabilities-count-list :full-path="groupFullPath" :filters="filters" />
</template>
<template #sticky>
<filters :projects="projects" @filterChange="handleFilterChange" />
</template>
<group-security-vulnerabilities v-if="isGroup" :filters="filters" />
<instance-security-vulnerabilities v-if="isInstance" :filters="filters" />
</security-dashboard-layout>
</div> </div>
</template> </template>
...@@ -35,6 +35,7 @@ export default (el, dashboardType) => { ...@@ -35,6 +35,7 @@ export default (el, dashboardType) => {
groupFullPath: el.dataset.groupFullPath, groupFullPath: el.dataset.groupFullPath,
securityConfigurationPath: el.dataset.securityConfigurationPath, securityConfigurationPath: el.dataset.securityConfigurationPath,
surveyRequestSvgPath: el.dataset.surveyRequestSvgPath, surveyRequestSvgPath: el.dataset.surveyRequestSvgPath,
securityDashboardHelpPath: el.dataset.securityDashboardHelpPath,
}; };
let component; let component;
...@@ -49,7 +50,6 @@ export default (el, dashboardType) => { ...@@ -49,7 +50,6 @@ export default (el, dashboardType) => {
component = ProjectSecurityCharts; component = ProjectSecurityCharts;
props.projectFullPath = el.dataset.projectFullPath; props.projectFullPath = el.dataset.projectFullPath;
props.hasVulnerabilities = parseBoolean(el.dataset.hasVulnerabilities); props.hasVulnerabilities = parseBoolean(el.dataset.hasVulnerabilities);
props.helpPath = el.dataset.securityDashboardHelpPath;
} }
const router = createRouter(); const router = createRouter();
......
import Vue from 'vue'; import Vue from 'vue';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants'; import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import FirstClassProjectSecurityDashboard from './components/first_class_project_security_dashboard.vue';
import UnavailableState from './components/unavailable_state.vue'; import UnavailableState from './components/unavailable_state.vue';
import VulnerabilityReport from './components/vulnerability_report.vue'; import VulnerabilityReport from './components/vulnerability_report.vue';
import apolloProvider from './graphql/provider'; import apolloProvider from './graphql/provider';
...@@ -62,10 +61,15 @@ export default (el, dashboardType) => { ...@@ -62,10 +61,15 @@ export default (el, dashboardType) => {
emptyStateSvgPath, emptyStateSvgPath,
notEnabledScannersHelpPath, notEnabledScannersHelpPath,
noPipelineRunScannersHelpPath, noPipelineRunScannersHelpPath,
instanceDashboardSettingsPath,
securityDashboardHelpPath,
securityConfigurationPath, securityConfigurationPath,
surveyRequestSvgPath, surveyRequestSvgPath,
vulnerabilitiesExportEndpoint, vulnerabilitiesExportEndpoint,
groupFullPath, groupFullPath,
projectFullPath,
autoFixDocumentation,
autoFixMrsPath,
hasVulnerabilities: parseBoolean(hasVulnerabilities), hasVulnerabilities: parseBoolean(hasVulnerabilities),
scanners: scanners ? JSON.parse(scanners) : [], scanners: scanners ? JSON.parse(scanners) : [],
hasJiraVulnerabilitiesIntegrationEnabled: parseBoolean( hasJiraVulnerabilitiesIntegrationEnabled: parseBoolean(
...@@ -74,16 +78,12 @@ export default (el, dashboardType) => { ...@@ -74,16 +78,12 @@ export default (el, dashboardType) => {
}; };
const props = { const props = {
securityDashboardHelpPath,
projectAddEndpoint, projectAddEndpoint,
projectListEndpoint, projectListEndpoint,
dashboardType, dashboardType,
}; };
let component;
if (dashboardType === DASHBOARD_TYPES.PROJECT) { if (dashboardType === DASHBOARD_TYPES.PROJECT) {
component = FirstClassProjectSecurityDashboard;
props.pipeline = { props.pipeline = {
createdAt: pipelineCreatedAt, createdAt: pipelineCreatedAt,
id: pipelineId, id: pipelineId,
...@@ -91,14 +91,6 @@ export default (el, dashboardType) => { ...@@ -91,14 +91,6 @@ export default (el, dashboardType) => {
securityBuildsFailedCount: Number(pipelineSecurityBuildsFailedCount), securityBuildsFailedCount: Number(pipelineSecurityBuildsFailedCount),
securityBuildsFailedPath: pipelineSecurityBuildsFailedPath, securityBuildsFailedPath: pipelineSecurityBuildsFailedPath,
}; };
provide.projectFullPath = projectFullPath;
provide.autoFixDocumentation = autoFixDocumentation;
provide.autoFixMrsPath = autoFixMrsPath;
} else if (dashboardType === DASHBOARD_TYPES.GROUP) {
component = VulnerabilityReport;
} else if (dashboardType === DASHBOARD_TYPES.INSTANCE) {
provide.instanceDashboardSettingsPath = instanceDashboardSettingsPath;
component = VulnerabilityReport;
} }
const router = createRouter(); const router = createRouter();
...@@ -111,7 +103,7 @@ export default (el, dashboardType) => { ...@@ -111,7 +103,7 @@ export default (el, dashboardType) => {
apolloProvider, apolloProvider,
provide, provide,
render(createElement) { render(createElement) {
return createElement(component, { props }); return createElement(VulnerabilityReport, { props });
}, },
}); });
}; };
...@@ -3,17 +3,17 @@ import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/ ...@@ -3,17 +3,17 @@ import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/
describe('reports not configured empty state', () => { describe('reports not configured empty state', () => {
let wrapper; let wrapper;
const helpPath = '/help';
const emptyStateSvgPath = '/placeholder.svg'; const emptyStateSvgPath = '/placeholder.svg';
const securityConfigurationPath = '/configuration'; const securityConfigurationPath = '/configuration';
const securityDashboardHelpPath = '/help';
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(ReportsNotConfigured, { wrapper = shallowMount(ReportsNotConfigured, {
provide: { provide: {
emptyStateSvgPath, emptyStateSvgPath,
securityConfigurationPath, securityConfigurationPath,
securityDashboardHelpPath,
}, },
propsData: { helpPath },
}); });
}; };
......
import { GlBanner } from '@gitlab/ui';
import { within } from '@testing-library/dom';
import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
import AutoFixUserCallout from 'ee/security_dashboard/components/auto_fix_user_callout.vue';
import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue';
import ReportsNotConfigured from 'ee/security_dashboard/components/empty_states/reports_not_configured.vue';
import FirstClassProjectSecurityDashboard from 'ee/security_dashboard/components/first_class_project_security_dashboard.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import ProjectPipelineStatus from 'ee/security_dashboard/components/project_pipeline_status.vue';
import ProjectVulnerabilitiesApp from 'ee/security_dashboard/components/project_vulnerabilities.vue';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import SurveyRequestBanner from 'ee/security_dashboard/components/survey_request_banner.vue';
import VulnerabilityCountList from 'ee/security_dashboard/components/vulnerability_count_list.vue';
const props = {
notEnabledScannersHelpPath: '/help/docs/',
noPipelineRunScannersHelpPath: '/new/pipeline',
pipeline: {
createdAt: '2020-10-06T20:08:07Z',
id: '214',
path: '/mixed-vulnerabilities/dependency-list-test-01/-/pipelines/214',
},
securityDashboardHelpPath: '/security/dashboard/help-path',
};
const provide = {
projectFullPath: '/group/project',
dashboardDocumentation: '/help/docs',
autoFixDocumentation: '/auto/fix/documentation',
emptyStateSvgPath: '/svgs/empty/svg',
glFeatures: {
securityAutoFix: true,
},
};
const filters = { foo: 'bar' };
describe('First class Project Security Dashboard component', () => {
let wrapper;
const findFilters = () => wrapper.findComponent(Filters);
const findProjectPipelineStatus = () => wrapper.findComponent(ProjectPipelineStatus);
const findVulnerabilities = () => wrapper.findComponent(ProjectVulnerabilitiesApp);
const findVulnerabilityCountList = () => wrapper.findComponent(VulnerabilityCountList);
const findUnconfiguredState = () => wrapper.findComponent(ReportsNotConfigured);
const findCsvExportButton = () => wrapper.findComponent(CsvExportButton);
const findAutoFixUserCallout = () => wrapper.findComponent(AutoFixUserCallout);
const findSurveyRequestBanner = () => wrapper.findComponent(SurveyRequestBanner);
const createComponent = (options) => {
wrapper = shallowMount(FirstClassProjectSecurityDashboard, {
propsData: {
...props,
...options.props,
},
provide,
stubs: { SecurityDashboardLayout, GlBanner },
...options,
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('on render when there are vulnerabilities', () => {
beforeEach(() => {
createComponent({
props: { hasVulnerabilities: true },
data: () => ({ filters }),
});
});
it('should render the header correctly', () => {
expect(
within(wrapper.element).getByRole('heading', { name: 'Vulnerability Report' }),
).not.toBe(null);
});
it('should render the vulnerabilities', () => {
expect(findVulnerabilities().exists()).toBe(true);
});
it('should pass down the properties correctly to the vulnerabilities', () => {
expect(findVulnerabilities().props()).toEqual({
projectFullPath: props.projectFullPath,
filters,
});
});
it('should pass down the properties correctly to the vulnerability count list', () => {
expect(findVulnerabilityCountList().props()).toEqual({
fullPath: provide.projectFullPath,
filters,
});
});
it('should render the filters component', () => {
expect(findFilters().exists()).toBe(true);
});
it('does not display the unconfigured state', () => {
expect(findUnconfiguredState().exists()).toBe(false);
});
it('should display the csv export button', () => {
expect(findCsvExportButton().props('vulnerabilitiesExportEndpoint')).toEqual(
props.vulnerabilitiesExportEndpoint,
);
});
it('should display the project pipeline status', () => {
expect(findProjectPipelineStatus()).toExist();
});
it('should show the survey request banner', () => {
expect(findSurveyRequestBanner().exists()).toBe(true);
});
});
describe('auto-fix user callout', () => {
describe('feature flag disabled', () => {
beforeEach(() => {
createComponent({
props: { hasVulnerabilities: true },
provide: {
...provide,
glFeatures: {
securityAutoFix: false,
},
},
});
});
it('does not show user callout', () => {
expect(findAutoFixUserCallout().exists()).toBe(false);
});
});
describe('cookie not set', () => {
beforeEach(() => {
jest.spyOn(Cookies, 'set');
createComponent({
props: { hasVulnerabilities: true },
});
});
it('shows user callout by default', () => {
expect(findAutoFixUserCallout().exists()).toBe(true);
});
it('when dismissed, hides the user callout and sets the cookie', async () => {
await findAutoFixUserCallout().vm.$emit('close');
expect(findAutoFixUserCallout().exists()).toBe(false);
expect(Cookies.set).toHaveBeenCalledWith('auto_fix_user_callout_dismissed', 'true');
});
});
describe('cookie set', () => {
beforeEach(() => {
jest.doMock('js-cookie', () => ({
get: jest.fn().mockReturnValue(true),
}));
createComponent({
props: { hasVulnerabilities: true },
});
});
it('does not show user callout', () => {
expect(findAutoFixUserCallout().exists()).toBe(false);
});
});
});
describe('with filter data', () => {
beforeEach(() => {
createComponent({
props: {
hasVulnerabilities: true,
},
data() {
return { filters };
},
});
});
it('should pass the filter data down to the vulnerabilities', () => {
expect(findVulnerabilities().props().filters).toEqual(filters);
});
});
describe('when there is no vulnerability', () => {
beforeEach(() => {
createComponent({
props: {
pipeline: { id: undefined },
},
});
});
it('displays the unconfigured state', () => {
expect(findUnconfiguredState().exists()).toBe(true);
});
it('shows the survey request banner', () => {
expect(findSurveyRequestBanner().exists()).toBe(true);
});
});
});
...@@ -137,7 +137,7 @@ describe('Project Security Charts component', () => { ...@@ -137,7 +137,7 @@ describe('Project Security Charts component', () => {
}); });
it('should display the empty state', () => { it('should display the empty state', () => {
expect(findEmptyState().props()).toEqual({ helpPath }); expect(findEmptyState().exists()).toBe(true);
}); });
it('should not display the chart', () => { it('should not display the chart', () => {
......
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import Cookies from 'js-cookie';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import AutoFixUserCallout from 'ee/security_dashboard/components/auto_fix_user_callout.vue';
import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue'; import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue';
import DashboardNotConfiguredGroup from 'ee/security_dashboard/components/empty_states/group_dashboard_not_configured.vue'; import DashboardNotConfiguredGroup from 'ee/security_dashboard/components/empty_states/group_dashboard_not_configured.vue';
import DashboardNotConfiguredInstance from 'ee/security_dashboard/components/empty_states/instance_dashboard_not_configured.vue'; import DashboardNotConfiguredInstance from 'ee/security_dashboard/components/empty_states/instance_dashboard_not_configured.vue';
import DashboardNotConfiguredProject from 'ee/security_dashboard/components/empty_states/reports_not_configured.vue';
import GroupVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue'; import GroupVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue';
import InstanceVulnerabilities from 'ee/security_dashboard/components/first_class_instance_security_dashboard_vulnerabilities.vue'; import InstanceVulnerabilities from 'ee/security_dashboard/components/first_class_instance_security_dashboard_vulnerabilities.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import ProjectPipelineStatus from 'ee/security_dashboard/components/project_pipeline_status.vue';
import ProjectVulnerabilities from 'ee/security_dashboard/components/project_vulnerabilities.vue';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import SurveyRequestBanner from 'ee/security_dashboard/components/survey_request_banner.vue'; import SurveyRequestBanner from 'ee/security_dashboard/components/survey_request_banner.vue';
import VulnerabilitiesCountList from 'ee/security_dashboard/components/vulnerability_count_list.vue'; import VulnerabilitiesCountList from 'ee/security_dashboard/components/vulnerability_count_list.vue';
...@@ -24,12 +29,17 @@ describe('Vulnerability Report', () => { ...@@ -24,12 +29,17 @@ describe('Vulnerability Report', () => {
const findSurveyRequestBanner = () => wrapper.findComponent(SurveyRequestBanner); const findSurveyRequestBanner = () => wrapper.findComponent(SurveyRequestBanner);
const findInstanceVulnerabilities = () => wrapper.findComponent(InstanceVulnerabilities); const findInstanceVulnerabilities = () => wrapper.findComponent(InstanceVulnerabilities);
const findGroupVulnerabilities = () => wrapper.findComponent(GroupVulnerabilities); const findGroupVulnerabilities = () => wrapper.findComponent(GroupVulnerabilities);
const findProjectVulnerabilities = () => wrapper.findComponent(ProjectVulnerabilities);
const findCsvExportButton = () => wrapper.findComponent(CsvExportButton); const findCsvExportButton = () => wrapper.findComponent(CsvExportButton);
const findGroupEmptyState = () => wrapper.findComponent(DashboardNotConfiguredGroup); const findGroupEmptyState = () => wrapper.findComponent(DashboardNotConfiguredGroup);
const findInstanceEmptyState = () => wrapper.findComponent(DashboardNotConfiguredInstance); const findInstanceEmptyState = () => wrapper.findComponent(DashboardNotConfiguredInstance);
const findProjectEmptyState = () => wrapper.findComponent(DashboardNotConfiguredProject);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findFilters = () => wrapper.findComponent(Filters); const findFilters = () => wrapper.findComponent(Filters);
const findVulnerabilitiesCountList = () => wrapper.findComponent(VulnerabilitiesCountList); const findVulnerabilitiesCountList = () => wrapper.findComponent(VulnerabilitiesCountList);
const findProjectPipelineStatus = () => wrapper.findComponent(ProjectPipelineStatus);
const findAutoFixUserCallout = () => wrapper.findComponent(AutoFixUserCallout);
const findHeader = () => wrapper.find('h2'); const findHeader = () => wrapper.find('h2');
const createWrapper = ({ data = {}, mocks, propsData, provide, apolloProvider }) => { const createWrapper = ({ data = {}, mocks, propsData, provide, apolloProvider }) => {
...@@ -45,7 +55,14 @@ describe('Vulnerability Report', () => { ...@@ -45,7 +55,14 @@ describe('Vulnerability Report', () => {
data: () => data, data: () => data,
mocks, mocks,
propsData, propsData,
provide: { groupFullPath: undefined, ...provide }, provide: {
groupFullPath: undefined,
projectFullPath: undefined,
autoFixDocumentation: undefined,
dashboardDocumentation: 'path/to/documentation',
securityDashboardHelpPath: 'path/to/project/documentation',
...provide,
},
stubs: { SecurityDashboardLayout }, stubs: { SecurityDashboardLayout },
}); });
}; };
...@@ -119,6 +136,10 @@ describe('Vulnerability Report', () => { ...@@ -119,6 +136,10 @@ describe('Vulnerability Report', () => {
it('shows the vulnerability count list and passes the filters prop', () => { it('shows the vulnerability count list and passes the filters prop', () => {
expect(findVulnerabilitiesCountList().props('filters')).toBe(filters); expect(findVulnerabilitiesCountList().props('filters')).toBe(filters);
}); });
it('does not show project pipeline status', () => {
expect(findProjectPipelineStatus().exists()).toBe(false);
});
}); });
describe('when initialized - group level', () => { describe('when initialized - group level', () => {
...@@ -168,6 +189,7 @@ describe('Vulnerability Report', () => { ...@@ -168,6 +189,7 @@ describe('Vulnerability Report', () => {
it('only renders the empty state', () => { it('only renders the empty state', () => {
expect(findGroupEmptyState().exists()).toBe(true); expect(findGroupEmptyState().exists()).toBe(true);
expect(findInstanceEmptyState().exists()).toBe(false); expect(findInstanceEmptyState().exists()).toBe(false);
expect(findProjectEmptyState().exists()).toBe(false);
expect(findCsvExportButton().exists()).toBe(false); expect(findCsvExportButton().exists()).toBe(false);
expect(findFilters().exists()).toBe(false); expect(findFilters().exists()).toBe(false);
expect(findVulnerabilitiesCountList().exists()).toBe(false); expect(findVulnerabilitiesCountList().exists()).toBe(false);
...@@ -201,4 +223,72 @@ describe('Vulnerability Report', () => { ...@@ -201,4 +223,72 @@ describe('Vulnerability Report', () => {
expect(findSurveyRequestBanner().exists()).toBe(false); expect(findSurveyRequestBanner().exists()).toBe(false);
}); });
}); });
describe('when initialized - project level', () => {
const createProjectWrapper = ({ securityAutoFix } = {}) =>
createWrapper({
provide: {
dashboardType: DASHBOARD_TYPES.PROJECT,
autoFixDocumentation: 'path/to/help-page',
glFeatures: {
securityAutoFix,
},
},
propsData: {
pipeline: {
id: '591',
},
},
apolloProvider: createApolloProvider(),
});
it('does not show user callout when feature flag is disabled', () => {
wrapper = createProjectWrapper({ securityAutoFix: false });
expect(findAutoFixUserCallout().exists()).toBe(false);
});
it('shows user callout when the cookie is not set and hides it when dismissed', async () => {
jest.spyOn(Cookies, 'set');
wrapper = createProjectWrapper({ securityAutoFix: true });
const autoFixUserCallOut = findAutoFixUserCallout();
expect(autoFixUserCallOut.exists()).toBe(true);
await autoFixUserCallOut.vm.$emit('close');
expect(autoFixUserCallOut.exists()).toBe(false);
expect(Cookies.set).toHaveBeenCalledWith(
wrapper.vm.$options.autoFixUserCalloutCookieName,
'true',
);
});
it('does not show user callout when the cookie is set', () => {
jest.doMock('js-cookie', () => ({ get: jest.fn().mockReturnValue(true) }));
wrapper = createProjectWrapper({ securityAutoFix: true });
expect(findAutoFixUserCallout().exists()).toBe(false);
});
it('shows the project pipeline status', () => {
wrapper = createProjectWrapper();
expect(findProjectPipelineStatus().exists()).toBe(true);
});
it('renders the vulnerabilities', () => {
wrapper = createProjectWrapper();
expect(findProjectVulnerabilities().exists()).toBe(true);
});
});
describe('when uninitialized - project level', () => {
beforeEach(() => {
wrapper = createWrapper({
provide: {
dashboardType: DASHBOARD_TYPES.PROJECT,
},
apolloProvider: createApolloProvider(),
});
});
it('renders empty project state', () => {
expect(findProjectEmptyState().exists()).toBe(true);
});
});
}); });
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