Skip to content

CM-44176 - Add tree view filtering by severity #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

## [Unreleased]

- Add tree view filtering by severity
- Add proper support for disabled modules
- Fix scrolling issue in violation panels

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using Cycode.VisualStudio.Extension.Shared.DTO;
using Cycode.VisualStudio.Extension.Shared.Services;

namespace Cycode.VisualStudio.Extension.Shared.Commands;

internal static class TreeViewFilterBySeverityCommand {
public static void Execute(OleMenuCommand command, string severity) {
ITemporaryStateService tempState =
ServiceLocator.GetService<ITemporaryStateService>();
IToolWindowMessengerService toolWindowMessengerService =
ServiceLocator.GetService<IToolWindowMessengerService>();

/*
* We must flip the bool first to reflect the new state.
*
* Unchecked means that we do not want to see that severity in the tree view.
* In other words, if the button highlighted, it means that we WANT to see that severity in the tree view.
*/
command.Checked = !command.Checked;
bool isFilterEnabled = !command.Checked;

switch (severity.ToLower()) {
case "critical":
tempState.IsTreeViewFilterByCriticalSeverityEnabled = isFilterEnabled;
break;
case "high":
tempState.IsTreeViewFilterByHighSeverityEnabled = isFilterEnabled;
break;
case "medium":
tempState.IsTreeViewFilterByMediumSeverityEnabled = isFilterEnabled;
break;
case "low":
tempState.IsTreeViewFilterByLowSeverityEnabled = isFilterEnabled;
break;
case "info":
tempState.IsTreeViewFilterByInfoSeverityEnabled = isFilterEnabled;
break;
}

// refresh tree view to apply filter
toolWindowMessengerService.Send(new MessageEventArgs(MessengerCommand.TreeViewRefresh));
}
}

[Command(PackageIds.TreeViewFilterByCriticalSeverityCommand)]
internal sealed class TreeViewFilterByCriticalSeverityCommand : BaseCommand<TreeViewFilterByCriticalSeverityCommand> {
protected override void Execute(object sender, EventArgs e) {
if (sender is OleMenuCommand command) {
TreeViewFilterBySeverityCommand.Execute(command, "critical");
}
}
}

[Command(PackageIds.TreeViewFilterByHighSeverityCommand)]
internal sealed class TreeViewFilterByHighSeverityCommand : BaseCommand<TreeViewFilterByHighSeverityCommand> {
protected override void Execute(object sender, EventArgs e) {
if (sender is OleMenuCommand command) {
TreeViewFilterBySeverityCommand.Execute(command, "high");
}
}
}

[Command(PackageIds.TreeViewFilterByMediumSeverityCommand)]
internal sealed class TreeViewFilterByMediumSeverityCommand : BaseCommand<TreeViewFilterByMediumSeverityCommand> {
protected override void Execute(object sender, EventArgs e) {
if (sender is OleMenuCommand command) {
TreeViewFilterBySeverityCommand.Execute(command, "medium");
}
}
}

[Command(PackageIds.TreeViewFilterByLowSeverityCommand)]
internal sealed class TreeViewFilterByLowSeverityCommand : BaseCommand<TreeViewFilterByLowSeverityCommand> {
protected override void Execute(object sender, EventArgs e) {
if (sender is OleMenuCommand command) {
TreeViewFilterBySeverityCommand.Execute(command, "low");
}
}
}

