public async Task Re_QueueSendNotificationWithDelayTest()
        {
            // Arrange
            // SendNotificationThrottled
            var    sendFunctionInstance = this.GetSendFunction();
            string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"Member\"}}}";
            SendQueueMessageContent messageContent = JsonConvert.DeserializeObject <SendQueueMessageContent>(data);

            this.notificationService
            .Setup(x => x.IsPendingNotification(It.IsAny <SendQueueMessageContent>()))
            .ReturnsAsync(true);
            this.notificationService
            .Setup(x => x.UpdateSentNotification(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <string>(), It.IsAny <string>()))
            .Returns(Task.CompletedTask);

            // mocking throttled.
            this.notificationService.Setup(x => x.IsSendNotificationThrottled()).ReturnsAsync(true);
            this.sendQueue.Setup(x => x.SendDelayedAsync(It.IsAny <SendQueueMessageContent>(), It.IsAny <double>())).Returns(Task.CompletedTask);

            // Act
            Func <Task> task = async() => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext());

            // Assert
            await task.Should().NotThrowAsync <NullReferenceException>();

            this.sendQueue.Verify(x => x.SendDelayedAsync(It.IsAny <SendQueueMessageContent>(), It.IsAny <double>()), Times.Once);
        }
コード例 #2
0
        /// <inheritdoc/>
        public async Task <bool> IsPendingNotification(SendQueueMessageContent message)
        {
            var recipient = message?.RecipientData;

            if (string.IsNullOrWhiteSpace(recipient?.RecipientId))
            {
                throw new InvalidOperationException("Recipient id is not set.");
            }

            // Check notification status for the recipient.
            var notification = await this.sentNotificationDataRepository.GetAsync(
                partitionKey : message.NotificationId,
                rowKey : message.RecipientData.RecipientId);

            // To avoid sending duplicate messages, we check if the Status code is either of the following:
            // 1. InitializationStatusCode: this means the notification has not been attempted to be sent to this recipient.
            // 2. FaultedAndRetryingStatusCode: this means the Azure Function previously attempted to send the notification
            //    to this recipient but failed and should be retried.
            if (notification?.StatusCode == SentNotificationDataEntity.InitializationStatusCode ||
                notification?.StatusCode == SentNotificationDataEntity.FaultedAndRetryingStatusCode)
            {
                return(true);
            }

            return(false);
        }
