Skip to content

Commit 8184faa

Browse files
committed
CM-44175 - Add tree view filtering by severity
1 parent 8a0c342 commit 8184faa

File tree

12 files changed

+325
-5
lines changed

12 files changed

+325
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44

55
- Add proper support for disabled modules
6+
- Add tree view filtering by severity
67
- Fix auth required state of status bar
78

89
## [v1.14.0]

package.json

Lines changed: 199 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,56 @@
6363
}
6464
],
6565
"view/item/context": [
66+
{
67+
"command": "cycode.enableFilterByCriticalSeverity",
68+
"when": "viewItem == FilterNode && !cycode:filter.severity.isCriticalEnabled",
69+
"group": "inline@1"
70+
},
71+
{
72+
"command": "cycode.disableFilterByCriticalSeverity",
73+
"when": "viewItem == FilterNode && cycode:filter.severity.isCriticalEnabled",
74+
"group": "inline@1"
75+
},
76+
{
77+
"command": "cycode.enableFilterByHighSeverity",
78+
"when": "viewItem == FilterNode && !cycode:filter.severity.isHighEnabled",
79+
"group": "inline@2"
80+
},
81+
{
82+
"command": "cycode.disableFilterByHighSeverity",
83+
"when": "viewItem == FilterNode && cycode:filter.severity.isHighEnabled",
84+
"group": "inline@2"
85+
},
86+
{
87+
"command": "cycode.enableFilterByMediumSeverity",
88+
"when": "viewItem == FilterNode && !cycode:filter.severity.isMediumEnabled",
89+
"group": "inline@3"
90+
},
91+
{
92+
"command": "cycode.disableFilterByMediumSeverity",
93+
"when": "viewItem == FilterNode && cycode:filter.severity.isMediumEnabled",
94+
"group": "inline@3"
95+
},
96+
{
97+
"command": "cycode.enableFilterByLowSeverity",
98+
"when": "viewItem == FilterNode && !cycode:filter.severity.isLowEnabled",
99+
"group": "inline@4"
100+
},
101+
{
102+
"command": "cycode.disableFilterByLowSeverity",
103+
"when": "viewItem == FilterNode && cycode:filter.severity.isLowEnabled",
104+
"group": "inline@4"
105+
},
106+
{
107+
"command": "cycode.enableFilterByInfoSeverity",
108+
"when": "viewItem == FilterNode && !cycode:filter.severity.isInfoEnabled",
109+
"group": "inline@5"
110+
},
111+
{
112+
"command": "cycode.disableFilterByInfoSeverity",
113+
"when": "viewItem == FilterNode && cycode:filter.severity.isInfoEnabled",
114+
"group": "inline@5"
115+
},
66116
{
67117
"command": "cycode.secretScanForProject",
68118
"when": "viewItem == secretScanTypeNode && cycode:modules.isSecretScanningEnabled",
@@ -99,6 +149,64 @@
99149
"command": "cycode.sastScanForProject",
100150
"when": "view == cycode.view.tree && viewItem == sastScanTypeNode && cycode:modules.isSastScanningEnabled"
101151
}
152+
],
153+
"commandPalette": [
154+
{
155+
"command": "cycode.secretScanForProject",
156+
"when": "cycode:modules.isSecretScanningEnabled"
157+
},
158+
{
159+
"command": "cycode.iacScanForProject",
160+
"when": "cycode:modules.isIacScanningEnabled"
161+
},
162+
{
163+
"command": "cycode.sastScanForProject",
164+
"when": "cycode:modules.isSastScanningEnabled"
165+
},
166+
{
167+
"command": "cycode.scaScan",
168+
"when": "cycode:modules.isScaScanningEnabled"
169+
},
170+
{
171+
"command": "cycode.enableFilterByCriticalSeverity",
172+
"when": "false"
173+
},
174+
{
175+
"command": "cycode.enableFilterByHighSeverity",
176+
"when": "false"
177+
},
178+
{
179+
"command": "cycode.enableFilterByMediumSeverity",
180+
"when": "false"
181+
},
182+
{
183+
"command": "cycode.enableFilterByLowSeverity",
184+
"when": "false"
185+
},
186+
{
187+
"command": "cycode.enableFilterByInfoSeverity",
188+
"when": "false"
189+
},
190+
{
191+
"command": "cycode.disableFilterByCriticalSeverity",
192+
"when": "false"
193+
},
194+
{
195+
"command": "cycode.disableFilterByHighSeverity",
196+
"when": "false"
197+
},
198+
{
199+
"command": "cycode.disableFilterByMediumSeverity",
200+
"when": "false"
201+
},
202+
{
203+
"command": "cycode.disableFilterByLowSeverity",
204+
"when": "false"
205+
},
206+
{
207+
"command": "cycode.disableFilterByInfoSeverity",
208+
"when": "false"
209+
}
102210
]
103211
},
104212
"configuration": {
@@ -198,6 +306,96 @@
198306
{
199307
"command": "cycode.auth",
200308
"title": "Authenticate with service"
309+
},
310+
{
311+
"command": "cycode.enableFilterByCriticalSeverity",
312+
"category": "Cycode",
313+
"title": "Enable filter by Critical severity",
314+
"icon": {
315+
"light": "resources/severity/Critical.png",
316+
"dark": "resources/severity/Critical.png"
317+
}
318+
},
319+
{
320+
"command": "cycode.enableFilterByHighSeverity",
321+
"category": "Cycode",
322+
"title": "Enable filter by High severity",
323+
"icon": {
324+
"light": "resources/severity/High.png",
325+
"dark": "resources/severity/High.png"
326+
}
327+
},
328+
{
329+
"command": "cycode.enableFilterByMediumSeverity",
330+
"category": "Cycode",
331+
"title": "Enable filter by Medium severity",
332+
"icon": {
333+
"light": "resources/severity/Medium.png",
334+
"dark": "resources/severity/Medium.png"
335+
}
336+
},
337+
{
338+
"command": "cycode.enableFilterByLowSeverity",
339+
"category": "Cycode",
340+
"title": "Enable filter by Low severity",
341+
"icon": {
342+
"light": "resources/severity/Low.png",
343+
"dark": "resources/severity/Low.png"
344+
}
345+
},
346+
{
347+
"command": "cycode.enableFilterByInfoSeverity",
348+
"category": "Cycode",
349+
"title": "Enable filter by Info severity",
350+
"icon": {
351+
"light": "resources/severity/Info.png",
352+
"dark": "resources/severity/Info.png"
353+
}
354+
},
355+
{
356+
"command": "cycode.disableFilterByCriticalSeverity",
357+
"category": "Cycode",
358+
"title": "Disable filter by Critical severity",
359+
"icon": {
360+
"light": "resources/severity/outline/Critical.png",
361+
"dark": "resources/severity/outline/Critical.png"
362+
}
363+
},
364+
{
365+
"command": "cycode.disableFilterByHighSeverity",
366+
"category": "Cycode",
367+
"title": "Disable filter by High severity",
368+
"icon": {
369+
"light": "resources/severity/outline/High.png",
370+
"dark": "resources/severity/outline/High.png"
371+
}
372+
},
373+
{
374+
"command": "cycode.disableFilterByMediumSeverity",
375+
"category": "Cycode",
376+
"title": "Disable filter by Medium severity",
377+
"icon": {
378+
"light": "resources/severity/outline/Medium.png",
379+
"dark": "resources/severity/outline/Medium.png"
380+
}
381+
},
382+
{
383+
"command": "cycode.disableFilterByLowSeverity",
384+
"category": "Cycode",
385+
"title": "Disable filter by Low severity",
386+
"icon": {
387+
"light": "resources/severity/outline/Low.png",
388+
"dark": "resources/severity/outline/Low.png"
389+
}
390+
},
391+
{
392+
"command": "cycode.disableFilterByInfoSeverity",
393+
"category": "Cycode",
394+
"title": "Disable filter by Info severity",
395+
"icon": {
396+
"light": "resources/severity/outline/Info.png",
397+
"dark": "resources/severity/outline/Info.png"
398+
}
201399
}
202400
],
203401
"viewsContainers": {
@@ -246,7 +444,7 @@
246444
},
247445
"icons": {
248446
"cycode-logo": {
249-
"description": "Cycode icon",
447+
"description": "Cycode Logo",
250448
"default": {
251449
"fontPath": "./resources/cycode.woff",
252450
"fontCharacter": "\\E800"
1.8 KB
Loading

resources/severity/outline/High.png

1.88 KB
Loading

resources/severity/outline/Info.png

1.87 KB
Loading

resources/severity/outline/Low.png

1.72 KB
Loading

resources/severity/outline/Medium.png

1.72 KB
Loading

src/commands/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import treeViewExpandAllCommand from './tree-view-expand-all-command';
1616
import treeViewCollapseAllCommand from './tree-view-collapse-all-command';
1717
import runAllScansCommand from './run-all-scans-command';
1818
import clearAllScanResultsCommand from './clear-all-scan-results-command';
19+
import registerTreeViewSeverityFilterCommands from './tree-view-filter-by-severity-command';
1920

2021
export enum VscodeCommands {
2122
SecretScanCommandId = 'cycode.secretScan',
@@ -74,4 +75,6 @@ export const registerCommands = (context: vscode.ExtensionContext): void => {
7475
for (const [commandId, commandCallback] of Object.entries(_VS_CODE_COMMANDS_ID_TO_CALLBACK)) {
7576
context.subscriptions.push(vscode.commands.registerCommand(commandId, commandCallback));
7677
}
78+
79+
registerTreeViewSeverityFilterCommands(context);
7780
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { container } from 'tsyringe';
2+
import { ExtensionServiceSymbol, StateServiceSymbol } from '../symbols';
3+
import vscode from 'vscode';
4+
import { IStateService } from '../services/state-service';
5+
import { IExtensionService } from '../services/extension-service';
6+
7+
const _SEVERITY_NAMES: readonly string[] = ['Critical', 'High', 'Medium', 'Low', 'Info'];
8+
9+
const treeViewFilterBySeverityCallback = (severityName: string, enabled: boolean) => {
10+
const extension = container.resolve<IExtensionService>(ExtensionServiceSymbol);
11+
const stateService = container.resolve<IStateService>(StateServiceSymbol);
12+
const tempState = stateService.tempState;
13+
14+
switch (severityName) {
15+
case 'Critical':
16+
tempState.IsTreeViewFilterByCriticalSeverityEnabled = enabled;
17+
break;
18+
case 'High':
19+
tempState.IsTreeViewFilterByHighSeverityEnabled = enabled;
20+
break;
21+
case 'Medium':
22+
tempState.IsTreeViewFilterByMediumSeverityEnabled = enabled;
23+
break;
24+
case 'Low':
25+
tempState.IsTreeViewFilterByLowSeverityEnabled = enabled;
26+
break;
27+
case 'Info':
28+
tempState.IsTreeViewFilterByInfoSeverityEnabled = enabled;
29+
break;
30+
}
31+
32+
stateService.save();
33+
extension.treeDataProvider.refresh();
34+
};
35+
36+
export default (context: vscode.ExtensionContext): void => {
37+
for (const severity of _SEVERITY_NAMES) {
38+
const enableFilterCommandId = `cycode.enableFilterBy${severity}Severity`;
39+
context.subscriptions.push(
40+
vscode.commands.registerCommand(
41+
enableFilterCommandId, () => { treeViewFilterBySeverityCallback(severity, true); },
42+
),
43+
);
44+
45+
const disableFilterCommandId = `cycode.disableFilterBy${severity}Severity`;
46+
context.subscriptions.push(
47+
vscode.commands.registerCommand(
48+
disableFilterCommandId, () => { treeViewFilterBySeverityCallback(severity, false); },
49+
),
50+
);
51+
}
52+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { BaseNode } from './base-node';
2+
import { ThemeIcon } from 'vscode';
3+
import vscode from 'vscode';
4+
5+
export class FilterNode extends BaseNode {
6+
constructor() {
7+
const title = 'Filters';
8+
const summary = '(hover to view)';
9+
super(title, summary);
10+
11+
this.contextValue = `FilterNode`;
12+
13+
this.iconPath = new ThemeIcon('filter');
14+
this.collapsibleState = vscode.TreeItemCollapsibleState.None;
15+
}
16+
}

src/providers/tree-data/provider.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import { FileNode } from './nodes/file-node';
66
import { DetectionNode } from './nodes/detection-node';
77
import { container } from 'tsyringe';
88
import { IScanResultsService } from '../../services/scan-results-service';
9-
import { ScanResultsServiceSymbol } from '../../symbols';
9+
import { ScanResultsServiceSymbol, StateServiceSymbol } from '../../symbols';
1010
import { DetectionBase } from '../../cli/models/scan-result/detection-base';
1111
import { VscodeCommands } from '../../commands';
12+
import { FilterNode } from './nodes/filter-node';
13+
import { IStateService } from '../../services/state-service';
1214

1315
export class TreeDataProvider implements vscode.TreeDataProvider<BaseNode> {
1416
public static readonly viewType = 'cycode.view.tree';
@@ -18,7 +20,7 @@ export class TreeDataProvider implements vscode.TreeDataProvider<BaseNode> {
1820
private _onDidChangeTreeData: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
1921
readonly onDidChangeTreeData: vscode.Event<void> = this._onDidChangeTreeData.event;
2022

21-
private _createdRootNodes: ScanTypeNode[] = [];
23+
private _createdRootNodes: BaseNode[] = [];
2224
private _createdNodesToChildren = new Map<BaseNode, BaseNode[]>();
2325
private _createdChildToParentNode = new Map<BaseNode, BaseNode>();
2426

@@ -52,6 +54,30 @@ export class TreeDataProvider implements vscode.TreeDataProvider<BaseNode> {
5254
return severityWeights[severity.toLowerCase()] || 0;
5355
}
5456

57+
private getEnabledSeverityFilters(): Set<string> {
58+
const stateService = container.resolve<IStateService>(StateServiceSymbol);
59+
const tempState = stateService.tempState;
60+
61+
const enabledSeverityFilters = new Set<string>();
62+
if (tempState.IsTreeViewFilterByCriticalSeverityEnabled) {
63+
enabledSeverityFilters.add('critical');
64+
}
65+
if (tempState.IsTreeViewFilterByHighSeverityEnabled) {
66+
enabledSeverityFilters.add('high');
67+
}
68+
if (tempState.IsTreeViewFilterByMediumSeverityEnabled) {
69+
enabledSeverityFilters.add('medium');
70+
}
71+
if (tempState.IsTreeViewFilterByLowSeverityEnabled) {
72+
enabledSeverityFilters.add('low');
73+
}
74+
if (tempState.IsTreeViewFilterByInfoSeverityEnabled) {
75+
enabledSeverityFilters.add('info');
76+
}
77+
78+
return enabledSeverityFilters;
79+
}
80+
5581
private getScanTypeNodeSummary(sortedDetections: DetectionBase[]): string {
5682
// detections must be sorted by severity
5783
const groupedBySeverity = sortedDetections.reduce<Map<string, DetectionBase[]>>((acc, detection) => {
@@ -72,9 +98,15 @@ export class TreeDataProvider implements vscode.TreeDataProvider<BaseNode> {
7298
const scanResultsService = container.resolve<IScanResultsService>(ScanResultsServiceSymbol);
7399
const detections = scanResultsService.getDetections(scanType);
74100

75-
const severitySortedDetections = detections.sort((a, b) => {
101+
const enabledSeverityFilters = this.getEnabledSeverityFilters();
102+
const severityFilteredDetections = detections.filter((detection) => {
103+
return !enabledSeverityFilters.has(detection.severity.toLowerCase());
104+
});
105+
106+
const severitySortedDetections = severityFilteredDetections.sort((a, b) => {
76107
return this.getSeverityWeight(b.severity) - this.getSeverityWeight(a.severity);
77108
});
109+
78110
const groupedByFilepathDetection = severitySortedDetections
79111
.reduce<Map<string, DetectionBase[]>>((acc, detection) => {
80112
const filepath = detection.detectionDetails.getFilepath();
@@ -104,7 +136,7 @@ export class TreeDataProvider implements vscode.TreeDataProvider<BaseNode> {
104136
}
105137

106138
public refresh(): void {
107-
this._createdRootNodes = [];
139+
this._createdRootNodes = [new FilterNode()];
108140
this._createdNodesToChildren.clear();
109141
this._createdChildToParentNode.clear();
110142

0 commit comments

Comments
 (0)