Skip to content

Commit 903f5b8

Browse files
SLVS-2526 Implement show rule help (#6421)
1 parent 973b20e commit 903f5b8

File tree

6 files changed

+78
-12
lines changed

6 files changed

+78
-12
lines changed

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

Lines changed: 8 additions & 4 deletions
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.IssueVisualizationControl.ViewModels.Commands;
2728
using SonarLint.VisualStudio.IssueVisualization.Models;
2829
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
2930
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
@@ -43,6 +44,7 @@ public class ReportViewModelTest
4344
private ILocalHotspotsStore localHotspotsStore;
4445
private IShowDependencyRiskInBrowserHandler showDependencyRiskInBrowserHandler;
4546
private IChangeDependencyRiskStatusHandler changeDependencyRiskStatusHandler;
47+
private INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand;
4648
private IMessageBox messageBox;
4749
private ITelemetryManager telemetryManager;
4850
private IThreadHandling threadHandling;
@@ -56,6 +58,7 @@ public void Initialize()
5658
localHotspotsStore = Substitute.For<ILocalHotspotsStore>();
5759
showDependencyRiskInBrowserHandler = Substitute.For<IShowDependencyRiskInBrowserHandler>();
5860
changeDependencyRiskStatusHandler = Substitute.For<IChangeDependencyRiskStatusHandler>();
61+
navigateToRuleDescriptionCommand = Substitute.For<INavigateToRuleDescriptionCommand>();
5962
messageBox = Substitute.For<IMessageBox>();
6063
telemetryManager = Substitute.For<ITelemetryManager>();
6164
threadHandling = Substitute.ForPartsOf<NoOpThreadHandler>();
@@ -74,6 +77,9 @@ public void Class_SubscribesToEvents()
7477
dependencyRisksStore.Received(1).DependencyRisksChanged += Arg.Any<EventHandler>();
7578
}
7679

80+
[TestMethod]
81+
public void Class_InitializesProperties() => testSubject.NavigateToRuleDescriptionCommand.Should().BeSameAs(navigateToRuleDescriptionCommand);
82+
7783
[TestMethod]
7884
public void Ctor_InitializesDependencyRisks()
7985
{
@@ -204,10 +210,7 @@ public async Task ChangeStatusAsync_NullTransition_DoesNotCallHandler_ShowsMessa
204210
}
205211

206212
[TestMethod]
207-
public void SelectedItem_Initially_IsNull()
208-
{
209-
testSubject.SelectedItem.Should().BeNull();
210-
}
213+
public void SelectedItem_Initially_IsNull() => testSubject.SelectedItem.Should().BeNull();
211214

212215
[TestMethod]
213216
public void SelectedItem_SetToDependencyRiskViewModel_CallsTelemetryForDependencyRisk()
@@ -469,6 +472,7 @@ private ReportViewModel CreateTestSubject()
469472
localHotspotsStore,
470473
showDependencyRiskInBrowserHandler,
471474
changeDependencyRiskStatusHandler,
475+
navigateToRuleDescriptionCommand,
472476
messageBox,
473477
telemetryManager,
474478
threadHandling);

src/IssueViz.Security/ReportView/ReportViewControl.xaml

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
1313
xmlns:vsimagecatalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
1414
xmlns:core="clr-namespace:SonarLint.VisualStudio.Core;assembly=SonarLint.VisualStudio.Core"
15+
xmlns:commands="clr-namespace:SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;assembly=SonarLint.VisualStudio.IssueVisualization"
1516
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=ReportViewModel}">
1617
<UserControl.Resources>
1718
<ResourceDictionary>
@@ -25,6 +26,8 @@
2526
<reportView:EnumToImageSourceConverter x:Key="EnumToImageSourceConverter" />
2627
<reportView:FileNameToMonikerConverter x:Key="FileNameToMonikerConverter" />
2728
<hotspots:HotspotTooltipConverter x:Key="HotspotTooltipConverter" />
29+
<commands:NavigateToRuleDescriptionCommandConverter x:Key="NavigateToRuleDescriptionCommandConverter" />
30+
2831
<wpf:BoolToVisibilityConverter x:Key="TrueToVisibleConverter" FalseValue="Collapsed" TrueValue="Visible" />
2932
<wpf:BoolToVisibilityConverter x:Key="TrueToCollapsedConverter" FalseValue="Visible" TrueValue="Collapsed"/>
3033
<wpf:EnglishPluralizationConverter x:Key="EnglishPluralizationConverter" />
@@ -54,6 +57,7 @@
5457
<Setter Property="IsExpanded" Value="True" />
5558
<Setter Property="Foreground" Value="{DynamicResource {x:Static vsShell:VsBrushes.BrandedUITextKey}}" />
5659
<EventSetter Event="PreviewMouseUp" Handler="TreeViewItem_OnPreviewMouseUp"/>
60+
<EventSetter Event="MouseDoubleClick" Handler="TreeViewItem_MouseDoubleClick"/>
5761
<Setter Property="Template">
5862
<Setter.Value>
5963
<ControlTemplate TargetType="TreeViewItem">
@@ -134,6 +138,18 @@
134138
<Setter Property="FontSize"
135139
Value="{DynamicResource {x:Static vsShell:VsFonts.Environment90PercentFontSizeKey}}" />
136140
<Setter Property="Foreground" Value="{DynamicResource {x:Static vsShell:VsBrushes.InactiveCaptionTextKey}}" />
141+
<Style.Triggers>
142+
<MultiDataTrigger>
143+
<MultiDataTrigger.Conditions>
144+
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, Path=IsSelected}" Value="True" />
145+
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, Path=IsSelectionActive}" Value="True" />
146+
</MultiDataTrigger.Conditions>
147+
<MultiDataTrigger.Setters>
148+
<Setter Property="Foreground"
149+
Value="{DynamicResource {x:Static vsTheming:TreeViewColors.SelectedItemActiveTextBrushKey}}" />
150+
</MultiDataTrigger.Setters>
151+
</MultiDataTrigger>
152+
</Style.Triggers>
137153
</Style>
138154

