Skip to content

Commit 6fe9904

Browse files
SLVS-2522 Show "View In Browser" in the context menu for hotspots
1 parent 77beb40 commit 6fe9904

File tree

8 files changed

+82
-12
lines changed

8 files changed

+82
-12
lines changed

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

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,20 @@ namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.ReportVie
2828
[TestClass]
2929
public class HotspotViewModelTests
3030
{
31+
private readonly LocalHotspot hotspot = CreateMockedHotspot("csharp:101",
32+
Guid.NewGuid(),
33+
1,
34+
66,
35+
"remove todo comment",
36+
"myClass.cs");
37+
private HotspotViewModel testSubject;
38+
39+
[TestInitialize]
40+
public void TestInitialize() => testSubject = new HotspotViewModel(hotspot);
41+
3142
[TestMethod]
3243
public void Ctor_InitializesPropertiesAsExpected()
3344
{
34-
var hotspot = CreateMockedHotspot("csharp:101",
35-
Guid.NewGuid(),
36-
1,
37-
66,
38-
"remove todo comment",
39-
"myClass.cs");
40-
41-
var testSubject = new HotspotViewModel(hotspot);
42-
4345
testSubject.LocalHotspot.Should().Be(hotspot);
4446
testSubject.RuleInfo.RuleKey.Should().Be(hotspot.Visualization.RuleId);
4547
testSubject.RuleInfo.IssueId.Should().Be(hotspot.Visualization.IssueId);
@@ -50,6 +52,22 @@ public void Ctor_InitializesPropertiesAsExpected()
5052
testSubject.Issue.Should().Be(hotspot.Visualization);
5153
}
5254

55+
[TestMethod]
56+
public void ExistsOnServer_LocalHotspot_ReturnsFalse()
57+
{
58+
hotspot.Visualization.Issue.IssueServerKey.Returns((string)null);
59+
60+
testSubject.ExistsOnServer.Should().BeFalse();
61+
}
62+
63+
[TestMethod]
64+
public void ExistsOnServer_ServerHotspot_ReturnsTrue()
65+
{
66+
hotspot.Visualization.Issue.IssueServerKey.Returns(Guid.NewGuid().ToString());
67+
68+
testSubject.ExistsOnServer.Should().BeTrue();
69+
}
70+
5371
private static LocalHotspot CreateMockedHotspot(
5472
string ruleId,
5573
Guid issueId,

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
*/
2020

2121
using SonarLint.VisualStudio.Core.Analysis;
22+
using SonarLint.VisualStudio.Integration.TestInfrastructure;
2223
using SonarLint.VisualStudio.IssueVisualization.Models;
2324
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
25+
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.ReviewHotspot;
2426
using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore;
2527
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView;
2628
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots;
@@ -33,12 +35,14 @@ public class HotspotsReportViewModelTest
3335
{
3436
private ILocalHotspotsStore localHotspotsStore;
3537
private HotspotsReportViewModel testSubject;
38+
private IReviewHotspotsService reviewHotspotsService;
3639

3740
[TestInitialize]
3841
public void TestInitialize()
3942
{
4043
localHotspotsStore = Substitute.For<ILocalHotspotsStore>();
41-
testSubject = new HotspotsReportViewModel(localHotspotsStore);
44+
reviewHotspotsService = Substitute.For<IReviewHotspotsService>();
45+
testSubject = new HotspotsReportViewModel(localHotspotsStore, reviewHotspotsService);
4246
}
4347

4448
[TestMethod]
@@ -114,6 +118,16 @@ public void HotspotsChanged_RaisedOnStoreIssuesChanged()
114118
raised.Should().BeTrue();
115119
}
116120

121+
[TestMethod]
122+
public async Task ShowHotspotInBrowserAsync_CallsHandler()
123+
{
124+
var hotspot = CreateMockedHotspot("myFile.cs");
125+
126+
await testSubject.ShowHotspotInBrowserAsync(hotspot);
127+
128+
reviewHotspotsService.Received(1).OpenHotspotAsync(hotspot.Visualization.Issue.IssueServerKey).IgnoreAwaitForAssert();
129+
}
130+
117131
private static LocalHotspot CreateMockedHotspot(string filePath)
118132
{
119133
var analysisIssueVisualization = Substitute.For<IAnalysisIssueVisualization>();

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ namespace SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots
2727
internal class HotspotViewModel : ViewModelBase, IAnalysisIssueViewModel
2828
{
2929
public LocalHotspot LocalHotspot { get; }
30+
public bool ExistsOnServer => LocalHotspot.Visualization.Issue.IssueServerKey != null;
3031

3132
public HotspotViewModel(LocalHotspot localHotspot)
3233
{

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using System.Collections.ObjectModel;
2222
using System.ComponentModel.Composition;
2323
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
24+
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.ReviewHotspot;
2425
using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore;
2526

2627
namespace SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots;
@@ -29,6 +30,8 @@ internal interface IHotspotsReportViewModel : IDisposable
2930
{
3031
ObservableCollection<IGroupViewModel> GetHotspotsGroupViewModels();
3132

33+
Task ShowHotspotInBrowserAsync(LocalHotspot localHotspot);
34+
3235
event EventHandler HotspotsChanged;
3336
}
3437

@@ -37,11 +40,13 @@ internal interface IHotspotsReportViewModel : IDisposable
3740
internal sealed class HotspotsReportViewModel : IHotspotsReportViewModel
3841
{
3942
private readonly ILocalHotspotsStore hotspotsStore;
43+
private readonly IReviewHotspotsService reviewHotspotsService;
4044

4145
[ImportingConstructor]
42-
public HotspotsReportViewModel(ILocalHotspotsStore hotspotsStore)
46+
public HotspotsReportViewModel(ILocalHotspotsStore hotspotsStore, IReviewHotspotsService reviewHotspotsService)
4347
{
4448
this.hotspotsStore = hotspotsStore;
49+
this.reviewHotspotsService = reviewHotspotsService;
4550
hotspotsStore.IssuesChanged += HotspotsStore_IssuesChanged;
4651
}
4752

@@ -55,6 +60,8 @@ public ObservableCollection<IGroupViewModel> GetHotspotsGroupViewModels()
5560
return GetGroupViewModel(hotspots);
5661
}
5762

63+
public async Task ShowHotspotInBrowserAsync(LocalHotspot localHotspot) => await reviewHotspotsService.OpenHotspotAsync(localHotspot.Visualization.Issue.IssueServerKey);
64+
5865
private static ObservableCollection<IGroupViewModel> GetGroupViewModel(IEnumerable<IIssueViewModel> issueViewModels)
5966
{
6067
var issuesByFileGrouping = issueViewModels.GroupBy(vm => vm.FilePath);

src/IssueViz.Security/ReportView/ReportViewControl.xaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@
191191
<MenuItem Click="ViewDependencyRiskInBrowser_OnClick" Style="{StaticResource ViewIssueInBrowserMenuItemStyle}" />
192192
</ContextMenu>
193193

194+
<ContextMenu x:Key="HotspotContextMenu"
195+
Visibility="{Binding Path=ExistsOnServer, Converter={StaticResource TrueToVisibleConverter}}">
196+
<MenuItem Click="ViewHotspotInBrowser_OnClick"
197+
Header="{x:Static res:Resources.ViewHotspotInBrowser}"
198+
Style="{StaticResource ViewIssueInBrowserMenuItemStyle}" />
199+
</ContextMenu>
200+
194201
<Style x:Key="ResolutionFilterBorderStyle" TargetType="Button">
195202
<Setter Property="Margin" Value="5, 3"/>
196203
<Setter Property="Padding" Value="2"/>
@@ -362,7 +369,10 @@
362369

363370
<!--Hotspots styling-->
364371
<DataTemplate DataType="{x:Type hotspots:HotspotViewModel}">
365-
<Grid Margin="5,2" MouseRightButtonDown="TreeViewItem_OnMouseRightButtonDown" ToolTip="{x:Static res:Resources.HotspotsControl_NavigationTooltip}">
372+
<Grid Margin="5,2"
373+
ContextMenu="{StaticResource HotspotContextMenu}"
374+
MouseRightButtonDown="TreeViewItem_OnMouseRightButtonDown"
375+
ToolTip="{x:Static res:Resources.HotspotsControl_NavigationTooltip}">
366376
<Grid.ColumnDefinitions>
367377
<ColumnDefinition Width="Auto" />
368378
<ColumnDefinition Width="Auto" />

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,12 @@ private static void ExecuteCommandIfValid(ICommand command, object parameter)
180180
command.Execute(parameter);
181181
}
182182
}
183+
184+
private async void ViewHotspotInBrowser_OnClick(object sender, RoutedEventArgs e)
185+
{
186+
if (ReportViewModel.SelectedItem is HotspotViewModel hotspotViewModel)
187+
{
188+
await HotspotsReportViewModel.ShowHotspotInBrowserAsync(hotspotViewModel.LocalHotspot);
189+
}
190+
}
183191
}

src/IssueViz.Security/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/IssueViz.Security/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,9 @@ Please check the logs for more details.</value>
242242
<data name="ViewIssueInSonarQubeServer" xml:space="preserve">
243243
<value>View in SonarQube Server</value>
244244
</data>
245+
<data name="ViewHotspotInBrowser" xml:space="preserve">
246+
<value>View security hotspot in browser</value>
247+
</data>
245248
<data name="ReportViewToolWindowCaption" xml:space="preserve">
246249
<value>SonarQube Dependency Risks</value>
247250
</data>

0 commit comments

Comments
 (0)