Skip to content

Commit 0ccb3a3

Browse files
SLVS-2522 Implement changing the status of a hotspot
1 parent 6fe9904 commit 0ccb3a3

File tree

4 files changed

+155
-5
lines changed

4 files changed

+155
-5
lines changed

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

Lines changed: 91 additions & 4 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 System.Windows;
22+
using SonarLint.VisualStudio.Core;
2123
using SonarLint.VisualStudio.Core.Analysis;
2224
using SonarLint.VisualStudio.Integration.TestInfrastructure;
2325
using SonarLint.VisualStudio.IssueVisualization.Models;
@@ -33,22 +35,28 @@ namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.ReportVie
3335
[TestClass]
3436
public class HotspotsReportViewModelTest
3537
{
38+
private readonly LocalHotspot serverHotspot = CreateMockedHotspot("myFile.cs", "serverKey");
3639
private ILocalHotspotsStore localHotspotsStore;
37-
private HotspotsReportViewModel testSubject;
40+
private IMessageBox messageBox;
3841
private IReviewHotspotsService reviewHotspotsService;
42+
private HotspotsReportViewModel testSubject;
3943

4044
[TestInitialize]
4145
public void TestInitialize()
4246
{
4347
localHotspotsStore = Substitute.For<ILocalHotspotsStore>();
4448
reviewHotspotsService = Substitute.For<IReviewHotspotsService>();
45-
testSubject = new HotspotsReportViewModel(localHotspotsStore, reviewHotspotsService);
49+
messageBox = Substitute.For<IMessageBox>();
50+
testSubject = new HotspotsReportViewModel(localHotspotsStore, reviewHotspotsService, messageBox);
4651
}
4752

4853
[TestMethod]
4954
public void MefCtor_CheckIsExported() =>
5055
MefTestHelpers.CheckTypeCanBeImported<HotspotsReportViewModel, IHotspotsReportViewModel>(
51-
MefTestHelpers.CreateExport<ILocalHotspotsStore>());
56+
MefTestHelpers.CreateExport<ILocalHotspotsStore>(),
57+
MefTestHelpers.CreateExport<IReviewHotspotsService>(),
58+
MefTestHelpers.CreateExport<IMessageBox>()
59+
);
5260

5361
[TestMethod]
5462
public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<HotspotsReportViewModel>();
@@ -128,12 +136,83 @@ public async Task ShowHotspotInBrowserAsync_CallsHandler()
128136
reviewHotspotsService.Received(1).OpenHotspotAsync(hotspot.Visualization.Issue.IssueServerKey).IgnoreAwaitForAssert();
129137
}
130138