139155
<Style x:Key="IssueTypeTextBlockStyle" TargetType="TextBlock">
@@ -154,6 +170,16 @@
154170
<Trigger Property="IsMouseOver" Value="True">
155171
<Setter Property="TextDecorations" Value="Underline" />
156172
</Trigger>
173+
<MultiDataTrigger>
174+
<MultiDataTrigger.Conditions>
175+
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, Path=IsSelected}" Value="True" />
176+
<Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, Path=IsSelectionActive}" Value="True" />
177+
</MultiDataTrigger.Conditions>
178+
<MultiDataTrigger.Setters>
179+
<Setter Property="Foreground"
180+
Value="{DynamicResource {x:Static vsTheming:TreeViewColors.SelectedItemActiveTextBrushKey}}" />
181+
</MultiDataTrigger.Setters>
182+
</MultiDataTrigger>
157183
</Style.Triggers>
158184
</Style>
159185

@@ -370,7 +396,14 @@
370396
Style="{StaticResource IssueTextBlockStyle}" />
371397
<TextBlock Grid.Column="3" Margin="5,0,0,0"
372398
Style="{StaticResource IssueTextBlockStyle}">
373-
<Hyperlink Style="{StaticResource RuleKeyLinkStyle}">
399+
<Hyperlink Style="{StaticResource RuleKeyLinkStyle}"
400+
Command="{Binding DataContext.NavigateToRuleDescriptionCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TreeView}}">
401+
<Hyperlink.CommandParameter>
402+
<MultiBinding Converter="{StaticResource NavigateToRuleDescriptionCommandConverter}">
403+
<Binding Path=".RuleInfo.RuleKey" />
404+
<Binding Path=".RuleInfo.IssueId" />
405+
</MultiBinding>
406+
</Hyperlink.CommandParameter>
374407
<TextBlock Text="{Binding RuleInfo.RuleKey}"
375408
Style="{StaticResource CellTextBlockStyle}" />
376409
</Hyperlink>

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

Lines changed: 17 additions & 0 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.IssueVisualizationControl.ViewModels.Commands;
3132
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
3233
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
3334
using SonarLint.VisualStudio.IssueVisualization.Security.ReviewStatus;
@@ -50,6 +51,7 @@ public ReportViewControl(
5051
ILocalHotspotsStore hotspotsStore,
5152
IShowDependencyRiskInBrowserHandler showDependencyRiskInBrowserHandler,
5253
IChangeDependencyRiskStatusHandler changeDependencyRiskStatusHandler,
54+
INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand,
5355
IMessageBox messageBox,
5456
ITelemetryManager telemetryManager,
5557
IThreadHandling threadHandling)
@@ -61,6 +63,7 @@ public ReportViewControl(
6163
hotspotsStore,
6264
showDependencyRiskInBrowserHandler,
6365
changeDependencyRiskStatusHandler,
66+
navigateToRuleDescriptionCommand,
6467
messageBox,
6568
telemetryManager,
6669
threadHandling);
@@ -144,4 +147,18 @@ private async void ChangeScaStatusMenuItem_OnClick(object sender, RoutedEventArg
144147
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) => ReportViewModel.SelectedItem = e.NewValue as IIssueViewModel;
145148

146149
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) => browserService.Navigate(e.Uri.AbsoluteUri);
150+
151+
private void TreeViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
152+
{
153+
if ((sender as FrameworkElement)?.DataContext is not IIssueViewModel issueViewModel)
154+
{
155+
return;
156+
}
157+
158+
var commandParam = new NavigateToRuleDescriptionCommandParam { FullRuleKey = issueViewModel.RuleInfo.RuleKey, IssueId = issueViewModel.RuleInfo.IssueId };
159+
if (ReportViewModel.NavigateToRuleDescriptionCommand.CanExecute(commandParam))
160+
{
161+
ReportViewModel.NavigateToRuleDescriptionCommand.Execute(commandParam);
162+
}
163+
}
147164
}

src/IssueViz.Security/ReportView/ReportViewModel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
using SonarLint.VisualStudio.Core.Analysis;
2626
using SonarLint.VisualStudio.Core.Binding;
2727
using SonarLint.VisualStudio.Core.Telemetry;
28+
using SonarLint.VisualStudio.IssueVisualization.IssueVisualizationControl.ViewModels.Commands;
2829
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
2930
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
3031
using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore;
@@ -49,10 +50,12 @@ public ReportViewModel(
4950
ILocalHotspotsStore hotspotsStore,
5051
IShowDependencyRiskInBrowserHandler showDependencyRiskInBrowserHandler,
5152
IChangeDependencyRiskStatusHandler changeDependencyRiskStatusHandler,
53+
INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand,
5254
IMessageBox messageBox,
5355
ITelemetryManager telemetryManager,
5456
IThreadHandling threadHandling) : base(activeSolutionBoundTracker)
5557
{
58+
NavigateToRuleDescriptionCommand = navigateToRuleDescriptionCommand;
5659
this.dependencyRisksStore = dependencyRisksStore;
5760
this.hotspotsStore = hotspotsStore;
5861
this.showDependencyRiskInBrowserHandler = showDependencyRiskInBrowserHandler;
@@ -68,6 +71,7 @@ public ReportViewModel(
6871

6972
public ObservableCollection<IGroupViewModel> GroupViewModels { get; } = [];
7073
public bool HasGroups => GroupViewModels.Count > 0;
74+
public INavigateToRuleDescriptionCommand NavigateToRuleDescriptionCommand { get; }
7175

7276
public IIssueViewModel SelectedItem
7377
{

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.IssueVisualizationControl.ViewModels.Commands;
2829
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
2930
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
3031

@@ -48,6 +49,7 @@ public ReportViewToolWindow(IServiceProvider serviceProvider)
4849
componentModel?.GetService<ILocalHotspotsStore>(),
4950
componentModel?.GetService<IShowDependencyRiskInBrowserHandler>(),
5051
componentModel?.GetService<IChangeDependencyRiskStatusHandler>(),
52+
componentModel?.GetService<INavigateToRuleDescriptionCommand>(),
5153
componentModel?.GetService<IMessageBox>(),
5254
componentModel?.GetService<ITelemetryManager>(),
5355
componentModel?.GetService<IThreadHandling>()

src/IssueViz/IssueVisualizationControl/ViewModels/Commands/NavigateToRuleDescriptionCommand.cs

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

21-
using System;
2221
using System.ComponentModel.Composition;
23-
using System.Diagnostics;
2422
using System.Globalization;
2523
using System.Windows.Data;
2624
using System.Windows.Input;
@@ -48,13 +46,13 @@ public NavigateToRuleDescriptionCommand(IEducation educationService)
4846
}
4947
},
5048
parameter => parameter is NavigateToRuleDescriptionCommandParam s &&
51-
!string.IsNullOrEmpty(s.FullRuleKey) &&
52-
SonarCompositeRuleId.TryParse(s.FullRuleKey, out var _))
49+
!string.IsNullOrEmpty(s.FullRuleKey) &&
50+
SonarCompositeRuleId.TryParse(s.FullRuleKey, out var _))
5351
{
5452
}
5553
}
5654

57-
internal class NavigateToRuleDescriptionCommandParam
55+
public class NavigateToRuleDescriptionCommandParam
5856
{
5957
/// <summary>
6058
/// The id of the issue that comes from SlCore
@@ -65,7 +63,11 @@ internal class NavigateToRuleDescriptionCommandParam
6563

6664
public class NavigateToRuleDescriptionCommandConverter : IMultiValueConverter
6765
{
68-
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
66+
public object Convert(
67+
object[] values,
68+
Type targetType,
69+
object parameter,
70+
CultureInfo culture)
6971
{
7072
if (values.Length == 2 && values[0] is string && (values[1] is Guid || values[1] == null))
7173
{
@@ -74,7 +76,11 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
7476
return null;
7577
}
7678

77-
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
79+
public object[] ConvertBack(
80+
object value,
81+
Type[] targetTypes,
82+
object parameter,
83+
CultureInfo culture)
7884
{
7985
Debug.Fail("We should not hit here");
8086
return null;

0 commit comments

Comments
 (0)