コード例 #3
0
        private async Task <IMessageActivity> GetMessageActivity(SendQueueMessageContent message)
        {
            var notification = await this.notificationRepo.GetAsync(
                NotificationDataTableNames.SendingNotificationsPartition,
                message.NotificationId);

            dynamic counterJson = JObject.Parse(notification.Content);

            counterJson.actions[0].Replace(
                JObject.FromObject(
                    new
            {
                type  = counterJson.actions[0].type,
                url   = counterJson.actions[0].url + "&notificationid=" + message.NotificationId + "&recipientid=" + message.RecipientData.RecipientId,
                title = counterJson.actions[0].title,
            }));

            var adaptiveCardAttachment = new Attachment()
            {
                ContentType = AdaptiveCardContentType,
                Content     = counterJson,
            };

            return(MessageFactory.Attachment(adaptiveCardAttachment));
        }
        /// <summary>
        /// Send a preview of a draft notification.
        /// </summary>
        /// <param name="draftNotificationEntity">Draft notification entity.</param>
        /// <param name="teamDataEntity">The team data entity.</param>
        /// <param name="teamsChannelId">The Teams channel id.</param>
        /// <returns>It returns HttpStatusCode.OK, if this method triggers the bot service to send the adaptive card successfully.
        /// It returns HttpStatusCode.TooManyRequests, if the bot service throttled the request to send the adaptive card.</returns>
        public async Task <HttpStatusCode> SendPreview(NotificationDataEntity draftNotificationEntity, TeamDataEntity teamDataEntity, string teamsChannelId)
        {
            if (draftNotificationEntity == null)
            {
                throw new ArgumentException("Null draft notification entity.");
            }

            if (teamDataEntity == null)
            {
                throw new ArgumentException("Null team data entity.");
            }

            if (string.IsNullOrWhiteSpace(teamsChannelId))
            {
                throw new ArgumentException("Null channel id.");
            }

            // Create bot conversation reference.
            var conversationReference = this.PrepareConversationReferenceAsync(teamDataEntity, teamsChannelId);

            // Ensure the bot service URL is trusted.
            if (!MicrosoftAppCredentials.IsTrustedServiceUrl(conversationReference.ServiceUrl))
            {
                MicrosoftAppCredentials.TrustServiceUrl(conversationReference.ServiceUrl);
            }

            // Trigger bot to send the adaptive card.
            try
            {
                var previewQueueMessageContent = new SendQueueMessageContent
                {
                    NotificationId = draftNotificationEntity.Id,
                    ActivtiyId     = null,
                    RecipientData  = null,
                    NotificationUpdatePreviewEntity = new NotificationUpdatePreviewEntity
                    {
                        ActionType             = "PreviewNotification",
                        NotificationDataEntity = null,
                        ConversationReferance  = conversationReference,
                        MessageActivity        = await this.GetPreviewMessageActivity(draftNotificationEntity),
                        ServiceUrl             = conversationReference.ServiceUrl,
                        AppID = this.botAppId,
                    },
                };
                await this.sendQueue.SendAsync(previewQueueMessageContent);

                return(HttpStatusCode.OK);
            }
            catch (ErrorResponseException e)
            {
                var errorResponse = (ErrorResponse)e.Body;
                if (errorResponse != null &&
                    errorResponse.Error.Code.Equals(DraftNotificationPreviewService.ThrottledErrorResponse, StringComparison.OrdinalIgnoreCase))
                {
                    return(HttpStatusCode.TooManyRequests);
                }

                throw;
            }
        }
        /// <summary>
        /// This method sets the globally accessible delay time indicating to all other functions that the system is currently in a
        /// throttled state and all messages need to be delayed and sends the queue message back to the queue with a delayed wait time.
        /// </summary>
        /// <param name="sendRetryDelayNumberOfSeconds">The number of seconds for the system and message to be delayed.</param>
        /// <param name="sendQueueMessageContent">The send queue message content to be sent back to the send queue for a delayed retry.</param>
        /// <param name="log">The logger.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public async Task DelaySendingNotificationAsync(
            double sendRetryDelayNumberOfSeconds,
            SendQueueMessageContent sendQueueMessageContent,
            ILogger log)
        {
            try
            {
                // Shorten this time by 15 seconds to ensure that when the delayed retry message is taken off of the queue
                // the Send Retry Delay Time will be earlier and will not block it
                var sendRetryDelayTime = DateTime.UtcNow + TimeSpan.FromSeconds(sendRetryDelayNumberOfSeconds - 15);

                var globalSendingNotificationDataEntity = new GlobalSendingNotificationDataEntity
                {
                    SendRetryDelayTime = sendRetryDelayTime,
                };

                var setGlobalSendingNotificationDataEntityTask = this.globalSendingNotificationDataRepository
                                                                 .SetGlobalSendingNotificationDataEntityAsync(globalSendingNotificationDataEntity);

                var sendDelayedRetryTask = this.sendQueue.SendDelayedAsync(sendQueueMessageContent, sendRetryDelayNumberOfSeconds);

                await Task.WhenAll(
                    setGlobalSendingNotificationDataEntityTask,
                    sendDelayedRetryTask);
            }
            catch (Exception e)
            {
                log.LogError(e, $"ERROR: {e.GetType()}: {e.Message}");
                throw;
            }
        }
