Skip to content

Commit bb42eb8

Browse files
SLVS-2525 Show taints in the report view
1 parent e9bd66b commit bb42eb8

File tree

7 files changed

+182
-8
lines changed

7 files changed

+182
-8
lines changed

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

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
2929
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView;
3030
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots;
31+
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Taint;
3132
using SonarLint.VisualStudio.TestInfrastructure;
3233

3334
namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.ReportView;
@@ -39,6 +40,7 @@ public class ReportViewModelTest
3940
private IActiveSolutionBoundTracker activeSolutionBoundTracker;
4041
private IDependencyRisksStore dependencyRisksStore;
4142
private IHotspotsReportViewModel hotspotsReportViewModel;
43+
private ITaintsReportViewModel taintsReportViewModel;
4244
private IShowDependencyRiskInBrowserHandler showDependencyRiskInBrowserHandler;
4345
private IChangeDependencyRiskStatusHandler changeDependencyRiskStatusHandler;
4446
private INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand;
@@ -54,6 +56,7 @@ public void Initialize()
5456
activeSolutionBoundTracker = Substitute.For<IActiveSolutionBoundTracker>();
5557
dependencyRisksStore = Substitute.For<IDependencyRisksStore>();
5658
hotspotsReportViewModel = Substitute.For<IHotspotsReportViewModel>();
59+
taintsReportViewModel = Substitute.For<ITaintsReportViewModel>();
5760
showDependencyRiskInBrowserHandler = Substitute.For<IShowDependencyRiskInBrowserHandler>();
5861
changeDependencyRiskStatusHandler = Substitute.For<IChangeDependencyRiskStatusHandler>();
5962
navigateToRuleDescriptionCommand = Substitute.For<INavigateToRuleDescriptionCommand>();
@@ -63,6 +66,7 @@ public void Initialize()
6366
threadHandling = Substitute.ForPartsOf<NoOpThreadHandler>();
6467
eventHandler = Substitute.For<PropertyChangedEventHandler>();
6568
hotspotsReportViewModel.GetHotspotsGroupViewModels().Returns([]);
69+
taintsReportViewModel.GetTaintsGroupViewModels().Returns([]);
6670

6771
testSubject = CreateTestSubject();
6872
}
@@ -75,6 +79,7 @@ public void Class_SubscribesToEvents()
7579
{
7680
hotspotsReportViewModel.Received(1).HotspotsChanged += Arg.Any<EventHandler>();
7781
dependencyRisksStore.Received(1).DependencyRisksChanged += Arg.Any<EventHandler>();
82+
taintsReportViewModel.Received(1).TaintsChanged += Arg.Any<EventHandler>();
7883
}
7984

8085
[TestMethod]
@@ -96,19 +101,50 @@ public void Ctor_InitializesDependencyRisks()
96101
VerifyExpectedDependencyRiskGroupViewModel(testSubject.GroupViewModels[0] as GroupDependencyRiskViewModel, dependencyRisk);
97102
}
98103

