feature: saved exports
initial - not feature complete
This commit is contained in:
@@ -81,6 +81,7 @@ namespace Disco.Services.Authorization
|
||||
{ "Config.UserFlag.Delete", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.UserFlag.Delete, (c, v) => c.Config.UserFlag.Delete = v, "Delete User Flags", "Can delete user flags", false) },
|
||||
{ "Config.UserFlag.Export", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.UserFlag.Export, (c, v) => c.Config.UserFlag.Export = v, "Export User Flag Assignments", "Can export user flag assignments", false) },
|
||||
{ "Config.UserFlag.Show", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.UserFlag.Show, (c, v) => c.Config.UserFlag.Show = v, "Show User Flags", "Can show user flags", false) },
|
||||
{ "Config.ManageSavedExports", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.ManageSavedExports, (c, v) => c.Config.ManageSavedExports = v, "Managed Saved Exports", "Can manage saved exports", false) },
|
||||
{ "Config.Show", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Config.Show, (c, v) => c.Config.Show = v, "Show Configuration", "Can show the configuration menu", false) },
|
||||
{ "Job.Lists.AllOpen", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Job.Lists.AllOpen, (c, v) => c.Job.Lists.AllOpen = v, "All Open List", "Can show list", false) },
|
||||
{ "Job.Lists.AwaitingFinanceAgreementBreach", new Tuple<Func<RoleClaims, bool>, Action<RoleClaims, bool>, string, string, bool>(c => c.Job.Lists.AwaitingFinanceAgreementBreach, (c, v) => c.Job.Lists.AwaitingFinanceAgreementBreach = v, "Awaiting Finance Agreement Breach List", "Can show list (NOTE: Requires Awaiting Finance List)", false) },
|
||||
@@ -325,6 +326,7 @@ namespace Disco.Services.Authorization
|
||||
new ClaimNavigatorItem("Config.UserFlag.Export", false),
|
||||
new ClaimNavigatorItem("Config.UserFlag.Show", false)
|
||||
}),
|
||||
new ClaimNavigatorItem("Config.ManageSavedExports", false),
|
||||
new ClaimNavigatorItem("Config.Show", false)
|
||||
}),
|
||||
new ClaimNavigatorItem("Job", "Job", "Permissions related to Jobs", false, new List<IClaimNavigatorItem>() {
|
||||
@@ -623,6 +625,7 @@ namespace Disco.Services.Authorization
|
||||
c.Config.UserFlag.Delete = true;
|
||||
c.Config.UserFlag.Export = true;
|
||||
c.Config.UserFlag.Show = true;
|
||||
c.Config.ManageSavedExports = true;
|
||||
c.Config.Show = true;
|
||||
c.Job.Lists.AllOpen = true;
|
||||
c.Job.Lists.AwaitingFinanceAgreementBreach = true;
|
||||
@@ -1187,6 +1190,11 @@ namespace Disco.Services.Authorization
|
||||
public const string Show = "Config.UserFlag.Show";
|
||||
}
|
||||
|
||||
/// <summary>Managed Saved Exports
|
||||
/// <para>Can manage saved exports</para>
|
||||
/// </summary>
|
||||
public const string ManageSavedExports = "Config.ManageSavedExports";
|
||||
|
||||
/// <summary>Show Configuration
|
||||
/// <para>Can show the configuration menu</para>
|
||||
/// </summary>
|
||||
|
||||
@@ -38,6 +38,9 @@ namespace Disco.Services.Authorization.Roles.ClaimGroups.Configuration
|
||||
[ClaimDetails("Show Configuration", "Can show the configuration menu")]
|
||||
public bool Show { get; set; }
|
||||
|
||||
[ClaimDetails("Managed Saved Exports", "Can manage saved exports")]
|
||||
public bool ManageSavedExports { get; set; }
|
||||
|
||||
public DeviceCertificateClaims DeviceCertificate { get; set; }
|
||||
|
||||
public EnrolmentClaims Enrolment { get; set; }
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
using Disco.Models.Exporting;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Devices;
|
||||
using Disco.Models.Services.Devices.DeviceFlag;
|
||||
using Disco.Models.Services.Exporting;
|
||||
using Disco.Services.Devices.DeviceFlags;
|
||||
using Disco.Services.Exporting;
|
||||
using Disco.Services.Plugins.Features.DetailsProvider;
|
||||
using Disco.Services.Tasks;
|
||||
@@ -19,31 +21,22 @@ namespace Disco.Services.Devices
|
||||
public class DeviceExport : IExport<DeviceExportOptions, DeviceExportRecord>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public bool TimestampSuffix { get; set; }
|
||||
public string Name { get; } = "Device Export";
|
||||
public DeviceExportOptions Options { get; set; }
|
||||
|
||||
public string SuggestedFilenamePrefix { get; } = "DeviceExport";
|
||||
public string FilenamePrefix { get; } = "DeviceExport";
|
||||
public string ExcelWorksheetName { get; } = "DeviceExport";
|
||||
public string ExcelTableName { get; } = "Devices";
|
||||
|
||||
[JsonConstructor]
|
||||
private DeviceExport()
|
||||
{
|
||||
}
|
||||
|
||||
public DeviceExport(string name, string description, bool timestampSuffix, DeviceExportOptions options)
|
||||
public DeviceExport(DeviceExportOptions options)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
Name = name;
|
||||
Description = description;
|
||||
TimestampSuffix = timestampSuffix;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public DeviceExport(DeviceExportOptions options)
|
||||
: this("Device Export", null, true, options)
|
||||
[JsonConstructor]
|
||||
public DeviceExport()
|
||||
: this(DeviceExportOptions.DefaultOptions())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -17,31 +17,22 @@ namespace Disco.Services.Devices.DeviceFlags
|
||||
public class DeviceFlagExport : IExport<DeviceFlagExportOptions, DeviceFlagExportRecord>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public bool TimestampSuffix { get; set; }
|
||||
public string Name { get; } = "Device Flag Export";
|
||||
public DeviceFlagExportOptions Options { get; set; }
|
||||
|
||||
public string SuggestedFilenamePrefix { get; } = "DeviceFlagExport";
|
||||
public string FilenamePrefix { get; } = "DeviceFlagExport";
|
||||
public string ExcelWorksheetName { get; } = "DeviceFlagExport";
|
||||
public string ExcelTableName { get; } = "DeviceFlags";
|
||||
|
||||
[JsonConstructor]
|
||||
private DeviceFlagExport()
|
||||
{
|
||||
}
|
||||
|
||||
public DeviceFlagExport(string name, string description, bool timestampSuffix, DeviceFlagExportOptions options)
|
||||
public DeviceFlagExport(DeviceFlagExportOptions options)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
Name = name;
|
||||
Description = description;
|
||||
TimestampSuffix = timestampSuffix;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public DeviceFlagExport(DeviceFlagExportOptions options)
|
||||
: this("Device Flag Export", null, true, options)
|
||||
[JsonConstructor]
|
||||
public DeviceFlagExport()
|
||||
: this(DeviceFlagExportOptions.DefaultOptions())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -425,6 +425,8 @@
|
||||
<Compile Include="Documents\ManagedGroups\DocumentTemplateUsersManagedGroup.cs" />
|
||||
<Compile Include="Documents\QRCodeBinaryEncoder.cs" />
|
||||
<Compile Include="Exporting\IExport.cs" />
|
||||
<Compile Include="Exporting\SavedExports.cs" />
|
||||
<Compile Include="Exporting\SavedExportTask.cs" />
|
||||
<Compile Include="Expressions\EvaluateExpressionParseException.cs" />
|
||||
<Compile Include="Expressions\EvaluateExpressionPart.cs" />
|
||||
<Compile Include="Expressions\Expression.cs" />
|
||||
@@ -531,6 +533,7 @@
|
||||
<Compile Include="Plugins\Features\DetailsProvider\DetailsProviderService.cs" />
|
||||
<Compile Include="Plugins\Features\DetailsProvider\UserContactFeature.cs" />
|
||||
<Compile Include="Plugins\Features\DocumentHandlerProvider\DocumentHandlerProviderFeature.cs" />
|
||||
<Compile Include="Plugins\Features\ExportProvider\ExportProviderFeature.cs" />
|
||||
<Compile Include="Plugins\Features\ExpressionExtensionProvider\ExpressionExtensionProviderFeature.cs" />
|
||||
<Compile Include="Plugins\Features\ExpressionExtensionProvider\ExpressionExtensionRegistration.cs" />
|
||||
<Compile Include="Plugins\Features\InsuranceProvider\InsuranceProviderFeature.cs" />
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Disco.Services.Exporting
|
||||
{
|
||||
private IExport context;
|
||||
public override string TaskName { get => context?.Name ?? "Exporting"; }
|
||||
public override bool SingleInstanceTask { get { return false; } }
|
||||
public override bool CancelInitiallySupported { get { return false; } }
|
||||
public override bool SingleInstanceTask { get; } = false;
|
||||
public override bool CancelInitiallySupported { get; } = false;
|
||||
|
||||
public static ExportTaskContext ScheduleNow(IExport export)
|
||||
{
|
||||
|
||||
@@ -16,35 +16,32 @@ namespace Disco.Services.Exporting
|
||||
{
|
||||
public static class Exporter
|
||||
{
|
||||
public static ExportResult Export<T, R>(IExport<T, R> context, DiscoDataContext database, IScheduledTaskStatus status)
|
||||
public static ExportResult Export<T, R>(IExport<T, R> export, DiscoDataContext database, IScheduledTaskStatus status)
|
||||
where T : IExportOptions, new()
|
||||
where R : IExportRecord
|
||||
{
|
||||
MemoryStream stream;
|
||||
string mimeType;
|
||||
|
||||
status.UpdateStatus(1, $"Exporting {context.Name}", "Gathering data");
|
||||
status.UpdateStatus(1, $"Exporting {export.Name}", "Gathering data");
|
||||
|
||||
var records = context.BuildRecords(database, status);
|
||||
var records = export.BuildRecords(database, status);
|
||||
|
||||
status.UpdateStatus(70, "Building metadata");
|
||||
|
||||
var metadata = context.BuildMetadata(database, records, status);
|
||||
var metadata = export.BuildMetadata(database, records, status);
|
||||
|
||||
if (metadata.Count == 0)
|
||||
throw new ArgumentException("At least one export field must be specified", nameof(context.Options));
|
||||
throw new ArgumentException("At least one export field must be specified", nameof(export.Options));
|
||||
|
||||
var filenameBuilder = new StringBuilder();
|
||||
filenameBuilder.Append(context.SuggestedFilenamePrefix);
|
||||
if (context.TimestampSuffix)
|
||||
{
|
||||
filenameBuilder.Append('-');
|
||||
filenameBuilder.Append(status.StartedTimestamp.Value.ToString("yyyyMMdd-HHmmss"));
|
||||
}
|
||||
filenameBuilder.Append(export.FilenamePrefix);
|
||||
filenameBuilder.Append('-');
|
||||
filenameBuilder.Append(status.StartedTimestamp.Value.ToString("yyyyMMdd-HHmmss"));
|
||||
|
||||
status.UpdateStatus(80, $"Rendering {records.Count} records for export");
|
||||
|
||||
switch (context.Options.Format)
|
||||
switch (export.Options.Format)
|
||||
{
|
||||
case ExportFormat.Csv:
|
||||
filenameBuilder.Append(".csv");
|
||||
@@ -54,10 +51,10 @@ namespace Disco.Services.Exporting
|
||||
case ExportFormat.Xlsx:
|
||||
filenameBuilder.Append(".xlsx");
|
||||
mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
stream = WriteXlsx(context.ExcelWorksheetName, context.ExcelTableName, metadata, records);
|
||||
stream = WriteXlsx(export.ExcelWorksheetName, export.ExcelTableName, metadata, records);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Unsupported export format: {context.Options.Format}");
|
||||
throw new NotSupportedException($"Unsupported export format: {export.Options.Format}");
|
||||
}
|
||||
|
||||
return new ExportResult()
|
||||
@@ -99,7 +96,6 @@ namespace Disco.Services.Exporting
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
|
||||
private static MemoryStream WriteXlsx<T>(string worksheetName, string tableName, List<ExportMetadataField<T>> metadata, List<T> records) where T : IExportRecord
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
@@ -151,7 +147,6 @@ namespace Disco.Services.Exporting
|
||||
|
||||
metadata.Add(columnName, valueAccessor, csvValueEncoder);
|
||||
}
|
||||
|
||||
public static void Add<T, V>(this ExportMetadata<T> metadata, string columnName, Func<T, V> valueAccessor, Func<object, string> csvValueEncoder = null)
|
||||
where T : IExportRecord
|
||||
{
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Disco.Services.Exporting
|
||||
public interface IExport
|
||||
{
|
||||
Guid Id { get; set; }
|
||||
string Name { get; set; }
|
||||
string Description { get; set; }
|
||||
[JsonIgnore]
|
||||
string Name { get; }
|
||||
|
||||
ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status);
|
||||
}
|
||||
@@ -22,10 +22,8 @@ namespace Disco.Services.Exporting
|
||||
where T : IExportOptions, new()
|
||||
where R : IExportRecord
|
||||
{
|
||||
bool TimestampSuffix { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
string SuggestedFilenamePrefix { get; }
|
||||
string FilenamePrefix { get; }
|
||||
[JsonIgnore]
|
||||
string ExcelWorksheetName { get; }
|
||||
[JsonIgnore]
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Services.Tasks;
|
||||
using Quartz;
|
||||
using System;
|
||||
|
||||
namespace Disco.Services.Exporting
|
||||
{
|
||||
public class SavedExportTask : ScheduledTask
|
||||
{
|
||||
public override string TaskName { get; } = "Saved Export Scheduler";
|
||||
public override bool SingleInstanceTask { get; } = true;
|
||||
public override bool CancelInitiallySupported { get; } = false;
|
||||
public override bool LogExceptionsOnly { get; } = true;
|
||||
|
||||
public override void InitalizeScheduledTask(DiscoDataContext Database)
|
||||
{
|
||||
|
||||
// run in 30 seconds, then every hour on the hour
|
||||
if (DateTime.Now.Minute != 59)
|
||||
{
|
||||
var immediateTrigger = TriggerBuilder.Create().StartAt(DateTimeOffset.Now.AddSeconds(30));
|
||||
ScheduleTask(immediateTrigger);
|
||||
}
|
||||
|
||||
var nextHourTicks = DateTime.UtcNow.Ticks;
|
||||
nextHourTicks -= nextHourTicks % TimeSpan.TicksPerHour; // round down to the hour
|
||||
var nextHour = new DateTime(nextHourTicks, DateTimeKind.Utc)
|
||||
.AddHours(1)
|
||||
.AddSeconds(1);
|
||||
var hourlyTrigger = TriggerBuilder.Create()
|
||||
.StartAt(nextHour)
|
||||
.WithSchedule(SimpleScheduleBuilder.RepeatHourlyForever());
|
||||
ScheduleTask(hourlyTrigger);
|
||||
}
|
||||
|
||||
protected override void ExecuteTask()
|
||||
{
|
||||
SavedExports.EvaluateSavedExports();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Exporting;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Devices;
|
||||
using Disco.Models.Services.Devices.DeviceFlag;
|
||||
using Disco.Models.Services.Exporting;
|
||||
using Disco.Models.Services.Jobs;
|
||||
using Disco.Models.Services.Users.UserFlags;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Devices;
|
||||
using Disco.Services.Devices.DeviceFlags;
|
||||
using Disco.Services.Jobs;
|
||||
using Disco.Services.Logging;
|
||||
using Disco.Services.Tasks;
|
||||
using Disco.Services.Users.UserFlags;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Disco.Services.Exporting
|
||||
{
|
||||
public static class SavedExports
|
||||
{
|
||||
private static Dictionary<string, (Type Type, string Name, Func<IExport, DiscoDataContext, IScheduledTaskStatus, ExportResult> ExporterDelegate)> exportTypes = new Dictionary<string, (Type, string, Func<IExport, DiscoDataContext, IScheduledTaskStatus, ExportResult>)>();
|
||||
|
||||
static SavedExports()
|
||||
{
|
||||
RegisterExportType<DeviceFlagExport, DeviceFlagExportOptions, DeviceFlagExportRecord>();
|
||||
RegisterExportType<DeviceExport, DeviceExportOptions, DeviceExportRecord>();
|
||||
RegisterExportType<JobExport, JobExportOptions, JobExportRecord>();
|
||||
RegisterExportType<UserFlagExport, UserFlagExportOptions, UserFlagExportRecord>();
|
||||
}
|
||||
|
||||
internal static void RegisterExportType<T, E, R>()
|
||||
where T : IExport<E, R>, new()
|
||||
where E : IExportOptions, new()
|
||||
where R : IExportRecord
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
if (exportTypes.TryGetValue(type.Name, out var existing))
|
||||
{
|
||||
if (existing.Type != type)
|
||||
throw new InvalidOperationException($"Export type already registered ({type.FullName})");
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = new T().Name;
|
||||
exportTypes[type.Name] = (type, name, (i, d, s) => Exporter.Export((T)i, d, s));
|
||||
}
|
||||
}
|
||||
|
||||
public static SavedExport SaveExport<T, R>(IExport<T, R> export, DiscoDataContext database, User createdBy)
|
||||
where T : IExportOptions, new()
|
||||
where R : IExportRecord
|
||||
{
|
||||
var exportType = export.GetType();
|
||||
if (!exportTypes.TryGetValue(exportType.Name, out var exportTypeRef) || exportType != exportTypeRef.Type)
|
||||
throw new InvalidOperationException($"Export type not registered for saving ({exportType.FullName})");
|
||||
|
||||
var saved = new SavedExport()
|
||||
{
|
||||
Version = 1,
|
||||
Id = Guid.NewGuid(),
|
||||
CreatedOn = DateTime.Now,
|
||||
CreatedBy = createdBy.UserId,
|
||||
Type = exportType.Name,
|
||||
Config = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(export, Formatting.None))),
|
||||
Enabled = false,
|
||||
};
|
||||
|
||||
var exports = database.DiscoConfiguration.SavedExports;
|
||||
exports.Add(saved);
|
||||
database.DiscoConfiguration.SavedExports = exports;
|
||||
database.SaveChanges();
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
public static void DeleteSavedExport(DiscoDataContext database, Guid id)
|
||||
{
|
||||
var exports = database.DiscoConfiguration.SavedExports;
|
||||
var existing = exports.FirstOrDefault(e => e.Id == id);
|
||||
if (existing == null)
|
||||
return;
|
||||
exports.Remove(existing);
|
||||
database.DiscoConfiguration.SavedExports = exports;
|
||||
database.SaveChanges();
|
||||
}
|
||||
|
||||
public static void UpdateSavedExport(DiscoDataContext database, SavedExport export)
|
||||
{
|
||||
var exports = database.DiscoConfiguration.SavedExports;
|
||||
var existing = exports.First(e => e.Id == export.Id);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(export.Name))
|
||||
throw new InvalidOperationException("Export name is required");
|
||||
|
||||
existing.Name = export.Name;
|
||||
existing.Description = export.Description;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(export.FilePath) || export.Schedule == null || export.Schedule.WeekDays == 0)
|
||||
{
|
||||
existing.Schedule = null;
|
||||
existing.FilePath = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// new file path - file cannot exist
|
||||
if (existing.FilePath == null && File.Exists(export.FilePath))
|
||||
throw new InvalidOperationException("Export file path already exists, delete the file before configuring the saved export");
|
||||
|
||||
// directory must exist
|
||||
if (!Directory.Exists(Path.GetDirectoryName(export.FilePath)))
|
||||
throw new InvalidOperationException("Invalid export file path, the directory does not exist");
|
||||
|
||||
existing.FilePath = export.FilePath;
|
||||
existing.TimestampSuffix = export.TimestampSuffix;
|
||||
|
||||
existing.Schedule = new SavedExportSchedule()
|
||||
{
|
||||
Version = 1,
|
||||
WeekDays = export.Schedule.WeekDays,
|
||||
StartHour = export.Schedule.EndHour.HasValue ? Math.Min(export.Schedule.StartHour, export.Schedule.EndHour.Value) : export.Schedule.StartHour,
|
||||
EndHour = !export.Schedule.EndHour.HasValue ? null : (export.Schedule.EndHour.Value == export.Schedule.StartHour ? (byte?)null : Math.Max(export.Schedule.StartHour, export.Schedule.EndHour.Value)),
|
||||
};
|
||||
}
|
||||
|
||||
if (existing.FilePath != null && existing.Schedule == null)
|
||||
throw new InvalidOperationException("Export file path requires a schedule");
|
||||
|
||||
if (export.OnDemandPrincipals == null || export.OnDemandPrincipals.Count == 0)
|
||||
existing.OnDemandPrincipals = null;
|
||||
else
|
||||
existing.OnDemandPrincipals = new List<string>(export.OnDemandPrincipals);
|
||||
|
||||
existing.Enabled = true;
|
||||
|
||||
database.DiscoConfiguration.SavedExports = exports;
|
||||
database.SaveChanges();
|
||||
}
|
||||
|
||||
public static SavedExport GetSavedExport(DiscoDataContext database, Guid id, out string exportTypeName)
|
||||
{
|
||||
var export = database.DiscoConfiguration.SavedExports.FirstOrDefault(e => e.Id == id);
|
||||
|
||||
if (export == null)
|
||||
{
|
||||
exportTypeName = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
exportTypeName = exportTypes[export.Type].Name;
|
||||
return export;
|
||||
}
|
||||
|
||||
public static List<SavedExport> GetSavedExports(DiscoDataContext database, string type, out string exportTypeName)
|
||||
{
|
||||
var exports = database.DiscoConfiguration.SavedExports.Where(e => e.Type == type).ToList();
|
||||
|
||||
if (exports.Count == 0)
|
||||
{
|
||||
exportTypeName = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
exportTypeName = exportTypes[type].Name;
|
||||
return exports;
|
||||
}
|
||||
|
||||
public static bool IsAuthorized(SavedExport savedExport, AuthorizationToken authorization)
|
||||
{
|
||||
if (authorization.Has(Claims.Config.ManageSavedExports))
|
||||
return true;
|
||||
|
||||
if (savedExport.OnDemandPrincipals == null || savedExport.OnDemandPrincipals.Count == 0)
|
||||
return false;
|
||||
|
||||
if (savedExport.OnDemandPrincipals.Contains(authorization.User.UserId, StringComparer.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
if (savedExport.OnDemandPrincipals.Any(p => authorization.GroupMembership.Contains(p, StringComparer.OrdinalIgnoreCase)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void EvaluateSavedExports()
|
||||
{
|
||||
using (var database = new DiscoDataContext())
|
||||
{
|
||||
CleanupSavedExports(database);
|
||||
|
||||
var scheduledExports = GetScheduledExports(database).ToList();
|
||||
|
||||
foreach (var scheduledExport in scheduledExports)
|
||||
{
|
||||
ExportResult exportResult = null;
|
||||
try
|
||||
{
|
||||
exportResult = EvaluateSavedExport(database, scheduledExport);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SystemLog.LogException($"Failed to generate saved '{scheduledExport.Name}' [{scheduledExport.Id}]", ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
var filePath = scheduledExport.FilePath;
|
||||
if (scheduledExport.TimestampSuffix)
|
||||
{
|
||||
var timestamp = DateTime.Now.ToString("yyyyMMdd-HH");
|
||||
var extension = Path.GetExtension(filePath);
|
||||
filePath = Path.Combine(Path.GetDirectoryName(filePath), $"{Path.GetFileNameWithoutExtension(filePath)}-{timestamp}{extension}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
exportResult.Result.CopyTo(fileStream);
|
||||
}
|
||||
|
||||
SystemLog.LogInformation($"Saved '{scheduledExport.Name}' [{scheduledExport.Name}] wrote to '{filePath}'");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SystemLog.LogException($"Failed to write saved '{scheduledExport.Name}' [{scheduledExport.Id}] to '{filePath}'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ExportResult EvaluateSavedExport(DiscoDataContext database, SavedExport savedExport)
|
||||
{
|
||||
var (exportType, _, exportDelegate) = exportTypes[savedExport.Type];
|
||||
var export = (IExport)JsonConvert.DeserializeObject(Encoding.UTF8.GetString(Convert.FromBase64String(savedExport.Config)), exportType);
|
||||
|
||||
return exportDelegate(export, database, ScheduledTaskMockStatus.Create(export.Name));
|
||||
}
|
||||
|
||||
private static void CleanupSavedExports(DiscoDataContext database)
|
||||
{
|
||||
var exports = database.DiscoConfiguration.SavedExports;
|
||||
var changed = false;
|
||||
|
||||
for (int i = 0; i < exports.Count; i++)
|
||||
{
|
||||
var export = exports[i];
|
||||
if (export.Enabled)
|
||||
continue;
|
||||
|
||||
if (export.CreatedOn.AddDays(1) < DateTime.Now)
|
||||
{
|
||||
exports.RemoveAt(i);
|
||||
i--;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
database.DiscoConfiguration.SavedExports = exports;
|
||||
database.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<SavedExport> GetScheduledExports(DiscoDataContext database)
|
||||
{
|
||||
var exports = database.DiscoConfiguration.SavedExports;
|
||||
var now = DateTime.Now;
|
||||
var hour = now.Hour;
|
||||
var day = (byte)(1 << (int)now.DayOfWeek);
|
||||
|
||||
foreach (var export in exports)
|
||||
{
|
||||
if (!export.Enabled)
|
||||
continue;
|
||||
|
||||
if (string.IsNullOrEmpty(export.FilePath))
|
||||
continue;
|
||||
|
||||
// skip unknown export types
|
||||
if (!exportTypes.ContainsKey(export.Type))
|
||||
continue;
|
||||
|
||||
var schedule = export.Schedule;
|
||||
if (schedule == null)
|
||||
continue;
|
||||
|
||||
// scheduled for today?
|
||||
if ((schedule.WeekDays & day) == 0)
|
||||
continue;
|
||||
|
||||
// always run if scheduled earlier today? (potentially missed)
|
||||
if (schedule.StartHour >= hour || export.LastRunOn.GetValueOrDefault().Date == DateTime.Today)
|
||||
{
|
||||
// are we beyond the end hour?
|
||||
if (schedule.EndHour.HasValue && hour > schedule.EndHour.Value)
|
||||
continue;
|
||||
|
||||
// if no end hour and not the start hour, skip
|
||||
if (!schedule.EndHour.HasValue && schedule.StartHour != hour)
|
||||
continue;
|
||||
|
||||
// before the start hour, skip
|
||||
if (hour < schedule.StartHour)
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return export;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,31 +18,23 @@ namespace Disco.Services.Jobs
|
||||
public class JobExport : IExport<JobExportOptions, JobExportRecord>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public bool TimestampSuffix { get; set; }
|
||||
public string Name { get; } = "Job Export";
|
||||
public JobExportOptions Options { get; set; }
|
||||
|
||||
public string SuggestedFilenamePrefix { get; } = "JobExport";
|
||||
public string FilenamePrefix { get; } = "JobExport";
|
||||
public string ExcelWorksheetName { get; } = "JobExport";
|
||||
public string ExcelTableName { get; } = "Jobs";
|
||||
|
||||
[JsonConstructor]
|
||||
private JobExport()
|
||||
{
|
||||
}
|
||||
|
||||
public JobExport(string name, string description, bool timestampSuffix, JobExportOptions options)
|
||||
public JobExport(JobExportOptions options)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
Name = name;
|
||||
Description = description;
|
||||
TimestampSuffix = timestampSuffix;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public JobExport(JobExportOptions options)
|
||||
: this("Job Export", null, true, options)
|
||||
|
||||
[JsonConstructor]
|
||||
public JobExport()
|
||||
: this(JobExportOptions.DefaultOptions())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,10 @@ namespace Disco.Services.Logging
|
||||
public class LogExport : IExport<LogExportOptions, LogLiveEvent>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public bool TimestampSuffix { get; set; }
|
||||
public string Name { get; } = "Log Export";
|
||||
public LogExportOptions Options { get; set; }
|
||||
|
||||
public string SuggestedFilenamePrefix { get; } = "DiscoIctLogs";
|
||||
public string FilenamePrefix { get; } = "DiscoIctLogs";
|
||||
public string ExcelWorksheetName { get; } = "Disco ICT Logs";
|
||||
public string ExcelTableName { get; } = "DiscoIctLogs";
|
||||
|
||||
@@ -29,20 +27,12 @@ namespace Disco.Services.Logging
|
||||
{
|
||||
}
|
||||
|
||||
public LogExport(string name, string description, bool timestampSuffix, LogExportOptions options)
|
||||
public LogExport(LogExportOptions options)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
Name = name;
|
||||
Description = description;
|
||||
TimestampSuffix = timestampSuffix;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public LogExport(LogExportOptions options)
|
||||
: this("Log Export", null, true, options)
|
||||
{
|
||||
}
|
||||
|
||||
public ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status)
|
||||
=> Exporter.Export(this, database, status);
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using Disco.Models.Exporting;
|
||||
using Disco.Models.Services.Exporting;
|
||||
using Disco.Services.Exporting;
|
||||
|
||||
namespace Disco.Services.Plugins.Features.ExportProvider
|
||||
{
|
||||
[PluginFeatureCategory(DisplayName = "Exporter")]
|
||||
public class ExportProviderFeature : PluginFeature
|
||||
{
|
||||
public void RegisterExportType<T, E, R>()
|
||||
where T : IExport<E, R>, new()
|
||||
where E : IExportOptions, new()
|
||||
where R : IExportRecord
|
||||
{
|
||||
SavedExports.RegisterExportType<T, E, R>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,37 +13,32 @@ using System.Linq;
|
||||
|
||||
namespace Disco.Services.Users.UserFlags
|
||||
{
|
||||
public class UserFlagExport : IExport<UserFlagExportOptions, UserFlagExportRecord>
|
||||
public sealed class UserFlagExport : IExport<UserFlagExportOptions, UserFlagExportRecord>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Name { get; } = "User Flag Export";
|
||||
public string Description { get; set; }
|
||||
public bool TimestampSuffix { get; set; }
|
||||
public UserFlagExportOptions Options { get; set; }
|
||||
|
||||
public string SuggestedFilenamePrefix { get; } = "UserFlagExport";
|
||||
public string FilenamePrefix { get; } = "UserFlagExport";
|
||||
public string ExcelWorksheetName { get; } = "UserFlagExport";
|
||||
public string ExcelTableName { get; } = "UserFlags";
|
||||
|
||||
[JsonConstructor]
|
||||
private UserFlagExport()
|
||||
{
|
||||
}
|
||||
|
||||
public UserFlagExport(string name, string description, bool timestampSuffix, UserFlagExportOptions options)
|
||||
public UserFlagExport(UserFlagExportOptions options)
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
Name = name;
|
||||
Description = description;
|
||||
TimestampSuffix = timestampSuffix;
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public UserFlagExport(UserFlagExportOptions options)
|
||||
: this("User Flag Export", null, true, options)
|
||||
[JsonConstructor]
|
||||
public UserFlagExport()
|
||||
: this(UserFlagExportOptions.DefaultOptions())
|
||||
{
|
||||
}
|
||||
|
||||
public static UserFlagExport Create()
|
||||
=> new UserFlagExport(new UserFlagExportOptions());
|
||||
|
||||
public ExportResult Export(DiscoDataContext database, IScheduledTaskStatus status)
|
||||
=> Exporter.Export(this, database, status);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user