コード例 #6
0
        public async Task <IActionResult> DeleteSentNotificationAsync(string id)
        {
            var notificationEntity = await this.notificationDataRepository.GetAsync(
                NotificationDataTableNames.SentNotificationsPartition,
                id);

            if (notificationEntity == null)
            {
                return(this.NotFound());
            }

            await this.notificationDataRepository.DeleteAsync(notificationEntity);

            var sendingNotificationEntity = await this.notificationDataRepository.GetAsync(
                NotificationDataTableNames.SendingNotificationsPartition,
                id);

            if (notificationEntity == null)
            {
                return(this.NotFound());
            }

            await this.notificationDataRepository.DeleteAsync(sendingNotificationEntity);

            var deleteQueueMessageContent = new SendQueueMessageContent
            {
                NotificationId = id,
                ActivtiyId     = null,
                RecipientData  = null,
                NotificationUpdatePreviewEntity = new NotificationUpdatePreviewEntity
                {
                    ActionType             = "DeleteNotification",
                    NotificationDataEntity = null,
                    ConversationReferance  = null,
                    MessageActivity        = null,
                    ServiceUrl             = null,
                    AppID = null,
                },
            };

            await this.sendQueue.SendAsync(deleteQueueMessageContent);

            var sentNotificationrepositoryEntities = await this.sentNotificationDataRepository.GetWithFilterAsync("PartitionKey eq'" + id + "'", id);

            foreach (var sentNotificationRepositoryEntity in sentNotificationrepositoryEntities)
            {
                var sentNotificationEntity = await this.sentNotificationDataRepository.GetAsync(
                    id,
                    sentNotificationRepositoryEntity.RowKey);

                if (notificationEntity == null)
                {
                    return(this.NotFound());
                }

                await this.sentNotificationDataRepository.DeleteAsync(sentNotificationEntity);
            }
            return(this.Ok());
        }
コード例 #7
0
        /// <summary>
        /// Get service url.
        /// </summary>
        /// <param name="message">Send Queue message.</param>
        /// <returns>Service url.</returns>
        public static string GetServiceUrl(this SendQueueMessageContent message)
        {
            var recipient = message.RecipientData;

            return(recipient.RecipientType switch
            {
                RecipientDataType.User => recipient.UserData.ServiceUrl,
                RecipientDataType.Team => recipient.TeamData.ServiceUrl,
                _ => throw new ArgumentException("Invalid recipient type"),
            });
        public async Task SendFunc_NullUserType_ShouldThrowArgumentNullException()
        {
            // Arrange
            var    sendFunctionInstance = this.GetSendFunction();
            string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"\"}}}";
            SendQueueMessageContent messageContent = JsonConvert.DeserializeObject <SendQueueMessageContent>(data);

            // Act
            Func <Task> task = async() => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext());

            // Assert
            await task.Should().ThrowAsync <ArgumentNullException>();
        }
コード例 #9
0
        private async Task <IMessageActivity> GetMessageActivity(SendQueueMessageContent message)
        {
            var notification = await this.notificationRepo.GetAsync(
                NotificationDataTableNames.SendingNotificationsPartition,
                message.NotificationId);

            var adaptiveCardAttachment = new Attachment()
            {
                ContentType = AdaptiveCardContentType,
                Content     = JsonConvert.DeserializeObject(notification.Content),
            };

            return(MessageFactory.Attachment(adaptiveCardAttachment));
        }
        public async Task SendFunc_GuestUser_ShouldNotSendMessage()
        {
            // Arrange
            var    sendFunctionInstance = this.GetSendFunction();
            string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"Guest\"}}}";
            SendQueueMessageContent messageContent = JsonConvert.DeserializeObject <SendQueueMessageContent>(data);

            // Act
            Func <Task> task = async() => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext());

            // Assert
            await task.Should().NotThrowAsync();

            this.notificationService.Verify(x => x.UpdateSentNotification(It.Is <string>(x => x.Equals("notificationId")), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <string>(), It.IsAny <string>()), Times.Once);
        }
コード例 #11
0
        public async Task NotificationPendingRecipientIdNotFoundTest()
        {
            // Arrange
            var notificationService = GetNotificationService();
            SendQueueMessageContent sendQueueMessageContent = new SendQueueMessageContent()
            {
                RecipientData = new RecipientData()
            };

            // Act
            Func <Task> task = async() => await notificationService.IsPendingNotification(sendQueueMessageContent);

            // Assert
            await task.Should().ThrowAsync <InvalidOperationException>().WithMessage("Recipient id is not set.");
        }
        /// <inheritdoc/>
        public async Task <bool> IsNotificationCanceled(SendQueueMessageContent message)
        {
            // Check notification status.
            var notification = await this.notificationDataRepository.GetAsync(
                partitionKey : NotificationDataTableNames.SentNotificationsPartition,
                rowKey : message.NotificationId);

            // Check if the Status is Canceled.
            if (notification.Status.Equals(nameof(NotificationStatus.Canceled)) ||
                notification.Status.Equals(nameof(NotificationStatus.Canceling)))
            {
                return(true);
            }

            return(false);
        }
