Example #1
0
        /// <summary>
        /// Get general channel Id if scrum channel id does not exist.
        /// </summary>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <param name="scrumConfigurationDetails">scrum configuration details.</param>
        /// <returns>Returns general channel Id if scrum channel id does not exist.</returns>
        private async Task <string> GetValidChannelIdAsync(ITurnContext turnContext, ScrumConfiguration scrumConfigurationDetails)
        {
            var teamsChannelInfo = await TeamsInfo.GetTeamChannelsAsync(turnContext, scrumConfigurationDetails.TeamId, CancellationToken.None);

            var channelInfo = teamsChannelInfo.Where(channel => channel.Id.Equals(scrumConfigurationDetails.ChannelId, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();

            if (channelInfo == null)
            {
                scrumConfigurationDetails.ChannelId   = scrumConfigurationDetails.TeamId;
                scrumConfigurationDetails.ChannelName = Strings.GeneralChannel;

                List <ScrumConfiguration> scrumConfigurations = new List <ScrumConfiguration>();
                scrumConfigurations.Add(scrumConfigurationDetails);

                var saveResponse = await this.scrumConfigurationStorageProvider.StoreOrUpdateScrumConfigurationEntitiesAsync(scrumConfigurations);

                if (!saveResponse)
                {
                    this.logger.LogError("Error while saving scrum configuration details");
                }

                return(scrumConfigurationDetails.TeamId);
            }

            return(scrumConfigurationDetails.ChannelId);
        }
Example #2
0
        /// <summary>
        /// Method ends the existing scrum if running and then sends the start scrum card.
        /// </summary>
        /// <param name="scrumConfiguration">Scrum configuration details obtained from storage.</param>
        /// <returns>A task that ends the existing scrum and sends the start scrum card .</returns>
        public async Task ScrumStartActivityAsync(ScrumConfiguration scrumConfiguration)
        {
            try
            {
                string serviceUrl = scrumConfiguration.ServiceUrl;
                MicrosoftAppCredentials.TrustServiceUrl(serviceUrl);

                var conversationReference = new ConversationReference()
                {
                    ChannelId = TeamsBotFrameworkChannelId,
                    Bot       = new ChannelAccount()
                    {
                        Id = $"28:{this.microsoftAppCredentials.MicrosoftAppId}"
                    },
                    ServiceUrl   = serviceUrl,
                    Conversation = new ConversationAccount()
                    {
                        ConversationType = Constants.ChannelConversationType, IsGroup = true, Id = scrumConfiguration.ChannelId, TenantId = this.options.Value.TenantId
                    },
                };

                this.logger.LogInformation($"Sending start scrum card to channelId: {scrumConfiguration.ChannelId}");

                await RetryPolicy.ExecuteAsync(async() =>
                {
                    try
                    {
                        await((BotFrameworkAdapter)this.adapter).ContinueConversationAsync(
                            this.microsoftAppCredentials.MicrosoftAppId,
                            conversationReference,
                            async(conversationTurnContext, conversationCancellationToken) =>
                        {
                            bool isValidScrum = await this.EndExistingScrumAsync(conversationTurnContext, scrumConfiguration, conversationCancellationToken);
                            if (!isValidScrum)
                            {
                                this.logger.LogInformation("Error while ending the existing scrum.");
                                await conversationTurnContext.SendActivityAsync(this.localizer.GetString(this.localizer.GetString("ErrorMessage")));
                            }

                            await this.SendScrumStartCardAsync(conversationTurnContext, scrumConfiguration, conversationCancellationToken);
                        },
                            CancellationToken.None);
                    }
                    catch (Exception ex)
                    {
                        this.logger.LogError(ex, "Error while performing retry logic to send scrum start card.");
                        throw;
                    }
                });
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error while sending start scrum to channel from background service.");
            }
        }
Example #3
0
        /// <summary>
        /// Method to validate the existing scrum if already running.
        /// </summary>
        /// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
        /// <param name="scrumConfiguration">Values obtained from ScrumConfiguration table.</param>
        /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
        /// <returns>A task that ends the existing scrum.</returns>
        private async Task <bool> EndExistingScrumAsync(ITurnContext turnContext, ScrumConfiguration scrumConfiguration, CancellationToken cancellationToken)
        {
            // If previous scrum is already running end that and refresh scrum start card.
            var scrumDetails = await this.scrumStorageProvider.GetScrumsByScrumTeamConfigIdAsync(scrumConfiguration.ScrumTeamConfigId, scrumConfiguration.AadGroupId);

            if (scrumDetails == null)
            {
                return(false);
            }

            foreach (var scrumDetail in scrumDetails)
            {
                if (!scrumDetail.IsCompleted)
                {
                    scrumDetail.IsCompleted = true;
                    var savedData = await this.scrumStorageProvider.CreateOrUpdateScrumAsync(scrumDetail);

                    if (!savedData)
                    {
                        this.logger.LogInformation($"Error while updating scrim table from storage for scrumTeamConfigId : {scrumConfiguration.ScrumTeamConfigId}");
                        return(false);
                    }

                    this.logger.LogInformation($"Getting scrum configuration details which are active. ScrumTeamConfigId: {scrumDetail.ScrumTeamConfigId}");
                    var scrumConfigurationDetails = await this.scrumConfigurationStorageProvider.GetScrumConfigurationDetailByScrumTeamConfigIdAsync(scrumDetail.ScrumTeamConfigId, scrumDetail.AadGroupId);

                    if (scrumConfigurationDetails == null || !scrumConfigurationDetails.IsActive)
                    {
                        return(false);
                    }

                    // End the existing running scrum and refresh start card with end scrum.
                    var scrumMembers         = scrumDetail.MembersActivityIdMap;
                    var membersActivityIdMap = JsonConvert.DeserializeObject <Dictionary <string, string> >(scrumMembers);
                    var updatedScrumSummary  = await this.scrumHelper.GetScrumSummaryAsync(scrumDetail.ScrumTeamConfigId, scrumConfiguration.AadGroupId, scrumDetail.ScrumStartCardResponseId, membersActivityIdMap);

                    if (updatedScrumSummary == null)
                    {
                        this.logger.LogInformation($"No data obtained from storage to update summary card for scrumStartCardActivityId : {scrumDetail.ScrumStartCardResponseId}");
                        continue;
                    }

                    await this.cardHelper.UpdateSummaryCardWithEndScrumAsync(updatedScrumSummary, scrumDetail, scrumConfiguration, membersActivityIdMap, scrumConfigurationDetails.TimeZone, turnContext, cancellationToken);

                    this.logger.LogInformation($"Ended existing running scrum for {scrumDetail.ThreadConversationId}");
                }
            }

            return(true);
        }
Example #4
0
        /// <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="scrumConfiguration">Scrum configuration 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, ScrumConfiguration scrumConfiguration, Dictionary <string, string> membersActivityIdMap, string timeZone, ITurnContext turnContext, CancellationToken cancellationToken)
        {
            var activitySummary  = MessageFactory.Attachment(ScrumCard.GetScrumStartCard(scrumSummary, membersActivityIdMap, scrumConfiguration?.ScrumTeamConfigId, scrum?.ScrumStartActivityId, this.localizer, timeZone));
            var teamsChannelInfo = await TeamsInfo.GetTeamChannelsAsync(turnContext, scrumConfiguration.TeamId, CancellationToken.None);

            var channelInfo = teamsChannelInfo.Where(channel => channel.Id.Equals(scrumConfiguration.ChannelId, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();

            if (channelInfo == null)
            {
                return(false);
            }

            activitySummary.Id           = scrum.ScrumStartCardResponseId;
            activitySummary.Conversation = new ConversationAccount
            {
                Id = $"{scrumConfiguration.ChannelId};messageid={scrum.ScrumStartCardResponseId}",
            };
            this.logger.LogInformation($"Trail card updated for: {scrum.ThreadConversationId} summaryCardActivityId: {scrum.ScrumStartCardResponseId}");
            await turnContext?.UpdateActivityAsync(activitySummary, cancellationToken);

            turnContext.Activity.Conversation = new ConversationAccount
            {
                Id = $"{scrumConfiguration.ChannelId};messageid={scrum.ScrumStartCardResponseId}",
            };
            await turnContext.SendActivityAsync(this.localizer.GetString("SuccessMessageAfterEndingScrum"), cancellationToken : cancellationToken);

            return(true);
        }
Example #5
0
        /// <summary>
        ///  Create a new scrum from the input.
        /// </summary>
        /// <param name="scrumStartCardResponseId">Activity id of scrum summary card.</param>
        /// <param name="scrumCardId">Activity Id of scrum card.</param>
        /// <param name="members">JSON serialized member and activity mapping.</param>
        /// <param name="scrumConfiguration">An instance of scrum configuration details.</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 that represents the work queued to execute.</returns>
        private async Task CreateScrumAsync(string scrumStartCardResponseId, string scrumCardId, string members, ScrumConfiguration scrumConfiguration, ITurnContext turnContext, CancellationToken cancellationToken)
        {
            string conversationId = turnContext.Activity.Conversation.Id;

            try
            {
                Scrum scrumEntity = new Scrum
                {
                    ThreadConversationId     = conversationId,
                    ScrumStartActivityId     = scrumCardId,
                    IsCompleted              = false,
                    MembersActivityIdMap     = members,
                    ScrumStartCardResponseId = scrumStartCardResponseId,
                    ScrumTeamConfigId        = scrumConfiguration.ScrumTeamConfigId,
                    ScrumId     = conversationId,
                    ChannelName = scrumConfiguration.ChannelName,
                    TeamId      = scrumConfiguration.TeamId,
                    CreatedOn   = DateTime.UtcNow.ToString(Constants.Rfc3339DateTimeFormat, CultureInfo.InvariantCulture),
                    AadGroupId  = scrumConfiguration.AadGroupId,
                };
                var savedData = await this.scrumStorageProvider.CreateOrUpdateScrumAsync(scrumEntity);

                if (!savedData)
                {
                    await turnContext.SendActivityAsync(this.localizer.GetString("ErrorSavingScrumData"), cancellationToken : cancellationToken);
                }
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, $"For {conversationId}: saving scrum data to table storage failed. {ex.Message}", SeverityLevel.Error);
                await turnContext.SendActivityAsync(this.localizer.GetString("ErrorMessage"), cancellationToken : cancellationToken);

                throw;
            }
        }
Example #6
0
        /// <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="scrumConfiguration">scrum configuration 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, ScrumConfiguration scrumConfiguration, CancellationToken cancellationToken)
        {
            try
            {
                string scrumTeamConfigId = scrumConfiguration.ScrumTeamConfigId;
                if (scrumTeamConfigId != null)
                {
                    this.logger.LogInformation($"Scrum start for ID: {scrumTeamConfigId}");
                    var scrumSummary = await this.scrumHelper.GetScrumSummaryAsync(scrumTeamConfigId, scrumConfiguration.AadGroupId);

                    if (scrumSummary == null)
                    {
                        this.logger.LogInformation($"scrum configuration details are deleted from storage.");
                        await turnContext.SendActivityAsync(string.Format(CultureInfo.CurrentCulture, this.localizer.GetString("ErrorScrumDeleted"), scrumConfiguration.ScrumTeamName), cancellationToken : cancellationToken);

                        return;
                    }

                    var scrumStartActivityId = Guid.NewGuid().ToString();

                    // Fetching the members list based on the teams id:
                    turnContext.Activity.Conversation.Id = scrumConfiguration.TeamId;
                    var scrumMembers = await this.GetValidMembersInScrumAsync(turnContext, scrumConfiguration.UserPrincipalNames, cancellationToken);

                    if (scrumMembers == null)
                    {
                        this.logger.LogInformation($"No scrum members are available to provide the scrum status");
                        await turnContext.SendActivityAsync(this.localizer.GetString("ErrorNoScrumMembersPresent"), cancellationToken : cancellationToken);

                        return;
                    }

                    var    membersActivityIdMap = this.GetActivityIdOfMembersInScrum(scrumMembers);
                    string membersList          = JsonConvert.SerializeObject(membersActivityIdMap);

                    // Mentioning the participants involved in the scrum
                    var mentionActivity = this.GetMentionsActivity(scrumMembers);

                    // Check if channel exists. If channel doesn't exist then scrum card will be sent in General channel.
                    scrumConfiguration.ChannelId = await this.GetValidChannelIdAsync(turnContext, scrumConfiguration);

                    // Send the start scrum card
                    turnContext.Activity.Conversation.Id = scrumConfiguration.ChannelId;
                    var attachment                 = ScrumCard.GetScrumStartCard(scrumSummary, membersActivityIdMap, scrumTeamConfigId, scrumStartActivityId, this.localizer, scrumConfiguration.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 = $"{scrumConfiguration.ChannelId};messageid={scrumStartActivityResponse.Id}",
                    };
                    await turnContext.SendActivityAsync(mentionActivity, cancellationToken);

                    await this.CreateScrumAsync(scrumStartActivityResponse.Id, scrumStartActivityId, membersList, scrumConfiguration, 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;
            }
        }