Commit dd9982b8 authored by Savas Vedova's avatar Savas Vedova

Merge branch '354082-fix-report-not-showing' into 'master'

Fix vulnerability report not showing for manually-added vulnerabilities

See merge request gitlab-org/gitlab!82698
parents cab2cc12 17b423da
<script>
import { GlFormGroup, GlFormInput, GlDropdown, GlTruncate, GlDropdownItem } from '@gitlab/ui';
import { groupBy, isEqual, isNumber, omit } from 'lodash';
import { groupBy, isEqual, isNumber } from 'lodash';
import { mapState, mapActions } from 'vuex';
import { REPORT_TYPES, SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
import { REPORT_TYPES_DEFAULT, SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
import ProtectedBranchesSelector from 'ee/vue_shared/components/branches_selector/protected_branches_selector.vue';
import { sprintf } from '~/locale';
import {
......@@ -21,8 +21,6 @@ import ApproversSelect from './approvers_select.vue';
const DEFAULT_NAME = 'Default';
export const EXCLUDED_REPORT_TYPE = 'cluster_image_scanning';
export const READONLY_NAMES = [LICENSE_CHECK_NAME, VULNERABILITY_CHECK_NAME, COVERAGE_CHECK_NAME];
function mapServerResponseToValidationErrors(messages) {
......@@ -75,7 +73,7 @@ export default {
severityLevels: [],
vulnerabilityStates: [],
approvalVulnerabilityStatesKeys: Object.keys(APPROVAL_VULNERABILITY_STATES),
reportTypesKeys: Object.keys(this.$options.REPORT_TYPES),
reportTypesKeys: Object.keys(REPORT_TYPES_DEFAULT),
severityLevelsKeys: Object.keys(SEVERITY_LEVELS),
...this.getInitialData(),
};
......@@ -265,10 +263,10 @@ export default {
case 0:
return APPROVAL_DIALOG_I18N.form.scannersSelectLabel;
case 1:
return this.$options.REPORT_TYPES[this.scanners[0]];
return REPORT_TYPES_DEFAULT[this.scanners[0]];
default:
return sprintf(APPROVAL_DIALOG_I18N.form.multipleSelectedLabel, {
firstLabel: this.$options.REPORT_TYPES[this.scanners[0]],
firstLabel: REPORT_TYPES_DEFAULT[this.scanners[0]],
numberOfAdditionalLabels: this.scanners.length - 1,
});
}
......@@ -476,7 +474,7 @@ export default {
},
},
APPROVAL_DIALOG_I18N,
REPORT_TYPES: omit(REPORT_TYPES, EXCLUDED_REPORT_TYPE),
REPORT_TYPES_DEFAULT,
SEVERITY_LEVELS,
APPROVAL_VULNERABILITY_STATES,
};
......@@ -519,7 +517,7 @@ export default {
<gl-truncate :text="$options.APPROVAL_DIALOG_I18N.form.selectAllLabel" />
</gl-dropdown-item>
<gl-dropdown-item
v-for="(value, key) in $options.REPORT_TYPES"
v-for="(value, key) in $options.REPORT_TYPES_DEFAULT"
:key="key"
is-check-item
:is-checked="isScannerSelected(key)"
......
<script>
import { GlToggle } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import { severityFilter, simpleScannerFilter } from 'ee/security_dashboard/helpers';
import { severityFilter, simpleScannerFilterPipeline } from 'ee/security_dashboard/helpers';
import { DISMISSAL_STATES } from 'ee/security_dashboard/store/modules/filters/constants';
import { s__ } from '~/locale';
import SimpleFilter from '../shared/filters/simple_filter.vue';
......@@ -16,7 +16,7 @@ export default {
},
data() {
return {
filterConfigs: [severityFilter, simpleScannerFilter],
filterConfigs: [severityFilter, simpleScannerFilterPipeline],
};
},
computed: {
......
......@@ -40,7 +40,7 @@ export default {
this.selectedFinding = undefined;
},
},
filtersToShow: [FILTERS.STATUS, FILTERS.SEVERITY, FILTERS.TOOL_SIMPLE],
filtersToShow: [FILTERS.STATUS, FILTERS.SEVERITY, FILTERS.TOOL_PIPELINE],
fieldsToShow: [
FIELDS.CHECKBOX,
FIELDS.STATUS,
......
......@@ -3,7 +3,6 @@ import { difference } from 'lodash';
import { getCookie, setCookie, parseBoolean } from '~/lib/utils/common_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { translateScannerNames } from '~/security_configuration/utils';
import SecurityTrainingPromo from 'ee/security_dashboard/components/shared/security_training_promo.vue';
import ReportNotConfiguredProject from '../shared/empty_states/report_not_configured_project.vue';
......@@ -25,7 +24,7 @@ export default {
SecurityTrainingPromo,
},
mixins: [glFeatureFlagsMixin()],
inject: ['fullPath', 'pipeline', 'autoFixDocumentation'],
inject: ['fullPath', 'pipeline', 'autoFixDocumentation', 'hasVulnerabilities'],
data() {
return {
scannerAlertDismissed: false,
......@@ -53,7 +52,15 @@ export default {
},
computed: {
isReportConfigured() {
return this.pipeline?.id;
// A report is configured when either a pipeline generated a security report, or when
// vulnerabilities are added through the new vulnerability page or API. There are 3 cases:
// 1. No vulnerabilities exist and no pipeline has ever generated a security report. The
// report is not configured.
// 2. Vulnerabilities were added through the new vulnerability page or the API. Whether a
// pipeline was run does not matter, the report is configured.
// 3. A pipeline was run that outputted a security report. Whether vulnerabilities exist does
// not matter, the report is configured.
return Boolean(this.pipeline.id) || this.hasVulnerabilities;
},
notEnabledSecurityScanners() {
const { available = [], enabled = [] } = this.securityScanners;
......@@ -63,6 +70,9 @@ export default {
const { enabled = [], pipelineRun = [] } = this.securityScanners;
return difference(enabled, pipelineRun);
},
shouldShowPipelineStatus() {
return this.pipeline.createdAt && this.pipeline.id && this.pipeline.path;
},
shouldShowScannersAlert() {
return (
!this.scannerAlertDismissed &&
......@@ -112,7 +122,7 @@ export default {
<vulnerability-report-tabs :query="$options.projectVulnerabilitiesQuery">
<template #header-development>
<project-pipeline-status :pipeline="pipeline" />
<project-pipeline-status v-if="shouldShowPipelineStatus" :pipeline="pipeline" />
</template>
</vulnerability-report-tabs>
</div>
......
......@@ -33,11 +33,6 @@ export default {
props: {
pipeline: { type: Object, required: true },
},
computed: {
shouldShowPipelineStatus() {
return this.pipeline.createdAt && this.pipeline.id && this.pipeline.path;
},
},
i18n: {
lastUpdated: __('Last updated'),
autoFixSolutions: s__('AutoRemediation|Auto-fix solutions'),
......@@ -47,24 +42,22 @@ export default {
</script>
<template>
<div v-if="shouldShowPipelineStatus">
<div
class="gl-display-flex gl-align-items-center gl-border-solid gl-border-1 gl-border-gray-100 gl-p-6"
>
<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">#{{ 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" class="gl-white-space-nowrap">{{
sprintf($options.i18n.autoFixMrsLink, { mrsCount: autoFixMrsCount })
}}</gl-link>
</div>
<div
class="gl-display-flex gl-align-items-center gl-border-solid gl-border-1 gl-border-gray-100 gl-p-6"
>
<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">#{{ 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" class="gl-white-space-nowrap">{{
sprintf($options.i18n.autoFixMrsLink, { mrsCount: autoFixMrsCount })
}}</gl-link>
</div>
</div>
</template>
......@@ -4,10 +4,11 @@ import {
severityFilter,
activityFilter,
projectFilter,
simpleScannerFilterNoClusterImage,
vendorScannerFilterNoClusterImage,
simpleScannerFilter,
simpleScannerFilterPipeline,
vendorScannerFilter,
} from 'ee/security_dashboard/helpers';
import { REPORT_TYPES_NO_CLUSTER_IMAGE } from 'ee/security_dashboard/store/constants';
import { REPORT_TYPES_WITH_MANUALLY_ADDED } from 'ee/security_dashboard/store/constants';
import { REPORT_TYPE_CLUSTER_IMAGE_SCANNING } from '~/vue_shared/security_reports/constants';
export const REPORT_TAB = {
......@@ -75,8 +76,9 @@ export const FILTERS = {
STATUS: stateFilter,
SEVERITY: severityFilter,
ACTIVITY: activityFilter,
TOOL_SIMPLE: simpleScannerFilterNoClusterImage,
TOOL_VENDOR: vendorScannerFilterNoClusterImage,
TOOL_SIMPLE: simpleScannerFilter,
TOOL_VENDOR: vendorScannerFilter,
TOOL_PIPELINE: simpleScannerFilterPipeline,
PROJECT: projectFilter,
};
......@@ -98,6 +100,6 @@ export const FILTER_PRESETS = {
};
export const REPORT_TYPE_PRESETS = {
DEVELOPMENT: Object.keys(REPORT_TYPES_NO_CLUSTER_IMAGE).map((type) => type.toUpperCase()),
DEVELOPMENT: Object.keys(REPORT_TYPES_WITH_MANUALLY_ADDED).map((type) => type.toUpperCase()),
OPERATIONAL: [REPORT_TYPE_CLUSTER_IMAGE_SCANNING.toUpperCase()],
};
import isPlainObject from 'lodash/isPlainObject';
import {
REPORT_TYPES,
REPORT_TYPES_NO_CLUSTER_IMAGE,
REPORT_TYPES_WITH_MANUALLY_ADDED,
REPORT_TYPES_WITH_CLUSTER_IMAGE,
REPORT_TYPES_ALL,
SEVERITY_LEVELS,
} from 'ee/security_dashboard/store/constants';
import convertReportType from 'ee/vue_shared/security_reports/store/utils/convert_report_type';
......@@ -51,15 +52,15 @@ export const createScannerOption = (vendor, reportType) => {
export const simpleScannerFilter = {
name: toolName,
id: 'reportType',
options: parseOptions(REPORT_TYPES),
options: parseOptions(REPORT_TYPES_WITH_MANUALLY_ADDED),
allOption: { name: allToolsName },
defaultOptions: [],
};
export const simpleScannerFilterNoClusterImage = {
export const simpleScannerFilterPipeline = {
name: toolName,
id: 'reportType',
options: parseOptions(REPORT_TYPES_NO_CLUSTER_IMAGE),
options: parseOptions(REPORT_TYPES_WITH_CLUSTER_IMAGE),
allOption: { name: allToolsName },
defaultOptions: [],
};
......@@ -69,15 +70,7 @@ export const simpleScannerFilterNoClusterImage = {
export const vendorScannerFilter = {
name: toolName,
id: 'scanner',
options: Object.keys(REPORT_TYPES).map((x) => createScannerOption(DEFAULT_SCANNER, x)),
allOption: { name: allToolsName },
defaultOptions: [],
};
export const vendorScannerFilterNoClusterImage = {
name: toolName,
id: 'scanner',
options: Object.keys(REPORT_TYPES_NO_CLUSTER_IMAGE).map((x) =>
options: Object.keys(REPORT_TYPES_WITH_MANUALLY_ADDED).map((x) =>
createScannerOption(DEFAULT_SCANNER, x),
),
allOption: { name: allToolsName },
......@@ -146,7 +139,7 @@ export const getFormattedSummary = (rawSummary = {}) => {
);
// Replace keys with translations found in REPORT_TYPES if available
const formattedEntries = withoutEmptyEntries.map(([scanType, scanSummary]) => {
const name = REPORT_TYPES[scanType];
const name = REPORT_TYPES_ALL[scanType];
return name ? [name, scanSummary] : null;
});
// Filter out keys that could not be matched with any translation and are thus considered invalid
......
......@@ -19,7 +19,7 @@ export const SEVERITY_LEVELS = {
info: s__('severity|Info'),
};
export const REPORT_TYPES_NO_CLUSTER_IMAGE = {
export const REPORT_TYPES_DEFAULT = {
container_scanning: s__('ciReport|Container Scanning'),
dast: s__('ciReport|DAST'),
dependency_scanning: s__('ciReport|Dependency Scanning'),
......@@ -29,10 +29,22 @@ export const REPORT_TYPES_NO_CLUSTER_IMAGE = {
api_fuzzing: s__('ciReport|API Fuzzing'),
};
export const REPORT_TYPES = {
...REPORT_TYPES_NO_CLUSTER_IMAGE,
export const REPORT_TYPES_WITH_CLUSTER_IMAGE = {
...REPORT_TYPES_DEFAULT,
cluster_image_scanning: s__('ciReport|Cluster Image Scanning'),
};
export const REPORT_TYPES_WITH_MANUALLY_ADDED = {
...REPORT_TYPES_DEFAULT,
generic: s__('ciReport|Manually Added'),
};
export const REPORT_TYPES_ALL = {
...REPORT_TYPES_DEFAULT,
...REPORT_TYPES_WITH_CLUSTER_IMAGE,
...REPORT_TYPES_WITH_MANUALLY_ADDED,
};
export const DASHBOARD_TYPES = {
PROJECT: 'project',
PIPELINE: 'pipeline',
......
<script>
import { GlSprintf, GlForm, GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui';
import { s__ } from '~/locale';
import {
REPORT_TYPES_NO_CLUSTER_IMAGE,
SEVERITY_LEVELS,
} from 'ee/security_dashboard/store/constants';
import { REPORT_TYPES_DEFAULT, SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
import ProtectedBranchesSelector from 'ee/vue_shared/components/branches_selector/protected_branches_selector.vue';
import PolicyRuleMultiSelect from 'ee/threat_monitoring/components/policy_rule_multi_select.vue';
import { APPROVAL_VULNERABILITY_STATES } from 'ee/approvals/constants';
......@@ -31,7 +28,7 @@ export default {
},
data() {
return {
reportTypesKeys: Object.keys(REPORT_TYPES_NO_CLUSTER_IMAGE),
reportTypesKeys: Object.keys(REPORT_TYPES_DEFAULT),
};
},
computed: {
......@@ -82,7 +79,7 @@ export default {
this.$emit('changed', { ...this.initRule, ...value });
},
},
REPORT_TYPES_NO_CLUSTER_IMAGE,
REPORT_TYPES_DEFAULT,
SEVERITY_LEVELS,
APPROVAL_VULNERABILITY_STATES,
i18n: {
......@@ -108,7 +105,7 @@ export default {
v-model="scannersToAdd"
class="gl-mr-3"
:item-type-name="$options.i18n.scanners"
:items="$options.REPORT_TYPES_NO_CLUSTER_IMAGE"
:items="$options.REPORT_TYPES_DEFAULT"
data-testid="scanners-select"
/>
</template>
......
<script>
import { GlFriendlyWrap, GlLink, GlBadge, GlSafeHtmlDirective } from '@gitlab/ui';
import { REPORT_TYPES } from 'ee/security_dashboard/store/constants';
import { REPORT_TYPES_ALL } from 'ee/security_dashboard/store/constants';
import FalsePositiveAlert from 'ee/vulnerabilities/components/false_positive_alert.vue';
import GenericReportSection from 'ee/vulnerabilities/components/generic_report/report_section.vue';
import {
......@@ -117,7 +117,7 @@ export default {
return this.vulnerability.response?.status_code;
},
scannerType() {
return REPORT_TYPES[this.vulnerability.report_type];
return REPORT_TYPES_ALL[this.vulnerability.report_type];
},
scannerUrl() {
return this.vulnerability.scanner?.url || '';
......
import { REPORT_TYPES } from 'ee/security_dashboard/store/constants';
import { REPORT_TYPES_ALL } from 'ee/security_dashboard/store/constants';
import { humanize } from '~/lib/utils/text_utility';
/**
......@@ -9,7 +9,7 @@ import { humanize } from '~/lib/utils/text_utility';
const convertReportType = (reportType) => {
if (!reportType) return '';
const lowerCaseType = reportType.toLowerCase();
return REPORT_TYPES[lowerCaseType] || humanize(lowerCaseType);
return REPORT_TYPES_ALL[lowerCaseType] || humanize(lowerCaseType);
};
export default convertReportType;
......@@ -179,6 +179,7 @@ module EE
survey_request_svg_path: image_path('illustrations/security-dashboard_empty.svg'),
security_dashboard_help_path: help_page_path('user/application_security/security_dashboard/index'),
no_vulnerabilities_svg_path: image_path('illustrations/issues.svg'),
dashboard_documentation: help_page_path('user/application_security/security_dashboard/index'),
project_full_path: project.full_path,
security_configuration_path: project_security_configuration_path(@project)
}.merge!(security_dashboard_pipeline_data(project))
......
......@@ -4,10 +4,7 @@ import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ApproversList from 'ee/approvals/components/approvers_list.vue';
import ApproversSelect from 'ee/approvals/components/approvers_select.vue';
import RuleForm, {
READONLY_NAMES,
EXCLUDED_REPORT_TYPE,
} from 'ee/approvals/components/rule_form.vue';
import RuleForm, { READONLY_NAMES } from 'ee/approvals/components/rule_form.vue';
import {
TYPE_USER,
TYPE_GROUP,
......@@ -18,7 +15,7 @@ import {
} from 'ee/approvals/constants';
import { createStoreOptions } from 'ee/approvals/stores';
import projectSettingsModule from 'ee/approvals/stores/modules/project_settings';
import { REPORT_TYPES } from 'ee/security_dashboard/store/constants';
import { REPORT_TYPES_DEFAULT } from 'ee/security_dashboard/store/constants';
import ProtectedBranchesSelector from 'ee/vue_shared/components/branches_selector/protected_branches_selector.vue';
import { stubComponent } from 'helpers/stub_component';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
......@@ -652,15 +649,6 @@ describe('EE Approvals RuleForm', () => {
findAllScannersSelected().trigger('click');
findForm().trigger('submit');
});
it(`dispatches the action on submit without including ${EXCLUDED_REPORT_TYPE}`, () => {
const reportTypesKeys = Object.keys(REPORT_TYPES);
const expectedScanners = reportTypesKeys.filter((item) => item !== EXCLUDED_REPORT_TYPE);
expect(actions.postRule).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ scanners: expectedScanners }),
);
});
});
describe('with invalid number of vulnerabilities', () => {
......@@ -695,8 +683,7 @@ describe('EE Approvals RuleForm', () => {
});
it('contains the supported report types and select all option', () => {
const supportedReportsPlusAll =
Object.keys(REPORT_TYPES).length - [EXCLUDED_REPORT_TYPE].length + 1;
const supportedReportsPlusAll = Object.keys(REPORT_TYPES_DEFAULT).length + 1;
expect(findScannersGroup().findAllComponents(GlTruncate)).toHaveLength(
supportedReportsPlusAll,
);
......
......@@ -23,6 +23,8 @@ Vue.use(VueApollo);
Vue.use(VueRouter);
const router = new VueRouter();
const examplePipeline = { id: 1, createdAt: 'now', path: 'path' };
describe('Project vulnerability report app component', () => {
useLocalStorageSpy();
......@@ -39,7 +41,8 @@ describe('Project vulnerability report app component', () => {
});
const createWrapper = ({
pipeline = { id: 1 },
pipeline = examplePipeline,
hasVulnerabilities = true,
securityScanners,
securityAutoFix = false,
secureVulnerabilityTraining = true,
......@@ -53,6 +56,7 @@ describe('Project vulnerability report app component', () => {
fullPath: '#',
autoFixDocumentation: '#',
pipeline,
hasVulnerabilities,
dashboardType: DASHBOARD_TYPES.PROJECT,
glFeatures: { securityAutoFix, secureVulnerabilityTraining },
},
......@@ -74,20 +78,40 @@ describe('Project vulnerability report app component', () => {
});
describe('report not configured component', () => {
it('shows the report not configured component if there are no projects', () => {
createWrapper({ pipeline: null });
it('shows the not configured component if there is no pipeline and no vulnerabilities', () => {
createWrapper({ pipeline: {}, hasVulnerabilities: false });
expect(findReportNotConfiguredProject().exists()).toBe(true);
expect(findVulnerabilityReportTabs().exists()).toBe(false);
});
it('shows the vulnerability report tabs and project pipeline status components if there are projects', () => {
const pipeline = { id: 1 };
createWrapper({ pipeline });
it.each`
pipeline | hasVulnerabilities
${examplePipeline} | ${false}
${{}} | ${true}
${examplePipeline} | ${true}
`(
'shows the report if pipeline is $pipeline and hasVulnerabilities is $hasVulnerabilities',
({ pipeline, hasVulnerabilities }) => {
createWrapper({ pipeline, hasVulnerabilities });
expect(findReportNotConfiguredProject().exists()).toBe(false);
expect(findVulnerabilityReportTabs().exists()).toBe(true);
},
);
});
describe('project pipeline status component', () => {
it('shows the component if there is a pipeline', () => {
createWrapper();
expect(findProjectPipelineStatus().props('pipeline')).toBe(examplePipeline);
});
it('does not show the component if there is no pipeline', () => {
createWrapper({ pipeline: {} });
expect(findReportNotConfiguredProject().exists()).toBe(false);
expect(findVulnerabilityReportTabs().exists()).toBe(true);
expect(findProjectPipelineStatus().props('pipeline')).toBe(pipeline);
expect(findProjectPipelineStatus().exists()).toBe(false);
});
});
......
......@@ -6,7 +6,7 @@ import VueRouter from 'vue-router';
import FilterItem from 'ee/security_dashboard/components/shared/filters/filter_item.vue';
import ScannerFilter from 'ee/security_dashboard/components/shared/filters/scanner_filter.vue';
import { DEFAULT_SCANNER, SCANNER_ID_PREFIX } from 'ee/security_dashboard/constants';
import { vendorScannerFilterNoClusterImage } from 'ee/security_dashboard/helpers';
import { vendorScannerFilter } from 'ee/security_dashboard/helpers';
Vue.use(VueRouter);
const router = new VueRouter();
......@@ -41,7 +41,7 @@ describe('Scanner Filter component', () => {
let filter;
const createWrapper = ({ scanners = customScanners } = {}) => {
filter = cloneDeep(vendorScannerFilterNoClusterImage);
filter = cloneDeep(vendorScannerFilter);
wrapper = shallowMount(ScannerFilter, {
router,
......@@ -124,7 +124,7 @@ describe('Scanner Filter component', () => {
it('emits filter-changed event with expected data for selected options', async () => {
const ids = ['GitLab.SAST', 'Custom.SAST', 'GitLab.API_FUZZING', 'GitLab.COVERAGE_FUZZING'];
router.replace({ query: { [vendorScannerFilterNoClusterImage.id]: ids } });
router.replace({ query: { [vendorScannerFilter.id]: ids } });
const selectedScanners = customScanners.filter((x) =>
ids.includes(`${x.vendor}.${x.report_type}`),
);
......
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { merge } from 'lodash';
import PipelineStatusBadge from 'ee/security_dashboard/components/shared/pipeline_status_badge.vue';
import ProjectPipelineStatus from 'ee/security_dashboard/components/shared/project_pipeline_status.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
const defaultPipeline = {
createdAt: '2020-10-06T20:08:07Z',
id: '214',
path: '/mixed-vulnerabilities/dependency-list-test-01/-/pipelines/214',
};
describe('Project Pipeline Status Component', () => {
let wrapper;
const DEFAULT_PROPS = {
pipeline: {
createdAt: '2020-10-06T20:08:07Z',
id: '214',
path: '/mixed-vulnerabilities/dependency-list-test-01/-/pipelines/214',
},
};
const findPipelineStatusBadge = () => wrapper.findComponent(PipelineStatusBadge);
const findTimeAgoTooltip = () => wrapper.findComponent(TimeAgoTooltip);
const findLink = () => wrapper.findComponent(GlLink);
......@@ -24,30 +21,25 @@ describe('Project Pipeline Status Component', () => {
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,
),
),
shallowMount(ProjectPipelineStatus, {
propsData: {
pipeline: defaultPipeline,
},
provide: {
projectFullPath: '/group/project',
glFeatures: { securityAutoFix: true },
autoFixMrsPath: '/merge_requests?label_name=GitLab-auto-fix',
},
data() {
return { autoFixMrsCount: 0 };
},
...options,
}),
);
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('default state', () => {
......@@ -59,7 +51,7 @@ describe('Project Pipeline Status Component', () => {
const TimeComponent = findTimeAgoTooltip();
expect(TimeComponent.exists()).toBeTruthy();
expect(TimeComponent.props()).toStrictEqual({
time: DEFAULT_PROPS.pipeline.createdAt,
time: defaultPipeline.createdAt,
cssClass: '',
tooltipPlacement: 'top',
});
......@@ -68,20 +60,12 @@ describe('Project Pipeline Status Component', () => {
it('should show the link component', () => {
const GlLinkComponent = findLink();
expect(GlLinkComponent.exists()).toBeTruthy();
expect(GlLinkComponent.text()).toBe(`#${DEFAULT_PROPS.pipeline.id}`);
expect(GlLinkComponent.attributes('href')).toBe(DEFAULT_PROPS.pipeline.path);
});
});
describe('when no pipeline has run', () => {
beforeEach(() => {
wrapper = createWrapper({ propsData: { pipeline: { path: '' } } });
expect(GlLinkComponent.text()).toBe(`#${defaultPipeline.id}`);
expect(GlLinkComponent.attributes('href')).toBe(defaultPipeline.path);
});
it('should not show the project_pipeline_status component', () => {
expect(findLink().exists()).toBe(false);
expect(findTimeAgoTooltip().exists()).toBe(false);
expect(findPipelineStatusBadge().exists()).toBe(false);
it('should show the pipeline status badge component', () => {
expect(findPipelineStatusBadge().props('pipeline')).toBe(defaultPipeline);
});
});
......
......@@ -196,6 +196,7 @@ RSpec.describe ProjectsHelper do
security_dashboard_help_path: '/help/user/application_security/security_dashboard/index',
project_full_path: project.full_path,
no_vulnerabilities_svg_path: start_with('/assets/illustrations/issues-'),
dashboard_documentation: '/help/user/application_security/security_dashboard/index',
security_configuration_path: end_with('/configuration')
}
end
......
......@@ -43806,6 +43806,9 @@ msgstr ""
msgid "ciReport|Manage licenses"
msgstr ""
msgid "ciReport|Manually Added"
msgstr ""
msgid "ciReport|New"
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