コード例 #13
0
        private async Task <IMessageActivity> GetMessageActivity(SendQueueMessageContent message)
        {
            var notification = await this.notificationRepo.GetAsync(
                NotificationDataTableNames.SendingNotificationsPartition,
                message.NotificationId);

            // replacing id and key for read tracking purposes
            notification.Content = notification.Content.Replace("[ID]", message.NotificationId);
            notification.Content = notification.Content.Replace("[KEY]", message.RecipientData.RecipientId);

            var adaptiveCardAttachment = new Attachment()
            {
                ContentType = AdaptiveCardContentType,
                Content     = JsonConvert.DeserializeObject(notification.Content),
            };

            return(MessageFactory.Attachment(adaptiveCardAttachment));
        }
        /// <summary>
        /// Process send notification response.
        /// </summary>
        /// <param name="messageContent">Message content.</param>
        /// <param name="sendMessageResponse">Send notification response.</param>
        /// <param name="log">Logger.</param>
        private async Task ProcessResponseAsync(
            SendQueueMessageContent messageContent,
            SendMessageResponse sendMessageResponse,
            ILogger log)
        {
            var statusReason = string.Empty;

            if (sendMessageResponse.ResultType == SendMessageResult.Succeeded)
            {
                log.LogInformation($"Successfully sent the message." +
                                   $"\nRecipient Id: {messageContent.RecipientData.RecipientId}");
            }
            else
            {
                log.LogError($"Failed to send message." +
                             $"\nRecipient Id: {messageContent.RecipientData.RecipientId}" +
                             $"\nResult: {sendMessageResponse.ResultType}." +
                             $"\nErrorMessage: {sendMessageResponse.ErrorMessage}.");

                statusReason = this.localizer.GetString("Failed");
            }

            await this.notificationService.UpdateSentNotification(
                notificationId : messageContent.NotificationId,
                recipientId : messageContent.RecipientData.RecipientId,
                totalNumberOfSendThrottles : sendMessageResponse.TotalNumberOfSendThrottles,
                statusCode : sendMessageResponse.StatusCode,
                allSendStatusCodes : sendMessageResponse.AllSendStatusCodes,
                errorMessage : statusReason,
                exception : sendMessageResponse.ErrorMessage);

            // Throttled
            if (sendMessageResponse.ResultType == SendMessageResult.Throttled)
            {
                // Set send function throttled.
                await this.notificationService.SetSendNotificationThrottled(this.sendRetryDelayNumberOfSeconds);

                // Requeue.
                await this.sendQueue.SendDelayedAsync(messageContent, this.sendRetryDelayNumberOfSeconds);

                return;
            }
        }
        public async Task SendNotificationResponseThrottledTest()
        {
            // Arrange
            var sendFunctionInstance = this.GetSendFunction();
            SendMessageResponse sendMessageResponse = new SendMessageResponse()
            {
                ResultType = SendMessageResult.Throttled,
            };
            string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"Member\"}}}";
            SendQueueMessageContent messageContent = JsonConvert.DeserializeObject <SendQueueMessageContent>(data);

            this.notificationService
            .Setup(x => x.IsPendingNotification(It.IsAny <SendQueueMessageContent>()))
            .ReturnsAsync(true);
            this.notificationService
            .Setup(x => x.UpdateSentNotification(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <string>(), It.IsAny <string>()))
            .Returns(Task.CompletedTask);

            this.notificationService
            .Setup(x => x.IsSendNotificationThrottled())
            .ReturnsAsync(false);
            this.sendQueue
            .Setup(x => x.SendDelayedAsync(It.IsAny <SendQueueMessageContent>(), It.IsAny <double>()))
            .Returns(Task.CompletedTask);

            var notificatioData = new SendingNotificationDataEntity()
            {
                Content = "{\"text\":\"Welcome\",\"displayText\":\"Hello\"}"
            };

            this.notificationRepo.Setup(x => x.GetAsync(It.IsAny <string>(), It.IsAny <string>())).ReturnsAsync(notificatioData);
            this.messageService.Setup(x => x.SendMessageAsync(It.IsAny <IMessageActivity>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), this.logger.Object)).ReturnsAsync(sendMessageResponse);
            this.notificationService.Setup(x => x.UpdateSentNotification(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <string>(), It.IsAny <string>())).Returns(Task.CompletedTask);

            // Act
            Func <Task> task = async() => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext());

            // Assert
            await task.Should().NotThrowAsync <NullReferenceException>();

            this.sendQueue.Verify(x => x.SendDelayedAsync(It.IsAny <SendQueueMessageContent>(), It.IsAny <double>()));
        }
