/// <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, }); }
/// <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)); }
/// <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); }
/// <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; }
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)); }
/// <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; } }
/// <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()); } }
/// <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"))); } }
/// <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; } } } }
/// <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")); } }
/// <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)); }
/// <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())); }
/// <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); }
/// <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"))); } }
/// <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); }
/// <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)); }