104+
[TestMethod]
105+
public void Ctor_InitializesHotspots()
106+
{
107+
var hotspotGroupViewModel = CreateMockedGroupViewModel(filePath: "myFile.cs");
108+
var hotspotGroupViewModel2 = CreateMockedGroupViewModel(filePath: "myFile2.cs");
109+
hotspotsReportViewModel.GetHotspotsGroupViewModels().Returns([hotspotGroupViewModel, hotspotGroupViewModel2]);
110+
111+
testSubject = CreateTestSubject();
112+
113+
testSubject.GroupViewModels.Should().HaveCount(2);
114+
testSubject.GroupViewModels.Should().Contain(hotspotGroupViewModel);
115+
testSubject.GroupViewModels.Should().Contain(hotspotGroupViewModel2);
116+
}
117+
118+
[TestMethod]
119+
public void Ctor_InitializesTaints()
120+
{
121+
var taintGroupViewModel = CreateMockedGroupViewModel(filePath: "myFile.cs");
122+
var taintGroupViewModel2 = CreateMockedGroupViewModel(filePath: "myFile2.cs");
123+
taintsReportViewModel.GetTaintsGroupViewModels().Returns([taintGroupViewModel, taintGroupViewModel2]);
124+
125+
testSubject = CreateTestSubject();
126+
127+
testSubject.GroupViewModels.Should().HaveCount(2);
128+
testSubject.GroupViewModels.Should().Contain(taintGroupViewModel);
129+
testSubject.GroupViewModels.Should().Contain(taintGroupViewModel2);
130+
}
131+
99132
[TestMethod]
100133
public void Ctor_MixedIssuesTypes_CreatesGroupViewModelsCorrectly()
101134
{
102135
var dependencyRisk = CreateDependencyRisk();
103136
MockRisksInStore(dependencyRisk);
104137
var hotspotGroupViewModel = CreateMockedGroupViewModel(filePath: "myFile.cs");
105138
hotspotsReportViewModel.GetHotspotsGroupViewModels().Returns([hotspotGroupViewModel]);
139+
var taintGroupViewModel = CreateMockedGroupViewModel(filePath: "myFile2.cs");
140+
taintsReportViewModel.GetTaintsGroupViewModels().Returns([taintGroupViewModel]);
106141

107142
testSubject = CreateTestSubject();
108143

109-
testSubject.GroupViewModels.Should().HaveCount(2);
144+
testSubject.GroupViewModels.Should().HaveCount(3);
110145
VerifyExpectedDependencyRiskGroupViewModel(testSubject.GroupViewModels[0] as GroupDependencyRiskViewModel, dependencyRisk);
111-
testSubject.GroupViewModels[1].Should().Be(hotspotGroupViewModel);
146+
testSubject.GroupViewModels.Should().Contain(hotspotGroupViewModel);
147+
testSubject.GroupViewModels.Should().Contain(taintGroupViewModel);
112148
}
113149

114150
[TestMethod]
@@ -130,6 +166,8 @@ public void Dispose_UnsubscribesFromEvents()
130166
dependencyRisksStore.Received(1).DependencyRisksChanged -= Arg.Any<EventHandler>();
131167
hotspotsReportViewModel.Received(1).HotspotsChanged -= Arg.Any<EventHandler>();
132168
hotspotsReportViewModel.Received(1).Dispose();
169+
taintsReportViewModel.Received(1).TaintsChanged -= Arg.Any<EventHandler>();
170+
taintsReportViewModel.Received(1).Dispose();
133171
}
134172

135173
[TestMethod]
@@ -203,7 +241,7 @@ public void HotspotsChanged_TwoGroups_UpdatesOnlyHotspotGroupViewModels()
203241
var group1 = CreateMockedGroupViewModel(filePath: "myFile.cs");
204242
var group2 = CreateMockedGroupViewModel(filePath: "myFile.cs");
205243
hotspotsReportViewModel.GetHotspotsGroupViewModels().Returns([group1, group2]);
206-
dependencyRisksStore.ClearReceivedCalls();
244+
ClearCallsForReportsViewModels();
207245

208246
hotspotsReportViewModel.HotspotsChanged += Raise.EventWith(testSubject, EventArgs.Empty);
209247

@@ -212,19 +250,53 @@ public void HotspotsChanged_TwoGroups_UpdatesOnlyHotspotGroupViewModels()
212250
testSubject.GroupViewModels.Should().Contain(group2);
213251
VerifyHasGroupsUpdated();
214252
dependencyRisksStore.DidNotReceive().GetAll();
253+
taintsReportViewModel.DidNotReceive().GetTaintsGroupViewModels();
215254
}
216255

