From 67f1c2a5d17884e41464c9ef2632d9dc8c160773 Mon Sep 17 00:00:00 2001 From: Gary Sharp Date: Thu, 6 Feb 2025 19:14:36 +1100 Subject: [PATCH] refactor: make exporting consistent --- .../Modules/DeviceProfilesConfiguration.cs | 2 +- .../Modules/DevicesConfiguration.cs | 8 +- .../Modules/DocumentsConfiguration.cs | 2 +- .../Modules/JobPreferencesConfiguration.cs | 56 ++-- .../OrganisationAddressesConfiguration.cs | 5 +- .../Configuration/SystemConfiguration.cs | 2 +- Disco.Models/Disco.Models.csproj | 11 +- Disco.Models/Exporting/IExportOptions.cs | 6 +- .../{Exporting => }/DeviceExportOptions.cs | 10 +- .../{Exporting => }/DeviceExportRecord.cs | 2 +- .../{Exporting => }/DeviceExportTypes.cs | 2 +- .../DeviceFlags/DeviceFlagExportOptions.cs | 4 +- .../Jobs/{Exporting => }/JobExportOptions.cs | 10 +- .../Jobs/{Exporting => }/JobExportRecord.cs | 2 +- .../Services/Logging/LogExportOptions.cs | 19 ++ .../Users/UserFlags/UserFlagExportOptions.cs | 4 +- .../DeviceFlag/ConfigDeviceFlagExportModel.cs | 5 +- .../UserFlag/ConfigUserFlagExportModel.cs | 5 +- Disco.Models/UI/Device/DeviceExportModel.cs | 7 +- Disco.Models/UI/Job/JobExportModel.cs | 7 +- ...DeviceExport.cs => DeviceExportContext.cs} | 205 +++++++------ ...agExport.cs => DeviceFlagExportContext.cs} | 75 ++--- .../DeviceFlags/DeviceFlagExportTask.cs | 46 --- .../Devices/Exporting/DeviceExportTask.cs | 44 --- Disco.Services/Disco.Services.csproj | 18 +- Disco.Services/Exporting/ExportTask.cs | 76 +++++ Disco.Services/Exporting/ExportTaskContext.cs | 15 +- .../{ExportHelpers.cs => Exporter.cs} | 54 +++- Disco.Services/Exporting/IExportContext.cs | 39 +++ .../Jobs/Exporting/JobExportTask.cs | 44 --- .../JobExport.cs => JobExportContext.cs} | 275 +++++++++--------- .../{LogExport.cs => LogExportContext.cs} | 73 +++-- ...FlagExport.cs => UserFlagExportContext.cs} | 82 +++--- .../Users/UserFlags/UserFlagExportTask.cs | 46 --- .../Areas/API/Controllers/DeviceController.cs | 38 +-- .../API/Controllers/DeviceFlagController.cs | 35 +-- .../Areas/API/Controllers/JobController.cs | 33 +-- .../API/Controllers/LoggingController.cs | 51 ++-- .../API/Controllers/UserFlagController.cs | 34 +-- .../Controllers/DeviceFlagController.cs | 22 +- .../Config/Controllers/UserFlagController.cs | 22 +- .../Config/Models/DeviceFlag/ExportModel.cs | 5 +- .../Config/Models/UserFlag/ExportModel.cs | 5 +- .../Config/Views/DeviceBatch/Show.cshtml | 2 +- .../Views/DeviceBatch/Show.generated.cs | 4 +- .../Config/Views/DeviceFlag/Export.cshtml | 8 +- .../Views/DeviceFlag/Export.generated.cs | 14 +- .../Config/Views/DeviceModel/Show.cshtml | 2 +- .../Views/DeviceModel/Show.generated.cs | 4 +- .../Config/Views/DeviceProfile/Show.cshtml | 2 +- .../Views/DeviceProfile/Show.generated.cs | 4 +- .../Areas/Config/Views/UserFlag/Export.cshtml | 8 +- .../Config/Views/UserFlag/Export.generated.cs | 14 +- Disco.Web/Controllers/DeviceController.cs | 23 +- Disco.Web/Controllers/JobController.cs | 18 +- .../T4MVC/API.DeviceController.generated.cs | 10 +- .../API.DeviceFlagController.generated.cs | 10 +- .../T4MVC/API.JobController.generated.cs | 4 +- .../T4MVC/API.UserFlagController.generated.cs | 10 +- .../Config.DeviceFlagController.generated.cs | 18 +- .../Config.UserFlagController.generated.cs | 18 +- .../T4MVC/DeviceController.generated.cs | 18 +- .../T4MVC/JobController.generated.cs | 10 +- Disco.Web/Models/Device/ExportModel.cs | 7 +- Disco.Web/Models/Job/ExportModel.cs | 7 +- Disco.Web/Views/Device/Export.cshtml | 10 +- Disco.Web/Views/Device/Export.generated.cs | 76 ++--- Disco.Web/Views/Job/Export.cshtml | 8 +- Disco.Web/Views/Job/Export.generated.cs | 14 +- 69 files changed, 908 insertions(+), 921 deletions(-) rename Disco.Models/Services/Devices/{Exporting => }/DeviceExportOptions.cs (97%) rename Disco.Models/Services/Devices/{Exporting => }/DeviceExportRecord.cs (98%) rename Disco.Models/Services/Devices/{Exporting => }/DeviceExportTypes.cs (67%) rename Disco.Models/Services/Jobs/{Exporting => }/JobExportOptions.cs (98%) rename Disco.Models/Services/Jobs/{Exporting => }/JobExportRecord.cs (97%) create mode 100644 Disco.Models/Services/Logging/LogExportOptions.cs rename Disco.Services/Devices/{Exporting/DeviceExport.cs => DeviceExportContext.cs} (92%) rename Disco.Services/Devices/DeviceFlags/{DeviceFlagExport.cs => DeviceFlagExportContext.cs} (88%) delete mode 100644 Disco.Services/Devices/DeviceFlags/DeviceFlagExportTask.cs delete mode 100644 Disco.Services/Devices/Exporting/DeviceExportTask.cs create mode 100644 Disco.Services/Exporting/ExportTask.cs rename Disco.Services/Exporting/{ExportHelpers.cs => Exporter.cs} (56%) create mode 100644 Disco.Services/Exporting/IExportContext.cs delete mode 100644 Disco.Services/Jobs/Exporting/JobExportTask.cs rename Disco.Services/Jobs/{Exporting/JobExport.cs => JobExportContext.cs} (83%) rename Disco.Services/Logging/{LogExport.cs => LogExportContext.cs} (60%) rename Disco.Services/Users/UserFlags/{UserFlagExport.cs => UserFlagExportContext.cs} (80%) delete mode 100644 Disco.Services/Users/UserFlags/UserFlagExportTask.cs diff --git a/Disco.Data/Configuration/Modules/DeviceProfilesConfiguration.cs b/Disco.Data/Configuration/Modules/DeviceProfilesConfiguration.cs index 717f433c..37916ced 100644 --- a/Disco.Data/Configuration/Modules/DeviceProfilesConfiguration.cs +++ b/Disco.Data/Configuration/Modules/DeviceProfilesConfiguration.cs @@ -7,7 +7,7 @@ namespace Disco.Data.Configuration.Modules { public DeviceProfilesConfiguration(DiscoDataContext Database) : base(Database) { } - public override string Scope { get { return "DeviceProfiles"; } } + public override string Scope { get; } = "DeviceProfiles"; public int DefaultDeviceProfileId { diff --git a/Disco.Data/Configuration/Modules/DevicesConfiguration.cs b/Disco.Data/Configuration/Modules/DevicesConfiguration.cs index 23f0e69e..b38fed40 100644 --- a/Disco.Data/Configuration/Modules/DevicesConfiguration.cs +++ b/Disco.Data/Configuration/Modules/DevicesConfiguration.cs @@ -1,5 +1,5 @@ using Disco.Data.Repository; -using Disco.Models.Services.Devices.Exporting; +using Disco.Models.Services.Devices; namespace Disco.Data.Configuration.Modules { @@ -7,12 +7,12 @@ namespace Disco.Data.Configuration.Modules { public DevicesConfiguration(DiscoDataContext Database) : base(Database) { } - public override string Scope { get { return "Devices"; } } + public override string Scope { get; } = "Devices"; public DeviceExportOptions LastExportOptions { - get { return this.Get(DeviceExportOptions.DefaultOptions()); } - set { this.Set(value); } + get => Get(DeviceExportOptions.DefaultOptions()); + set => Set(value); } } } diff --git a/Disco.Data/Configuration/Modules/DocumentsConfiguration.cs b/Disco.Data/Configuration/Modules/DocumentsConfiguration.cs index e8cd960c..4fde929a 100644 --- a/Disco.Data/Configuration/Modules/DocumentsConfiguration.cs +++ b/Disco.Data/Configuration/Modules/DocumentsConfiguration.cs @@ -8,7 +8,7 @@ namespace Disco.Data.Configuration.Modules { public DocumentsConfiguration(DiscoDataContext Database) : base(Database) { } - public override string Scope { get { return "Documents"; } } + public override string Scope { get; } = "Documents"; public List Packages { diff --git a/Disco.Data/Configuration/Modules/JobPreferencesConfiguration.cs b/Disco.Data/Configuration/Modules/JobPreferencesConfiguration.cs index 7a88628e..174a9fc0 100644 --- a/Disco.Data/Configuration/Modules/JobPreferencesConfiguration.cs +++ b/Disco.Data/Configuration/Modules/JobPreferencesConfiguration.cs @@ -1,6 +1,5 @@ using Disco.Data.Repository; using Disco.Models.Services.Jobs; -using Disco.Models.Services.Jobs.Exporting; using System; using System.Collections.Generic; @@ -10,15 +9,15 @@ namespace Disco.Data.Configuration.Modules { public JobPreferencesConfiguration(DiscoDataContext Database) : base(Database) { } - public override string Scope { get { return "JobPreferences"; } } + public override string Scope { get; } = "JobPreferences"; /// /// Initial comments template for new jobs /// public string InitialCommentsTemplate { - get { return Get(null); } - set { Set(value); } + get => Get(null); + set => Set(value); } /// @@ -26,11 +25,11 @@ namespace Disco.Data.Configuration.Modules /// public int LongRunningJobDaysThreshold { - get { return Get(7); } + get => Get(7); set { if (value < 0) - throw new ArgumentOutOfRangeException("value", "The Long Running Job Days Threshold cannot be less than zero"); + throw new ArgumentOutOfRangeException(nameof(value), "The Long Running Job Days Threshold cannot be less than zero"); Set(value); } @@ -41,11 +40,11 @@ namespace Disco.Data.Configuration.Modules /// public int StaleJobMinutesThreshold { - get { return Get(60 * 24 * 2); } // Default to 48 Hours (2 days) + get => Get(60 * 24 * 2); // Default to 48 Hours (2 days) set { if (value < 0) - throw new ArgumentOutOfRangeException("value", "The Stale Job Minutes Threshold cannot be less than zero"); + throw new ArgumentOutOfRangeException(nameof(value), "The Stale Job Minutes Threshold cannot be less than zero"); Set(value); } @@ -53,8 +52,8 @@ namespace Disco.Data.Configuration.Modules public bool LodgmentIncludeAllAttachmentsByDefault { - get { return Get(false); } - set { Set(value); } + get => Get(false); + set => Set(value); } /// @@ -63,11 +62,11 @@ namespace Disco.Data.Configuration.Modules /// public string DefaultNoticeboardTheme { - get { return Get("default"); } + get => Get("default"); set { if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentNullException("DefaultNoticeboardTheme"); + throw new ArgumentNullException(nameof(DefaultNoticeboardTheme)); Set(value); } @@ -75,47 +74,48 @@ namespace Disco.Data.Configuration.Modules public LocationModes LocationMode { - get { return Get(LocationModes.Unrestricted); } - set { Set(value); } + get => Get(LocationModes.Unrestricted); + set => Set(value); } public List LocationList { - get { return Get(new List()); } - set { Set(value); } + get => Get(new List()); + set => Set(value); } public string OnCreateExpression { - get { return Get(null); } - set { Set(value); } + get => Get(null); + set => Set(value); } public string OnDeviceReadyForReturnExpression { - get { return Get(null); } - set { Set(value); } + get => Get(null); + set => Set(value); } public string OnCloseExpression { - get { return Get(null); } - set { Set(value); } + get => Get(null); + set => Set(value); } public JobExportOptions LastExportOptions { - get { return this.Get(JobExportOptions.DefaultOptions()); } - set { - this.Set(value); - this.LastExportDate = DateTime.Now; + get => Get(JobExportOptions.DefaultOptions()); + set + { + Set(value); + LastExportDate = DateTime.Now; } } public DateTime? LastExportDate { - get { return this.Get(null); } - set { this.Set(value); } + get => Get(null); + set => Set(value); } } } diff --git a/Disco.Data/Configuration/Modules/OrganisationAddressesConfiguration.cs b/Disco.Data/Configuration/Modules/OrganisationAddressesConfiguration.cs index 8d7c94d9..52e71898 100644 --- a/Disco.Data/Configuration/Modules/OrganisationAddressesConfiguration.cs +++ b/Disco.Data/Configuration/Modules/OrganisationAddressesConfiguration.cs @@ -1,6 +1,5 @@ using Disco.Data.Repository; using Disco.Models.BI.Config; -using Newtonsoft.Json; using System.Collections.Generic; using System.Linq; @@ -8,11 +7,9 @@ namespace Disco.Data.Configuration.Modules { public class OrganisationAddressesConfiguration : ConfigurationBase { - private const string scope = "OrganisationAddresses"; - public OrganisationAddressesConfiguration(DiscoDataContext Database) : base(Database) { } - public override string Scope { get { return scope; } } + public override string Scope { get; } = "OrganisationAddresses"; public OrganisationAddress GetAddress(int Id) { diff --git a/Disco.Data/Configuration/SystemConfiguration.cs b/Disco.Data/Configuration/SystemConfiguration.cs index 30ad8409..cbfed83d 100644 --- a/Disco.Data/Configuration/SystemConfiguration.cs +++ b/Disco.Data/Configuration/SystemConfiguration.cs @@ -83,7 +83,7 @@ namespace Disco.Data.Configuration #endregion - public override string Scope { get { return "System"; } } + public override string Scope { get; } = "System"; public string DataStoreLocation { diff --git a/Disco.Models/Disco.Models.csproj b/Disco.Models/Disco.Models.csproj index d16c08e6..125dd19e 100644 --- a/Disco.Models/Disco.Models.csproj +++ b/Disco.Models/Disco.Models.csproj @@ -83,8 +83,8 @@ - - + + @@ -137,10 +137,10 @@ - + - - + + @@ -159,6 +159,7 @@ + diff --git a/Disco.Models/Exporting/IExportOptions.cs b/Disco.Models/Exporting/IExportOptions.cs index 43ac02d2..1e3f2f0a 100644 --- a/Disco.Models/Exporting/IExportOptions.cs +++ b/Disco.Models/Exporting/IExportOptions.cs @@ -4,9 +4,7 @@ namespace Disco.Models.Services.Exporting { public interface IExportOptions { - ExportFormat Format { get; } - string FilenamePrefix { get; } - string ExcelWorksheetName { get; } - string ExcelTableName { get; } + int Version { get; set; } + ExportFormat Format { get; set; } } } diff --git a/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs b/Disco.Models/Services/Devices/DeviceExportOptions.cs similarity index 97% rename from Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs rename to Disco.Models/Services/Devices/DeviceExportOptions.cs index 3dd46069..c4371cae 100644 --- a/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs +++ b/Disco.Models/Services/Devices/DeviceExportOptions.cs @@ -2,17 +2,15 @@ using Disco.Models.Services.Exporting; using System.ComponentModel.DataAnnotations; -namespace Disco.Models.Services.Devices.Exporting +namespace Disco.Models.Services.Devices { public class DeviceExportOptions : IExportOptions { + public int Version { get; set; } = 1; + public ExportFormat Format { get; set; } + public DeviceExportTypes ExportType { get; set; } public int? ExportTypeTargetId { get; set; } - - public ExportFormat Format { get; set; } - public string FilenamePrefix { get; } = "DiscoDeviceExport"; - public string ExcelWorksheetName { get; } = "DeviceExport"; - public string ExcelTableName { get; } = "Devices"; // Device [Display(ShortName = "Device", Name = "Serial Number", Description = "The device serial number")] diff --git a/Disco.Models/Services/Devices/Exporting/DeviceExportRecord.cs b/Disco.Models/Services/Devices/DeviceExportRecord.cs similarity index 98% rename from Disco.Models/Services/Devices/Exporting/DeviceExportRecord.cs rename to Disco.Models/Services/Devices/DeviceExportRecord.cs index f7b71997..312ae995 100644 --- a/Disco.Models/Services/Devices/Exporting/DeviceExportRecord.cs +++ b/Disco.Models/Services/Devices/DeviceExportRecord.cs @@ -4,7 +4,7 @@ using Disco.Models.Repository; using System; using System.Collections.Generic; -namespace Disco.Models.Services.Devices.Exporting +namespace Disco.Models.Services.Devices { public class DeviceExportRecord : IExportRecord { diff --git a/Disco.Models/Services/Devices/Exporting/DeviceExportTypes.cs b/Disco.Models/Services/Devices/DeviceExportTypes.cs similarity index 67% rename from Disco.Models/Services/Devices/Exporting/DeviceExportTypes.cs rename to Disco.Models/Services/Devices/DeviceExportTypes.cs index d92a7027..dfb98517 100644 --- a/Disco.Models/Services/Devices/Exporting/DeviceExportTypes.cs +++ b/Disco.Models/Services/Devices/DeviceExportTypes.cs @@ -1,4 +1,4 @@ -namespace Disco.Models.Services.Devices.Exporting +namespace Disco.Models.Services.Devices { public enum DeviceExportTypes { diff --git a/Disco.Models/Services/Devices/DeviceFlags/DeviceFlagExportOptions.cs b/Disco.Models/Services/Devices/DeviceFlags/DeviceFlagExportOptions.cs index a539fc1f..d275b30d 100644 --- a/Disco.Models/Services/Devices/DeviceFlags/DeviceFlagExportOptions.cs +++ b/Disco.Models/Services/Devices/DeviceFlags/DeviceFlagExportOptions.cs @@ -7,10 +7,8 @@ namespace Disco.Models.Services.Devices.DeviceFlag { public class DeviceFlagExportOptions : IExportOptions { + public int Version { get; set; } = 1; public ExportFormat Format { get; set; } - public string FilenamePrefix { get; } = "DiscoDeviceFlagExport"; - public string ExcelWorksheetName { get; } = "DeviceFlagExport"; - public string ExcelTableName { get; } = "DeviceFlags"; [Required] public List DeviceFlagIds { get; set; } = new List(); diff --git a/Disco.Models/Services/Jobs/Exporting/JobExportOptions.cs b/Disco.Models/Services/Jobs/JobExportOptions.cs similarity index 98% rename from Disco.Models/Services/Jobs/Exporting/JobExportOptions.cs rename to Disco.Models/Services/Jobs/JobExportOptions.cs index 47044a68..1beb7ad8 100644 --- a/Disco.Models/Services/Jobs/Exporting/JobExportOptions.cs +++ b/Disco.Models/Services/Jobs/JobExportOptions.cs @@ -4,10 +4,13 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -namespace Disco.Models.Services.Jobs.Exporting +namespace Disco.Models.Services.Jobs { public class JobExportOptions : IExportOptions { + public int Version { get; set; } = 1; + public ExportFormat Format { get; set; } + [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true, ConvertEmptyStringToNull = true, HtmlEncode = false)] public DateTime FilterStartDate { get; set; } [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true, ConvertEmptyStringToNull = true, HtmlEncode = false)] @@ -17,11 +20,6 @@ namespace Disco.Models.Services.Jobs.Exporting public List FilterJobSubTypeIds { get; set; } public int? FilterJobQueueId { get; set; } - public ExportFormat Format { get; set; } - public string FilenamePrefix { get; } = "DiscoJobExport"; - public string ExcelWorksheetName { get; } = "JobExport"; - public string ExcelTableName { get; } = "Jobs"; - // Job [Display(ShortName = "Job", Name = "Identifier", Description = "The identifier of the job")] public bool JobId { get; set; } diff --git a/Disco.Models/Services/Jobs/Exporting/JobExportRecord.cs b/Disco.Models/Services/Jobs/JobExportRecord.cs similarity index 97% rename from Disco.Models/Services/Jobs/Exporting/JobExportRecord.cs rename to Disco.Models/Services/Jobs/JobExportRecord.cs index 9df3186f..c8ac9086 100644 --- a/Disco.Models/Services/Jobs/Exporting/JobExportRecord.cs +++ b/Disco.Models/Services/Jobs/JobExportRecord.cs @@ -3,7 +3,7 @@ using Disco.Models.Repository; using System; using System.Collections.Generic; -namespace Disco.Models.Services.Jobs.Exporting +namespace Disco.Models.Services.Jobs { public class JobExportRecord : IExportRecord { diff --git a/Disco.Models/Services/Logging/LogExportOptions.cs b/Disco.Models/Services/Logging/LogExportOptions.cs new file mode 100644 index 00000000..2175afd1 --- /dev/null +++ b/Disco.Models/Services/Logging/LogExportOptions.cs @@ -0,0 +1,19 @@ +using Disco.Models.Exporting; +using Disco.Models.Services.Exporting; +using System; +using System.Collections.Generic; + +namespace Disco.Models.Services.Logging +{ + public class LogExportOptions : IExportOptions + { + public int Version { get; set; } = 1; + public ExportFormat Format { get; set; } + + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public int? ModuleId { get; set; } + public List EventTypeIds { get; set; } + public int? Take { get; set; } + } +} diff --git a/Disco.Models/Services/Users/UserFlags/UserFlagExportOptions.cs b/Disco.Models/Services/Users/UserFlags/UserFlagExportOptions.cs index 733f0303..d48c2420 100644 --- a/Disco.Models/Services/Users/UserFlags/UserFlagExportOptions.cs +++ b/Disco.Models/Services/Users/UserFlags/UserFlagExportOptions.cs @@ -7,10 +7,8 @@ namespace Disco.Models.Services.Users.UserFlags { public class UserFlagExportOptions : IExportOptions { + public int Version { get; set; } = 1; public ExportFormat Format { get; set; } - public string FilenamePrefix { get; } = "DiscoUserFlagExport"; - public string ExcelWorksheetName { get; } = "UserFlagExport"; - public string ExcelTableName { get; } = "UserFlags"; [Required] public List UserFlagIds { get; set; } = new List(); diff --git a/Disco.Models/UI/Config/DeviceFlag/ConfigDeviceFlagExportModel.cs b/Disco.Models/UI/Config/DeviceFlag/ConfigDeviceFlagExportModel.cs index 9b15b0c2..9ff236c9 100644 --- a/Disco.Models/UI/Config/DeviceFlag/ConfigDeviceFlagExportModel.cs +++ b/Disco.Models/UI/Config/DeviceFlag/ConfigDeviceFlagExportModel.cs @@ -1,6 +1,7 @@ using Disco.Models.Services.Devices.DeviceFlag; using Disco.Models.Services.Exporting; using Disco.Models.UI; +using System; using System.Collections.Generic; namespace Disco.Models.Areas.Config.UI.DeviceFlag @@ -9,8 +10,8 @@ namespace Disco.Models.Areas.Config.UI.DeviceFlag { DeviceFlagExportOptions Options { get; set; } - string ExportSessionId { get; set; } - ExportResult ExportSessionResult { get; set; } + Guid? ExportId { get; set; } + ExportResult ExportResult { get; set; } List DeviceFlags { get; set; } } diff --git a/Disco.Models/UI/Config/UserFlag/ConfigUserFlagExportModel.cs b/Disco.Models/UI/Config/UserFlag/ConfigUserFlagExportModel.cs index c490760b..026f9ddb 100644 --- a/Disco.Models/UI/Config/UserFlag/ConfigUserFlagExportModel.cs +++ b/Disco.Models/UI/Config/UserFlag/ConfigUserFlagExportModel.cs @@ -1,6 +1,7 @@ using Disco.Models.Services.Exporting; using Disco.Models.Services.Users.UserFlags; using Disco.Models.UI; +using System; using System.Collections.Generic; namespace Disco.Models.Areas.Config.UI.UserFlag @@ -9,8 +10,8 @@ namespace Disco.Models.Areas.Config.UI.UserFlag { UserFlagExportOptions Options { get; set; } - string ExportSessionId { get; set; } - ExportResult ExportSessionResult { get; set; } + Guid? ExportId { get; set; } + ExportResult ExportResult { get; set; } List UserFlags { get; set; } } diff --git a/Disco.Models/UI/Device/DeviceExportModel.cs b/Disco.Models/UI/Device/DeviceExportModel.cs index 5b3d4158..69d7b636 100644 --- a/Disco.Models/UI/Device/DeviceExportModel.cs +++ b/Disco.Models/UI/Device/DeviceExportModel.cs @@ -1,5 +1,6 @@ -using Disco.Models.Services.Devices.Exporting; +using Disco.Models.Services.Devices; using Disco.Models.Services.Exporting; +using System; using System.Collections.Generic; namespace Disco.Models.UI.Device @@ -8,8 +9,8 @@ namespace Disco.Models.UI.Device { DeviceExportOptions Options { get; set; } - string ExportSessionId { get; set; } - ExportResult ExportSessionResult { get; set; } + Guid? ExportId { get; set; } + ExportResult ExportResult { get; set; } IEnumerable> DeviceBatches { get; set; } IEnumerable> DeviceModels { get; set; } diff --git a/Disco.Models/UI/Job/JobExportModel.cs b/Disco.Models/UI/Job/JobExportModel.cs index bde91b12..d395f119 100644 --- a/Disco.Models/UI/Job/JobExportModel.cs +++ b/Disco.Models/UI/Job/JobExportModel.cs @@ -1,6 +1,7 @@ using Disco.Models.Repository; using Disco.Models.Services.Exporting; -using Disco.Models.Services.Jobs.Exporting; +using Disco.Models.Services.Jobs; +using System; using System.Collections.Generic; namespace Disco.Models.UI.Job @@ -9,8 +10,8 @@ namespace Disco.Models.UI.Job { JobExportOptions Options { get; set; } - string ExportSessionId { get; set; } - ExportResult ExportSessionResult { get; set; } + Guid? ExportId { get; set; } + ExportResult ExportResult { get; set; } List JobQueues { get; set; } List> JobStatuses { get; set; } diff --git a/Disco.Services/Devices/Exporting/DeviceExport.cs b/Disco.Services/Devices/DeviceExportContext.cs similarity index 92% rename from Disco.Services/Devices/Exporting/DeviceExport.cs rename to Disco.Services/Devices/DeviceExportContext.cs index 74bed922..dd09e97a 100644 --- a/Disco.Services/Devices/Exporting/DeviceExport.cs +++ b/Disco.Services/Devices/DeviceExportContext.cs @@ -1,32 +1,89 @@ using Disco.Data.Repository; using Disco.Models.Exporting; using Disco.Models.Repository; -using Disco.Models.Services.Devices.Exporting; +using Disco.Models.Services.Devices; using Disco.Models.Services.Exporting; +using Disco.Services.Exporting; using Disco.Services.Plugins.Features.DetailsProvider; using Disco.Services.Tasks; using Disco.Services.Users; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Data; using System.Data.Entity; using System.Linq; +using System.Text.Json.Serialization; -namespace Disco.Services.Devices.Exporting +namespace Disco.Services.Devices { using Metadata = ExportFieldMetadata; - public static class DeviceExport + public class DeviceExportContext : IExportContext { + public Guid Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool TimestampSuffix { get; set; } + public DeviceExportOptions Options { get; set; } - public static ExportResult GenerateExport(DiscoDataContext Database, Func, IQueryable> Filter, DeviceExportOptions Options, IScheduledTaskStatus TaskStatus) + public string SuggestedFilenamePrefix { get; } = "DeviceExport"; + public string ExcelWorksheetName { get; } = "DeviceExport"; + public string ExcelTableName { get; } = "Devices"; + + [JsonConstructor] + private DeviceExportContext() { - var deviceQuery = Database.Devices + } + + public DeviceExportContext(string name, string description, bool timestampSuffix, DeviceExportOptions options) + { + Id = Guid.NewGuid(); + Name = name; + Description = description; + TimestampSuffix = timestampSuffix; + Options = options; + } + + public DeviceExportContext(DeviceExportOptions options) + : this("Device Export", null, true, options) + { + } + + public ExportResult Export(DiscoDataContext database, IScheduledTaskStatus taskStatus) + => Exporter.Export(database, this, taskStatus); + + private IQueryable BuildFilteredRecords(DiscoDataContext database) + { + var query = database.Devices .Include(d => d.AssignedUser.UserDetails) .Include(d => d.DeviceDetails); - if (Filter != null) - deviceQuery = Filter(deviceQuery); + + switch (Options.ExportType) + { + case DeviceExportTypes.All: + break; + case DeviceExportTypes.Batch: + if (Options.ExportTypeTargetId.HasValue && Options.ExportTypeTargetId.Value > 0) + query = query.Where(d => d.DeviceBatchId != Options.ExportTypeTargetId); + else + query = query.Where(d => d.DeviceBatchId != null); + break; + case DeviceExportTypes.Model: + query = query.Where(d => d.DeviceModelId == Options.ExportTypeTargetId); + break; + case DeviceExportTypes.Profile: + query = query.Where(d => d.DeviceProfileId == Options.ExportTypeTargetId); + break; + default: + throw new ArgumentException($"Unknown Device Export Type '{Options.ExportType}'", nameof(Options.ExportType)); + } + + return query; + } + + public List BuildRecords(DiscoDataContext database, IScheduledTaskStatus taskStatus) + { + var query = BuildFilteredRecords(database); // Update Users if (Options.AssignedUserDisplayName || @@ -35,13 +92,13 @@ namespace Disco.Services.Devices.Exporting Options.AssignedUserPhoneNumber || Options.AssignedUserEmailAddress) { - TaskStatus.UpdateStatus(5, "Refreshing user details from Active Directory"); - var userIds = deviceQuery.Where(d => d.AssignedUserId != null).Select(d => d.AssignedUserId).Distinct().ToList(); + taskStatus.UpdateStatus(5, "Refreshing user details from Active Directory"); + var userIds = query.Where(d => d.AssignedUserId != null).Select(d => d.AssignedUserId).Distinct().ToList(); foreach (var userId in userIds) { try { - UserService.GetUser(userId, Database); + UserService.GetUser(userId, database); } catch (Exception) { } // Ignore Errors } @@ -50,91 +107,18 @@ namespace Disco.Services.Devices.Exporting // Update Last Network Logon Date if (Options.DeviceLastNetworkLogon) { - TaskStatus.UpdateStatus(15, "Refreshing device last network logon dates from Active Directory"); + taskStatus.UpdateStatus(15, "Refreshing device last network logon dates from Active Directory"); try { - Interop.ActiveDirectory.ADNetworkLogonDatesUpdateTask.UpdateLastNetworkLogonDates(Database, ScheduledTaskMockStatus.Create("UpdateLastNetworkLogonDates")); - Database.SaveChanges(); + Interop.ActiveDirectory.ADNetworkLogonDatesUpdateTask.UpdateLastNetworkLogonDates(database, ScheduledTaskMockStatus.Create("UpdateLastNetworkLogonDates")); + database.SaveChanges(); } catch (Exception) { } // Ignore Errors } - TaskStatus.UpdateStatus(25, "Extracting records from the database"); + taskStatus.UpdateStatus(25, "Gathering database records"); - var records = BuildRecords(deviceQuery).ToList(); - - // materialize device details - records.ForEach(r => - { - if (Options.DetailBios) - r.DeviceDetailBios = r.DeviceDetails.Bios(); - if (Options.DetailBaseBoard) - r.DeviceDetailBaseBoard = r.DeviceDetails.BaseBoard(); - if (Options.DetailComputerSystem) - r.DeviceDetailComputerSystem = r.DeviceDetails.ComputerSystem(); - if (Options.DetailProcessors) - r.DeviceDetailProcessors = r.DeviceDetails.Processors(); - if (Options.DetailMemory) - r.DeviceDetailPhysicalMemory = r.DeviceDetails.PhysicalMemory(); - if (Options.DetailDiskDrives) - r.DeviceDetailDiskDrives = r.DeviceDetails.DiskDrives(); - if (Options.DetailLanAdapters || Options.DetailWLanAdapters) - { - r.DeviceDetailNetworkAdapters = r.DeviceDetails.NetworkAdapters(); - if (r.DeviceDetailNetworkAdapters == null) - { - r.DeviceDetailLanMacAddresses = r.DeviceDetails.LanMacAddress()?.Split(';').Select(a => a.Trim()).ToList(); - r.DeviceDetailWlanMacAddresses = r.DeviceDetails.WLanMacAddress()?.Split(';').Select(a => a.Trim()).ToList(); - } - } - if (Options.DetailBatteries) - r.DeviceDetailBatteries = r.DeviceDetails.Batteries(); - - if (Options.AssignedUserDetailCustom && r.AssignedUser != null) - { - var detailsService = new DetailsProviderService(Database); - r.AssignedUserCustomDetails = detailsService.GetDetails(r.AssignedUser); - } - }); - - TaskStatus.UpdateStatus(70, "Building metadata"); - var metadata = Options.BuildMetadata(records); - - if (metadata.Count == 0) - throw new ArgumentException("At least one export field must be specified", "Options"); - - TaskStatus.UpdateStatus(80, $"Formatting {records.Count} records for export"); - - return ExportHelpers.WriteExport(Options, TaskStatus, metadata, records); - } - - public static ExportResult GenerateExport(DiscoDataContext Database, DeviceExportOptions Options, IScheduledTaskStatus TaskStatus) - { - switch (Options.ExportType) - { - case DeviceExportTypes.All: - return GenerateExport(Database, null, Options, TaskStatus); - case DeviceExportTypes.Batch: - if (Options.ExportTypeTargetId.HasValue && Options.ExportTypeTargetId.Value > 0) - return GenerateExport(Database, devices => devices.Where(d => d.DeviceBatchId == Options.ExportTypeTargetId), Options, TaskStatus); - else - return GenerateExport(Database, devices => devices.Where(d => d.DeviceBatchId == null), Options, TaskStatus); - case DeviceExportTypes.Model: - return GenerateExport(Database, devices => devices.Where(d => d.DeviceModelId == Options.ExportTypeTargetId), Options, TaskStatus); - case DeviceExportTypes.Profile: - return GenerateExport(Database, devices => devices.Where(d => d.DeviceProfileId == Options.ExportTypeTargetId), Options, TaskStatus); - default: - throw new ArgumentException(string.Format("Unknown Device Export Type", Options.ExportType.ToString()), "Options"); - } - } - public static ExportResult GenerateExport(DiscoDataContext Database, DeviceExportOptions Options) - { - return GenerateExport(Database, Options, ScheduledTaskMockStatus.Create("Device Export")); - } - - private static IEnumerable BuildRecords(IQueryable Devices) - { - return Devices.Select(d => new DeviceExportRecord() + var records = query.Select(d => new DeviceExportRecord() { Device = d, @@ -170,10 +154,46 @@ namespace Disco.Services.Devices.Exporting AttachmentsCount = d.DeviceAttachments.Count(), DeviceCertificates = d.DeviceCertificates.Where(dc => dc.Enabled).OrderByDescending(dc => dc.AllocatedDate) + }).ToList(); + + // materialize device details + records.ForEach(r => + { + if (Options.DetailBios) + r.DeviceDetailBios = r.DeviceDetails.Bios(); + if (Options.DetailBaseBoard) + r.DeviceDetailBaseBoard = r.DeviceDetails.BaseBoard(); + if (Options.DetailComputerSystem) + r.DeviceDetailComputerSystem = r.DeviceDetails.ComputerSystem(); + if (Options.DetailProcessors) + r.DeviceDetailProcessors = r.DeviceDetails.Processors(); + if (Options.DetailMemory) + r.DeviceDetailPhysicalMemory = r.DeviceDetails.PhysicalMemory(); + if (Options.DetailDiskDrives) + r.DeviceDetailDiskDrives = r.DeviceDetails.DiskDrives(); + if (Options.DetailLanAdapters || Options.DetailWLanAdapters) + { + r.DeviceDetailNetworkAdapters = r.DeviceDetails.NetworkAdapters(); + if (r.DeviceDetailNetworkAdapters == null) + { + r.DeviceDetailLanMacAddresses = r.DeviceDetails.LanMacAddress()?.Split(';').Select(a => a.Trim()).ToList(); + r.DeviceDetailWlanMacAddresses = r.DeviceDetails.WLanMacAddress()?.Split(';').Select(a => a.Trim()).ToList(); + } + } + if (Options.DetailBatteries) + r.DeviceDetailBatteries = r.DeviceDetails.Batteries(); + + if (Options.AssignedUserDetailCustom && r.AssignedUser != null) + { + var detailsService = new DetailsProviderService(database); + r.AssignedUserCustomDetails = detailsService.GetDetails(r.AssignedUser); + } }); + + return records; } - private static List BuildMetadata(this DeviceExportOptions options, List records) + public List BuildMetadata(DiscoDataContext database, List records, IScheduledTaskStatus taskStatus) { var processorMaxCount = Math.Max(1, records.Max(r => r.DeviceDetailProcessors?.Count ?? 0)); var memoryMaxCount = Math.Max(1, records.Max(r => r.DeviceDetailPhysicalMemory?.Count ?? 0)); @@ -184,7 +204,7 @@ namespace Disco.Services.Devices.Exporting var batteriesMaxCount = Math.Max(1, records.Max(r => r.DeviceDetailBatteries?.Count ?? 0)); IEnumerable assignedUserDetailCustomKeys = null; - if (options.AssignedUserDetailCustom) + if (Options.AssignedUserDetailCustom) assignedUserDetailCustomKeys = records.Where(r => r.AssignedUserCustomDetails != null).SelectMany(r => r.AssignedUserCustomDetails.Keys).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); var allAccessors = BuildRecordAccessors(processorMaxCount, memoryMaxCount, diskDriveMaxCount, lanAdapterMaxCount, wlanAdapterMaxCount, certificateMaxCount, batteriesMaxCount, assignedUserDetailCustomKeys); @@ -196,7 +216,7 @@ namespace Disco.Services.Devices.Exporting property = p, details = (DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() }) - .Where(p => p.details != null && (bool)p.property.GetValue(options)) + .Where(p => p.details != null && (bool)p.property.GetValue(Options)) .SelectMany(p => { var fieldMetadata = allAccessors[p.property.Name]; @@ -460,6 +480,5 @@ namespace Disco.Services.Devices.Exporting return metadata; } - } } diff --git a/Disco.Services/Devices/DeviceFlags/DeviceFlagExport.cs b/Disco.Services/Devices/DeviceFlags/DeviceFlagExportContext.cs similarity index 88% rename from Disco.Services/Devices/DeviceFlags/DeviceFlagExport.cs rename to Disco.Services/Devices/DeviceFlags/DeviceFlagExportContext.cs index 0370b973..d0584357 100644 --- a/Disco.Services/Devices/DeviceFlags/DeviceFlagExport.cs +++ b/Disco.Services/Devices/DeviceFlags/DeviceFlagExportContext.cs @@ -2,9 +2,11 @@ using Disco.Models.Exporting; using Disco.Models.Services.Devices.DeviceFlag; using Disco.Models.Services.Exporting; +using Disco.Services.Exporting; using Disco.Services.Plugins.Features.DetailsProvider; using Disco.Services.Tasks; using Disco.Services.Users; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -15,57 +17,67 @@ namespace Disco.Services.Devices.DeviceFlags { using Metadata = ExportFieldMetadata; - public class DeviceFlagExport + public class DeviceFlagExportContext : IExportContext { - private readonly DiscoDataContext database; - private readonly DeviceFlagExportOptions options; + public Guid Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool TimestampSuffix { get; set; } + public DeviceFlagExportOptions Options { get; set; } - public DeviceFlagExport(DiscoDataContext database, DeviceFlagExportOptions options) + public string SuggestedFilenamePrefix { get; } = "DeviceFlagExport"; + public string ExcelWorksheetName { get; } = "DeviceFlagExport"; + public string ExcelTableName { get; } = "DeviceFlags"; + + [JsonConstructor] + private DeviceFlagExportContext() { - this.database = database; - this.options = options; } - public ExportResult Generate(IScheduledTaskStatus status) + public DeviceFlagExportContext(string name, string description, bool timestampSuffix, DeviceFlagExportOptions options) { - var records = BuildRecords(status); - - var metadata = BuildMetadata(records, status); - - if (metadata.Count == 0) - throw new ArgumentException("At least one export field must be specified", nameof(options)); - - status.UpdateStatus(90, $"Formatting {records.Count} records for export"); - return ExportHelpers.WriteExport(options, status, metadata, records); + Id = Guid.NewGuid(); + Name = name; + Description = description; + TimestampSuffix = timestampSuffix; + Options = options; } - private List BuildRecords(IScheduledTaskStatus status) + public DeviceFlagExportContext(DeviceFlagExportOptions options) + : this("Device Flag Export", null, true, options) + { + } + + public ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status) + => Exporter.Export(database, this, status); + + public List BuildRecords(DiscoDataContext database, IScheduledTaskStatus status) { var query = database.DeviceFlagAssignments .Include(a => a.DeviceFlag); - if (options.HasDeviceOptions()) + if (Options.HasDeviceOptions()) query = query.Include(a => a.Device); - if (options.HasDeviceModelOptions()) + if (Options.HasDeviceModelOptions()) query = query.Include(a => a.Device.DeviceModel); - if (options.HasDeviceBatchOptions()) + if (Options.HasDeviceBatchOptions()) query = query.Include(a => a.Device.DeviceBatch); - if (options.HasDeviceProfileOptions()) + if (Options.HasDeviceProfileOptions()) query = query.Include(a => a.Device.DeviceProfile); - if (options.HasAssignedUserOptions()) + if (Options.HasAssignedUserOptions()) query = query.Include(a => a.Device.AssignedUser); - if (options.AssignedUserDetailCustom) + if (Options.AssignedUserDetailCustom) query = query.Include(a => a.Device.AssignedUser.UserDetails); - query = query.Where(a => options.DeviceFlagIds.Contains(a.DeviceFlagId)); + query = query.Where(a => Options.DeviceFlagIds.Contains(a.DeviceFlagId)); - if (options.CurrentOnly) + if (Options.CurrentOnly) { query = query.Where(a => !a.RemovedDate.HasValue); } // Update Users - if (options.HasAssignedUserOptions()) + if (Options.HasAssignedUserOptions()) { status.UpdateStatus(5, "Refreshing user details from Active Directory"); var userIds = query.Where(d => d.Device.AssignedUserId != null).Select(d => d.Device.AssignedUserId).Distinct().ToList(); @@ -86,7 +98,7 @@ namespace Disco.Services.Devices.DeviceFlags Assignment = a }).ToList(); - if (options.AssignedUserDetailCustom) + if (Options.AssignedUserDetailCustom) { status.UpdateStatus(50, "Extracting custom user detail records"); @@ -108,12 +120,10 @@ namespace Disco.Services.Devices.DeviceFlags return records; } - private List BuildMetadata(List records, IScheduledTaskStatus status) + public List BuildMetadata(DiscoDataContext database, List records, IScheduledTaskStatus status) { - status.UpdateStatus(80, "Building metadata"); - IEnumerable userDetailCustomKeys = null; - if (options.AssignedUserDetailCustom) + if (Options.AssignedUserDetailCustom) userDetailCustomKeys = records.Where(r => r.AssignedUserCustomDetails != null).SelectMany(r => r.AssignedUserCustomDetails.Keys).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); var accessors = BuildAccessors(userDetailCustomKeys); @@ -125,7 +135,7 @@ namespace Disco.Services.Devices.DeviceFlags property = p, details = (DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() }) - .Where(p => p.details != null && p.property.Name != nameof(options.CurrentOnly) && (bool)p.property.GetValue(options)) + .Where(p => p.details != null && p.property.Name != nameof(Options.CurrentOnly) && (bool)p.property.GetValue(Options)) .SelectMany(p => { var fieldMetadata = accessors[p.property.Name]; @@ -224,6 +234,5 @@ namespace Disco.Services.Devices.DeviceFlags return metadata; } - } } diff --git a/Disco.Services/Devices/DeviceFlags/DeviceFlagExportTask.cs b/Disco.Services/Devices/DeviceFlags/DeviceFlagExportTask.cs deleted file mode 100644 index f328f688..00000000 --- a/Disco.Services/Devices/DeviceFlags/DeviceFlagExportTask.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Disco.Data.Repository; -using Disco.Models.Services.Devices.DeviceFlag; -using Disco.Services.Exporting; -using Disco.Services.Tasks; -using Quartz; - -namespace Disco.Services.Devices.DeviceFlags -{ - public class DeviceFlagExportTask : ScheduledTask - { - private const string JobDataMapContext = "Context"; - - public override string TaskName { get; } = "Export Device Flags"; - public override bool SingleInstanceTask { get { return false; } } - public override bool CancelInitiallySupported { get { return false; } } - - public static ExportTaskContext ScheduleNow(DeviceFlagExportOptions options) - { - // Build Context - var context = new ExportTaskContext(options); - - // Build Data Map - var task = new DeviceFlagExportTask(); - JobDataMap taskData = new JobDataMap() { { JobDataMapContext, context } }; - - // Schedule Task - context.TaskStatus = task.ScheduleTask(taskData); - - return context; - } - - protected override void ExecuteTask() - { - var context = (ExportTaskContext)ExecutionContext.JobDetail.JobDataMap[JobDataMapContext]; - - Status.UpdateStatus(10, "Exporting Device Flag Records", "Starting..."); - - using (DiscoDataContext Database = new DiscoDataContext()) - { - var export = new DeviceFlagExport(Database, context.Options); - - context.Result = export.Generate(Status); - } - } - } -} diff --git a/Disco.Services/Devices/Exporting/DeviceExportTask.cs b/Disco.Services/Devices/Exporting/DeviceExportTask.cs deleted file mode 100644 index 6a95844f..00000000 --- a/Disco.Services/Devices/Exporting/DeviceExportTask.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Disco.Models.Services.Devices.Exporting; -using Disco.Services.Tasks; -using Quartz; -using Disco.Data.Repository; -using Disco.Services.Exporting; - -namespace Disco.Services.Devices.Exporting -{ - public class DeviceExportTask : ScheduledTask - { - private const string JobDataMapContext = "Context"; - - public override string TaskName { get { return "Export Devices"; } } - public override bool SingleInstanceTask { get { return false; } } - public override bool CancelInitiallySupported { get { return false; } } - - public static ExportTaskContext ScheduleNow(DeviceExportOptions Options) - { - // Build Context - var context = new ExportTaskContext(Options); - - // Build Data Map - var task = new DeviceExportTask(); - JobDataMap taskData = new JobDataMap() { { JobDataMapContext, context} }; - - // Schedule Task - context.TaskStatus = task.ScheduleTask(taskData); - - return context; - } - - protected override void ExecuteTask() - { - var context = (ExportTaskContext)ExecutionContext.JobDetail.JobDataMap[JobDataMapContext]; - - Status.UpdateStatus(10, "Exporting Device Records", "Starting..."); - - using (DiscoDataContext Database = new DiscoDataContext()) - { - context.Result = DeviceExport.GenerateExport(Database, context.Options, Status); - } - } - } -} diff --git a/Disco.Services/Disco.Services.csproj b/Disco.Services/Disco.Services.csproj index 74ff961d..4ee96d72 100644 --- a/Disco.Services/Disco.Services.csproj +++ b/Disco.Services/Disco.Services.csproj @@ -347,6 +347,7 @@ + @@ -357,18 +358,16 @@ - - + - - - + + @@ -425,6 +424,7 @@ + @@ -491,8 +491,7 @@ - - + @@ -512,7 +511,7 @@ - + @@ -600,8 +599,7 @@ - - + diff --git a/Disco.Services/Exporting/ExportTask.cs b/Disco.Services/Exporting/ExportTask.cs new file mode 100644 index 00000000..031cc8c5 --- /dev/null +++ b/Disco.Services/Exporting/ExportTask.cs @@ -0,0 +1,76 @@ +using Disco.Data.Repository; +using Disco.Services.Tasks; +using Quartz; +using System; +using System.Web; +using System.Web.Caching; + +namespace Disco.Services.Exporting +{ + public class ExportTask : ScheduledTask + { + private IExportContext context; + public override string TaskName { get => context?.Name ?? "Exporting"; } + public override bool SingleInstanceTask { get { return false; } } + public override bool CancelInitiallySupported { get { return false; } } + + public static ExportTaskContext ScheduleNow(IExportContext exportContext) + { + // Build Context + var taskContext = new ExportTaskContext(exportContext); + + // Build Data Map + var task = new ExportTask(); + JobDataMap taskData = new JobDataMap() { { nameof(ExportTask), taskContext } }; + + // Schedule Task + taskContext.TaskStatus = task.ScheduleTask(taskData); + + return taskContext; + } + + private static string GetCacheKey(Guid exportId) => $"ExportTask_{exportId}"; + + public static ExportTaskContext ScheduleNowCacheResult(IExportContext exportContext, Func returnUrlBuilder) + { + var taskContext = ScheduleNow(exportContext); + + var key = GetCacheKey(taskContext.Id); + HttpRuntime.Cache.Insert(key, taskContext, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); + + taskContext.TaskStatus.SetFinishedUrl(returnUrlBuilder(taskContext.Id)); + + return taskContext; + } + + public static bool TryFromCache(Guid? exportId, out ExportTaskContext exportContext) + { + if (exportId != null) + { + var key = GetCacheKey(exportId.Value); + + if (HttpRuntime.Cache.Get(key) is ExportTaskContext context) + { + exportContext = context; + return true; + } + } + + exportContext = null; + return false; + } + + protected override void ExecuteTask() + { + var context = (ExportTaskContext)ExecutionContext.JobDetail.JobDataMap[nameof(ExportTask)]; + this.context = context.ExportContext; + + Status.UpdateStatus(0, "Exporting", "Starting..."); + + using (var database = new DiscoDataContext()) + { + context.Result = context.ExportContext.Export(database, Status); + } + } + } +} diff --git a/Disco.Services/Exporting/ExportTaskContext.cs b/Disco.Services/Exporting/ExportTaskContext.cs index 4ff1ac37..3e7df7f0 100644 --- a/Disco.Services/Exporting/ExportTaskContext.cs +++ b/Disco.Services/Exporting/ExportTaskContext.cs @@ -1,19 +1,20 @@ using Disco.Models.Services.Exporting; using Disco.Services.Tasks; +using System; namespace Disco.Services.Exporting { - public class ExportTaskContext where T : IExportOptions + public class ExportTaskContext { - public T Options { get; private set; } + public IExportContext ExportContext { get; } + public ScheduledTaskStatus TaskStatus { get; internal set; } + public ExportResult Result { get; internal set; } - public ScheduledTaskStatus TaskStatus { get; set; } + public Guid Id => ExportContext.Id; - public ExportResult Result { get; set; } - - public ExportTaskContext(T Options) + public ExportTaskContext(IExportContext context) { - this.Options = Options; + ExportContext = context; } } } diff --git a/Disco.Services/Exporting/ExportHelpers.cs b/Disco.Services/Exporting/Exporter.cs similarity index 56% rename from Disco.Services/Exporting/ExportHelpers.cs rename to Disco.Services/Exporting/Exporter.cs index af265003..8d0a351c 100644 --- a/Disco.Services/Exporting/ExportHelpers.cs +++ b/Disco.Services/Exporting/Exporter.cs @@ -1,4 +1,5 @@ using ClosedXML.Excel; +using Disco.Data.Repository; using Disco.Models.Exporting; using Disco.Models.Services.Exporting; using Disco.Services.Tasks; @@ -9,43 +10,66 @@ using System.IO; using System.Linq; using System.Text; -namespace Disco.Services +namespace Disco.Services.Exporting { - internal class ExportHelpers + public static class Exporter { - public static ExportResult WriteExport(IExportOptions options, IScheduledTaskStatus status, List> metadata, List records) where T : IExportRecord + public static ExportResult Export(DiscoDataContext database, IExportContext context, IScheduledTaskStatus status) + where T : IExportOptions, new() + where R : IExportRecord { - var filenameWithoutExtension = $"{options.FilenamePrefix}-{status.StartedTimestamp.Value:yyyyMMdd-HHmmss}"; MemoryStream stream; - string filename; string mimeType; - switch (options.Format) + status.UpdateStatus(1, $"Exporting {context.Name}", "Gathering data"); + + var records = context.BuildRecords(database, status); + + status.UpdateStatus(70, "Building metadata"); + + var metadata = context.BuildMetadata(database, records, status); + + if (metadata.Count == 0) + throw new ArgumentException("At least one export field must be specified", nameof(context.Options)); + + var filenameBuilder = new StringBuilder(); + filenameBuilder.Append(context.SuggestedFilenamePrefix); + if (context.TimestampSuffix) + { + filenameBuilder.Append('-'); + filenameBuilder.Append(status.StartedTimestamp.Value.ToString("yyyyMMdd-HHmmss")); + } + + status.UpdateStatus(80, $"Rendering {records.Count} records for export"); + + switch (context.Options.Format) { case ExportFormat.Csv: - stream = WriteCSV(filenameWithoutExtension, metadata, records, out filename, out mimeType); + filenameBuilder.Append(".csv"); + mimeType = "text/csv"; + stream = WriteCSV(metadata, records); break; case ExportFormat.Xlsx: - stream = WriteXlsx(filenameWithoutExtension, options.ExcelWorksheetName, options.ExcelTableName, metadata, records, out filename, out mimeType); + filenameBuilder.Append(".xlsx"); + mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + stream = WriteXlsx(context.ExcelWorksheetName, context.ExcelTableName, metadata, records); break; default: - throw new NotSupportedException($"Unsupported export format: {options.Format}"); + throw new NotSupportedException($"Unsupported export format: {context.Options.Format}"); } return new ExportResult() { Result = stream, RecordCount = records.Count, - Filename = filename, + Filename = filenameBuilder.ToString(), MimeType = mimeType, }; } - private static MemoryStream WriteCSV(string filenameWithoutExtension, List> metadata, List records, out string filename, out string mimeType) where T : IExportRecord + private static MemoryStream WriteCSV(List> metadata, List records) where T : IExportRecord { var stream = new MemoryStream(); - mimeType = "text/csv"; - filename = $"{filenameWithoutExtension}.csv"; using (StreamWriter writer = new StreamWriter(stream, Encoding.Default, 0x400, true)) { @@ -74,11 +98,9 @@ namespace Disco.Services return stream; } - private static MemoryStream WriteXlsx(string filenameWithoutExtension, string worksheetName, string tableName, List> metadata, List records, out string filename, out string mimeType) where T : IExportRecord + private static MemoryStream WriteXlsx(string worksheetName, string tableName, List> metadata, List records) where T : IExportRecord { var stream = new MemoryStream(); - mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; - filename = $"{filenameWithoutExtension}.xlsx"; // Create DataTable var dataTable = new DataTable(); diff --git a/Disco.Services/Exporting/IExportContext.cs b/Disco.Services/Exporting/IExportContext.cs new file mode 100644 index 00000000..6450f24d --- /dev/null +++ b/Disco.Services/Exporting/IExportContext.cs @@ -0,0 +1,39 @@ +using Disco.Data.Repository; +using Disco.Models.Exporting; +using Disco.Models.Services.Exporting; +using Disco.Services.Tasks; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Disco.Services.Exporting +{ + public interface IExportContext + { + Guid Id { get; set; } + string Name { get; set; } + string Description { get; set; } + + ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status); + } + + public interface IExportContext + : IExportContext + where T : IExportOptions, new() + where R : IExportRecord + { + bool TimestampSuffix { get; set; } + + [JsonIgnore] + string SuggestedFilenamePrefix { get; } + [JsonIgnore] + string ExcelWorksheetName { get; } + [JsonIgnore] + string ExcelTableName { get; } + + T Options { get; set; } + + List BuildRecords(DiscoDataContext database, IScheduledTaskStatus status); + List> BuildMetadata(DiscoDataContext database, List records, IScheduledTaskStatus status); + } +} diff --git a/Disco.Services/Jobs/Exporting/JobExportTask.cs b/Disco.Services/Jobs/Exporting/JobExportTask.cs deleted file mode 100644 index 3246bb5f..00000000 --- a/Disco.Services/Jobs/Exporting/JobExportTask.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Disco.Data.Repository; -using Disco.Models.Services.Jobs.Exporting; -using Disco.Services.Exporting; -using Disco.Services.Tasks; -using Quartz; - -namespace Disco.Services.Jobs.Exporting -{ - public class JobExportTask : ScheduledTask - { - private const string JobDataMapContext = "Context"; - - public override string TaskName { get { return "Export Jobs"; } } - public override bool SingleInstanceTask { get { return false; } } - public override bool CancelInitiallySupported { get { return false; } } - - public static ExportTaskContext ScheduleNow(JobExportOptions Options) - { - // Build Context - var context = new ExportTaskContext(Options); - - // Build Data Map - var task = new JobExportTask(); - JobDataMap taskData = new JobDataMap() { { JobDataMapContext, context} }; - - // Schedule Task - context.TaskStatus = task.ScheduleTask(taskData); - - return context; - } - - protected override void ExecuteTask() - { - var context = (ExportTaskContext)ExecutionContext.JobDetail.JobDataMap[JobDataMapContext]; - - Status.UpdateStatus(10, "Exporting Job Records", "Starting..."); - - using (DiscoDataContext Database = new DiscoDataContext()) - { - context.Result = JobExport.GenerateExport(Database, context.Options, Status); - } - } - } -} diff --git a/Disco.Services/Jobs/Exporting/JobExport.cs b/Disco.Services/Jobs/JobExportContext.cs similarity index 83% rename from Disco.Services/Jobs/Exporting/JobExport.cs rename to Disco.Services/Jobs/JobExportContext.cs index 731ad04f..74dd0561 100644 --- a/Disco.Services/Jobs/Exporting/JobExport.cs +++ b/Disco.Services/Jobs/JobExportContext.cs @@ -2,41 +2,131 @@ using Disco.Models.Exporting; using Disco.Models.Repository; using Disco.Models.Services.Exporting; -using Disco.Models.Services.Jobs.Exporting; +using Disco.Models.Services.Jobs; +using Disco.Services.Exporting; using Disco.Services.Jobs.JobQueues; using Disco.Services.Plugins.Features.DetailsProvider; using Disco.Services.Tasks; using Disco.Services.Users; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Data; using System.Linq; -namespace Disco.Services.Jobs.Exporting +namespace Disco.Services.Jobs { using Metadata = ExportFieldMetadata; - public static class JobExport + public class JobExportContext : IExportContext { - public static ExportResult GenerateExport(DiscoDataContext database, Func, IQueryable> filter, JobExportOptions options, IScheduledTaskStatus taskStatus) + public Guid Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool TimestampSuffix { get; set; } + public JobExportOptions Options { get; set; } + + public string SuggestedFilenamePrefix { get; } = "JobExport"; + public string ExcelWorksheetName { get; } = "JobExport"; + public string ExcelTableName { get; } = "Jobs"; + + [JsonConstructor] + private JobExportContext() + { + } + + public JobExportContext(string name, string description, bool timestampSuffix, JobExportOptions options) + { + Id = Guid.NewGuid(); + Name = name; + Description = description; + TimestampSuffix = timestampSuffix; + Options = options; + } + + public JobExportContext(JobExportOptions options) + : this("Job Export", null, true, options) + { + } + + public ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status) + => Exporter.Export(database, this, status); + + private IQueryable BuildFilteredRecords(DiscoDataContext database) + { + var o = Options; + + var q = database.Jobs.Where(j => j.OpenedDate >= o.FilterStartDate); + if (o.FilterEndDate.HasValue) + q = q.Where(j => j.OpenedDate <= o.FilterEndDate); + + if (o.FilterJobTypeId != null) + q = q.Where(j => j.JobTypeId == o.FilterJobTypeId); + + if (o.FilterJobSubTypeIds?.Any() ?? false) + q = q.Where(j => j.JobSubTypes.Any(st => o.FilterJobSubTypeIds.Contains(st.Id))); + + if (o.FilterJobQueueId.HasValue) + q = q.Where(j => j.JobQueues.Any(jq => !jq.RemovedDate.HasValue && jq.JobQueueId == o.FilterJobQueueId)); + + if (o.FilterJobStatusId != null) + { + if (o.FilterJobStatusId != Job.JobStatusIds.Closed) + q = q.Where(j => j.ClosedDate == null); + + switch (o.FilterJobStatusId) + { + case Job.JobStatusIds.Open: + // already filtered + break; + case Job.JobStatusIds.AwaitingAccountingPayment: + q = q.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.AccountingChargeAddedDate != null && j.JobMetaNonWarranty.AccountingChargePaidDate == null); + break; + case Job.JobStatusIds.AwaitingAccountingCharge: + q = q.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.AccountingChargeRequiredDate == null && (j.JobMetaNonWarranty.AccountingChargePaidDate != null || j.JobMetaNonWarranty.AccountingChargeAddedDate != null)); + break; + case Job.JobStatusIds.AwaitingDeviceReturn: + q = q.Where(j => j.DeviceReadyForReturn != null && j.DeviceReturnedDate == null); + break; + case Job.JobStatusIds.AwaitingInsuranceProcessing: + q = q.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.IsInsuranceClaim && j.JobMetaInsurance.ClaimFormSentDate == null); + break; + case Job.JobStatusIds.AwaitingRepairs: + q = q.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.RepairerLoggedDate != null && j.JobMetaNonWarranty.RepairerCompletedDate == null); + break; + case Job.JobStatusIds.AwaitingUserAction: + q = q.Where(j => j.WaitingForUserAction != null); + break; + case Job.JobStatusIds.AwaitingWarrantyRepair: + q = q.Where(j => j.JobTypeId == JobType.JobTypeIds.HWar && j.JobMetaWarranty.ExternalLoggedDate != null && j.JobMetaWarranty.ExternalCompletedDate == null); + break; + case Job.JobStatusIds.Closed: + q = q.Where(j => j.ClosedDate != null); + break; + default: + throw new ArgumentException($"Unknown Job Status Id: {o.FilterJobStatusId}", nameof(o.FilterJobStatusId)); + } + } + + return q; + } + + public List BuildRecords(DiscoDataContext database, IScheduledTaskStatus status) { database.Configuration.LazyLoadingEnabled = false; database.Configuration.ProxyCreationEnabled = false; - var jobQuery = (IQueryable)database.Jobs; - if (filter != null) - jobQuery = filter(jobQuery); + var query = BuildFilteredRecords(database); // Update Users - if (options.UserDisplayName || - options.UserSurname || - options.UserGivenName || - options.UserPhoneNumber || - options.UserEmailAddress) + if (Options.UserDisplayName || + Options.UserSurname || + Options.UserGivenName || + Options.UserPhoneNumber || + Options.UserEmailAddress) { - taskStatus.UpdateStatus(5, "Refreshing user details from Active Directory"); - var userIds = jobQuery.Where(d => d.UserId != null).Select(d => d.UserId).Distinct().ToList(); + status.UpdateStatus(5, "Refreshing user details from Active Directory"); + var userIds = query.Where(d => d.UserId != null).Select(d => d.UserId).Distinct().ToList(); foreach (var userId in userIds) { try @@ -48,9 +138,9 @@ namespace Disco.Services.Jobs.Exporting } // Update Last Network Logon Date - if (options.DeviceLastNetworkLogon) + if (Options.DeviceLastNetworkLogon) { - taskStatus.UpdateStatus(15, "Refreshing device last network logon dates from Active Directory"); + status.UpdateStatus(15, "Refreshing device last network logon dates from Active Directory"); try { Interop.ActiveDirectory.ADNetworkLogonDatesUpdateTask.UpdateLastNetworkLogonDates(database, ScheduledTaskMockStatus.Create("UpdateLastNetworkLogonDates")); @@ -59,119 +149,9 @@ namespace Disco.Services.Jobs.Exporting catch (Exception) { } // Ignore Errors } - taskStatus.UpdateStatus(25, "Extracting records from the database"); + status.UpdateStatus(25, "Extracting records from the database"); - var records = BuildRecords(jobQuery).ToList(); - - records.ForEach(r => - { - if (options.JobStatus) - { - r.JobStatus = JobExtensions.CalculateStatusId( - r.Job.ClosedDate, - r.Job.JobTypeId, - r.JobMetaWarranty?.ExternalLoggedDate, - r.JobMetaWarranty?.ExternalCompletedDate, - r.JobMetaNonWarranty?.RepairerLoggedDate, - r.JobMetaNonWarranty?.RepairerCompletedDate, - r.JobMetaNonWarranty?.AccountingChargeRequiredDate, - r.JobMetaNonWarranty?.AccountingChargeAddedDate, - r.JobMetaNonWarranty?.AccountingChargePaidDate, - r.JobMetaNonWarranty?.IsInsuranceClaim, - r.JobMetaInsurance?.ClaimFormSentDate, - r.Job.WaitingForUserAction, - r.Job.DeviceReadyForReturn, - r.Job.DeviceReturnedDate); - } - - if (options.UserDetailCustom && r.User != null) - { - var detailsService = new DetailsProviderService(database); - r.UserCustomDetails = detailsService.GetDetails(r.User); - } - }); - - taskStatus.UpdateStatus(70, "Building metadata"); - var metadata = options.BuildMetadata(records); - - if (metadata.Count == 0) - throw new ArgumentException("At least one export field must be specified", "Options"); - - taskStatus.UpdateStatus(80, $"Formatting {records.Count} records for export"); - - return ExportHelpers.WriteExport(options, taskStatus, metadata, records); - } - - public static ExportResult GenerateExport(DiscoDataContext database, JobExportOptions options, IScheduledTaskStatus taskStatus) - { - Func, IQueryable> filter = q => - { - var r = q.Where(j => j.OpenedDate >= options.FilterStartDate); - if (options.FilterEndDate.HasValue) - r = r.Where(j => j.OpenedDate <= options.FilterEndDate); - - if (options.FilterJobTypeId != null) - r = r.Where(j => j.JobTypeId == options.FilterJobTypeId); - - if (options.FilterJobSubTypeIds?.Any() ?? false) - r = r.Where(j => j.JobSubTypes.Any(st => options.FilterJobSubTypeIds.Contains(st.Id))); - - if (options.FilterJobQueueId.HasValue) - r = r.Where(j => j.JobQueues.Any(jq => !jq.RemovedDate.HasValue && jq.JobQueueId == options.FilterJobQueueId)); - - if (options.FilterJobStatusId != null) - { - if (options.FilterJobStatusId != Job.JobStatusIds.Closed) - r = r.Where(j => j.ClosedDate == null); - - switch (options.FilterJobStatusId) - { - case Job.JobStatusIds.Open: - // already filtered - break; - case Job.JobStatusIds.AwaitingAccountingPayment: - r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.AccountingChargeAddedDate != null && j.JobMetaNonWarranty.AccountingChargePaidDate == null); - break; - case Job.JobStatusIds.AwaitingAccountingCharge: - r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.AccountingChargeRequiredDate == null && (j.JobMetaNonWarranty.AccountingChargePaidDate != null || j.JobMetaNonWarranty.AccountingChargeAddedDate != null)); - break; - case Job.JobStatusIds.AwaitingDeviceReturn: - r = r.Where(j => j.DeviceReadyForReturn != null && j.DeviceReturnedDate == null); - break; - case Job.JobStatusIds.AwaitingInsuranceProcessing: - r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.IsInsuranceClaim && j.JobMetaInsurance.ClaimFormSentDate == null); - break; - case Job.JobStatusIds.AwaitingRepairs: - r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HNWar && j.JobMetaNonWarranty.RepairerLoggedDate != null && j.JobMetaNonWarranty.RepairerCompletedDate == null); - break; - case Job.JobStatusIds.AwaitingUserAction: - r = r.Where(j => j.WaitingForUserAction != null); - break; - case Job.JobStatusIds.AwaitingWarrantyRepair: - r = r.Where(j => j.JobTypeId == JobType.JobTypeIds.HWar && j.JobMetaWarranty.ExternalLoggedDate != null && j.JobMetaWarranty.ExternalCompletedDate == null); - break; - case Job.JobStatusIds.Closed: - r = r.Where(j => j.ClosedDate != null); - break; - default: - throw new ArgumentException($"Unknown Job Status Id: {options.FilterJobStatusId}", nameof(options.FilterJobStatusId)); - } - } - - return r; - }; - - return GenerateExport(database, filter, options, taskStatus); - } - - public static ExportResult GenerateExport(DiscoDataContext database, JobExportOptions options) - { - return GenerateExport(database, options, ScheduledTaskMockStatus.Create("Job Export")); - } - - private static IEnumerable BuildRecords(IQueryable jobs) - { - return jobs.Select(j => new JobExportRecord() + var records = query.Select(j => new JobExportRecord() { Job = j, JobTypeDescription = j.JobType.Description, @@ -214,13 +194,43 @@ namespace Disco.Services.Jobs.Exporting DeviceProfileId = j.Device.DeviceProfileId, DeviceProfileName = j.Device.DeviceProfile.Name, DeviceProfileShortName = j.Device.DeviceProfile.ShortName, + }).ToList(); + + records.ForEach(r => + { + if (Options.JobStatus) + { + r.JobStatus = JobExtensions.CalculateStatusId( + r.Job.ClosedDate, + r.Job.JobTypeId, + r.JobMetaWarranty?.ExternalLoggedDate, + r.JobMetaWarranty?.ExternalCompletedDate, + r.JobMetaNonWarranty?.RepairerLoggedDate, + r.JobMetaNonWarranty?.RepairerCompletedDate, + r.JobMetaNonWarranty?.AccountingChargeRequiredDate, + r.JobMetaNonWarranty?.AccountingChargeAddedDate, + r.JobMetaNonWarranty?.AccountingChargePaidDate, + r.JobMetaNonWarranty?.IsInsuranceClaim, + r.JobMetaInsurance?.ClaimFormSentDate, + r.Job.WaitingForUserAction, + r.Job.DeviceReadyForReturn, + r.Job.DeviceReturnedDate); + } + + if (Options.UserDetailCustom && r.User != null) + { + var detailsService = new DetailsProviderService(database); + r.UserCustomDetails = detailsService.GetDetails(r.User); + } }); + + return records; } - private static List BuildMetadata(this JobExportOptions options, List records) + public List BuildMetadata(DiscoDataContext database, List records, IScheduledTaskStatus status) { IEnumerable userDetailCustomKeys = null; - if (options.UserDetailCustom) + if (Options.UserDetailCustom) userDetailCustomKeys = records.Where(r => r.UserCustomDetails != null).SelectMany(r => r.UserCustomDetails.Keys).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); var allAccessors = BuildRecordAccessors(userDetailCustomKeys); @@ -232,7 +242,7 @@ namespace Disco.Services.Jobs.Exporting property = p, details = (DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() }) - .Where(p => p.details != null && (bool)p.property.GetValue(options)) + .Where(p => p.details != null && (bool)p.property.GetValue(Options)) .SelectMany(p => { var fieldMetadata = allAccessors[p.property.Name]; @@ -397,6 +407,5 @@ namespace Disco.Services.Jobs.Exporting return metadata; } - } } diff --git a/Disco.Services/Logging/LogExport.cs b/Disco.Services/Logging/LogExportContext.cs similarity index 60% rename from Disco.Services/Logging/LogExport.cs rename to Disco.Services/Logging/LogExportContext.cs index 501c4a7e..47d1db52 100644 --- a/Disco.Services/Logging/LogExport.cs +++ b/Disco.Services/Logging/LogExportContext.cs @@ -1,7 +1,11 @@ -using Disco.Models.Exporting; +using Disco.Data.Repository; +using Disco.Models.Exporting; using Disco.Models.Services.Exporting; +using Disco.Models.Services.Logging; +using Disco.Services.Exporting; using Disco.Services.Logging.Models; using Disco.Services.Tasks; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -10,12 +14,56 @@ namespace Disco.Services.Logging { using Metadata = ExportFieldMetadata; - public static class LogExport + public class LogExportContext : IExportContext { - public static ExportResult GenerateExport(ExportFormat format, List records) - { - var options = new LogExportOptions(format); + public Guid Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool TimestampSuffix { get; set; } + public LogExportOptions Options { get; set; } + public string SuggestedFilenamePrefix { get; } = "DiscoIctLogs"; + public string ExcelWorksheetName { get; } = "Disco ICT Logs"; + public string ExcelTableName { get; } = "DiscoIctLogs"; + + [JsonConstructor] + private LogExportContext() + { + } + + public LogExportContext(string name, string description, bool timestampSuffix, LogExportOptions options) + { + Id = Guid.NewGuid(); + Name = name; + Description = description; + TimestampSuffix = timestampSuffix; + Options = options; + } + + public LogExportContext(LogExportOptions options) + : this("Log Export", null, true, options) + { + } + + public ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status) + => Exporter.Export(database, this, status); + + public List BuildRecords(DiscoDataContext database, IScheduledTaskStatus status) + { + var logRetriever = new ReadLogContext() + { + Start = Options.StartDate, + End = Options.EndDate, + Module = Options.ModuleId, + EventTypes = Options.EventTypeIds, + Take = Options.Take, + }; + + return logRetriever.Query(database); + } + + public List BuildMetadata(DiscoDataContext database, List records, IScheduledTaskStatus status) + { const string DateFormat = "yyyy-MM-dd"; const string DateTimeFormat = DateFormat + " HH:mm:ss"; Func csvStringEncoded = (o) => o == null ? null : $"\"{((string)o).Replace("\"", "\"\"")}\""; @@ -48,20 +96,7 @@ namespace Disco.Services.Logging } } - return ExportHelpers.WriteExport(options, ScheduledTaskMockStatus.Create("Export Disco ICT Logs"), metadata, records); - } - } - - public class LogExportOptions : IExportOptions - { - public ExportFormat Format { get; set; } - public string FilenamePrefix { get; } = "DiscoIctLogs"; - public string ExcelWorksheetName { get; set; } = "Disco ICT Logs"; - public string ExcelTableName { get; set; } = "DiscoIctLogs"; - - public LogExportOptions(ExportFormat format) - { - Format = format; + return metadata; } } } diff --git a/Disco.Services/Users/UserFlags/UserFlagExport.cs b/Disco.Services/Users/UserFlags/UserFlagExportContext.cs similarity index 80% rename from Disco.Services/Users/UserFlags/UserFlagExport.cs rename to Disco.Services/Users/UserFlags/UserFlagExportContext.cs index 97c6fcd9..0e72c1f2 100644 --- a/Disco.Services/Users/UserFlags/UserFlagExport.cs +++ b/Disco.Services/Users/UserFlags/UserFlagExportContext.cs @@ -2,8 +2,11 @@ using Disco.Models.Exporting; using Disco.Models.Services.Exporting; using Disco.Models.Services.Users.UserFlags; +using Disco.Services.Exporting; using Disco.Services.Plugins.Features.DetailsProvider; using Disco.Services.Tasks; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -14,48 +17,56 @@ namespace Disco.Services.Users.UserFlags { using Metadata = ExportFieldMetadata; - public class UserFlagExport + public class UserFlagExportContext : IExportContext { - private readonly DiscoDataContext database; - private readonly UserFlagExportOptions options; + public Guid Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool TimestampSuffix { get; set; } + public UserFlagExportOptions Options { get; set; } - public UserFlagExport(DiscoDataContext database, UserFlagExportOptions options) + public string SuggestedFilenamePrefix { get; } = "UserFlagExport"; + public string ExcelWorksheetName { get; } = "UserFlagExport"; + public string ExcelTableName { get; } = "UserFlags"; + + [JsonConstructor] + private UserFlagExportContext() { - this.database = database; - this.options = options; } - public ExportResult Generate(IScheduledTaskStatus status) + public UserFlagExportContext(string name, string description, bool timestampSuffix, UserFlagExportOptions options) { - var records = BuildRecords(status); - - var metadata = BuildMetadata(records, status); - - if (metadata.Count == 0) - throw new ArgumentException("At least one export field must be specified", nameof(options)); - - status.UpdateStatus(90, $"Formatting {records.Count} records for export"); - return ExportHelpers.WriteExport(options, status, metadata, records); + Id = Guid.NewGuid(); + Name = name; + Description = description; + TimestampSuffix = timestampSuffix; + Options = options; } - private List BuildRecords(IScheduledTaskStatus status) + public UserFlagExportContext(UserFlagExportOptions options) + : this("User Flag Export", null, true, options) + { + } + + public ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status) + => Exporter.Export(database, this, status); + + public List BuildRecords(DiscoDataContext database, IScheduledTaskStatus status) { var query = database.UserFlagAssignments .Include(a => a.User.UserDetails) .Include(a => a.UserFlag) - .Where(a => options.UserFlagIds.Contains(a.UserFlagId)); + .Where(a => Options.UserFlagIds.Contains(a.UserFlagId)); - if (options.CurrentOnly) - { + if (Options.CurrentOnly) query = query.Where(a => !a.RemovedDate.HasValue); - } // Update Users - if (options.UserDisplayName || - options.UserSurname || - options.UserGivenName || - options.UserPhoneNumber || - options.UserEmailAddress) + if (Options.UserDisplayName || + Options.UserSurname || + Options.UserGivenName || + Options.UserPhoneNumber || + Options.UserEmailAddress) { status.UpdateStatus(5, "Refreshing user details from Active Directory"); var userIds = query.Select(d => d.UserId).Distinct().ToList(); @@ -72,11 +83,11 @@ namespace Disco.Services.Users.UserFlags status.UpdateStatus(15, "Extracting records from the database"); var records = query.Select(a => new UserFlagExportRecord() - { - Assignment = a - }).ToList(); + { + Assignment = a + }).ToList(); - if (options.UserDetailCustom) + if (Options.UserDetailCustom) { status.UpdateStatus(50, "Extracting custom user detail records"); @@ -93,12 +104,12 @@ namespace Disco.Services.Users.UserFlags return records; } - private List BuildMetadata(List records, IScheduledTaskStatus status) + public List BuildMetadata(DiscoDataContext database, List records, IScheduledTaskStatus status) { status.UpdateStatus(80, "Building metadata"); IEnumerable userDetailCustomKeys = null; - if (options.UserDetailCustom) + if (Options.UserDetailCustom) userDetailCustomKeys = records.Where(r => r.UserCustomDetails != null).SelectMany(r => r.UserCustomDetails.Keys).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); var accessors = BuildAccessors(userDetailCustomKeys); @@ -110,9 +121,9 @@ namespace Disco.Services.Users.UserFlags property = p, details = (DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() }) - .Where(p => p.details != null && p.property.Name != nameof(options.CurrentOnly) && (bool)p.property.GetValue(options)) - .SelectMany(p => - { + .Where(p => p.details != null && p.property.Name != nameof(Options.CurrentOnly) && (bool)p.property.GetValue(Options)) + .SelectMany(p => + { var fieldMetadata = accessors[p.property.Name]; fieldMetadata.ForEach(f => { @@ -171,6 +182,5 @@ namespace Disco.Services.Users.UserFlags return metadata; } - } } diff --git a/Disco.Services/Users/UserFlags/UserFlagExportTask.cs b/Disco.Services/Users/UserFlags/UserFlagExportTask.cs deleted file mode 100644 index 9ecc725b..00000000 --- a/Disco.Services/Users/UserFlags/UserFlagExportTask.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Disco.Data.Repository; -using Disco.Models.Services.Users.UserFlags; -using Disco.Services.Exporting; -using Disco.Services.Tasks; -using Quartz; - -namespace Disco.Services.Users.UserFlags -{ - public class UserFlagExportTask : ScheduledTask - { - private const string JobDataMapContext = "Context"; - - public override string TaskName { get; } = "Export User Flags"; - public override bool SingleInstanceTask { get { return false; } } - public override bool CancelInitiallySupported { get { return false; } } - - public static ExportTaskContext ScheduleNow(UserFlagExportOptions options) - { - // Build Context - var context = new ExportTaskContext(options); - - // Build Data Map - var task = new UserFlagExportTask(); - JobDataMap taskData = new JobDataMap() { { JobDataMapContext, context } }; - - // Schedule Task - context.TaskStatus = task.ScheduleTask(taskData); - - return context; - } - - protected override void ExecuteTask() - { - var context = (ExportTaskContext)ExecutionContext.JobDetail.JobDataMap[JobDataMapContext]; - - Status.UpdateStatus(10, "Exporting User Flag Records", "Starting..."); - - using (DiscoDataContext Database = new DiscoDataContext()) - { - var export = new UserFlagExport(Database, context.Options); - - context.Result = export.Generate(Status); - } - } - } -} diff --git a/Disco.Web/Areas/API/Controllers/DeviceController.cs b/Disco.Web/Areas/API/Controllers/DeviceController.cs index 545a7ed1..c116c5a9 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceController.cs @@ -1,9 +1,8 @@ using Disco.Models.Repository; -using Disco.Models.Services.Devices.Exporting; using Disco.Models.Services.Devices.Importing; using Disco.Services; using Disco.Services.Authorization; -using Disco.Services.Devices.Exporting; +using Disco.Services.Devices; using Disco.Services.Devices.Importing; using Disco.Services.Exporting; using Disco.Services.Interop; @@ -685,7 +684,6 @@ namespace Disco.Web.Areas.API.Controllers #endregion #region Exporting - internal const string ExportSessionCacheKey = "DeviceExportContext_{0}"; [DiscoAuthorize(Claims.Device.Actions.Export)] [HttpPost, ValidateAntiForgeryToken] @@ -699,39 +697,29 @@ namespace Disco.Web.Areas.API.Controllers Database.SaveChanges(); // Start Export - var exportContext = DeviceExportTask.ScheduleNow(Model.Options); - - // Store Export Context in Web Cache - string key = string.Format(ExportSessionCacheKey, exportContext.TaskStatus.SessionId); - HttpRuntime.Cache.Insert(key, exportContext, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); - - // Set Task Finished Url - var finishedActionResult = MVC.Device.Export(exportContext.TaskStatus.SessionId, null, null); - exportContext.TaskStatus.SetFinishedUrl(Url.Action(finishedActionResult)); + var exportContext = new DeviceExportContext(Model.Options); + var taskContext = ExportTask.ScheduleNowCacheResult(exportContext, id => Url.Action(MVC.Device.Export(id, null, null))); // Try waiting for completion - if (exportContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(2))) - return RedirectToAction(finishedActionResult); + if (taskContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(1))) + return RedirectToAction(MVC.Device.Export(taskContext.Id, null, null)); else - return RedirectToAction(MVC.Config.Logging.TaskStatus(exportContext.TaskStatus.SessionId)); + return RedirectToAction(MVC.Config.Logging.TaskStatus(taskContext.TaskStatus.SessionId)); } [DiscoAuthorize(Claims.Device.Actions.Export)] - public virtual ActionResult ExportRetrieve(string Id) + public virtual ActionResult ExportRetrieve(Guid id) { - if (string.IsNullOrWhiteSpace(Id)) - throw new ArgumentNullException("Id"); + if (id == Guid.Empty) + throw new ArgumentNullException(nameof(id)); - string key = string.Format(ExportSessionCacheKey, Id); - var context = HttpRuntime.Cache.Get(key) as ExportTaskContext; - - if (context == null) - throw new ArgumentException("The Id specified is invalid, or the export data expired (60 minutes)", "Id"); + if (!ExportTask.TryFromCache(id, out var context)) + throw new ArgumentException("The export id specified is invalid, or the export data expired (60 minutes)", nameof(id)); if (context.Result == null || context.Result.Result == null) - throw new ArgumentException("The export session is still running, or failed to complete successfully", "Id"); + throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(id)); if (context.Result.RecordCount == 0) - throw new ArgumentException("No records were found to export", nameof(Id)); + throw new ArgumentException("No records were found to export", nameof(id)); var fileStream = context.Result.Result; diff --git a/Disco.Web/Areas/API/Controllers/DeviceFlagController.cs b/Disco.Web/Areas/API/Controllers/DeviceFlagController.cs index ddaef458..b44a069a 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceFlagController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceFlagController.cs @@ -404,7 +404,6 @@ namespace Disco.Web.Areas.API.Controllers #endregion #region Exporting - internal const string ExportSessionCacheKey = "DeviceFlagExportContext_{0}"; [DiscoAuthorize(Claims.Config.DeviceFlag.Export)] public virtual ActionResult Export(ExportModel Model) @@ -413,39 +412,29 @@ namespace Disco.Web.Areas.API.Controllers throw new ArgumentNullException(nameof(Model)); // Start Export - var exportContext = DeviceFlagExportTask.ScheduleNow(Model.Options); - - // Store Export Context in Web Cache - string key = string.Format(ExportSessionCacheKey, exportContext.TaskStatus.SessionId); - HttpRuntime.Cache.Insert(key, exportContext, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); - - // Set Task Finished Url - var finishedActionResult = MVC.Config.DeviceFlag.Export(exportContext.TaskStatus.SessionId, null, null); - exportContext.TaskStatus.SetFinishedUrl(Url.Action(finishedActionResult)); + var exportContext = new DeviceFlagExportContext(Model.Options); + var taskContext = ExportTask.ScheduleNowCacheResult(exportContext, id => Url.Action(MVC.Config.DeviceFlag.Export(id, null, null))); // Try waiting for completion - if (exportContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(2))) - return RedirectToAction(finishedActionResult); + if (taskContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(1))) + return RedirectToAction(MVC.Config.DeviceFlag.Export(taskContext.Id, null, null)); else - return RedirectToAction(MVC.Config.Logging.TaskStatus(exportContext.TaskStatus.SessionId)); + return RedirectToAction(MVC.Config.Logging.TaskStatus(taskContext.TaskStatus.SessionId)); } [DiscoAuthorize(Claims.Config.DeviceFlag.Export)] - public virtual ActionResult ExportRetrieve(string Id) + public virtual ActionResult ExportRetrieve(Guid id) { - if (string.IsNullOrWhiteSpace(Id)) - throw new ArgumentNullException("Id"); + if (id == Guid.Empty) + throw new ArgumentNullException(nameof(id)); - string key = string.Format(ExportSessionCacheKey, Id); - var context = HttpRuntime.Cache.Get(key) as ExportTaskContext; - - if (context == null) - throw new ArgumentException("The Id specified is invalid, or the export data expired (60 minutes)", nameof(Id)); + if (!ExportTask.TryFromCache(id, out var context)) + throw new ArgumentException("The export id specified is invalid, or the export data expired (60 minutes)", nameof(id)); if (context.Result == null || context.Result.Result == null) - throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(Id)); + throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(id)); if (context.Result.RecordCount == 0) - throw new ArgumentException("No records were found to export", nameof(Id)); + throw new ArgumentException("No records were found to export", nameof(id)); var fileStream = context.Result.Result; diff --git a/Disco.Web/Areas/API/Controllers/JobController.cs b/Disco.Web/Areas/API/Controllers/JobController.cs index 95406a1b..4a88523c 100644 --- a/Disco.Web/Areas/API/Controllers/JobController.cs +++ b/Disco.Web/Areas/API/Controllers/JobController.cs @@ -1,13 +1,11 @@ using Disco.Models.Repository; using Disco.Models.Services.Jobs; -using Disco.Models.Services.Jobs.Exporting; using Disco.Models.Services.Jobs.JobLists; using Disco.Services; using Disco.Services.Authorization; using Disco.Services.Exporting; using Disco.Services.Interop; using Disco.Services.Jobs; -using Disco.Services.Jobs.Exporting; using Disco.Services.Jobs.JobLists; using Disco.Services.Jobs.Statistics; using Disco.Services.Users; @@ -2168,7 +2166,6 @@ namespace Disco.Web.Areas.API.Controllers } #region Exporting - internal const string ExportSessionCacheKey = "JobExportContext_{0}"; [DiscoAuthorize(Claims.Job.Actions.Export)] [HttpPost, ValidateAntiForgeryToken] @@ -2182,34 +2179,24 @@ namespace Disco.Web.Areas.API.Controllers Database.SaveChanges(); // Start Export - var exportContext = JobExportTask.ScheduleNow(model.Options); - - // Store Export Context in Web Cache - string key = string.Format(ExportSessionCacheKey, exportContext.TaskStatus.SessionId); - HttpRuntime.Cache.Insert(key, exportContext, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); - - // Set Task Finished Url - var finishedActionResult = MVC.Job.Export(exportContext.TaskStatus.SessionId); - exportContext.TaskStatus.SetFinishedUrl(Url.Action(finishedActionResult)); + var exportContext = new JobExportContext(model.Options); + var taskContext = ExportTask.ScheduleNowCacheResult(exportContext, id => Url.Action(MVC.Job.Export(id))); // Try waiting for completion - if (exportContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(2))) - return RedirectToAction(finishedActionResult); + if (taskContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(1))) + return RedirectToAction(MVC.Job.Export(taskContext.Id)); else - return RedirectToAction(MVC.Config.Logging.TaskStatus(exportContext.TaskStatus.SessionId)); + return RedirectToAction(MVC.Config.Logging.TaskStatus(taskContext.TaskStatus.SessionId)); } [DiscoAuthorize(Claims.Job.Actions.Export)] - public virtual ActionResult ExportRetrieve(string id) + public virtual ActionResult ExportRetrieve(Guid id) { - if (string.IsNullOrWhiteSpace(id)) - throw new ArgumentNullException("Id"); + if (id == Guid.Empty) + throw new ArgumentNullException(nameof(id)); - string key = string.Format(ExportSessionCacheKey, id); - var context = HttpRuntime.Cache.Get(key) as ExportTaskContext; - - if (context == null) - throw new ArgumentException("The Id specified is invalid, or the export data expired (60 minutes)", nameof(id)); + if (!ExportTask.TryFromCache(id, out var context)) + throw new ArgumentException("The export id specified is invalid, or the export data expired (60 minutes)", nameof(id)); if (context.Result == null || context.Result.Result == null) throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(id)); diff --git a/Disco.Web/Areas/API/Controllers/LoggingController.cs b/Disco.Web/Areas/API/Controllers/LoggingController.cs index 91d9ee8f..412298ad 100644 --- a/Disco.Web/Areas/API/Controllers/LoggingController.cs +++ b/Disco.Web/Areas/API/Controllers/LoggingController.cs @@ -1,4 +1,5 @@ using Disco.Models.Exporting; +using Disco.Models.Services.Logging; using Disco.Services.Authorization; using Disco.Services.Logging; using Disco.Services.Tasks; @@ -23,30 +24,40 @@ namespace Disco.Web.Areas.API.Controllers [HttpPost, ValidateAntiForgeryToken, DiscoAuthorize(Claims.Config.Logging.Show)] public virtual ActionResult RetrieveEvents(string Format, DateTime? Start = null, DateTime? End = null, int? ModuleId = null, List EventTypeIds = null, int? Take = null) { - var logRetriever = new ReadLogContext() + if (string.Equals(Format, "json", StringComparison.OrdinalIgnoreCase)) { - Start = Start, - End = End, - Module = ModuleId, - EventTypes = EventTypeIds, - Take = Take - }; - var results = logRetriever.Query(Database); - - var exportFormat = ExportFormat.Xlsx; - - switch (Format.ToLower()) - { - case "json": - return Json(results, JsonRequestBehavior.AllowGet); - case "csv": - exportFormat = ExportFormat.Csv; - break; + var logRetriever = new ReadLogContext() + { + Start = Start, + End = End, + Module = ModuleId, + EventTypes = EventTypeIds, + Take = Take, + }; + var results = logRetriever.Query(Database); + return Json(results, JsonRequestBehavior.AllowGet); } + else + { + var exportFormat = ExportFormat.Xlsx; + if (string.Equals(Format, "csv", StringComparison.OrdinalIgnoreCase)) + exportFormat = ExportFormat.Csv; - var export = LogExport.GenerateExport(exportFormat, results); + var options = new LogExportOptions() + { + Format = exportFormat, + StartDate = Start, + EndDate = End, + ModuleId = ModuleId, + EventTypeIds = EventTypeIds, + Take = Take, + }; + var exportContext = new LogExportContext(options); - return File(export.Result, export.MimeType, export.Filename); + var export = exportContext.Export(Database, ScheduledTaskMockStatus.Create("Log Export")); + + return File(export.Result, export.MimeType, export.Filename); + } } public virtual ActionResult ScheduledTaskStatus(string id) diff --git a/Disco.Web/Areas/API/Controllers/UserFlagController.cs b/Disco.Web/Areas/API/Controllers/UserFlagController.cs index 6b8f1853..7a327d0a 100644 --- a/Disco.Web/Areas/API/Controllers/UserFlagController.cs +++ b/Disco.Web/Areas/API/Controllers/UserFlagController.cs @@ -409,7 +409,6 @@ namespace Disco.Web.Areas.API.Controllers #endregion #region Exporting - internal const string ExportSessionCacheKey = "UserFlagExportContext_{0}"; [DiscoAuthorize(Claims.Config.UserFlag.Export)] public virtual ActionResult Export(ExportModel Model) @@ -418,39 +417,26 @@ namespace Disco.Web.Areas.API.Controllers throw new ArgumentNullException(nameof(Model)); // Start Export - var exportContext = UserFlagExportTask.ScheduleNow(Model.Options); - - // Store Export Context in Web Cache - string key = string.Format(ExportSessionCacheKey, exportContext.TaskStatus.SessionId); - HttpRuntime.Cache.Insert(key, exportContext, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); - - // Set Task Finished Url - var finishedActionResult = MVC.Config.UserFlag.Export(exportContext.TaskStatus.SessionId, null, null); - exportContext.TaskStatus.SetFinishedUrl(Url.Action(finishedActionResult)); + var exportContext = new UserFlagExportContext(Model.Options); + var taskContext = ExportTask.ScheduleNowCacheResult(exportContext, id => Url.Action(MVC.Config.UserFlag.Export(id, null, null))); // Try waiting for completion - if (exportContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(2))) - return RedirectToAction(finishedActionResult); + if (taskContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(1))) + return RedirectToAction(MVC.Config.UserFlag.Export(taskContext.Id, null, null)); else - return RedirectToAction(MVC.Config.Logging.TaskStatus(exportContext.TaskStatus.SessionId)); + return RedirectToAction(MVC.Config.Logging.TaskStatus(taskContext.TaskStatus.SessionId)); } [DiscoAuthorize(Claims.Config.UserFlag.Export)] - public virtual ActionResult ExportRetrieve(string Id) + public virtual ActionResult ExportRetrieve(Guid id) { - if (string.IsNullOrWhiteSpace(Id)) - throw new ArgumentNullException("Id"); - - string key = string.Format(ExportSessionCacheKey, Id); - var context = HttpRuntime.Cache.Get(key) as ExportTaskContext; - - if (context == null) - throw new ArgumentException("The Id specified is invalid, or the export data expired (60 minutes)", nameof(Id)); + if (!ExportTask.TryFromCache(id, out var context)) + throw new ArgumentException("The export id specified is invalid, or the export data expired (60 minutes)", nameof(id)); if (context.Result == null || context.Result.Result == null) - throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(Id)); + throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(id)); if (context.Result.RecordCount == 0) - throw new ArgumentException("No records were found to export", nameof(Id)); + throw new ArgumentException("No records were found to export", nameof(id)); var fileStream = context.Result.Result; diff --git a/Disco.Web/Areas/Config/Controllers/DeviceFlagController.cs b/Disco.Web/Areas/Config/Controllers/DeviceFlagController.cs index a63dd9ba..a23ec72e 100644 --- a/Disco.Web/Areas/Config/Controllers/DeviceFlagController.cs +++ b/Disco.Web/Areas/Config/Controllers/DeviceFlagController.cs @@ -122,7 +122,7 @@ namespace Disco.Web.Areas.Config.Controllers #region Export [DiscoAuthorizeAny(Claims.Config.DeviceFlag.Export), HttpGet] - public virtual ActionResult Export(string DownloadId, int? DeviceFlagId, bool? CurrentOnly) + public virtual ActionResult Export(Guid? exportId, int? deviceFlagId, bool? currentOnly) { var m = new ExportModel() { @@ -130,26 +130,20 @@ namespace Disco.Web.Areas.Config.Controllers DeviceFlags = DeviceFlagService.GetDeviceFlags(), }; - if (!string.IsNullOrWhiteSpace(DownloadId)) + if (ExportTask.TryFromCache(exportId, out var context)) { - string key = string.Format(API.Controllers.DeviceFlagController.ExportSessionCacheKey, DownloadId); - var context = HttpRuntime.Cache.Get(key) as ExportTaskContext; - - if (context != null) - { - m.ExportSessionResult = context.Result; - m.ExportSessionId = DownloadId; - } + m.ExportId = context.Id; + m.ExportResult = context.Result; } - if (DeviceFlagId.HasValue && CurrentOnly.HasValue) + if (deviceFlagId.HasValue && currentOnly.HasValue) { - m.Options.DeviceFlagIds = new List() { DeviceFlagId.Value }; - m.Options.CurrentOnly = CurrentOnly.Value; + m.Options.DeviceFlagIds = new List() { deviceFlagId.Value }; + m.Options.CurrentOnly = currentOnly.Value; } // UI Extensions - UIExtensions.ExecuteExtensions(this.ControllerContext, m); + UIExtensions.ExecuteExtensions(ControllerContext, m); return View(m); } diff --git a/Disco.Web/Areas/Config/Controllers/UserFlagController.cs b/Disco.Web/Areas/Config/Controllers/UserFlagController.cs index be499034..55f9161e 100644 --- a/Disco.Web/Areas/Config/Controllers/UserFlagController.cs +++ b/Disco.Web/Areas/Config/Controllers/UserFlagController.cs @@ -124,7 +124,7 @@ namespace Disco.Web.Areas.Config.Controllers #region Export [DiscoAuthorizeAny(Claims.Config.UserFlag.Export), HttpGet] - public virtual ActionResult Export(string DownloadId, int? UserFlagId, bool? CurrentOnly) + public virtual ActionResult Export(Guid? exportId, int? userFlagId, bool? currentOnly) { var m = new ExportModel() { @@ -132,26 +132,20 @@ namespace Disco.Web.Areas.Config.Controllers UserFlags = UserFlagService.GetUserFlags(), }; - if (!string.IsNullOrWhiteSpace(DownloadId)) + if (ExportTask.TryFromCache(exportId, out var context)) { - string key = string.Format(API.Controllers.UserFlagController.ExportSessionCacheKey, DownloadId); - var context = HttpRuntime.Cache.Get(key) as ExportTaskContext; - - if (context != null) - { - m.ExportSessionResult = context.Result; - m.ExportSessionId = DownloadId; - } + m.ExportId = exportId; + m.ExportResult = context.Result; } - if (UserFlagId.HasValue && CurrentOnly.HasValue) + if (userFlagId.HasValue && currentOnly.HasValue) { - m.Options.UserFlagIds = new List() { UserFlagId.Value }; - m.Options.CurrentOnly = CurrentOnly.Value; + m.Options.UserFlagIds = new List() { userFlagId.Value }; + m.Options.CurrentOnly = currentOnly.Value; } // UI Extensions - UIExtensions.ExecuteExtensions(this.ControllerContext, m); + UIExtensions.ExecuteExtensions(ControllerContext, m); return View(m); } diff --git a/Disco.Web/Areas/Config/Models/DeviceFlag/ExportModel.cs b/Disco.Web/Areas/Config/Models/DeviceFlag/ExportModel.cs index 7efc7e8f..5578ebf4 100644 --- a/Disco.Web/Areas/Config/Models/DeviceFlag/ExportModel.cs +++ b/Disco.Web/Areas/Config/Models/DeviceFlag/ExportModel.cs @@ -1,6 +1,7 @@ using Disco.Models.Areas.Config.UI.DeviceFlag; using Disco.Models.Services.Devices.DeviceFlag; using Disco.Models.Services.Exporting; +using System; using System.Collections.Generic; namespace Disco.Web.Areas.Config.Models.DeviceFlag @@ -9,8 +10,8 @@ namespace Disco.Web.Areas.Config.Models.DeviceFlag { public DeviceFlagExportOptions Options { get; set; } - public string ExportSessionId { get; set; } - public ExportResult ExportSessionResult { get; set; } + public Guid? ExportId { get; set; } + public ExportResult ExportResult { get; set; } public List DeviceFlags { get; set; } } diff --git a/Disco.Web/Areas/Config/Models/UserFlag/ExportModel.cs b/Disco.Web/Areas/Config/Models/UserFlag/ExportModel.cs index 0a3b8398..673ceb2c 100644 --- a/Disco.Web/Areas/Config/Models/UserFlag/ExportModel.cs +++ b/Disco.Web/Areas/Config/Models/UserFlag/ExportModel.cs @@ -1,6 +1,7 @@ using Disco.Models.Areas.Config.UI.UserFlag; using Disco.Models.Services.Exporting; using Disco.Models.Services.Users.UserFlags; +using System; using System.Collections.Generic; namespace Disco.Web.Areas.Config.Models.UserFlag @@ -9,8 +10,8 @@ namespace Disco.Web.Areas.Config.Models.UserFlag { public UserFlagExportOptions Options { get; set; } - public string ExportSessionId { get; set; } - public ExportResult ExportSessionResult { get; set; } + public Guid? ExportId { get; set; } + public ExportResult ExportResult { get; set; } public List UserFlags { get; set; } } diff --git a/Disco.Web/Areas/Config/Views/DeviceBatch/Show.cshtml b/Disco.Web/Areas/Config/Views/DeviceBatch/Show.cshtml index 18d756ec..907133cb 100644 --- a/Disco.Web/Areas/Config/Views/DeviceBatch/Show.cshtml +++ b/Disco.Web/Areas/Config/Views/DeviceBatch/Show.cshtml @@ -1017,7 +1017,7 @@ { if (Authorization.Has(Claims.Device.Actions.Export)) { - @Html.ActionLinkButton("Export Devices", MVC.Device.Export(null, Disco.Models.Services.Devices.Exporting.DeviceExportTypes.Batch, Model.DeviceBatch.Id)) + @Html.ActionLinkButton("Export Devices", MVC.Device.Export(null, Disco.Models.Services.Devices.DeviceExportTypes.Batch, Model.DeviceBatch.Id)) } if (Authorization.Has(Claims.Device.Search) && Model.DeviceCount > 0) { diff --git a/Disco.Web/Areas/Config/Views/DeviceBatch/Show.generated.cs b/Disco.Web/Areas/Config/Views/DeviceBatch/Show.generated.cs index fa94c260..33ff58ab 100644 --- a/Disco.Web/Areas/Config/Views/DeviceBatch/Show.generated.cs +++ b/Disco.Web/Areas/Config/Views/DeviceBatch/Show.generated.cs @@ -2778,14 +2778,14 @@ WriteLiteral(" "); #line hidden #line 1020 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" - Write(Html.ActionLinkButton("Export Devices", MVC.Device.Export(null, Disco.Models.Services.Devices.Exporting.DeviceExportTypes.Batch, Model.DeviceBatch.Id))); + Write(Html.ActionLinkButton("Export Devices", MVC.Device.Export(null, Disco.Models.Services.Devices.DeviceExportTypes.Batch, Model.DeviceBatch.Id))); #line default #line hidden #line 1020 "..\..\Areas\Config\Views\DeviceBatch\Show.cshtml" - + } if (Authorization.Has(Claims.Device.Search) && Model.DeviceCount > 0) { diff --git a/Disco.Web/Areas/Config/Views/DeviceFlag/Export.cshtml b/Disco.Web/Areas/Config/Views/DeviceFlag/Export.cshtml index c6dd4717..357cc372 100644 --- a/Disco.Web/Areas/Config/Views/DeviceFlag/Export.cshtml +++ b/Disco.Web/Areas/Config/Views/DeviceFlag/Export.cshtml @@ -161,17 +161,17 @@ } -@if (Model.ExportSessionId != null) +@if (Model.ExportId.HasValue) {
- @if (Model.ExportSessionResult.RecordCount == 0) + @if (Model.ExportResult.RecordCount == 0) {

No records matched the filter criteria

} else { -

@Model.ExportSessionResult.RecordCount record@(Model.ExportSessionResult.RecordCount != 1 ? "s" : null) were successfully exported.

- Download Device Flag Export +

@Model.ExportResult.RecordCount record@(Model.ExportResult.RecordCount != 1 ? "s" : null) were successfully exported.

+ Download Device Flag Export }
} -@if (Model.ExportSessionId != null) +@if (Model.ExportId.HasValue) {
- @if (Model.ExportSessionResult.RecordCount == 0) + @if (Model.ExportResult.RecordCount == 0) {

No records matched the filter criteria

} else { -

@Model.ExportSessionResult.RecordCount record@(Model.ExportSessionResult.RecordCount != 1 ? "s" : null) were successfully exported.

- Download User Flag Export +

@Model.ExportResult.RecordCount record@(Model.ExportResult.RecordCount != 1 ? "s" : null) were successfully exported.

+ Download User Flag Export }
} -@if (Model.ExportSessionId != null) +@if (Model.ExportId.HasValue) {
- @if (Model.ExportSessionResult.RecordCount == 0) + @if (Model.ExportResult.RecordCount == 0) {

No records matched the filter criteria

} else { -

@Model.ExportSessionResult.RecordCount record@(Model.ExportSessionResult.RecordCount != 1 ? "s" : null) were successfully exported.

- Download Device Export +

@Model.ExportResult.RecordCount record@(Model.ExportResult.RecordCount != 1 ? "s" : null) were successfully exported.

+ Download Device Export }
} -@if (Model.ExportSessionId != null) +@if (Model.ExportId.HasValue) {
- @if (Model.ExportSessionResult.RecordCount == 0) + @if (Model.ExportResult.RecordCount == 0) {

No records matched the filter criteria

} else { -

@Model.ExportSessionResult.RecordCount record@(Model.ExportSessionResult.RecordCount != 1 ? "s" : null) were successfully exported.

- Download Job Export +

@Model.ExportResult.RecordCount record@(Model.ExportResult.RecordCount != 1 ? "s" : null) were successfully exported.

+ Download Job Export }