Skip to content

Commit 2c66794

Browse files
SLVS-2521 Implement navigation to the hotspot location (#6422)
1 parent 903f5b8 commit 2c66794

File tree

7 files changed

+96
-9
lines changed

7 files changed

+96
-9
lines changed

src/IssueViz.Security.UnitTests/ReportView/Hotspots/HotspotViewModelTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public void Ctor_InitializesPropertiesAsExpected()
4747
testSubject.Column.Should().Be(hotspot.Visualization.Issue.PrimaryLocation.TextRange.StartLineOffset);
4848
testSubject.Title.Should().Be(hotspot.Visualization.Issue.PrimaryLocation.Message);
4949
testSubject.FilePath.Should().Be(hotspot.Visualization.Issue.PrimaryLocation.FilePath);
50+
testSubject.Issue.Should().Be(hotspot.Visualization);
5051
}
5152

5253
private static LocalHotspot CreateMockedHotspot(

src/IssueViz.Security.UnitTests/ReportView/ReportViewModelTest.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
using SonarLint.VisualStudio.Core.Analysis;
2525
using SonarLint.VisualStudio.Core.Binding;
2626
using SonarLint.VisualStudio.Core.Telemetry;
27+
using SonarLint.VisualStudio.IssueVisualization.Editor;
2728
using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;
2829
using SonarLint.VisualStudio.IssueVisualization.Models;
2930
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
@@ -45,6 +46,7 @@ public class ReportViewModelTest
4546
private IShowDependencyRiskInBrowserHandler showDependencyRiskInBrowserHandler;
4647
private IChangeDependencyRiskStatusHandler changeDependencyRiskStatusHandler;
4748
private INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand;
49+
private ILocationNavigator locationNavigator;
4850
private IMessageBox messageBox;
4951
private ITelemetryManager telemetryManager;
5052
private IThreadHandling threadHandling;
@@ -59,6 +61,7 @@ public void Initialize()
5961
showDependencyRiskInBrowserHandler = Substitute.For<IShowDependencyRiskInBrowserHandler>();
6062
changeDependencyRiskStatusHandler = Substitute.For<IChangeDependencyRiskStatusHandler>();
6163
navigateToRuleDescriptionCommand = Substitute.For<INavigateToRuleDescriptionCommand>();
64+
locationNavigator = Substitute.For<ILocationNavigator>();
6265
messageBox = Substitute.For<IMessageBox>();
6366
telemetryManager = Substitute.For<ITelemetryManager>();
6467
threadHandling = Substitute.ForPartsOf<NoOpThreadHandler>();
@@ -78,7 +81,11 @@ public void Class_SubscribesToEvents()
7881
}
7982

8083
[TestMethod]
81-
public void Class_InitializesProperties() => testSubject.NavigateToRuleDescriptionCommand.Should().BeSameAs(navigateToRuleDescriptionCommand);
84+
public void Class_InitializesProperties()
85+
{
86+
testSubject.NavigateToRuleDescriptionCommand.Should().BeSameAs(navigateToRuleDescriptionCommand);
87+
testSubject.NavigateToLocationCommand.Should().NotBeNull();
88+
}
8289

8390
[TestMethod]
8491
public void Ctor_InitializesDependencyRisks()
@@ -465,6 +472,35 @@ public void HasRisks_ReturnsTrue_WhenThereAreRisks()
465472
[TestMethod]
466473
public void HasRisks_ReturnsFalse_WhenThereAreNoRisks() => testSubject.HasGroups.Should().BeFalse();
467474

