private void ProcessWatchers(List <IssueDto> issues, DateTime lastChecked) { var lastCheckedLocal = lastChecked.ToLocal(_issueManager.UserContext.User.TimeZone); Dictionary <int, WatcherData> targets = new Dictionary <int, WatcherData>(); Dictionary <string, WatcherData> emailTargets = new Dictionary <string, WatcherData>(); var userManager = new UserManager(_issueManager); List <int> projectsMissingFollowerTemplate = new List <int>(); int emailWatchers = -3; LogDebugMessage(string.Concat("Processing follower - ", issues.Count, " items found")); // Build array of users that are watching issues foreach (var issue in issues) { //Safety check if (issue.Watchers.Count == 0) { continue; } if (issue.Revised == issue.Created) { continue; } if (issue.Revised.ToUtc(_issueManager.UserContext.User.TimeZone) <= lastChecked) { continue; } var history = _issueManager.GetHistory(issue); issue.History = new List <IssueAuditDto>(history); history.RemoveAll(h => h.Entity.Created <= lastCheckedLocal); foreach (var watcher in issue.Watchers) { if (watcher.Entity.UserId != null) { if (targets.ContainsKey(watcher.Entity.UserId.Value)) { WatcherData data = targets[watcher.Entity.UserId.Value]; var permissionManager = new PermissionsManager(data.User, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false); if (!permissionManager.CanSeeItem(issue.Project, issue)) { continue; } if (!data.User.Entity.EmailMeMyChanges && IsUserOnlyChange(history, data.User.Entity.Id)) { continue; } data.IssueId.Add(issue.Entity.Id); } else { WatcherData data = new WatcherData(); data.User = userManager.Get(watcher.Entity.UserId.Value); if (data.User.Entity.Active) { var permissionManager = new PermissionsManager(data.User, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false); if (!permissionManager.CanSeeItem(issue.Project, issue)) { continue; } if (!data.User.Entity.EmailMeMyChanges && IsUserOnlyChange(history, data.User.Entity.Id)) { continue; } data.IssueId.Add(issue.Entity.Id); targets.Add(watcher.Entity.UserId.Value, data); } } } else { if (emailTargets.ContainsKey(watcher.Entity.Email.ToLower())) { WatcherData data = emailTargets[watcher.Entity.Email.ToLower()]; data = targets[data.User.Entity.Id]; data.IssueId.Add(issue.Entity.Id); } else { WatcherData data = new WatcherData(); data.User = new UserDto(new User()); data.User.Entity.Id = emailWatchers--; data.User.Entity.Email = watcher.Entity.Email; data.User.Entity.EmailMe = true; data.User.Entity.EmailMeMyChanges = true; data.User.Entity.ProjectGroups.Add(new ProjectGroupMembership() { ProjectGroupId = Constants.GlobalGroupEveryone, UserId = data.User.Entity.Id }); UserSettings settings = new UserSettings(); settings.IndividualFollowerAlerts = true; data.User.Entity.Settings = settings.ToJson(); var group = new ProjectGroup() { Id = Constants.GlobalGroupEveryone, Members = new List <ProjectGroupMembership>() }; group.Members2.Add(new ProjectGroupMembership() { UserId = data.User.Entity.Id, ProjectGroupId = Constants.GlobalGroupEveryone }); data.User.ProjectGroups.Add(group); data.IssueId.Add(issue.Entity.Id); emailTargets.Add(watcher.Entity.Email.ToLower(), data); targets.Add(data.User.Entity.Id, data); } } } } var langMan = new LanguageManager(_issueManager); AlertsTemplateHelper alerts = new AlertsTemplateHelper(_templates, GetUrl(_issueManager), _languages); // Now loop through users sending them watcher summary email Dictionary <int, List <IssueCommentDto> > originalComments = new Dictionary <int, List <IssueCommentDto> >(); List <int> processedProjects; foreach (var target in targets) { processedProjects = new List <int>(); if (originalComments.Count > 0) { foreach (var kv in originalComments) { IssueDto issue = issues.Find(i => i.Entity.Id == kv.Key); // Safety check if (issue == null || issue.Entity.IsNew) { continue; } issue.Comments = kv.Value; } originalComments = new Dictionary <int, List <IssueCommentDto> >(); } var recipient = target.Value; // Safety check if (!recipient.User.Entity.EmailMe || recipient.User.Entity.Email.IsEmpty()) { continue; } AlertTypeWatchersTemplateModel model = new AlertTypeWatchersTemplateModel(); model.TheRecipient = recipient.User; model.Version = GeminiVersion.Version; model.GeminiUrl = alerts.GeminiUrl; foreach (int issueId in recipient.IssueId) { IssueDto issue = issues.Find(i => i.Entity.Id == issueId); // Safety check if (issue == null || issue.Entity.IsNew) { continue; } issue.ChangeLog = _issueManager.GetChangeLog(issue, _issueManager.UserContext.User, recipient.User, lastCheckedLocal); var permissionManager = new PermissionsManager(recipient.User, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false); foreach (var comment in issue.Comments) { if (!permissionManager.CanSeeComment(issue, comment)) { originalComments.Add(issueId, issue.Comments); List <IssueCommentDto> comments = new List <IssueCommentDto>(issue.Comments); comments.RemoveAll(c => !permissionManager.CanSeeComment(issue, c)); issue.Comments = comments; break; } } if (issue.ChangeLog.Count == 0) { continue; } if (recipient.User.GetSettings().IndividualFollowerAlerts) { var template = alerts.FindTemplateForProject(AlertTemplateType.Updated, issue.Entity.ProjectId, GetLanguageId(recipient.User)); if (template == null) { LogDebugMessage("No update notification template found"); continue; } var indModel = new AlertTypeIndividualTemplateModel(); indModel.GeminiUrl = model.GeminiUrl; indModel.LinkViewItem = NavigationHelper.GetIssueUrl(_issueManager.UserContext, issue.Entity.ProjectId, issue.EscapedProjectCode, issue.Entity.Id); indModel.TheItem = issue; indModel.TheRecipient = recipient.User; indModel.Version = GeminiVersion.Version; indModel.IsNewItem = false; string html = alerts.GenerateHtml(template, indModel); if (GeminiApp.GeminiLicense.IsFree) { html = alerts.AddSignature(html); } string log; string subject = template.Options.Subject.HasValue() ? alerts.GenerateHtml(template, indModel, true) : string.Format("[{0}] {1} {2} ({3})", issue.IssueKey, issue.Type, "Updated", issue.Title, issue.IsClosed ? "Closed" : string.Empty); LogDebugMessage(string.Concat("Processing follower - Send item ", issue.IssueKey, " to ", recipient.User.Entity.Email)); EmailHelper.Send(_issueManager.UserContext.Config, subject, html, recipient.User.Entity.Email, recipient.User.Fullname, true, out log); } else { model.TheItemsUpdated.Add(issue); } } if (recipient.User.GetSettings().IndividualFollowerAlerts) { continue; } // Safety check! if (model.ChangeCount > 0) { var watcherAlertTemplates = alerts.Templates.FindAll(s => s.AlertType == AlertTemplateType.Watchers); if (watcherAlertTemplates.Count == 0) { LogDebugMessage("No follower notification template found"); continue; } if (!watcherAlertTemplates.Any(p => p.GetAssociatedProjectValue().IsEmpty())) { List <Project> allItemProjects = model.TheItemsUpdated.Select(s => s.Project).ToList(); allItemProjects = allItemProjects.Where(s => !watcherAlertTemplates.Any(a => a.GetAssociatedProjects().Contains(s.Id))).ToList(); if (projectsMissingFollowerTemplate.Count > 0) { allItemProjects = allItemProjects.Where(s => !projectsMissingFollowerTemplate.Contains(s.Id)).ToList(); } if (allItemProjects.Count > 0) { LogDebugMessage(string.Concat("No follower notification template found for project ", string.Join(", ", allItemProjects.Select(s => s.Name).Distinct()))); projectsMissingFollowerTemplate.AddRange(allItemProjects.Select(s => s.Id).Distinct()); } } watcherAlertTemplates.Sort((x, y) => y.GetAssociatedProjectValue().CompareTo(x.GetAssociatedProjectValue())); foreach (var watcherTemplate in watcherAlertTemplates) { var allTemplateProjects = watcherTemplate.GetAssociatedProjects(); var issuesTemplate = allTemplateProjects.Count == 0 ? model.TheItemsUpdated : model.TheItemsUpdated.FindAll(s => allTemplateProjects.Contains(s.Entity.ProjectId)); if (issuesTemplate.Count == 0) { continue; } var projectIds = issuesTemplate.Select(s => s.Entity.ProjectId).Distinct(); if (processedProjects.Count > 0) { projectIds = projectIds.Where(s => !processedProjects.Contains(s)); issuesTemplate = issuesTemplate.Where(s => !processedProjects.Contains(s.Entity.ProjectId)).ToList(); } if (processedProjects.Contains(0) || projectIds.Count() == 0 || issuesTemplate.Count == 0) { continue; } AlertTypeWatchersTemplateModel projectTemplateModel = new AlertTypeWatchersTemplateModel(); projectTemplateModel.TheItemsUpdated.AddRange(issuesTemplate); projectTemplateModel.TheRecipient = model.TheRecipient; projectTemplateModel.Version = model.Version; projectTemplateModel.GeminiUrl = model.GeminiUrl; AlertTemplate template = alerts.FindTemplateForProject(AlertTemplateType.Watchers, issuesTemplate.First().Entity.ProjectId, GetLanguageId(model.TheRecipient)); if (template.Id == 0) { continue; } // Generate email template string html = alerts.GenerateHtml(template, projectTemplateModel); if (GeminiApp.GeminiLicense.IsFree) { html = alerts.AddSignature(html); } string subject = template.Options.Subject.HasValue() ? alerts.GenerateHtml(template, projectTemplateModel, true) : string.Format("{0} {1}", projectTemplateModel.ChangeCount, "Gemini Updates"); // Send email string log; LogDebugMessage(string.Concat("Processing follower - Send items ", issuesTemplate.Select(i => i.IssueKey).ToDelimited(", "), " to ", recipient.User.Entity.Email)); EmailHelper.Send(_issueManager.UserContext.Config, subject, html, recipient.User.Entity.Email, recipient.User.Entity.Fullname, true, out log); if (allTemplateProjects.Count == 0) { processedProjects.Add(0); } else { processedProjects.AddRange(projectIds); } } } } }
private void ProcessWatchers(List<IssueDto> issues, DateTime lastChecked) { var lastCheckedLocal = lastChecked.ToLocal(_issueManager.UserContext.User.TimeZone); Dictionary<int, WatcherData> targets = new Dictionary<int, WatcherData>(); Dictionary<string, WatcherData> emailTargets = new Dictionary<string, WatcherData>(); var userManager = new UserManager(_issueManager); List<int> projectsMissingFollowerTemplate = new List<int>(); int emailWatchers = -3; // Build array of users that are watching issues foreach (var issue in issues) { //Safety check if (issue.Watchers.Count == 0) continue; if (issue.Revised == issue.Created) continue; if (issue.Revised.ToUtc(_issueManager.UserContext.User.TimeZone) <= lastChecked) continue; var history = _issueManager.GetHistory(issue); issue.History = new List<IssueAuditDto>(history); history.RemoveAll(h => h.Entity.Created <= lastCheckedLocal); foreach (var watcher in issue.Watchers) { if (watcher.Entity.UserId != null) { if (targets.ContainsKey(watcher.Entity.UserId.Value)) { WatcherData data = targets[watcher.Entity.UserId.Value]; var permissionManager = new PermissionsManager(data.User, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false); if (!permissionManager.CanSeeItem(issue.Project, issue)) continue; if (!data.User.Entity.EmailMeMyChanges && IsUserOnlyChange(history, data.User.Entity.Id)) continue; data.IssueId.Add(issue.Entity.Id); } else { WatcherData data = new WatcherData(); data.User = userManager.Get(watcher.Entity.UserId.Value); if (data.User.Entity.Active) { var permissionManager = new PermissionsManager(data.User, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false); if (!permissionManager.CanSeeItem(issue.Project, issue)) continue; if (!data.User.Entity.EmailMeMyChanges && IsUserOnlyChange(history, data.User.Entity.Id)) continue; data.IssueId.Add(issue.Entity.Id); targets.Add(watcher.Entity.UserId.Value, data); } } } else { if (emailTargets.ContainsKey(watcher.Entity.Email.ToLower())) { WatcherData data = emailTargets[watcher.Entity.Email.ToLower()]; data = targets[data.User.Entity.Id]; data.IssueId.Add(issue.Entity.Id); } else { WatcherData data = new WatcherData(); data.User = new UserDto(new User()); data.User.Entity.Id = emailWatchers--; data.User.Entity.Email = watcher.Entity.Email; data.User.Entity.EmailMe = true; data.User.Entity.EmailMeMyChanges = true; data.User.Entity.ProjectGroups.Add(new ProjectGroupMembership() { ProjectGroupId = Constants.GlobalGroupEveryone, UserId = data.User.Entity.Id }); UserSettings settings = new UserSettings(); settings.IndividualFollowerAlerts = true; data.User.Entity.Settings = settings.ToJson(); var group = new ProjectGroup() { Id = Constants.GlobalGroupEveryone, Members = new List<ProjectGroupMembership>() }; group.Members2.Add(new ProjectGroupMembership() { UserId = data.User.Entity.Id, ProjectGroupId = Constants.GlobalGroupEveryone }); data.User.ProjectGroups.Add(group); data.IssueId.Add(issue.Entity.Id); emailTargets.Add(watcher.Entity.Email.ToLower(), data); targets.Add(data.User.Entity.Id, data); } } } } AlertsTemplateHelper alerts = new AlertsTemplateHelper(_templates, GetUrl(_issueManager)); // Now loop through users sending them watcher summary email Dictionary<int, List<IssueCommentDto>> originalComments = new Dictionary<int, List<IssueCommentDto>>(); List<int> processedProjects; foreach (var target in targets) { processedProjects = new List<int>(); if (originalComments.Count > 0) { foreach (var kv in originalComments) { IssueDto issue = issues.Find(i => i.Entity.Id == kv.Key); // Safety check if (issue == null || issue.Entity.IsNew) continue; issue.Comments = kv.Value; } originalComments = new Dictionary<int, List<IssueCommentDto>>(); } var recipient = target.Value; // Safety check if (!recipient.User.Entity.EmailMe || recipient.User.Entity.Email.IsEmpty()) continue; AlertTypeWatchersTemplateModel model = new AlertTypeWatchersTemplateModel(); model.TheRecipient = recipient.User; model.Version = GeminiVersion.Version; model.GeminiUrl = alerts.GeminiUrl; foreach (int issueId in recipient.IssueId) { IssueDto issue = issues.Find(i => i.Entity.Id == issueId); // Safety check if (issue == null || issue.Entity.IsNew) continue; issue.ChangeLog = _issueManager.GetChangeLog(issue, _issueManager.UserContext.User, recipient.User, lastCheckedLocal); var permissionManager = new PermissionsManager(recipient.User, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false); foreach (var comment in issue.Comments) { if (!permissionManager.CanSeeComment(issue, comment)) { originalComments.Add(issueId, issue.Comments); List<IssueCommentDto> comments = new List<IssueCommentDto>(issue.Comments); comments.RemoveAll(c => !permissionManager.CanSeeComment(issue, c)); issue.Comments = comments; break; } } if (issue.ChangeLog.Count == 0) continue; if (recipient.User.GetSettings().IndividualFollowerAlerts) { var template = alerts.FindTemplateForProject(AlertTemplateType.Updated, issue.Entity.ProjectId); if (template == null) { LogDebugMessage("No update notification template found"); continue; } var indModel = new AlertTypeIndividualTemplateModel(); indModel.GeminiUrl = model.GeminiUrl; indModel.LinkViewItem = NavigationHelper.GetIssueUrl(_issueManager.UserContext, issue.Entity.ProjectId, issue.EscapedProjectCode, issue.Entity.Id); indModel.TheItem = issue; indModel.TheRecipient = recipient.User; indModel.Version = GeminiVersion.Version; indModel.IsNewItem = false; string html = alerts.GenerateHtml(template, indModel); if (GeminiApp.GeminiLicense.IsFree) html = alerts.AddSignature(html); string log; string subject = template.Options.Subject.HasValue() ? alerts.GenerateHtml(template, indModel, true) : string.Format("[{0}] {1} {2} ({3})", issue.IssueKey, issue.Type, "Updated", issue.Title, issue.IsClosed ? "Closed" : string.Empty); EmailHelper.Send(_issueManager.UserContext.Config, subject, html, recipient.User.Entity.Email, recipient.User.Fullname, true, out log); } else { model.TheItemsUpdated.Add(issue); } } if (recipient.User.GetSettings().IndividualFollowerAlerts) continue; // Safety check! if (model.ChangeCount > 0) { var watcherAlertTemplates = alerts.Templates.FindAll(s => s.AlertType == AlertTemplateType.Watchers); if (watcherAlertTemplates.Count == 0) { LogDebugMessage("No follower notification template found"); continue; } if (!watcherAlertTemplates.Any(p => p.GetAssociatedProjectValue().IsEmpty())) { List<Project> allItemProjects = model.TheItemsUpdated.Select(s => s.Project).ToList(); allItemProjects = allItemProjects.Where(s => !watcherAlertTemplates.Any(a => a.GetAssociatedProjects().Contains(s.Id))).ToList(); if (projectsMissingFollowerTemplate.Count > 0) { allItemProjects = allItemProjects.Where(s => !projectsMissingFollowerTemplate.Contains(s.Id)).ToList(); } if (allItemProjects.Count > 0) { LogDebugMessage(string.Concat("No follower notification template found for project ", string.Join(", ", allItemProjects.Select(s => s.Name).Distinct()))); projectsMissingFollowerTemplate.AddRange(allItemProjects.Select(s => s.Id).Distinct()); } } watcherAlertTemplates.Sort((x, y) => y.GetAssociatedProjectValue().CompareTo(x.GetAssociatedProjectValue())); foreach(var watcherTemplate in watcherAlertTemplates) { var allTemplateProjects = watcherTemplate.GetAssociatedProjects(); var issuesTemplate = allTemplateProjects.Count == 0 ? model.TheItemsUpdated : model.TheItemsUpdated.FindAll(s => allTemplateProjects.Contains(s.Entity.ProjectId)); if (issuesTemplate.Count == 0) continue; var projectIds = issuesTemplate.Select(s => s.Entity.ProjectId).Distinct(); if (processedProjects.Count > 0) { projectIds = projectIds.Where(s => !processedProjects.Contains(s)); issuesTemplate = issuesTemplate.Where(s => !processedProjects.Contains(s.Entity.ProjectId)).ToList(); } if (processedProjects.Contains(0) || projectIds.Count() == 0 || issuesTemplate.Count == 0) continue; AlertTypeWatchersTemplateModel projectTemplateModel = new AlertTypeWatchersTemplateModel(); projectTemplateModel.TheItemsUpdated.AddRange(issuesTemplate); projectTemplateModel.TheRecipient = model.TheRecipient; projectTemplateModel.Version = model.Version; projectTemplateModel.GeminiUrl = model.GeminiUrl; AlertTemplate template = alerts.FindTemplateForProject(AlertTemplateType.Watchers, issuesTemplate.First().Entity.ProjectId); if (template.Id == 0) { continue; } // Generate email template string html = alerts.GenerateHtml(template, projectTemplateModel); if (GeminiApp.GeminiLicense.IsFree) html = alerts.AddSignature(html); string subject = template.Options.Subject.HasValue() ? alerts.GenerateHtml(template, projectTemplateModel, true) : string.Format("{0} {1}", projectTemplateModel.ChangeCount, "Gemini Updates"); // Send email string log; EmailHelper.Send(_issueManager.UserContext.Config, subject, html, recipient.User.Entity.Email, recipient.User.Entity.Fullname, true, out log); if (allTemplateProjects.Count == 0) { processedProjects.Add(0); } else { processedProjects.AddRange(projectIds); } } } } }