217256
[TestMethod]
218257
public void HotspotsChanged_NoGroups_RaisesProperty()
219258
{
220259
hotspotsReportViewModel.GetHotspotsGroupViewModels().Returns([]);
221-
dependencyRisksStore.ClearReceivedCalls();
260+
ClearCallsForReportsViewModels();
222261

223262
hotspotsReportViewModel.HotspotsChanged += Raise.EventWith(testSubject, EventArgs.Empty);
224263

225264
testSubject.GroupViewModels.Should().BeEmpty();
226265
VerifyHasGroupsUpdated();
227266
dependencyRisksStore.DidNotReceive().GetAll();
267+
taintsReportViewModel.DidNotReceive().GetTaintsGroupViewModels();
268+
}
269+
270+
[TestMethod]
271+
public void TaintsChanged_TwoGroups_UpdatesOnlyTaintsGroupViewModels()
272+
{
273+
var group1 = CreateMockedGroupViewModel(filePath: "myFile.cs");
274+
var group2 = CreateMockedGroupViewModel(filePath: "myFile.cs");
275+
taintsReportViewModel.GetTaintsGroupViewModels().Returns([group1, group2]);
276+
ClearCallsForReportsViewModels();
277+
278+
taintsReportViewModel.TaintsChanged += Raise.EventWith(testSubject, EventArgs.Empty);
279+
280+
testSubject.GroupViewModels.Should().HaveCount(2);
281+
testSubject.GroupViewModels.Should().Contain(group1);
282+
testSubject.GroupViewModels.Should().Contain(group2);
283+
VerifyHasGroupsUpdated();
284+
dependencyRisksStore.DidNotReceive().GetAll();
285+
hotspotsReportViewModel.DidNotReceive().GetHotspotsGroupViewModels();
286+
}
287+
288+
[TestMethod]
289+
public void TaintsChanged_NoGroups_RaisesProperty()
290+
{
291+
taintsReportViewModel.GetTaintsGroupViewModels().Returns([]);
292+
ClearCallsForReportsViewModels();
293+
294+
taintsReportViewModel.TaintsChanged += Raise.EventWith(testSubject, EventArgs.Empty);
295+
296+
testSubject.GroupViewModels.Should().BeEmpty();
297+
VerifyHasGroupsUpdated();
298+
dependencyRisksStore.DidNotReceive().GetAll();
299+
hotspotsReportViewModel.DidNotReceive().GetHotspotsGroupViewModels();
228300
}
229301

230302
[TestMethod]
@@ -260,11 +332,12 @@ public void DependencyRisksAddedInStore_DoesNotUpdateHotspots()
260332
var addedRisk = CreateDependencyRisk();
261333
dependencyRisksStore.GetAll().Returns([], [addedRisk]);
262334
testSubject = CreateTestSubject();
263-
hotspotsReportViewModel.ClearReceivedCalls();
335+
ClearCallsForReportsViewModels();
264336

265337
dependencyRisksStore.DependencyRisksChanged += Raise.Event<EventHandler>();
266338

267339
hotspotsReportViewModel.DidNotReceive().GetHotspotsGroupViewModels();
340+
taintsReportViewModel.DidNotReceive().GetTaintsGroupViewModels();
268341
VerifyHasGroupsUpdated();
269342
}
270343

@@ -300,16 +373,17 @@ public void DependencyRisksRemovedFromStore_DoesNotUpdateHotspots()
300373
var initialRisk = CreateDependencyRisk();
301374
dependencyRisksStore.GetAll().Returns([initialRisk], new IDependencyRisk[] { });
302375
testSubject = CreateTestSubject();
303-
hotspotsReportViewModel.ClearReceivedCalls();
376+
ClearCallsForReportsViewModels();
304377

305378
dependencyRisksStore.DependencyRisksChanged += Raise.Event<EventHandler>();
306379

307380
hotspotsReportViewModel.DidNotReceive().GetHotspotsGroupViewModels();
381+
taintsReportViewModel.DidNotReceive().GetTaintsGroupViewModels();
308382
VerifyHasGroupsUpdated();
309383
}
310384