475+
[TestMethod]
476+
public void NavigateToLocationCommand_NullParameter_CanExecuteReturnsFalse() => testSubject.NavigateToLocationCommand.CanExecute(null).Should().BeFalse();
477+
478+
[TestMethod]
479+
public void NavigateToLocationCommand_NotAnalysisIssueViewModelParameter_CanExecuteReturnsFalse()
480+
{
481+
var viewModel = Substitute.For<IIssueViewModel>();
482+
483+
testSubject.NavigateToLocationCommand.CanExecute(viewModel).Should().BeFalse();
484+
}
485+
486+
[TestMethod]
487+
public void NavigateToLocationCommand_AnalysisIssueViewModelParameter_CanExecuteReturnsTrue()
488+
{
489+
var analysisIssueViewModel = Substitute.For<IAnalysisIssueViewModel>();
490+
491+
testSubject.NavigateToLocationCommand.CanExecute(analysisIssueViewModel).Should().BeTrue();
492+
}
493+
494+
[TestMethod]
495+
public void NavigateToLocationCommand_NavigatesToLocation()
496+
{
497+
var analysisIssueViewModel = Substitute.For<IAnalysisIssueViewModel>();
498+
499+
testSubject.NavigateToLocationCommand.Execute(analysisIssueViewModel);
500+
501+
locationNavigator.Received(1).TryNavigatePartial(analysisIssueViewModel.Issue);
502+
}
503+
468504
private ReportViewModel CreateTestSubject()
469505
{
470506
var reportViewModel = new ReportViewModel(activeSolutionBoundTracker,
@@ -473,6 +509,7 @@ private ReportViewModel CreateTestSubject()
473509
showDependencyRiskInBrowserHandler,
474510
changeDependencyRiskStatusHandler,
475511
navigateToRuleDescriptionCommand,
512+
locationNavigator,
476513
messageBox,
477514
telemetryManager,
478515
threadHandling);

src/IssueViz.Security/ReportView/Hotspots/HotspotViewModel.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919
*/
2020

2121
using SonarLint.VisualStudio.Core.WPF;
22+
using SonarLint.VisualStudio.IssueVisualization.Models;
2223
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
2324

2425
namespace SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots;
2526

26-
internal class HotspotViewModel : ViewModelBase, IIssueViewModel
27+
internal class HotspotViewModel : ViewModelBase, IAnalysisIssueViewModel
2728
{
2829
public LocalHotspot LocalHotspot { get; }
2930

@@ -38,4 +39,5 @@ public HotspotViewModel(LocalHotspot localHotspot)
3839
public string Title => LocalHotspot.Visualization.Issue.PrimaryLocation.Message;
3940
public string FilePath => LocalHotspot.Visualization.Issue.PrimaryLocation.FilePath;
4041
public RuleInfoViewModel RuleInfo { get; }
42+
public IAnalysisIssueVisualization Issue => LocalHotspot.Visualization;
4143
}

src/IssueViz.Security/ReportView/IIssueViewModel.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
*/
2020

21+
using SonarLint.VisualStudio.IssueVisualization.Models;
22+
2123
namespace SonarLint.VisualStudio.IssueVisualization.Security.ReportView;
2224

2325
public interface IIssueViewModel
@@ -28,3 +30,8 @@ public interface IIssueViewModel
2830
string FilePath { get; }
2931
RuleInfoViewModel RuleInfo { get; }
3032
}
33+
34+
public interface IAnalysisIssueViewModel : IIssueViewModel
35+
{
36+
IAnalysisIssueVisualization Issue { get; }
37+
}

src/IssueViz.Security/ReportView/ReportViewControl.xaml.cs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
using SonarLint.VisualStudio.Core;
2929
using SonarLint.VisualStudio.Core.Binding;
3030
using SonarLint.VisualStudio.Core.Telemetry;
31+
using SonarLint.VisualStudio.IssueVisualization.Editor;
3132
using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;
3233
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
3334
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
@@ -52,6 +53,7 @@ public ReportViewControl(
5253
IShowDependencyRiskInBrowserHandler showDependencyRiskInBrowserHandler,
5354
IChangeDependencyRiskStatusHandler changeDependencyRiskStatusHandler,
5455
INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand,
56+
ILocationNavigator locationNavigator,
5557
IMessageBox messageBox,
5658
ITelemetryManager telemetryManager,
5759
IThreadHandling threadHandling)
@@ -64,6 +66,7 @@ public ReportViewControl(
6466
showDependencyRiskInBrowserHandler,
6567
changeDependencyRiskStatusHandler,
6668
navigateToRuleDescriptionCommand,
69+
locationNavigator,
6770
messageBox,
6871
telemetryManager,
6972
threadHandling);
@@ -150,15 +153,32 @@ private async void ChangeScaStatusMenuItem_OnClick(object sender, RoutedEventArg
150153

151154
private void TreeViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
152155
{
153-
if ((sender as FrameworkElement)?.DataContext is not IIssueViewModel issueViewModel)
156+
NavigateToLocation(sender);
157+
ShowRuleHelp(sender);
158+
}
159+
160+
private void NavigateToLocation(object sender)
161+
{
162+
if ((sender as FrameworkElement)?.DataContext is IAnalysisIssueViewModel analysisIssueViewModel)
154163
{
155-
return;
164+
ExecuteCommandIfValid(ReportViewModel.NavigateToLocationCommand, analysisIssueViewModel);
156165
}
166+
}
157167

158-
var commandParam = new NavigateToRuleDescriptionCommandParam { FullRuleKey = issueViewModel.RuleInfo.RuleKey, IssueId = issueViewModel.RuleInfo.IssueId };
159-
if (ReportViewModel.NavigateToRuleDescriptionCommand.CanExecute(commandParam))
168+
private void ShowRuleHelp(object sender)
169+
{
170+
if ((sender as FrameworkElement)?.DataContext is IIssueViewModel issueViewModel)
171+
{
172+
var commandParam = new NavigateToRuleDescriptionCommandParam { FullRuleKey = issueViewModel.RuleInfo.RuleKey, IssueId = issueViewModel.RuleInfo.IssueId };
173+
ExecuteCommandIfValid(ReportViewModel.NavigateToRuleDescriptionCommand, commandParam);
174+
}
175+
}
176+
177+
private static void ExecuteCommandIfValid(ICommand command, object parameter)
178+
{
179+
if (command.CanExecute(parameter))
160180
{
161-
ReportViewModel.NavigateToRuleDescriptionCommand.Execute(commandParam);
181+
command.Execute(parameter);
162182
}
163183
}
164184
}