コード例 #16
0
        /// <summary>
        /// The service used to fetch or generate the necessary parameters for sending the notification.
        /// </summary>
        /// <param name="messageContent">The message content of the send queue message.</param>
        /// <param name="log">The logger.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public async Task <GetSendNotificationParamsResponse> GetSendNotificationParamsAsync(
            SendQueueMessageContent messageContent,
            ILogger log)
        {
            // Fetch the current sending notification. This is where the data about what is being sent is stored.
            var activeNotificationEntity = await this.sendingNotificationDataRepository.GetAsync(
                NotificationDataTableNames.SendingNotificationsPartition,
                messageContent.NotificationId);

            // Store the content for the notification that is to be sent.
            var getSendNotificationParamsResponse = new GetSendNotificationParamsResponse
            {
                NotificationContent     = activeNotificationEntity.Content,
                ForceCloseAzureFunction = false,
            };

            // Depending on the recipient type and the data in the send queue message,
            // fetch or generate the recipient Id, service URL, and conversation Id to be used for
            // sending the notification.
            switch (messageContent.RecipientData.RecipientType)
            {
            case RecipientDataType.User:
                await this.SetParamsForUserRecipientAsync(
                    getSendNotificationParamsResponse,
                    messageContent.RecipientData.UserData,
                    messageContent,
                    log);

                break;

            case RecipientDataType.Team:
                this.SetParamsForTeamRecipient(
                    getSendNotificationParamsResponse,
                    messageContent.RecipientData.TeamData);
                break;

            default:
                throw new ArgumentException($"Invalid recipient type: {messageContent.RecipientData.RecipientType}");
            }

            return(getSendNotificationParamsResponse);
        }
        public async Task SendNotificationWhenNoConversationIdTest()
        {
            // Arrange
            var    sendFunctionInstance = this.GetSendFunction();
            string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"\"}}}";
            SendQueueMessageContent messageContent = JsonConvert.DeserializeObject <SendQueueMessageContent>(data);

            this.notificationService
            .Setup(x => x.IsPendingNotification(It.IsAny <SendQueueMessageContent>()))
            .ReturnsAsync(true);
            this.notificationService
            .Setup(x => x.UpdateSentNotification(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <string>(), It.IsAny <string>()))
            .Returns(Task.CompletedTask);

            // Act
            Func <Task> task = async() => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext());

            // Assert
            await task.Should().NotThrowAsync <NullReferenceException>();

            this.notificationService.Verify(x => x.UpdateSentNotification(It.Is <string>(x => x.Equals("notificationId")), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <string>(), It.IsAny <string>()));
        }
        public async Task SendNotificationException_Test()
        {
            // Arrange
            var sendFunctionInstance = this.GetSendFunction();
            SendMessageResponse sendMessageResponse = new SendMessageResponse()
            {
                ResultType = SendMessageResult.Throttled,
            };
            string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : null }}";
            SendQueueMessageContent messageContent = JsonConvert.DeserializeObject <SendQueueMessageContent>(data);

            this.notificationService
            .Setup(x => x.IsPendingNotification(It.IsAny <SendQueueMessageContent>()))
            .ReturnsAsync(true);
            this.notificationService
            .Setup(x => x.UpdateSentNotification(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <int>(), It.IsAny <string>(), It.IsAny <string>()))
            .Returns(Task.CompletedTask);

            // Act
            Func <Task> task = async() => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext());

            // Assert
            await task.Should().ThrowAsync <NullReferenceException>();
        }
