Skip to content

Commit 9053114

Browse files
committed
CM-44176 - Add tree view filtering by severity
1 parent 3d90956 commit 9053114

File tree

8 files changed

+210
-3
lines changed

8 files changed

+210
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
## [Unreleased]
66

7+
- Add tree view filtering by severity
78
- Add proper support for disabled modules
89
- Fix scrolling issue in violation panels
910

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using Cycode.VisualStudio.Extension.Shared.DTO;
2+
using Cycode.VisualStudio.Extension.Shared.Services;
3+
4+
namespace Cycode.VisualStudio.Extension.Shared.Commands;
5+
6+
internal static class TreeViewFilterBySeverityCommand {
7+
public static void Execute(OleMenuCommand command, string severity) {
8+
ITemporaryStateService tempState =
9+
ServiceLocator.GetService<ITemporaryStateService>();
10+
IToolWindowMessengerService toolWindowMessengerService =
11+
ServiceLocator.GetService<IToolWindowMessengerService>();
12+
13+
switch (severity.ToLower()) {
14+
case "critical":
15+
tempState.IsTreeViewFilterByCriticalSeverityEnabled = !command.Checked;
16+
break;
17+
case "high":
18+
tempState.IsTreeViewFilterByHighSeverityEnabled = !command.Checked;
19+
break;
20+
case "medium":
21+
tempState.IsTreeViewFilterByMediumSeverityEnabled = !command.Checked;
22+
break;
23+
case "low":
24+
tempState.IsTreeViewFilterByLowSeverityEnabled = !command.Checked;
25+
break;
26+
case "info":
27+
tempState.IsTreeViewFilterByInfoSeverityEnabled = !command.Checked;
28+
break;
29+
}
30+
31+
command.Checked = !command.Checked; // toggle checked state
32+
33+
// refresh tree view to apply filter
34+
toolWindowMessengerService.Send(new MessageEventArgs(MessengerCommand.TreeViewRefresh));
35+
}
36+
}
37+
38+
[Command(PackageIds.TreeViewFilterByCriticalSeverityCommand)]
39+
internal sealed class TreeViewFilterByCriticalSeverityCommand : BaseCommand<TreeViewFilterByCriticalSeverityCommand> {
40+
protected override void Execute(object sender, EventArgs e) {
41+
if (sender is OleMenuCommand command) {
42+
TreeViewFilterBySeverityCommand.Execute(command, "critical");
43+
}
44+
}
45+
}
46+
47+
[Command(PackageIds.TreeViewFilterByHighSeverityCommand)]
48+
internal sealed class TreeViewFilterByHighSeverityCommand : BaseCommand<TreeViewFilterByHighSeverityCommand> {
49+
protected override void Execute(object sender, EventArgs e) {
50+
if (sender is OleMenuCommand command) {
51+
TreeViewFilterBySeverityCommand.Execute(command, "high");
52+
}
53+
}
54+
}
55+
56+
[Command(PackageIds.TreeViewFilterByMediumSeverityCommand)]
57+
internal sealed class TreeViewFilterByMediumSeverityCommand : BaseCommand<TreeViewFilterByMediumSeverityCommand> {
58+
protected override void Execute(object sender, EventArgs e) {
59+
if (sender is OleMenuCommand command) {
60+
TreeViewFilterBySeverityCommand.Execute(command, "medium");
61+
}
62+
}
63+
}
64+
65+
[Command(PackageIds.TreeViewFilterByLowSeverityCommand)]
66+
internal sealed class TreeViewFilterByLowSeverityCommand : BaseCommand<TreeViewFilterByLowSeverityCommand> {
67+
protected override void Execute(object sender, EventArgs e) {
68+
if (sender is OleMenuCommand command) {
69+
TreeViewFilterBySeverityCommand.Execute(command, "low");
70+
}
71+
}
72+
}
73+
74+
[Command(PackageIds.TreeViewFilterByInfoSeverityCommand)]
75+
internal sealed class TreeViewFilterByInfoSeverityCommand : BaseCommand<TreeViewFilterByInfoSeverityCommand> {
76+
protected override void Execute(object sender, EventArgs e) {
77+
if (sender is OleMenuCommand command) {
78+
TreeViewFilterBySeverityCommand.Execute(command, "info");
79+
}
80+
}
81+
}