src/IssueViz.Security/ReportView/ReportViewModel.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121
using System.Collections.ObjectModel;
2222
using System.Windows;
2323
using System.Windows.Data;
24+
using System.Windows.Input;
25+
using Microsoft.VisualStudio.PlatformUI;
2426
using SonarLint.VisualStudio.Core;
2527
using SonarLint.VisualStudio.Core.Analysis;
2628
using SonarLint.VisualStudio.Core.Binding;
2729
using SonarLint.VisualStudio.Core.Telemetry;
30+
using SonarLint.VisualStudio.IssueVisualization.Editor;
2831
using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;
2932
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
3033
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
@@ -51,11 +54,11 @@ public ReportViewModel(
5154
IShowDependencyRiskInBrowserHandler showDependencyRiskInBrowserHandler,
5255
IChangeDependencyRiskStatusHandler changeDependencyRiskStatusHandler,
5356
INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand,
57+
ILocationNavigator locationNavigator,
5458
IMessageBox messageBox,
5559
ITelemetryManager telemetryManager,
5660
IThreadHandling threadHandling) : base(activeSolutionBoundTracker)
5761
{
58-
NavigateToRuleDescriptionCommand = navigateToRuleDescriptionCommand;
5962
this.dependencyRisksStore = dependencyRisksStore;
6063
this.hotspotsStore = hotspotsStore;
6164
this.showDependencyRiskInBrowserHandler = showDependencyRiskInBrowserHandler;
@@ -66,12 +69,15 @@ public ReportViewModel(
6669
threadHandling.RunOnUIThread(() => { BindingOperations.EnableCollectionSynchronization(GroupViewModels, @lock); });
6770
hotspotsStore.IssuesChanged += HotspotsStore_IssuesChanged;
6871
dependencyRisksStore.DependencyRisksChanged += DependencyRisksStore_DependencyRiskChanged;
72+
73+
InitializeCommands(navigateToRuleDescriptionCommand, locationNavigator);
6974
InitializeViewModels();
7075
}
7176

7277
public ObservableCollection<IGroupViewModel> GroupViewModels { get; } = [];
7378
public bool HasGroups => GroupViewModels.Count > 0;
74-
public INavigateToRuleDescriptionCommand NavigateToRuleDescriptionCommand { get; }
79+
public INavigateToRuleDescriptionCommand NavigateToRuleDescriptionCommand { get; set; }
80+
public ICommand NavigateToLocationCommand { get; set; }
7581

7682
public IIssueViewModel SelectedItem
7783
{
@@ -180,4 +186,16 @@ private static ObservableCollection<IGroupViewModel> GetGroupViewModel(IEnumerab
180186

181187
return groupViewModels;
182188
}
189+
190+
private void InitializeCommands(
191+
INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand,
192+
ILocationNavigator locationNavigator)
193+
{
194+
NavigateToRuleDescriptionCommand = navigateToRuleDescriptionCommand;
195+
NavigateToLocationCommand = new DelegateCommand(parameter =>
196+
{
197+
var analysisIssueViewModel = (IAnalysisIssueViewModel)parameter;
198+
locationNavigator.TryNavigate(analysisIssueViewModel.Issue);
199+
}, parameter => parameter is IAnalysisIssueViewModel);
200+
}
183201
}

src/IssueViz.Security/ReportView/ReportViewToolWindow.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
using SonarLint.VisualStudio.Core;
2626
using SonarLint.VisualStudio.Core.Binding;
2727
using SonarLint.VisualStudio.Core.Telemetry;
28+
using SonarLint.VisualStudio.IssueVisualization.Editor;
2829
using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;
2930
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
3031
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
@@ -50,6 +51,7 @@ public ReportViewToolWindow(IServiceProvider serviceProvider)
5051
componentModel?.GetService<IShowDependencyRiskInBrowserHandler>(),
5152
componentModel?.GetService<IChangeDependencyRiskStatusHandler>(),
5253
componentModel?.GetService<INavigateToRuleDescriptionCommand>(),
54+
componentModel?.GetService<ILocationNavigator>(),
5355
componentModel?.GetService<IMessageBox>(),
5456
componentModel?.GetService<ITelemetryManager>(),
5557
componentModel?.GetService<IThreadHandling>()

0 commit comments

Comments
 (0)