Commit 23750887 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '229661-use-new-scanner-search-graphql' into 'master'

Use GraphQL scannerId property for vulnerability report scanner filter

See merge request gitlab-org/gitlab!60556
parents 024ebc76 20e5de5f
<script> <script>
import { GlDropdownDivider, GlDropdownItem, GlTruncate } from '@gitlab/ui'; import { GlDropdownDivider, GlDropdownItem, GlTruncate } from '@gitlab/ui';
import { union, uniq, without, get, set, keyBy } from 'lodash'; import { union, without, get, set, keyBy } from 'lodash';
import { DEFAULT_SCANNER } from 'ee/security_dashboard/constants'; import { DEFAULT_SCANNER, SCANNER_ID_PREFIX } from 'ee/security_dashboard/constants';
import { createScannerOption } from '../../helpers'; import { createScannerOption } from '../../helpers';
import FilterBody from './filter_body.vue'; import FilterBody from './filter_body.vue';
import FilterItem from './filter_item.vue'; import FilterItem from './filter_item.vue';
...@@ -29,7 +29,7 @@ export default { ...@@ -29,7 +29,7 @@ export default {
* id: 'used for querystring', * id: 'used for querystring',
* reportType: 'used for GraphQL', * reportType: 'used for GraphQL',
* name: 'used for Vue template', * name: 'used for Vue template',
* externalIds: ['used', 'for', 'GraphQL'], * scannerIds: ['used', 'for', 'GraphQL'],
* }, * },
* $category: { ... } * $category: { ... }
* }, * },
...@@ -43,8 +43,8 @@ export default { ...@@ -43,8 +43,8 @@ export default {
* parent keys are used for O(1) lookups so we can assign the entries in the scanners array to * parent keys are used for O(1) lookups so we can assign the entries in the scanners array to
* the correct category object: * the correct category object:
* *
* const scanners = [{ vendor: 'GitLab', report_type: 'SAST', external_id: 'eslint'}] * const scanners = [{ vendor: 'GitLab', report_type: 'SAST', id: 123}]
* this.groups.GitLab.SAST.externalIds.push(scanner[0].external_id) * this.groups.GitLab.SAST.scannerIds.push(scanner[0].id)
* *
* In the template, we use Object.entries() and Object.values() on this computed property to * In the template, we use Object.entries() and Object.values() on this computed property to
* render the hierarchical options. * render the hierarchical options.
...@@ -63,17 +63,15 @@ export default { ...@@ -63,17 +63,15 @@ export default {
set(groups, id, createScannerOption(vendor, reportType)); set(groups, id, createScannerOption(vendor, reportType));
} }
// Add the external ID to the group's report type. // Add the scanner ID to the group's report type.
groups[vendor][reportType].externalIds.push(scanner.external_id); groups[vendor][reportType].scannerIds.push(scanner.id);
}); });
return groups; return groups;
}, },
filterObject() { filterObject() {
const reportType = uniq(this.selectedOptions.map((x) => x.reportType)); const ids = this.selectedOptions.flatMap((x) => x.scannerIds);
const scanner = uniq(this.selectedOptions.flatMap((x) => x.externalIds)); return { scannerId: ids.map((x) => `${SCANNER_ID_PREFIX}${x}`) };
return { reportType, scanner };
}, },
}, },
methods: { methods: {
......
...@@ -7,3 +7,4 @@ export const SURVEY_BANNER_LOCAL_STORAGE_KEY = 'vulnerability_management_survey_ ...@@ -7,3 +7,4 @@ export const SURVEY_BANNER_LOCAL_STORAGE_KEY = 'vulnerability_management_survey_
export const SURVEY_BANNER_CURRENT_ID = 'survey1'; export const SURVEY_BANNER_CURRENT_ID = 'survey1';
export const DEFAULT_SCANNER = 'GitLab'; export const DEFAULT_SCANNER = 'GitLab';
export const SCANNER_ID_PREFIX = 'gid://gitlab/Vulnerabilities::Scanner/';
#import "~/graphql_shared/fragments/pageInfoCursorsOnly.fragment.graphql" #import "~/graphql_shared/fragments/pageInfoCursorsOnly.fragment.graphql"
#import "../fragments/vulnerability.fragment.graphql" #import "../fragments/vulnerability.fragment.graphql"
query group( query groupVulnerabilities(
$fullPath: ID! $fullPath: ID!
$after: String $after: String
$first: Int $first: Int
...@@ -9,7 +9,7 @@ query group( ...@@ -9,7 +9,7 @@ query group(
$severity: [VulnerabilitySeverity!] $severity: [VulnerabilitySeverity!]
$reportType: [VulnerabilityReportType!] $reportType: [VulnerabilityReportType!]
$scanner: [String!] $scanner: [String!]
$scannerId: [ID!] $scannerId: [VulnerabilitiesScannerID!]
$state: [VulnerabilityState!] $state: [VulnerabilityState!]
$sort: VulnerabilitySort $sort: VulnerabilitySort
$hasIssues: Boolean $hasIssues: Boolean
......
...@@ -8,7 +8,7 @@ query instanceVulnerabilities( ...@@ -8,7 +8,7 @@ query instanceVulnerabilities(
$severity: [VulnerabilitySeverity!] $severity: [VulnerabilitySeverity!]
$reportType: [VulnerabilityReportType!] $reportType: [VulnerabilityReportType!]
$scanner: [String!] $scanner: [String!]
$scannerId: [ID!] $scannerId: [VulnerabilitiesScannerID!]
$state: [VulnerabilityState!] $state: [VulnerabilityState!]
$sort: VulnerabilitySort $sort: VulnerabilitySort
$hasIssues: Boolean $hasIssues: Boolean
......
...@@ -8,7 +8,7 @@ query projectVulnerabilities( ...@@ -8,7 +8,7 @@ query projectVulnerabilities(
$severity: [VulnerabilitySeverity!] $severity: [VulnerabilitySeverity!]
$reportType: [VulnerabilityReportType!] $reportType: [VulnerabilityReportType!]
$scanner: [String!] $scanner: [String!]
$scannerId: [ID!] $scannerId: [VulnerabilitiesScannerID!]
$state: [VulnerabilityState!] $state: [VulnerabilityState!]
$sort: VulnerabilitySort $sort: VulnerabilitySort
$hasIssues: Boolean $hasIssues: Boolean
......
...@@ -39,13 +39,13 @@ export const createScannerOption = (vendor, reportType) => { ...@@ -39,13 +39,13 @@ export const createScannerOption = (vendor, reportType) => {
id: gon.features?.customSecurityScanners ? `${vendor}.${type}` : type, id: gon.features?.customSecurityScanners ? `${vendor}.${type}` : type,
reportType: reportType.toUpperCase(), reportType: reportType.toUpperCase(),
name: convertReportType(reportType), name: convertReportType(reportType),
externalIds: [], scannerIds: [],
}; };
}; };
export const scannerFilter = { export const scannerFilter = {
name: s__('SecurityReports|Scanner'), name: s__('SecurityReports|Scanner'),
id: 'reportType', id: gon.features?.customSecurityScanners ? 'scanner' : 'reportType',
options: Object.keys(REPORT_TYPES).map((x) => createScannerOption(DEFAULT_SCANNER, x)), options: Object.keys(REPORT_TYPES).map((x) => createScannerOption(DEFAULT_SCANNER, x)),
allOption: BASE_FILTERS.report_type, allOption: BASE_FILTERS.report_type,
defaultOptions: [], defaultOptions: [],
......
...@@ -12,7 +12,7 @@ module VulnerabilityScanners ...@@ -12,7 +12,7 @@ module VulnerabilityScanners
.with_report_type .with_report_type
.map do |scanner| .map do |scanner|
{ {
external_id: scanner.external_id, id: scanner.id,
vendor: scanner.vendor, vendor: scanner.vendor,
report_type: ::Enums::Vulnerability.report_types.key(scanner.report_type).upcase report_type: ::Enums::Vulnerability.report_types.key(scanner.report_type).upcase
} }
......
import { GlDropdownItem } from '@gitlab/ui'; import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { sampleSize, cloneDeep } from 'lodash'; import { sampleSize, cloneDeep } from 'lodash';
import VueRouter from 'vue-router';
import FilterItem from 'ee/security_dashboard/components/filters/filter_item.vue'; import FilterItem from 'ee/security_dashboard/components/filters/filter_item.vue';
import ScannerFilter from 'ee/security_dashboard/components/filters/scanner_filter.vue'; import ScannerFilter from 'ee/security_dashboard/components/filters/scanner_filter.vue';
import { DEFAULT_SCANNER } from 'ee/security_dashboard/constants'; import { DEFAULT_SCANNER, SCANNER_ID_PREFIX } from 'ee/security_dashboard/constants';
import { scannerFilter } from 'ee/security_dashboard/helpers'; import { scannerFilter } from 'ee/security_dashboard/helpers';
const filter = cloneDeep(scannerFilter); const localVue = createLocalVue();
filter.options = filter.options.map((option) => ({ localVue.use(VueRouter);
...option, const router = new VueRouter();
id: `GitLab.${option.id}`,
}));
const createScannerConfig = (vendor, reportType, externalId) => ({ const createScannerConfig = (vendor, reportType, id) => ({
vendor, vendor,
report_type: reportType, report_type: reportType,
external_id: externalId, id,
}); });
const scanners = [ const scanners = [
createScannerConfig(DEFAULT_SCANNER, 'DEPENDENCY_SCANNING', 'bundler_audit'), createScannerConfig(DEFAULT_SCANNER, 'DEPENDENCY_SCANNING', 1),
createScannerConfig(DEFAULT_SCANNER, 'SAST', 'eslint'), createScannerConfig(DEFAULT_SCANNER, 'DEPENDENCY_SCANNING', 2),
createScannerConfig(DEFAULT_SCANNER, 'SAST', 'find_sec_bugs'), createScannerConfig(DEFAULT_SCANNER, 'SAST', 3),
createScannerConfig(DEFAULT_SCANNER, 'DEPENDENCY_SCANNING', 'gemnasium'), createScannerConfig(DEFAULT_SCANNER, 'SAST', 4),
createScannerConfig(DEFAULT_SCANNER, 'SECRET_DETECTION', 'gitleaks'), createScannerConfig(DEFAULT_SCANNER, 'SECRET_DETECTION', 5),
createScannerConfig(DEFAULT_SCANNER, 'CONTAINER_SCANNING', 'klar'), createScannerConfig(DEFAULT_SCANNER, 'CONTAINER_SCANNING', 6),
createScannerConfig(DEFAULT_SCANNER, 'COVERAGE_FUZZING', 'libfuzzer'), createScannerConfig(DEFAULT_SCANNER, 'COVERAGE_FUZZING', 7),
createScannerConfig(DEFAULT_SCANNER, 'SAST', 'pmd-apex'), createScannerConfig(DEFAULT_SCANNER, 'DAST', 8),
createScannerConfig(DEFAULT_SCANNER, 'SAST', 'sobelow'), createScannerConfig(DEFAULT_SCANNER, 'DAST', 9),
createScannerConfig(DEFAULT_SCANNER, 'SAST', 'tslint'), createScannerConfig('Custom', 'SAST', 10),
createScannerConfig(DEFAULT_SCANNER, 'DAST', 'zaproxy'), createScannerConfig('Custom', 'SAST', 11),
createScannerConfig('Custom', 'SAST', 'custom1'), createScannerConfig('Custom', 'DAST', 12),
createScannerConfig('Custom', 'SAST', 'custom2'),
createScannerConfig('Custom', 'DAST', 'custom3'),
]; ];
describe('Scanner Filter component', () => { describe('Scanner Filter component', () => {
let wrapper; let wrapper;
let filter;
const createWrapper = () => { const createWrapper = () => {
filter = cloneDeep(scannerFilter);
filter.options = filter.options.map((option) => ({
...option,
id: `GitLab.${option.id}`,
}));
wrapper = shallowMount(ScannerFilter, { wrapper = shallowMount(ScannerFilter, {
localVue,
router,
propsData: { filter }, propsData: { filter },
provide: { scanners }, provide: { scanners },
}); });
...@@ -47,14 +53,18 @@ describe('Scanner Filter component', () => { ...@@ -47,14 +53,18 @@ describe('Scanner Filter component', () => {
beforeEach(() => { beforeEach(() => {
gon.features = { customSecurityScanners: true }; gon.features = { customSecurityScanners: true };
createWrapper();
}); });
afterEach(() => { afterEach(() => {
// Clear out the querystring if one exists. It persists between tests.
if (wrapper.vm.$route.query[filter.id]) {
wrapper.vm.$router.push('/');
}
wrapper.destroy(); wrapper.destroy();
}); });
it('shows the correct dropdown items', () => { it('shows the correct dropdown items', () => {
createWrapper();
const getTestIds = (selector) => const getTestIds = (selector) =>
wrapper.findAll(selector).wrappers.map((x) => x.attributes('data-testid')); wrapper.findAll(selector).wrappers.map((x) => x.attributes('data-testid'));
...@@ -74,6 +84,7 @@ describe('Scanner Filter component', () => { ...@@ -74,6 +84,7 @@ describe('Scanner Filter component', () => {
}); });
it('toggles selection of all items in a group when the group header is clicked', async () => { it('toggles selection of all items in a group when the group header is clicked', async () => {
createWrapper();
const expectSelectedItems = (items) => { const expectSelectedItems = (items) => {
const checkedItems = wrapper const checkedItems = wrapper
.findAll(FilterItem) .findAll(FilterItem)
...@@ -100,14 +111,15 @@ describe('Scanner Filter component', () => { ...@@ -100,14 +111,15 @@ describe('Scanner Filter component', () => {
await clickAndCheck(filter.options); // Third click selects all again. await clickAndCheck(filter.options); // Third click selects all again.
}); });
it('emits filter-changed event with expected data when selected options is changed', async () => { it('emits filter-changed event with expected data for selected options', async () => {
const selectedIds = ['GitLab.SAST', 'Custom.SAST']; const ids = ['GitLab.SAST', 'Custom.SAST'];
const selectedOptions = wrapper.vm.options.filter((x) => selectedIds.includes(x.id)); router.replace({ query: { [filter.id]: ids } });
await wrapper.setData({ selectedOptions }); const selectedScanners = scanners.filter((x) => ids.includes(`${x.vendor}.${x.report_type}`));
createWrapper();
await wrapper.vm.$nextTick();
expect(wrapper.emitted('filter-changed')[1][0]).toEqual({ expect(wrapper.emitted('filter-changed')[0][0]).toEqual({
reportType: ['SAST'], scannerId: selectedScanners.map((x) => `${SCANNER_ID_PREFIX}${x.id}`),
scanner: scanners.filter((x) => x.report_type === 'SAST').map((x) => x.external_id),
}); });
}); });
}); });
...@@ -163,13 +163,13 @@ RSpec.describe ProjectsHelper do ...@@ -163,13 +163,13 @@ RSpec.describe ProjectsHelper do
no_pipeline_run_scanners_help_path: "/#{project.full_path}/-/pipelines/new", no_pipeline_run_scanners_help_path: "/#{project.full_path}/-/pipelines/new",
auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests'), auto_fix_documentation: help_page_path('user/application_security/index', anchor: 'auto-fix-merge-requests'),
auto_fix_mrs_path: end_with('/merge_requests?label_name=GitLab-auto-fix'), auto_fix_mrs_path: end_with('/merge_requests?label_name=GitLab-auto-fix'),
scanners: '[{"external_id":"security_vendor","vendor":"Security Vendor","report_type":"SAST"}]' scanners: '[{"id":123,"vendor":"Security Vendor","report_type":"SAST"}]'
} }
end end
before do before do
create(:vulnerability, project: project) create(:vulnerability, project: project)
scanner = create(:vulnerabilities_scanner, project: project, external_id: 'security_vendor') scanner = create(:vulnerabilities_scanner, project: project, id: 123)
create(:vulnerabilities_finding, project: project, scanner: scanner) create(:vulnerabilities_finding, project: project, scanner: scanner)
end end
......
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe VulnerabilityScanners::ListService do RSpec.describe VulnerabilityScanners::ListService do
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) } let_it_be(:project) { create(:project, group: group) }
let_it_be(:vulnerability_scanner) { create(:vulnerabilities_scanner, project: project, external_id: 'external_vendor') } let_it_be(:vulnerability_scanner) { create(:vulnerabilities_scanner, project: project, id: 123) }
let_it_be(:vulnerability_finding) { create(:vulnerabilities_finding, project: project, scanner: vulnerability_scanner) } let_it_be(:vulnerability_finding) { create(:vulnerabilities_finding, project: project, scanner: vulnerability_scanner) }
let(:service) { described_class.new(vulnerable) } let(:service) { described_class.new(vulnerable) }
...@@ -15,12 +15,12 @@ RSpec.describe VulnerabilityScanners::ListService do ...@@ -15,12 +15,12 @@ RSpec.describe VulnerabilityScanners::ListService do
context 'when looking for scanners for group' do context 'when looking for scanners for group' do
let(:vulnerable) { group } let(:vulnerable) { group }
it { is_expected.to eq([{ external_id: "external_vendor", vendor: "Security Vendor", report_type: "SAST" }]) } it { is_expected.to eq([{ id: 123, vendor: "Security Vendor", report_type: "SAST" }]) }
end end
context 'when looking for scanners for project' do context 'when looking for scanners for project' do
let(:vulnerable) { project } let(:vulnerable) { project }
it { is_expected.to eq([{ external_id: "external_vendor", vendor: "Security Vendor", report_type: "SAST" }]) } it { is_expected.to eq([{ id: 123, vendor: "Security Vendor", report_type: "SAST" }]) }
end end
end end
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