src/extension/Cycode.VisualStudio.Extension.Shared/Components/TreeView/CycodeTreeViewControl.xaml.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public partial class CycodeTreeViewControl {
2222

2323
private static readonly IToolWindowMessengerService _toolWindowMessengerService =
2424
ServiceLocator.GetService<IToolWindowMessengerService>();
25+
26+
private static readonly ITemporaryStateService _tempState =
27+
ServiceLocator.GetService<ITemporaryStateService>();
2528

2629
private static readonly ILoggerService _logger = ServiceLocator.GetService<ILoggerService>();
2730

@@ -90,6 +93,28 @@ private static int GetSeverityWeight(string severity) {
9093
_ => 0
9194
};
9295
}
96+
97+
private HashSet<string> GetEnabledSeverityFilters() {
98+
HashSet<string> enabledSeverityFilters = [];
99+
100+
if (_tempState.IsTreeViewFilterByCriticalSeverityEnabled) {
101+
enabledSeverityFilters.Add("critical");
102+
}
103+
if (_tempState.IsTreeViewFilterByHighSeverityEnabled) {
104+
enabledSeverityFilters.Add("high");
105+
}
106+
if (_tempState.IsTreeViewFilterByMediumSeverityEnabled) {
107+
enabledSeverityFilters.Add("medium");
108+
}
109+
if (_tempState.IsTreeViewFilterByLowSeverityEnabled) {
110+
enabledSeverityFilters.Add("low");
111+
}
112+
if (_tempState.IsTreeViewFilterByInfoSeverityEnabled) {
113+
enabledSeverityFilters.Add("info");
114+
}
115+
116+
return enabledSeverityFilters;
117+
}
93118