311385
[TestMethod]
312-
public void HasRisks_ReturnsTrue_WhenThereAreRisks()
386+
public void HasGroups_ReturnsTrue_WhenThereAreRisks()
313387
{
314388
MockRisksInStore(CreateDependencyRisk());
315389
testSubject = CreateTestSubject();
@@ -318,7 +392,7 @@ public void HasRisks_ReturnsTrue_WhenThereAreRisks()
318392
}
319393

320394
[TestMethod]
321-
public void HasRisks_ReturnsFalse_WhenThereAreNoRisks() => testSubject.HasGroups.Should().BeFalse();
395+
public void HasGroups_ReturnsFalse_WhenThereAreNoRisks() => testSubject.HasGroups.Should().BeFalse();
322396

323397
[TestMethod]
324398
public void NavigateToLocationCommand_NullParameter_CanExecuteReturnsFalse() => testSubject.NavigateToLocationCommand.CanExecute(null).Should().BeFalse();
@@ -356,6 +430,7 @@ private ReportViewModel CreateTestSubject()
356430
locationNavigator,
357431
hotspotsReportViewModel,
358432
new DependencyRisksReportViewModel(dependencyRisksStore, showDependencyRiskInBrowserHandler, changeDependencyRiskStatusHandler, messageBox),
433+
taintsReportViewModel,
359434
telemetryManager,
360435
threadHandling);
361436
reportViewModel.PropertyChanged += eventHandler;
@@ -391,4 +466,11 @@ private static void VerifyExpectedDependencyRiskGroupViewModel(GroupDependencyRi
391466
}
392467

393468
private void VerifyHasGroupsUpdated() => eventHandler.Received().Invoke(Arg.Any<object>(), Arg.Is<PropertyChangedEventArgs>(p => p.PropertyName == nameof(testSubject.HasGroups)));
469+
470+
private void ClearCallsForReportsViewModels()
471+
{
472+
dependencyRisksStore.ClearReceivedCalls();
473+
taintsReportViewModel.ClearReceivedCalls();
474+
hotspotsReportViewModel.ClearReceivedCalls();
475+
}
394476
}

src/IssueViz.Security/ReportView/IIssueViewModel.cs

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

2323
namespace SonarLint.VisualStudio.IssueVisualization.Security.ReportView;
2424

