From 177c5a8a321a0bf187e3a0bcd90687c885f82a0d Mon Sep 17 00:00:00 2001 From: "Stephen M. Redd" Date: Fri, 21 Apr 2017 09:02:54 -0400 Subject: [PATCH 01/10] Updates Readme --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 64c3a9d9b..f0819023d 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,7 @@ Documentation can be found in the [TicketDesk GitHub Wiki](https://github.com/St Project Status: =========== -[TicketDesk 2.5 is in beta](https://github.com/StephenRedd/TicketDesk/releases/tag/td2-v2.5.0). Development should resume in December 2015 towards a stable release version. - -View a working [demo of TicketDesk 2.5 here](http://ticketdesk2.azurewebsites.net/). The demo is automatically deployed by continuous integration from the development branch. +[TicketDesk 2.5 is in beta](https://github.com/StephenRedd/TicketDesk/releases/tag/td2-v2.5.1). The current stable version is [TicektDesk 2.1](https://github.com/StephenRedd/TicketDesk/releases/tag/td2-v2.1.3). @@ -46,4 +44,4 @@ Deployment Please visit the [wiki](https://github.com/NullDesk/TicketDesk/wiki) for comprehensive documentation on how to deploy TicketDesk to production IIS server or Azure. -For experienced server admins, please see the [Quick Setup Instructions](https://github.com/NullDesk/TicketDesk/wiki/Quick-Setup-Instructions) for an abbreviated overview. \ No newline at end of file +For experienced server admins, please see the [Quick Setup Instructions](https://github.com/NullDesk/TicketDesk/wiki/Quick-Setup-Instructions) for an abbreviated overview. From 44ddebfdaab13cdca4b3ce7b50692d0655a80d71 Mon Sep 17 00:00:00 2001 From: Stephen Redd Date: Sun, 9 Jul 2017 01:41:40 -0400 Subject: [PATCH 02/10] Ref #139 Fixes first run setup user for notes - Admin User from first-run-setup not correctly registered for notifications - Restart appdomain when notification settings change - Changed how seed methods register accounts for notifications --- .../Migrations/Configuration.cs | 27 +----------- .../DemoPushNotificationDataManager.cs | 24 +++++++---- .../Controllers/FirstRunSetupController.cs | 42 +++++++++++++++---- .../PushNotificationSettingsController.cs | 3 ++ 4 files changed, 55 insertions(+), 41 deletions(-) diff --git a/TicketDesk/TicketDesk.PushNotifications/Migrations/Configuration.cs b/TicketDesk/TicketDesk.PushNotifications/Migrations/Configuration.cs index fd3f2c44e..e1344910b 100644 --- a/TicketDesk/TicketDesk.PushNotifications/Migrations/Configuration.cs +++ b/TicketDesk/TicketDesk.PushNotifications/Migrations/Configuration.cs @@ -34,33 +34,10 @@ protected override void Seed(TdPushNotificationContext context) { DemoPushNotificationDataManager.SetupDemoPushNotificationData(context); } - else - { - InitializeStockUserSettings(context); - } + base.Seed(context); } - public static void InitializeStockUserSettings(TdPushNotificationContext context) - { - - if (!context.SubscriberPushNotificationSettings.Any(s => s.SubscriberId == "64165817-9cb5-472f-8bfb-6a35ca54be6a")) - { - context.SubscriberPushNotificationSettings.Add(new SubscriberNotificationSetting() - { - SubscriberId = "64165817-9cb5-472f-8bfb-6a35ca54be6a", - IsEnabled = true, - PushNotificationDestinations = new[] - { - new PushNotificationDestination() - { - SubscriberName = "Admin User", - DestinationAddress = "admin@example.com", - DestinationType = "email" - } - } - }); - } - } + } } diff --git a/TicketDesk/TicketDesk.PushNotifications/Migrations/DemoPushNotificationDataManager.cs b/TicketDesk/TicketDesk.PushNotifications/Migrations/DemoPushNotificationDataManager.cs index 9dac78df7..4020ea12c 100644 --- a/TicketDesk/TicketDesk.PushNotifications/Migrations/DemoPushNotificationDataManager.cs +++ b/TicketDesk/TicketDesk.PushNotifications/Migrations/DemoPushNotificationDataManager.cs @@ -29,13 +29,15 @@ public static void RemoveAllPushNotificationData(TdPushNotificationContext conte context.SaveChanges(); - Configuration.InitializeStockUserSettings(context); - context.SaveChanges(); + } public static void SetupDemoPushNotificationData(TdPushNotificationContext context) { - if (!context.SubscriberPushNotificationSettings.Any( - s => s.SubscriberId == "64165817-9cb5-472f-8bfb-6a35ca54be6a")) + if (!context.SubscriberPushNotificationSettings.Any(s => + s.SubscriberId == "64165817-9cb5-472f-8bfb-6a35ca54be6a" + || s.PushNotificationDestinations.Any(d => + d.DestinationAddress == "admin@example.com" + && d.DestinationType == "email"))) { context.SubscriberPushNotificationSettings.Add(new SubscriberNotificationSetting() { @@ -52,8 +54,11 @@ public static void SetupDemoPushNotificationData(TdPushNotificationContext conte } }); } - if (!context.SubscriberPushNotificationSettings.Any( - s => s.SubscriberId == "72bdddfb-805a-4883-94b9-aa494f5f52dc")) + if (!context.SubscriberPushNotificationSettings.Any(s => + s.SubscriberId == "72bdddfb-805a-4883-94b9-aa494f5f52dc" + || s.PushNotificationDestinations.Any(d => + d.DestinationAddress == "staff@example.com" + && d.DestinationType == "email"))) { context.SubscriberPushNotificationSettings.Add(new SubscriberNotificationSetting() { @@ -71,8 +76,11 @@ public static void SetupDemoPushNotificationData(TdPushNotificationContext conte }); } - if (!context.SubscriberPushNotificationSettings.Any( - s => s.SubscriberId == "17f78f38-fa68-445f-90de-38896140db28")) + if (!context.SubscriberPushNotificationSettings.Any(s => + s.SubscriberId == "17f78f38-fa68-445f-90de-38896140db28" + || s.PushNotificationDestinations.Any(d => + d.DestinationAddress == "user@example.com" + && d.DestinationType == "email"))) { context.SubscriberPushNotificationSettings.Add(new SubscriberNotificationSetting() { diff --git a/TicketDesk/TicketDesk.Web.Client/Controllers/FirstRunSetupController.cs b/TicketDesk/TicketDesk.Web.Client/Controllers/FirstRunSetupController.cs index 6401964b9..c6309ab2b 100644 --- a/TicketDesk/TicketDesk.Web.Client/Controllers/FirstRunSetupController.cs +++ b/TicketDesk/TicketDesk.Web.Client/Controllers/FirstRunSetupController.cs @@ -23,6 +23,7 @@ using TicketDesk.Domain.Migrations; using TicketDesk.Domain.Model; using TicketDesk.PushNotifications; +using TicketDesk.PushNotifications.Model; using TicketDesk.Search.Common; using TicketDesk.Web.Client.Models; using TicketDesk.Web.Identity; @@ -83,11 +84,11 @@ public async Task UpgradeDatabase() - + return RedirectToAction("CheckUpgradeProgress"); } - + [Route("check-upgrade-progress")] @@ -174,9 +175,9 @@ public async Task CreateDatabase(string email, string password, st new MigrateDatabaseToLatestVersion(true)); ctx.Database.Initialize(true); } - - + + var filter = GlobalFilters.Filters.FirstOrDefault(f => f.Instance is DbSetupFilter); if (filter != null) @@ -186,11 +187,14 @@ public async Task CreateDatabase(string email, string password, st Database.SetInitializer(new TdIdentityDbInitializer()); var existingUser = UserManager.FindByName(email); + TicketDeskUser newUser = null; if (existingUser == null) { - var user = new TicketDeskUser {UserName = email, Email = email, DisplayName = displayName}; - await UserManager.CreateAsync(user, password); - await UserManager.AddToRoleAsync(user.Id, "TdAdministrators"); + newUser = new TicketDeskUser { UserName = email, Email = email, DisplayName = displayName }; + await UserManager.CreateAsync(newUser, password); + await UserManager.AddToRoleAsync(newUser.Id, "TdAdministrators"); + + } else { @@ -208,6 +212,28 @@ public async Task CreateDatabase(string email, string password, st Database.SetInitializer(new TdPushNotificationDbInitializer()); Startup.ConfigurePushNotifications(); + if (newUser != null) + { + using (var notificationContext = new TdPushNotificationContext()) + { + notificationContext.SubscriberPushNotificationSettingsManager.AddSettingsForSubscriber( + new SubscriberNotificationSetting + { + SubscriberId = newUser.Id, + IsEnabled = true, + PushNotificationDestinations = new[] + { + new PushNotificationDestination() + { + DestinationType = "email", + DestinationAddress = newUser.Email, + SubscriberName = newUser.DisplayName + } + } + }); + notificationContext.SaveChanges(); + } + } UpdateSearchIndex(); return RedirectToAction("Index"); } @@ -270,7 +296,7 @@ public ActionResult LegacySecurity() public ActionResult SetupCompleteInfo() { HttpContext.GetOwinContext().Authentication.SignOut(); - + if (!Model.DatabaseStatus.IsDatabaseReady || !Model.DatabaseStatus.IsCompatibleWithEfModel || Model.DatabaseStatus.HasLegacySecurityObjects) { return new EmptyResult(); diff --git a/TicketDesk/TicketDesk.Web.Client/Controllers/PushNotificationSettingsController.cs b/TicketDesk/TicketDesk.Web.Client/Controllers/PushNotificationSettingsController.cs index a25886a92..b9f7a70d1 100644 --- a/TicketDesk/TicketDesk.Web.Client/Controllers/PushNotificationSettingsController.cs +++ b/TicketDesk/TicketDesk.Web.Client/Controllers/PushNotificationSettingsController.cs @@ -63,6 +63,9 @@ public ActionResult Index(ApplicationPushNotificationSetting settings, string si } ViewBag.CurrentRootUrl = GetCurrentRootUrl(); ViewBag.SiteRootUrl = GetRootUrlSetting(); + + Task.Delay(500).ContinueWith(t => System.Web.HttpRuntime.UnloadAppDomain()).ConfigureAwait(false); + return View(dbSetting); } From ad72846ee9378f83815cb18097bd4dea24afbbb5 Mon Sep 17 00:00:00 2001 From: Stephen Redd Date: Mon, 10 Jul 2017 00:36:01 -0400 Subject: [PATCH 03/10] Fixes #139 Adds setting to disable note bg taks --- .../PushNotifications/Strings.Designer.cs | 27 ++++++ .../PushNotifications/Strings.pt-BR.resx | 6 ++ .../PushNotifications/Strings.resx | 9 ++ .../ApplicationPushNotificationSetting.cs | 15 ++- .../App_Start/Startup.PushNotifications.cs | 93 +++++++++++-------- .../PushNotificationSettings/Index.cshtml | 12 +++ 6 files changed, 120 insertions(+), 42 deletions(-) diff --git a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.Designer.cs b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.Designer.cs index 6e001cdf4..40e62daac 100644 --- a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.Designer.cs +++ b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.Designer.cs @@ -78,6 +78,33 @@ public static string ApiKey_Description { } } + /// + /// Looks up a localized string similar to Enable background notification queues. + /// + public static string BackgroundQueueEnabled { + get { + return ResourceManager.GetString("BackgroundQueueEnabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to When turned off, disables use of background task threads to queue pending notifications. Use if you are having problems with email notifications.. + /// + public static string BackgroundQueueEnabled_Description { + get { + return ResourceManager.GetString("BackgroundQueueEnabled_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enabled. + /// + public static string BackgroundQueueEnabled_Prompt { + get { + return ResourceManager.GetString("BackgroundQueueEnabled_Prompt", resourceCulture); + } + } + /// /// Looks up a localized string similar to Click Tracking. /// diff --git a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.pt-BR.resx b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.pt-BR.resx index c8f65240b..6fa7f0da2 100644 --- a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.pt-BR.resx +++ b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.pt-BR.resx @@ -273,4 +273,10 @@ Deixe em branco se a autenticação não for necessária + + Ativar filas de notificação em segundo plano + + + Ativado + \ No newline at end of file diff --git a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.resx b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.resx index 032e5d80d..78de30e0d 100644 --- a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.resx +++ b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.resx @@ -273,4 +273,13 @@ Leave empty if authentication is not required + + Enable background notification queues + + + When turned off, disables use of background task threads to queue pending notifications. Use if you are having problems with email notifications. + + + Enabled + \ No newline at end of file diff --git a/TicketDesk/TicketDesk.PushNotifications/Model/ApplicationPushNotificationSetting.cs b/TicketDesk/TicketDesk.PushNotifications/Model/ApplicationPushNotificationSetting.cs index 4ecee7119..68d8c4941 100644 --- a/TicketDesk/TicketDesk.PushNotifications/Model/ApplicationPushNotificationSetting.cs +++ b/TicketDesk/TicketDesk.PushNotifications/Model/ApplicationPushNotificationSetting.cs @@ -30,11 +30,13 @@ public ApplicationPushNotificationSetting() { ApplicationName = "TicketDesk"; IsEnabled = false; + IsBackgroundQueueEnabled = true; DeliveryIntervalMinutes = 2; AntiNoiseSettings = new AntiNoiseSetting(); RetryAttempts = 5; RetryIntervalMinutes = 2; - DeliveryProviderSettings = new List { }; + // ReSharper disable once VirtualMemberCallInConstructor + DeliveryProviderSettings = new List(); } [Key] @@ -58,6 +60,7 @@ public string Serialized var jsettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }; var jData = JsonConvert.DeserializeObject(value, jsettings); IsEnabled = jData.IsEnabled; + IsBackgroundQueueEnabled = jData.IsBackgroundQueueEnabled; DeliveryIntervalMinutes = jData.DeliveryIntervalMinutes; RetryAttempts = jData.RetryAttempts; RetryIntervalMinutes = jData.RetryIntervalMinutes; @@ -74,6 +77,12 @@ public string Serialized [Display(Name = "NotificationsEnabled", Prompt = "NotificationsEnabled_Prompt", ResourceType = typeof(Strings))] public bool IsEnabled { get; set; } + [NotMapped] + [Display(Name = "BackgroundQueueEnabled", Prompt = "BackgroundQueueEnabled_Prompt", + ResourceType = typeof(Strings))] + [LocalizedDescription("BackgroundQueueEnabled_Description", NameResourceType = typeof(Strings))] + public bool IsBackgroundQueueEnabled { get; set; } + [NotMapped] [Display(Name = "DeliveryAttemptInterval", ResourceType = typeof(Strings))] [LocalizedDescription("DeliveryAttemptInterval_Description", NameResourceType = typeof(Strings))] @@ -127,8 +136,8 @@ public class AntiNoiseSetting public AntiNoiseSetting() { IsConsolidationEnabled = true; - InitialConsolidationDelayMinutes = 6; - MaxConsolidationDelayMinutes = 16; + InitialConsolidationDelayMinutes = 2; + MaxConsolidationDelayMinutes = 10; ExcludeSubscriberEvents = true; } diff --git a/TicketDesk/TicketDesk.Web.Client/App_Start/Startup.PushNotifications.cs b/TicketDesk/TicketDesk.Web.Client/App_Start/Startup.PushNotifications.cs index cfdcc3c87..cc11646bd 100644 --- a/TicketDesk/TicketDesk.Web.Client/App_Start/Startup.PushNotifications.cs +++ b/TicketDesk/TicketDesk.Web.Client/App_Start/Startup.PushNotifications.cs @@ -16,13 +16,13 @@ using System.Configuration; using System.Data.Entity; using System.Linq; +using System.Threading; using System.Web.Hosting; using System.Web.Mvc; using TicketDesk.Domain; using TicketDesk.Domain.Model; using TicketDesk.PushNotifications; using TicketDesk.PushNotifications.Migrations; -using TicketDesk.PushNotifications.Model; using TicketDesk.Web.Client.Infrastructure; namespace TicketDesk.Web.Client @@ -59,53 +59,68 @@ public static void ConfigurePushNotifications() { InProcessPushNotificationScheduler.Start(context.TicketDeskPushNotificationSettings.DeliveryIntervalMinutes); } + + if (context.TicketDeskPushNotificationSettings.IsBackgroundQueueEnabled) + { + //register for static notifications created event handler + TdDomainContext.NotificationsCreated += (sender, notifications) => + { + HostingEnvironment.QueueBackgroundWorkItem(CreateNotifications(notifications)); + }; + } + else + { + TdDomainContext.NotificationsCreated += (sender, notifications) => + { + CreateNotifications(notifications)(CancellationToken.None); + }; + } context.Dispose();//ensure that no one accidentally holds a reference to this in closure - //register for static notifications created event handler - TdDomainContext.NotificationsCreated += (sender, notifications) => + } + } + + private static Action CreateNotifications(IEnumerable notifications) + { + return ct => + { + // ReSharper disable once EmptyGeneralCatchClause + try { - HostingEnvironment.QueueBackgroundWorkItem(ct => + var notificationIds = notifications.Select(n => n.EventId).ToArray(); + var domainContext = DependencyResolver.Current.GetService(); + var multiProject = domainContext.Projects.Count() > 1; + //fetch these back and make sure all dependent entities we need are loaded + var notes = domainContext.TicketEventNotifications + .Include(t => t.TicketEvent) + .Include(t => t.TicketEvent.Ticket) + .Include(t => t.TicketEvent.Ticket.Project) + .Include(t => t.TicketSubscriber) + .Where(t => notificationIds.Contains(t.EventId)) + .ToArray(); + + if (notes.Any()) { - // ReSharper disable once EmptyGeneralCatchClause - try + using (var noteContext = new TdPushNotificationContext()) { - var notificationIds = notifications.Select(n => n.EventId).ToArray(); - var domainContext = DependencyResolver.Current.GetService(); - var multiProject = domainContext.Projects.Count() > 1; - //fetch these back and make sure all dependent entities we need are loaded - var notes = domainContext.TicketEventNotifications - .Include(t => t.TicketEvent) - .Include(t => t.TicketEvent.Ticket) - .Include(t => t.TicketEvent.Ticket.Project) - .Include(t => t.TicketSubscriber) - .Where(t => notificationIds.Contains(t.EventId)) - .ToArray(); - - if (notes.Any()) - { - using (var noteContext = new TdPushNotificationContext()) - { - var subscriberExclude = - noteContext.TicketDeskPushNotificationSettings.AntiNoiseSettings - .ExcludeSubscriberEvents; + var subscriberExclude = + noteContext.TicketDeskPushNotificationSettings.AntiNoiseSettings + .ExcludeSubscriberEvents; - var noteEvents = notes.ToNotificationEventInfoCollection(subscriberExclude, - multiProject); + var noteEvents = notes.ToNotificationEventInfoCollection(subscriberExclude, + multiProject); - noteContext.AddNotifications(noteEvents); + noteContext.AddNotifications(noteEvents); - noteContext.SaveChanges(); - } - } - } - catch - { - //TODO: Log this somewhere + noteContext.SaveChanges(); } - }); - }; - } + } + } + catch + { + //TODO: Log this somewhere + } + }; } - } } \ No newline at end of file diff --git a/TicketDesk/TicketDesk.Web.Client/Views/PushNotificationSettings/Index.cshtml b/TicketDesk/TicketDesk.Web.Client/Views/PushNotificationSettings/Index.cshtml index 8f9d92312..f6bb61b2d 100644 --- a/TicketDesk/TicketDesk.Web.Client/Views/PushNotificationSettings/Index.cshtml +++ b/TicketDesk/TicketDesk.Web.Client/Views/PushNotificationSettings/Index.cshtml @@ -83,6 +83,18 @@ } + +
+ @Html.LabelFor(model => model.IsBackgroundQueueEnabled, new { @class = "col-md-4 col-sm-4 control-label" }) +
+ + @Html.DescriptionFor(model => model.IsBackgroundQueueEnabled, "help-block", "span") + +
+
From 33ab45bfbfea3912134fdbc2d4e46462399aa757 Mon Sep 17 00:00:00 2001 From: Stephen Redd Date: Mon, 10 Jul 2017 00:44:50 -0400 Subject: [PATCH 04/10] Fixes #137 sets demo mode value on sign-in POST --- TicketDesk/TicketDesk.Web.Client/Controllers/UserController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TicketDesk/TicketDesk.Web.Client/Controllers/UserController.cs b/TicketDesk/TicketDesk.Web.Client/Controllers/UserController.cs index 1aa8162b7..127278ae4 100644 --- a/TicketDesk/TicketDesk.Web.Client/Controllers/UserController.cs +++ b/TicketDesk/TicketDesk.Web.Client/Controllers/UserController.cs @@ -119,6 +119,8 @@ public ActionResult SignIn(string returnUrl) [Route("sign-in")] public async Task SignIn(UserSignInViewModel model, string returnUrl) { + ViewBag.IsDemoMode = (ConfigurationManager.AppSettings["ticketdesk:DemoModeEnabled"] ?? "false").Equals("true", StringComparison.InvariantCultureIgnoreCase); + if (!ModelState.IsValid) { return View(model); From 33fd887264b30f15a8b8a7c51721bfff40df4c76 Mon Sep 17 00:00:00 2001 From: Stephen Redd Date: Mon, 10 Jul 2017 01:01:44 -0400 Subject: [PATCH 05/10] Ref #140 Fixes ticket html email layout --- .../Views/Emails/Ticket.Html.cshtml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/TicketDesk/TicketDesk.Web.Client/Views/Emails/Ticket.Html.cshtml b/TicketDesk/TicketDesk.Web.Client/Views/Emails/Ticket.Html.cshtml index 3b41202ce..fb11c5cf6 100644 --- a/TicketDesk/TicketDesk.Web.Client/Views/Emails/Ticket.Html.cshtml +++ b/TicketDesk/TicketDesk.Web.Client/Views/Emails/Ticket.Html.cshtml @@ -62,20 +62,19 @@ Content-Type: text/html; charset=utf-8
- - - - -
+ @Model.Ticket.TicketStatus.GetDisplayName() + #@Model.Ticket.TicketId
+ + @Model.Ticket.Title From eae1ae02faca1408510a38fe7ff832e203a8961d Mon Sep 17 00:00:00 2001 From: Stephen Redd Date: Mon, 17 Jul 2017 18:16:33 -0400 Subject: [PATCH 06/10] Ref #50 Adds broadcast notifications - Supports sending to specific email address or all staff (and admin) users - Adds UI for broadcast settings in push notification settings - Changes demo data management and push notification settings UI to reset app on saving changes - Adds saved changes message in push notification UI - Moves push notification providers to top of settings UI --- .../201707150459325_Td2.5.019.Designer.cs | 29 ++++ .../Migrations/201707150459325_Td2.5.019.cs | 18 +++ .../Migrations/201707150459325_Td2.5.019.resx | 126 +++++++++++++++ .../TicketDesk.Domain/Model/TicketEvent.cs | 4 + .../TicketDesk.Domain/TdDomainContext.cs | 43 ++++-- .../TicketDesk.Domain.csproj | 7 + .../PushNotifications/Strings.Designer.cs | 92 ++++++++++- .../PushNotifications/Strings.pt-BR.resx | 35 ++++- .../PushNotifications/Strings.resx | 32 +++- .../Views/Emails/Strings.Designer.cs | 9 ++ .../Views/Emails/Strings.pt-BR.resx | 3 + .../Views/Emails/Strings.resx | 3 + .../Index.Designer.cs | 18 +++ .../PushNotificationSettings/Index.pt-BR.resx | 6 + .../Views/PushNotificationSettings/Index.resx | 6 + .../Delivery/SmtpDeliveryProvider.cs | 11 +- .../ApplicationPushNotificationSetting.cs | 47 +++++- .../Model/NewTicketPushNotificationInfo.cs | 41 +++++ .../Model/PushNotificationDestination.cs | 1 + .../Model/SubscriberNotificationSetting.cs | 1 + .../TdPushNotificationContext.cs | 16 +- .../TicketDesk.PushNotifications.csproj | 1 + .../App_Start/Startup.PushNotifications.cs | 146 +++++++++++++++++- .../Controllers/DataManagementController.cs | 5 + .../PushNotificationSettingsController.cs | 3 + .../TicketEventNotificationExtensions.cs | 43 ++++-- .../Models/TicketEmail.cs | 2 + .../Views/Emails/Ticket.cshtml | 2 +- .../PushNotificationSettings/Index.cshtml | 85 ++++++++-- .../ListDeliveryProviderSettings.cshtml | 2 +- .../Model/UserEqualityComparer.cs | 32 ++++ .../TicketDesk.Web.Identity.csproj | 1 + 32 files changed, 812 insertions(+), 58 deletions(-) create mode 100644 TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.Designer.cs create mode 100644 TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.cs create mode 100644 TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.resx create mode 100644 TicketDesk/TicketDesk.PushNotifications/Model/NewTicketPushNotificationInfo.cs create mode 100644 TicketDesk/TicketDesk.Web.Identity/Model/UserEqualityComparer.cs diff --git a/TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.Designer.cs b/TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.Designer.cs new file mode 100644 index 000000000..d6d46d5d2 --- /dev/null +++ b/TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.Designer.cs @@ -0,0 +1,29 @@ +// +namespace TicketDesk.Domain.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class Td25019 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Td25019)); + + string IMigrationMetadata.Id + { + get { return "201707150459325_Td2.5.019"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.cs b/TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.cs new file mode 100644 index 000000000..76779e98b --- /dev/null +++ b/TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.cs @@ -0,0 +1,18 @@ +namespace TicketDesk.Domain.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Td25019 : DbMigration + { + public override void Up() + { + AddColumn("dbo.TicketEvents", "ForActivity", c => c.Int(nullable: false, defaultValue: 16384)); + } + + public override void Down() + { + DropColumn("dbo.TicketEvents", "ForActivity"); + } + } +} diff --git a/TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.resx b/TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.resx new file mode 100644 index 000000000..77c5aa501 --- /dev/null +++ b/TicketDesk/TicketDesk.Domain/Migrations/201707150459325_Td2.5.019.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + H4sIAAAAAAAEAO1dW28buRV+L9D/IOipLbKW7cTZxLB34ZXjrds4Nixn0TeDnqHkaeainRl57Rb9ZX3oT+pfKDlX3m8zGklJESCwhuThOYeHh4e3j//9939OfnyOwtETTLMgiU/HB3v74xGMvcQP4sXpeJXPv3s3/vGH3//u5IMfPY9+qfO9xvlQyTg7HT/m+fJ4Msm8RxiBbC8KvDTJknm+5yXRBPjJ5HB///3k4GACEYkxojUandyu4jyIYPED/ZwmsQeX+QqEV4kPw6z6jlJmBdXRJxDBbAk8eDq+C7wvMD+H2Ze98yQCQTwenYUBQIzMYDgfj0AcJznIEZvHnzM4y9MkXsyW6AMI716WEOWbgzCDFfvHbXZTSfYPsSSTtmBNyltlOeLIjuDB60o1E7a4k4LHjeoKtUbLED5jsQsNno7Plssw8AryNzCNggw3ZzaDeY7aezxiWTiehikuLdD6XtFSe0qCr0ZcsVeNDSFTw/9ejaarMF+l8DSGqzwF4avRzeoB0fwrfLlLvsD4NF6FISkWEuwmTZYwzV8qqWYwRa0b/AP641HJLmr2QqAr8PwRxov88XSM/hyPLoJn6NdfKjv4HAfI3lGhPF2hnxNSgxNChcaaRXYIvfxjkOX9KJaj983q1VulQf7Sl1Ypat+STqdhAOMuxkkR+JY0h0aUtJSW6I7ZNAlxDy18tr0ydTS/Xv1+QEFA/iLr7P108vWaKEqjPhBavYVzXir8gVYOVg9LhW0ZjoC0eQ4O3xk1zyckCngIYZNO6HeWJyn8GcYwBTn0b0CewxTFMp+SmGedYZQIARomUVCmjhIE3KhracdDaS2CIdi6Emp4UNTEDEqW9VCulK6FcdNKwieTtjMpuxiq/u9INS79qiq62c5UMXHp23cjomgp6GWcvz506gyXPiwUq+0QZZW6Xru/79ZrjSpH7eSlwbIcnqQ8HBnyoK6yMoO6np+CGKRISXh+t0pTNLF8uSpoFRVR9Uv8lkPb4MFmlUPeQD6Bp2BROiGa69KeUee7hWGRnj0Gy6oTlmn3Tbe5SJPoNgmbUnXK/R1IFxBluEuEybNklXqunbak5dJny++b7bJl3S49ti05cIfto0qTAbMUENek7Jrr8A5TxOwiwb1z4IrvgjxUi7uWas9hDoIw6yG2taz4MvtzHoWNS0yQdwCxvdbAAoczPYwiGrNIIbbin1R2cXj0di0WWVZ9jv6rK8d/3wURvJ7PM+wA+xwOmNqvf0Mlhxf6DMXCixj6d0kPdZs4mxlS14oONOkE21YrBvaqtFXbWdBFQfAmLPIjyPLPSx8Jstnae1TrTRokaTEQKsTpwdjO5nM0jmbTImJpO5ar+9vNwLKJGrsElnXkKAks67jTLtj98IT6liriLTLc14EnyxyZKol8qSyi6FfP5Gz1gKcuD6jtFZy2uaTsclkkPPP53BhHg7WKY5QsZbVNk/BIZOhhTlG0kvvEoii+nbMLYeaC383OREymBQ2bQ017igqHXChAvjQqLK/XleZ1Bd+FejYRApTtspmAeKuHXIbXiyQ98/LgiQxsWn/bJunUr/Hr/YyX4gFdOKQ6DOqfkjyYV4vT2gGezHxPjQhCCWTZVSGAtExfoxdZQceRjCS1+6OaMG8b4qxzGOxleNNs0FCCDO2YL7NP8Leuw8pldguB/21OjShnM4yTUvhdrWNznbg4yEYWNhWQmLjYSUnOeASiWvpjknVXR9zS2CEPvA1edWCH6B4qOU3YxWYtn9h3sGI0w3Y3X1R4G+wWseFuiVXhoSafqLrNbJA79jt3yzdc+BHbumBlyMnI8XmzDqeriOKbNXTMiIuN1+UGX84nTvdRk1T9oUKnA1KoJ0o2s83MZhXx43u1RXSZXYRg0R6ldx7rC3o9mhFqIh+m4QtSICkxra0rGKEBo96nwGsDSIRfQLhCP/c53VK5r5Cju4znSZP/QJ3/FmZJ+ITj4ir/oTr/NEwyIvdrvq3KVtG2VLvk0bRVabSuTVUT3GhjlWunZk115vvNIqNZY81Wy2X4wjWxrslA7MGQK/VGZxi/rmCWc8XeqYvdgS/w+gmH17VIb40MsLUojTTldnST/a1WDKZAcQBVVeIGZFmr2iMN+0V/aLIfHWi4v4XXS0gws3+o4f9n1Pc/L1t+9t9o2L9I0MhMc/Vm/71GCtSPgvnLWZ4D7zEq993q5j54rxHpgx/kVZxC+Z23r99pZCuPUxAt//1bjXBliev4J/gIwvn1vDWDo6PXb018EbKGxAsKx0K5o2abk679Q+yP1Hue7FIuGqiRawnw8VvkjE7Hf+IkktJs1ihbms3mK030gGYTEb2Oz9GYmsPRmVfe7JqCzAO+4Cw3qp7+ggIWiA8yBCCcopAQucMgzvnoJoi9YAlCJetMKbvzsZi5pho25RyifoPDdWVzdK6/qYZRmk5HJxPCsEzsjVqNV9uHeGmetbtqwcnW+MT75Dqj3hb7E3FvYgOKpRNbExS1TlcWhrVC6eqlgeXolzKFdkrviDjZrH6nx6iHbJUl62TqZleWOzNOHUFnD8NKMFg/4hcM1RasWD3kDj0SK9a2HUVxqmhXPLxUhIHdvLTFdt7XEwbm5nZFWzsDen3BBpFFJ+Jn3Gtw4jyLa/CDqh2eHt0539obkGWwTkOsh6utU7Q4zlphsW1ja/ai45a74rx53gf22nyrbJ27Lhe3UZkclWiX0fxy5RJ/h8+i86+fM1gtjNYrp6wd5eWlX/6KdrvG366si+4NVzswnIHShKv5tIhas3qhIVEqXEShtm8jAvWRcQmVKv43J8UcVFORpYc2oyqos+MS2uS4ZUS0PNctoVb4Hw0ZYvNMRIjammNIEQbOstVeIiAyia8ZsD1OuwLXSEDYEddvtWtuBJXWoFnfSEtoLD198lKmAfmKkOmaEKeJukNo1SFcBdIrtptG5KemlCoyW65wXrAQK5FxBWYK1S5RGDaYs5oF51hkqtXMYK3msJxYlKvT6k4+a92ARZIe2NYsZTOrbnOr9RqoYDZl05zO+icPnsj0LAvGzcJxToxyrNRqShCAd7LD+jxDE/I1aSeTEkyw+oBRiYSogydXYLnEA3RbsvoympUQhNPvZvbgfFFJY+JlFEYfw21TU56kYAGZVHzQ1ocXQZrhSyHgAeBtyKkfcdnYAFcSldS1yWNYvvnqkKUui/8mTY08QCCId/kZQkXoAsmLN0cL0SFhAyZERhgpEoQg1SEnTZNwFcWSRH7yPGpAwHRIRwYqEEAfCSqUiULCc5FS8FSzv2R8mFz3AUYcc4k51CWjRmdhmHoQmCO6LnlFAFBGQtOIUL2ITPOyFoFFSFQycWloqh4kpCvvIt/JhHEo3CID58O4NRraPRo5z2Za081jSuZpBm5SWlLWFMROPeVP5Bv4Wlq8j6USrOlRN1IFZKl0c+rNjRmSZPNRsIS6GZOqg59uFiUO6QwMSlZQptV2XY9Uq2ppegjLrKLMAmGJ56v8bk6tBU2inFfz1YavAgWJZqn4ZE6jgTQiqTQfzenUl6RJMvU3C4lqhCJKpvqjhY5bBCJKye1na1rlDWoBtTLBnF4FEkRSqj6Z0yARf6hwlPhua9/1wWzewusUC53xgD6U5vhkR9oVqI+UeJVuTp2G6yHp0ikuFHlFsGk2/q3G4KHdW/3VwpZYlB3KoNjEr3SErJb1+hgmhUudxmOlpPT6B8zmkA5JSHpyR0NHGnTxqRYdvz6KT/X2+uPQQ1WD58GJZ+ceCHgOXlGWTsG2y8noUFAYJC0qYRu7ML3I2lt3pjYPO3VtNaXd6eb0+Q9qoUF5MkROsYJDoHtl8cmGRomGQBMpv32lIxe51N+Hvcu3RIwNXUVi/Rbubpkbbcdir6OPBsRnCFxbTlhWMy0uL3oL5sWi6+NKevUdbmbyZ7vwY29KG2p46iRHt6YnT33YN76ytEzN9R1oUsni+9QVFfGSMX+lWSVj+VVyx7mHNeQetwQk9XLXqvl9CSp5YGvl9j7ZLE3tzR4os9d5Uu076p9h4zYiyyzjERL/KfDxJuTsJcthtIcz7M1+Dcsl/jbDFYiDOczy8kbw+HAf3+6knnLbnmfVJlnmh4J9W+LetW6bcrNvxcRPIPUeQcq/FmOLHyzbbqTr+UMEnv9IEjcBa5Tt7PVCWrSD1gNh0caVNVnr11scLEvqlhweTglwP9aivrg9V6K3WgwG09tTKMIqjugqHF4+wa9bZjmIllI9tZBp/TzsIzxHP/D7HOu0C6YW7sT2ZezD59PxP4tSx6PLv903BV+NrlM04ByP9kf/cgQJKjeKlCbT+QWOvuhSD2yY2LfL+xlS7+bwJsZDYI9jz7yHYeIpnJ6/EBIucCu6v26B9w6wo0gqMN+bFHpB+cDu9139Bs8A9cBFb2Lx71eYkHZ9rqLo+n09UKFWf2/PVfSmatFrFGsgvjYVsU9PSHi3tRPJSxMuTmVLBnERPsAWY+Gbjsd1uU7DMYMdvJaIQ4aG31esyIDfd5p/dB9JGXD73jwKh10/7HC3rr7M1yQAn9ePU52RzjfkHoR5+4W/tfUn9xWrpF8RZSO57N0HyfkV8sKyfODAhgjwt+29lgwJlVNw1dENUEjpLv6JRknf4WFdCgWx7XjUgw7waqu2MkYH3GfnhukPcHktIQ2DsdzbwtqApuIEcOzQnv3gCvc3MVvPcrgEIZgJXMyXqp2RH+v0oXEZ2YuITmCTTugdQo1IjFGxmWgN1/F14jcODdgivOrrjhjZAf9lI1iM2wfxsjb0xY2AHGpvkvcD/9UVhmvHwBL/j4y4Hc6SxHvohMW4Q25TeShzx3ynMZqhY0PzeIHb7w97a1/rmfy2esbdwxfctF8ssMPcEA13yBOKTzdv2AUyRyabLRoWZ4VtQB3+nyH8X3ls8nTsP+AN9HLWLQQT5PyiECdQBRMoqupGhsbG0q87BUe+ThBRl8H3iIlXwbWkhipVXo0E3UtRFz1EqOqlc2p4UIJFaeEKjdAK5Ryo0KPEVRdeRlJnkSavTITxpMI81EEeimqiIROZutYNisj0Kgpog3dO/KjCXdLgx4btQT00YFc+VeaKivFAdw/S0FQ0s1hVTEuJb7qV8IRutsJ7OfHNs3VbzUCwg47ibp0l9Ywe6GY7xDDFXHozF9MCHJC/CINizFWM947LX+ewOPhYkzhBNOPyLlNLtM6Dn+6qw1yGozoLs0FzBXPgo9DzLEXtCbwcJXswy4pRsnqU60P0AP3L+HqVL1c5EhlGDyF1sxsHy6r6CwREmueT6+LAU9aHCIjNwC/fE1sFYfuo4YVga09CAkfh1SYobsscb4YuXhpK5XOAJoQq9TWThzsYLUNELLuOZ4B4Hs+CNxSXfIQL4L3U95nkRPQNQav95DwAixREWUWjLY9+Ihv2o+cf/gfxiMn6vKMAAA== + + + dbo + + \ No newline at end of file diff --git a/TicketDesk/TicketDesk.Domain/Model/TicketEvent.cs b/TicketDesk/TicketDesk.Domain/Model/TicketEvent.cs index 6c58cc6c6..73cb66675 100644 --- a/TicketDesk/TicketDesk.Domain/Model/TicketEvent.cs +++ b/TicketDesk/TicketDesk.Domain/Model/TicketEvent.cs @@ -62,6 +62,8 @@ public TicketEvent() public virtual ICollection TicketEventNotifications { get; set; } + public TicketActivity ForActivity { get; set; } + /// /// Creates the activity event. @@ -81,6 +83,7 @@ public static TicketEvent CreateActivityEvent( { var tc = new TicketEvent { + ForActivity = activity, Comment = comment, EventBy = eventByUserId, EventDate = DateTime.Now, @@ -95,6 +98,7 @@ public static TicketEvent CreateActivityEvent( /// public void CreateSubscriberEventNotifications() { + foreach (var subscriber in Ticket.TicketSubscribers) { var isSubscriberEvent = EventBy == subscriber.SubscriberId; diff --git a/TicketDesk/TicketDesk.Domain/TdDomainContext.cs b/TicketDesk/TicketDesk.Domain/TdDomainContext.cs index e070d01b4..f8eb8e730 100644 --- a/TicketDesk/TicketDesk.Domain/TdDomainContext.cs +++ b/TicketDesk/TicketDesk.Domain/TdDomainContext.cs @@ -39,27 +39,30 @@ public sealed class TdDomainContext : DbContext /// public static event EventHandler> TicketsChanged; + public static event EventHandler> TicketsCreated; + + private static void RaiseTicketsCreated(TdDomainContext sender, IEnumerable tickets) + { + //TODO: Static events have their (rare) uses, but this should use a service bus or formal pub/sub mechanism eventually + TicketsCreated?.Invoke(sender, tickets); + } + private static void RaiseTicketsChanged(TdDomainContext sender, IEnumerable tickets) { //TODO: Static events have their (rare) uses, but this should use a service bus or formal pub/sub mechanism eventually - if (TicketsChanged != null) - { - TicketsChanged(sender, tickets); - } + TicketsChanged?.Invoke(sender, tickets); } public static event EventHandler> NotificationsCreated; + private static void RaiseNotificationsCreated(TdDomainContext sender, IEnumerable notifications) { //TODO: Static events have their (rare) uses, but this should use a service bus or formal pub/sub mechanism eventually - if (NotificationsCreated != null) - { - NotificationsCreated(sender, notifications); - } + NotificationsCreated?.Invoke(sender, notifications); } - public TdDomainSecurityProviderBase SecurityProvider { get; private set; } + public TicketActionManager TicketActions { get; private set; } @@ -244,15 +247,18 @@ public override int SaveChanges() private void RaiseEntityChangeEvents(PendingEventEntities pendingEntityChanges) { + RaiseTicketsCreated(this, pendingEntityChanges.PendingNewTickets); RaiseTicketsChanged(this, pendingEntityChanges.PendingTicketChanges); RaiseNotificationsCreated(this, pendingEntityChanges.PendingEventNotificationChanges); } private PendingEventEntities OnSaving() { - var pending = new PendingEventEntities(); - pending.PendingTicketChanges = GetTicketChanges(); - + var pending = new PendingEventEntities + { + PendingTicketChanges = GetTicketChanges(), + PendingNewTickets = GetNewTickets() + }; if (SecurityProvider != null) { ProcessDeletedProjects(); @@ -385,7 +391,16 @@ private void PrePopulateNewTicket(Ticket newTicket) newTicket.EnsureSubscribers(); } - + private IEnumerable GetNewTickets() + { + + return ChangeTracker.Entries() + .Where(t => t.State == EntityState.Added) + .Select(t => t.Entity) + .ToArray(); //execute now, because after save changes this query will return no results + } + + private IEnumerable GetTicketChanges() { @@ -420,6 +435,8 @@ private IEnumerable GetTicketEventNotificationChanges() private class PendingEventEntities { + + public IEnumerable PendingNewTickets { get; set; } public IEnumerable PendingTicketChanges { get; set; } public IEnumerable PendingEventNotificationChanges { get; set; } diff --git a/TicketDesk/TicketDesk.Domain/TicketDesk.Domain.csproj b/TicketDesk/TicketDesk.Domain/TicketDesk.Domain.csproj index 6d5dee271..6f4a99a62 100644 --- a/TicketDesk/TicketDesk.Domain/TicketDesk.Domain.csproj +++ b/TicketDesk/TicketDesk.Domain/TicketDesk.Domain.csproj @@ -131,6 +131,10 @@ 201506240309537_Td2.5.018.cs + + + 201707150459325_Td2.5.019.cs + @@ -246,6 +250,9 @@ 201506240309537_Td2.5.018.cs + + 201707150459325_Td2.5.019.cs + diff --git a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.Designer.cs b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.Designer.cs index 40e62daac..ffe9a3537 100644 --- a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.Designer.cs +++ b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.Designer.cs @@ -88,7 +88,7 @@ public static string BackgroundQueueEnabled { } /// - /// Looks up a localized string similar to When turned off, disables use of background task threads to queue pending notifications. Use if you are having problems with email notifications.. + /// Looks up a localized string similar to Enables background queuing of notifications.<br><br>Affects localizaton! When background queue is enabled, all email notificaitons are localized using the server's default culture and language. When disabled, the user's language choices will be used to generate emails.. /// public static string BackgroundQueueEnabled_Description { get { @@ -105,6 +105,24 @@ public static string BackgroundQueueEnabled_Prompt { } } + /// + /// Looks up a localized string similar to Broadcast Mode. + /// + public static string BroadcastMode { + get { + return ResourceManager.GetString("BroadcastMode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Send new ticket notifications to a custom email distrubution list, or to all TicketDesk admin and staff users.. + /// + public static string BroadcastMode_Description { + get { + return ResourceManager.GetString("BroadcastMode_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to Click Tracking. /// @@ -321,6 +339,24 @@ public static string InitialRetryInterval_Description { } } + /// + /// Looks up a localized string similar to New Ticket Notificatons Enabled. + /// + public static string IsBroadcastEnabled { + get { + return ResourceManager.GetString("IsBroadcastEnabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enabled. + /// + public static string IsBroadcastEnabled_Prompt { + get { + return ResourceManager.GetString("IsBroadcastEnabled_Prompt", resourceCulture); + } + } + /// /// Looks up a localized string similar to Maximum consolidation delay (minutes). /// @@ -420,6 +456,24 @@ public static string Provider_Prompt { } } + /// + /// Looks up a localized string similar to Send to all administrators and help desk staff. + /// + public static string PushNotificationBroadcastModeAllStaff { + get { + return ResourceManager.GetString("PushNotificationBroadcastModeAllStaff", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Send to specified email address. + /// + public static string PushNotificationBroadcastModeCustomAddress { + get { + return ResourceManager.GetString("PushNotificationBroadcastModeCustomAddress", resourceCulture); + } + } + /// /// Looks up a localized string similar to SendGrid Provider (Email). /// @@ -429,6 +483,42 @@ public static string SendGridProvider { } } + /// + /// Looks up a localized string similar to To Email Address. + /// + public static string SendToCustomEmailAddress { + get { + return ResourceManager.GetString("SendToCustomEmailAddress", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The email recipient address. Used only when the mode is set to use a custom email address. + /// + public static string SendToCustomEmailAddress_Description { + get { + return ResourceManager.GetString("SendToCustomEmailAddress_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to To Email Display Name. + /// + public static string SendToCustomEmailDisplayName { + get { + return ResourceManager.GetString("SendToCustomEmailDisplayName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The display name for the email recipient. Used only when the mode is set to use a custom email address. + /// + public static string SendToCustomEmailDisplayName_Description { + get { + return ResourceManager.GetString("SendToCustomEmailDisplayName_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to Send to Sink. /// diff --git a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.pt-BR.resx b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.pt-BR.resx index 6fa7f0da2..c0cbe44b9 100644 --- a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.pt-BR.resx +++ b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.pt-BR.resx @@ -274,9 +274,42 @@ Deixe em branco se a autenticação não for necessária - Ativar filas de notificação em segundo plano + Permite a fila em segundo plano de notificações. Ativado + + Permite fila em segundo plano de notificações<br><br>Afeta localizaton! Quando a fila de fundo está ativada, todas as notificações de email são localizadas usando a cultura e o idioma padrão do servidor. Quando desativado, as opções de idioma do usuário serão usadas para gerar emails. + + + Modo + + + Envie novas notificações de ingresso para uma lista de distribuição de e-mail personalizada ou para todos os usuários do administrador e da equipe do TicketDesk. + + + Novas notificações de boletim habilitadas + + + Ativado + + + Enviar para todos os administradores e funcionários da mesa de ajuda + + + Enviar para o endereço de e-mail especificado + + + Para endereço de e-mail + + + O endereço do destinatário do e-mail. Usado somente quando o modo está configurado para usar um endereço de e-mail personalizado + + + Para Nome de exibição de e-mail + + + O nome de exibição para o destinatário do e-mail. Usado somente quando o modo está configurado para usar um endereço de e-mail personalizado + \ No newline at end of file diff --git a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.resx b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.resx index 78de30e0d..a05ebbdae 100644 --- a/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.resx +++ b/TicketDesk/TicketDesk.Localization/PushNotifications/Strings.resx @@ -277,9 +277,39 @@ Enable background notification queues - When turned off, disables use of background task threads to queue pending notifications. Use if you are having problems with email notifications. + Enables background queuing of notifications.<br><br>Affects localizaton! When background queue is enabled, all email notificaitons are localized using the server's default culture and language. When disabled, the user's language choices will be used to generate emails. Enabled + + Broadcast Mode + + + Send new ticket notifications to a custom email distrubution list, or to all TicketDesk admin and staff users. + + + New Ticket Notificatons Enabled + + + Enabled + + + Send to all administrators and help desk staff + + + Send to specified email address + + + To Email Address + + + The email recipient address. Used only when the mode is set to use a custom email address + + + To Email Display Name + + + The display name for the email recipient. Used only when the mode is set to use a custom email address + \ No newline at end of file diff --git a/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.Designer.cs b/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.Designer.cs index 25f3fe794..d16c23f5b 100644 --- a/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.Designer.cs +++ b/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.Designer.cs @@ -87,6 +87,15 @@ public static string Details { } } + /// + /// Looks up a localized string similar to New. + /// + public static string New { + get { + return ResourceManager.GetString("New", resourceCulture); + } + } + /// /// Looks up a localized string similar to Owner: {0}. /// diff --git a/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.pt-BR.resx b/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.pt-BR.resx index e830bea4b..fd8b25881 100644 --- a/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.pt-BR.resx +++ b/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.pt-BR.resx @@ -126,6 +126,9 @@ Detalhes + + Novo + Solicitante: {0} diff --git a/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.resx b/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.resx index 5492293c9..51a6b1501 100644 --- a/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.resx +++ b/TicketDesk/TicketDesk.Localization/Views/Emails/Strings.resx @@ -126,6 +126,9 @@ Details + + New + Owner: {0} diff --git a/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.Designer.cs b/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.Designer.cs index b6e3dd8cb..3209e7600 100644 --- a/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.Designer.cs +++ b/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.Designer.cs @@ -69,6 +69,15 @@ public static string AntiNoise_Header { } } + /// + /// Looks up a localized string similar to New Ticket Notifications. + /// + public static string BroadcastNotification_Header { + get { + return ResourceManager.GetString("BroadcastNotification_Header", resourceCulture); + } + } + /// /// Looks up a localized string similar to Delivery providers. /// @@ -115,6 +124,15 @@ public static string RequiredProvider { } } + /// + /// Looks up a localized string similar to Settings saved!. + /// + public static string Saved_Alert { + get { + return ResourceManager.GetString("Saved_Alert", resourceCulture); + } + } + /// /// Looks up a localized string similar to Save. /// diff --git a/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.pt-BR.resx b/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.pt-BR.resx index fdc1de4d7..f2bfaefa6 100644 --- a/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.pt-BR.resx +++ b/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.pt-BR.resx @@ -120,6 +120,9 @@ Configurações de anti-ruído + + Novas Notificações de Bilhetes + Provedores de entrega @@ -136,6 +139,9 @@ Pelo menos um provedor deve estar habilitado para que as mensagens sejam enviadas. + + Configurações salvas! + Salvar diff --git a/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.resx b/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.resx index fa044cfc4..e57493d44 100644 --- a/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.resx +++ b/TicketDesk/TicketDesk.Localization/Views/PushNotificationSettings/Index.resx @@ -120,6 +120,9 @@ Anti-noise settings + + New Ticket Notifications + Delivery providers @@ -136,6 +139,9 @@ At least one provider must be enabled in order for messages to be sent. + + Settings saved! + Save diff --git a/TicketDesk/TicketDesk.PushNotifications/Delivery/SmtpDeliveryProvider.cs b/TicketDesk/TicketDesk.PushNotifications/Delivery/SmtpDeliveryProvider.cs index c06e8626b..2999c0289 100644 --- a/TicketDesk/TicketDesk.PushNotifications/Delivery/SmtpDeliveryProvider.cs +++ b/TicketDesk/TicketDesk.PushNotifications/Delivery/SmtpDeliveryProvider.cs @@ -35,15 +35,14 @@ public SmtpDeliveryProvider(JToken configuration) public override Task SendNotificationAsync(PushNotificationItem notificationItem, object message, CancellationToken ct) { - var cfg = (SmtpDeliveryProviderConfiguration)Configuration; - var sent = false; - - var smsg = message as SerializableMailMessage; - if (smsg != null) + SmtpDeliveryProviderConfiguration cfg = (SmtpDeliveryProviderConfiguration)Configuration; + bool sent = false; + + if (message is SerializableMailMessage smsg) { try { - var client = new SmtpClient() + SmtpClient client = new SmtpClient() { Host = cfg.SmtpServer, Port = cfg.SmtpPort ?? 25, diff --git a/TicketDesk/TicketDesk.PushNotifications/Model/ApplicationPushNotificationSetting.cs b/TicketDesk/TicketDesk.PushNotifications/Model/ApplicationPushNotificationSetting.cs index 68d8c4941..306d87fa7 100644 --- a/TicketDesk/TicketDesk.PushNotifications/Model/ApplicationPushNotificationSetting.cs +++ b/TicketDesk/TicketDesk.PushNotifications/Model/ApplicationPushNotificationSetting.cs @@ -37,6 +37,7 @@ public ApplicationPushNotificationSetting() RetryIntervalMinutes = 2; // ReSharper disable once VirtualMemberCallInConstructor DeliveryProviderSettings = new List(); + BroadcastSettings = new BroadcastSetting(); } [Key] @@ -50,7 +51,7 @@ public ApplicationPushNotificationSetting() [ScaffoldColumn(false)] public string Serialized { - get { return JsonConvert.SerializeObject(this); } + get => JsonConvert.SerializeObject(this); set { if (string.IsNullOrEmpty(value)) @@ -66,13 +67,14 @@ public string Serialized RetryIntervalMinutes = jData.RetryIntervalMinutes; DeliveryProviderSettings = jData.DeliveryProviderSettings; AntiNoiseSettings = jData.AntiNoiseSettings; + BroadcastSettings = jData.BroadcastSettings; } } [NotMapped] [Display(Name = "DeliveryProviders", ResourceType = typeof(Strings))] public virtual ICollection DeliveryProviderSettings { get; set; } - + [NotMapped] [Display(Name = "NotificationsEnabled", Prompt = "NotificationsEnabled_Prompt", ResourceType = typeof(Strings))] public bool IsEnabled { get; set; } @@ -101,6 +103,38 @@ public string Serialized [NotMapped] public AntiNoiseSetting AntiNoiseSettings { get; set; } + [NotMapped] + public BroadcastSetting BroadcastSettings { get; set; } + + + public class BroadcastSetting + { + public BroadcastSetting() + { + BroadcastMode = PushNotificationBroadcastMode.AllStaff; + IsBroadcastEnabled = true; + } + + [NotMapped] + [Display(Name = "BroadcastMode", ResourceType = typeof(Strings))] + [LocalizedDescription("BroadcastMode_Description", NameResourceType = typeof(Strings))] + public PushNotificationBroadcastMode BroadcastMode { get; set; } + + [NotMapped] + [Display(Name = "IsBroadcastEnabled", Prompt = "IsBroadcastEnabled_Prompt", ResourceType = typeof(Strings))] + public bool IsBroadcastEnabled { get; set; } + + [NotMapped] + [EmailAddress] + [Display(Name = "SendToCustomEmailAddress", ResourceType = typeof(Strings))] + [LocalizedDescription("SendToCustomEmailAddress_Description", NameResourceType = typeof(Strings))] + public string SendToCustomEmailAddress { get; set; } + + [NotMapped] + [Display(Name = "SendToCustomEmailDisplayName", ResourceType = typeof(Strings))] + [LocalizedDescription("SendToCustomEmailDisplayName_Description", NameResourceType = typeof(Strings))] + public string SendToCustomEmailDisplayName { get; set; } + } public class PushNotificationDeliveryProviderSetting { @@ -161,5 +195,14 @@ public AntiNoiseSetting() [LocalizedDescription("ExcludeSubscribersOwnEvents_Description", NameResourceType = typeof(Strings))] public bool ExcludeSubscriberEvents { get; set; } } + + public enum PushNotificationBroadcastMode + { + [Display(Name = "PushNotificationBroadcastModeAllStaff", ResourceType = typeof(Strings))] + AllStaff, + [Display(Name = "PushNotificationBroadcastModeCustomAddress", ResourceType = typeof(Strings))] + + CustomAddress + } } } diff --git a/TicketDesk/TicketDesk.PushNotifications/Model/NewTicketPushNotificationInfo.cs b/TicketDesk/TicketDesk.PushNotifications/Model/NewTicketPushNotificationInfo.cs new file mode 100644 index 000000000..18af649f7 --- /dev/null +++ b/TicketDesk/TicketDesk.PushNotifications/Model/NewTicketPushNotificationInfo.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TicketDesk.PushNotifications.Model +{ + public class NewTicketPushNotificationInfo + { + public int TicketId { get; set; } + + + public string MessageContent { get; set; } + + internal IEnumerable ToPushNotificationItems( + SubscriberNotificationSetting userSettings + ) + { + var now = DateTimeOffset.Now; + return userSettings.PushNotificationDestinations.Select(dest => + new PushNotificationItem() + { + ContentSourceId = TicketId, + ContentSourceType = "new ticket", + SubscriberId = "new ticket broadcast", + Destination = dest, + DestinationId = dest.DestinationId, + DeliveryStatus = userSettings.IsEnabled + ? PushNotificationItemStatus.Scheduled + : PushNotificationItemStatus.Disabled, + RetryCount = 0, + CreatedDate = now, + ScheduledSendDate = now, + MessageContent = MessageContent + } + ).ToList(); + + } + } +} diff --git a/TicketDesk/TicketDesk.PushNotifications/Model/PushNotificationDestination.cs b/TicketDesk/TicketDesk.PushNotifications/Model/PushNotificationDestination.cs index 234b211d2..d0b5b8538 100644 --- a/TicketDesk/TicketDesk.PushNotifications/Model/PushNotificationDestination.cs +++ b/TicketDesk/TicketDesk.PushNotifications/Model/PushNotificationDestination.cs @@ -35,6 +35,7 @@ public class PushNotificationDestination [StringLength(50, ErrorMessageResourceName = "FieldMaximumLength", ErrorMessageResourceType = typeof(Validation))] public string DestinationType { get; set; } + [Index("IX_SubscriberDestination", 3, IsUnique = true)] [StringLength(256, ErrorMessageResourceName = "FieldMaximumLength", ErrorMessageResourceType = typeof(Validation))] public string SubscriberId { get; set; } diff --git a/TicketDesk/TicketDesk.PushNotifications/Model/SubscriberNotificationSetting.cs b/TicketDesk/TicketDesk.PushNotifications/Model/SubscriberNotificationSetting.cs index dfd58fa5f..21be42684 100644 --- a/TicketDesk/TicketDesk.PushNotifications/Model/SubscriberNotificationSetting.cs +++ b/TicketDesk/TicketDesk.PushNotifications/Model/SubscriberNotificationSetting.cs @@ -25,6 +25,7 @@ public class SubscriberNotificationSetting public SubscriberNotificationSetting() { IsEnabled = true; + PushNotificationDestinations = new List(); } [Key] diff --git a/TicketDesk/TicketDesk.PushNotifications/TdPushNotificationContext.cs b/TicketDesk/TicketDesk.PushNotifications/TdPushNotificationContext.cs index 96b7ff63a..a214e952e 100644 --- a/TicketDesk/TicketDesk.PushNotifications/TdPushNotificationContext.cs +++ b/TicketDesk/TicketDesk.PushNotifications/TdPushNotificationContext.cs @@ -98,14 +98,26 @@ public ApplicationPushNotificationSetting TicketDeskPushNotificationSettings } } + public bool AddNotifications(IEnumerable infoItems) + { + var userSettings = SubscriberPushNotificationSettings.GetSettingsForUser("new ticket broadcast"); + + foreach (var item in infoItems) + { + PushNotificationItems.AddRange(item.ToPushNotificationItems(userSettings)); + } + return true; + } + public bool AddNotifications(IEnumerable infoItems) { + var appSettings = TicketDeskPushNotificationSettings; + foreach (var item in infoItems) { var citem = item; //foreach closure workaround var userSettings = SubscriberPushNotificationSettings.GetSettingsForUser(citem.SubscriberId); - var appSettings = TicketDeskPushNotificationSettings; - + //get items already in db that haven't been sent yet var existingItems =TicketPushNotificationItems .Include(t => t.PushNotificationItem) diff --git a/TicketDesk/TicketDesk.PushNotifications/TicketDesk.PushNotifications.csproj b/TicketDesk/TicketDesk.PushNotifications/TicketDesk.PushNotifications.csproj index d65f35de1..fe5c9cddb 100644 --- a/TicketDesk/TicketDesk.PushNotifications/TicketDesk.PushNotifications.csproj +++ b/TicketDesk/TicketDesk.PushNotifications/TicketDesk.PushNotifications.csproj @@ -77,6 +77,7 @@ + diff --git a/TicketDesk/TicketDesk.Web.Client/App_Start/Startup.PushNotifications.cs b/TicketDesk/TicketDesk.Web.Client/App_Start/Startup.PushNotifications.cs index cc11646bd..f4c4d0713 100644 --- a/TicketDesk/TicketDesk.Web.Client/App_Start/Startup.PushNotifications.cs +++ b/TicketDesk/TicketDesk.Web.Client/App_Start/Startup.PushNotifications.cs @@ -23,14 +23,17 @@ using TicketDesk.Domain.Model; using TicketDesk.PushNotifications; using TicketDesk.PushNotifications.Migrations; +using TicketDesk.PushNotifications.Model; using TicketDesk.Web.Client.Infrastructure; +using TicketDesk.Web.Identity; +using TicketDesk.Web.Identity.Model; namespace TicketDesk.Web.Client { public partial class Startup { /// - /// Configures the push notifications. + /// Configures the push notifications. /// public static void ConfigurePushNotifications() { @@ -50,14 +53,18 @@ public static void ConfigurePushNotifications() DemoPushNotificationDataManager.SetupDemoPushNotificationData(context); } + if (context.TicketDeskPushNotificationSettings.IsEnabled) { + EnsureBroadcastNotificaitonsConfiguration(context); + //TODO: poor man's detection of appropriate scheduler var siteName = Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME"); var isAzureWebSite = !string.IsNullOrEmpty(siteName); if (!isAzureWebSite) { - InProcessPushNotificationScheduler.Start(context.TicketDeskPushNotificationSettings.DeliveryIntervalMinutes); + InProcessPushNotificationScheduler.Start(context.TicketDeskPushNotificationSettings + .DeliveryIntervalMinutes); } if (context.TicketDeskPushNotificationSettings.IsBackgroundQueueEnabled) @@ -65,22 +72,149 @@ public static void ConfigurePushNotifications() //register for static notifications created event handler TdDomainContext.NotificationsCreated += (sender, notifications) => { - HostingEnvironment.QueueBackgroundWorkItem(CreateNotifications(notifications)); + HostingEnvironment.QueueBackgroundWorkItem(CreateTicketEventNotifications(notifications)); + }; + + TdDomainContext.TicketsCreated += (sender, tickets) => + { + HostingEnvironment.QueueBackgroundWorkItem(CreateNewTicketBroadcastNotification(tickets)); }; } else { TdDomainContext.NotificationsCreated += (sender, notifications) => { - CreateNotifications(notifications)(CancellationToken.None); + CreateTicketEventNotifications(notifications)(CancellationToken.None); + }; + TdDomainContext.TicketsCreated += (sender, tickets) => + { + CreateNewTicketBroadcastNotification(tickets)(CancellationToken.None); }; } - context.Dispose();//ensure that no one accidentally holds a reference to this in closure + } + context.Dispose(); //ensure that no one accidentally holds a reference to this in closure + } + + private static void EnsureBroadcastNotificaitonsConfiguration(TdPushNotificationContext context) + { + var broadcastUserSettings = context.SubscriberPushNotificationSettingsManager + .GetSettingsForSubscriberAsync("new ticket broadcast").Result; + var broadcastAppSettings = context.TicketDeskPushNotificationSettings.BroadcastSettings; + if (broadcastUserSettings == null) + { + broadcastUserSettings = new SubscriberNotificationSetting + { + SubscriberId = "new ticket broadcast" + }; + context.SubscriberPushNotificationSettingsManager.AddSettingsForSubscriber(broadcastUserSettings); } + if + ( + broadcastAppSettings.BroadcastMode == + ApplicationPushNotificationSetting.PushNotificationBroadcastMode.CustomAddress + && + !string.IsNullOrEmpty(broadcastAppSettings.SendToCustomEmailAddress) + ) + { + broadcastUserSettings.PushNotificationDestinations.Add( + new PushNotificationDestination + { + SubscriberId = "new ticket broadcast", + DestinationAddress = broadcastAppSettings.SendToCustomEmailAddress, + DestinationType = "email", + SubscriberName = broadcastAppSettings.SendToCustomEmailDisplayName + } + ); + } + else + { + + + var userManager = DependencyResolver.Current.GetService(); + var roleManager = DependencyResolver.Current.GetService(); + var usersForNotification = roleManager.GetTdHelpDeskUsers(userManager) + .Union(roleManager.GetTdTdAdministrators(userManager)) + .Distinct(new UniqueNameEmailDisplayUserEqualityComparer()).ToArray(); + + + var existingDestinations = broadcastUserSettings + .PushNotificationDestinations + .Where(pnd => pnd.DestinationType == "email" && pnd.SubscriberId == "new ticket broadcast") + .ToArray(); + //remove users not in list anymore + var usersToRemove = existingDestinations + .Where(us => !usersForNotification + .Any(un => un.Email == us.DestinationAddress && un.DisplayName == us.SubscriberName)) + .ToList(); + foreach (var us in usersToRemove) + { + broadcastUserSettings.PushNotificationDestinations.Remove(us); + } + + //add users in list, but not already in destinations + foreach (var nu in usersForNotification) + { + if (!existingDestinations.Any(ed => nu.Email == ed.DestinationAddress && + nu.DisplayName == ed.SubscriberName)) + { + broadcastUserSettings.PushNotificationDestinations.Add( + new PushNotificationDestination + { + SubscriberId = "new ticket broadcast", + DestinationAddress = nu.Email, + DestinationType = "email", + SubscriberName = nu.DisplayName + } + ); + } + } + } + broadcastUserSettings.IsEnabled = broadcastAppSettings.IsBroadcastEnabled; + context.SaveChanges(); + } + + + private static Action CreateNewTicketBroadcastNotification(IEnumerable tickets) + { + return ct => + { + try + { + var notificationIds = tickets.Select(t => + t.TicketEvents.First(te => te.ForActivity == TicketActivity.Create || + te.ForActivity == TicketActivity.CreateOnBehalfOf).EventId) + .ToArray(); + + var domainContext = DependencyResolver.Current.GetService(); + var multiProject = domainContext.Projects.Count() > 1; + var notes = domainContext.TicketEventNotifications + .Include(t => t.TicketEvent) + .Include(t => t.TicketEvent.Ticket) + .Include(t => t.TicketEvent.Ticket.Project) + .Where(t => notificationIds.Contains(t.EventId)) + .ToArray(); + + if (notes.Any()) + { + using (var noteContext = new TdPushNotificationContext()) + { + var newNoteEvents = notes.ToNewTicketPushNotificationInfoCollection(multiProject); + noteContext.AddNotifications(newNoteEvents); + + noteContext.SaveChanges(); + } + } + } + catch + { + //TODO: Log this somewhere + } + }; } - private static Action CreateNotifications(IEnumerable notifications) + private static Action CreateTicketEventNotifications( + IEnumerable notifications) { return ct => { diff --git a/TicketDesk/TicketDesk.Web.Client/Controllers/DataManagementController.cs b/TicketDesk/TicketDesk.Web.Client/Controllers/DataManagementController.cs index 13421c1e0..d6a7425cb 100644 --- a/TicketDesk/TicketDesk.Web.Client/Controllers/DataManagementController.cs +++ b/TicketDesk/TicketDesk.Web.Client/Controllers/DataManagementController.cs @@ -11,6 +11,7 @@ // attribution must remain intact, and a copy of the license must be // provided to the recipient. +using System.Threading.Tasks; using System.Web.Mvc; using Microsoft.AspNet.Identity; using TicketDesk.Domain; @@ -56,6 +57,8 @@ public ActionResult RemoveDemoData() DemoIdentityDataManager.RemoveIdentity(IdentityContext, User.Identity.GetUserId()); DemoPushNotificationDataManager.RemoveAllPushNotificationData(PushNotificationContext); ViewBag.DemoDataRemoved = true; + Task.Delay(500).ContinueWith(t => System.Web.HttpRuntime.UnloadAppDomain()).ConfigureAwait(false); + return View("Demo"); } @@ -69,6 +72,8 @@ public ActionResult CreateDemoData() DemoIdentityDataManager.SetupDemoIdentityData(IdentityContext, User.Identity.GetUserId()); DemoPushNotificationDataManager.SetupDemoPushNotificationData(PushNotificationContext); ViewBag.DemoDataCreated = true; + Task.Delay(500).ContinueWith(t => System.Web.HttpRuntime.UnloadAppDomain()).ConfigureAwait(false); + return View("Demo"); } } diff --git a/TicketDesk/TicketDesk.Web.Client/Controllers/PushNotificationSettingsController.cs b/TicketDesk/TicketDesk.Web.Client/Controllers/PushNotificationSettingsController.cs index b9f7a70d1..25b9b3131 100644 --- a/TicketDesk/TicketDesk.Web.Client/Controllers/PushNotificationSettingsController.cs +++ b/TicketDesk/TicketDesk.Web.Client/Controllers/PushNotificationSettingsController.cs @@ -36,6 +36,8 @@ public class PushNotificationSettingsController : Controller public PushNotificationSettingsController(TdPushNotificationContext noteContext, TdDomainContext domainContext) { + ViewBag.Saved = false; + NoteContext = noteContext; DomainContext = domainContext; } @@ -60,6 +62,7 @@ public ActionResult Index(ApplicationPushNotificationSetting settings, string si NoteContext.SaveChanges(); DomainContext.TicketDeskSettings.ClientSettings.Settings["DefaultSiteRootUrl"] = siteRootUrl.TrimEnd('/'); DomainContext.SaveChanges(); + ViewBag.Saved = true; } ViewBag.CurrentRootUrl = GetCurrentRootUrl(); ViewBag.SiteRootUrl = GetRootUrlSetting(); diff --git a/TicketDesk/TicketDesk.Web.Client/Models/Extensions/TicketEventNotificationExtensions.cs b/TicketDesk/TicketDesk.Web.Client/Models/Extensions/TicketEventNotificationExtensions.cs index c49499db7..5fc1baed8 100644 --- a/TicketDesk/TicketDesk.Web.Client/Models/Extensions/TicketEventNotificationExtensions.cs +++ b/TicketDesk/TicketDesk.Web.Client/Models/Extensions/TicketEventNotificationExtensions.cs @@ -43,30 +43,45 @@ private static string RootUrl } + public static IEnumerable ToNewTicketPushNotificationInfoCollection( + this IEnumerable eventNotifications, + bool multiProject + ) + { + return eventNotifications.Select(note => new NewTicketPushNotificationInfo() + { + TicketId = note.TicketId, + MessageContent = GetEmailForNote(note, multiProject, true) + }); + } + public static IEnumerable ToNotificationEventInfoCollection( - this IEnumerable eventNotifications, bool subscriberExclude, bool multiProject) + this IEnumerable eventNotifications, + bool subscriberExclude, + bool multiProject) { - return eventNotifications.Select(note => + return eventNotifications.Select(note => new TicketPushNotificationEventInfo() { - - return new TicketPushNotificationEventInfo() - { - TicketId = note.TicketId, - SubscriberId = note.SubscriberId, - EventId = note.EventId, - CancelNotification = subscriberExclude && note.IsRead, - MessageContent = GetEmailForNote(note, multiProject) - }; - + TicketId = note.TicketId, + SubscriberId = note.SubscriberId, + EventId = note.EventId, + CancelNotification = subscriberExclude && note.IsRead, + MessageContent = GetEmailForNote(note, multiProject) }); } - private static string GetEmailForNote(TicketEventNotification note, bool multiProject) + private static string GetEmailForNote(TicketEventNotification note, bool multiProject, bool forNewTicket = false) { - var email = new TicketEmail { Ticket = note.TicketEvent.Ticket, SiteRootUrl = RootUrl, IsMultiProject = multiProject }; + var email = new TicketEmail + { + Ticket = note.TicketEvent.Ticket, + SiteRootUrl = RootUrl, + IsMultiProject = multiProject, + ForNewTicket = forNewTicket + }; var mailService = new EmailService(); SerializableMailMessage message = mailService.CreateMailMessage(email); using (var ms = new MemoryStream()) diff --git a/TicketDesk/TicketDesk.Web.Client/Models/TicketEmail.cs b/TicketDesk/TicketDesk.Web.Client/Models/TicketEmail.cs index a5ab4dfbe..308a71f2e 100644 --- a/TicketDesk/TicketDesk.Web.Client/Models/TicketEmail.cs +++ b/TicketDesk/TicketDesk.Web.Client/Models/TicketEmail.cs @@ -13,5 +13,7 @@ public class TicketEmail : Email public string SiteRootUrl { get; set; } public bool IsMultiProject { get; set; } + + public bool ForNewTicket { get; set; } } } \ No newline at end of file diff --git a/TicketDesk/TicketDesk.Web.Client/Views/Emails/Ticket.cshtml b/TicketDesk/TicketDesk.Web.Client/Views/Emails/Ticket.cshtml index 61a7670e7..8b4da88fd 100644 --- a/TicketDesk/TicketDesk.Web.Client/Views/Emails/Ticket.cshtml +++ b/TicketDesk/TicketDesk.Web.Client/Views/Emails/Ticket.cshtml @@ -15,5 +15,5 @@ @using TicketDesk.Localization.Views.Emails @model TicketDesk.Web.Client.Models.TicketEmail -Subject: @string.Format(Strings.Subject, Model.Ticket.TicketId, Model.Ticket.Title) +Subject: @((Model.ForNewTicket)? string.Format("[{0}]", Strings.New) : string.Empty) @string.Format(Strings.Subject, Model.Ticket.TicketId, Model.Ticket.Title) Views: Text, Html diff --git a/TicketDesk/TicketDesk.Web.Client/Views/PushNotificationSettings/Index.cshtml b/TicketDesk/TicketDesk.Web.Client/Views/PushNotificationSettings/Index.cshtml index f6bb61b2d..339a8f11e 100644 --- a/TicketDesk/TicketDesk.Web.Client/Views/PushNotificationSettings/Index.cshtml +++ b/TicketDesk/TicketDesk.Web.Client/Views/PushNotificationSettings/Index.cshtml @@ -37,6 +37,7 @@ $.validator.methods.url = function (value, element) { return this.optional(element) || (/^https?:\/\/\w+(\.\w+)*(:[0-9]+)?\/?(\/[.\w]*)*$/).test(value); }; + }); } @@ -54,13 +55,30 @@ @Html.Raw(Index.DemoMode_Alert) } + else { + if (ViewBag.Saved) + { +
+ @Html.Raw(Index.Saved_Alert) +
The server has been restarted, there may be a brief delay while the system re-initializes.
+
+ } using (Html.BeginForm("Index", "PushNotificationSettings", FormMethod.Post, new { @class = "form-horizontal" })) { @Html.AntiForgeryToken()
+ +
+

@Index.Delivery_Header

+
+
+ @Html.Action("ListDeliveryProviderSettings") +
+ +

@Index.General_Header

@@ -83,8 +101,8 @@ }
- -
+ +
@Html.LabelFor(model => model.IsBackgroundQueueEnabled, new { @class = "col-md-4 col-sm-4 control-label" })
@Html.DescriptionFor(model => model.IsBackgroundQueueEnabled, "help-block", "span") - +
@@ -106,7 +124,7 @@ type="url" data-val="true" data-val-url="@Index.SiteRootUrl_Validation" - value="@ViewBag.SiteRootUrl"/> + value="@ViewBag.SiteRootUrl" />
+ + + + +
+