131-
private static LocalHotspot CreateMockedHotspot(string filePath)
139+
[TestMethod]
140+
public async Task GetAllowedStatusesAsync_ChangeStatusPermitted_ReturnsListOfAllowedStatuses()
141+
{
142+
var allowedStatuses = new List<HotspotStatus> { HotspotStatus.Fixed, HotspotStatus.ToReview };
143+
MockChangeStatusPermitted(serverHotspot.Visualization.Issue.IssueServerKey, allowedStatuses);
144+
145+
var result = await testSubject.GetAllowedStatusesAsync(new HotspotViewModel(serverHotspot));
146+
147+
result.Should().BeEquivalentTo(allowedStatuses);
148+
messageBox.DidNotReceive().Show(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<MessageBoxButton>(), Arg.Any<MessageBoxImage>());
149+
}
150+
151+
[TestMethod]
152+
public async Task GetAllowedStatusesAsync_ChangeStatusNotPermitted_ShowsMessageBoxAndReturnsNull()
153+
{
154+
var reason = "Not permitted";
155+
MockChangeStatusNotPermitted(serverHotspot.Visualization.Issue.IssueServerKey, reason);
156+
157+
var result = await testSubject.GetAllowedStatusesAsync(new HotspotViewModel(serverHotspot));
158+
159+
result.Should().BeNull();
160+
messageBox.Received(1).Show(Arg.Is<string>(x => x == string.Format(Resources.ReviewHotspotWindow_CheckReviewPermittedFailureMessage, reason)),
161+
Arg.Is<string>(x => x == Resources.ReviewHotspotWindow_FailureTitle), MessageBoxButton.OK, MessageBoxImage.Error);
162+
}
163+
164+
[TestMethod]
165+
public async Task GetAllowedStatusesAsync_NoStatusSelected_ShowsMessageBoxAndReturnsNull()
166+
{
167+
var result = await testSubject.GetAllowedStatusesAsync(null);
168+
169+
result.Should().BeNull();
170+
messageBox.Received(1).Show(
171+
Arg.Is<string>(x => x == string.Format(Resources.ReviewHotspotWindow_CheckReviewPermittedFailureMessage, Resources.ReviewHotspotWindow_NoStatusSelectedFailureMessage)),
172+
Arg.Is<string>(x => x == Resources.ReviewHotspotWindow_FailureTitle), MessageBoxButton.OK, MessageBoxImage.Error);
173+
}
174+
175+
[TestMethod]
176+
[DataRow(HotspotStatus.Fixed)]
177+
[DataRow(HotspotStatus.ToReview)]
178+
[DataRow(HotspotStatus.Acknowledged)]
179+
[DataRow(HotspotStatus.Safe)]
180+
public async Task ChangeHotspotStatusAsync_Succeeds_ReturnsTrue(HotspotStatus newStatus)
181+
{
182+
var hotspotViewModel = new HotspotViewModel(serverHotspot);
183+
MockReviewHotspot(serverHotspot.Visualization.Issue.IssueServerKey, newStatus, true);
184+
185+
var result = await testSubject.ChangeHotspotStatusAsync(hotspotViewModel, newStatus);
186+
187+
result.Should().BeTrue();
188+
reviewHotspotsService.Received(1).ReviewHotspotAsync(serverHotspot.Visualization.Issue.IssueServerKey, newStatus).IgnoreAwaitForAssert();
189+
messageBox.DidNotReceive().Show(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<MessageBoxButton>(), Arg.Any<MessageBoxImage>());
190+
}
191+
192+
[TestMethod]
193+
[DataRow(HotspotStatus.Fixed)]
194+
[DataRow(HotspotStatus.ToReview)]
195+
[DataRow(HotspotStatus.Acknowledged)]
196+
[DataRow(HotspotStatus.Safe)]
197+
public async Task ChangeHotspotStatusAsync_Fails_ShowsMessageBox(HotspotStatus newStatus)
198+
{
199+
var hotspotViewModel = new HotspotViewModel(serverHotspot);
200+
MockReviewHotspot(serverHotspot.Visualization.Issue.IssueServerKey, newStatus, false);
201+
202+
var result = await testSubject.ChangeHotspotStatusAsync(hotspotViewModel, newStatus);
203+
204+
result.Should().BeFalse();
205+
messageBox.Received(1).Show(Arg.Is<string>(x => x == Resources.ReviewHotspotWindow_ReviewFailureMessage), Arg.Is<string>(x => x == Resources.ReviewHotspotWindow_FailureTitle),
206+
MessageBoxButton.OK, MessageBoxImage.Error);
207+
}
208+
209+
private static LocalHotspot CreateMockedHotspot(string filePath, string hotspotKey = null)
132210
{
133211
var analysisIssueVisualization = Substitute.For<IAnalysisIssueVisualization>();
134212
var analysisIssueBase = Substitute.For<IAnalysisIssueBase>();
135213
analysisIssueBase.PrimaryLocation.FilePath.Returns(filePath);
136214
analysisIssueVisualization.Issue.Returns(analysisIssueBase);
215+
analysisIssueVisualization.Issue.IssueServerKey.Returns(hotspotKey);
137216

138217
return new LocalHotspot(analysisIssueVisualization, default, default);
139218
}
@@ -150,4 +229,12 @@ private static void VerifyExpectedHotspotGroupViewModel(GroupFileViewModel group
150229
groupFileVm.FilteredIssues.Should().ContainSingle(vm => ((HotspotViewModel)vm).LocalHotspot == expectedHotspot);
151230
}
152231
}
232+
233+
private void MockChangeStatusPermitted(string hotspotKey, List<HotspotStatus> allowedStatuses) =>
234+
reviewHotspotsService.CheckReviewHotspotPermittedAsync(hotspotKey).Returns(new ReviewHotspotPermittedArgs(allowedStatuses));
235+
236+
private void MockChangeStatusNotPermitted(string hotspotKey, string reason) =>
237+
reviewHotspotsService.CheckReviewHotspotPermittedAsync(hotspotKey).Returns(new ReviewHotspotNotPermittedArgs(reason));
238+
239+
private void MockReviewHotspot(string hotspotKey, HotspotStatus newStatus, bool succeeded) => reviewHotspotsService.ReviewHotspotAsync(hotspotKey, newStatus).Returns(succeeded);
153240
}

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020

