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