94119
private static string GetRootNodeSummary(IEnumerable<DetectionBase> sortedDetections) {
95120
// detections must be sorted by severity already
@@ -115,7 +140,11 @@ private void CreateDetectionNodes(
115140
IEnumerable<DetectionBase> detections,
116141
Func<DetectionBase, BaseNode> createNodeCallback
117142
) {
118-
List<DetectionBase> sortedDetections = detections
143+
HashSet<string> enabledSeverityFilters = GetEnabledSeverityFilters();
144+
List<DetectionBase> severityFilteredDetections = detections
145+
.Where(detection => !enabledSeverityFilters.Contains(detection.Severity.ToLower()))
146+
.ToList();
147+
List<DetectionBase> sortedDetections = severityFilteredDetections
119148
.OrderByDescending(detection => GetSeverityWeight(detection.Severity))
120149
.ToList();
121150
IEnumerable<IGrouping<string, DetectionBase>> detectionsByFile =

src/extension/Cycode.VisualStudio.Extension.Shared/Cycode.VisualStudio.Extension.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<Compile Include="$(MSBuildThisFileDirectory)Commands\RunAllScansCommand.cs"/>
5050
<Compile Include="$(MSBuildThisFileDirectory)Commands\TreeViewCollapseAllCommand.cs"/>
5151
<Compile Include="$(MSBuildThisFileDirectory)Commands\TreeViewExpandAllCommand.cs"/>
52+
<Compile Include="$(MSBuildThisFileDirectory)Commands\TreeViewFilterBySeverityCommands.cs" />
5253
<Compile Include="$(MSBuildThisFileDirectory)Components\TreeView\Nodes\BaseNode.cs"/>
5354
<Compile Include="$(MSBuildThisFileDirectory)Components\TreeView\Nodes\DetectionNodes\IacDetectionNode.cs"/>
5455
<Compile Include="$(MSBuildThisFileDirectory)Components\TreeView\Nodes\DetectionNodes\SastDetectionNode.cs"/>

src/extension/Cycode.VisualStudio.Extension.Shared/CycodePackage.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@
22
global using Community.VisualStudio.Toolkit;
33
global using Microsoft.VisualStudio.Shell;
44
global using Task = System.Threading.Tasks.Task;
5+
using System.ComponentModel.Design;
56
using System.Runtime.InteropServices;
67
using System.Threading;
7-
using Community.VisualStudio.Toolkit;
88
using Cycode.VisualStudio.Extension.Shared.Components.ToolWindows;
99
using Cycode.VisualStudio.Extension.Shared.Options;
1010
using Cycode.VisualStudio.Extension.Shared.Sentry;
1111
using Cycode.VisualStudio.Extension.Shared.Services;
1212
using Cycode.VisualStudio.Extension.Shared.Services.ErrorList;
1313
using Cycode.VisualStudio.Extension.Shared.Services.ErrorTagger;
1414
using Microsoft.Extensions.DependencyInjection;
15-
using Microsoft.VisualStudio.Shell;
1615

1716
namespace Cycode.VisualStudio.Extension.Shared;
1817

@@ -64,6 +63,23 @@ protected override async Task InitializeAsync(
6463
cycodeService.InstallCliIfNeededAndCheckAuthenticationAsync().FireAndForget();
6564

6665
logger.Info("CycodePackage.InitializeAsync completed.");
66+
67+
// set the filter commands to checked because they are enabled by default (no actual filtering)
68+
if (await GetServiceAsync(typeof(IMenuCommandService)) is OleMenuCommandService commandService) {
69+
int[] filterCommands = [
70+
PackageIds.TreeViewFilterByCriticalSeverityCommand,
71+
PackageIds.TreeViewFilterByHighSeverityCommand,
72+
PackageIds.TreeViewFilterByMediumSeverityCommand,
73+
PackageIds.TreeViewFilterByLowSeverityCommand,
74+
PackageIds.TreeViewFilterByInfoSeverityCommand
75+
];
76+
foreach (int commandId in filterCommands) {
77+
MenuCommand cmd = commandService.FindCommand(new CommandID(PackageGuids.Cycode, commandId));
78+
if (cmd != null) {
79+
cmd.Checked = true;
80+
}
81+
}
82+
}
6783
}
6884

6985
private static void OnSettingsSaved(General obj) {

src/extension/Cycode.VisualStudio.Extension.Shared/Services/TemporaryStateService.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ public interface ITemporaryStateService {
1313
bool IsIacScanningEnabled { get; }
1414
bool IsSastScanningEnabled { get; }
1515
bool IsAiLargeLanguageModelEnabled { get; }
16+
17+
bool IsTreeViewFilterByCriticalSeverityEnabled { get; set; }
18+
bool IsTreeViewFilterByHighSeverityEnabled { get; set; }
19+
bool IsTreeViewFilterByMediumSeverityEnabled { get; set; }
20+
bool IsTreeViewFilterByLowSeverityEnabled { get; set; }
21+
bool IsTreeViewFilterByInfoSeverityEnabled { get; set; }
1622
}
1723

1824
public class TemporaryStateService : ITemporaryStateService {
@@ -37,6 +43,12 @@ public StatusResult CliStatus {
3743
public bool IsSastScanningEnabled => _cliStatus?.SupportedModules?.SastScanning == true;
3844
public bool IsAiLargeLanguageModelEnabled => _cliStatus?.SupportedModules?.AiLargeLanguageModel == true;
3945

46+
public bool IsTreeViewFilterByCriticalSeverityEnabled { get; set; } = false;
47+
public bool IsTreeViewFilterByHighSeverityEnabled { get; set; } = false;
48+
public bool IsTreeViewFilterByMediumSeverityEnabled { get; set; } = false;
49+
public bool IsTreeViewFilterByLowSeverityEnabled { get; set; } = false;
50+
public bool IsTreeViewFilterByInfoSeverityEnabled { get; set; } = false;
51+
4052
public TemporaryStateService(ILoggerService loggerService) {
4153
_loggerService = loggerService;
4254
_loggerService.Info("CycodeTemporaryStateService init");

src/extension/Cycode.VisualStudio.Extension.Shared/VSCommandTable.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ internal sealed class PackageIds {
2323
public const int TreeViewExpandAllCommand = 0x1057;
2424
public const int TreeViewCollapseAllCommand = 0x1058;
2525

26+
public const int TreeViewFilterByCriticalSeverityCommand = 0x1059;
27+
public const int TreeViewFilterByHighSeverityCommand = 0x1060;
28+
public const int TreeViewFilterByMediumSeverityCommand = 0x1061;
29+
public const int TreeViewFilterByLowSeverityCommand = 0x1062;
30+
public const int TreeViewFilterByInfoSeverityCommand = 0x1063;
31+
2632
public const int TopMenuCycodeCommand = 0x1103;
2733
public const int TopMenuOpenSettingsCommand = 0x1104;
2834
}

src/extension/Cycode.VisualStudio.Extension.Shared/VSCommandTable.vsct

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,41 @@
8383
<ButtonText>Open documentation</ButtonText>
8484
</Strings>
8585
</Button>
86+
<Button guid="Cycode" id="TreeViewFilterByCriticalSeverityCommand" priority="0x0107" type="Button">
87+
<Parent guid="Cycode" id="TWindowToolbarGroup"/>
88+
<Icon guid="criticalSeverityIcon" id="criticalSeverityIconID" />
89+
<Strings>
90+
<ButtonText>Filter by Critical severity</ButtonText>
91+
</Strings>
92+
</Button>
93+
<Button guid="Cycode" id="TreeViewFilterByHighSeverityCommand" priority="0x0108" type="Button">
94+
<Parent guid="Cycode" id="TWindowToolbarGroup"/>
95+
<Icon guid="highSeverityIcon" id="highSeverityIconID" />
96+
<Strings>
97+
<ButtonText>Filter by High severity</ButtonText>
98+
</Strings>
99+
</Button>
100+
<Button guid="Cycode" id="TreeViewFilterByMediumSeverityCommand" priority="0x0109" type="Button">
101+
<Parent guid="Cycode" id="TWindowToolbarGroup"/>
102+
<Icon guid="mediumSeverityIcon" id="mediumSeverityIconID" />
103+
<Strings>
104+
<ButtonText>Filter by Medium severity</ButtonText>
105+
</Strings>
106+
</Button>
107+
<Button guid="Cycode" id="TreeViewFilterByLowSeverityCommand" priority="0x0110" type="Button">
108+
<Parent guid="Cycode" id="TWindowToolbarGroup"/>
109+
<Icon guid="lowSeverityIcon" id="lowSeverityIconID" />
110+
<Strings>
111+
<ButtonText>Filter by Low severity</ButtonText>
112+
</Strings>
113+
</Button>
114+
<Button guid="Cycode" id="TreeViewFilterByInfoSeverityCommand" priority="0x0111" type="Button">
115+
<Parent guid="Cycode" id="TWindowToolbarGroup"/>
116+
<Icon guid="infoSeverityIcon" id="infoSeverityIconID" />
117+
<Strings>
118+
<ButtonText>Filter by Info severity</ButtonText>
119+
</Strings>
120+
</Button>
86121
</Buttons>
87122

88123
<Groups>
@@ -117,6 +152,11 @@
117152
<Bitmap guid="previousIcon" href="Resources\KnownMonikers\Previous.png" usedList="previousIconID" />
118153
<Bitmap guid="expandAllIcon" href="Resources\KnownMonikers\ExpandAll.png" usedList="expandAllIconID" />
119154
<Bitmap guid="collapseAllIcon" href="Resources\KnownMonikers\CollapseAll.png" usedList="collapseAllIconID" />
155+
<Bitmap guid="criticalSeverityIcon" href="Resources\Severity\C.png" usedList="criticalSeverityIconID" />
156+
<Bitmap guid="highSeverityIcon" href="Resources\Severity\H.png" usedList="highSeverityIconID" />
157+
<Bitmap guid="mediumSeverityIcon" href="Resources\Severity\M.png" usedList="mediumSeverityIconID" />
158+
<Bitmap guid="lowSeverityIcon" href="Resources\Severity\L.png" usedList="lowSeverityIconID" />
159+
<Bitmap guid="infoSeverityIcon" href="Resources\Severity\I.png" usedList="infoSeverityIconID" />
120160
</Bitmaps>
121161
</Commands>
122162

@@ -134,6 +174,12 @@
134174
<IDSymbol name="TreeViewExpandAllCommand" value="0x1057" />
135175
<IDSymbol name="TreeViewCollapseAllCommand" value="0x1058" />
136176

177+
<IDSymbol name="TreeViewFilterByCriticalSeverityCommand" value="0x1059" />
178+
<IDSymbol name="TreeViewFilterByHighSeverityCommand" value="0x1060" />
179+
<IDSymbol name="TreeViewFilterByMediumSeverityCommand" value="0x1061" />
180+
<IDSymbol name="TreeViewFilterByLowSeverityCommand" value="0x1062" />
181+
<IDSymbol name="TreeViewFilterByInfoSeverityCommand" value="0x1063" />
182+
137183
<IDSymbol name="TopMenu" value="0x1100" />
138184
<IDSymbol name="TopMenuGroup" value="0x1102" />
139185
<IDSymbol name="TopMenuCycodeCommand" value="0x1103" />
@@ -158,5 +204,20 @@
158204
<GuidSymbol name="collapseAllIcon" value="{633fe1d6-4183-4bb7-bc87-4b5a377960db}">
159205
<IDSymbol name="collapseAllIconID" value="1" />
160206
</GuidSymbol>
207+
<GuidSymbol name="criticalSeverityIcon" value="{ab7e55e2-d1e2-4af8-8dbf-f412ae322d3c}">
208+
<IDSymbol name="criticalSeverityIconID" value="1" />
209+
</GuidSymbol>
210+
<GuidSymbol name="highSeverityIcon" value="{4d4ea4c3-5670-4cf3-a004-cd9d06695657}">
211+
<IDSymbol name="highSeverityIconID" value="1" />
212+
</GuidSymbol>
213+
<GuidSymbol name="mediumSeverityIcon" value="{e07319db-f082-45d1-b6dd-ea20599dbd4c}">
214+
<IDSymbol name="mediumSeverityIconID" value="1" />
215+
</GuidSymbol>
216+
<GuidSymbol name="lowSeverityIcon" value="{77ca5748-7065-45ca-afb0-416dd709d6ba}">
217+
<IDSymbol name="lowSeverityIconID" value="1" />
218+
</GuidSymbol>
219+
<GuidSymbol name="infoSeverityIcon" value="{bb3fdec5-04d4-4fe6-9fef-991a480c0d99}">
220+
<IDSymbol name="infoSeverityIconID" value="1" />
221+
</GuidSymbol>
161222
</Symbols>
162223
</CommandTable>

0 commit comments

Comments
 (0)