[Command(PackageIds.TreeViewFilterByInfoSeverityCommand)]
internal sealed class TreeViewFilterByInfoSeverityCommand : BaseCommand<TreeViewFilterByInfoSeverityCommand> {
protected override void Execute(object sender, EventArgs e) {
if (sender is OleMenuCommand command) {
TreeViewFilterBySeverityCommand.Execute(command, "info");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public partial class CycodeTreeViewControl {

private static readonly IToolWindowMessengerService _toolWindowMessengerService =
ServiceLocator.GetService<IToolWindowMessengerService>();

private static readonly ITemporaryStateService _tempState =
ServiceLocator.GetService<ITemporaryStateService>();

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

Expand Down Expand Up @@ -90,6 +93,28 @@ private static int GetSeverityWeight(string severity) {
_ => 0
};
}

private static HashSet<string> GetEnabledSeverityFilters() {
HashSet<string> enabledSeverityFilters = [];

if (_tempState.IsTreeViewFilterByCriticalSeverityEnabled) {
enabledSeverityFilters.Add("critical");
}
if (_tempState.IsTreeViewFilterByHighSeverityEnabled) {
enabledSeverityFilters.Add("high");
}
if (_tempState.IsTreeViewFilterByMediumSeverityEnabled) {
enabledSeverityFilters.Add("medium");
}
if (_tempState.IsTreeViewFilterByLowSeverityEnabled) {
enabledSeverityFilters.Add("low");
}
if (_tempState.IsTreeViewFilterByInfoSeverityEnabled) {
enabledSeverityFilters.Add("info");
}

return enabledSeverityFilters;
}

private static string GetRootNodeSummary(IEnumerable<DetectionBase> sortedDetections) {
// detections must be sorted by severity already
Expand All @@ -115,7 +140,11 @@ private void CreateDetectionNodes(
IEnumerable<DetectionBase> detections,
Func<DetectionBase, BaseNode> createNodeCallback
) {
List<DetectionBase> sortedDetections = detections
HashSet<string> enabledSeverityFilters = GetEnabledSeverityFilters();
List<DetectionBase> severityFilteredDetections = detections
.Where(detection => !enabledSeverityFilters.Contains(detection.Severity.ToLower()))
.ToList();
List<DetectionBase> sortedDetections = severityFilteredDetections
.OrderByDescending(detection => GetSeverityWeight(detection.Severity))
.ToList();
IEnumerable<IGrouping<string, DetectionBase>> detectionsByFile =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Commands\RunAllScansCommand.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Commands\TreeViewCollapseAllCommand.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Commands\TreeViewExpandAllCommand.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Commands\TreeViewFilterBySeverityCommands.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Components\TreeView\Nodes\BaseNode.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Components\TreeView\Nodes\DetectionNodes\IacDetectionNode.cs"/>
<Compile Include="$(MSBuildThisFileDirectory)Components\TreeView\Nodes\DetectionNodes\SastDetectionNode.cs"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@
global using Community.VisualStudio.Toolkit;
global using Microsoft.VisualStudio.Shell;
global using Task = System.Threading.Tasks.Task;
using System.ComponentModel.Design;
using System.Runtime.InteropServices;
using System.Threading;
using Community.VisualStudio.Toolkit;
using Cycode.VisualStudio.Extension.Shared.Components.ToolWindows;
using Cycode.VisualStudio.Extension.Shared.Options;
using Cycode.VisualStudio.Extension.Shared.Sentry;
using Cycode.VisualStudio.Extension.Shared.Services;
using Cycode.VisualStudio.Extension.Shared.Services.ErrorList;
using Cycode.VisualStudio.Extension.Shared.Services.ErrorTagger;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.Shell;

namespace Cycode.VisualStudio.Extension.Shared;

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

logger.Info("CycodePackage.InitializeAsync completed.");

// set the filter commands to checked because they are enabled by default (no actual filtering)
if (await GetServiceAsync(typeof(IMenuCommandService)) is OleMenuCommandService commandService) {
int[] filterCommands = [
PackageIds.TreeViewFilterByCriticalSeverityCommand,
PackageIds.TreeViewFilterByHighSeverityCommand,
PackageIds.TreeViewFilterByMediumSeverityCommand,
PackageIds.TreeViewFilterByLowSeverityCommand,
PackageIds.TreeViewFilterByInfoSeverityCommand
];
foreach (int commandId in filterCommands) {
MenuCommand cmd = commandService.FindCommand(new CommandID(PackageGuids.Cycode, commandId));
if (cmd != null) {
cmd.Checked = true;
}
}
}
}

private static void OnSettingsSaved(General obj) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ public interface ITemporaryStateService {
bool IsIacScanningEnabled { get; }
bool IsSastScanningEnabled { get; }
bool IsAiLargeLanguageModelEnabled { get; }

bool IsTreeViewFilterByCriticalSeverityEnabled { get; set; }
bool IsTreeViewFilterByHighSeverityEnabled { get; set; }
bool IsTreeViewFilterByMediumSeverityEnabled { get; set; }
bool IsTreeViewFilterByLowSeverityEnabled { get; set; }
bool IsTreeViewFilterByInfoSeverityEnabled { get; set; }
}

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

public bool IsTreeViewFilterByCriticalSeverityEnabled { get; set; } = false;
public bool IsTreeViewFilterByHighSeverityEnabled { get; set; } = false;
public bool IsTreeViewFilterByMediumSeverityEnabled { get; set; } = false;
public bool IsTreeViewFilterByLowSeverityEnabled { get; set; } = false;
public bool IsTreeViewFilterByInfoSeverityEnabled { get; set; } = false;

public TemporaryStateService(ILoggerService loggerService) {
_loggerService = loggerService;
_loggerService.Info("CycodeTemporaryStateService init");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ internal sealed class PackageGuids {
internal sealed class PackageIds {
public const int ViewOpenToolWindowCommand = 0x1001;

public const int TWindowToolbar = 0x1050;
public const int TWindowToolbar = 0x1030;

public const int ToolbarOpenSettingsCommand = 0x1052;
public const int ToolbarRunAllScansCommand = 0x1053;
public const int ToolbarOpenWebDocsCommand = 0x1054;
Expand All @@ -23,6 +24,12 @@ internal sealed class PackageIds {
public const int TreeViewExpandAllCommand = 0x1057;
public const int TreeViewCollapseAllCommand = 0x1058;

public const int TreeViewFilterByCriticalSeverityCommand = 0x1059;
public const int TreeViewFilterByHighSeverityCommand = 0x1060;
public const int TreeViewFilterByMediumSeverityCommand = 0x1061;
public const int TreeViewFilterByLowSeverityCommand = 0x1062;
public const int TreeViewFilterByInfoSeverityCommand = 0x1063;

public const int TopMenuCycodeCommand = 0x1103;
public const int TopMenuOpenSettingsCommand = 0x1104;
}
Loading
Loading