/// <summary> /// Notify a pairup. /// </summary> /// <param name="teamModel">DB team model info.</param> /// <param name="teamName">MS-Teams team name</param> /// <param name="pair">The pairup</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Number of users notified successfully</returns> private async Task <int> NotifyPairAsync(TeamInstallInfo teamModel, string teamName, Tuple <ChannelAccount, ChannelAccount> pair, CancellationToken cancellationToken) { // Get the default culture info to use in resource files. var cultureName = CloudConfigurationManager.GetSetting("DefaultCulture"); Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(cultureName); this.telemetryClient.TrackTrace($"Sending pairup notification to {pair.Item1.Id} and {pair.Item2.Id}"); var teamsPerson1 = JObject.FromObject(pair.Item1).ToObject <TeamsChannelAccount>(); var teamsPerson2 = JObject.FromObject(pair.Item2).ToObject <TeamsChannelAccount>(); // Fill in person2's info in the card for person1 var cardForPerson1 = PairUpNotificationAdaptiveCard.GetCard(teamName, teamsPerson1, teamsPerson2, this.botDisplayName); // Fill in person1's info in the card for person2 var cardForPerson2 = PairUpNotificationAdaptiveCard.GetCard(teamName, teamsPerson2, teamsPerson1, this.botDisplayName); // Send notifications and return the number that was successful var notifyResults = await Task.WhenAll( this.conversationHelper.NotifyUserAsync(this.botAdapter, teamModel.ServiceUrl, teamModel.TeamId, MessageFactory.Attachment(cardForPerson1), teamsPerson1, teamModel.TenantId, cancellationToken), this.conversationHelper.NotifyUserAsync(this.botAdapter, teamModel.ServiceUrl, teamModel.TeamId, MessageFactory.Attachment(cardForPerson2), teamsPerson2, teamModel.TenantId, cancellationToken)); return(notifyResults.Count(wasNotified => wasNotified)); }
/// <summary> /// Save information about the team from which the bot was removed. /// </summary> /// <param name="serviceUrl">The service url</param> /// <param name="teamId">The team id</param> /// <param name="tenantId">The tenant id</param> /// <returns>Tracking task</returns> public Task SaveRemoveFromTeam(string serviceUrl, string teamId, string tenantId) { var teamInstallInfo = new TeamInstallInfo { TeamId = teamId, TenantId = tenantId, }; return(this.dataProvider.UpdateTeamInstallStatusAsync(teamInstallInfo, false)); }
/// <summary> /// Save information about the team to which the bot was added. /// </summary> /// <param name="serviceUrl">The service url</param> /// <param name="teamId">The team id</param> /// <param name="tenantId">The tenant id</param> /// <param name="botInstaller">Person that has added the bot to the team</param> /// <returns>Tracking task</returns> public Task SaveAddedToTeam(string serviceUrl, string teamId, string tenantId, string botInstaller) { var teamInstallInfo = new TeamInstallInfo { ServiceUrl = serviceUrl, TeamId = teamId, TenantId = tenantId, InstallerName = botInstaller }; return(this.dataProvider.UpdateTeamInstallStatusAsync(teamInstallInfo, true)); }
/// <summary> /// Updates team installation status in store. If the bot is installed, the info is saved, otherwise info for the team is deleted. /// </summary> /// <param name="team">The team installation info</param> /// <param name="installed">Value that indicates if bot is installed</param> /// <returns>Tracking task</returns> public async Task TeamInstallUpdate(TeamInstallInfo team, bool installed) { await this.EnsureInitializedAsync(); if (installed) { var response = await this.documentClient.UpsertDocumentAsync(this.teamsCollection.SelfLink, team); } else { var documentUri = UriFactory.CreateDocumentUri(this.database.Id, this.teamsCollection.Id, team.Id); var response = await this.documentClient.DeleteDocumentAsync(documentUri, new RequestOptions { PartitionKey = new PartitionKey(team.Id) }); } }
private static async Task <List <ChannelAccount> > GetOptedInUsers(TeamInstallInfo teamInfo) { var optedInUsers = new List <ChannelAccount>(); var members = await GetTeamMembers(teamInfo.ServiceUrl, teamInfo.TeamId, teamInfo.TenantId); foreach (var member in members) { var optInStatus = MeetupBotDataProvider.GetUserOptInStatus(teamInfo.TenantId, member.ObjectId); if (optInStatus == null || optInStatus.OptedIn) { optedInUsers.Add(member); } } return(optedInUsers); }
private static async Task <List <TeamsChannelAccount> > GetOptedInUsers(TeamInstallInfo teamInfo, Dictionary <string, UserOptInInfo> optInInfo) { var optedInUsers = new List <TeamsChannelAccount>(); var members = await GetTeamMembers(teamInfo.ServiceUrl, teamInfo.TeamId, teamInfo.TenantId); foreach (var member in members) { var isBot = string.IsNullOrEmpty(member.Surname); optInInfo.TryGetValue(member.ObjectId, out UserOptInInfo optInStatus); if ((optInStatus == null || optInStatus.OptedIn) && !isBot) { optedInUsers.Add(member); } } return(optedInUsers); }
/// <summary> /// Notify a pairup. /// </summary> /// <param name="teamModel">DB team model info.</param> /// <param name="teamName">MS-Teams team name</param> /// <param name="pair">The pairup</param> /// <param name="cancellationToken">Propagates notification that operations should be canceled.</param> /// <returns>Number of users notified successfully</returns> private async Task <int> NotifyPairAsync(TeamInstallInfo teamModel, string teamName, Tuple <ChannelAccount, ChannelAccount> pair, CancellationToken cancellationToken) { this.telemetryClient.TrackTrace($"Sending pairup notification to {pair?.Item1?.Id} and {pair?.Item2?.Id}"); var teamsPerson1 = JObject.FromObject(pair.Item1).ToObject <TeamsChannelAccount>(); var teamsPerson2 = JObject.FromObject(pair.Item2).ToObject <TeamsChannelAccount>(); // Fill in person2's info in the card for person1 var cardForPerson1 = PairUpNotificationAdaptiveCard.GetCard(teamName, teamsPerson1, teamsPerson2, this.botDisplayName); // Fill in person1's info in the card for person2 var cardForPerson2 = PairUpNotificationAdaptiveCard.GetCard(teamName, teamsPerson2, teamsPerson1, this.botDisplayName); // Send notifications and return the number that was successful var notifyResults = await Task.WhenAll( this.conversationHelper.NotifyUserAsync(this.botAdapter, teamModel.ServiceUrl, teamModel.TeamId, MessageFactory.Attachment(cardForPerson1), teamsPerson1, teamModel.TenantId, cancellationToken), this.conversationHelper.NotifyUserAsync(this.botAdapter, teamModel.ServiceUrl, teamModel.TeamId, MessageFactory.Attachment(cardForPerson2), teamsPerson2, teamModel.TenantId, cancellationToken)); return(notifyResults.Count(wasNotified => wasNotified)); }
/// <summary> /// Gets user info from the data store, or else generates it /// </summary> /// <param name="userId">User object Id</param> /// <param name="teamModel">DB team model info</param> /// <returns>List of pairs</returns> private async Task <UserInfo> GetOrCreateUserInfoAsync(string userId, TeamInstallInfo teamModel) { this.telemetryClient.TrackTrace($"Getting info for {userId}"); UserInfo userInfo = await this.dataProvider.GetUserInfoAsync(userId); if (userInfo == null) { this.telemetryClient.TrackTrace($"{userId} info is not saved, generating now"); userInfo = new UserInfo() { TenantId = teamModel.TenantId, UserId = userId, OptedIn = true, ServiceUrl = teamModel.ServiceUrl, RecentPairUps = new List <UserInfo>(), }; } return(userInfo); }
private async Task <List <ChannelAccount> > GetOptedInUsers(ConnectorClient connectorClient, TeamInstallInfo teamInfo) { // Pull the roster of specified team and then remove everyone who has opted out explicitly var members = await connectorClient.Conversations.GetConversationMembersAsync(teamInfo.TeamId); this.telemetryClient.TrackTrace($"Found {members.Count} in team {teamInfo.TeamId}"); var tasks = members.Select(m => this.dataProvider.GetUserInfoAsync(m.AsTeamsChannelAccount().ObjectId)); var results = await Task.WhenAll(tasks); return(members .Zip(results, (member, userInfo) => ((userInfo == null) || userInfo.OptedIn) ? member : null) .Where(m => m != null) .ToList()); }
/// <summary> /// Get list of opted in users to start matching process /// </summary> /// <param name="dbMembersLookup">Lookup of DB users opt-in status</param> /// <param name="teamInfo">The team that the bot has been installed to</param> /// <returns>Opted in users' channels</returns> private async Task <List <ChannelAccount> > GetOptedInUsersAsync(Dictionary <string, bool> dbMembersLookup, TeamInstallInfo teamInfo) { // Pull the roster of specified team and then remove everyone who has opted out explicitly var members = await this.conversationHelper.GetTeamMembers(this.botAdapter, teamInfo); this.telemetryClient.TrackTrace($"Found {members.Count} in team {teamInfo.TeamId}"); return(members .Where(member => member != null) .Where(member => { var memberObjectId = this.GetChannelUserObjectId(member); return !dbMembersLookup.ContainsKey(memberObjectId) || dbMembersLookup[memberObjectId]; }) .ToList()); }
private async Task <List <ChannelAccount> > GetOptedInUsers(ConnectorClient connectorClient, TeamInstallInfo teamInfo) { // Pull the roster of specified team and then remove everyone who has opted out explicitly var members = await connectorClient.Conversations.GetConversationMembersAsync(teamInfo.TeamId); this.telemetryClient.TrackTrace($"Found {members.Count} in team {teamInfo.TeamId}"); var tasks = members.Select(m => this.dataProvider.GetUserInfoAsync(m.AsTeamsChannelAccount().ObjectId)); var results = await Task.WhenAll(tasks); //---------testing------------ this.telemetryClient.TrackTrace($"Testing, before filtering {members.Count} in team {teamInfo.TeamId}"); foreach (var m in members) { this.telemetryClient.TrackTrace($"Channel member: {m.Name}, role: {m.Role}"); } var members2 = members .Zip(results, (member, userInfo) => ((userInfo == null) || userInfo.OptedIn) ? member : null) .Where(m => m != null) .ToList(); this.telemetryClient.TrackTrace($"Testing, after filtering {members2.Count} in team {teamInfo.TeamId}"); foreach (var m in members2) { this.telemetryClient.TrackTrace($"OptIn member: {m.Name}, role: {m.Role}"); } var members3 = members .Zip(results, (member, userInfo) => ((userInfo == null) || userInfo.OptedIn) ? member : null) .Where(m => m == null) .ToList(); this.telemetryClient.TrackTrace($"Testing, during filtering {members3.Count} filtered out in team {teamInfo.TeamId}"); foreach (var m in members3) { this.telemetryClient.TrackTrace($"OptOut member: {m.Name}, role: {m.Role}"); } //---------------------------- return(members2); }
/// <summary> /// Pair list of users into groups of 2 users per group /// </summary> /// <param name="users">Users accounts</param> /// <param name="teamModel">DB team model info.</param> /// <returns>List of pairs</returns> private List <Tuple <ChannelAccount, ChannelAccount> > MakePairs(List <ChannelAccount> users, TeamInstallInfo teamModel) { if (users.Count > 1) { this.telemetryClient.TrackTrace($"Making {users.Count / 2} pairs among {users.Count} users"); } else { this.telemetryClient.TrackTrace($"Pairs could not be made because there is only 1 user in the team"); } this.Randomize(users); LinkedList <ChannelAccount> queue = new LinkedList <ChannelAccount>(users); var pairs = new List <Tuple <ChannelAccount, ChannelAccount> >(); while (queue.Count > 0) { ChannelAccount pairUserOne = queue.First.Value; ChannelAccount pairUserTwo = null; UserInfo pairUserOneInfo = this.GetOrCreateUserInfoAsync(this.GetChannelUserObjectId(pairUserOne), teamModel)?.Result; this.telemetryClient.TrackTrace($"Dequeuing (1) {pairUserOneInfo?.UserId}"); queue.RemoveFirst(); if (pairUserOneInfo.RecentPairUps == null) { pairUserOneInfo.RecentPairUps = new List <UserInfo>(); } bool foundPerfectPairing = false; for (LinkedListNode <ChannelAccount> restOfQueue = queue.First; restOfQueue != null; restOfQueue = restOfQueue.Next) { pairUserTwo = restOfQueue.Value; UserInfo pairUserTwoInfo = this.GetOrCreateUserInfoAsync(this.GetChannelUserObjectId(pairUserTwo), teamModel)?.Result; if (pairUserTwoInfo.RecentPairUps == null) { pairUserTwoInfo.RecentPairUps = new List <UserInfo>(); } this.telemetryClient.TrackTrace($"Processing {pairUserOneInfo?.UserId} and {pairUserTwoInfo?.UserId}"); // check if userone and usertwo have already paired recently if (this.SamePairNotCreatedRecently(pairUserOneInfo, pairUserTwoInfo)) { this.telemetryClient.TrackTrace($"Pairing {pairUserOneInfo?.UserId} and {pairUserTwoInfo?.UserId}"); pairs.Add(new Tuple <ChannelAccount, ChannelAccount>(pairUserOne, pairUserTwo)); this.UpdateUserRecentlyPairedAsync(pairUserOneInfo, pairUserTwoInfo); // Remove pairUserTwo since user has been paired this.telemetryClient.TrackTrace($"Dequeuing (2) {pairUserTwoInfo?.UserId}"); queue.Remove(pairUserTwo); foundPerfectPairing = true; break; } } // Not possible to find a perfect pairing, so just use next. if (!foundPerfectPairing) { this.telemetryClient.TrackTrace($"No perfect pair; selecting next user"); pairUserTwo = queue.First?.Value; if (pairUserTwo != null) { pairs.Add(new Tuple <ChannelAccount, ChannelAccount>(pairUserOne, pairUserTwo)); queue.RemoveFirst(); this.telemetryClient.TrackTrace($"Pair formed; dequeued next user"); } else { this.telemetryClient.TrackTrace($"No more users left to pair with"); } } } this.telemetryClient.TrackTrace($"Formed {pairs.Count} pairs"); return(pairs); }