Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SLVS-1786 Promote t-sql to standalone users #5972

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions src/ConnectedMode.UnitTests/Promote/PromoteGoldBarTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* 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.
*/

using SonarLint.VisualStudio.ConnectedMode.Promote;
using SonarLint.VisualStudio.ConnectedMode.UI;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Binding;
using SonarLint.VisualStudio.Core.Notifications;
using SonarLint.VisualStudio.Core.Telemetry;
using SonarLint.VisualStudio.TestInfrastructure;

namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Promote;

[TestClass]
public class PromoteGoldBarTests
{
private const string LanguageToPromote = "TSQL";

private INotificationService notificationService;
private IDoNotShowAgainNotificationAction doNotShowAgainNotificationAction;
private IActiveSolutionBoundTracker activeSolutionBoundTracker;
private IBrowserService browserService;
private ITelemetryManager telemetryManager;
private IConnectedModeUIManager connectedModeUiManager;
private PromoteGoldBar testSubject;

[TestInitialize]
public void TestInitialize()
{
notificationService = Substitute.For<INotificationService>();
doNotShowAgainNotificationAction = Substitute.For<IDoNotShowAgainNotificationAction>();
activeSolutionBoundTracker = Substitute.For<IActiveSolutionBoundTracker>();
browserService = Substitute.For<IBrowserService>();
telemetryManager = Substitute.For<ITelemetryManager>();
connectedModeUiManager = Substitute.For<IConnectedModeUIManager>();
testSubject = new PromoteGoldBar(notificationService, doNotShowAgainNotificationAction, activeSolutionBoundTracker, browserService, telemetryManager, connectedModeUiManager);
}

[TestMethod]
public void MefCtor_CheckExports() =>
MefTestHelpers.CheckTypeCanBeImported<PromoteGoldBar, IPromoteGoldBar>(
MefTestHelpers.CreateExport<INotificationService>(),
MefTestHelpers.CreateExport<IDoNotShowAgainNotificationAction>(),
MefTestHelpers.CreateExport<IActiveSolutionBoundTracker>(),
MefTestHelpers.CreateExport<IBrowserService>(),
MefTestHelpers.CreateExport<ITelemetryManager>(),
MefTestHelpers.CreateExport<IConnectedModeUIManager>());

[TestMethod]
public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<PromoteGoldBar>();

[TestMethod]
public void PromoteConnectedMode_ShowsNotification_WithId()
{
testSubject.PromoteConnectedMode(LanguageToPromote);

notificationService.Received(1).ShowNotification(Arg.Is<Notification>(n => n.Id == "PromoteNotification"));
}

[TestMethod]
public void PromoteConnectedMode_ShowsNotification_WithMessageThatContainsTheLanguageToPromote()
{
testSubject.PromoteConnectedMode(LanguageToPromote);

notificationService.Received(1).ShowNotification(Arg.Is<Notification>(n =>
n.Message == string.Format(Resources.PromoteConnectedModeLanguagesMessage, LanguageToPromote)
));
}

[TestMethod]
public void PromoteConnectedMode_ShowsNotification_WithCorrectActions()
{
testSubject.PromoteConnectedMode(LanguageToPromote);

notificationService.Received(1).ShowNotification(Arg.Is<Notification>(n =>
n.Actions.ToList().Count == 4 &&
n.Actions.ToList()[0].CommandText == Resources.PromoteBind &&
n.Actions.ToList()[1].CommandText == Resources.PromoteSonarQubeCloud &&
n.Actions.ToList()[2].CommandText == Resources.PromoteLearnMore &&
n.Actions.ToList()[3] == doNotShowAgainNotificationAction
));
}

[TestMethod]
public void PromoteConnectedMode_BindAction_ShowsManageBindingDialog()
{
testSubject.PromoteConnectedMode(LanguageToPromote);
var notification = (Notification)notificationService.ReceivedCalls().Single().GetArguments()[0];
var bindAction = notification.Actions.First(a => a.CommandText == Resources.PromoteBind);

bindAction.Action(null);

connectedModeUiManager.Received(1).ShowManageBindingDialog();
}

[TestMethod]
public void PromoteConnectedMode_SonarQubeCloudAction_NavigatesToCorrectUrl()
{
testSubject.PromoteConnectedMode(LanguageToPromote);
var notification = (Notification) notificationService.ReceivedCalls().Single().GetArguments()[0];
var sonarQubeCloudAction = notification.Actions.First(a => a.CommandText == Resources.PromoteSonarQubeCloud);

sonarQubeCloudAction.Action(null);

browserService.Received(1).Navigate(TelemetryLinks.LinkIdToUrls[TelemetryLinks.SonarQubeCloudFreeSignUpId]);
}

[TestMethod]
public void PromoteConnectedMode_LearnMoreAction_NavigatesToCorrectUrl()
{
testSubject.PromoteConnectedMode(LanguageToPromote);
var notification = (Notification) notificationService.ReceivedCalls().Single().GetArguments()[0];
var learnMoreAction = notification.Actions.First(a => a.CommandText == Resources.PromoteLearnMore);

learnMoreAction.Action(null);

browserService.Received(1).Navigate(DocumentationLinks.ConnectedModeBenefits);
}

[TestMethod]
public void OnActiveSolutionBindingChanged_ConnectedMode_ClosesNotification()
{
var eventArgs = new ActiveSolutionBindingEventArgs(new BindingConfiguration(null, SonarLintMode.Connected, null));

testSubject.PromoteConnectedMode(LanguageToPromote);
activeSolutionBoundTracker.SolutionBindingChanged += Raise.EventWith(this, eventArgs);

notificationService.Received(1).CloseNotification();
}

[TestMethod]
public void Dispose_UnsubscribesFromAllEvents()
{
testSubject.Dispose();

activeSolutionBoundTracker.Received(1).SolutionBindingChanged -= Arg.Any<EventHandler<ActiveSolutionBindingEventArgs>>();
}
}
100 changes: 100 additions & 0 deletions src/ConnectedMode/Promote/PromoteGoldBar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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.
*/

