/// <summary> /// Update the first trail card with user details. /// </summary> /// <param name="scrumSummary">Scrum summary information to be shown on card.</param> /// <param name="scrum">Scrum details.</param> /// <param name="scrumMaster">Scrum master details.</param> /// <param name="membersActivityIdMap">Members id who are part of the scrum.</param> /// <param name="timeZone">Used to convert scrum start time as per specified time zone.</param> /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>A task of type bool where true represents summary card updated successfully while false indicates failure in updating the summary card.</returns> public async Task <bool> UpdateSummaryCardWithEndScrumAsync(ScrumSummary scrumSummary, Scrum scrum, ScrumMaster scrumMaster, Dictionary <string, string> membersActivityIdMap, string timeZone, ITurnContext turnContext, CancellationToken cancellationToken) { if (scrumSummary != null) { var activitySummary = MessageFactory.Attachment(ScrumCard.GetScrumStartCard(scrumSummary, membersActivityIdMap, scrumMaster?.ScrumMasterId, scrum?.ScrumStartActivityId, this.localizer, timeZone)); var teamsChannelInfo = await TeamsInfo.GetTeamChannelsAsync(turnContext, scrumMaster.TeamId, CancellationToken.None); var channelInfo = teamsChannelInfo.Where(channel => channel.Id.Equals(scrumMaster.ChannelId, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); if (channelInfo != null) { activitySummary.Id = scrum.ScrumStartCardResponseId; activitySummary.Conversation = new ConversationAccount { Id = $"{scrumMaster.ChannelId};messageid={scrum.ScrumStartCardResponseId}", }; this.logger.LogInformation($"Trail card updated for: {scrum.ThreadConversationId} summaryCardActivityId: {scrum.ScrumStartCardResponseId}"); await turnContext?.UpdateActivityAsync(activitySummary, cancellationToken); } return(true); } else { this.logger.LogInformation($"No data obtained from storage to update summary card for summaryCardActivityId : {scrum?.ScrumStartCardResponseId}"); return(false); } }
/// <summary> /// Get scrum details adaptive card response. /// </summary> /// <param name="scrumMembers">Members who are part of the scrum.</param> /// <param name="scrumTeamConfigId">Unique identifier for scrum team configuration details.</param> /// <param name="scrumStartActivityId">Scrum start card activity Id.</param> /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Returns scrum details card to be displayed in task module.</returns> public async Task <TaskModuleResponse> GetScrumDetailsCardResponseAsync(string scrumMembers, string scrumTeamConfigId, string scrumStartActivityId, ITurnContext <IInvokeActivity> turnContext, CancellationToken cancellationToken) { turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext)); var activity = turnContext.Activity; this.logger.LogInformation($"Getting information about scrum with summaryCardId: {scrumStartActivityId}"); string aadGroupId = await this.activityHelper.GetTeamAadGroupIdAsync(turnContext, cancellationToken); var scrumSummary = (await this.scrumStorageProvider.GetScrumsByScrumStartActivityIdAsync(scrumStartActivityId, aadGroupId)).FirstOrDefault(); if (scrumSummary == null) { this.logger.LogInformation($"Value obtained from storage for scrum summary is null."); return(this.GetTaskModuleErrorResponse(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorScrumDoesNotExist"), activity.From.Name), this.localizer.GetString("ScrumDetailsTitle"))); } this.logger.LogInformation($"Received information about scrum with scrumId: {scrumSummary.ScrumId}"); var scrumStatusDetails = await this.scrumStatusStorageProvider.GetScrumStatusBySummaryCardIdAsync(scrumSummary.ScrumStartCardResponseId, aadGroupId); var membersActivityIdMap = JsonConvert.DeserializeObject <Dictionary <string, string> >(scrumMembers); var updatedScrumSummary = await this.scrumHelper.GetScrumSummaryAsync(scrumTeamConfigId, aadGroupId, scrumSummary.ScrumStartCardResponseId, membersActivityIdMap); if (scrumStatusDetails == null || updatedScrumSummary == null) { this.logger.LogInformation($"Value obtained from storage for scrum is null."); return(this.GetTaskModuleErrorResponse(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorScrumDoesNotExist"), activity.From.Name), this.localizer.GetString("ScrumDetailsTitle"))); } var scrumConfigurationDetails = await this.scrumConfigurationStorageProvider.GetScrumConfigurationDetailByScrumTeamConfigIdAsync(scrumSummary.ScrumTeamConfigId, scrumSummary.AadGroupId); activity.Conversation.Id = scrumConfigurationDetails.TeamId; var validScrumMembers = new List <TeamsChannelAccount>(); string continuationToken = null; do { var currentPage = await TeamsInfo.GetPagedMembersAsync(turnContext, 100, continuationToken, cancellationToken); continuationToken = currentPage.ContinuationToken; validScrumMembers.AddRange(currentPage.Members.Where(member => membersActivityIdMap.ContainsKey(member.Id))); }while (continuationToken != null); return(new TaskModuleResponse { Task = new TaskModuleContinueResponse { Value = new TaskModuleTaskInfo() { Card = ScrumCard.GetScrumDetailsCard(scrumStatusDetails, updatedScrumSummary, validScrumMembers, this.appBaseUri, this.localizer, scrumConfigurationDetails.TimeZone), Height = TaskModuleHeight, Width = TaskModuleWidth, Title = this.localizer.GetString("ScrumDetailsTitle"), }, }, }); }
/// <summary> /// Method that sends the start scrum card to the channel. /// </summary> /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param> /// <param name="scrumMaster">Scrum master details obtained from storage.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>A task that sends the start scrum card.</returns> private async Task SendScrumStartCardAsync(ITurnContext turnContext, ScrumMaster scrumMaster, CancellationToken cancellationToken) { try { string scrumMasterId = scrumMaster.ScrumMasterId; if (scrumMasterId != null) { this.logger.LogInformation($"Scrum start for ID: {scrumMasterId}"); var scrumSummary = await this.scrumHelper.GetScrumSummaryAsync(scrumMasterId); if (scrumSummary == null) { this.logger.LogInformation($"Scrum master details are deleted from storage."); await turnContext.SendActivityAsync(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorScrumDeleted"), scrumMaster.TeamName), cancellationToken : cancellationToken); return; } // scrumSummary.ScrumStartTime = scrumMaster.StartTime; var scrumStartActivityId = Guid.NewGuid().ToString(); // Fetching the members list based on the teams id: turnContext.Activity.Conversation.Id = scrumMaster.TeamId; var membersActivityIdMap = await this.GetActivityIdOfMembersInScrumAsync(turnContext, scrumMaster, cancellationToken); string membersList = JsonConvert.SerializeObject(membersActivityIdMap); // Mentioning the participants involved in the scrum var mentionActivity = await this.GetMentionsActivityAsync(turnContext, scrumMaster, cancellationToken); // Check if channel exists. If channel doesn't exist then scrum card will be sent in General channel. scrumMaster.ChannelId = await this.GetValidChannelIdAsync(turnContext, scrumMaster); // Send the start scrum card turnContext.Activity.Conversation.Id = scrumMaster.ChannelId; var attachment = ScrumCard.GetScrumStartCard(scrumSummary, membersActivityIdMap, scrumMasterId, scrumStartActivityId, this.localizer, scrumMaster.TimeZone); var scrumStartActivity = MessageFactory.Attachment(attachment); var scrumStartActivityResponse = await turnContext.SendActivityAsync(scrumStartActivity, cancellationToken); // Update the conversation id to send mentioned participants as reply to scrum start card. turnContext.Activity.Conversation = new ConversationAccount { Id = $"{scrumMaster.ChannelId};messageid={scrumStartActivityResponse.Id}", }; await turnContext.SendActivityAsync(mentionActivity, cancellationToken); await this.CreateScrumAsync(scrumStartActivityResponse.Id, scrumStartActivityId, membersList, scrumMaster, turnContext, cancellationToken); this.logger.LogInformation($"Scrum start details saved to table storage for: {turnContext.Activity.Conversation.Id}"); } } catch (Exception ex) { this.logger.LogError(ex, $"Start scrum failed for {turnContext.Activity.Conversation.Id}: {ex.Message}", SeverityLevel.Error); throw; } }
/// <summary> /// Get end scrum summary card activity. /// </summary> /// <param name="scrum">Scrum details of the running scrum.</param> /// <param name="conversationId">Conversation id for updating the conversation.</param> /// <param name="scrumMembers">Members who are part of the scrum.</param> /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Returns end scrum summary card activity to be updated in team.</returns> public async Task <IActivity> GetEndScrumSummaryActivityAsync(Scrum scrum, string conversationId, string scrumMembers, ITurnContext <IMessageActivity> turnContext, CancellationToken cancellationToken) { var activity = turnContext?.Activity; if (scrum == null || scrum.IsCompleted == true) { await turnContext.SendActivityAsync(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorScrumDoesNotExist"), activity.From.Name), cancellationToken : cancellationToken); return(null); } var activityId = this.GetActivityIdToMatch(scrum.MembersActivityIdMap, activity.From.Id); if (string.IsNullOrEmpty(activityId)) { await turnContext.SendActivityAsync(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorUserIsNotPartOfRunningScrumAndTryToEndScrum"), activity.From.Name), cancellationToken : cancellationToken); this.logger.LogInformation($"Member who is updating the scrum is not the part of scrum for: {conversationId}"); return(null); } if (string.IsNullOrEmpty(scrumMembers)) { this.logger.LogInformation("Scrum members detail could not be found"); await turnContext.SendActivityAsync(this.localizer.GetString("ErrorMessage"), cancellationToken : cancellationToken); return(null); } var membersActivityIdMap = JsonConvert.DeserializeObject <Dictionary <string, string> >(scrumMembers); scrum.IsCompleted = true; scrum.ThreadConversationId = conversationId; var savedData = await this.scrumStorageProvider.CreateOrUpdateScrumAsync(scrum); if (!savedData) { this.logger.LogError("Error in saving scrum information in storage."); await turnContext.SendActivityAsync(this.localizer.GetString("ErrorSavingScrumData"), cancellationToken : cancellationToken); return(null); } var scrumStartCardResponseId = scrum.ScrumStartCardResponseId; var scrumMaster = await this.scrumMasterStorageProvider.GetScrumMasterDetailsByScrumMasterIdAsync(scrum.ScrumMasterId); var updatedScrumSummary = await this.scrumHelper.GetScrumSummaryAsync(scrum.ScrumMasterId, scrumStartCardResponseId, membersActivityIdMap); var scrumStartCard = ScrumCard.GetScrumStartCard(updatedScrumSummary, membersActivityIdMap, scrum.ScrumMasterId, scrum.ScrumStartActivityId, this.localizer, scrumMaster.TimeZone); var activitySummary = MessageFactory.Attachment(scrumStartCard); activitySummary.Id = scrumStartCardResponseId; activitySummary.Conversation = activity.Conversation; return(activitySummary); }
/// <summary> /// Update the scrum summary card with updated details. /// </summary> /// <param name="scrumSummary">Scrum summary information to be shown on card.</param> /// <param name="summaryCardActivityId">Summary card activity id.</param> /// <param name="scrumTeamConfigId">Unique identifier for scrum team configuration details.</param> /// <param name="scrumStartActivityId">Scrum start card activity id</param> /// <param name="membersActivityIdMap">Members id who are part of the scrum.</param> /// <param name="timeZone">Used to convert scrum start time as per specified time zone.</param> /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>A task of type boolean where true represents summary card updated successfully while false indicates failure in updating the summary card.</returns> public async Task <bool> UpdateSummaryCardAsync(ScrumSummary scrumSummary, string summaryCardActivityId, string scrumTeamConfigId, string scrumStartActivityId, Dictionary <string, string> membersActivityIdMap, string timeZone, ITurnContext turnContext, CancellationToken cancellationToken) { var activitySummary = MessageFactory.Attachment(ScrumCard.GetScrumStartCard(scrumSummary, membersActivityIdMap, scrumTeamConfigId, scrumStartActivityId, this.localizer, timeZone)); activitySummary.Id = summaryCardActivityId; activitySummary.Conversation = turnContext?.Activity.Conversation; this.logger.LogInformation($"Trail card updated for: {turnContext.Activity.Conversation.Id} summaryCardActivityId: {summaryCardActivityId}"); await turnContext.UpdateActivityAsync(activitySummary, cancellationToken); return(true); }
/// <summary> /// Get scrum status validation card with errors listed. /// </summary> /// <param name="membersActivityIdMap">Members id who are part of the scrum.</param> /// <param name="scrumMasterId">Unique identifier for scrum master details.</param> /// <param name="scrumStartActivityId">Scrum start card activity Id.</param> /// <param name="scrumStatus">Scrum status details filled by user.</param> /// <returns>Returns scrum status validation card to be sent in response.</returns> public TaskModuleResponse GetScrumStatusValidationCardResponse(string membersActivityIdMap, string scrumMasterId, string scrumStartActivityId, ScrumStatus scrumStatus) { return(new TaskModuleResponse { Task = new TaskModuleContinueResponse { Value = new TaskModuleTaskInfo() { Card = ScrumCard.GetScrumStatusUpdateCard(membersActivityIdMap, scrumMasterId, scrumStartActivityId, scrumStatus, this.localizer, string.IsNullOrWhiteSpace(scrumStatus?.YesterdayTaskDescription), string.IsNullOrWhiteSpace(scrumStatus?.TodayTaskDescription)), Height = TaskModuleHeight, Width = TaskModuleWidth, Title = this.localizer.GetString("UpdateStatusTitle"), }, }, }); }
/// <summary> /// Get scrum status update adaptive card in response to be filled by scrum member. /// </summary> /// <param name="membersActivityIdMap">Members id who are part of the scrum.</param> /// <param name="scrumMasterId">Unique identifier for scrum master details.</param> /// <param name="scrumStartActivityId">Scrum start card activity Id.</param> /// <param name="scrumStatus">Scrum status details.</param> /// <returns>Returns scrum status update card to be displayed in task module.</returns> public TaskModuleResponse GetScrumStatusUpdateCardResponse(string membersActivityIdMap, string scrumMasterId, string scrumStartActivityId, ScrumStatus scrumStatus) { return(new TaskModuleResponse { Task = new TaskModuleContinueResponse { Value = new TaskModuleTaskInfo() { Card = ScrumCard.GetScrumStatusUpdateCard(membersActivityIdMap, scrumMasterId, scrumStartActivityId, scrumStatus, this.localizer), Height = TaskModuleHeight, Width = TaskModuleWidth, Title = this.localizer.GetString("UpdateStatusTitle"), }, }, }); }
/// <summary> /// Get end scrum summary card activity. /// </summary> /// <param name="scrum">Scrum details of the running scrum.</param> /// <param name="conversationId">Conversation id for updating the conversation.</param> /// <param name="scrumMembers">Members who are part of the scrum.</param> /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Returns end scrum summary card activity to be updated in team.</returns> public async Task <IActivity> GetEndScrumSummaryActivityAsync(Scrum scrum, string conversationId, string scrumMembers, ITurnContext <IMessageActivity> turnContext, CancellationToken cancellationToken) { turnContext = turnContext ?? throw new ArgumentNullException(nameof(turnContext)); var activity = turnContext.Activity; if (scrum == null) { return(null); } var activityId = this.CheckUserExistsInScrumMembers(scrum.MembersActivityIdMap, activity.From.Id); if (string.IsNullOrEmpty(activityId)) { await turnContext.SendActivityAsync(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorUserIsNotPartOfRunningScrumAndTryToEndScrum"), activity.From.Name), cancellationToken : cancellationToken); this.logger.LogInformation($"Member who is updating the scrum is not the part of scrum for: {conversationId}"); return(null); } if (string.IsNullOrEmpty(scrumMembers)) { this.logger.LogInformation("Scrum members detail could not be found"); await turnContext.SendActivityAsync(this.localizer.GetString("ErrorMessage"), cancellationToken : cancellationToken); return(null); } var membersActivityIdMap = JsonConvert.DeserializeObject <Dictionary <string, string> >(scrumMembers); var scrumStartCardResponseId = scrum.ScrumStartCardResponseId; string aadGroupId = await this.GetTeamAadGroupIdAsync(turnContext, cancellationToken); var scrumConfiguration = await this.scrumConfigurationStorageProvider.GetScrumConfigurationDetailByScrumTeamConfigIdAsync(scrum.ScrumTeamConfigId, aadGroupId); var updatedScrumSummary = await this.scrumHelper.GetScrumSummaryAsync(scrum.ScrumTeamConfigId, scrumConfiguration.AadGroupId, scrumStartCardResponseId, membersActivityIdMap); var scrumStartCard = ScrumCard.GetScrumStartCard(updatedScrumSummary, membersActivityIdMap, scrum.ScrumTeamConfigId, scrum.ScrumStartActivityId, this.localizer, scrumConfiguration.TimeZone); var activitySummary = MessageFactory.Attachment(scrumStartCard); activitySummary.Id = scrumStartCardResponseId; activitySummary.Conversation = activity.Conversation; return(activitySummary); }