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