public async Task <Activity> ForwardToSkillAsync(SkillManifest skillManifest, IServiceClientCredentials serviceClientCredentials, ITurnContext turnContext, Activity activity, Action <Activity> tokenRequestHandler = null, Action <Activity> fallbackHandler = null)
        {
            if (_streamingTransportClient == null)
            {
                // establish websocket connection
                _streamingTransportClient = new WebSocketClient(
                    EnsureWebSocketUrl(skillManifest.Endpoint.ToString()),
                    new SkillCallingRequestHandler(
                        turnContext,
                        _botTelemetryClient,
                        GetTokenCallback(turnContext, tokenRequestHandler),
                        GetFallbackCallback(turnContext, fallbackHandler),
                        GetHandoffActivityCallback()));
            }

            // acquire AAD token
            MicrosoftAppCredentials.TrustServiceUrl(skillManifest.Endpoint.AbsoluteUri);
            var token = await serviceClientCredentials.GetTokenAsync();

            // put AAD token in the header
            var headers = new Dictionary <string, string>();

            headers.Add("Authorization", $"Bearer {token}");

            await _streamingTransportClient.ConnectAsync(headers);

            // set recipient to the skill
            var recipientId = activity.Recipient.Id;

            activity.Recipient.Id = skillManifest.MSAappId;

            // Serialize the activity and POST to the Skill endpoint
            var body    = new StringContent(JsonConvert.SerializeObject(activity, SerializationSettings.BotSchemaSerializationSettings), Encoding.UTF8, SerializationSettings.ApplicationJson);
            var request = StreamingRequest.CreatePost(string.Empty, body);

            // set back recipient id to make things consistent
            activity.Recipient.Id = recipientId;

            var stopWatch = new System.Diagnostics.Stopwatch();

            stopWatch.Start();
            await _streamingTransportClient.SendAsync(request);

            stopWatch.Stop();

            _botTelemetryClient.TrackEvent(
                "SkillWebSocketTurnLatency",
                new Dictionary <string, string>
            {
                { "SkillName", skillManifest.Name },
                { "SkillEndpoint", skillManifest.Endpoint.ToString() },
            },
                new Dictionary <string, double>
            {
                { "Latency", stopWatch.ElapsedMilliseconds },
            });

            return(_handoffActivity);
        }
示例#2
0
        public static void TrackQnAMakerEvent(this IBotTelemetryClient telemetryClient, string message, QueryResult[] qnaResults)
        {
            var topAnswer = qnaResults.FirstOrDefault();

            telemetryClient.TrackEvent("QnAMakerResult", new Dictionary <string, string> {
                ["MessageContent"] = message,
                ["TopAnswer"]      = topAnswer?.Answer,
                ["TopAnswerScore"] = topAnswer?.Score.ToString(),
                ["TopAnswerId"]    = topAnswer?.Metadata.SingleOrDefault(x => x.Name == "id")?.Value
            });
        }
示例#3
0
        //gets response from documentation QnA Maker
        private async Task ProcessDocumentationQnAAsync(ITurnContext <IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            Logger.LogInformation("ProcessSampleQnAAsync");

            var results = await BotServices.DocumentationQnA.GetAnswersAsync(turnContext);

            if (results.Any())
            {
                await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
            }
            else
            {
                var answerNotFoundProperties = new Dictionary <string, string>();
                answerNotFoundProperties.Add(
                    "question",
                    turnContext.Activity.Text);
                TelemetryClient.TrackEvent("QnANotFound", answerNotFoundProperties);
                await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
            }
        }
示例#4
0
        private void Server_Disconnected(object sender, DisconnectedEventArgs e)
        {
            if (_stopWatch.IsRunning)
            {
                _stopWatch.Stop();

                _botTelemetryClient.TrackEvent("SkillWebSocketOpenCloseLatency", null, new Dictionary <string, double>
                {
                    { "Latency", _stopWatch.ElapsedMilliseconds },
                });
            }
        }
