protected override Task AfterEventLoggedAsync(object sender, SysEventArgs e) { //if (e.GetEventCategory() == SysEventCategory.Issue) { try { var config = EmailDispatcher.Instance.EmailSettings; //if (config?.Notifications.Enabled ?? false) { // clone entities to avoid trouble when the dbcontext that created them goes out of scope (disposed) e.User = ((AppUser)e.User).Clone(); e.ObjectState = config; return(_emailProducer.ProcessAsync(new SysEventArgs <Issue>(e))); } } catch (Exception ex) { Trace.WriteLine(ex); } } return(Task.CompletedTask); }
/// <summary> /// Attempts to invoke the <see cref="Notify"/> event. /// </summary> /// <param name="args">The event data.</param> internal static void OnNotify(SysEventArgs args) { try { Notify?.Invoke(typeof(ApplicationDbContext), args); } catch { } }
Task TrackEmailConfigurationChange(object sender, SysEventArgs e) { if (!_updatingEmailSettings && e.EventType == SysEventType.EmailConfigUpdated) { Logger.LogInformation("E-mail configuration has been changed. Invaliding the application database context."); lock (_emailSettingsSyncLock) { return(SyncEmailSettings()); } } return(Task.CompletedTask); }
protected override Task AfterEventLoggedAsync(object sender, SysEventArgs e) => _notifier.ProcessAsync(e);
/// <summary> /// Asynchronously produces notification messages for all intended recipients based on the specified event type. /// </summary> /// <param name="args">An object that holds the event data.</param> /// <param name="cancellationToken">The token used to cancel an ongoing async operation.</param> /// <returns></returns> public virtual Task ProcessAsync(SysEventArgs args, CancellationToken cancellationToken = default(CancellationToken)) { CurrentEvent = args; if (args.EventType == SysEventType.Unspecified) { return(Task.CompletedTask); } try { var userName = GetUserName(args.User); var targetId = GetUserId(args.User); var cat = args.GetEventCategory(); if (cat == SysEventCategory.User && args.Data is IdentityUser iduser) { userName = iduser.UserName; targetId = iduser.Id; } int?dataId = null; var dataName = Empty; if (args.Data is ILogItem data) { dataId = data.Id; dataName = data.Name; } string message = null; MessageType messageType = MessageType.Info; switch (CurrentEvent.EventType) { case SysEventType.LoginFailure: messageType = MessageType.Warn; message = Format(SysEventLoginFailure, userName); break; case SysEventType.LoginSuccess: message = Format(SysEventLoginSuccess, userName); break; case SysEventType.Logout: message = Format(SysEventLogout, userName); break; case SysEventType.IssueCreated: message = Format(SysEventIssueCreated, userName); break; case SysEventType.IssueAssigned: message = Format(SysEventIssueAssigned, dataId, userName); break; case SysEventType.IssueUpdated: message = Format(SysEventIssueUpdated, dataId, userName); break; case SysEventType.IssueClosed: message = Format(SysEventIssueClosed, dataId, userName); break; case SysEventType.IssueReopened: message = Format(SysEventIssueReopened, userName); break; case SysEventType.IssueDeleted: messageType = MessageType.Warn; message = Format(SysEventIssueDeleted, userName); break; case SysEventType.UserRegistered: message = Format(SysEventUserRegistered, userName); break; case SysEventType.UserCreated: messageType = MessageType.Success; message = Format(SysEventUserCreated, userName); break; case SysEventType.UserUpdated: messageType = MessageType.Success; message = Format(SysEventUserUpdated, userName); break; case SysEventType.UserDeleted: messageType = MessageType.Warn; message = Format(SysEventUserDeleted, userName); break; case SysEventType.UserImported: message = Format(SysEventUserImported, userName); break; case SysEventType.UserPasswordChanged: message = Format(SysEventUserPasswordChanged, userName); break; case SysEventType.CategoryCreated: messageType = MessageType.Success; message = Format(SysEventCategoryCreated, dataName); break; case SysEventType.CategoryUpdated: messageType = MessageType.Success; message = Format(SysEventCategoryUpdated, dataName); break; case SysEventType.CategoryDeleted: messageType = MessageType.Warn; message = Format(SysEventCategoryDeleted, dataName); break; case SysEventType.EmailConfigUpdated: messageType = MessageType.Success; message = SysEventEmailConfigUpdated; break; case SysEventType.EmailSendFailed: messageType = MessageType.Warn; message = SysEventEmailSendFailed; break; case SysEventType.EmailSendSuccess: messageType = MessageType.Success; message = SysEventEmailSendSuccess; break; default: break; } if (message != null) { Consumer.Enqueue(CreateNotification(message, targetId, messageType, userName)); Consumer.Notify(); } } catch (Exception ex) { Logger.LogError(ex, Empty); } return(Task.CompletedTask); }
/// <summary> /// Events logged on behalf of the <see cref="ApplicationDbContext"/>. /// </summary> /// <param name="sender">The sender of the event.</param> /// <param name="e">The event data.</param> /// <returns></returns> private Task Db_SysEvent(object sender, SysEventArgs e) => EventLogger.LogAsync(e);
/// <summary> /// Method invoked after a system event has been logged. Since it does nothing, classes that inherit <see cref="HelpDeskControllerBase"/> should override it to do something meaningful. /// </summary> /// <param name="sender">The sender of the event.</param> /// <param name="e">The event data.</param> /// <returns></returns> protected virtual Task AfterEventLoggedAsync(object sender, SysEventArgs e) => Task.CompletedTask;
/// <summary> /// Asynchronously produces e-mails and notification messages for all intended recipients based on the specified event type. /// </summary> /// <param name="args">An object that holds issue-related event data.</param> /// <param name="cancellationToken">The token used to cancel an ongoing async operation.</param> /// <returns></returns> public override async Task ProcessAsync(SysEventArgs args, CancellationToken cancellationToken = default(CancellationToken)) { // give a chance to the base message producer to process the event await base.ProcessAsync(args, cancellationToken); if (!(args is SysEventArgs <Issue> e)) { throw new ArgumentException($"Must be of type {nameof(SysEventArgs<Issue>)}.", nameof(args)); } var issue = e.Data ?? throw new ArgumentNullException("Data"); var user = (e.User as AppUser) ?? throw new ArgumentNullException("User"); var settings = e.ObjectState as EmailSettingsViewModel ?? throw new ArgumentNullException("ObjectState", $"ObjectState must be of type {nameof(EmailSettingsViewModel)}"); var notifs = settings.Notifications; _emailNotificationsEnabled = notifs.Enabled ?? false; try { if (issue.User == null && e.EventType != SysEventType.IssueDeleted) { issue = await _issueRepo.All(q => q.QueryIssues(id: issue.Id, withIncludes: true)).SingleOrDefaultAsync(); } var fullName = user.FullName(); var userName = user.UserName; var notify = false; var outgoing = settings.Outgoing; var from = outgoing.FromDisplay; var useFromNameForAll = outgoing.UseFromNameForAll ?? false; var replyToAddrList = new InternetAddressList(); if (!string.IsNullOrWhiteSpace(outgoing.ReplyTo)) { replyToAddrList.AddRange(outgoing.ReplyTo); } EmailTemplate temp; var templates = settings.Templates; switch (e.EventType) { case SysEventType.IssueCreated: { // Sent to technicians when a new ticket arrives. All technicians that have permissions to the category get one of these. // Ticket confirmation notification (the one users get after submitting a new ticket)? notify = notifs.TicketConfirmationNotification ?? false; if (notify && user.SendEmail && user.EmailConfirmed) { // "Ticket confirmation" email template: Sent to the ticket-submitter after the app received his ticket. temp = templates.TicketConfirmation; var m = CreateMessage( temp.ReplaceSubject(issue.Subject).ToString(), temp.ReplaceBody(issue.Body, issue.Subject) .Replace("#Numero_ticket#", $"{issue.Id}") .Replace("#Articles_Base_Connaissances#", string.Empty) //.Replace("#Suggested_KB_articles#", string.Empty) .ToString(), from, to: user.Email ); m.ReplyTo.AddRange(replyToAddrList); Enqueue(WrapMessage(m, user.Id)); } temp = templates.NewTicket; var subj = temp.ReplaceSubject(issue.Subject).ToString(); var body = temp.ReplaceBody(issue.Body, issue.Subject).Replace("#Numero_ticket#", $"{issue.Id}").ToString(); from = user.GetEmailAddress(); // notify all admins? if (notifs.NotifyAllAdmins ?? false) { var qry = _userRepo.All(q => q.NotDisabled().Admins().Not(user.Id).CanReceiveEmails()); await ForUsersAsync(qry, subj, body, from, replyToAddrList, cancellationToken); } // notify techs in their categories? if (notifs.NotifyTechs ?? false) { var qry = _userRepo.All(q => q.NotDisabled().Techs().Not(user.Id).CanReceiveEmails()); await ForUsersAsync(qry, subj, body, from, replyToAddrList, cancellationToken); } } break; case SysEventType.IssueAssigned: { // Notify ALL technicians in a category when another technician TAKES a ticket temp = await TemplateForUpdate($"{fullName} a pris en charge la résolution du ticket #{issue.Id}."); if (user.IsTech && (notifs.NotifyAllTechsOnTechTakeOver ?? false)) { await NotifyTechs(temp); } // notify ticket owner NotifyOwner(temp); } break; case SysEventType.IssueUpdated: { // Sent to both technicians and ticket-submitter (and all ticket-subscribers if any) when a new reply is added to the ticket temp = await TemplateForUpdate($"Le ticket #{issue.Id} a été mis à jour par {fullName}."); if (issue.UpdatedByUser) { from = user.GetEmailAddress(); var techsNotified = false; // Notify ALL technicians in a category when a customer updates a ticket // (not just the ticket-technician and ticket-subscribers)? if (issue.UpdatedForTechView || (notifs.NotifyAllTechsOnCustomerUpdate ?? false)) { await NotifyTechs(temp); techsNotified = true; } await NotifyAssignee(temp); // send to all subscribers but the submitter var qry = _subsRepo.All(q => q.QueryIssueSubscribers(issue.Id).But(user.Id)); if (techsNotified) { qry = qry.NotTechs(); // exclude the techs who've been notified previously } await ForSubscribersAsync(qry, temp.Subject, temp.Body, from, replyToAddrList, cancellationToken); } else { // send to submitter NotifyOwner(temp); // send to subscribers except the owner and updater var qry = _subsRepo.All(q => q.QueryIssueSubscribers(issue.Id).But(user.Id).But(issue.User.Id)); await ForSubscribersAsync(qry, temp.Subject, temp.Body, from, replyToAddrList, cancellationToken); } } break; case SysEventType.IssueClosed: { // Sent to subscribers when a ticket is closed. Note that "Ticket closed notifications" setting has to be on. if (notifs.TicketClosedNotification ?? false) { temp = await TemplateForUpdate($"Le ticket #{issue.Id} a été fermé par {fullName}."); NotifyOwner(temp); } } break; case SysEventType.IssueReopened: // no template for this scenario? break; case SysEventType.IssueDeleted: // no template for this scenario? break; default: // definitely no template for this scenario! break; } Consumer.Notify(); async Task <EmailTemplate> TemplateForUpdate(string whatHappened) { var comments = await _commentRepo.GetAsync(q => q.QueryCommentsForIssue(issue.Id).Skip(0).Take(3).ToArray()); var recent = string.Empty; if (comments.Length > 0) { try { recent = await _emailTemplatesViewRender.RenderToStringAsync(TEMPLATE_RECENT_COMMENTS, comments); } catch (Exception ex) { Logger.LogWarning(ex, "An error occured while rendering the recent comments e-mail template."); var sb = new StringBuilder("<h3>Messages récents</h3>"); foreach (var c in comments) { sb.AppendLine(); sb.AppendLine(c.Body); } recent = sb.ReplaceLineBreaks().ToString().Trim(); } } temp = templates.TicketUpdated; var subj = temp.ReplaceSubject(issue.Subject).ToString(); var body = temp .ReplaceBody(issue.Body, issue.Subject) .Replace("#Quoi_De_Neuf#", whatHappened) .Replace("#Messages_recents#", recent) .Replace("#Categorie#", issue.Category?.Name) .Replace("#Statut#", issue.Status?.Name) .Replace("#Priorite#", UtilExtensions.PriorityName(issue.Priority)) //.Replace("#What_Happened#", whatHappened) //.Replace("#Recent_messages#", recent) //.Replace("#Category#", catname) //.Replace("#Status#", statname) //.Replace("#Priority#", priority) .ToString(); return(new EmailTemplate { Body = body, Subject = subj }); } async Task NotifyTechs(EmailTemplate et) { var qry = _userRepo.All(q => q.NotDisabled().Techs().Not(user.Id).CanReceiveEmails()); await ForUsersAsync(qry, et.Subject, et.Body, from, replyToAddrList, cancellationToken); } async Task NotifyAssignee(EmailTemplate et) { if (issue.IsAssigned()) { var owner = await _userRepo.GetAsync(q => q.Find(issue.AssignedToUserId)); if (owner != null) { EnqueueTemplate(et, owner); } } } void NotifyOwner(EmailTemplate et) { var owner = issue.User; if (owner.SendEmail && owner.Id != user.Id) { EnqueueTemplate(et, owner); } } void SetFromName(AppUser u) { if (!useFromNameForAll) { from = user.GetEmailAddress(); } } void EnqueueTemplate(EmailTemplate et, AppUser owner) { SetFromName(owner); Enqueue(WrapMessage(CreateMessage(et.Subject, et.Body, from, owner.Email), owner.Id)); } } catch (Exception ex) { Logger.LogError(ex, $"Error while producing issue e-mails in {nameof(ProcessAsync)}."); } }