using System.ComponentModel.Composition;
using SonarLint.VisualStudio.ConnectedMode.UI;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Binding;
using SonarLint.VisualStudio.Core.Notifications;
using SonarLint.VisualStudio.Core.Telemetry;

namespace SonarLint.VisualStudio.ConnectedMode.Promote;

public interface IPromoteGoldBar
{
void PromoteConnectedMode(string languagesToPromote);
}

[Export(typeof(IPromoteGoldBar))]
[PartCreationPolicy(CreationPolicy.Shared)]
public sealed class PromoteGoldBar : IPromoteGoldBar, IDisposable
{
private readonly INotificationService notificationService;
private readonly IDoNotShowAgainNotificationAction doNotShowAgainNotificationAction;
private readonly IActiveSolutionBoundTracker activeSolutionBoundTracker;
private readonly IBrowserService browserService;
private readonly ITelemetryManager telemetryManager;
private readonly IConnectedModeUIManager connectedModeUiManager;

[ImportingConstructor]
public PromoteGoldBar(
vnaskos-sonar marked this conversation as resolved.
Show resolved Hide resolved
INotificationService notificationService,
IDoNotShowAgainNotificationAction doNotShowAgainNotificationAction,
IActiveSolutionBoundTracker activeSolutionBoundTracker,
IBrowserService browserService,
ITelemetryManager telemetryManager,
IConnectedModeUIManager connectedModeUiManager)
{
this.notificationService = notificationService;
this.doNotShowAgainNotificationAction = doNotShowAgainNotificationAction;
this.activeSolutionBoundTracker = activeSolutionBoundTracker;
this.browserService = browserService;
this.telemetryManager = telemetryManager;
this.connectedModeUiManager = connectedModeUiManager;

this.activeSolutionBoundTracker.SolutionBindingChanged += OnActiveSolutionBindingChanged;
}

public void PromoteConnectedMode(string languagesToPromote)
{
vnaskos-sonar marked this conversation as resolved.
Show resolved Hide resolved
var notification = new Notification(
id: "PromoteNotification",
message: string.Format(Resources.PromoteConnectedModeLanguagesMessage, languagesToPromote),
actions:
[
new NotificationAction(Resources.PromoteBind, _ => OnBind(), false),
new NotificationAction(Resources.PromoteSonarQubeCloud, _ => OnTrySonarQubeCloud(), false),
new NotificationAction(Resources.PromoteLearnMore, _ => OnLearnMore(), false),
doNotShowAgainNotificationAction
]);

notificationService.ShowNotification(notification);
}

public void Dispose() => activeSolutionBoundTracker.SolutionBindingChanged -= OnActiveSolutionBindingChanged;

private void OnActiveSolutionBindingChanged(object sender, ActiveSolutionBindingEventArgs e)
{
if (e.Configuration.Mode == SonarLintMode.Connected)
{
notificationService.CloseNotification();
}
}

private void OnBind() => connectedModeUiManager.ShowManageBindingDialog();

private void OnTrySonarQubeCloud()
{
browserService.Navigate(TelemetryLinks.LinkIdToUrls[TelemetryLinks.SonarQubeCloudFreeSignUpId]);
telemetryManager.LinkClicked(TelemetryLinks.SonarQubeCloudFreeSignUpId);
}

private void OnLearnMore() => browserService.Navigate(DocumentationLinks.ConnectedModeBenefits);
}
36 changes: 36 additions & 0 deletions src/ConnectedMode/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions src/ConnectedMode/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -334,4 +334,16 @@
<data name="DotnetAnalyzerIndicatorTemplate" xml:space="preserve">
<value>[ConnectedMode/DotnetAnalyzerIndicator] {0}</value>
</data>
<data name="PromoteLearnMore" xml:space="preserve">
<value>Learn more</value>
</data>
<data name="PromoteConnectedModeLanguagesMessage" xml:space="preserve">
<value>Consider connecting to SonarQube to enable analysis on the following languages: {0}</value>
</data>
<data name="PromoteBind" xml:space="preserve">
<value>Bind</value>
</data>
<data name="PromoteSonarQubeCloud" xml:space="preserve">
<value>Try SonarQube Cloud for free</value>
</data>
</root>
1 change: 1 addition & 0 deletions src/Core/DocumentationLinks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static class DocumentationLinks
public const string MigrateToConnectedModeV7_NotesForTfvcUsers
= "https://docs.sonarsource.com/sonarqube-for-ide/visual-studio/team-features/migrate-connected-mode-to-v7/#notes-for-tfvc-users";
public const string ConnectedMode = "https://docs.sonarsource.com/sonarqube-for-ide/visual-studio/team-features/connected-mode/";
public const string ConnectedModeBenefits = "https://docs.sonarsource.com/sonarqube-for-ide/visual-studio/team-features/connected-mode#benefits";
public const string TaintVulnerabilities = "https://docs.sonarsource.com/sonarqube-for-ide/visual-studio/using-sonarlint/taint-vulnerabilities/";
public const string DisablingARule = "https://docs.sonarsource.com/sonarqube-for-ide/visual-studio/using-sonarlint/rules/#disabling-a-rule";
public const string UseSharedBinding = "https://docs.sonarsource.com/sonarqube-for-ide/visual-studio/team-features/connected-mode-setup/#bind-using-shared-configuration";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.
*/