コード例 #19
0
        /// <summary>
        /// The main point of this is to set the recipient Id, service URL, and conversation Id for the send parameters.
        /// The recipient Id and service URL is always set in the incoming user data.
        ///
        /// For the conversation Id there are three possibilities:
        ///     Sending to all users - the conversationId is present in the incoming user data.
        ///     Sending to a team's members - the conversation Id was already stored for that user in the User Data table.
        ///         For this scenario, the incoming user data is stored back into the User Data table
        ///         because it may hold more information that was not present in the User Data table originally,
        ///         such as name
        ///     Sending to a team's members - the conversation Id was not stored for that user in the User Data table.
        /// </summary>
        /// <param name="getSendNotificationParamsResponse">The send notification parameters to be updated.</param>
        /// <param name="incomingUserDataEntity">The incoming user data entity.</param>
        /// <param name="messageContent">The queue message content.</param>
        /// <param name="log">The logger.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        private async Task SetParamsForUserRecipientAsync(
            GetSendNotificationParamsResponse getSendNotificationParamsResponse,
            UserDataEntity incomingUserDataEntity,
            SendQueueMessageContent messageContent,
            ILogger log)
        {
            try
            {
                // The AAD Id and service URL will always be set in the incoming user data.
                getSendNotificationParamsResponse.RecipientId = incomingUserDataEntity.AadId;
                getSendNotificationParamsResponse.ServiceUrl  = incomingUserDataEntity.ServiceUrl;

                /*
                 * The rest of the logic in this method is primarily for fetching/generating the conversation Id.
                 *
                 */

                // Case where the conversation Id is included in the incoming user data.
                if (!string.IsNullOrWhiteSpace(incomingUserDataEntity.ConversationId))
                {
                    getSendNotificationParamsResponse.ConversationId = incomingUserDataEntity.ConversationId;

                    return;
                }

                // Case where the conversation Id is not included in the incoming user data (the conversation Id may or
                // may not be stored in the User Data table).

                // Fetch the data for that user from the User Data table to see if they are present.
                // It is possible that that user's data has not been stored in the User Data table.
                // If this is the case, then the conversation will need to be created for that user.
                var storedUserDataEntity = await this.userDataRepository.GetAsync(
                    UserDataTableNames.UserDataPartition,
                    incomingUserDataEntity.AadId);

                // These blocks are used to determine the conversation Id to be used when sending the notification.
                string conversationId = null;
                if (storedUserDataEntity != null)
                {
                    /*
                     * Case where the user's data was stored in the User Data table so their conversation Id is
                     * known. Update the user's entry in the User Data table, though, with the incoming user data
                     * because the incoming user data may hold more information than what is already stored for
                     * that user, such as their name.
                     */

                    // Set the conversation Id to be used when sending the notification.
                    conversationId = storedUserDataEntity.ConversationId;

                    // Set the conversation Id to ensure it is not removed from the User Data table on the update.
                    incomingUserDataEntity.ConversationId = storedUserDataEntity.ConversationId;

                    incomingUserDataEntity.PartitionKey = UserDataTableNames.UserDataPartition;
                    incomingUserDataEntity.RowKey       = incomingUserDataEntity.AadId;

                    await this.userDataRepository.InsertOrMergeAsync(incomingUserDataEntity);
                }
                else
                {
                    /*
                     * Falling into this block means that the user data and the conversation Id for this user
                     * has not been stored. Because of this, the conversation needs to be created and that
                     * conversation Id needs to be stored for that user for later use.
                     */

                    var createConversationResponse = await this.createUserConversationService.CreateConversationAsync(
                        userDataEntity : incomingUserDataEntity,
                        maxNumberOfAttempts : this.maxNumberOfAttempts,
                        log : log);

                    if (createConversationResponse.ResultType == CreateUserConversationResultType.Succeeded)
                    {
                        // Set the conversation Id to be used when sending the notification.
                        conversationId = createConversationResponse.ConversationId;

                        // Store the newly created conversation Id so the create conversation
                        // request will not need to be made again for the user for future notifications.
                        incomingUserDataEntity.ConversationId = createConversationResponse.ConversationId;

                        incomingUserDataEntity.PartitionKey = UserDataTableNames.UserDataPartition;
                        incomingUserDataEntity.RowKey       = incomingUserDataEntity.AadId;

                        await this.userDataRepository.InsertOrMergeAsync(incomingUserDataEntity);
                    }
                    else if (createConversationResponse.ResultType == CreateUserConversationResultType.Throttled)
                    {
                        // If the request was attempted the maximum number of allowed attempts and received
                        // all throttling responses, then set the overall delay time for the system so all
                        // other calls will be delayed and add the message back to the queue with a delay to be
                        // attempted later.
                        await this.delaySendingNotificationService.DelaySendingNotificationAsync(
                            sendRetryDelayNumberOfSeconds : this.sendRetryDelayNumberOfSeconds,
                            sendQueueMessageContent : messageContent,
                            log : log);

                        // Signal that the Azure Function should be completed to be attempted later.
                        getSendNotificationParamsResponse.ForceCloseAzureFunction = true;
                        return;
                    }
                    else if (createConversationResponse.ResultType == CreateUserConversationResultType.Failed)
                    {
                        // If the create conversation call failed, save the results, do not attempt the
                        // request again, and end the function.
                        await this.manageResultDataService.ProcessResultDataAsync(
                            notificationId : messageContent.NotificationId,
                            recipientId : incomingUserDataEntity.AadId,
                            totalNumberOfSendThrottles : 0,
                            isStatusCodeFromCreateConversation : true,
                            statusCode : createConversationResponse.StatusCode,
                            allSendStatusCodes : string.Empty,
                            errorMessage : createConversationResponse.ErrorMessage,
                            log : log);

                        // Signal that the Azure Function should be completed.
                        getSendNotificationParamsResponse.ForceCloseAzureFunction = true;
                        return;
                    }
                }

                // Set the conversation Id to be used when sending the notification.
                getSendNotificationParamsResponse.ConversationId = conversationId;
            }
            catch (Exception e)
            {
                var errorMessage = $"{e.GetType()}: {e.Message}";
                log.LogError(e, $"ERROR: {errorMessage}");

                throw;
            }
        }