@Index.BroadcastNotification_Header

+
+
+
+ @Html.LabelFor(model => model.BroadcastSettings.IsBroadcastEnabled, new { @class = "col-md-4 col-sm-4 control-label" }) +
+ + @Html.DescriptionFor(model => model.BroadcastSettings.IsBroadcastEnabled, "help-block", "span") + +
+
+
+ @Html.LabelFor(model => model.BroadcastSettings.BroadcastMode, new { @class = "col-md-4 col-sm-4 control-label" }) +
+ + @Html.EnumDropDownListFor(model => model.BroadcastSettings.BroadcastMode, new { @class = "form-control" }) + + @Html.DescriptionFor(model => model.BroadcastSettings.BroadcastMode, "help-block", "span") +
+
+
+ @Html.LabelFor(model => model.BroadcastSettings.SendToCustomEmailDisplayName, new { @class = "col-md-4 col-sm-4 control-label" }) +
+ @Html.EditorFor(model => model.BroadcastSettings.SendToCustomEmailDisplayName, new { htmlAttributes = new { @class = "form-control input-sm" } }) + + @Html.DescriptionFor(model => model.BroadcastSettings.SendToCustomEmailDisplayName, "help-block", "span") +
+
+
+ @Html.LabelFor(model => model.BroadcastSettings.SendToCustomEmailAddress, new { @class = "col-md-4 col-sm-4 control-label" }) +
+ @Html.EditorFor(model => model.BroadcastSettings.SendToCustomEmailAddress, new { htmlAttributes = new { @class = "form-control input-sm" } }) + @Html.ValidationMessageFor(model => model.BroadcastSettings.SendToCustomEmailDisplayName, "", new { @class = "text-danger" }) + + @Html.DescriptionFor(model => model.BroadcastSettings.SendToCustomEmailAddress, "help-block", "span") +
+
+ +
+ + + + +

@Index.AntiNoise_Header

@@ -192,12 +262,7 @@ -
-

@Index.Delivery_Header

-
-
- @Html.Action("ListDeliveryProviderSettings") -
+