diff --git a/src/Cli/dotnet/Telemetry/TelemetryFilter.cs b/src/Cli/dotnet/Telemetry/TelemetryFilter.cs index 00a758add1de..8d36a0bd5cec 100644 --- a/src/Cli/dotnet/Telemetry/TelemetryFilter.cs +++ b/src/Cli/dotnet/Telemetry/TelemetryFilter.cs @@ -48,12 +48,29 @@ private static IEnumerable FilterImpl(ParseResult parseRes yield break; } + // When a top-level terminating option (--help, --version, --info) is invoked without a + // subcommand, RootSubCommandResult() returns empty string. Use the option name as the + // verb so these invocations are visible in telemetry. + if (string.IsNullOrEmpty(topLevelCommandName) + && parseResult.Action is InvocableOptionAction { Terminating: true } topLevelOptionAction) + { + topLevelCommandName = topLevelOptionAction.Option.Name; + } + Dictionary properties = new() { ["verb"] = topLevelCommandName }; if (!string.IsNullOrEmpty(globalJsonState)) { properties["globalJson"] = globalJsonState; } + // When --help is the active action on any command (e.g. "dotnet build --help"), + // record that the invocation was a help request so it can be distinguished from + // an actual command execution. + if (parseResult.Action is PrintHelpAction) + { + properties["help"] = "true"; + } + yield return new TelemetryEntryFormat("toplevelparser/command", properties); if (parseResult.IsDotnetBuiltInCommand() && diff --git a/test/dotnet.Tests/TelemetryTests/TelemetryFilterTest.cs b/test/dotnet.Tests/TelemetryTests/TelemetryFilterTest.cs index 17653dfc4cb5..ab435ebea141 100644 --- a/test/dotnet.Tests/TelemetryTests/TelemetryFilterTest.cs +++ b/test/dotnet.Tests/TelemetryTests/TelemetryFilterTest.cs @@ -137,4 +137,59 @@ public void WhenCalledWithMissingArgumentWorkloadSubLevelCommandNameAndArgumentS e.Properties["subcommand"] == Sha256Hasher.Hash("INSTALL")); } + + [TestMethod] + public void DotnetHelpShouldSendHelpVerbToTelemetry() + { + var parseResult = Parser.Parse(["--help"]); + TelemetryEventEntry.SendFiltered(parseResult); + _fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == "toplevelparser/command" && + e.Properties.ContainsKey("verb") && + e.Properties["verb"] == Sha256Hasher.Hash("--HELP") && + e.Properties.ContainsKey("help") && + e.Properties["help"] == Sha256Hasher.Hash("TRUE")); + } + + [TestMethod] + public void DotnetVersionShouldSendVersionVerbToTelemetry() + { + var parseResult = Parser.Parse(["--version"]); + TelemetryEventEntry.SendFiltered(parseResult); + _fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == "toplevelparser/command" && + e.Properties.ContainsKey("verb") && + e.Properties["verb"] == Sha256Hasher.Hash("--VERSION")); + } + + [TestMethod] + public void DotnetInfoShouldSendInfoVerbToTelemetry() + { + var parseResult = Parser.Parse(["--info"]); + TelemetryEventEntry.SendFiltered(parseResult); + _fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == "toplevelparser/command" && + e.Properties.ContainsKey("verb") && + e.Properties["verb"] == Sha256Hasher.Hash("--INFO")); + } + + [TestMethod] + public void SubcommandHelpShouldSendVerbWithHelpProperty() + { + var parseResult = Parser.Parse(["build", "--help"]); + TelemetryEventEntry.SendFiltered(parseResult); + _fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == "toplevelparser/command" && + e.Properties.ContainsKey("verb") && + e.Properties["verb"] == Sha256Hasher.Hash("BUILD") && + e.Properties.ContainsKey("help") && + e.Properties["help"] == Sha256Hasher.Hash("TRUE")); + } + + [TestMethod] + public void RegularBuildCommandShouldNotHaveHelpProperty() + { + var parseResult = Parser.Parse(["build"]); + TelemetryEventEntry.SendFiltered(parseResult); + _fakeTelemetry.LogEntries.Should().Contain(e => e.EventName == "toplevelparser/command" && + e.Properties.ContainsKey("verb") && + e.Properties["verb"] == Sha256Hasher.Hash("BUILD") && + !e.Properties.ContainsKey("help")); + } }