From 782273f5d7e4762232b83c1850687b98169424c1 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Thu, 9 Jan 2025 09:29:19 +0100 Subject: [PATCH] SLVS-1729 Promote SQC free plan inside the IDE extensions (#5940) --- .../UI/ConnectedModeServicesTests.cs | 10 +-- src/ConnectedMode/UI/ConnectedModeServices.cs | 7 +- .../ManageConnectionsDialog.xaml.cs | 2 +- .../UI/Resources/UiResources.Designer.cs | 20 +----- .../UI/Resources/UiResources.resx | 8 +-- .../ServerSelectionDialog.xaml | 16 ++--- .../ServerSelectionDialog.xaml.cs | 44 ++++++------ src/Core/Telemetry/ITelemetryManager.cs | 26 ++++--- src/Core/Telemetry/TelemetryLinks.cs | 28 ++++++++ .../Telemetry/TelemetryManagerTests.cs | 69 +++++++++---------- src/Integration/Telemetry/TelemetryManager.cs | 41 +++-------- 11 files changed, 127 insertions(+), 144 deletions(-) create mode 100644 src/Core/Telemetry/TelemetryLinks.cs diff --git a/src/ConnectedMode.UnitTests/UI/ConnectedModeServicesTests.cs b/src/ConnectedMode.UnitTests/UI/ConnectedModeServicesTests.cs index 1c9587097..175e6b9c3 100644 --- a/src/ConnectedMode.UnitTests/UI/ConnectedModeServicesTests.cs +++ b/src/ConnectedMode.UnitTests/UI/ConnectedModeServicesTests.cs @@ -18,10 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.TestInfrastructure; using SonarLint.VisualStudio.ConnectedMode.UI; +using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; +using SonarLint.VisualStudio.Core.Telemetry; +using SonarLint.VisualStudio.TestInfrastructure; namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.UI; @@ -29,8 +30,7 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.UI; public class ConnectedModeServicesTests { [TestMethod] - public void MefCtor_CheckIsExported() - { + public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), @@ -38,6 +38,6 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); - } } diff --git a/src/ConnectedMode/UI/ConnectedModeServices.cs b/src/ConnectedMode/UI/ConnectedModeServices.cs index 9bf9b9266..bf138865f 100644 --- a/src/ConnectedMode/UI/ConnectedModeServices.cs +++ b/src/ConnectedMode/UI/ConnectedModeServices.cs @@ -19,9 +19,9 @@ */ using System.ComponentModel.Composition; -using SonarLint.VisualStudio.ConnectedMode.Shared; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; +using SonarLint.VisualStudio.Core.Telemetry; namespace SonarLint.VisualStudio.ConnectedMode.UI; @@ -34,6 +34,7 @@ public interface IConnectedModeServices public IConfigurationProvider ConfigurationProvider { get; } public IServerConnectionsRepositoryAdapter ServerConnectionsRepositoryAdapter { get; } public IMessageBox MessageBox { get; } + public ITelemetryManager TelemetryManager { get; } } [Export(typeof(IConnectedModeServices))] @@ -46,11 +47,13 @@ public class ConnectedModeServices( IConfigurationProvider configurationProvider, IServerConnectionsRepositoryAdapter serverConnectionsRepositoryAdapter, IMessageBox messageBox, - ILogger logger) + ILogger logger, + ITelemetryManager telemetryManager) : IConnectedModeServices { public IServerConnectionsRepositoryAdapter ServerConnectionsRepositoryAdapter { get; } = serverConnectionsRepositoryAdapter; public IMessageBox MessageBox { get; } = messageBox; + public ITelemetryManager TelemetryManager { get; } = telemetryManager; public IBrowserService BrowserService { get; } = browserService; public IThreadHandling ThreadHandling { get; } = threadHandling; public ILogger Logger { get; } = logger; diff --git a/src/ConnectedMode/UI/ManageConnections/ManageConnectionsDialog.xaml.cs b/src/ConnectedMode/UI/ManageConnections/ManageConnectionsDialog.xaml.cs index 75c2ea742..6e2973b8d 100644 --- a/src/ConnectedMode/UI/ManageConnections/ManageConnectionsDialog.xaml.cs +++ b/src/ConnectedMode/UI/ManageConnections/ManageConnectionsDialog.xaml.cs @@ -78,7 +78,7 @@ private async void NewConnection_Clicked(object sender, RoutedEventArgs e) private ConnectionInfo GetTransientConnection() { - var serverSelectionDialog = new ServerSelectionDialog(connectedModeServices.BrowserService); + var serverSelectionDialog = new ServerSelectionDialog(connectedModeServices.BrowserService, connectedModeServices.TelemetryManager); return serverSelectionDialog.ShowDialog(this) != true ? null : serverSelectionDialog.ViewModel.CreateTransientConnectionInfo(); } diff --git a/src/ConnectedMode/UI/Resources/UiResources.Designer.cs b/src/ConnectedMode/UI/Resources/UiResources.Designer.cs index fde5a44d9..f7b2cee7e 100644 --- a/src/ConnectedMode/UI/Resources/UiResources.Designer.cs +++ b/src/ConnectedMode/UI/Resources/UiResources.Designer.cs @@ -223,7 +223,7 @@ public static string CloseButton { } /// - /// Looks up a localized string similar to Discover which connection is best for your team . + /// Looks up a localized string similar to Explore SonarQube Cloud with our . /// public static string ConnectionDiscoveringText { get { @@ -456,24 +456,6 @@ public static string FetchingBindingStatusText { } } - /// - /// Looks up a localized string similar to is entirely free for open source projects. - /// - public static string FreeSonarCloudOfferDescription { - get { - return ResourceManager.GetString("FreeSonarCloudOfferDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SonarQube Server offers a free. - /// - public static string FreeSonarQubeOfferDescription { - get { - return ResourceManager.GetString("FreeSonarQubeOfferDescription", resourceCulture); - } - } - /// /// Looks up a localized string similar to The URL you entered is not secure. Use HTTPS connections to protect sensitive information.. /// diff --git a/src/ConnectedMode/UI/Resources/UiResources.resx b/src/ConnectedMode/UI/Resources/UiResources.resx index cb4a911e7..b165b487b 100644 --- a/src/ConnectedMode/UI/Resources/UiResources.resx +++ b/src/ConnectedMode/UI/Resources/UiResources.resx @@ -130,7 +130,7 @@ Choose a connection type - Discover which connection is best for your team + Explore SonarQube Cloud with our Credentials @@ -183,12 +183,6 @@ A Software-as-a-Service (Saas) tool that easily integrates into the cloud DevOps platforms and extends the CI/CD workflow to systematically help developers and organizations deliver CleanCode. - - is entirely free for open source projects - - - SonarQube Server offers a free - account/security diff --git a/src/ConnectedMode/UI/ServerSelection/ServerSelectionDialog.xaml b/src/ConnectedMode/UI/ServerSelection/ServerSelectionDialog.xaml index 6fdf0b81b..e09806d80 100644 --- a/src/ConnectedMode/UI/ServerSelection/ServerSelectionDialog.xaml +++ b/src/ConnectedMode/UI/ServerSelection/ServerSelectionDialog.xaml @@ -1,7 +1,6 @@  - + @@ -48,13 +47,9 @@ - - SonarQube Cloud - - - + @@ -68,10 +63,6 @@ - - - Community Build - @@ -117,7 +108,8 @@ - here + + free tier diff --git a/src/ConnectedMode/UI/ServerSelection/ServerSelectionDialog.xaml.cs b/src/ConnectedMode/UI/ServerSelection/ServerSelectionDialog.xaml.cs index 9762d4cbd..78e671a8e 100644 --- a/src/ConnectedMode/UI/ServerSelection/ServerSelectionDialog.xaml.cs +++ b/src/ConnectedMode/UI/ServerSelection/ServerSelectionDialog.xaml.cs @@ -22,31 +22,35 @@ using System.Windows; using System.Windows.Navigation; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Telemetry; -namespace SonarLint.VisualStudio.ConnectedMode.UI.ServerSelection +namespace SonarLint.VisualStudio.ConnectedMode.UI.ServerSelection; + +[ExcludeFromCodeCoverage] // UI, not really unit-testable +public partial class ServerSelectionDialog : Window { - [ExcludeFromCodeCoverage] // UI, not really unit-testable - public partial class ServerSelectionDialog : Window - { - private readonly IBrowserService browserService; + private readonly IBrowserService browserService; + private readonly ITelemetryManager telemetryManager; - public ServerSelectionDialog(IBrowserService browserService) - { - this.browserService = browserService; - InitializeComponent(); - } + public ServerSelectionViewModel ViewModel { get; } = new(); - public ServerSelectionViewModel ViewModel { get; } = new(); + public ServerSelectionDialog(IBrowserService browserService, ITelemetryManager telemetryManager) + { + this.browserService = browserService; + this.telemetryManager = telemetryManager; + InitializeComponent(); + } - private void ViewWebsite(object sender, RequestNavigateEventArgs e) - { - browserService.Navigate(e.Uri.AbsoluteUri); - } + private void FreeSonaQubeCloudFreeTier_OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + var telemetryLinkId = TelemetryLinks.SonarQubeCloudFreeSignUpId; + browserService.Navigate(TelemetryLinks.LinkIdToUrls[telemetryLinkId]); + telemetryManager.LinkClicked(telemetryLinkId); + } - private void OkButton_OnClick(object sender, RoutedEventArgs e) - { - DialogResult = true; - Close(); - } + private void OkButton_OnClick(object sender, RoutedEventArgs e) + { + DialogResult = true; + Close(); } } diff --git a/src/Core/Telemetry/ITelemetryManager.cs b/src/Core/Telemetry/ITelemetryManager.cs index ee6f55871..c2541aca4 100644 --- a/src/Core/Telemetry/ITelemetryManager.cs +++ b/src/Core/Telemetry/ITelemetryManager.cs @@ -18,15 +18,21 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarLint.VisualStudio.Core.Telemetry +namespace SonarLint.VisualStudio.Core.Telemetry; + +public interface ITelemetryManager { - public interface ITelemetryManager - { - SlCoreTelemetryStatus GetStatus(); - void OptOut(); - void OptIn(); - void LanguageAnalyzed(string languageKey, TimeSpan analysisTime); - void TaintIssueInvestigatedLocally(); - void TaintIssueInvestigatedRemotely(); - } + SlCoreTelemetryStatus GetStatus(); + + void OptOut(); + + void OptIn(); + + void LanguageAnalyzed(string languageKey, TimeSpan analysisTime); + + void TaintIssueInvestigatedLocally(); + + void TaintIssueInvestigatedRemotely(); + + void LinkClicked(string linkId); } diff --git a/src/Core/Telemetry/TelemetryLinks.cs b/src/Core/Telemetry/TelemetryLinks.cs new file mode 100644 index 000000000..7b2a9e3b5 --- /dev/null +++ b/src/Core/Telemetry/TelemetryLinks.cs @@ -0,0 +1,28 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarLint.VisualStudio.Core.Telemetry; + +public static class TelemetryLinks +{ + public const string SonarQubeCloudFreeSignUpId = "sonarqubeCloudFreeSignUp"; + + public static Dictionary LinkIdToUrls { get; } = new() { { SonarQubeCloudFreeSignUpId, "https://www.sonarsource.com/products/sonarcloud/signup-free/" } }; +} diff --git a/src/Integration.UnitTests/Telemetry/TelemetryManagerTests.cs b/src/Integration.UnitTests/Telemetry/TelemetryManagerTests.cs index 73e5da581..11b742a34 100644 --- a/src/Integration.UnitTests/Telemetry/TelemetryManagerTests.cs +++ b/src/Integration.UnitTests/Telemetry/TelemetryManagerTests.cs @@ -31,6 +31,21 @@ namespace SonarLint.VisualStudio.Integration.UnitTests.Telemetry; [TestClass] public class TelemetryManagerTests { + private TelemetryManager telemetryManager; + private ISlCoreTelemetryHelper telemetryHandler; + private ITelemetrySLCoreService telemetryService; + private IKnownUIContexts knownUiContexts; + + [TestInitialize] + public void TestInitialize() + { + telemetryHandler = Substitute.For(); + MockTelemetryService(); + knownUiContexts = Substitute.For(); + + telemetryManager = new TelemetryManager(telemetryHandler, knownUiContexts); + } + [TestMethod] public void MefCtor_CheckIsExported() { @@ -55,9 +70,7 @@ public void MefCtor_CheckIsSingleton() [DataRow(SlCoreTelemetryStatus.Enabled)] public void GetStatus_CallsRpcService(SlCoreTelemetryStatus status) { - CreteTelemetryService(out var telemetryHandler, out _); telemetryHandler.GetStatus().Returns(status); - var telemetryManager = CreateTestSubject(telemetryHandler); telemetryManager.GetStatus().Should().Be(status); } @@ -65,9 +78,6 @@ public void GetStatus_CallsRpcService(SlCoreTelemetryStatus status) [TestMethod] public void OptOut_CallsRpcService() { - CreteTelemetryService(out var telemetryHandler, out var telemetryService); - var telemetryManager = CreateTestSubject(telemetryHandler); - telemetryManager.OptOut(); Received.InOrder(() => @@ -80,9 +90,6 @@ public void OptOut_CallsRpcService() [TestMethod] public void OptIn_CallsRpcService() { - CreteTelemetryService(out var telemetryHandler, out var telemetryService); - var telemetryManager = CreateTestSubject(telemetryHandler); - telemetryManager.OptIn(); Received.InOrder(() => @@ -95,9 +102,6 @@ public void OptIn_CallsRpcService() [TestMethod] public void TaintIssueInvestigatedLocally_CallsRpcService() { - CreteTelemetryService(out var telemetryHandler, out var telemetryService); - var telemetryManager = CreateTestSubject(telemetryHandler); - telemetryManager.TaintIssueInvestigatedLocally(); Received.InOrder(() => @@ -110,9 +114,6 @@ public void TaintIssueInvestigatedLocally_CallsRpcService() [TestMethod] public void TaintVulnerabilitiesInvestigatedRemotely_CallsRpcService() { - CreteTelemetryService(out var telemetryHandler, out var telemetryService); - var telemetryManager = CreateTestSubject(telemetryHandler); - telemetryManager.TaintIssueInvestigatedRemotely(); Received.InOrder(() => @@ -125,9 +126,6 @@ public void TaintVulnerabilitiesInvestigatedRemotely_CallsRpcService() [TestMethod] public void QuickFixApplied_CallsRpcService() { - CreteTelemetryService(out var telemetryHandler, out var telemetryService); - var telemetryManager = CreateTestSubject(telemetryHandler); - telemetryManager.QuickFixApplied("myrule"); Received.InOrder(() => @@ -148,9 +146,6 @@ public void QuickFixApplied_CallsRpcService() [DataRow(SonarLanguageKeys.Secrets, Language.SECRETS, 8)] public void LanguageAnalyzed_CallsRpcService(string languageKey, Language language, int analysisTimeMs) { - CreteTelemetryService(out var telemetryHandler, out var telemetryService); - var telemetryManager = CreateTestSubject(telemetryHandler); - telemetryManager.LanguageAnalyzed(languageKey, TimeSpan.FromMilliseconds(analysisTimeMs)); Received.InOrder(() => @@ -165,10 +160,6 @@ public void LanguageAnalyzed_CallsRpcService(string languageKey, Language langua [DataRow(false)] public void CSharpUIContext_SendsCSharpAnalysisUpdate(bool activated) { - CreteTelemetryService(out var telemetryHandler, out var telemetryService); - var knownUiContexts = Substitute.For(); - var telemetryManager = CreateTestSubject(telemetryHandler, knownUiContexts); - knownUiContexts.CSharpProjectContextChanged += Raise.EventWith(new UIContextChangedEventArgs(activated)); telemetryService.Received(activated ? 1 : 0).AnalysisDoneOnSingleLanguage(Arg.Is(a => a.language == Language.CS)); @@ -179,28 +170,30 @@ public void CSharpUIContext_SendsCSharpAnalysisUpdate(bool activated) [DataRow(false)] public void VBNetUIContext_SendsVBNetAnalysisUpdate(bool activated) { - CreteTelemetryService(out var telemetryHandler, out var telemetryService); - var knownUiContexts = Substitute.For(); - var telemetryManager = CreateTestSubject(telemetryHandler, knownUiContexts); - knownUiContexts.VBProjectContextChanged += Raise.EventWith(new UIContextChangedEventArgs(activated)); telemetryService.Received(activated ? 1 : 0).AnalysisDoneOnSingleLanguage(Arg.Is(a => a.language == Language.VBNET)); } - private static void CreteTelemetryService(out ISlCoreTelemetryHelper telemetryHandler, out ITelemetrySLCoreService telemetryService) + [TestMethod] + public void LinkClicked_CallsRpcService() { - telemetryHandler = Substitute.For(); - var telemetryServiceMock = Substitute.For(); - telemetryService = telemetryServiceMock; - telemetryHandler - .When(x => x.Notify(Arg.Any>())) - .Do(callInfo => callInfo.Arg>()(telemetryServiceMock)); + var linkId = "anId"; + + telemetryManager.LinkClicked(linkId); + + Received.InOrder(() => + { + telemetryHandler.Notify(Arg.Any>()); + telemetryService.HelpAndFeedbackLinkClicked(Arg.Is(a => a.itemId == linkId)); + }); } - private static TelemetryManager CreateTestSubject(ISlCoreTelemetryHelper telemetryHandler, IKnownUIContexts uiContexts = null) + private void MockTelemetryService() { - uiContexts ??= Substitute.For(); - return new TelemetryManager(telemetryHandler, uiContexts); + telemetryService = Substitute.For(); + telemetryHandler + .When(x => x.Notify(Arg.Any>())) + .Do(callInfo => callInfo.Arg>()(telemetryService)); } } diff --git a/src/Integration/Telemetry/TelemetryManager.cs b/src/Integration/Telemetry/TelemetryManager.cs index d15781cfd..fc4f00738 100644 --- a/src/Integration/Telemetry/TelemetryManager.cs +++ b/src/Integration/Telemetry/TelemetryManager.cs @@ -34,8 +34,8 @@ internal sealed class TelemetryManager : ITelemetryManager, IQuickFixesTelemetryManager, IDisposable { - private readonly ISlCoreTelemetryHelper telemetryHelper; private readonly IKnownUIContexts knownUiContexts; + private readonly ISlCoreTelemetryHelper telemetryHelper; [ImportingConstructor] public TelemetryManager(ISlCoreTelemetryHelper telemetryHelper, IKnownUIContexts knownUIContexts) @@ -46,26 +46,13 @@ public TelemetryManager(ISlCoreTelemetryHelper telemetryHelper, IKnownUIContexts knownUiContexts.VBProjectContextChanged += OnVBProjectContextChanged; } - public void QuickFixApplied(string ruleId) - { - telemetryHelper.Notify(telemetryService => telemetryService.AddQuickFixAppliedForRule(new AddQuickFixAppliedForRuleParams(ruleId))); - } + public void QuickFixApplied(string ruleId) => telemetryHelper.Notify(telemetryService => telemetryService.AddQuickFixAppliedForRule(new AddQuickFixAppliedForRuleParams(ruleId))); - public SlCoreTelemetryStatus GetStatus() - { - return telemetryHelper.GetStatus(); - } - - public void OptIn() - { - telemetryHelper.Notify(telemetryService => telemetryService.EnableTelemetry()); - } + public SlCoreTelemetryStatus GetStatus() => telemetryHelper.GetStatus(); + public void OptIn() => telemetryHelper.Notify(telemetryService => telemetryService.EnableTelemetry()); - public void OptOut() - { - telemetryHelper.Notify(telemetryService => telemetryService.DisableTelemetry()); - } + public void OptOut() => telemetryHelper.Notify(telemetryService => telemetryService.DisableTelemetry()); public void LanguageAnalyzed(string languageKey, TimeSpan analysisTime) { @@ -74,15 +61,11 @@ public void LanguageAnalyzed(string languageKey, TimeSpan analysisTime) telemetryService.AnalysisDoneOnSingleLanguage(new AnalysisDoneOnSingleLanguageParams(language, (int)Math.Round(analysisTime.TotalMilliseconds)))); } - public void TaintIssueInvestigatedLocally() - { - telemetryHelper.Notify(telemetryService => telemetryService.TaintVulnerabilitiesInvestigatedLocally()); - } + public void TaintIssueInvestigatedLocally() => telemetryHelper.Notify(telemetryService => telemetryService.TaintVulnerabilitiesInvestigatedLocally()); - public void TaintIssueInvestigatedRemotely() - { - telemetryHelper.Notify(telemetryService => telemetryService.TaintVulnerabilitiesInvestigatedRemotely()); - } + public void TaintIssueInvestigatedRemotely() => telemetryHelper.Notify(telemetryService => telemetryService.TaintVulnerabilitiesInvestigatedRemotely()); + + public void LinkClicked(string linkId) => telemetryHelper.Notify(telemetryService => telemetryService.HelpAndFeedbackLinkClicked(new HelpAndFeedbackClickedParams(linkId))); public void Dispose() { @@ -106,9 +89,8 @@ private void OnVBProjectContextChanged(object sender, UIContextChangedEventArgs } } - private static Language Convert(string languageKey) - { - return languageKey switch + private static Language Convert(string languageKey) => + languageKey switch { SonarLanguageKeys.CPlusPlus => Language.CPP, SonarLanguageKeys.C => Language.C, @@ -120,5 +102,4 @@ private static Language Convert(string languageKey) SonarLanguageKeys.Secrets => Language.SECRETS, _ => throw new ArgumentOutOfRangeException(nameof(languageKey), languageKey, null) }; - } }