2121
using System.Collections.ObjectModel;
2222
using System.ComponentModel.Composition;
23+
using System.Windows;
24+
using SonarLint.VisualStudio.Core;
25+
using SonarLint.VisualStudio.Core.Analysis;
2326
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
2427
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.ReviewHotspot;
2528
using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore;
@@ -32,6 +35,10 @@ internal interface IHotspotsReportViewModel : IDisposable
3235

3336
Task ShowHotspotInBrowserAsync(LocalHotspot localHotspot);
3437

38+
Task<IEnumerable<HotspotStatus>> GetAllowedStatusesAsync(HotspotViewModel selectedHotspotViewModel);
39+
40+
Task<bool> ChangeHotspotStatusAsync(HotspotViewModel selectedHotspotViewModel, HotspotStatus newStatus);
41+
3542
event EventHandler HotspotsChanged;
3643
}
3744

@@ -41,12 +48,14 @@ internal sealed class HotspotsReportViewModel : IHotspotsReportViewModel
4148
{
4249
private readonly ILocalHotspotsStore hotspotsStore;
4350
private readonly IReviewHotspotsService reviewHotspotsService;
51+
private readonly IMessageBox messageBox;
4452

4553
[ImportingConstructor]
46-
public HotspotsReportViewModel(ILocalHotspotsStore hotspotsStore, IReviewHotspotsService reviewHotspotsService)
54+
public HotspotsReportViewModel(ILocalHotspotsStore hotspotsStore, IReviewHotspotsService reviewHotspotsService, IMessageBox messageBox)
4755
{
4856
this.hotspotsStore = hotspotsStore;
4957
this.reviewHotspotsService = reviewHotspotsService;
58+
this.messageBox = messageBox;
5059
hotspotsStore.IssuesChanged += HotspotsStore_IssuesChanged;
5160
}
5261

@@ -62,6 +71,33 @@ public ObservableCollection<IGroupViewModel> GetHotspotsGroupViewModels()
6271

6372
public async Task ShowHotspotInBrowserAsync(LocalHotspot localHotspot) => await reviewHotspotsService.OpenHotspotAsync(localHotspot.Visualization.Issue.IssueServerKey);
6473

74+
public async Task<IEnumerable<HotspotStatus>> GetAllowedStatusesAsync(HotspotViewModel selectedHotspotViewModel)
75+
{
76+
var response = selectedHotspotViewModel == null
77+
? new ReviewHotspotNotPermittedArgs(Resources.ReviewHotspotWindow_NoStatusSelectedFailureMessage)
78+
: await reviewHotspotsService.CheckReviewHotspotPermittedAsync(selectedHotspotViewModel.LocalHotspot.Visualization.Issue.IssueServerKey);
79+
switch (response)
80+
{
81+
case ReviewHotspotPermittedArgs reviewHotspotPermittedArgs:
82+
return reviewHotspotPermittedArgs.AllowedStatuses;
83+
case ReviewHotspotNotPermittedArgs reviewHotspotNotPermittedArgs:
84+
messageBox.Show(string.Format(Resources.ReviewHotspotWindow_CheckReviewPermittedFailureMessage, reviewHotspotNotPermittedArgs.Reason), Resources.ReviewHotspotWindow_FailureTitle,
85+
MessageBoxButton.OK, MessageBoxImage.Error);
86+
break;
87+
}
88+
return null;
89+
}
90+
91+
public async Task<bool> ChangeHotspotStatusAsync(HotspotViewModel selectedHotspotViewModel, HotspotStatus newStatus)
92+
{
93+
var wasChanged = await reviewHotspotsService.ReviewHotspotAsync(selectedHotspotViewModel.LocalHotspot.Visualization.Issue.IssueServerKey, newStatus);
94+
if (!wasChanged)
95+
{
96+
messageBox.Show(Resources.ReviewHotspotWindow_ReviewFailureMessage, Resources.ReviewHotspotWindow_FailureTitle, MessageBoxButton.OK, MessageBoxImage.Error);
97+
}
98+
return wasChanged;
99+
}
100+
65101
private static ObservableCollection<IGroupViewModel> GetGroupViewModel(IEnumerable<IIssueViewModel> issueViewModels)
66102
{
67103
var issuesByFileGrouping = issueViewModels.GroupBy(vm => vm.FilePath);

src/IssueViz.Security/ReportView/ReportViewControl.xaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@
193193

194194
<ContextMenu x:Key="HotspotContextMenu"
195195
Visibility="{Binding Path=ExistsOnServer, Converter={StaticResource TrueToVisibleConverter}}">
196+
<MenuItem Header="{x:Static res:Resources.ChangeStatusMenuItem}"
197+
Click="ChangeHotspotStatusMenuItem_OnClick"
198+
Style="{StaticResource ChangeStatusMenuItemStyle}"/>
196199
<MenuItem Click="ViewHotspotInBrowser_OnClick"
197200
Header="{x:Static res:Resources.ViewHotspotInBrowser}"
198201
Style="{StaticResource ViewIssueInBrowserMenuItemStyle}" />

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
using System.Windows.Navigation;
2727
using SonarLint.VisualStudio.ConnectedMode.UI;
2828
using SonarLint.VisualStudio.Core;
29+
using SonarLint.VisualStudio.Core.Analysis;
2930
using SonarLint.VisualStudio.Core.Binding;
3031
using SonarLint.VisualStudio.Core.Telemetry;
3132
using SonarLint.VisualStudio.IssueVisualization.Editor;
3233
using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;
3334
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
35+
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots.HotspotsList.ViewModels;
3436
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots;
3537
using SonarLint.VisualStudio.IssueVisualization.Security.ReviewStatus;
38+
using HotspotViewModel = SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots.HotspotViewModel;
3639

3740
namespace SonarLint.VisualStudio.IssueVisualization.Security.ReportView;
3841

@@ -188,4 +191,25 @@ private async void ViewHotspotInBrowser_OnClick(object sender, RoutedEventArgs e
188191
await HotspotsReportViewModel.ShowHotspotInBrowserAsync(hotspotViewModel.LocalHotspot);
189192
}
190193
}
194+
195+
private async void ChangeHotspotStatusMenuItem_OnClick(object sender, RoutedEventArgs e)
196+
{
197+
if (sender is not MenuItem { DataContext: HotspotViewModel hotspotViewModel } ||
198+
await HotspotsReportViewModel.GetAllowedStatusesAsync(hotspotViewModel) is not { } allowedStatuses)
199+
{
200+
return;
201+
}
202+
203+
var changeHotspotStatusViewModel = new ChangeHotspotStatusViewModel(hotspotViewModel.LocalHotspot.HotspotStatus, allowedStatuses);
204+
var dialog = new ChangeStatusWindow(changeHotspotStatusViewModel, browserService, activeSolutionBoundTracker);
205+
if (dialog.ShowDialog(Application.Current.MainWindow) is true)
206+
{
207+
var newStatus = changeHotspotStatusViewModel.SelectedStatusViewModel.GetCurrentStatus<HotspotStatus>();
208+
var wasChanged = await HotspotsReportViewModel.ChangeHotspotStatusAsync(hotspotViewModel, newStatus);
209+
if (wasChanged && newStatus is HotspotStatus.Fixed or HotspotStatus.Safe)
210+
{
211+
ReportViewModel.GroupViewModels.ToList().ForEach(vm => vm.FilteredIssues.Remove(hotspotViewModel));
212+
}
213+
}
214+
}
191215
}

0 commit comments

Comments
 (0)