コード例 #20
0
        /// <summary>
        /// Checks if the data queue message should be processed. Scenarios it checks for are:
        ///     If the entire system is currently in a throttled state.
        ///     If the notification has already been attempted to be sent to this recipient.
        /// </summary>
        /// <param name="messageContent">The data queue message content.</param>
        /// <param name="sendRetryDelayNumberOfSeconds">The send retry delay number of seconds.</param>
        /// <param name="log">The logger.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public async Task <bool> VerifyMessageShouldBeProcessedAsync(
            SendQueueMessageContent messageContent,
            double sendRetryDelayNumberOfSeconds,
            ILogger log)
        {
            try
            {
                // Fetch the current global sending notification data. This is where data about the overall system's
                // status is stored e.g. is everything in a delayed state because the bot is being throttled.
                var globalSendingNotificationDataEntityTask = this.globalSendingNotificationDataRepository
                                                              .GetGlobalSendingNotificationDataEntityAsync();

                // Fetch the sent notification data to verify it has only been initialized and a notification
                // has not already been sent to this recipient.
                var existingSentNotificationDataEntityTask = this.sentNotificationDataRepository
                                                             .GetAsync(
                    partitionKey: messageContent.NotificationId,
                    rowKey: messageContent.RecipientData.RecipientId);

                await Task.WhenAll(
                    globalSendingNotificationDataEntityTask,
                    existingSentNotificationDataEntityTask);

                var globalSendingNotificationDataEntity = await globalSendingNotificationDataEntityTask;
                var existingSentNotificationDataEntity  = await existingSentNotificationDataEntityTask;

                var shouldProceedWithProcessing = true;

                // If the overall system is in a throttled state and needs to be delayed,
                // add the message back on the queue with a delay and stop processing the queue message.
                if (globalSendingNotificationDataEntity?.SendRetryDelayTime != null &&
                    DateTime.UtcNow < globalSendingNotificationDataEntity.SendRetryDelayTime)
                {
                    await this.sendQueue.SendDelayedAsync(messageContent, sendRetryDelayNumberOfSeconds);

                    shouldProceedWithProcessing = false;
                }

                // First, verify that the recipient's sent notification data has been stored and initialized. This
                // verifies a notification is expected to be sent to this recipient.
                // Next, in order to not send a duplicate notification to this recipient, verify that the StatusCode
                // in the sent notification data is set to either:
                //      The InitializationStatusCode (likely 0) - this means the notification has not been attempted
                //          to be sent to this recipient.
                //      The FaultedAndRetryingStatusCode (likely -1) - this means the Azure Function previously attempted
                //          to send the notification to this recipient but threw an exception, so sending the
                //          notification should be attempted again.
                // If it is neither of these scenarios, then complete the function in order to not send a duplicate
                // notification to this recipient.
                else if (existingSentNotificationDataEntity == null ||
                         (existingSentNotificationDataEntity.StatusCode != SentNotificationDataEntity.InitializationStatusCode &&
                          existingSentNotificationDataEntity.StatusCode != SentNotificationDataEntity.FaultedAndRetryingStatusCode))
                {
                    shouldProceedWithProcessing = false;
                }

                return(shouldProceedWithProcessing);
            }
            catch (Exception e)
            {
                var errorMessage = $"{e.GetType()}: {e.Message}";
                log.LogError(e, $"ERROR: {errorMessage}");
                throw;
            }
        }
コード例 #21
0
        public async Task <IActionResult> UpdateSentNotificationAsync([FromBody] DraftNotification notification)
        {
            var containsHiddenMembership = await this.groupsService.ContainsHiddenMembershipAsync(notification.Groups);

            if (containsHiddenMembership)
            {
                return(this.Forbid());
            }

            if (!notification.Validate(this.localizer, out string errorMessage))
            {
                return(this.BadRequest(errorMessage));
            }

            var senttNotificationDataEntity = await this.notificationDataRepository.GetAsync(
                NotificationDataTableNames.SentNotificationsPartition,
                notification.Id);

            var updateSentNotificationDataEntity = new NotificationDataEntity
            {
                PartitionKey       = NotificationDataTableNames.SentNotificationsPartition,
                RowKey             = notification.Id,
                Id                 = notification.Id,
                Channel            = notification.Channel,
                TemplateID         = notification.TemplateID,
                Title              = notification.Title,
                ImageLink          = notification.ImageLink,
                Summary            = notification.Summary,
                Author             = notification.Author,
                ButtonTitle        = notification.ButtonTitle,
                ButtonLink         = notification.ButtonLink,
                CreatedBy          = this.HttpContext.User?.Identity?.Name,
                CreatedDate        = DateTime.UtcNow,
                SentDate           = DateTime.UtcNow,
                Edited             = DateTime.UtcNow,
                IsDraft            = false,
                Teams              = notification.Teams,
                Rosters            = notification.Rosters,
                Groups             = notification.Groups,
                AllUsers           = notification.AllUsers,
                MessageVersion     = senttNotificationDataEntity.MessageVersion,
                Succeeded          = senttNotificationDataEntity.Succeeded,
                Failed             = 0,
                Throttled          = 0,
                TotalMessageCount  = senttNotificationDataEntity.TotalMessageCount,
                SendingStartedDate = DateTime.UtcNow,
                Status             = NotificationStatus.Sent.ToString(),
            };
            var id = updateSentNotificationDataEntity.Id;
            var sentNotificationId = await this.notificationDataRepository.UpdateSentNotificationAsync(updateSentNotificationDataEntity, id);

            await this.sentNotificationUpdateDataRepository.EnsureSentNotificationDataTableExistsAsync();

            var sendQueueMessageContent = new SendQueueMessageContent
            {
                NotificationId = sentNotificationId,
                ActivtiyId     = null,
                RecipientData  = null,
                NotificationUpdatePreviewEntity = new NotificationUpdatePreviewEntity
                {
                    ActionType             = "EditNotification",
                    NotificationDataEntity = updateSentNotificationDataEntity,
                    ConversationReferance  = null,
                    MessageActivity        = null,
                    ServiceUrl             = null,
                    AppID = null,
                },
            };

            await this.sendQueue.SendAsync(sendQueueMessageContent);

            return(this.Ok());
        }