25+
// TODO by SLVS-2525 introduce type to show in the UI so that user can distinguish between different issue types (e.g., hotspot, taint, normal issues)
2526
public interface IIssueViewModel
2627
{
2728
int? Line { get; }

src/IssueViz.Security/ReportView/ReportViewControl.xaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,60 @@
410410
</TextBlock>
411411
</Grid>
412412
</DataTemplate>
413+
414+
<!--Issue styling-->
415+
<DataTemplate DataType="{x:Type reportView:IssueViewModel}">
416+
<Grid Margin="5,2"
417+
MouseRightButtonDown="TreeViewItem_OnMouseRightButtonDown">
418+
<Grid.ColumnDefinitions>
419+
<ColumnDefinition Width="Auto" />
420+
<ColumnDefinition Width="Auto" />
421+
<ColumnDefinition Width="Auto" />
422+
<ColumnDefinition Width="Auto" />
423+
</Grid.ColumnDefinitions>
424+
<Image Grid.Column="0" Height="17" VerticalAlignment="Center">
425+
<Image.Source>
426+
<MultiBinding
427+
Converter="{StaticResource EnumToImageSourceConverter}" ConverterParameter="Severity">
428+
<Binding Path="Issue.Issue.Severity" />
429+
<Binding RelativeSource="{RelativeSource Self}" />
430+
<Binding Path="ResourceFinder"
431+
RelativeSource="{RelativeSource FindAncestor, AncestorType=UserControl}" />
432+
</MultiBinding>
433+
</Image.Source>
434+
<Image.ToolTip>
435+
<StackPanel>
436+
<TextBlock>
437+
<Run Text="{x:Static res:Resources.DependencyRiskImpactSeverityTooltip}" />
438+
<Run Text="{Binding Issue.Issue.Severity, Mode=OneWay}" />
439+
</TextBlock>
440+
</StackPanel>
441+
</Image.ToolTip>
442+
</Image>
443+
<TextBlock Grid.Column="1"
444+
Margin="2,0,0,2"
445+
Style="{StaticResource LocationTextBlockStyle}">
446+
<Run Text="{Binding Line, Mode=OneWay, StringFormat={}({0}\,}" />
447+
<Run Text="{Binding Path=Column, Mode=OneWay, StringFormat={}{0})}" />
448+
</TextBlock>
449+
<TextBlock Grid.Column="2" Text="{Binding Title}" Margin="5,0,0,0"
450+
Style="{StaticResource IssueTextBlockStyle}" />
451+
<TextBlock Grid.Column="3" Margin="5,0,0,0"
452+
Style="{StaticResource IssueTextBlockStyle}">
453+
<Hyperlink Style="{StaticResource RuleKeyLinkStyle}"
454+
Command="{Binding DataContext.NavigateToRuleDescriptionCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TreeView}}">
455+
<Hyperlink.CommandParameter>
456+
<MultiBinding Converter="{StaticResource NavigateToRuleDescriptionCommandConverter}">
457+
<Binding Path=".RuleInfo.RuleKey" />
458+
<Binding Path=".RuleInfo.IssueId" />
459+
</MultiBinding>
460+
</Hyperlink.CommandParameter>
461+
<TextBlock Text="{Binding RuleInfo.RuleKey}"
462+
Style="{StaticResource CellTextBlockStyle}" />
463+
</Hyperlink>
464+
</TextBlock>
465+
</Grid>
466+
</DataTemplate>
413467
</TreeView.Resources>
414468
</TreeView>
415469

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;
3333
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
3434
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots;
35+
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Taint;
3536
using SonarLint.VisualStudio.IssueVisualization.Security.ReviewStatus;
3637

3738
namespace SonarLint.VisualStudio.IssueVisualization.Security.ReportView;
@@ -45,13 +46,15 @@ internal sealed partial class ReportViewControl : UserControl
4546
public ReportViewModel ReportViewModel { get; }
4647
public IHotspotsReportViewModel HotspotsReportViewModel { get; }
4748
public IDependencyRisksReportViewModel DependencyRisksReportViewModel { get; }
49+
public ITaintsReportViewModel TaintsReportViewModel { get; }
4850
public IResourceFinder ResourceFinder { get; } = new ResourceFinder();
4951

5052
public ReportViewControl(
5153
IActiveSolutionBoundTracker activeSolutionBoundTracker,
5254
IBrowserService browserService,
5355
IHotspotsReportViewModel hotspotsReportViewModel,
5456
IDependencyRisksReportViewModel dependencyRisksReportViewModel,
57+
ITaintsReportViewModel taintsReportViewModel,
5558
INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand,
5659
ILocationNavigator locationNavigator,
5760
ITelemetryManager telemetryManager,
@@ -61,11 +64,13 @@ public ReportViewControl(
6164
this.browserService = browserService;
6265
HotspotsReportViewModel = hotspotsReportViewModel;
6366
DependencyRisksReportViewModel = dependencyRisksReportViewModel;
67+
TaintsReportViewModel = taintsReportViewModel;
6468
ReportViewModel = new ReportViewModel(activeSolutionBoundTracker,
6569
navigateToRuleDescriptionCommand,
6670
locationNavigator,
6771
HotspotsReportViewModel,
6872
DependencyRisksReportViewModel,
73+
TaintsReportViewModel,
6974
telemetryManager,
7075
threadHandling);
7176
InitializeComponent();

0 commit comments

Comments
 (0)