diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTelemetryTests.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTelemetryTests.cs index 424fe30a378e..207b6dabce49 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTelemetryTests.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTelemetryTests.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Management.Automation.Subsystem; using System.Threading; using System.Threading.Tasks; @@ -48,11 +49,11 @@ public AzPredictorTelemetryTests(ModelFixture modelFixture) [Theory] [InlineData("git status")] [InlineData("New-Item")] - [InlineData(@"$a='ResourceGroup01'")] + [InlineData(@"$a=ls 'ResourceGroup01'")] public async Task VerifyStartEarlyProessingForOneUnsupportedCommandHistory(string inputData) { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); IReadOnlyList history = new List() { @@ -62,7 +63,7 @@ public async Task VerifyStartEarlyProessingForOneUnsupportedCommandHistory(strin azPredictor.StartEarlyProcessing(AzPredictorTelemetryTests.AzPredictorClient, history); await telemetryClient.HistoryTaskCompletionSource.Task; - Assert.Equal(AzPredictorConstants.CommandPlaceholder, telemetryClient.HistoryData.Command); + Assert.Equal(AzPredictorTelemetryTests.GetCommandName(inputData), telemetryClient.HistoryData.Command); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.HistoryData.ClientId); await telemetryClient.RequestPredictionTaskCompletionSource.Task; @@ -72,14 +73,14 @@ public async Task VerifyStartEarlyProessingForOneUnsupportedCommandHistory(strin Assert.EndsWith("CommandHistory", telemetryClient.RecordedTelemetry[0].EventName); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]); - Assert.Equal(AzPredictorConstants.CommandPlaceholder, telemetryClient.RecordedTelemetry[0].Properties["History"]); + Assert.Equal(AzPredictorTelemetryTests.GetCommandName(inputData), telemetryClient.RecordedTelemetry[0].Properties["History"]); Assert.EndsWith("RequestPrediction", telemetryClient.RecordedTelemetry[1].EventName); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RecordedTelemetry[1].Properties["ClientId"]); Assert.Equal($"{AzPredictorConstants.CommandPlaceholder}\n{AzPredictorConstants.CommandPlaceholder}", telemetryClient.RecordedTelemetry[1].Properties["Command"]); - // The correlation id are changed in OnHistory. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // The request id are changed in OnRequestPrediction. + AzPredictorTelemetryTests.EnsureDifferentRequestId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); @@ -97,7 +98,7 @@ public async Task VerifyStartEarlyProessingForOneUnsupportedCommandHistory(strin public async Task VerifyStartEarlyProcessingForOneSupportedCommandWithoutParameter() { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); var testCaseClientId = "TestCase"; @@ -126,8 +127,8 @@ public async Task VerifyStartEarlyProcessingForOneSupportedCommandWithoutParamet Assert.Equal(testCaseClientId, telemetryClient.RecordedTelemetry[1].Properties["ClientId"]); Assert.Equal($"{AzPredictorConstants.CommandPlaceholder}\n{history[0]}", telemetryClient.RecordedTelemetry[1].Properties["Command"]); - // The correlation id are changed in OnHistory. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // The request id are changed in OnRequestPrediction. + AzPredictorTelemetryTests.EnsureDifferentRequestId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); @@ -145,7 +146,7 @@ public async Task VerifyStartEarlyProcessingForOneSupportedCommandWithoutParamet public async Task VerifyStartEarlyProcessingForOneSupportedCommandWithParameter() { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); var testCaseClientId = "TestCase"; // There is only one command with parameter. @@ -173,8 +174,8 @@ public async Task VerifyStartEarlyProcessingForOneSupportedCommandWithParameter( Assert.Equal(testCaseClientId, telemetryClient.RecordedTelemetry[1].Properties["ClientId"]); Assert.Equal($"{AzPredictorConstants.CommandPlaceholder}\n{maskedCommand}", telemetryClient.RecordedTelemetry[1].Properties["Command"]); - // The correlation id are changed in OnHistory. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // The request id are changed in OnRequestPrediction. + AzPredictorTelemetryTests.EnsureDifferentRequestId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); @@ -192,7 +193,7 @@ public async Task VerifyStartEarlyProcessingForOneSupportedCommandWithParameter( public async Task VerifyStartEarlyProcessingForTwoSupportedCommandHistory() { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); IReadOnlyList history = new List() { "Get-AzContext", @@ -224,8 +225,8 @@ public async Task VerifyStartEarlyProcessingForTwoSupportedCommandHistory() Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RecordedTelemetry[1].Properties["ClientId"]); Assert.Equal(string.Join("\n", maskedCommands), telemetryClient.RecordedTelemetry[1].Properties["Command"]); - // The correlation id are changed in OnHistory. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // The request id are changed in OnRequestPrediction. + AzPredictorTelemetryTests.EnsureDifferentRequestId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); @@ -243,7 +244,7 @@ public async Task VerifyStartEarlyProcessingForTwoSupportedCommandHistory() public async Task VerifyStartEarlyProcessingForTwoUnsupportedCommandInHistory() { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); IReadOnlyList history = new List() { @@ -264,13 +265,14 @@ public async Task VerifyStartEarlyProcessingForTwoUnsupportedCommandInHistory() Assert.EndsWith("CommandHistory", telemetryClient.RecordedTelemetry[0].EventName); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]); + // Should use the placeholder for the assignment like \"$a='ResourceGroup01'\" where there is no command name at the right side. Assert.Equal(AzPredictorConstants.CommandPlaceholder, telemetryClient.RecordedTelemetry[0].Properties["History"]); Assert.EndsWith("RequestPrediction", telemetryClient.RecordedTelemetry[1].EventName); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RecordedTelemetry[1].Properties["ClientId"]); Assert.Equal($"{AzPredictorConstants.CommandPlaceholder}\n{AzPredictorConstants.CommandPlaceholder}", telemetryClient.RecordedTelemetry[1].Properties["Command"]); - // The correlation id are changed in OnHistory. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // The request id are changed in OnRequestPrediction. + AzPredictorTelemetryTests.EnsureDifferentRequestId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); @@ -288,7 +290,7 @@ public async Task VerifyStartEarlyProcessingForTwoUnsupportedCommandInHistory() public async Task VerifyStartEarlyProcessingForUnsupportedCommandAfterSupportedOnes() { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); var history = new List() { @@ -325,37 +327,35 @@ public async Task VerifyStartEarlyProcessingForUnsupportedCommandAfterSupportedO telemetryClient.ResetWaitingTasks(); expectedTelemetryCount = 1; - telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount + 1; + telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount; history.Add("git status"); azPredictor.StartEarlyProcessing(AzPredictorTelemetryTests.AzPredictorClient, history); await telemetryClient.HistoryTaskCompletionSource.Task; - Assert.Equal(AzPredictorConstants.CommandPlaceholder, telemetryClient.HistoryData.Command); + Assert.Equal(AzPredictorTelemetryTests.GetCommandName(history.Last()), telemetryClient.HistoryData.Command); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.HistoryData.ClientId); // Don't need to await on telemetryClient.RequestPredictioinTask, because "git" isn't a supported command and RequestPredictionsAsync isn't called. - // The commands to request predictions are the same as previous request. - Assert.Equal(new List() { "New-AzResourceGroup -Name:***", "New-AzVM -Location *** -Name ***" }, telemetryClient.RequestPredictionData.Commands); - Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RequestPredictionData.ClientId); VerifyTelemetryRecordCount(expectedTelemetryCount, telemetryClient); + Assert.Null(telemetryClient.RequestPredictionData); Assert.EndsWith("CommandHistory", telemetryClient.RecordedTelemetry[0].EventName); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]); - Assert.Equal(AzPredictorConstants.CommandPlaceholder, telemetryClient.RecordedTelemetry[0].Properties["History"]); + Assert.Equal(AzPredictorTelemetryTests.GetCommandName(history.Last()), telemetryClient.RecordedTelemetry[0].Properties["History"]); var secondHistoryData = telemetryClient.HistoryData; // Make sure that the RequestPrediction event can be correlated to the right History event. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(firstHistoryData, firstRequestPredictionData); - AzPredictorTelemetryTests.EnsureSameCorrelationId(firstRequestPredictionData, secondHistoryData); + AzPredictorTelemetryTests.EnsureDifferentRequestId(firstHistoryData, firstRequestPredictionData); + AzPredictorTelemetryTests.EnsureSameRequestId(firstRequestPredictionData, secondHistoryData); AzPredictorTelemetryTests.EnsureSameSessionId(firstHistoryData, firstRequestPredictionData); AzPredictorTelemetryTests.EnsureSameSessionId(firstRequestPredictionData, secondHistoryData); telemetryClient.ResetWaitingTasks(); expectedTelemetryCount = 1; - telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount + 1; + telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount; history.Add(@"$a='NewResourceName'"); azPredictor.StartEarlyProcessing(AzPredictorTelemetryTests.AzPredictorClient, history); @@ -365,24 +365,22 @@ public async Task VerifyStartEarlyProcessingForUnsupportedCommandAfterSupportedO Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.HistoryData.ClientId); // Don't need to await on telemetryClient.RequestPredictioinTask, because assignment isn't a supported command and RequestPredictionsAsync isn't called. - // The commands to request predictions are the same as previous request. - Assert.Equal(new List() { "New-AzResourceGroup -Name:***", "New-AzVM -Location *** -Name ***" }, telemetryClient.RequestPredictionData.Commands); - Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RequestPredictionData.ClientId); VerifyTelemetryRecordCount(expectedTelemetryCount, telemetryClient); + Assert.Null(telemetryClient.RequestPredictionData); Assert.EndsWith("CommandHistory", telemetryClient.RecordedTelemetry[0].EventName); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]); - Assert.Equal(AzPredictorConstants.CommandPlaceholder, telemetryClient.RecordedTelemetry[0].Properties["History"]); + Assert.Equal(AzPredictorTelemetryTests.GetCommandName(history.Last()), telemetryClient.RecordedTelemetry[0].Properties["History"]); - // There is no new request prediction. The correlation id isn't changed. - AzPredictorTelemetryTests.EnsureSameCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // There is no new request prediction. The request id isn't changed. + AzPredictorTelemetryTests.EnsureSameRequestId(firstRequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. - AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + AzPredictorTelemetryTests.EnsureSameSessionId(firstRequestPredictionData, telemetryClient.HistoryData); telemetryClient.ResetWaitingTasks(); expectedTelemetryCount = 2; - telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount + 1; + telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount; history.Add("Get-AzResourceGroup -Name:ResourceGroup01"); azPredictor.StartEarlyProcessing(AzPredictorTelemetryTests.AzPredictorClient, history); @@ -409,8 +407,8 @@ public async Task VerifyStartEarlyProcessingForUnsupportedCommandAfterSupportedO Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RecordedTelemetry[1].Properties["ClientId"]); Assert.Equal(string.Join("\n", maskedCommands), telemetryClient.RecordedTelemetry[1].Properties["Command"]); - // The correlation id are changed in OnHistory. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // The request id are changed in OnRequestPrediction. + AzPredictorTelemetryTests.EnsureDifferentRequestId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); @@ -423,7 +421,7 @@ public async Task VerifyStartEarlyProcessingForUnsupportedCommandAfterSupportedO public async Task VerifyStartEarlyProcessingForUnsupportedAndSupportedCommands() { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); var history = new List() { "git status", @@ -445,8 +443,8 @@ public async Task VerifyStartEarlyProcessingForUnsupportedAndSupportedCommands() await telemetryClient.RequestPredictionTaskCompletionSource.Task; Assert.Equal(maskedCommands, telemetryClient.RequestPredictionData.Commands); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RequestPredictionData.ClientId); - // The correlation id are changed in OnHistory. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // The request id are changed in OnRequestPrediction. + AzPredictorTelemetryTests.EnsureDifferentRequestId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); @@ -461,7 +459,7 @@ public async Task VerifyStartEarlyProcessingForUnsupportedAndSupportedCommands() public async Task VerifyStartEarlyProcessingForSupportedAndUnsupportedCommands() { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); var history = new List() { "New-AzVM -Name hello -Location WestUS", @@ -477,14 +475,14 @@ public async Task VerifyStartEarlyProcessingForSupportedAndUnsupportedCommands() }; await telemetryClient.HistoryTaskCompletionSource.Task; - Assert.Equal(maskedCommands[0], telemetryClient.HistoryData.Command); + Assert.Equal(AzPredictorTelemetryTests.GetCommandName(history[1]), telemetryClient.HistoryData.Command); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.HistoryData.ClientId); await telemetryClient.RequestPredictionTaskCompletionSource.Task; Assert.Equal(maskedCommands, telemetryClient.RequestPredictionData.Commands); Assert.Equal(AzPredictorTelemetryTests.AzPredictorClient, telemetryClient.RequestPredictionData.ClientId); - // The correlation id are changed in OnHistory. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // The request id are changed in OnRequestPrediction. + AzPredictorTelemetryTests.EnsureDifferentRequestId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); @@ -499,8 +497,7 @@ public async Task VerifyStartEarlyProcessingForSupportedAndUnsupportedCommands() public async Task VerifyStartEarlyProcessingException() { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: true, expectedTelemetryCount + 1); - + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: true, expectedTelemetryCount); var history = new List() { "New-AzVM -Name hello -Location WestUS", @@ -529,8 +526,8 @@ public async Task VerifyStartEarlyProcessingException() Assert.Equal($"{AzPredictorConstants.CommandPlaceholder}\n{maskedCommand}", telemetryClient.RecordedTelemetry[1].Properties["Command"]); Assert.StartsWith($"Type: {typeof(MockTestException)}\nStack Trace: ", telemetryClient.RecordedTelemetry[1].Properties["Exception"]); - // The correlation id are changed in OnHistory. - AzPredictorTelemetryTests.EnsureDifferentCorrelationId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); + // The request id are changed in OnRequestPrediction. + AzPredictorTelemetryTests.EnsureDifferentRequestId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); // SetssionId is not changed. AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.RequestPredictionData, telemetryClient.HistoryData); @@ -543,7 +540,7 @@ public async Task VerifyStartEarlyProcessingException() private void VerifySameSuggestionSessionId() { var expectedTelemetryCount = 1; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); var predictionContext = PredictionContext.Create("New-AzResourceGroup -Name 'ResourceGroup01' -Location 'Central US' -WhatIf"); var suggestionPackage = azPredictor.GetSuggestion(AzPredictorTelemetryTests.AzPredictorClient, predictionContext, CancellationToken.None); @@ -564,7 +561,7 @@ private void VerifySameSuggestionSessionId() var displayCountOrIndex = 3; telemetryClient.ResetWaitingTasks(); - telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount + 1; + telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount; azPredictor.OnSuggestionDisplayed(AzPredictorTelemetryTests.AzPredictorClient, suggestionPackage.Session.Value, displayCountOrIndex); @@ -579,7 +576,7 @@ private void VerifySameSuggestionSessionId() var acceptedSuggestion = "SuggestionAccepted"; telemetryClient.ResetWaitingTasks(); - telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount + 1; + telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount; azPredictor.OnSuggestionAccepted(AzPredictorTelemetryTests.AzPredictorClient, suggestionPackage.Session.Value, acceptedSuggestion); @@ -596,8 +593,8 @@ private void VerifySameSuggestionSessionId() Assert.Equal(suggestionPackage.Session.Value, telemetryClient.SuggestionAcceptedData.SuggestionSessionId); Assert.Equal(acceptedSuggestion, telemetryClient.SuggestionAcceptedData.Suggestion); - AzPredictorTelemetryTests.EnsureSameCorrelationId(telemetryClient.GetSuggestionData, telemetryClient.SuggestionDisplayedData); - AzPredictorTelemetryTests.EnsureSameCorrelationId(telemetryClient.GetSuggestionData, telemetryClient.SuggestionAcceptedData); + AzPredictorTelemetryTests.EnsureSameRequestId(telemetryClient.GetSuggestionData, telemetryClient.SuggestionDisplayedData); + AzPredictorTelemetryTests.EnsureSameRequestId(telemetryClient.GetSuggestionData, telemetryClient.SuggestionAcceptedData); AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.GetSuggestionData, telemetryClient.SuggestionDisplayedData); AzPredictorTelemetryTests.EnsureSameSessionId(telemetryClient.GetSuggestionData, telemetryClient.SuggestionAcceptedData); @@ -612,7 +609,7 @@ private void VerifySameSuggestionSessionId() public void VerifySuggestionSessionIdChanged() { var expectedTelemetryCount = 2; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); var predictionContext = PredictionContext.Create("New-AzResourceGroup -Name 'ResourceGroup01' -Location 'Central US' -WhatIf"); var firstSuggestionPackage = azPredictor.GetSuggestion(AzPredictorTelemetryTests.AzPredictorClient, predictionContext, CancellationToken.None); @@ -622,7 +619,7 @@ public void VerifySuggestionSessionIdChanged() var secondGetSuggestionData = telemetryClient.GetSuggestionData; Assert.NotEqual(secondSuggestionPackage.Session, firstSuggestionPackage.Session); - AzPredictorTelemetryTests.EnsureSameCorrelationId(secondGetSuggestionData, firstGetSuggestionData); + AzPredictorTelemetryTests.EnsureSameRequestId(secondGetSuggestionData, firstGetSuggestionData); AzPredictorTelemetryTests.EnsureSameSessionId(secondGetSuggestionData, firstGetSuggestionData); VerifyTelemetryRecordCount(expectedTelemetryCount, telemetryClient); @@ -635,7 +632,7 @@ public void VerifySuggestionSessionIdChanged() public void VerifyListViewInSuggestionDisplayed() { var expectedTelemetryCount = 1; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); uint suggestionSessionId = 2; var suggestionCountOrIndex = 4; @@ -663,7 +660,7 @@ public void VerifyListViewInSuggestionDisplayed() public void VerifyInlineViewInSuggestionDisplayedAtEdge() { var expectedTelemetryCount = 1; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); uint suggestionSessionId = 40; var suggestionCountOrIndex = 0; @@ -691,7 +688,7 @@ public void VerifyInlineViewInSuggestionDisplayedAtEdge() public void VerifyInlineViewInSuggestionDisplayed() { var expectedTelemetryCount = 1; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); uint suggestionSessionId = 14; var suggestionCountOrIndex = -1; @@ -719,7 +716,7 @@ public void VerifyInlineViewInSuggestionDisplayed() public void VerifyExceptionInGetSuggestion() { var expectedTelemetryCount = 1; - var (azPredictor, telemetryClient) = CreateTestObjects(throwException: true, expectedTelemetryCount + 1); + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: true, expectedTelemetryCount); var predictionContext = PredictionContext.Create("New-AzResourceGroup -Name 'ResourceGroup01' -Location 'Central US' -WhatIf"); var suggestionPackage = azPredictor.GetSuggestion(AzPredictorTelemetryTests.AzPredictorClient, predictionContext, CancellationToken.None); @@ -735,6 +732,114 @@ public void VerifyExceptionInGetSuggestion() Assert.StartsWith($"Type: {typeof(MockTestException)}\nStack Trace: ", telemetryClient.RecordedTelemetry[0].Properties["Exception"]); } + /// + /// Verify that the command id is matched. + /// + [Fact] + public void VerifyCommandIds() + { + var expectedTelemetryCount = 3; + var (azPredictor, telemetryClient) = CreateTestObjects(throwException: false, expectedTelemetryCount); + + var history = new List() + { + "New-AzVM -Name hello -Location WestUS", + }; + + azPredictor.StartEarlyProcessing(AzPredictorTelemetryTests.AzPredictorClient, history); + + var predictionContext = PredictionContext.Create("New-AzResourceGroup -Name 'ResourceGroup01' -Location 'Central US'"); + var suggestionPackage = azPredictor.GetSuggestion(AzPredictorTelemetryTests.AzPredictorClient, predictionContext, CancellationToken.None); + + VerifyTelemetryRecordCount(expectedTelemetryCount, telemetryClient); + + var originalHistoryData = telemetryClient.HistoryData; + var firstRequestPredictionData = telemetryClient.RequestPredictionData; + var firstGetSuggestionData = telemetryClient.GetSuggestionData; + + expectedTelemetryCount = 2; + telemetryClient.ResetWaitingTasks(); + telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount; + + history = new List() + { + "Get-AzSqlServer", + }; + + azPredictor.StartEarlyProcessing(AzPredictorTelemetryTests.AzPredictorClient, history); + + VerifyTelemetryRecordCount(expectedTelemetryCount, telemetryClient); + + var firstHistoryData = telemetryClient.HistoryData; + var secondRequestPredictionData = telemetryClient.RequestPredictionData; + + EnsureSameRequestId(firstRequestPredictionData, firstGetSuggestionData); + EnsureSameRequestId(firstRequestPredictionData, firstHistoryData); + EnsureSameCommandId(firstHistoryData, firstRequestPredictionData); + EnsureSameCommandId(firstHistoryData, firstGetSuggestionData); + + // TODO should verify the RecordedTelemetry + + EnsureDifferentComamandId(firstHistoryData, originalHistoryData); + EnsureDifferentRequestId(firstRequestPredictionData, secondRequestPredictionData); + + // Now add a "git" command. It doesn't change the request id, but change the command id. + + expectedTelemetryCount = 2; + telemetryClient.ResetWaitingTasks(); + telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount; + + predictionContext = PredictionContext.Create("New-AzResourceGroup -Name 'ResourceGroup01' -Location 'Central US'"); + suggestionPackage = azPredictor.GetSuggestion(AzPredictorTelemetryTests.AzPredictorClient, predictionContext, CancellationToken.None); + + history =new List() + { + "git status", + }; + + azPredictor.StartEarlyProcessing(AzPredictorTelemetryTests.AzPredictorClient, history); + + VerifyTelemetryRecordCount(expectedTelemetryCount, telemetryClient); + + var secondGetSuggestionData = telemetryClient.GetSuggestionData; + var secondHistoryData = telemetryClient.HistoryData; + var thirdRequestPredictionData = telemetryClient.RequestPredictionData; + + expectedTelemetryCount = 2; + telemetryClient.ResetWaitingTasks(); + telemetryClient.ExceptedTelemetryRecordCount = expectedTelemetryCount; + + predictionContext = PredictionContext.Create("New-AzVM -Name 'VM01' -Location 'Central US'"); + suggestionPackage = azPredictor.GetSuggestion(AzPredictorTelemetryTests.AzPredictorClient, predictionContext, CancellationToken.None); + + history =new List() + { + "git commit", + }; + + azPredictor.StartEarlyProcessing(AzPredictorTelemetryTests.AzPredictorClient, history); + + VerifyTelemetryRecordCount(expectedTelemetryCount, telemetryClient); + + var thirdGetSuggestionData = telemetryClient.GetSuggestionData; + var thirdHistoryData = telemetryClient.HistoryData; + var fourthRequestPredictionData = telemetryClient.RequestPredictionData; + + Assert.Null(thirdRequestPredictionData); // Since it's "git", we don't send the request. + Assert.Null(fourthRequestPredictionData); // Since it's "git", we don't send the request. + + EnsureSameRequestId(secondRequestPredictionData, secondGetSuggestionData); + EnsureSameRequestId(secondRequestPredictionData, secondHistoryData); + EnsureSameRequestId(secondRequestPredictionData, thirdGetSuggestionData); + EnsureSameRequestId(secondRequestPredictionData, thirdHistoryData); + + EnsureDifferentComamandId(secondHistoryData, firstHistoryData); + EnsureSameCommandId(secondHistoryData, secondGetSuggestionData); + + EnsureDifferentComamandId(thirdHistoryData, secondHistoryData); + EnsureSameCommandId(thirdHistoryData, thirdGetSuggestionData); + } + private (AzPredictor, MockAzPredictorTelemetryClient) CreateTestObjects(bool throwException, int expectedTelemetryEvent) { var telemetryClient = new MockAzPredictorTelemetryClient(); @@ -757,31 +862,63 @@ public void VerifyExceptionInGetSuggestion() /// /// Verifies that the number of telemetry events to be sent is equal to . /// - /// - /// It requires to set up the with the one more than . - /// That means the expectedTelemetryEvent in should be one plus - /// . - /// Internally, it'll check that the task in times out because it's waiting for one more telemetry event and that telemetry event never comes. - /// private void VerifyTelemetryRecordCount(int expectedCount, MockAzPredictorTelemetryClient telemetryClient) { - Assert.False(telemetryClient.SendTelemetryTaskCompletionSource.Task.Wait(TimeSpan.FromMilliseconds(200))); + Assert.True(telemetryClient.SendTelemetryTaskCompletionSource.Task.Wait(TimeSpan.FromMilliseconds(500))); Assert.Equal(expectedCount, telemetryClient.RecordedTelemetry.Count); } - private static void EnsureDifferentCorrelationId(ITelemetryData expected, ITelemetryData actual) + private static void EnsureDifferentComamandId(ITelemetryData expected, ITelemetryData actual) { - Assert.NotEqual(expected.CorrelationId, actual.CorrelationId); + Assert.NotEqual(expected.CommandId, actual.CommandId); } - private static void EnsureSameCorrelationId(ITelemetryData expected, ITelemetryData actual) + private static void EnsureSameCommandId(ITelemetryData expected, ITelemetryData actual) { - Assert.Equal(expected.CorrelationId, actual.CorrelationId); + Assert.Equal(expected.CommandId, actual.CommandId); + } + + private static void EnsureDifferentRequestId(ITelemetryData expected, ITelemetryData actual) + { + Assert.NotEqual(expected.RequestId, actual.RequestId); + } + + private static void EnsureSameRequestId(ITelemetryData expected, ITelemetryData actual) + { + Assert.Equal(expected.RequestId, actual.RequestId); } private static void EnsureSameSessionId(ITelemetryData expected, ITelemetryData actual) { Assert.Equal(expected.SessionId, actual.SessionId); } + + /// + /// Gets the command name offrom the user input. + /// + /// + /// It only needs to be able to get the commands name from the user input in the test cases. It's not intended to be + /// comprehensive and fine tuned. + /// + private static string GetCommandName(string commandInput) + { + // If there is a '=' in it, treat as assignment and parse the right side. + var inputSegments = commandInput.Split('='); + + if (inputSegments.Count() == 1) + { + return commandInput.Split(' ')[0]; + } + + var rightSide = inputSegments.Last().Trim(); + + if ((('a' <= rightSide[0]) && (rightSide[0] <= 'z')) || (('A' <= rightSide[0]) && (rightSide[0] <= 'Z'))) + { + return GetCommandName(rightSide); + } + + return AzPredictorConstants.CommandPlaceholder; + + } } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs index 759a04622cfd..905e4bb1bf22 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorTelemetryClient.cs @@ -102,6 +102,8 @@ public void ResetWaitingTasks() HistoryTaskCompletionSource = new TaskCompletionSource(); RequestPredictionTaskCompletionSource = new TaskCompletionSource(); SendTelemetryTaskCompletionSource = new TaskCompletionSource(); + HistoryData = default; + RequestPredictionData = default; } protected override TelemetryClient GetApplicationInsightTelemetryClient() diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Az.Tools.Predictor.csproj b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Az.Tools.Predictor.csproj index 27a9b1191cc0..df2738b6fedd 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Az.Tools.Predictor.csproj +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Az.Tools.Predictor.csproj @@ -16,7 +16,7 @@ Az.Tools.Predictor - 0.2.0 + 0.2.1 Microsoft Corporation Microsoft Corporation Microsoft Corporation. All rights reserved. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Az.Tools.Predictor.psd1 b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Az.Tools.Predictor.psd1 index d991c8d12d6d..f03252aa578c 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Az.Tools.Predictor.psd1 +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Az.Tools.Predictor.psd1 @@ -11,7 +11,7 @@ RootModule = '' # Version number of this module. -ModuleVersion = '0.2.0' +ModuleVersion = '0.2.1' # Supported PSEditions CompatiblePSEditions = 'Core' @@ -43,11 +43,11 @@ PowerShellVersion = '7.2' # Modules that must be imported into the global environment prior to importing this module # RequiredModules = @(@{ModuleName="PSReadLine"; ModuleVersion="2.2.0-beta2"}) -NestedModules = @("Microsoft.Azure.PowerShell.Tools.AzPredictor.dll") +# NestedModules = @("Microsoft.Azure.PowerShell.Tools.AzPredictor.dll") ScriptsToProcess = @("PromptSurvey.ps1") -CmdletsToExport = @("Enable-AzPredictor", "Disable-AzPredictor") +CmdletsToExport = @("Enable-AzPredictor", "Disable-AzPredictor", "Open-AzSurvey") # Format files (.ps1xml) to be loaded when importing this module @@ -69,7 +69,8 @@ PrivateData = @{ # IconUri = '' # ReleaseNotes of this module - ReleaseNotes = '* second preview release + ReleaseNotes = '* Add a cmdlet to open survey page and update message +* Second preview release * Add Enable-AzPredictor and Disable-AzPredictor cmdlet to simplify configuration * Use PSReadline new API' diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs index e7d320ec01f5..41373ed60dd5 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs @@ -120,11 +120,11 @@ public void StartEarlyProcessing(string clientId, IReadOnlyList history) { string secondToLastLine = history.TakeLast(AzPredictorConstants.CommandHistoryCountToProcess).First(); var secondToLastCommand = GetAstAndMaskedCommandLine(secondToLastLine); - _lastTwoMaskedCommands.Enqueue(secondToLastCommand.Item2); + _lastTwoMaskedCommands.Enqueue(secondToLastCommand.IsSupported ? secondToLastCommand.MaskedValue : AzPredictorConstants.CommandPlaceholder); - if (!string.Equals(AzPredictorConstants.CommandPlaceholder, secondToLastCommand.Item2, StringComparison.Ordinal)) + if (secondToLastCommand.IsSupported) { - _service.RecordHistory(secondToLastCommand.Item1); + _service.RecordHistory(secondToLastCommand.Ast); } } else @@ -139,7 +139,7 @@ public void StartEarlyProcessing(string clientId, IReadOnlyList history) string lastLine = history.Last(); var lastCommand = GetAstAndMaskedCommandLine(lastLine); - bool isLastCommandSupported = !string.Equals(AzPredictorConstants.CommandPlaceholder, lastCommand.Item2, StringComparison.Ordinal); + bool isLastCommandSupported = lastCommand.IsSupported; if (isLastCommandSupported) { @@ -162,7 +162,7 @@ public void StartEarlyProcessing(string clientId, IReadOnlyList history) _lastTwoMaskedCommands.Enqueue(existingInQueue); } - _telemetryClient.OnHistory(new HistoryTelemetryData(clientId, lastCommand.Item2)); + _telemetryClient.OnHistory(new HistoryTelemetryData(clientId, lastCommand.MaskedValue ?? AzPredictorConstants.CommandPlaceholder)); if (isLastTwoCommandsChanged) { @@ -202,21 +202,17 @@ public void StartEarlyProcessing(string clientId, IReadOnlyList history) } } - ValueTuple GetAstAndMaskedCommandLine(string commandLine) + (CommandAst Ast, string MaskedValue, bool IsSupported) GetAstAndMaskedCommandLine(string commandLine) { var asts = Parser.ParseInput(commandLine, out _, out _); var allNestedAsts = asts?.FindAll((ast) => ast is CommandAst, true); var commandAst = allNestedAsts?.LastOrDefault() as CommandAst; - string maskedCommandLine = AzPredictorConstants.CommandPlaceholder; var commandName = commandAst?.CommandElements?.FirstOrDefault().ToString(); + bool isSupported = _service.IsSupportedCommand(commandName); + string maskedCommandLine = CommandLineUtilities.MaskCommandLine(commandAst); - if (_service.IsSupportedCommand(commandName)) - { - maskedCommandLine = CommandLineUtilities.MaskCommandLine(commandAst); - } - - return ValueTuple.Create(commandAst, maskedCommandLine); + return (commandAst, maskedCommandLine, isSupported); } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs index 0535c910ba85..ff9f8b64d488 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs @@ -251,7 +251,7 @@ public virtual async Task RequestPredictionsAsync(IEnumerable comm { SetCommandToRequestPrediction(localCommands); - AzPredictorService.SetHttpRequestHeader(_client?.DefaultRequestHeaders, _azContext.HashUserId, _telemetryClient.CorrelationId); + AzPredictorService.SetHttpRequestHeader(_client?.DefaultRequestHeaders, _azContext.HashUserId, _telemetryClient.RequestId); var requestContext = new PredictionRequestBody.RequestContext() { @@ -313,7 +313,7 @@ protected virtual void RequestAllPredictiveCommands() try { - AzPredictorService.SetHttpRequestHeader(_client.DefaultRequestHeaders, _azContext.HashUserId, _telemetryClient.CorrelationId); + AzPredictorService.SetHttpRequestHeader(_client.DefaultRequestHeaders, _azContext.HashUserId, _telemetryClient.RequestId); var httpResponseMessage = await _client.GetAsync(_commandsEndpoint); hasSentHttpRequest = true; diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Commands/OpenAzSurvey.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Commands/OpenAzSurvey.cs new file mode 100644 index 000000000000..57a401c2a851 --- /dev/null +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Commands/OpenAzSurvey.cs @@ -0,0 +1,82 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Management.Automation; +using System.Text; +using System.Text.Json; + +namespace Microsoft.Azure.PowerShell.Tools.AzPredictor +{ + /// + /// Cmdlet to open a survey link in the default browser + /// This cmdlet will open a survey link in the default browser. All data from this survey will be anonymized. See the Microsoft Privacy Policy (https://privacy.microsoft.com/) for more information + /// + [Cmdlet("Open", "AzSurvey"), OutputType(typeof(bool))] + public sealed class OpenAzSurvey : PSCmdlet + { + private const string _SurveyLinkFormat = "https://aka.ms/azpredictorisurvey?SessionId={0}&Q_CHL=cmdlet"; + /// + /// Indicates whether the user would like to receive output. + /// + [Parameter(Mandatory = false)] + public SwitchParameter PassThru { get; set; } + + /// + protected override void ProcessRecord() + { + var profileSettings = Settings.GetProfileSettings(); + var surveyId = profileSettings?.SurveyId?.ToString(CultureInfo.InvariantCulture) ?? "000000"; + + var link = string.Format(OpenAzSurveyLink._SurveyLinkFormat, surveyId, CultureInfo.InvariantCulture); + + Console.WriteLine($"Opening survey {link}"); + + // TODO [mahuang] Ouput the link for user to copy/paste in case it's not open in the default browser. + var processStartInfo = new ProcessStartInfo(); + processStartInfo.FileName = link; + processStartInfo.UseShellExecute = true; + + Process.Start(processStartInfo); + + if (profileSettings != null) + { + profileSettings.SurveyId = null; + + try + { + var fileContent = JsonSerializer.Serialize(profileSettings, new JsonSerializerOptions(JsonUtilities.DefaultSerializerOptions) + { + WriteIndented = true, + }); + var profileSettingFilePath = Settings.GetProfileSettingsFilePath(); + File.WriteAllText(profileSettingFilePath, fileContent, Encoding.UTF8); + } + catch + { + // Ignore all exceptions. + } + } + + if (PassThru.IsPresent) + { + WriteObject(true); + } + } + } +} diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/InterceptSurvey.ps1 b/tools/Az.Tools.Predictor/Az.Tools.Predictor/InterceptSurvey.ps1 index 1b359e079b51..97387762d9d3 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/InterceptSurvey.ps1 +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/InterceptSurvey.ps1 @@ -41,7 +41,8 @@ if ($env:Azure_PS_Intercept_Survey -eq "false") { $mutexName = "AzModulesInterceptSurvey" $mutexTiimeout = 1000 $interceptDays = 30 -$interceptLoadTimes = 3 +$interceptIntervals = ( 3, 2, 5 ) +$interceptMaxTimes = 3 # Show intercept this many times in total. $today = Get-Date $mutexTimeout = 500 @@ -54,10 +55,10 @@ function ConvertTo-String { return $date.ToString("yyyy-MM-dd") } -function Init-InterceptFile { - $interceptContent = @{ +function Init-InterceptObject { + return @{ "lastInterceptCheckDate"=ConvertTo-String($today); - "interceptTriggered"=$false; + "interceptTriggered"=0; "modules"=@(@{ "name"=$moduleName; "majorVersion"=$majorVersion; @@ -65,6 +66,10 @@ function Init-InterceptFile { "lastActiveDate"=ConvertTo-String($today); }) } +} + +function Init-InterceptFile { + $interceptContent = Init-InterceptObject ConvertTo-Json -InputObject $interceptContent | Out-File -FilePath $interceptFilePath -Encoding utf8 } @@ -106,12 +111,12 @@ function Update-InterceptObject { if ($recordedMajorVersion -ne $majorVersion) { $thisModule.activeDays = 1 $thisModule.lastActiveDate = ConvertTo-String($today) - $interceptObject.interceptTriggered = $false + $interceptObject.interceptTriggered = 0 return $false } - if ($interceptObject.interceptTriggered) { + if ($interceptObject.interceptTriggered -ge $interceptMaxTimes) { return $false } @@ -133,10 +138,10 @@ function Update-InterceptObject { $newActiveDays++ } - if ($newActiveDays -ge $interceptLoadTimes) { + if ($newActiveDays -ge $interceptIntervals[$interceptObject.interceptTriggered]) { $thisModule.activeDays = 0 $thisModule.lastActiveDate = ConvertTo-String($today) - $interceptObject.interceptTriggered = $true + $interceptObject.interceptTriggered++ return $true } @@ -166,15 +171,21 @@ try try { $fileContent = Get-Content $interceptFilePath | Out-String $interceptObject = ConvertFrom-Json $fileContent + + if ($interceptObject.interceptTriggered.GetType() -eq $true.GetType()) { + # HACK change the use of interceptTriggered from a boolean to an integer. It's to count how many times the intercept is tirggered. + $interceptObject.interceptTriggered = 0 + } } catch { - Init-InterceptFile } - if (-not ($interceptObject -eq $null)) { - $shouldIntercept = Update-InterceptObject($interceptObject) - - ConvertTo-Json -InputObject $interceptObject | Out-File $interceptFilePath -Encoding utf8 + if ($null -eq $interceptObject) { + $interceptObject = Init-InterceptObject } + + $shouldIntercept = Update-InterceptObject($interceptObject) + + ConvertTo-Json -InputObject $interceptObject | Out-File $interceptFilePath -Encoding utf8 } } catch { } @@ -207,7 +218,11 @@ if ($shouldIntercept) { } } - $escape = $([char]27) - Write-Host "`n$escape[7mHow was your experience using Az predictor? $escape[27m`n" -NoNewline; Write-Host "$escape[7mhttp://aka.ms/azpredictorisurvey?SessionId=$surveyId$escape[27m" -NoNewline - Write-Host "`n" + Write-Host "---------------------------------------------------"; + Write-Host "Survey:" -ForegroundColor $Host.PrivateData.VerboseBackgroundColor -BackgroundColor $host.PrivateData.VerboseForegroundColor -NoNewline; + Write-Host " How was your experience using Az predictor?"; + Write-Host ""; + Write-Host "Run " -NoNewline; Write-Host "Open-AzSurvey" -ForegroundColor $Host.PrivateData.VerboseBackgroundColor -BackgroundColor $host.PrivateData.VerboseForegroundColor -NoNewline; Write-Host " to give us your feedback."; + Write-Host "---------------------------------------------------"; + } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/PromptSurvey.ps1 b/tools/Az.Tools.Predictor/Az.Tools.Predictor/PromptSurvey.ps1 index 05f6f1ce9245..521df950642e 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/PromptSurvey.ps1 +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/PromptSurvey.ps1 @@ -13,4 +13,4 @@ # ---------------------------------------------------------------------------------- $targetScript = (Join-Path -Path $PSScriptRoot -ChildPath "InterceptSurvey.ps1") -& $targetScript "Az.Tools.Predictor" 0 +& $targetScript "Az.Tools.Predictor-Preview3" 0 diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs index 2c3f9e8a8ead..effecbefdd34 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs @@ -12,6 +12,7 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities; using System; using System.IO; using System.Text; @@ -26,10 +27,6 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor sealed class Settings { private static Settings _instance; - private static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; /// /// The maximum number of suggestions that have the same command name. @@ -103,57 +100,73 @@ public static Settings GetSettings() return Settings._instance; } + /// + /// Gets the settings from the user profile folder. + /// + internal static Settings GetProfileSettings() + { + string profileSettingFilePath = Settings.GetProfileSettingsFilePath(); + + if (File.Exists(profileSettingFilePath)) + { + try + { + var fileContent = File.ReadAllText(profileSettingFilePath, Encoding.UTF8); + return JsonSerializer.Deserialize(fileContent, JsonUtilities.DefaultSerializerOptions); + } + catch + { + // Ignore all the exception + } + } + + return null; + } + + /// + /// Gets the file path of the settings in the user profile folder. + /// + internal static string GetProfileSettingsFilePath() + { + var userProfileDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string profileDirectoryPath = Path.Join(userProfileDirectory, AzPredictorConstants.AzureProfileDirectoryName); + return Path.Join(profileDirectoryPath, AzPredictorConstants.SettingsFileName); + } + private static Settings CreateDefaultSettings() { var fileInfo = new FileInfo(typeof(Settings).Assembly.Location); var directory = fileInfo.DirectoryName; var settingFilePath = Path.Join(directory, AzPredictorConstants.SettingsFileName); var fileContent = File.ReadAllText(settingFilePath, Encoding.UTF8); - var settings = JsonSerializer.Deserialize(fileContent, Settings._jsonSerializerOptions); + var settings = JsonSerializer.Deserialize(fileContent, JsonUtilities.DefaultSerializerOptions); return settings; } private void OverrideSettingsFromProfile() { - var userProfileDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - string profileDirectoryPath = Path.Join(userProfileDirectory, AzPredictorConstants.AzureProfileDirectoryName); - string profileSettingFilePath = Path.Join(profileDirectoryPath, AzPredictorConstants.SettingsFileName); + var profileSettings = Settings.GetProfileSettings(); - if (File.Exists(profileSettingFilePath)) + if (profileSettings != null) { - try + if (!string.IsNullOrWhiteSpace(profileSettings.ServiceUri)) { - var fileContent = File.ReadAllText(profileSettingFilePath, Encoding.UTF8); - var profileSettings = JsonSerializer.Deserialize(fileContent, Settings._jsonSerializerOptions); - - if (!string.IsNullOrWhiteSpace(profileSettings.ServiceUri)) - { - ServiceUri = profileSettings.ServiceUri; - } - - if (profileSettings.SuggestionCount.HasValue && (profileSettings.SuggestionCount.Value > 0)) - { - SuggestionCount = profileSettings.SuggestionCount; - } - - if (profileSettings.MaxAllowedCommandDuplicate.HasValue && (profileSettings.MaxAllowedCommandDuplicate.Value > 0)) - { - this.MaxAllowedCommandDuplicate = profileSettings.MaxAllowedCommandDuplicate; - } - - this.SetAsInternal = profileSettings.SetAsInternal; - this.SurveyId = profileSettings.SurveyId; - - profileSettings.SurveyId = null; + ServiceUri = profileSettings.ServiceUri; + } - fileContent = JsonSerializer.Serialize(profileSettings, new JsonSerializerOptions(Settings._jsonSerializerOptions) { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); - File.WriteAllText(profileSettingFilePath, fileContent, Encoding.UTF8); + if (profileSettings.SuggestionCount.HasValue && (profileSettings.SuggestionCount.Value > 0)) + { + SuggestionCount = profileSettings.SuggestionCount; } - catch + + if (profileSettings.MaxAllowedCommandDuplicate.HasValue && (profileSettings.MaxAllowedCommandDuplicate.Value > 0)) { - // Ignore all the exception and not to update the settings. + this.MaxAllowedCommandDuplicate = profileSettings.MaxAllowedCommandDuplicate; } + + this.SetAsInternal = profileSettings.SetAsInternal; + this.SurveyId = profileSettings.SurveyId; } } diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/AzPredictorTelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/AzPredictorTelemetryClient.cs index 80681229d2a2..e9f4d0f0970e 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/AzPredictorTelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/AzPredictorTelemetryClient.cs @@ -34,10 +34,10 @@ internal class AzPredictorTelemetryClient : ITelemetryClient private const string TelemetryEventPrefix = "Az.Tools.Predictor"; /// - public string SessionId { get; } = Guid.NewGuid().ToString(); + public string RequestId { get; private set; } = Guid.NewGuid().ToString(); /// - public string CorrelationId { get; private set; } = Guid.NewGuid().ToString(); + public string SessionId { get; } = Guid.NewGuid().ToString(); /// /// The client that sends the telemetry to the server. @@ -92,11 +92,7 @@ public virtual void OnHistory(HistoryTelemetryData telemetryData) /// public virtual void OnRequestPrediction(RequestPredictionTelemetryData telemetryData) { - // OnRequestPrediction may be running as a task on a different thread, or it's not triggerred at all because the history isn't changed, - // regardless, we only update the correlation id when that happens. - // If correlation id isn't updated, that's the same as the prediction used for suggestion isn't updated yet. - // The correlation id should be to correlate the prediction from the server and the suggestions presented to the user. - CorrelationId = Guid.NewGuid().ToString(); + RequestId = Guid.NewGuid().ToString(); PostTelemetryData(telemetryData); @@ -208,7 +204,8 @@ private void PostTelemetryData(ITelemetryData telemetryData) } telemetryData.SessionId = SessionId; - telemetryData.CorrelationId = CorrelationId; + telemetryData.RequestId = RequestId; + telemetryData.CommandId = _commandId; _telemetryDispatcher.Post(telemetryData); } @@ -362,8 +359,8 @@ private IDictionary CreateProperties(ITelemetryData telemetryDat return new Dictionary() { { "SessionId", telemetryData.SessionId }, - { "CorrelationId", telemetryData.CorrelationId }, - { "CommandId", _commandId }, + { "RequestId", telemetryData.RequestId}, + { "CommandId", telemetryData.CommandId}, { "UserId", _azContext.HashUserId }, { "IsInternal", _azContext.IsInternal.ToString(CultureInfo.InvariantCulture) }, { "SurveyId", (_azContext as AzContext)?.SurveyId }, diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/GetSuggestionTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/GetSuggestionTelemetryData.cs index 39f3262a9853..021ce0b47f61 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/GetSuggestionTelemetryData.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/GetSuggestionTelemetryData.cs @@ -23,10 +23,13 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry public sealed class GetSuggestionTelemetryData : ITelemetryData { /// - string ITelemetryData.SessionId { get; set; } + string ITelemetryData.CommandId { get; set; } + + /// + string ITelemetryData.RequestId { get; set; } /// - string ITelemetryData.CorrelationId { get; set; } + string ITelemetryData.SessionId { get; set; } /// /// Gets the id of the client that makes the calls. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/HistoryTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/HistoryTelemetryData.cs index 8a46c0111737..4636642551fd 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/HistoryTelemetryData.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/HistoryTelemetryData.cs @@ -20,10 +20,13 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry public sealed class HistoryTelemetryData : ITelemetryData { /// - string ITelemetryData.SessionId { get; set; } + string ITelemetryData.CommandId { get; set; } + + /// + string ITelemetryData.RequestId { get; set; } /// - string ITelemetryData.CorrelationId { get; set; } + string ITelemetryData.SessionId { get; set; } /// /// Gets the id of the client that makes the calls. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryClient.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryClient.cs index 71e61d2ad4a2..3fa8208b25b3 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryClient.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryClient.cs @@ -20,9 +20,9 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry public interface ITelemetryClient { /// - /// Gets the correlation id for the telemetry events. + /// Gets the id to correlate the request and the server. /// - public string CorrelationId { get; } + public string RequestId { get; } /// /// Gets the session id for the telemetry events. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryData.cs index 6ccf7b880a3e..86a9b6b4da06 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryData.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ITelemetryData.cs @@ -20,9 +20,14 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry public interface ITelemetryData { /// - /// Gets the correlation id. + /// Gets the id to correlate events proceeding to and including a command history. /// - string CorrelationId { get; internal set; } + public string CommandId { get; internal set; } + + /// + /// Gets the id to correlate the request and the server. + /// + public string RequestId { get; internal set; } /// /// Gets the session id. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ParameterMapTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ParameterMapTelemetryData.cs index 91ba42baff51..c2ac05b8f5a5 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ParameterMapTelemetryData.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/ParameterMapTelemetryData.cs @@ -22,10 +22,13 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry public sealed class ParameterMapTelemetryData : ITelemetryData { /// - string ITelemetryData.SessionId { get; set; } + string ITelemetryData.CommandId { get; set; } + + /// + string ITelemetryData.RequestId { get; set; } /// - string ITelemetryData.CorrelationId { get; set; } + string ITelemetryData.SessionId { get; set; } /// /// Gets the exception if there is an error during the operation. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/RequestPredictionTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/RequestPredictionTelemetryData.cs index 6cf2983d1910..73f6e165dbcd 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/RequestPredictionTelemetryData.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/RequestPredictionTelemetryData.cs @@ -23,10 +23,13 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry public sealed class RequestPredictionTelemetryData : ITelemetryData { /// - string ITelemetryData.SessionId { get; set; } + string ITelemetryData.CommandId { get; set; } + + /// + string ITelemetryData.RequestId { get; set; } /// - string ITelemetryData.CorrelationId { get; set; } + string ITelemetryData.SessionId { get; set; } /// /// Gets the id of the client that makes the calls. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionAcceptedTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionAcceptedTelemetryData.cs index 5b1e0ee1b572..b880f1910657 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionAcceptedTelemetryData.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionAcceptedTelemetryData.cs @@ -20,10 +20,13 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry public sealed class SuggestionAcceptedTelemetryData : ITelemetryData { /// - string ITelemetryData.SessionId { get; set; } + string ITelemetryData.CommandId { get; set; } + + /// + string ITelemetryData.RequestId { get; set; } /// - string ITelemetryData.CorrelationId { get; set; } + string ITelemetryData.SessionId { get; set; } /// /// Gets the id of the client that makes the calls. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionDisplayedTelemetryData.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionDisplayedTelemetryData.cs index 1d7581c481af..fc6b54ec5dca 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionDisplayedTelemetryData.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Telemetry/SuggestionDisplayedTelemetryData.cs @@ -20,10 +20,13 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Telemetry public sealed class SuggestionDisplayedTelemetryData : ITelemetryData { /// - string ITelemetryData.SessionId { get; set; } + string ITelemetryData.CommandId { get; set; } + + /// + string ITelemetryData.RequestId { get; set; } /// - string ITelemetryData.CorrelationId { get; set; } + string ITelemetryData.SessionId { get; set; } /// /// Gets the id of the client that makes the calls. diff --git a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs index 656ab2419e30..368390cb3076 100644 --- a/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs +++ b/tools/Az.Tools.Predictor/Az.Tools.Predictor/Utilities/JsonUtilities.cs @@ -13,7 +13,6 @@ // ---------------------------------------------------------------------------------- using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities.Converters; -using System; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; @@ -29,16 +28,20 @@ internal static class JsonUtilities /// The default serialization options: /// 1. Use camel case in the naming. /// 2. Use string instead of number for enums. + /// 3. Use the string values (camel case) for enum. + /// 4. Use the string values for the type in the properties and keys. + /// 3. Skip the property if the value is null. /// public static readonly JsonSerializerOptions DefaultSerializerOptions = new JsonSerializerOptions() { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase), new VersionConverter(), new DictionaryTKeyVersionTValueConverter() }, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; /// diff --git a/tools/Az.Tools.Predictor/build.proj b/tools/Az.Tools.Predictor/build.proj index 227db7dd9272..94046b6f3e98 100644 --- a/tools/Az.Tools.Predictor/build.proj +++ b/tools/Az.Tools.Predictor/build.proj @@ -13,7 +13,7 @@ Az.Tools.Predictor $(RepoArtifacts)Tools $(ModuleName).sln - Net5.0 + Net6.0 $(RepoArtifacts)/TestResults/$(ModuleName)