示例#5
0
        private void LogFeedback(FeedbackRecord record)
        {
            var properties = new Dictionary <string, string>()
            {
                { nameof(FeedbackRecord.Tag), record.Tag },
                { nameof(FeedbackRecord.Feedback), record.Feedback },
                { nameof(FeedbackRecord.Comment), record.Comment },
                { nameof(FeedbackRecord.Request.Text), record.Request?.Text },
                { nameof(FeedbackRecord.Request.Id), record.Request?.Conversation.Id },
                { nameof(FeedbackRecord.Request.ChannelId), record.Request?.ChannelId },
            };

            _telemetryClient.TrackEvent(_traceName, properties);
        }
        /// <summary>
        /// Sends feedback to be logged.
        /// </summary>
        /// <param name="record">FeedbackRecord object to be logged.</param>
        /// <param name="telemetryClient">IBotTelemetryClient object used to log feedback record<param>
        public static void LogFeedback(FeedbackRecord record, IBotTelemetryClient telemetryClient)
        {
            var properties = new Dictionary <string, string>()
            {
                { nameof(FeedbackRecord.Tag), record.Tag },
                { nameof(FeedbackRecord.Feedback), record.Feedback },
                { nameof(FeedbackRecord.Comment), record.Comment },
                { nameof(FeedbackRecord.Request.Text), record.Request?.Text },
                { nameof(FeedbackRecord.Request.Id), record.Request?.Conversation.Id },
                { nameof(FeedbackRecord.Request.ChannelId), record.Request?.ChannelId },
            };

            telemetryClient.TrackEvent("Feedback", properties);
        }
        /// <summary>
        /// Sends activities to the conversation.
        /// </summary>
        /// <param name="turnContext">The context object for the turn.</param>
        /// <param name="activities">The activities to send.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>A task that represents the work queued to execute.</returns>
        /// <remarks>If the activities are successfully sent, the task result contains
        /// an array of <see cref="ResourceResponse"/> objects containing the IDs that
        /// the receiving channel assigned to the activities.</remarks>
        /// <seealso cref="ITurnContext.OnSendActivities(SendActivitiesHandler)"/>
        public override async Task <ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken)
        {
            if (turnContext == null)
            {
                throw new ArgumentNullException(nameof(turnContext));
            }

            if (activities == null)
            {
                throw new ArgumentNullException(nameof(activities));
            }

            if (activities.Length == 0)
            {
                throw new ArgumentException("Expecting one or more activities, but the array was empty.", nameof(activities));
            }

            var responses = new ResourceResponse[activities.Length];

            /*
             * NOTE: we're using for here (vs. foreach) because we want to simultaneously index into the
             * activities array to get the activity to process as well as use that index to assign
             * the response to the responses array and this is the most cost effective way to do that.
             */
            for (var index = 0; index < activities.Length; index++)
            {
                var activity = activities[index];
                if (string.IsNullOrWhiteSpace(activity.Id))
                {
                    activity.Id = Guid.NewGuid().ToString("n");
                }

                var response = default(ResourceResponse);

                if (activity.Type == ActivityTypesEx.Delay)
                {
                    // The Activity Schema doesn't have a delay type build in, so it's simulated
                    // here in the Bot. This matches the behavior in the Node connector.
                    var delayMs = (int)activity.Value;
                    await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);

                    // No need to create a response. One will be created below.
                }

                // set SemanticAction property of the activity properly
                EnsureActivitySemanticAction(turnContext, activity);

                if (activity.Type != ActivityTypes.Trace ||
                    (activity.Type == ActivityTypes.Trace && activity.ChannelId == "emulator"))
                {
                    var requestPath = $"/activities/{activity.Id}";
                    var request     = StreamingRequest.CreatePost(requestPath);

                    // set callerId to empty so it's not sent over the wire
                    activity.CallerId = null;

                    request.SetBody(activity);

                    _botTelemetryClient.TrackTrace($"Sending activity. ReplyToId: {activity.ReplyToId}", Severity.Information, null);

                    var stopWatch = new Diagnostics.Stopwatch();

                    try
                    {
                        stopWatch.Start();
                        response = await SendRequestAsync <ResourceResponse>(request).ConfigureAwait(false);

                        stopWatch.Stop();
                    }
                    catch (Exception ex)
                    {
                        throw new SkillWebSocketCallbackException($"Callback failed. Verb: POST, Path: {requestPath}", ex);
                    }

                    _botTelemetryClient.TrackEvent("SkillWebSocketSendActivityLatency", null, new Dictionary <string, double>
                    {
                        { "Latency", stopWatch.ElapsedMilliseconds },
                    });
                }

                // If No response is set, then defult to a "simple" response. This can't really be done
                // above, as there are cases where the ReplyTo/SendTo methods will also return null
                // (See below) so the check has to happen here.

                // Note: In addition to the Invoke / Delay / Activity cases, this code also applies
                // with Skype and Teams with regards to typing events.  When sending a typing event in
                // these _channels they do not return a RequestResponse which causes the bot to blow up.
                // https://github.com/Microsoft/botbuilder-dotnet/issues/460
                // bug report : https://github.com/Microsoft/botbuilder-dotnet/issues/465
                if (response == null)
                {
                    response = new ResourceResponse(activity.Id ?? string.Empty);
                }

                responses[index] = response;
            }

            return(responses);
        }
