private async void ReloadTimeEntry() { await Task.Run(() => { var param = new NameValueCollection { { Redmine.Net.Api.RedmineKeys.USER_ID, $"=me" }, { Redmine.Net.Api.RedmineKeys.SPENT_ON, $"={DateTimePicker.Value:yyyy-MM-dd}" }, { Redmine.Net.Api.RedmineKeys.SORT, "project" } }; var timeEntryList = timeEntryManager.GetList(param); if (timeEntryList is null) { timeEntryList = new List <TimeEntry>(); } TimeEntryModelList = new List <TimeEntryModel>(); timeEntryList.ForEach(row => { var issue = issueManager.Get(row.Issue.Id.ToString(), new NameValueCollection()); TimeEntryModel tEM = new TimeEntryModel() { Id = row.Id, ProjectName = row.Project.Name.Substring((row.Project.Name.IndexOf('】') + 1)), SubjectId = issue.Id, Subject = issue.Subject, Hours = row.Hours, Percent = issue.DoneRatio, Comments = row.Comments, }; TimeEntryModelList.Add(tEM); }); Invoke((EventHandler) delegate { DataGridViewTimeEntry.DataSource = new List <TimeEntryModel>(TimeEntryModelList.Select(row => (TimeEntryModel)row.Clone())); LogOutputTextBox.Text = LogOutputRefresh(); ChangePanelState(); Timer_GetIssue_Tick(null, new EventArgs()); }); }); }
public ActionResult GetCreatePopup(int itemId) { GeneratePopupModel model = new GeneratePopupModel(); model.GeminiDateFormat = CurrentUser.GeminiDateFormat; model.Item = IssueManager.Get(itemId); return(Json(new JsonResponse() { Success = true, Message = "", Result = new { Html = RenderPartialViewToString(this, AppManager.Instance.GetAppUrl(AppGuid, "views/_GeneratePopup.cshtml"), model) } })); }
public ActionResult GetIssueRow(int issueId) { var issue = IssueManager.Get(issueId); ItemsGridModel model = new ItemsGridModel(); var visibility = GetChangelogFields(issue.Entity.ProjectId); List <int> projectIds = new List <int>(); projectIds.Add(issue.Entity.ProjectId); var properties = GridManager.GetDisplayProperties(MetaManager.TypeGetAll(new List <ProjectDto>() { UserContext.Project }), visibility, projectIds); model.AllowSequencing = false; model.ShowSequencing = false; model.GroupDependencies = false; model.Issues.Add(issue); model.Columns = GridManager.DescribeGridColumns(properties); model.DisplayData = GridManager.GetDisplayData(model.Issues, model.Columns); model.Options = GridManager.DescribeGridOptions(); JsonResponse response = new JsonResponse(); response.Success = true; response.Result = new { Html = RenderPartialViewToString(this, "~/Views/Shared/DisplayTemplates/IssueDtoRow.cshtml", model), }; string data = response.ToJson().Replace("<", "\\u003c").Replace(">", "\\u003e").Replace("&", "\\u0026"); return(Content(data, Request.Files.Count == 0 ? "application/json" : "text/html")); }
public ActionResult Create(string startDate, string endDate, int itemId) { DateTime?startDateTime = ParseDateString.GetDateForString(startDate); DateTime?endDateTime = ParseDateString.GetDateForString(endDate); if (startDateTime == null || endDateTime == null || endDateTime < startDateTime) { return(JsonError("Make sure Start Date and End Date are valid dates")); } //If selection range is bigger than 3 years set the last date to max 3 years if (((endDateTime.Value - DateTime.Today).TotalDays / 365) > 3) { endDateTime = DateTime.Today.AddYears(3); if (startDateTime > endDateTime) { startDateTime = endDateTime; } } var closedStatuses = MetaManager.StatusGetClosed(); List <IssueLinkType> linkTypes = IssueManager.GeminiContext.Meta.LinkTypeGet(); IssueLinkType repeatedLinkType = linkTypes.Find(t => string.Compare(t.Label, "Repeated", true) == 0); if (repeatedLinkType == null && linkTypes.Count > 0) { repeatedLinkType = linkTypes[0]; } var issue = IssueManager.Get(itemId); RepeatParser repeat = new RepeatParser(issue.Repeated); List <IssueDto> repeatedIssues = IssueManager.GetItemsForOriginator(IssueOriginatorType.Repeat, issue.Id.ToString()); if (repeatedIssues.Count > 0) { var previousItemsToDelete = repeatedIssues.FindAll(c => c.Created.Date() >= startDateTime.Value && c.Created.Date() <= endDateTime.Value && !closedStatuses.Contains(c.Entity.StatusId)); foreach (var item in previousItemsToDelete) { IssueManager.Delete(item.Entity.Id); } } for (DateTime date = startDateTime.Value; date <= endDateTime.Value; date = date.AddDays(1)) { repeat.CurrentDateTime = date.Date(); IssueDto lastRepeated = IssueManager.GetLastCreatedIssueForOriginator(IssueOriginatorType.Repeat, issue.Id.ToString()); DateTime lastRepeatedDate = issue.Created; if (lastRepeated != null && lastRepeated.Entity.IsExisting) { lastRepeatedDate = lastRepeated.Created; } if (repeat.MaximumRepeats > 0) { repeatedIssues = IssueManager.GetItemsForOriginator(IssueOriginatorType.Repeat, issue.Id.ToString()); if (repeatedIssues != null && repeatedIssues.Count >= repeat.MaximumRepeats) { continue; } } //If last item was created into the future do this if (lastRepeatedDate > date.Date()) { List <IssueDto> tmpRepeatedIssues = IssueManager.GetItemsForOriginator(IssueOriginatorType.Repeat, issue.Id.ToString()); List <IssueDto> ItemsBeforeStartDate = tmpRepeatedIssues.FindAll(i => i.Created < date.Date()); if (ItemsBeforeStartDate.Count == 0) { lastRepeatedDate = issue.Created; } else { lastRepeatedDate = ItemsBeforeStartDate.OrderBy("Created").Last().Created; } } if (repeat.NeedsToRepeat(lastRepeatedDate)) { var customFields = issue.CustomFields; issue.Attachments = new List <IssueAttachmentDto>(); issue.Entity.Repeated = string.Empty; issue.Entity.OriginatorData = issue.Entity.Id.ToString(); issue.Entity.OriginatorType = IssueOriginatorType.Repeat; issue.Entity.ParentIssueId = null; issue.Entity.IsParent = false; issue.Entity.StatusId = 0; issue.Entity.ResolutionId = 0; if (issue.Entity.StartDate.HasValue && issue.Entity.DueDate.HasValue && issue.Entity.StartDate != new DateTime() && issue.Entity.DueDate != new DateTime()) { TimeSpan tsDates = issue.Entity.DueDate.Value - issue.Entity.StartDate.Value; issue.Entity.DueDate = date.AddDays(tsDates.TotalDays); issue.Entity.StartDate = date.Date(); } else { issue.Entity.StartDate = null; issue.Entity.DueDate = null; } int issueId = issue.Id; issue.Entity.Created = date; IssueDto repeated = IssueManager.Create(issue.Entity); if (repeated.Entity.Id > 0) { string statment = "update gemini_issues set created = @created where issueid = @issueid"; SQLService.Instance.ExecuteQuery(statment, new { created = new DateTime(date.Year, date.Month, date.Day, 8, 0, 0).ToUtc(UserContext.User.TimeZone), issueid = repeated.Entity.Id }); } if (customFields != null && customFields.Count > 0) { foreach (var field in customFields) { try { //Find the existing ID to 'replace', if exists. var existingCF = repeated.CustomFields .SingleOrDefault(s => s.Entity.CustomFieldId == field.Entity.CustomFieldId); field.Entity.Id = existingCF == null ? 0 : existingCF.Entity.Id; field.Entity.IssueId = repeated.Entity.Id; field.Entity.ProjectId = repeated.Entity.ProjectId; CustomFieldManager.Update(new CustomFieldData(field.Entity)); } catch (Exception ex) { LogException(ex); } } } if (repeatedLinkType != null) { IssueManager.IssueLinkCreate(repeated.Entity.Id, issueId, repeatedLinkType.Id); } } } return(JsonSuccess()); }
public string Commit(CommitObject commits, [FromUri] string token) { string apikey = string.Empty; string result = string.Empty; CurrentUser = new UserDto(new User() { ProjectGroups = new List <ProjectGroupMembership>() { new ProjectGroupMembership() { ProjectGroupId = Countersoft.Gemini.Commons.Constants.GlobalGroupAdministrators, UserId = 0 } } }); UserContext.User = CurrentUser; PermissionsManager = PermissionsManager.Copy(CurrentUser); UserContext.PermissionsManager = PermissionsManager; if (Request.Headers.Authorization != null && Request.Headers.Authorization.Parameter != null) { var authDetails = Encoding.Default.GetString(Convert.FromBase64String(Request.Headers.Authorization.Parameter)).Split(':'); if (authDetails.Length == 2) { apikey = authDetails[0]; } } else if (token != null) { apikey = token; } if (apikey.Length == 0 || GeminiApp.Config.ApiKey.Length == 0 || !apikey.StartsWith(GeminiApp.Config.ApiKey, StringComparison.InvariantCultureIgnoreCase)) { string error; if (GeminiApp.Config.ApiKey.Length == 0) { error = "Web.config is missing API key"; } else { error = "Wrong API key: " + apikey; } GeminiApp.LogException(new Exception(error) { Source = SourceControlProvider.GitHub.ToString() }, false); return(error); } if (commits == null) { try { var body = Request.Content.ReadAsStringAsync(); body.Wait(); GeminiApp.LogException("Null CodeCommit", string.Concat("Null CodeCommit - ", body.Result), false); } catch { try { GeminiApp.LogException("Null CodeCommit", "Null CodeCommit - Empty!", false); } catch { } } return(string.Empty); } foreach (var commit in commits.commits) { Regex ex = new Regex("GEM:(?<issueid>[0-9]+)", RegexOptions.IgnoreCase); MatchCollection matches = ex.Matches(commit.message); if (matches.Count > 0) { var baseUrl = commits.repository.url.ReplaceIgnoreCase("https://github.com/", "https://api.github.com/"); List <string> filesModified = new List <string>(); var commitIndex = commit.url.IndexOf("commit"); if (commitIndex != -1) { var url = string.Concat(commit.url.Remove(commitIndex, commit.url.Length - commitIndex), "blob/master/"); for (int i = 0; i < commit.added.Length; i++) { filesModified.Add(string.Concat("{\"Filename\":\"", commit.added[i], "\", \"FileId\":\"", string.Empty, "\",\"PreviousFileRevisionId\":\"", string.Empty, "\" }")); } for (int i = 0; i < commit.modified.Length; i++) { filesModified.Add(string.Concat("{\"Filename\":\"", commit.modified[i], "\",\"FileId\":\"", string.Empty, "\",\"PreviousFileRevisionId\":\"", string.Empty, "\"}")); } for (int i = 0; i < commit.removed.Length; i++) { filesModified.Add(string.Concat("{\"Filename\":\"", commit.removed[i], "\",\"FileId\":\"", string.Empty, "\",\"PreviousFileRevisionId\":\"", string.Empty, "\"}")); } } CodeCommit codeCommit = new CodeCommit(); codeCommit.Provider = SourceControlProvider.GitHub; codeCommit.Comment = commit.message; codeCommit.Fullname = commit.author.name; codeCommit.Data = string.Concat("{\"RevisionId\":\"", commit.id, "\",\"PreviousRevisionId\":\"", string.Empty, "\",\"Files\":[", string.Join(",", filesModified.ToArray()), "],\"RepositoryName\":\"", commits.repository.name, "\",\"RepositoryUrl\":\"", baseUrl, "\",\"IsPrivate\":\"", commits.repository.PRIVATE, "\"}"); commit.message = ex.Replace(commit.message, string.Empty); List <int> issuesAlreadyProcessed = new List <int>(); foreach (Match match in matches) { var issue = IssueManager.Get(match.ToString().Remove(0, 4).ToInt()); if (issue != null && !issuesAlreadyProcessed.Contains(issue.Id)) { codeCommit.IssueId = issue.Id; GeminiContext.CodeCommits.Create(codeCommit); issuesAlreadyProcessed.Add(issue.Id); try { if (match.Index + match.Length + 1 + 5 <= codeCommit.Comment.Length) { var time = codeCommit.Comment.Substring(match.Index + match.Length + 1, 5); var timeEx = new System.Text.RegularExpressions.Regex("[0-9][0-9]:[0-9][0-9]"); var m = timeEx.Match(time); if (m.Success) { // Okay, log time! var timeTypes = MetaManager.TimeTypeGetAll(issue.Project.TemplateId); if (timeTypes.Count > 0) { // Let's try and find the user var user = commit.author.email.HasValue() ? Cache.Users.Find(u => u.Username.Equals(commit.author.email, StringComparison.InvariantCultureIgnoreCase) || u.Email.Equals(commit.author.email, StringComparison.InvariantCultureIgnoreCase) || u.Fullname.Equals(commit.author.email, StringComparison.InvariantCultureIgnoreCase)) : null; if (user == null) { user = commit.author.name.HasValue() ? Cache.Users.Find(u => u.Username.Equals(commit.author.name, StringComparison.InvariantCultureIgnoreCase) || u.Email.Equals(commit.author.name, StringComparison.InvariantCultureIgnoreCase) || u.Fullname.Equals(commit.author.name, StringComparison.InvariantCultureIgnoreCase)) : null; } var timeEntry = new IssueTimeTracking(); timeEntry.IssueId = issue.Id; timeEntry.ProjectId = issue.Entity.ProjectId; timeEntry.Comment = codeCommit.Comment.ToMax(1990); timeEntry.EntryDate = DateTime.Now; timeEntry.Hours = m.Value.Substring(0, 2).ToInt(); timeEntry.Minutes = m.Value.Substring(3, 2).ToInt(); timeEntry.TimeTypeId = timeTypes[0].Entity.Id; timeEntry.UserId = user == null ? Countersoft.Gemini.Commons.Constants.SystemAccountUserId : user.Id; TimeTrackingManager.Create(timeEntry); } } } } catch (Exception timeEx) { LogManager.LogError(timeEx, "GitHub - Time log"); } } } } } return(result); }
public List <CodeCommit> CodeCommit(string auth, string payload = "") { if (auth.IsEmpty()) { GeminiApp.LogException(new UnauthorizedAccessException() { Source = SourceControlProvider.Bitbucket.ToString() }, false); return(null); } var authDetails = Encoding.Default.GetString(Convert.FromBase64String(auth)).Split(':'); string apikey = string.Empty; if (authDetails.Length == 2) { apikey = authDetails[0]; } CurrentUser = new UserDto(new User() { ProjectGroups = new List <ProjectGroupMembership>() { new ProjectGroupMembership() { ProjectGroupId = Countersoft.Gemini.Commons.Constants.GlobalGroupAdministrators, UserId = 0 } } }); UserContext.User = CurrentUser; PermissionsManager = PermissionsManager.Copy(CurrentUser); UserContext.PermissionsManager = PermissionsManager; if (apikey.Length == 0 || GeminiApp.Config.ApiKey.Length == 0 || !apikey.StartsWith(GeminiApp.Config.ApiKey, StringComparison.InvariantCultureIgnoreCase)) { string error; if (GeminiApp.Config.ApiKey.Length == 0) { error = "Web.config is missing API key."; } else { error = string.Format("Wrong API key: {0}.", apikey); } GeminiApp.LogException(new Exception(error) { Source = SourceControlProvider.Bitbucket.ToString() }, false); return(null); } try { var body = Request.Content.ReadAsStringAsync(); body.Wait(); //GeminiApp.LogException("Null CodeCommit", string.Concat("Null CodeCommit - ", body.Result), false); } catch { try { GeminiApp.LogException("Null CodeCommit", "Null CodeCommit - Empty!", false); } catch { } } Payload commits = System.Web.HttpContext.Current.Request.Form["payload"].FromJson <Payload>(); if (commits == null) { return(null); } List <CodeCommit> allCommits = new List <CodeCommit>(); foreach (var commit in commits.commits) { Regex ex = new Regex("GEM:(?<issueid>[0-9]+)", RegexOptions.IgnoreCase); MatchCollection matches = ex.Matches(commit.message); List <int> issueAdded = new List <int>(); List <string> filesModified = new List <string>(); foreach (var file in commit.files) { FileCommitType type = FileCommitType.Created; if (file.type.Equals("modified", StringComparison.InvariantCultureIgnoreCase)) { type = FileCommitType.Modified; } else if (file.type.Equals("removed", StringComparison.InvariantCultureIgnoreCase)) { type = FileCommitType.Deleted; } filesModified.Add(string.Concat("{\"Filename\":\"", file.file, "\", \"FileId\":\"", string.Empty, "\",\"PreviousFileRevisionId\":\"", string.Empty, "\", \"Type\":\"", type.ToString(), "\" }")); } var data = commit.ToJson(); if (matches.Count > 0) { foreach (Match match in matches) { IssueDto issue = IssueManager.Get(match.ToString().Remove(0, 4).ToInt()); if (issue != null) { if (!issueAdded.Contains(issue.Id)) { CodeCommit newCodeCommit = new CodeCommit(); newCodeCommit.Provider = SourceControlProvider.Bitbucket; newCodeCommit.Comment = commit.message; newCodeCommit.Fullname = commit.author; newCodeCommit.Data = string.Concat("{\"RevisionId\":\"", commit.raw_node, "\",\"PreviousRevisionId\":\"", commit.parents[0], "\",\"Files\":[", string.Join(",", filesModified.ToArray()), "],\"RepositoryName\":\"", commits.repository.name, "\",\"RepositoryUrl\":\"", String.Concat(commits.canon_url, commits.repository.absolute_url), "\",\"IsPrivate\":\"", commits.repository.is_private, "\"}");; newCodeCommit.IssueId = issue.Id; allCommits.Add(GeminiContext.CodeCommits.Create(newCodeCommit)); issueAdded.Add(issue.Id); try { if (match.Index + match.Length + 1 + 5 <= commit.message.Length) { var time = commit.message.Substring(match.Index + match.Length + 1, 5); var timeEx = new System.Text.RegularExpressions.Regex("[0-9][0-9]:[0-9][0-9]"); var m = timeEx.Match(time); if (m.Success) { // Okay, log time! var timeTypes = MetaManager.TimeTypeGetAll(issue.Project.TemplateId); if (timeTypes.Count > 0) { // Let's try and find the user var user = commit.author.HasValue() ? Cache.Users.Find(u => u.Username.Equals(commit.author, StringComparison.InvariantCultureIgnoreCase) || u.Email.Equals(commit.author, StringComparison.InvariantCultureIgnoreCase) || u.Fullname.Equals(commit.author, StringComparison.InvariantCultureIgnoreCase)) : null; var timeEntry = new IssueTimeTracking(); timeEntry.IssueId = issue.Id; timeEntry.ProjectId = issue.Entity.ProjectId; timeEntry.Comment = commit.message.ToMax(1990); timeEntry.EntryDate = DateTime.Now; timeEntry.Hours = m.Value.Substring(0, 2).ToInt(); timeEntry.Minutes = m.Value.Substring(3, 2).ToInt(); timeEntry.TimeTypeId = timeTypes[0].Entity.Id; timeEntry.UserId = user == null ? Countersoft.Gemini.Commons.Constants.SystemAccountUserId : user.Id; TimeTrackingManager.Create(timeEntry); } } } } catch (Exception timeEx) { LogManager.LogError(timeEx, "BitBucket - Time log"); } } } else { GeminiApp.LogException(new Exception(string.Concat("Item ID ", match.ToString().Remove(0, 4).ToInt(), " could not be found.")) { Source = "Commit Failed" }, false); } } } } return(allCommits); }
private void ProcessAppNavCardAlerts() { var navigationCardsManager = new NavigationCardsManager(_issueManager); List <NavigationCard> cards = navigationCardsManager.GetPendingAlertCards(); LogDebugMessage("Email templates loaded: " + _templates.Count); LogDebugMessage("Pending card alerts: " + cards.Count); // ? We need to store user id and issue id for every email we send out -- avoids dupes? List <string> issuesEmailedToUsers = new List <string>(50); List <string> individualIssuesEmailedToUsers = new List <string>(50); AlertsTemplateHelper alerts = new AlertsTemplateHelper(_templates, GetUrl(_issueManager), _languages); UserManager userManager = new UserManager(_issueManager); bool refreshCache = false; var allOptOuts = navigationCardsManager.GetOptOuts(); foreach (NavigationCard card in cards) { List <IssueDto> individualIssues = new List <IssueDto>(); // Safety checks if (!card.UserId.HasValue) { continue; } if (card.CardData.Alerts.Count == 0) { continue; } UserDto recepient = userManager.Get(card.UserId.Value); // Safety check if (!recepient.Entity.EmailMe || recepient.Entity.Email.IsEmpty()) { continue; } DateTime lastChecked = card.CardData.AlertsLastSent.HasValue ? card.CardData.AlertsLastSent.Value : card.Created; DateTime lastCheckedLocal = lastChecked.ToLocal(_issueManager.UserContext.User.TimeZone); AlertTypeAppNavAlertsTemplateModel model = new AlertTypeAppNavAlertsTemplateModel(); model.TheRecipient = recepient; model.Version = GeminiVersion.Version; model.GeminiUrl = alerts.GeminiUrl; List <int> issuesToAlert = new List <int>(card.CardData.Alerts); foreach (int issueId in issuesToAlert) { IssueDto issue = null; try { issue = _issueManager.Get(issueId); } catch (Exception ex) { LogException(ex); } // Safety check if (issue == null || issue.Entity.IsNew) { continue; } // Dupe check string dupeKey = string.Format("{0}-{1}-{2}", recepient.Entity.Id, issueId, card.Id); if (issuesEmailedToUsers.Contains(dupeKey)) { continue; } var permissionManager = new PermissionsManager(recepient, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false); if (!permissionManager.CanSeeItem(issue.Project, issue)) { continue; } foreach (var comment in issue.Comments) { if (!permissionManager.CanSeeComment(issue, comment)) { issue.Comments.RemoveAll(c => !permissionManager.CanSeeComment(issue, c)); break; } } // Remove the reported by first entry! if (issue.History.Count > 0 && issue.History[issue.History.Count - 1].Entity.AttributeChanged == ItemAttributeVisibility.ReportedBy) { issue.History.RemoveAt(issue.History.Count - 1); } issue.ChangeLog = _issueManager.GetChangeLog(issue, _issueManager.UserContext.User, recepient, lastCheckedLocal); // Populate model for email template if (card.CardData.Subscription.IndividualAlert) { individualIssues.Add(issue); } if (card.CardData.Subscription.Created && issue.Created.ToUtc(_issueManager.UserContext.User.TimeZone) >= lastChecked) { model.TheItemsCreated.Add(issue); } else { List <IssueAuditDto> allChanges = issue.History.FindAll(h => h.Entity.Created.ToUtc(_issueManager.UserContext.User.TimeZone) >= lastChecked); List <IssueAuditDto> commentChanges = allChanges.FindAll(a => !a.Entity.IsCustom && a.Entity.AttributeChanged == ItemAttributeVisibility.AssociatedComments); List <IssueAuditDto> nonCommentChanges = allChanges.FindAll(a => a.Entity.IsCustom || a.Entity.AttributeChanged != ItemAttributeVisibility.AssociatedComments); // Add comments and updates if (card.CardData.Subscription.Updated && nonCommentChanges.Count > 0 || card.CardData.Subscription.Commented && commentChanges.Count > 0 && issue.Comments.Count > 0) { model.TheItemsUpdated.Add(issue); } if (card.CardData.Subscription.Commented && commentChanges.Count > 0 && issue.Comments.Count > 0) { model.TheItemsCommented.Add(issue); } } // Record the fact that we have processed this issue for this recepient (to prevent dupes) issuesEmailedToUsers.Add(dupeKey); } model.CardTitle = string.Format("{0} {1}", card.Key, card.Title); model.CardKey = card.Key; model.CardDescription = card.Title; model.CardComment = card.CardData.Comment; model.CardUrl = string.Concat(model.GeminiUrl, "workspace/", card.Id, '/', card.Url); // Safety check! if (model.ChangeCount > 0) { List <UserDto> subscribers = GetCardSubscribers(card, navigationCardsManager, userManager, recepient); //if (!subscribers.Contains(recepient) && subscribers.Find(u => u.Entity.Id == recepient.Entity.Id) == null) subscribers.Insert(0, recepient); if (card.CardData.Subscription.IndividualAlert) { foreach (var user in subscribers) { if (allOptOuts.Any(s => s.UserId == user.Entity.Id && s.CardId == card.Id && s.OptOutType == OptOutEmails.OptOutTypes.Alert)) { continue; } foreach (var issue in individualIssues) { string individualDupeKey = string.Format("{0}-{1}", user.Entity.Id, issue.Entity.Id); if (individualIssuesEmailedToUsers.Contains(individualDupeKey)) { continue; } if (user != recepient) { var permissionManager = new PermissionsManager(user, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false); if (!permissionManager.CanSeeItem(issue.Project, issue)) { continue; } issue.ChangeLog = _issueManager.GetChangeLog(issue, _issueManager.UserContext.User, user, lastCheckedLocal); } 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 = user; indModel.Version = GeminiVersion.Version; indModel.IsNewItem = model.TheItemsCreated.Contains(issue); indModel.CardKey = model.CardKey; indModel.CardDescription = model.CardDescription; indModel.CardComment = model.CardComment; indModel.CardUrl = model.CardUrl; if (!indModel.IsNewItem && issue.ChangeLog.Count == 0) { continue; } var template = alerts.FindTemplateForProject( indModel.IsNewItem ? AlertTemplateType.Created : AlertTemplateType.Updated, issue.Entity.ProjectId, GetLanguageId(user)); string html = alerts.GenerateHtml(template, indModel); if (GeminiApp.GeminiLicense.IsFree) { html = alerts.AddSignature(html); } // Send email string log; string subject = template.Options.Subject.HasValue() ? alerts.GenerateHtml(template, indModel, true) : string.Format("[{0}] {1} {2} ({3})", issue.IssueKey, issue.Type, model.TheItemsCreated.Contains(issue) ? "Created" : "Updated", issue.Title, issue.IsClosed ? "Closed" : string.Empty); EmailHelper.Send(_issueManager.UserContext.Config, subject, html, user.Entity.Email, user.Fullname, true, out log); individualIssuesEmailedToUsers.Add(individualDupeKey); } } } else { var cloneCreated = new List <IssueDto>(model.TheItemsCreated); var cloneUpdated = new List <IssueDto>(model.TheItemsUpdated); var cloneCommented = new List <IssueDto>(model.TheItemsCommented); // Find email template to use (for this project or fall back to default template) foreach (var user in subscribers) { AlertTemplate template = alerts.FindTemplateForProject(AlertTemplateType.AppNavAlerts, 0, GetLanguageId(user)); if (allOptOuts.Any(s => s.UserId == user.Entity.Id && s.CardId == card.Id && s.OptOutType == OptOutEmails.OptOutTypes.Alert)) { continue; } model.TheItemsCreated = new List <IssueDto>(cloneCreated); model.TheItemsUpdated = new List <IssueDto>(cloneUpdated); model.TheItemsCommented = new List <IssueDto>(cloneCommented); if (user != recepient) { var permissionManager = new PermissionsManager(user, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false); model.TheItemsCreated.RemoveAll(i => !permissionManager.CanSeeItem(i.Project, i)); model.TheItemsUpdated.RemoveAll(i => !permissionManager.CanSeeItem(i.Project, i)); model.TheItemsCommented.RemoveAll(i => !permissionManager.CanSeeItem(i.Project, i)); foreach (var issue in model.TheItemsCreated.Concat(model.TheItemsUpdated).Concat(model.TheItemsCommented)) { issue.ChangeLog = _issueManager.GetChangeLog(issue, _issueManager.UserContext.User, user, lastCheckedLocal); } } //model.TheItemsCreated.RemoveAll(i => i.ChangeLog.Count == 0); model.TheItemsUpdated.RemoveAll(i => i.ChangeLog.Count == 0); model.TheItemsCommented.RemoveAll(i => i.ChangeLog.Count == 0); if (model.ChangeCount == 0) { continue; } // Generate email template string html = alerts.GenerateHtml(template, model); if (GeminiApp.GeminiLicense.IsFree) { html = alerts.AddSignature(html); } string subject = template.Options.Subject.HasValue() ? alerts.GenerateHtml(template, model, true) : string.Format("{0} {1}", card.Key, card.Title); // Send email string log; EmailHelper.Send(_issueManager.UserContext.Config, subject, html, user.Entity.Email, user.Fullname, true, out log); } } } // Remove the alert notifications and update the database lock (card.CardData.Alerts) { card.CardData.Alerts.RemoveAll(a => issuesToAlert.Contains(a)); } card.CardData.AlertsLastSent = DateTime.UtcNow; refreshCache = true; navigationCardsManager.Update(card, false, false); } if (refreshCache) { navigationCardsManager.Cache.NavigationCards.Invalidate(); var webNodes = GeminiApp.Container.Resolve <IWebNodes>(); webNodes.AddDataOnAllNodesButMe(new WebNodeData() { NodeGuid = GeminiApp.GUID, Key = "cache", Value = navigationCardsManager.Cache.NavigationCards.CacheKey }); } }