/// <summary>
        /// Sends the telemetry with the suggestion returned to the user.
        /// </summary>
        private void SendTelemetry(GetSuggestionTelemetryData telemetryData)
        {
            var suggestions      = telemetryData.Suggestion?.PredictiveSuggestions;
            var suggestionSource = telemetryData.Suggestion?.SuggestionSources;
            var sourceTexts      = telemetryData.Suggestion?.SourceTexts;
            var maskedUserInput  = CommandLineUtilities.MaskCommandLine(telemetryData.UserInput?.FindAll((ast) => ast is CommandAst, true).LastOrDefault() as CommandAst);

            if ((suggestions != null) && (sourceTexts != null))
            {
                for (int i = 0; i < suggestions.Count; ++i)
                {
                    _userAcceptedAndSuggestion[suggestions[i].SuggestionText] = sourceTexts[i];
                }
            }

            var properties = CreateProperties(telemetryData);

            properties.Add("ClientId", telemetryData.ClientId);
            properties.Add("SuggestionSessionId", telemetryData != null ? telemetryData.SuggestionSessionId.ToString(CultureInfo.InvariantCulture) : string.Empty);
            properties.Add("UserInput", maskedUserInput ?? string.Empty);
            properties.Add("Suggestion", sourceTexts != null ? JsonSerializer.Serialize(sourceTexts.Zip(suggestionSource).Select((s) => Tuple.Create(s.First, s.Second)), JsonUtilities.TelemetrySerializerOptions) : string.Empty);
            properties.Add("IsCancelled", telemetryData.IsCancellationRequested.ToString(CultureInfo.InvariantCulture));
            properties.Add("Exception", AzPredictorTelemetryClient.FormatException(telemetryData.Exception));

            SendTelemetry($"{AzPredictorTelemetryClient.TelemetryEventPrefix}/GetSuggestion", properties);
        }
Example #2
0
        public void VerifyMaskCommandLine(string expected, string input)
        {
            var asts          = Parser.ParseInput(input, out _, out _);
            var allNestedAsts = asts?.FindAll((ast) => ast is CommandAst, true);
            var commandAst    = allNestedAsts?.LastOrDefault() as CommandAst;

            string maskedCommandLine = CommandLineUtilities.MaskCommandLine(commandAst);

            Assert.Equal(expected, maskedCommandLine);
        }
Example #3
0
        private ParsedCommandLineHistory GetAstAndMaskedCommandLine(string commandLine)
        {
            var commandAst = CommandLineUtilities.GetCommandAst(commandLine);

            var    commandName       = commandAst?.CommandElements?.FirstOrDefault().ToString();
            bool   isSupported       = _service.IsSupportedCommand(commandName);
            string maskedCommandLine = CommandLineUtilities.MaskCommandLine(commandAst);

            return(new ParsedCommandLineHistory
            {
                Ast = commandAst,
                MaskedCommandLine = maskedCommandLine,
                IsSupported = isSupported
            });
        }
Example #4
0
        private ParsedCommandLineHistory 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(new ParsedCommandLineHistory
            {
                Ast = commandAst,
                MaskedCommandLine = maskedCommandLine,
                IsSupported = isSupported
            });
        }
Example #5
0
        /// <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);
            }
        }
Example #6
0
        /// <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));
            }
        }