using SonarLint.VisualStudio.ConnectedMode.Promote;
using SonarLint.VisualStudio.SLCore.Common.Models;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Listener.Promote;

namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests.Implementation;

[TestClass]
public class PromoteListenerTests
{
private IPromoteGoldBar promoteGoldBar;
private PromoteListener testSubject;

[TestInitialize]
public void TestInitialize()
{
promoteGoldBar = Substitute.For<IPromoteGoldBar>();
testSubject = new PromoteListener(promoteGoldBar);
}

[TestMethod]
public void MefCtor_CheckExports() =>
MefTestHelpers.CheckTypeCanBeImported<PromoteListener, ISLCoreListener>(
MefTestHelpers.CreateExport<IPromoteGoldBar>());

[TestMethod]
public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<PromoteListener>();

[TestMethod]
public void PromoteExtraEnabledLanguagesInConnectedMode_DisplaysGoldBarWithCommaSeparatedLanguages()
{
var parameters = new PromoteExtraEnabledLanguagesInConnectedModeParams("CONFIGURATION_SCOPE_ID", [Language.TSQL, Language.PLSQL]);

testSubject.PromoteExtraEnabledLanguagesInConnectedMode(parameters);

promoteGoldBar.Received().PromoteConnectedMode("TSQL, PLSQL");
}
}
Loading