示例#8
0
 public static void TrackEventEx(this IBotTelemetryClient telemetryClient, string eventName, Activity activity, string dialogId = null, IDictionary <string, string> properties = null, IDictionary <string, double> metrics = null)
 {
     telemetryClient.TrackEvent(eventName, GetFinalProperties(activity, dialogId, properties), metrics);
 }
        /// <summary>
        /// Records incoming and outgoing activities to the Application Insights store.
        /// </summary>
        /// <param name="context">The context object for this turn.</param>
        /// <param name="nextTurn">The delegate to call to continue the bot middleware pipeline.</param>
        /// <param name="cancellationToken">A cancellation token that can be used by other objects
        /// or threads to receive notice of cancellation.</param>
        /// <returns>A task that represents the work queued to execute.</returns>
        /// <seealso cref="ITurnContext"/>
        /// <seealso cref="Bot.Schema.IActivity"/>
        public async Task OnTurnAsync(ITurnContext context, NextDelegate nextTurn, CancellationToken cancellationToken)
        {
            BotAssert.ContextNotNull(context);

            context.TurnState.Add(TelemetryLoggerMiddleware.AppInsightsServiceKey, _telemetryClient);

            // log incoming activity at beginning of turn
            if (context.Activity != null)
            {
                var activity = context.Activity;

                // Log the Application Insights Bot Message Received
                _telemetryClient.TrackEvent(BotMsgReceiveEvent, this.FillReceiveEventProperties(activity));
            }

            // hook up onSend pipeline
            context.OnSendActivities(async(ctx, activities, nextSend) =>
            {
                // run full pipeline
                var responses = await nextSend().ConfigureAwait(false);

                foreach (var activity in activities)
                {
                    _telemetryClient.TrackEvent(BotMsgSendEvent, this.FillSendEventProperties(activity));
                }

                return(responses);
            });

            // hook up update activity pipeline
            context.OnUpdateActivity(async(ctx, activity, nextUpdate) =>
            {
                // run full pipeline
                var response = await nextUpdate().ConfigureAwait(false);

                _telemetryClient.TrackEvent(BotMsgUpdateEvent, this.FillUpdateEventProperties(activity));

                return(response);
            });

            // hook up delete activity pipeline
            context.OnDeleteActivity(async(ctx, reference, nextDelete) =>
            {
                // run full pipeline
                await nextDelete().ConfigureAwait(false);

                var deleteActivity = new Activity
                {
                    Type = ActivityTypes.MessageDelete,
                    Id   = reference.ActivityId,
                }
                .ApplyConversationReference(reference, isIncoming: false)
                .AsMessageDeleteActivity();

                _telemetryClient.TrackEvent(BotMsgDeleteEvent, this.FillDeleteEventProperties(deleteActivity));
            });

            if (nextTurn != null)
            {
                await nextTurn(cancellationToken).ConfigureAwait(false);
            }
        }
示例#10
0
        public async override Task <Response> ProcessRequestAsync(ReceiveRequest request, object context = null, ILogger <RequestHandler> logger = null)
        {
            if (Bot == null)
            {
                throw new ArgumentNullException(nameof(Bot));
            }

            if (SkillWebSocketBotAdapter == null)
            {
                throw new ArgumentNullException(nameof(SkillWebSocketBotAdapter));
            }

            var response = new Response();

            var body = await request.ReadBodyAsString().ConfigureAwait(false);

            if (string.IsNullOrEmpty(body) || request.Streams?.Count == 0)
            {
                response.StatusCode = (int)HttpStatusCode.BadRequest;
                response.SetBody("Empty request body.");
                return(response);
            }

            if (request.Streams.Where(x => x.Type != "application/json; charset=utf-8").Any())
            {
                response.StatusCode = (int)HttpStatusCode.NotAcceptable;
                return(response);
            }

            Activity activity = null;

            try
            {
                activity = JsonConvert.DeserializeObject <Activity>(body, Serialization.Settings);
            }
            catch (Exception ex)
            {
                _botTelemetryClient.TrackException(ex);

                response.StatusCode = (int)HttpStatusCode.BadRequest;
                response.SetBody("Request body is not an Activity instance.");
                return(response);
            }

            try
            {
                var cancellationTokenSource = new CancellationTokenSource();
                _stopWatch.Start();
                var invokeResponse = await this.SkillWebSocketBotAdapter.ProcessActivityAsync(activity, new BotCallbackHandler(this.Bot.OnTurnAsync), cancellationTokenSource.Token).ConfigureAwait(false);

                _stopWatch.Stop();

                _botTelemetryClient.TrackEvent("SkillWebSocketProcessRequestLatency", null, new Dictionary <string, double>
                {
                    { "Latency", _stopWatch.ElapsedMilliseconds },
                });

                // trigger cancel token after activity is handled. this will stop the typing indicator
                cancellationTokenSource.Cancel();

                if (invokeResponse == null)
                {
                    response.StatusCode = (int)HttpStatusCode.OK;
                }
                else
                {
                    response.StatusCode = invokeResponse.Status;
                    if (invokeResponse.Body != null)
                    {
                        response.SetBody(invokeResponse.Body);
                    }
                }
            }
            catch (SkillWebSocketCallbackException ex)
            {
                _botTelemetryClient.TrackException(ex);

                response.StatusCode = (int)HttpStatusCode.InternalServerError;
                response.SetBody(ex.Message);

                return(response);
            }
            catch (Exception ex)
            {
                _botTelemetryClient.TrackException(ex);

                response.StatusCode = (int)HttpStatusCode.InternalServerError;
            }

            return(response);
        }