Merge pull request #7266 from livarcocc/show_first_run_message
Show first run notice even when CLI is installed by native installers.
This commit is contained in:
commit
e5786487ec
9 changed files with 206 additions and 19 deletions
|
@ -45,12 +45,14 @@ namespace Microsoft.DotNet.Configurer
|
||||||
{
|
{
|
||||||
PrintUnauthorizedAccessMessage();
|
PrintUnauthorizedAccessMessage();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
PrintNugetCachePrimeMessage();
|
PrintNugetCachePrimeMessage();
|
||||||
|
|
||||||
_nugetCachePrimer.PrimeCache();
|
_nugetCachePrimer.PrimeCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ShouldPrintFirstTimeUseNotice()
|
private bool ShouldPrintFirstTimeUseNotice()
|
||||||
{
|
{
|
||||||
|
@ -81,6 +83,8 @@ namespace Microsoft.DotNet.Configurer
|
||||||
private bool ShouldPrimeNugetCache()
|
private bool ShouldPrimeNugetCache()
|
||||||
{
|
{
|
||||||
return ShouldRunFirstRunExperience() &&
|
return ShouldRunFirstRunExperience() &&
|
||||||
|
!_nugetCacheSentinel.Exists() &&
|
||||||
|
!_nugetCacheSentinel.InProgressSentinelAlreadyExists() &&
|
||||||
!_nugetCachePrimer.SkipPrimingTheCache();
|
!_nugetCachePrimer.SkipPrimingTheCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +100,7 @@ namespace Microsoft.DotNet.Configurer
|
||||||
var skipFirstTimeExperience =
|
var skipFirstTimeExperience =
|
||||||
_environmentProvider.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false);
|
_environmentProvider.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false);
|
||||||
|
|
||||||
return !skipFirstTimeExperience &&
|
return !skipFirstTimeExperience;
|
||||||
!_nugetCacheSentinel.Exists() &&
|
|
||||||
!_nugetCacheSentinel.InProgressSentinelAlreadyExists();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.DotNet.Cli.Utils;
|
||||||
|
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||||
|
using NuGet.Configuration;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Configurer
|
||||||
|
{
|
||||||
|
public class NoOpFirstTimeUseNoticeSentinel : IFirstTimeUseNoticeSentinel
|
||||||
|
{
|
||||||
|
public bool Exists()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateIfNotExists()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ namespace Microsoft.DotNet.Configurer
|
||||||
|
|
||||||
public bool InProgressSentinelAlreadyExists()
|
public bool InProgressSentinelAlreadyExists()
|
||||||
{
|
{
|
||||||
return CouldNotGetAHandleToTheInProgressSentinel();
|
return CouldNotGetAHandleToTheInProgressSentinel() && !UnauthorizedAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Exists()
|
public bool Exists()
|
||||||
|
|
|
@ -81,8 +81,10 @@ namespace Microsoft.DotNet.Cli
|
||||||
var lastArg = 0;
|
var lastArg = 0;
|
||||||
var cliFallbackFolderPathCalculator = new CliFallbackFolderPathCalculator();
|
var cliFallbackFolderPathCalculator = new CliFallbackFolderPathCalculator();
|
||||||
using (INuGetCacheSentinel nugetCacheSentinel = new NuGetCacheSentinel(cliFallbackFolderPathCalculator))
|
using (INuGetCacheSentinel nugetCacheSentinel = new NuGetCacheSentinel(cliFallbackFolderPathCalculator))
|
||||||
using (IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel(cliFallbackFolderPathCalculator))
|
using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel =
|
||||||
|
new FirstTimeUseNoticeSentinel(cliFallbackFolderPathCalculator))
|
||||||
{
|
{
|
||||||
|
IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = disposableFirstTimeUseNoticeSentinel;
|
||||||
for (; lastArg < args.Length; lastArg++)
|
for (; lastArg < args.Length; lastArg++)
|
||||||
{
|
{
|
||||||
if (IsArg(args[lastArg], "d", "diagnostics"))
|
if (IsArg(args[lastArg], "d", "diagnostics"))
|
||||||
|
@ -113,10 +115,19 @@ namespace Microsoft.DotNet.Cli
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ConfigureDotNetForFirstTimeUse(nugetCacheSentinel, firstTimeUseNoticeSentinel, cliFallbackFolderPathCalculator);
|
|
||||||
|
|
||||||
// It's the command, and we're done!
|
// It's the command, and we're done!
|
||||||
command = args[lastArg];
|
command = args[lastArg];
|
||||||
|
|
||||||
|
if (IsDotnetBeingInvokedFromNativeInstaller(command))
|
||||||
|
{
|
||||||
|
firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureDotNetForFirstTimeUse(
|
||||||
|
nugetCacheSentinel,
|
||||||
|
firstTimeUseNoticeSentinel,
|
||||||
|
cliFallbackFolderPathCalculator);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +175,11 @@ namespace Microsoft.DotNet.Cli
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitCode;
|
return exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsDotnetBeingInvokedFromNativeInstaller(string command)
|
||||||
|
{
|
||||||
|
return command == "internal-reportinstallsuccess";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ConfigureDotNetForFirstTimeUse(
|
private static void ConfigureDotNetForFirstTimeUse(
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Microsoft.DotNet.Cli
|
||||||
|
|
||||||
public Telemetry(IFirstTimeUseNoticeSentinel sentinel) : this(sentinel, null) { }
|
public Telemetry(IFirstTimeUseNoticeSentinel sentinel) : this(sentinel, null) { }
|
||||||
|
|
||||||
public Telemetry(IFirstTimeUseNoticeSentinel sentinel, string sessionId)
|
public Telemetry(IFirstTimeUseNoticeSentinel sentinel, string sessionId, bool blockThreadInitialization = false)
|
||||||
{
|
{
|
||||||
Enabled = !Env.GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(sentinel);
|
Enabled = !Env.GetEnvironmentVariableAsBool(TelemetryOptout) && PermissionExists(sentinel);
|
||||||
|
|
||||||
|
@ -49,9 +49,16 @@ namespace Microsoft.DotNet.Cli
|
||||||
// Store the session ID in a static field so that it can be reused
|
// Store the session ID in a static field so that it can be reused
|
||||||
CurrentSessionId = sessionId ?? Guid.NewGuid().ToString();
|
CurrentSessionId = sessionId ?? Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
if (blockThreadInitialization)
|
||||||
|
{
|
||||||
|
InitializeTelemetry();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
//initialize in task to offload to parallel thread
|
//initialize in task to offload to parallel thread
|
||||||
_trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry());
|
_trackEventTask = Task.Factory.StartNew(() => InitializeTelemetry());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool PermissionExists(IFirstTimeUseNoticeSentinel sentinel)
|
private bool PermissionExists(IFirstTimeUseNoticeSentinel sentinel)
|
||||||
{
|
{
|
||||||
|
@ -126,9 +133,9 @@ namespace Microsoft.DotNet.Cli
|
||||||
_client.TrackEvent(eventName, eventProperties, eventMeasurements);
|
_client.TrackEvent(eventName, eventProperties, eventMeasurements);
|
||||||
_client.Flush();
|
_client.Flush();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Debug.Fail("Exception during TrackEventTask");
|
Debug.Fail(e.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace Microsoft.DotNet.Cli
|
||||||
{
|
{
|
||||||
var sessionId =
|
var sessionId =
|
||||||
Environment.GetEnvironmentVariable(TelemetrySessionIdEnvironmentVariableName);
|
Environment.GetEnvironmentVariable(TelemetrySessionIdEnvironmentVariableName);
|
||||||
telemetry = new Telemetry(new FirstTimeUseNoticeSentinel(new CliFallbackFolderPathCalculator()), sessionId);
|
telemetry = new Telemetry(new NoOpFirstTimeUseNoticeSentinel(), sessionId, blockThreadInitialization: true);
|
||||||
}
|
}
|
||||||
public bool Enabled => telemetry.Enabled;
|
public bool Enabled => telemetry.Enabled;
|
||||||
|
|
||||||
|
|
|
@ -233,5 +233,73 @@ namespace Microsoft.DotNet.Configurer.UnitTests
|
||||||
_nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Once);
|
_nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Once);
|
||||||
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
|
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void It_prints_the_first_time_use_notice_if_the_cache_sentinel_exists_but_the_first_notice_sentinel_does_not()
|
||||||
|
{
|
||||||
|
_nugetCacheSentinelMock.Setup(n => n.Exists()).Returns(true);
|
||||||
|
_firstTimeUseNoticeSentinelMock.Setup(n => n.Exists()).Returns(false);
|
||||||
|
|
||||||
|
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
|
||||||
|
_nugetCachePrimerMock.Object,
|
||||||
|
_nugetCacheSentinelMock.Object,
|
||||||
|
_firstTimeUseNoticeSentinelMock.Object,
|
||||||
|
_environmentProviderMock.Object,
|
||||||
|
_reporterMock.Object,
|
||||||
|
CliFallbackFolderPath);
|
||||||
|
|
||||||
|
dotnetFirstTimeUseConfigurer.Configure();
|
||||||
|
|
||||||
|
_reporterMock.Verify(r =>
|
||||||
|
r.WriteLine(It.Is<string>(str => str == LocalizableStrings.FirstTimeWelcomeMessage)));
|
||||||
|
_reporterMock.Verify(
|
||||||
|
r => r.WriteLine(It.Is<string>(str => str == LocalizableStrings.NugetCachePrimeMessage)),
|
||||||
|
Times.Never);
|
||||||
|
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void It_prints_the_unauthorized_notice_if_the_cache_sentinel_reports_Unauthorized()
|
||||||
|
{
|
||||||
|
_nugetCacheSentinelMock.Setup(n => n.UnauthorizedAccess).Returns(true);
|
||||||
|
|
||||||
|
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
|
||||||
|
_nugetCachePrimerMock.Object,
|
||||||
|
_nugetCacheSentinelMock.Object,
|
||||||
|
_firstTimeUseNoticeSentinelMock.Object,
|
||||||
|
_environmentProviderMock.Object,
|
||||||
|
_reporterMock.Object,
|
||||||
|
CliFallbackFolderPath);
|
||||||
|
|
||||||
|
dotnetFirstTimeUseConfigurer.Configure();
|
||||||
|
|
||||||
|
_reporterMock.Verify(r =>
|
||||||
|
r.WriteLine(It.Is<string>(str => str == LocalizableStrings.FirstTimeWelcomeMessage)));
|
||||||
|
_reporterMock.Verify(r =>
|
||||||
|
r.WriteLine(It.Is<string>(str =>
|
||||||
|
str == string.Format(LocalizableStrings.UnauthorizedAccessMessage, CliFallbackFolderPath))));
|
||||||
|
_reporterMock.Verify(
|
||||||
|
r => r.WriteLine(It.Is<string>(str => str == LocalizableStrings.NugetCachePrimeMessage)),
|
||||||
|
Times.Never);
|
||||||
|
_reporterMock.Verify(r => r.Write(It.IsAny<string>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void It_does_not_prime_the_cache_if_the_cache_sentinel_reports_Unauthorized()
|
||||||
|
{
|
||||||
|
_nugetCacheSentinelMock.Setup(n => n.UnauthorizedAccess).Returns(true);
|
||||||
|
|
||||||
|
var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer(
|
||||||
|
_nugetCachePrimerMock.Object,
|
||||||
|
_nugetCacheSentinelMock.Object,
|
||||||
|
_firstTimeUseNoticeSentinelMock.Object,
|
||||||
|
_environmentProviderMock.Object,
|
||||||
|
_reporterMock.Object,
|
||||||
|
CliFallbackFolderPath);
|
||||||
|
|
||||||
|
dotnetFirstTimeUseConfigurer.Configure();
|
||||||
|
|
||||||
|
_nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Never);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,18 @@ namespace Microsoft.DotNet.Configurer.UnitTests
|
||||||
nugetCacheSentinel.InProgressSentinelAlreadyExists().Should().BeTrue();
|
nugetCacheSentinel.InProgressSentinelAlreadyExists().Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void It_returns_false_to_the_in_progress_sentinel_already_exists_when_it_fails_to_get_a_handle_to_it_but_it_failed_because_it_was_unauthorized()
|
||||||
|
{
|
||||||
|
var fileMock = new FileMock();
|
||||||
|
var directoryMock = new DirectoryMock();
|
||||||
|
fileMock.InProgressSentinel = null;
|
||||||
|
var nugetCacheSentinel =
|
||||||
|
new NuGetCacheSentinel(NUGET_CACHE_PATH, fileMock, directoryMock);
|
||||||
|
|
||||||
|
nugetCacheSentinel.InProgressSentinelAlreadyExists().Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void It_returns_false_to_the_in_progress_sentinel_already_exists_when_it_succeeds_in_getting_a_handle_to_it()
|
public void It_returns_false_to_the_in_progress_sentinel_already_exists_when_it_succeeds_in_getting_a_handle_to_it()
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,15 +20,17 @@ namespace Microsoft.DotNet.Tests
|
||||||
private static CommandResult _firstDotnetNonVerbUseCommandResult;
|
private static CommandResult _firstDotnetNonVerbUseCommandResult;
|
||||||
private static CommandResult _firstDotnetVerbUseCommandResult;
|
private static CommandResult _firstDotnetVerbUseCommandResult;
|
||||||
private static DirectoryInfo _nugetFallbackFolder;
|
private static DirectoryInfo _nugetFallbackFolder;
|
||||||
|
private static DirectoryInfo _dotDotnetFolder;
|
||||||
|
private static string _testDirectory;
|
||||||
|
|
||||||
static GivenThatTheUserIsRunningDotNetForTheFirstTime()
|
static GivenThatTheUserIsRunningDotNetForTheFirstTime()
|
||||||
{
|
{
|
||||||
var testDirectory = TestAssets.CreateTestDirectory("Dotnet_first_time_experience_tests");
|
_testDirectory = TestAssets.CreateTestDirectory("Dotnet_first_time_experience_tests").FullName;
|
||||||
var testNuGetHome = Path.Combine(testDirectory.FullName, "nuget_home");
|
var testNuGetHome = Path.Combine(_testDirectory, "nuget_home");
|
||||||
var cliTestFallbackFolder = Path.Combine(testNuGetHome, ".dotnet", "NuGetFallbackFolder");
|
var cliTestFallbackFolder = Path.Combine(testNuGetHome, ".dotnet", "NuGetFallbackFolder");
|
||||||
|
|
||||||
var command = new DotnetCommand()
|
var command = new DotnetCommand()
|
||||||
.WithWorkingDirectory(testDirectory);
|
.WithWorkingDirectory(_testDirectory);
|
||||||
command.Environment["HOME"] = testNuGetHome;
|
command.Environment["HOME"] = testNuGetHome;
|
||||||
command.Environment["USERPROFILE"] = testNuGetHome;
|
command.Environment["USERPROFILE"] = testNuGetHome;
|
||||||
command.Environment["APPDATA"] = testNuGetHome;
|
command.Environment["APPDATA"] = testNuGetHome;
|
||||||
|
@ -40,6 +42,7 @@ namespace Microsoft.DotNet.Tests
|
||||||
_firstDotnetVerbUseCommandResult = command.ExecuteWithCapturedOutput("new --debug:ephemeral-hive");
|
_firstDotnetVerbUseCommandResult = command.ExecuteWithCapturedOutput("new --debug:ephemeral-hive");
|
||||||
|
|
||||||
_nugetFallbackFolder = new DirectoryInfo(cliTestFallbackFolder);
|
_nugetFallbackFolder = new DirectoryInfo(cliTestFallbackFolder);
|
||||||
|
_dotDotnetFolder = new DirectoryInfo(Path.Combine(testNuGetHome, ".dotnet"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -77,6 +80,60 @@ namespace Microsoft.DotNet.Tests
|
||||||
.HaveFile($"{GetDotnetVersion()}.dotnetSentinel");
|
.HaveFile($"{GetDotnetVersion()}.dotnetSentinel");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ItCreatesAFirstUseSentinelFileUnderTheDotDotNetFolder()
|
||||||
|
{
|
||||||
|
_dotDotnetFolder
|
||||||
|
.Should()
|
||||||
|
.HaveFile($"{GetDotnetVersion()}.dotnetFirstUseSentinel");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ItDoesNotCreateAFirstUseSentinelFileUnderTheDotDotNetFolderWhenInternalReportInstallSuccessIsInvoked()
|
||||||
|
{
|
||||||
|
var emptyHome = Path.Combine(_testDirectory, "empty_home");
|
||||||
|
|
||||||
|
var command = new DotnetCommand()
|
||||||
|
.WithWorkingDirectory(_testDirectory);
|
||||||
|
command.Environment["HOME"] = emptyHome;
|
||||||
|
command.Environment["USERPROFILE"] = emptyHome;
|
||||||
|
command.Environment["APPDATA"] = emptyHome;
|
||||||
|
command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = _nugetFallbackFolder.FullName;
|
||||||
|
command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "";
|
||||||
|
// Disable to prevent the creation of the .dotnet folder by optimizationdata.
|
||||||
|
command.Environment["DOTNET_DISABLE_MULTICOREJIT"] = "true";
|
||||||
|
command.Environment["SkipInvalidConfigurations"] = "true";
|
||||||
|
|
||||||
|
command.ExecuteWithCapturedOutput("internal-reportinstallsuccess test").Should().Pass();
|
||||||
|
|
||||||
|
var emptyHomeFolder = new DirectoryInfo(Path.Combine(emptyHome, ".dotnet"));
|
||||||
|
emptyHomeFolder.Should().NotExist();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ItShowsTheTelemetryNoticeWhenInvokingACommandAfterInternalReportInstallSuccessHasBeenInvoked()
|
||||||
|
{
|
||||||
|
var newHome = Path.Combine(_testDirectory, "new_home");
|
||||||
|
var newHomeFolder = new DirectoryInfo(Path.Combine(newHome, ".dotnet"));
|
||||||
|
|
||||||
|
var command = new DotnetCommand()
|
||||||
|
.WithWorkingDirectory(_testDirectory);
|
||||||
|
command.Environment["HOME"] = newHome;
|
||||||
|
command.Environment["USERPROFILE"] = newHome;
|
||||||
|
command.Environment["APPDATA"] = newHome;
|
||||||
|
command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = _nugetFallbackFolder.FullName;
|
||||||
|
command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "";
|
||||||
|
command.Environment["SkipInvalidConfigurations"] = "true";
|
||||||
|
|
||||||
|
command.ExecuteWithCapturedOutput("internal-reportinstallsuccess test").Should().Pass();
|
||||||
|
|
||||||
|
var result = command.ExecuteWithCapturedOutput("new --debug:ephemeral-hive");
|
||||||
|
|
||||||
|
result.StdOut
|
||||||
|
.Should()
|
||||||
|
.ContainVisuallySameFragment(Configurer.LocalizableStrings.FirstTimeWelcomeMessage);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ItRestoresTheNuGetPackagesToTheNuGetCacheFolder()
|
public void ItRestoresTheNuGetPackagesToTheNuGetCacheFolder()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue