Beispiel #1
0
        /// <summary>
        /// Construct the response card - when user asks a question to the QnA Maker through the bot.
        /// </summary>
        /// <param name="response">The response model.</param>
        /// <param name="userQuestion">Actual question that the user has asked the bot.</param>
        /// <param name="appBaseUri">The base URI where the app is hosted.</param>
        /// <param name="payload">The response card payload.</param>
        /// <returns>The response card to append to a message as an attachment.</returns>
        public static Attachment GetCard(QnASearchResult response, string userQuestion, string appBaseUri, ResponseCardPayload payload)
        {
            bool isRichCard = false;
            AdaptiveSubmitActionData answerModel = new AdaptiveSubmitActionData();

            if (Validators.IsValidJSON(response.Answer))
            {
                answerModel = JsonConvert.DeserializeObject <AdaptiveSubmitActionData>(response.Answer);
                isRichCard  = Validators.IsRichCard(answerModel);
            }

            string       answer       = isRichCard ? answerModel.Description : response.Answer;
            AdaptiveCard responseCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 2))
            {
                Body    = BuildResponseCardBody(response, userQuestion, answer, appBaseUri, payload, isRichCard),
                Actions = BuildListOfActions(userQuestion, answer),
            };

            if (!string.IsNullOrEmpty(answerModel.RedirectionUrl))
            {
                responseCard.Actions.Add(
                    new AdaptiveOpenUrlAction
                {
                    Title = Strings.OpenArticle,
                    Url   = new Uri(answerModel.RedirectionUrl),
                });
            }

            return(new Attachment
            {
                ContentType = AdaptiveCard.ContentType,
                Content = responseCard,
            });
        }
Beispiel #2
0
 /// <summary>
 ///  This method is used to check html tags are present or not.
 /// </summary>
 /// <param name="qnaPairEntity">Qna pair entity that contains question and answer information.</param>
 /// <returns>Boolean value where true represent html tags are present while false represent html tags are not present.</returns>
 public static bool IsContainsHtml(AdaptiveSubmitActionData qnaPairEntity)
 {
     return(Regex.IsMatch(qnaPairEntity?.UpdatedQuestion?.Trim(), HtmlPattern) ||
            Regex.IsMatch(qnaPairEntity?.ImageUrl?.Trim(), HtmlPattern) ||
            Regex.IsMatch(qnaPairEntity?.Description?.Trim(), HtmlPattern) ||
            Regex.IsMatch(qnaPairEntity?.Title?.Trim(), HtmlPattern));
 }
Beispiel #3
0
        /// <summary>
        /// Image And redirection url fields validation.
        /// </summary>
        /// <param name="qnaPairEntity">Qna pair entity that contains question and answer information.</param>
        /// <returns>Return a question data object.</returns>
        public static AdaptiveSubmitActionData ValidateImageAndRedirectionUrls(AdaptiveSubmitActionData qnaPairEntity)
        {
            qnaPairEntity.IsInvalidImageUrl = IsImageUrlInvalid(qnaPairEntity);

            // qnaPairEntity.IsInvalidRedirectUrl = IsRedirectionUrlInvalid(qnaPairEntity);
            return(qnaPairEntity);
        }
Beispiel #4
0
 /// <summary>
 ///  This method is used to check if card is rich or not.
 /// </summary>
 /// <param name="questionObject">Question data object.</param>
 /// <returns>Boolean value where true represent it is a rich card while false represent it is a normal card.</returns>
 public static bool IsRichCard(AdaptiveSubmitActionData questionObject)
 {
     return(!string.IsNullOrEmpty(questionObject?.Title?.Trim()) ||
            !string.IsNullOrEmpty(questionObject?.Subtitle?.Trim()) ||
            !string.IsNullOrEmpty(questionObject?.ImageUrl?.Trim()) ||
            !string.IsNullOrEmpty(questionObject?.RedirectionUrl?.Trim()));
 }
        /// <summary>
        /// Return card response.
        /// </summary>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <param name="postedQnaPairEntity">Qna pair entity that contains question and answer information.</param>
        /// <param name="answer">Answer text.</param>
        /// <returns>Card attachment.</returns>
        private async Task<Attachment> CardResponseAsync(
            ITurnContext<IInvokeActivity> turnContext,
            AdaptiveSubmitActionData postedQnaPairEntity,
            string answer)
        {
            Attachment qnaAdaptiveCard = new Attachment();
            bool isSaved;

            if (postedQnaPairEntity.UpdatedQuestion?.ToUpperInvariant().Trim() == postedQnaPairEntity.OriginalQuestion?.ToUpperInvariant().Trim())
            {
                postedQnaPairEntity.IsTestKnowledgeBase = false;
                isSaved = await this.SaveQnAPairAsync(turnContext, answer, postedQnaPairEntity).ConfigureAwait(false);
                if (!isSaved)
                {
                    postedQnaPairEntity.IsTestKnowledgeBase = true;
                    await this.SaveQnAPairAsync(turnContext, answer, postedQnaPairEntity).ConfigureAwait(false);
                }
            }
            else
            {
                // Check if question exist in the production/test knowledgebase & exactly the same question.
                var hasQuestionExist = await this.qnaServiceProvider.QuestionExistsInKbAsync(postedQnaPairEntity.UpdatedQuestion).ConfigureAwait(false);

                // Edit the question if it doesn't exist in the test knowledgebse.
                if (hasQuestionExist)
                {
                    // If edited question text is already exist in the test knowledgebase.
                    postedQnaPairEntity.IsQuestionAlreadyExists = true;
                }
                else
                {
                    // Save the edited question in the knowledgebase.
                    postedQnaPairEntity.IsTestKnowledgeBase = false;
                    isSaved = await this.SaveQnAPairAsync(turnContext, answer, postedQnaPairEntity).ConfigureAwait(false);
                    if (!isSaved)
                    {
                        postedQnaPairEntity.IsTestKnowledgeBase = true;
                        await this.SaveQnAPairAsync(turnContext, answer, postedQnaPairEntity).ConfigureAwait(false);
                    }
                }

                if (postedQnaPairEntity.IsQuestionAlreadyExists)
                {
                    // Response with question already exist(in test knowledgebase).
                    qnaAdaptiveCard = MessagingExtensionQnaCard.AddQuestionForm(postedQnaPairEntity, this.appBaseUri);
                }
            }

            return qnaAdaptiveCard;
        }
Beispiel #6
0
        public async Task <ActionResult> Edit(int id, string question, string answer)
        {
            var qnaModel = new QnAQuestionModel();
            AdaptiveSubmitActionData postedValues = new AdaptiveSubmitActionData();

            if (id > 0)
            {
                var knowledgeBaseId = await this.configurationProvider.GetSavedEntityDetailAsync(ConfigurationEntityTypes.KnowledgeBaseId).ConfigureAwait(false);

                var qnaitems = await this.qnaServiceProvider.DownloadKnowledgebaseAsync(knowledgeBaseId);

                var answerData = qnaitems.FirstOrDefault(k => k.Id == id);

                if (answerData != null)
                {
                    postedValues.QnaPairId        = id;
                    postedValues.OriginalQuestion = answerData.Questions[0];
                    postedValues.UpdatedQuestion  = answerData.Questions[0];

                    if (Validators.IsValidJSON(answerData.Answer))
                    {
                        AnswerModel answerModel = JsonConvert.DeserializeObject <AnswerModel>(answerData.Answer);
                        postedValues.Description    = answerModel.Description;
                        postedValues.Title          = answerModel.Title;
                        postedValues.Subtitle       = answerModel.Subtitle;
                        postedValues.ImageUrl       = answerModel.ImageUrl;
                        postedValues.RedirectionUrl = answerModel.RedirectionUrl;
                    }
                    else
                    {
                        postedValues.Description = answerData.Answer;
                        //postedValues.ImageUrl = "https://3uc74q2sbxzd4.blob.core.windows.net/faqplus-image-container/20210513080531_Feb1_Byron_03.jpg";
                        if (!String.IsNullOrEmpty(postedValues.ImageUrl))
                        {
                            qnaModel.ImageMd = $"![]({postedValues.ImageUrl})";
                        }
                    }
                }
                else
                {
                    postedValues.Description = "ERROR: QnA Pair Not Found";
                }
            }

            qnaModel.PostedValues = postedValues;
            qnaModel.AppId        = this.appId;

            return(View(qnaModel));
        }
Beispiel #7
0
        /// <summary>
        /// Store user rejected data to Microsoft Azure Table storage.
        /// </summary>
        /// <param name="cardPostedData">Represents card submitted data.</param>
        /// <param name="name">Gets or sets display friendly name.</param>
        /// <param name="aadObjectId">Gets or sets this account's object ID within Azure Active Directory (AAD).</param>
        /// <returns>Represent a task queued for operation.</returns>
        public CompanyResponseEntity AddRejectedData(AdaptiveSubmitActionData cardPostedData, string name, string aadObjectId)
        {
            cardPostedData = cardPostedData ?? throw new ArgumentNullException(nameof(cardPostedData));

            CompanyResponseEntity companyResponseEntity;

            companyResponseEntity = this.companyResponseStorageProvider.GetCompanyResponseEntityAsync(cardPostedData.ResponseId).GetAwaiter().GetResult();
            companyResponseEntity.ApprovalStatus         = cardPostedData.ApprovalStatus;
            companyResponseEntity.ApprovedOrRejectedBy   = name;
            companyResponseEntity.ApproverUserId         = aadObjectId;
            companyResponseEntity.ApprovedOrRejectedDate = DateTime.UtcNow;
            companyResponseEntity.ApprovalRemark         = cardPostedData.ApprovalRemark;

            return(companyResponseEntity);
        }
        /// <summary>
        /// Process task module fetch request.
        /// </summary>
        /// <param name="turnContext">Provides context for a turn in a bot.</param>
        /// <returns>A task that represents a response.</returns>
        public async Task <TaskModuleResponse> OnTaskModuleFetchRequestAsync(ITurnContext <IInvokeActivity> turnContext)
        {
            turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext), "Turn context cannot be null");
            var member = await TeamsInfo.GetMemberAsync(turnContext, turnContext.Activity.From.Id, CancellationToken.None);

            if (member == null)
            {
                return(this.GetTaskModuleResponse(new Uri($"{this.botOptions.Value.AppBaseUri}/error"), this.localizer.GetString("ErrorTitle")));
            }

            var activity = turnContext.Activity as Activity;

            var activityValue = JObject.Parse(activity.Value?.ToString())["data"].ToString();

            AdaptiveSubmitActionData adaptiveTaskModuleCardAction = JsonConvert.DeserializeObject <AdaptiveSubmitActionData>(activityValue);

            if (adaptiveTaskModuleCardAction == null)
            {
                this.logger.LogInformation("Value obtained from task module fetch action is null");
            }

            var command = adaptiveTaskModuleCardAction.Command;
            Uri taskModuleRequestUrl;

            switch (command)
            {
            case BotCommands.EditEvent:
                taskModuleRequestUrl = new Uri($"{this.botOptions.Value.AppBaseUri}/create-event?teamId={adaptiveTaskModuleCardAction.EventId}&eventId={adaptiveTaskModuleCardAction.EventId}");
                return(this.GetTaskModuleResponse(taskModuleRequestUrl, this.localizer.GetString("EditEventCardButton")));

            case BotCommands.CreateEvent:
                taskModuleRequestUrl = new Uri($"{this.botOptions.Value.AppBaseUri}/create-event");
                return(this.GetTaskModuleResponse(taskModuleRequestUrl, this.localizer.GetString("CreateEventButtonWelcomeCard")));

            case BotCommands.CloseRegistration:
                taskModuleRequestUrl = new Uri($"{this.botOptions.Value.AppBaseUri}/close-or-cancel-event?operationType={(int)EventOperationType.CloseRegistration}&eventId={adaptiveTaskModuleCardAction.EventId}&teamId={adaptiveTaskModuleCardAction.TeamId}");
                return(this.GetTaskModuleResponse(taskModuleRequestUrl, this.localizer.GetString("CloseRegistrationCardButton")));

            case BotCommands.RegisterForEvent:
                taskModuleRequestUrl = new Uri($"{this.botOptions.Value.AppBaseUri}/register-remove?eventId={adaptiveTaskModuleCardAction.EventId}&teamId={adaptiveTaskModuleCardAction.TeamId}");
                return(this.GetTaskModuleResponse(taskModuleRequestUrl, this.localizer.GetString("RegisterButton")));

            default:
                return(this.GetTaskModuleResponse(new Uri($"{this.botOptions.Value.AppBaseUri}/error"), this.localizer.GetString("ErrorTitle")));
            }
        }
        /// <summary>
        /// When OnTurn method receives a fetch invoke activity on bot turn, it calls this method.
        /// </summary>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <param name="taskModuleRequest">Task module invoke request value payload.</param>
        /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
        /// <returns>A task that represents a task module response.</returns>
        /// <remarks>
        /// Reference link: https://docs.microsoft.com/en-us/dotnet/api/microsoft.bot.builder.teams.teamsactivityhandler.onteamstaskmodulefetchasync?view=botbuilder-dotnet-stable.
        /// </remarks>
        protected override async Task <TaskModuleResponse> OnTeamsTaskModuleFetchAsync(
            ITurnContext <IInvokeActivity> turnContext,
            TaskModuleRequest taskModuleRequest,
            CancellationToken cancellationToken)
        {
            try
            {
                turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext));
                this.RecordEvent(nameof(this.OnTeamsTaskModuleFetchAsync), turnContext);

                var activity      = turnContext.Activity;
                var activityValue = JObject.Parse(activity.Value?.ToString())["data"].ToString();

                AdaptiveSubmitActionData adaptiveSubmitActionData = JsonConvert.DeserializeObject <AdaptiveSubmitActionData>(JObject.Parse(taskModuleRequest?.Data.ToString()).SelectToken("data").ToString());
                if (adaptiveSubmitActionData == null)
                {
                    this.logger.LogInformation("Value obtained from task module fetch action is null");
                }

                var adaptiveActionType = adaptiveSubmitActionData.AdaptiveActionType;
                var postId             = adaptiveSubmitActionData.Id;
                switch (adaptiveActionType.ToUpperInvariant())
                {
                case BotCommandConstants.ViewLearningModule:
                    this.logger.LogInformation("Fetch task module to show learning module detail.");
                    return(this.GetTaskModuleResponse(string.Format(CultureInfo.InvariantCulture, $"{this.botOptions.Value.AppBaseUri}/learningmodulepreview?viewMode=1&learningModuleId={postId}"), this.localizer.GetString("learningModuleDetails")));

                case BotCommandConstants.ViewResource:
                    this.logger.LogInformation("Fetch task module to show resource detail.");
                    return(this.GetTaskModuleResponse(string.Format(CultureInfo.InvariantCulture, $"{this.botOptions.Value.AppBaseUri}/previewcontent?viewMode=1&resourceId={postId}"), this.localizer.GetString("resourceDetails")));

                default:
                    this.logger.LogInformation($"Invalid command for task module fetch activity. Command is: {adaptiveActionType}");
                    var reply = this.localizer.GetString("invalidCommand");
                    await turnContext.SendActivityAsync(reply).ConfigureAwait(false);

                    return(null);
                }
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error while fetching task module");
                throw;
            }
        }
Beispiel #10
0
        /// <summary>
        /// Get combined description for rich card.
        /// </summary>
        /// <param name="questionData">Question data object.</param>
        /// <returns>Combined description for rich card.</returns>
        public static string BuildCombinedDescriptionAsync(AdaptiveSubmitActionData questionData)
        {
            if (!string.IsNullOrWhiteSpace(questionData?.Title?.Trim()) ||
                !string.IsNullOrWhiteSpace(questionData?.ImageUrl?.Trim()))
            {
                var answerModel = new AnswerModel
                {
                    Description = questionData?.Description.Trim(),
                    Title       = questionData?.Title?.Trim(),
                    ImageUrl    = questionData?.ImageUrl?.Trim(),
                };

                return(JsonConvert.SerializeObject(answerModel));
            }
            else
            {
                return(questionData.Description.Trim());
            }
        }
Beispiel #11
0
        /// <summary>
        /// When OnTurn method receives a fetch invoke activity on bot turn, it calls this method.
        /// </summary>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <param name="taskModuleRequest">Task module invoke request value payload.</param>
        /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
        /// <returns>A task that represents a task module response.</returns>
        /// <remarks>
        /// Reference link: https://docs.microsoft.com/en-us/dotnet/api/microsoft.bot.builder.teams.teamsactivityhandler.onteamstaskmodulefetchasync?view=botbuilder-dotnet-stable.
        /// </remarks>
        protected override async Task <TaskModuleResponse> OnTeamsTaskModuleFetchAsync(
            ITurnContext <IInvokeActivity> turnContext,
            TaskModuleRequest taskModuleRequest,
            CancellationToken cancellationToken)
        {
            try
            {
                turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext));
                this.RecordEvent(nameof(this.OnTeamsTaskModuleFetchAsync), turnContext);

                var activity = turnContext.Activity;
                AdaptiveSubmitActionData adaptiveSubmitActionData = JsonConvert.DeserializeObject <AdaptiveSubmitActionData>(taskModuleRequest?.Data?.ToString());

                if (adaptiveSubmitActionData == null)
                {
                    this.logger.LogInformation("Value obtained from task module fetch action is null");
                    return(this.cardHelper.GetTaskModuleErrorResponse(this.localizer.GetString("ErrorMessage"), this.localizer.GetString("BotFailureTitle")));
                }

                string scrumStartActivityId = adaptiveSubmitActionData.ScrumStartActivityId;
                string adaptiveActionType   = adaptiveSubmitActionData.AdaptiveActionType;

                switch (adaptiveActionType.ToUpperInvariant())
                {
                case Constants.ScrumDetailsTaskModuleCommand:
                    if (!string.IsNullOrEmpty(scrumStartActivityId))
                    {
                        return(await this.cardHelper.GetScrumDetailsCardResponseAsync(adaptiveSubmitActionData.ScrumMembers, adaptiveSubmitActionData.ScrumMasterId, scrumStartActivityId, turnContext, cancellationToken));
                    }

                    break;

                case Constants.UpdateStatusTaskModuleCommand:
                    var scrumInfo = await this.scrumHelper.GetActiveScrumAsync(adaptiveSubmitActionData.ScrumMasterId);

                    if (scrumInfo == null || scrumInfo.IsCompleted == true)
                    {
                        this.logger.LogInformation($"The scrum is not running at this moment: {activity.Conversation.Id}");
                        return(this.cardHelper.GetTaskModuleErrorResponse(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorScrumDoesNotExist"), activity.From.Name), this.localizer.GetString("UpdateStatusTitle")));
                    }

                    var activityId = this.activityHelper.GetActivityIdToMatch(scrumInfo.MembersActivityIdMap, activity.From.Id);
                    if (string.IsNullOrEmpty(activityId))
                    {
                        this.logger.LogInformation($"Member who is updating the scrum is not the part of scrum for: {activity.Conversation.Id}");
                        return(this.cardHelper.GetTaskModuleErrorResponse(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorUserIsNotPartOfRunningScrumAndTryUpdateStatus"), activity.From.Name), this.localizer.GetString("UpdateStatusTitle")));
                    }

                    return(this.cardHelper.GetScrumStatusUpdateCardResponse(adaptiveSubmitActionData.ScrumMembers, adaptiveSubmitActionData.ScrumMasterId, scrumStartActivityId, new ScrumStatus()));

                case Constants.SettingsTaskModuleCommand:
                    string customAPIAuthenticationToken = this.tokenHelper.GenerateAPIAuthToken(applicationBasePath: activity.ServiceUrl, fromId: activity.From.Id, jwtExpiryMinutes: 60);
                    return(this.cardHelper.GetSettingsCardResponse(customAPIAuthenticationToken, this.telemetryClient?.InstrumentationKey, activity.ServiceUrl));

                default:
                    this.logger.LogInformation($"Invalid command for task module fetch activity. Command is: {adaptiveActionType}");
                    return(this.cardHelper.GetTaskModuleErrorResponse(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("TaskModuleInvalidCommandText"), adaptiveActionType), this.localizer.GetString("BotFailureTitle")));
                }

                return(null);
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error while fetching task module.", SeverityLevel.Error);
                return(this.cardHelper.GetTaskModuleErrorResponse(this.localizer.GetString("ErrorMessage"), this.localizer.GetString("BotFailureTitle")));
            }
        }
Beispiel #12
0
        /// <summary>
        /// Stores scrum status details in storage.
        /// </summary>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <param name="scrumStatus">Scrum status entity to be stored in table storage.</param>
        /// <param name="adaptiveSubmitActionData">Data submitted in task module.</param>
        /// <param name="scrumStartCardResponseId">Scrum start card response id.</param>
        /// <param name="aadGroupId">Azure Active Directory group Id.</param>
        /// <returns>Returns success or failure on whether data is stored in storage.</returns>
        public async Task <bool> SaveScrumStatusDetailsAsync(ITurnContext <IInvokeActivity> turnContext, ScrumStatus scrumStatus, AdaptiveSubmitActionData adaptiveSubmitActionData, string scrumStartCardResponseId, string aadGroupId)
        {
            scrumStatus = scrumStatus ?? throw new ArgumentNullException(nameof(scrumStatus));
            turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext));

            scrumStatus.MembersActivityIdMap     = adaptiveSubmitActionData?.ScrumMembers;
            scrumStatus.ScrumStartCardResponseId = scrumStartCardResponseId;
            scrumStatus.CreatedOn       = DateTime.UtcNow.ToString(Constants.Rfc3339DateTimeFormat, CultureInfo.CurrentCulture);
            scrumStatus.Username        = turnContext.Activity.From.Name;
            scrumStatus.AadGroupId      = aadGroupId;
            scrumStatus.UserAadObjectId = turnContext.Activity.From.AadObjectId;
            return(await this.scrumStatusStorageProvider.CreateOrUpdateScrumStatusAsync(scrumStatus));
        }
        /// <summary>
        /// Method perform update operation of question and answer pair.
        /// </summary>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <param name="answer">Answer of the given question.</param>
        /// <param name="qnaPairEntity">Qna pair entity that contains question and answer information.</param>
        /// <returns>A <see cref="Task"/> of type bool where true represents question and answer pair updated successfully while false indicates failure in updating the question and answer pair.</returns>
        public async Task<bool> SaveQnAPairAsync(ITurnContext turnContext, string answer, AdaptiveSubmitActionData qnaPairEntity)
        {
            QnASearchResult searchResult;
            var qnaAnswerResponse = await this.qnaServiceProvider.GenerateAnswerAsync(qnaPairEntity.OriginalQuestion, qnaPairEntity.IsTestKnowledgeBase).ConfigureAwait(false);
            searchResult = qnaAnswerResponse.Answers.FirstOrDefault();
            bool isSameQuestion = false;

            // Check if question exist in the knowledgebase.
            if (searchResult != null && searchResult.Questions.Count > 0)
            {
                // Check if the edited question & result returned from the knowledgebase are same.
                isSameQuestion = searchResult.Questions.First().ToUpperInvariant() == qnaPairEntity.OriginalQuestion.ToUpperInvariant();
            }

            // Edit the QnA pair if the question is exist in the knowledgebase & exactly the same question on which we are performing the action.
            if (searchResult.Id != -1 && isSameQuestion)
            {
                int qnaPairId = searchResult.Id.Value;
                await this.qnaServiceProvider.UpdateQnaAsync(qnaPairId, answer, turnContext.Activity.From.AadObjectId, qnaPairEntity.UpdatedQuestion, qnaPairEntity.OriginalQuestion).ConfigureAwait(false);
                this.logger.LogInformation($"Question updated by: {turnContext.Activity.Conversation.AadObjectId}");
                Attachment attachment = new Attachment();
                if (qnaPairEntity.IsRichCard)
                {
                    qnaPairEntity.IsPreviewCard = false;
                    qnaPairEntity.IsTestKnowledgeBase = true;
                    attachment = MessagingExtensionQnaCard.ShowRichCard(qnaPairEntity, turnContext.Activity.From.Name, Strings.LastEditedText);
                }
                else
                {
                    qnaPairEntity.IsTestKnowledgeBase = true;
                    qnaPairEntity.Description = answer ?? throw new ArgumentNullException(nameof(answer));
                    attachment = MessagingExtensionQnaCard.ShowNormalCard(qnaPairEntity, turnContext.Activity.From.Name, actionPerformed: Strings.LastEditedText);
                }

                var activityId = this.activityStorageProvider.GetAsync(qnaAnswerResponse.Answers.First().Metadata.FirstOrDefault(x => x.Name == Constants.MetadataActivityReferenceId)?.Value).Result.FirstOrDefault().ActivityId;
                var updateCardActivity = new Activity(ActivityTypes.Message)
                {
                    Id = activityId ?? throw new ArgumentNullException(nameof(activityId)),
                    Conversation = turnContext.Activity.Conversation,
                    Attachments = new List<Attachment> { attachment },
                };

                // Send edited question and answer card as response.
                await turnContext.UpdateActivityAsync(updateCardActivity, cancellationToken: default).ConfigureAwait(false);
            }
            else
            {
                return false;
            }

            return true;
        }
        /// <summary>
        /// Validate the adaptive card fields while editing the question and answer pair.
        /// </summary>
        /// <param name="postedQnaPairEntity">Qna pair entity contains submitted card data.</param>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <returns>Envelope for Task Module Response.</returns>
        public async Task<TaskModuleResponse> EditQnAPairAsync(
            AdaptiveSubmitActionData postedQnaPairEntity,
            ITurnContext<IInvokeActivity> turnContext)
        {
            // Check if fields contains Html tags or Question and answer empty then return response with error message.
            if (Validators.IsContainsHtml(postedQnaPairEntity) || Validators.IsQnaFieldsNullOrEmpty(postedQnaPairEntity))
            {
                // Returns the card with validation errors on add QnA task module.
                return await TaskModuleActivity.GetTaskModuleResponseAsync(MessagingExtensionQnaCard.AddQuestionForm(Validators.HtmlAndQnaEmptyValidation(postedQnaPairEntity), this.appBaseUri)).ConfigureAwait(false);
            }

            if (Validators.IsRichCard(postedQnaPairEntity))
            {
                if (Validators.IsImageUrlInvalid(postedQnaPairEntity) || Validators.IsRedirectionUrlInvalid(postedQnaPairEntity))
                {
                    // Show the error message on task module response for edit QnA pair, if user has entered invalid image or redirection url.
                    return await TaskModuleActivity.GetTaskModuleResponseAsync(MessagingExtensionQnaCard.AddQuestionForm(Validators.ValidateImageAndRedirectionUrls(postedQnaPairEntity), this.appBaseUri)).ConfigureAwait(false);
                }

                string combinedDescription = QnaHelper.BuildCombinedDescriptionAsync(postedQnaPairEntity);
                postedQnaPairEntity.IsRichCard = true;

                if (postedQnaPairEntity.UpdatedQuestion?.ToUpperInvariant().Trim() == postedQnaPairEntity.OriginalQuestion?.ToUpperInvariant().Trim())
                {
                    // Save the QnA pair, return the response and closes the task module.
                    await TaskModuleActivity.GetTaskModuleResponseAsync(this.CardResponseAsync(
                        turnContext,
                        postedQnaPairEntity,
                        combinedDescription).Result).ConfigureAwait(false);
                    return default;
                }
                else
                {
                    var hasQuestionExist = await this.qnaServiceProvider.QuestionExistsInKbAsync(postedQnaPairEntity.UpdatedQuestion).ConfigureAwait(false);
                    if (hasQuestionExist)
                    {
                        // Shows the error message on task module, if question already exist.
                        return await TaskModuleActivity.GetTaskModuleResponseAsync(this.CardResponseAsync(
                            turnContext,
                            postedQnaPairEntity,
                            combinedDescription).Result).ConfigureAwait(false);
                    }
                    else
                    {
                        // Save the QnA pair, return the response and closes the task module.
                        await TaskModuleActivity.GetTaskModuleResponseAsync(this.CardResponseAsync(
                            turnContext,
                            postedQnaPairEntity,
                            combinedDescription).Result).ConfigureAwait(false);
                        return default;
                    }
                }
            }
            else
            {
                // Normal card section.
                if (postedQnaPairEntity.UpdatedQuestion?.ToUpperInvariant().Trim() == postedQnaPairEntity.OriginalQuestion?.ToUpperInvariant().Trim())
                {
                    // Save the QnA pair, return the response and closes the task module.
                    await TaskModuleActivity.GetTaskModuleResponseAsync(this.CardResponseAsync(
                        turnContext,
                        postedQnaPairEntity,
                        postedQnaPairEntity.Description).Result).ConfigureAwait(false);
                    return default;
                }
                else
                {
                    var hasQuestionExist = await this.qnaServiceProvider.QuestionExistsInKbAsync(postedQnaPairEntity.UpdatedQuestion).ConfigureAwait(false);
                    if (hasQuestionExist)
                    {
                        // Shows the error message on task module, if question already exist.
                        return await TaskModuleActivity.GetTaskModuleResponseAsync(this.CardResponseAsync(
                            turnContext,
                            postedQnaPairEntity,
                            postedQnaPairEntity.Description).Result).ConfigureAwait(false);
                    }
                    else
                    {
                        // Save the QnA pair, return the response and closes the task module.
                        await TaskModuleActivity.GetTaskModuleResponseAsync(this.CardResponseAsync(
                            turnContext,
                            postedQnaPairEntity,
                            postedQnaPairEntity.Description).Result).ConfigureAwait(false);
                        return default;
                    }
                }
            }
        }
Beispiel #15
0
        /// <summary>
        /// Method to request more information details card from new hire.
        /// </summary>
        /// <param name="turnContext">Provides context for a turn of a bot.</param>
        /// <param name="valuesfromCard">Values from card.</param>
        /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
        /// <returns>Request more information notification card.</returns>
        public async Task RequestMoreInfoActionAsync(ITurnContext <IMessageActivity> turnContext, AdaptiveSubmitActionData valuesfromCard, CancellationToken cancellationToken)
        {
            turnContext    = turnContext ?? throw new ArgumentNullException(nameof(turnContext));
            valuesfromCard = valuesfromCard ?? throw new ArgumentNullException(nameof(valuesfromCard));

            if (string.IsNullOrEmpty(valuesfromCard.Comments))
            {
                await turnContext.SendActivityAsync(this.localizer.GetString("TellMeMoreInputValidationText"));

                return;
            }

            var introduction = await this.introductionStorageProvider.GetIntroductionDetailAsync(
                valuesfromCard.IntroductionEntity.NewHireAadObjectId,
                valuesfromCard.IntroductionEntity.ManagerAadObjectId);

            if (introduction.ApprovalStatus == (int)IntroductionStatus.Approved)
            {
                await turnContext.SendActivityAsync(this.localizer.GetString("ManagerApprovalValidationText"));
            }
            else
            {
                valuesfromCard.IntroductionEntity.Comments       = valuesfromCard.Comments;
                valuesfromCard.IntroductionEntity.ApprovalStatus = (int)IntroductionStatus.TellMeMore;
                await this.introductionStorageProvider.StoreOrUpdateIntroductionDetailAsync(valuesfromCard.IntroductionEntity);

                var newHireNotification = MessageFactory.Attachment(TellMeMoreCard.GetCard(this.botOptions.Value.AppBaseUri, this.localizer, valuesfromCard.IntroductionEntity));
                newHireNotification.Conversation = new ConversationAccount {
                    Id = valuesfromCard.IntroductionEntity.NewHireConversationId
                };
                await turnContext.Adapter.SendActivitiesAsync(turnContext, new Activity[] { (Activity)newHireNotification }, cancellationToken);

                await turnContext.SendActivityAsync(this.localizer.GetString("RequestMoreInfoNotificationText"));
            }
        }
Beispiel #16
0
 /// <summary>
 ///  This method is used to check if redirect url is invalid.
 /// </summary>
 /// <param name="qnaPairEntity">Qna pair entity that contains question and answer information.</param>
 /// <returns>Boolean value where true represent redirect url is invalid while false represent redirect url is valid.</returns>
 public static bool IsRedirectionUrlInvalid(AdaptiveSubmitActionData qnaPairEntity)
 {
     return(!string.IsNullOrEmpty(qnaPairEntity?.RedirectionUrl?.Trim()) &&
            !Regex.IsMatch(qnaPairEntity?.RedirectionUrl?.Trim(), Constants.ValidRedirectUrlPattern));
 }
Beispiel #17
0
 /// <summary>
 ///  This method is used to check if question or answer is empty.
 /// </summary>
 /// <param name="qnaPairEntity">Qna pair entity that contains question and answer information.</param>
 /// <returns>Boolean value where true represent question or answer is empty while false represent question or answer is not empty.</returns>
 public static bool IsQnaFieldsNullOrEmpty(AdaptiveSubmitActionData qnaPairEntity)
 {
     return(string.IsNullOrEmpty(qnaPairEntity?.UpdatedQuestion?.Trim()) || string.IsNullOrEmpty(qnaPairEntity?.Description?.Trim()));
 }
Beispiel #18
0
 /// <summary>
 ///  This method is used to check if image url is valid or not.
 /// </summary>
 /// <param name="qnaPairEntity">Qna pair entity that contains question and answer information.</param>
 /// <returns>Boolean value where true represent image url is valid while false represent image url in not valid.</returns>
 public static bool IsImageUrlInvalid(AdaptiveSubmitActionData qnaPairEntity)
 {
     return(!string.IsNullOrEmpty(qnaPairEntity?.ImageUrl?.Trim()) ? !Regex.IsMatch(qnaPairEntity?.ImageUrl?.Trim(), ValidImgUrlPattern) : false);
 }
Beispiel #19
0
        /// <summary>
        /// When OnTurn method receives a submit invoke activity on bot turn, it calls this method.
        /// </summary>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <param name="taskModuleRequest">Task module invoke request value payload.</param>
        /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
        /// <returns>A task that represents a task module response.</returns>
        /// <remarks>
        /// Reference link: https://docs.microsoft.com/en-us/dotnet/api/microsoft.bot.builder.teams.teamsactivityhandler.onteamstaskmodulesubmitasync?view=botbuilder-dotnet-stable.
        /// </remarks>
        protected override async Task <TaskModuleResponse> OnTeamsTaskModuleSubmitAsync(
            ITurnContext <IInvokeActivity> turnContext,
            TaskModuleRequest taskModuleRequest,
            CancellationToken cancellationToken)
        {
            try
            {
                turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext));
                this.RecordEvent(nameof(this.OnTeamsTaskModuleSubmitAsync), turnContext);

                var activity = turnContext.Activity;

                var         activityValue = JObject.Parse(activity.Value?.ToString())["data"].ToString();
                ScrumStatus scrumStatus   = JsonConvert.DeserializeObject <ScrumStatus>(activityValue);
                AdaptiveSubmitActionData adaptiveSubmitActionData = JsonConvert.DeserializeObject <AdaptiveSubmitActionData>(activityValue);

                if (scrumStatus == null || adaptiveSubmitActionData == null)
                {
                    this.logger.LogInformation("Value obtained from task module submit action is null");
                    return(this.cardHelper.GetTaskModuleErrorResponse(this.localizer.GetString("ErrorMessage"), this.localizer.GetString("BotFailureTitle")));
                }

                this.logger.LogInformation($"OnTeamsTaskModuleSubmitAsync: {JObject.Parse(activity.Value.ToString())["data"]}");

                switch (adaptiveSubmitActionData.AdaptiveActionType.ToUpperInvariant())
                {
                case Constants.UpdateStatusTaskModuleCommand:
                    string scrumStartActivityId = adaptiveSubmitActionData.ScrumStartActivityId;
                    string scrumMembers         = adaptiveSubmitActionData.ScrumMembers;
                    string scrumMasterId        = adaptiveSubmitActionData.ScrumMasterId;
                    if (string.IsNullOrWhiteSpace(scrumStatus.YesterdayTaskDescription) || string.IsNullOrWhiteSpace(scrumStatus.TodayTaskDescription))
                    {
                        return(this.cardHelper.GetScrumStatusValidationCardResponse(scrumMembers, scrumMasterId, scrumStartActivityId, scrumStatus));
                    }

                    this.logger.LogInformation($"Getting scrum master details which are active. ScrumMasterId: {scrumMasterId}");
                    var scrumMasterDetails = await this.scrumMasterStorageProvider.GetScrumMasterDetailsByScrumMasterIdAsync(scrumMasterId);

                    if (scrumMasterDetails == null || !scrumMasterDetails.IsActive)
                    {
                        this.logger.LogInformation($"Scrum master details for the scrum master id: {scrumMasterId} could not be found or scrum is inactive");
                        return(this.cardHelper.GetTaskModuleErrorResponse(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorScrumMasterDetailsNullOrInactive"), activity.From.Name), this.localizer.GetString("UpdateStatusTitle")));
                    }

                    var scrum = (await this.scrumStorageProvider.GetScrumByScrumStartActivityIdAsync(scrumStartActivityId)).FirstOrDefault();
                    await this.scrumHelper.SaveScrumStatusDetailsAsync(turnContext, scrumStatus, adaptiveSubmitActionData, scrum?.ScrumStartCardResponseId);

                    var membersActivityIdMap = JsonConvert.DeserializeObject <Dictionary <string, string> >(scrumMembers);
                    var updatedScrumSummary  = await this.scrumHelper.GetScrumSummaryAsync(scrumMasterId, scrum?.ScrumStartCardResponseId, membersActivityIdMap);

                    await this.cardHelper.UpdateSummaryCardAsync(updatedScrumSummary, scrum?.ScrumStartCardResponseId, scrumMasterId, scrumStartActivityId, membersActivityIdMap, scrumMasterDetails.TimeZone, turnContext, cancellationToken);

                    return(null);

                default:
                    return(null);
                }
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error while submitting task module.", SeverityLevel.Error);
                return(this.cardHelper.GetTaskModuleErrorResponse(this.localizer.GetString("ErrorMessage"), this.localizer.GetString("BotFailureTitle")));
            }
        }
Beispiel #20
0
 /// <summary>
 /// Html, question and answer fields validation.
 /// </summary>
 /// <param name="qnaPairEntity">Qna pair entity that contains question and answer information.</param>
 /// <returns>Return a question data object.</returns>
 public static AdaptiveSubmitActionData HtmlAndQnaEmptyValidation(AdaptiveSubmitActionData qnaPairEntity)
 {
     qnaPairEntity.IsHTMLPresent    = IsContainsHtml(qnaPairEntity);
     qnaPairEntity.IsQnaNullOrEmpty = IsQnaFieldsNullOrEmpty(qnaPairEntity);
     return(qnaPairEntity);
 }
Beispiel #21
0
 /// <summary>
 /// Stores scrum status details in storage.
 /// </summary>
 /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
 /// <param name="scrumStatus">Scrum status entity to be stored in table storage.</param>
 /// <param name="adaptiveSubmitActionData">Data submitted in task module.</param>
 /// <param name="summaryCardId">Scrum summary card id.</param>
 /// <returns>Returns success or failure on whether data is stored in storage.</returns>
 public async Task <bool> SaveScrumStatusDetailsAsync(ITurnContext <IInvokeActivity> turnContext, ScrumStatus scrumStatus, AdaptiveSubmitActionData adaptiveSubmitActionData, string summaryCardId)
 {
     scrumStatus = scrumStatus ?? throw new ArgumentNullException(nameof(scrumStatus));
     scrumStatus.MembersActivityIdMap = adaptiveSubmitActionData?.ScrumMembers;
     scrumStatus.SummaryCardId        = summaryCardId;
     scrumStatus.CreatedOn            = DateTime.UtcNow.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", CultureInfo.CurrentCulture);
     scrumStatus.Username             = turnContext?.Activity.From.Name;
     scrumStatus.AadObjectId          = turnContext.Activity.From.AadObjectId;
     return(await this.scrumStatusStorageProvider.CreateOrUpdateScrumStatusAsync(scrumStatus));
 }