public void OnCommandLineExecuted(PredictionClient client, string commandLine, bool success) { if (success && AzPredictor._azAccountCommands.Contains(commandLine.Trim().Split().FirstOrDefault())) { // The context only changes when the user executes the corresponding command successfully. _azContext?.UpdateContext(); } if (!_parsedCommandLineHistory.TryRemove(commandLine, out var parsedResult)) { // We should already parsed the last command in OnCommandLineAccepted which we don't need to do again now. // Just in case that wasn't correct or that's missing, we clear the _parsedCommandLineHistory and parse it now. // On possible reason it's missing is because we're still initializing in the task and don't handle // OnCommandLineAccepted. _parsedCommandLineHistory.Clear(); parsedResult = GetAstAndMaskedCommandLine(commandLine); } if (parsedResult.IsSupported && _surveyHelper?.ShouldPromptSurvey() == true) { _surveyHelper.PromptSurvey(); } _telemetryClient.OnHistory(new HistoryTelemetryData(client, parsedResult.MaskedCommandLine ?? AzPredictorConstants.CommandPlaceholder, success)); _commandLineExecutedCompletion?.SetResult(); }
/// <summary> /// Constructs a new instance of <see cref="AzPredictor"/> to use in PowerShell's prediction subsystem. /// </summary> public AzPredictor() { // To make import-module fast, we'll do all the initialization in a task. // Slow initialization may make opening a PowerShell window slow if "Import-Module" is added to the user's profile. Task.Run(() => { _settings = Settings.GetSettings(); var azContext = new AzContext() { IsInternal = (_settings.SetAsInternal == true) ? true : false, SurveyId = _settings.SurveyId?.ToString(CultureInfo.InvariantCulture) ?? string.Empty, }; RegisterDisposableObject(azContext); _azContext = azContext; _azContext.UpdateContext(); _telemetryClient = new AzPredictorTelemetryClient(_azContext); _service = new AzPredictorService(_settings.ServiceUri, _telemetryClient, _azContext); _isInitialized = true; }); }
/// <summary> /// Constructs a new instance of <see cref="AzPredictor"/> to use in PowerShell's prediction subsystem. /// </summary> public AzPredictor() { _powerShellRuntime = new PowerShellRuntime(); _surveyHelper = new AzPredictorSurveyHelper(_powerShellRuntime); // To make import-module fast, we'll do all the initialization in a task. // Slow initialization may make opening a PowerShell window slow if "Import-Module" is added to the user's profile. Task.Run(() => { _settings = Settings.GetSettings(); _azContext = new AzContext(_powerShellRuntime) { IsInternal = (_settings.SetAsInternal == true) ? true : false, }; _azContext.UpdateContext(); // This will run the script in the right context. var _ = _azContext.PowerShellVersion; _telemetryClient = new AzPredictorTelemetryClient(_azContext); _service = new AzPredictorService(_settings.ServiceUri, _telemetryClient, _azContext); _isInitialized = true; }); }
/// <inhericdoc /> public void StartEarlyProcessing(string clientId, IReadOnlyList <string> history) { // The context only changes when the user executes the corresponding command. _azContext?.UpdateContext(); if (history.Count > 0) { // We try to find the commands to request predictions for. // We should only have "start_of_snippet" when there are no enough Az commands for prediction. // We then ignore that when there are new "start_of_snippet". // This is the scenario. // 1. New-AzResourceGroup -Name **** // 2. $resourceName="Test" // 3. $resourceLocation="westus2" // 4. New-AzVM -Name $resourceName -Location $resourceLocation // // We'll replace 2 and 3 with "start_of_snippet" but if we request prediction using 2 and 3, that'll reset the // workflow. We want to predict only by Az commands. That's to use commands 1 and 4. bool isLastTwoCommandsChanged = false; if (_lastTwoMaskedCommands.Count == 0) { // This is the first time we populate our record. Push the second to last command in history to the // queue. If there is only one command in history, push the command placeholder. if (history.Count() > 1) { string secondToLastLine = history.TakeLast(AzPredictorConstants.CommandHistoryCountToProcess).First(); var secondToLastCommand = GetAstAndMaskedCommandLine(secondToLastLine); _lastTwoMaskedCommands.Enqueue(secondToLastCommand.IsSupported ? secondToLastCommand.MaskedValue : AzPredictorConstants.CommandPlaceholder); if (secondToLastCommand.IsSupported) { _service.RecordHistory(secondToLastCommand.Ast); } } else { _lastTwoMaskedCommands.Enqueue(AzPredictorConstants.CommandPlaceholder); // We only extract parameter values from the command line in _service.RecordHistory. // So we don't need to do that for a placeholder. } isLastTwoCommandsChanged = true; } string lastLine = history.Last(); var lastCommand = GetAstAndMaskedCommandLine(lastLine); bool isLastCommandSupported = lastCommand.IsSupported; if (isLastCommandSupported) { if (_lastTwoMaskedCommands.Count == 2) { // There are already two commands, dequeue the oldest one. _lastTwoMaskedCommands.Dequeue(); } _lastTwoMaskedCommands.Enqueue(lastCommand.Item2); isLastTwoCommandsChanged = true; _service.RecordHistory(lastCommand.Item1); } else if (_lastTwoMaskedCommands.Count == 1) { isLastTwoCommandsChanged = true; var existingInQueue = _lastTwoMaskedCommands.Dequeue(); _lastTwoMaskedCommands.Enqueue(AzPredictorConstants.CommandPlaceholder); _lastTwoMaskedCommands.Enqueue(existingInQueue); } _telemetryClient.OnHistory(new HistoryTelemetryData(clientId, lastCommand.MaskedValue ?? AzPredictorConstants.CommandPlaceholder)); if (isLastTwoCommandsChanged) { // When it's called multiple times, we only need to keep the one for the latest command. _predictionRequestCancellationSource?.Cancel(); _predictionRequestCancellationSource = new CancellationTokenSource(); // Need to create a new object to hold the string. They're used in a seperate thread the the contents in // _lastTwoMaskedCommands may change when the method is called again. var lastTwoMaskedCommands = new List <string>(_lastTwoMaskedCommands); Exception exception = null; var hasSentHttpRequest = false; // We don't need to block on the task. It sends the HTTP request and update prediction list. That can run at the background. Task.Run(async() => { try { await _service.RequestPredictionsAsync(lastTwoMaskedCommands, _predictionRequestCancellationSource.Token); } catch (ServiceRequestException e) { hasSentHttpRequest = e.IsRequestSent; exception = e.InnerException; } catch (Exception e) { exception = e; } finally { _telemetryClient.OnRequestPrediction(new RequestPredictionTelemetryData(clientId, lastTwoMaskedCommands, hasSentHttpRequest, (exception is OperationCanceledException ? null : exception))); } }, _predictionRequestCancellationSource.Token); } } (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; var commandName = commandAst?.CommandElements?.FirstOrDefault().ToString(); bool isSupported = _service.IsSupportedCommand(commandName); string maskedCommandLine = CommandLineUtilities.MaskCommandLine(commandAst); return(commandAst, maskedCommandLine, isSupported); } }
/// <inhericdoc /> public void StartEarlyProcessing(IReadOnlyList <string> history) { // The context only changes when the user executes the corresponding command. _azContext?.UpdateContext(); if (history.Count > 0) { // We try to find the commands to request predictions for. // We should only have "start_of_snippet" when there are no enough Az commands for prediction. // We then ignore that when there are new "start_of_snippet". // This is the scenario. // 1. New-AzResourceGroup -Name **** // 2. $resourceName="Test" // 3. $resourceLocation="westus2" // 4. New-AzVM -Name $resourceName -Location $resourceLocation // // We'll replace 2 and 3 with "start_of_snippet" but if we request prediction using 2 and 3, that'll reset the // workflow. We want to predict only by Az commands. That's to use commands 1 and 4. bool isLastTwoCommandsChanged = false; if (_lastTwoMaskedCommands.Count == 0) { // This is the first time we populate our record. Push the second to last command in history to the // queue. If there is only one command in history, push the command placeholder. if (history.Count() > 1) { string secondToLastLine = history.TakeLast(AzPredictorConstants.CommandHistoryCountToProcess).First(); var secondToLastCommand = GetAstAndMaskedCommandLine(secondToLastLine); _lastTwoMaskedCommands.Enqueue(secondToLastCommand.Item2); if (!string.Equals(AzPredictorConstants.CommandPlaceholder, secondToLastCommand.Item2, StringComparison.Ordinal)) { _service.RecordHistory(secondToLastCommand.Item1); } } else { _lastTwoMaskedCommands.Enqueue(AzPredictorConstants.CommandPlaceholder); // We only extract parameter values from the command line in _service.RecordHistory. // So we don't need to do that for a placeholder. } isLastTwoCommandsChanged = true; } string lastLine = history.Last(); var lastCommand = GetAstAndMaskedCommandLine(lastLine); bool isLastCommandSupported = !string.Equals(AzPredictorConstants.CommandPlaceholder, lastCommand.Item2, StringComparison.Ordinal); if (isLastCommandSupported) { if (_lastTwoMaskedCommands.Count == 2) { // There are already two commands, dequeue the oldest one. _lastTwoMaskedCommands.Dequeue(); } _lastTwoMaskedCommands.Enqueue(lastCommand.Item2); isLastTwoCommandsChanged = true; _service.RecordHistory(lastCommand.Item1); } else if (_lastTwoMaskedCommands.Count == 1) { isLastTwoCommandsChanged = true; var existingInQueue = _lastTwoMaskedCommands.Dequeue(); _lastTwoMaskedCommands.Enqueue(AzPredictorConstants.CommandPlaceholder); _lastTwoMaskedCommands.Enqueue(existingInQueue); } _telemetryClient.OnHistory(new HistoryTelemetryData(lastCommand.Item2)); if (isLastTwoCommandsChanged) { _service.RequestPredictions(_lastTwoMaskedCommands); } } ValueTuple <CommandAst, string> 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(); if (_service.IsSupportedCommand(commandName)) { maskedCommandLine = CommandLineUtilities.MaskCommandLine(commandAst); } return(ValueTuple.Create(commandAst, maskedCommandLine)); } }
/// <inhericdoc /> public void StartEarlyProcessing(IReadOnlyList <string> history) { // The context only changes when the user executes the corresponding command. _azContext?.UpdateContext(); if (history.Count > 0) { if (_lastTwoMaskedCommands.Any()) { _lastTwoMaskedCommands.Dequeue(); } else { // This is the first time we populate our record. Push the second to last command in history to the // queue. If there is only one command in history, push the command placeholder. if (history.Count() > 1) { string secondToLastLine = history.TakeLast(AzPredictorConstants.CommandHistoryCountToProcess).First(); var secondToLastCommand = GetAstAndMaskedCommandLine(secondToLastLine); _lastTwoMaskedCommands.Enqueue(secondToLastCommand.Item2); _service.RecordHistory(secondToLastCommand.Item1); } else { _lastTwoMaskedCommands.Enqueue(AzPredictorConstants.CommandPlaceholder); // We only extract parameter values from the command line in _service.RecordHistory. // So we don't need to do that for a placeholder. } } string lastLine = history.Last(); var lastCommand = GetAstAndMaskedCommandLine(lastLine); _lastTwoMaskedCommands.Enqueue(lastCommand.Item2); if ((lastCommand.Item2 != null) && !string.Equals(AzPredictorConstants.CommandPlaceholder, lastCommand.Item2, StringComparison.Ordinal)) { _service.RecordHistory(lastCommand.Item1); } _telemetryClient.OnHistory(new HistoryTelemetryData(lastCommand.Item2)); _service.RequestPredictions(_lastTwoMaskedCommands); } ValueTuple <CommandAst, string> 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 = null; var commandName = commandAst?.CommandElements?.FirstOrDefault().ToString(); if (_service.IsSupportedCommand(commandName)) { maskedCommandLine = CommandLineUtilities.MaskCommandLine(commandAst); } else { maskedCommandLine = AzPredictorConstants.CommandPlaceholder; } return(ValueTuple.Create(commandAst, maskedCommandLine)); } }