Feature: Job Queues
Also UI style, theme and element changes
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class JobExtensions
|
||||
{
|
||||
public static JobTableStatusItemModel ToJobTableStatusItemModel(this Job j)
|
||||
{
|
||||
var i = new JobTableStatusItemModel()
|
||||
{
|
||||
Id = j.Id,
|
||||
OpenedDate = j.OpenedDate,
|
||||
ClosedDate = j.ClosedDate,
|
||||
JobTypeId = j.JobTypeId,
|
||||
JobTypeDescription = j.JobType.Description,
|
||||
DeviceHeldLocation = j.DeviceHeldLocation,
|
||||
Flags = j.Flags,
|
||||
|
||||
WaitingForUserAction = j.WaitingForUserAction,
|
||||
DeviceReadyForReturn = j.DeviceReadyForReturn,
|
||||
DeviceHeld = j.DeviceHeld,
|
||||
DeviceReturnedDate = j.DeviceReturnedDate
|
||||
};
|
||||
|
||||
if (j.Device != null)
|
||||
{
|
||||
i.DeviceSerialNumber = j.DeviceSerialNumber;
|
||||
i.DeviceModelDescription = j.Device.DeviceModel.Description;
|
||||
i.DeviceAddressId = j.Device.DeviceProfile.DefaultOrganisationAddress;
|
||||
|
||||
if (j.JobMetaWarranty != null)
|
||||
{
|
||||
i.JobMetaWarranty_ExternalReference = j.JobMetaWarranty.ExternalReference;
|
||||
i.JobMetaWarranty_ExternalLoggedDate = j.JobMetaWarranty.ExternalLoggedDate;
|
||||
i.JobMetaWarranty_ExternalCompletedDate = j.JobMetaWarranty.ExternalCompletedDate;
|
||||
i.JobMetaWarranty_ExternalName = j.JobMetaWarranty.ExternalName;
|
||||
}
|
||||
if (j.JobMetaNonWarranty != null)
|
||||
{
|
||||
i.JobMetaNonWarranty_RepairerLoggedDate = j.JobMetaNonWarranty.RepairerLoggedDate;
|
||||
i.JobMetaNonWarranty_RepairerCompletedDate = j.JobMetaNonWarranty.RepairerCompletedDate;
|
||||
i.JobMetaNonWarranty_AccountingChargeAddedDate = j.JobMetaNonWarranty.AccountingChargeAddedDate;
|
||||
i.JobMetaNonWarranty_AccountingChargePaidDate = j.JobMetaNonWarranty.AccountingChargePaidDate;
|
||||
i.JobMetaNonWarranty_AccountingChargeRequiredDate = j.JobMetaNonWarranty.AccountingChargeRequiredDate;
|
||||
i.JobMetaNonWarranty_IsInsuranceClaim = j.JobMetaNonWarranty.IsInsuranceClaim;
|
||||
i.JobMetaNonWarranty_RepairerName = j.JobMetaNonWarranty.RepairerName;
|
||||
if (j.JobMetaInsurance != null)
|
||||
{
|
||||
i.JobMetaInsurance_ClaimFormSentDate = j.JobMetaInsurance.ClaimFormSentDate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (j.User != null)
|
||||
{
|
||||
i.UserId = j.UserId;
|
||||
i.UserDisplayName = j.User.DisplayName;
|
||||
}
|
||||
if (j.OpenedTechUser != null)
|
||||
{
|
||||
i.OpenedTechUserId = j.OpenedTechUserId;
|
||||
i.OpenedTechUserDisplayName = j.OpenedTechUser.DisplayName;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
public static string JobStatusDescription(string StatusId, Job j = null)
|
||||
{
|
||||
switch (StatusId)
|
||||
{
|
||||
case Job.JobStatusIds.Open:
|
||||
return "Open";
|
||||
case Job.JobStatusIds.Closed:
|
||||
return "Closed";
|
||||
case Job.JobStatusIds.AwaitingWarrantyRepair:
|
||||
if (j == null)
|
||||
return "Awaiting Warranty Repair";
|
||||
else
|
||||
if (j.DeviceHeld.HasValue)
|
||||
return string.Format("Awaiting Warranty Repair ({0})", j.JobMetaWarranty.ExternalName);
|
||||
else
|
||||
return string.Format("Awaiting Warranty Repair - Not Held ({0})", j.JobMetaWarranty.ExternalName);
|
||||
case Job.JobStatusIds.AwaitingRepairs:
|
||||
if (j == null)
|
||||
return "Awaiting Repairs";
|
||||
else
|
||||
if (j.DeviceHeld.HasValue)
|
||||
return string.Format("Awaiting Repairs ({0})", j.JobMetaNonWarranty.RepairerName);
|
||||
else
|
||||
return string.Format("Awaiting Repairs - Not Held ({0})", j.JobMetaNonWarranty.RepairerName);
|
||||
case Job.JobStatusIds.AwaitingDeviceReturn:
|
||||
return "Awaiting Device Return";
|
||||
case Job.JobStatusIds.AwaitingUserAction:
|
||||
return "Awaiting User Action";
|
||||
case Job.JobStatusIds.AwaitingAccountingPayment:
|
||||
return "Awaiting Accounting Payment";
|
||||
case Job.JobStatusIds.AwaitingAccountingCharge:
|
||||
return "Awaiting Accounting Charge";
|
||||
case Job.JobStatusIds.AwaitingInsuranceProcessing:
|
||||
return "Awaiting Insurance Processing";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public static string JobStatusDescription(string StatusId, JobTableStatusItemModel j = null)
|
||||
{
|
||||
switch (StatusId)
|
||||
{
|
||||
case Job.JobStatusIds.Open:
|
||||
return "Open";
|
||||
case Job.JobStatusIds.Closed:
|
||||
return "Closed";
|
||||
case Job.JobStatusIds.AwaitingWarrantyRepair:
|
||||
if (j == null)
|
||||
return "Awaiting Warranty Repair";
|
||||
else
|
||||
if (j.DeviceHeld.HasValue)
|
||||
return string.Format("Awaiting Warranty Repair ({0})", j.JobMetaWarranty_ExternalName);
|
||||
else
|
||||
return string.Format("Awaiting Warranty Repair - Not Held ({0})", j.JobMetaWarranty_ExternalName);
|
||||
case Job.JobStatusIds.AwaitingRepairs:
|
||||
if (j == null)
|
||||
return "Awaiting Repairs";
|
||||
else
|
||||
if (j.DeviceHeld.HasValue)
|
||||
return string.Format("Awaiting Repairs ({0})", j.JobMetaNonWarranty_RepairerName);
|
||||
else
|
||||
return string.Format("Awaiting Repairs - Not Held ({0})", j.JobMetaNonWarranty_RepairerName);
|
||||
case Job.JobStatusIds.AwaitingDeviceReturn:
|
||||
return "Awaiting Device Return";
|
||||
case Job.JobStatusIds.AwaitingUserAction:
|
||||
return "Awaiting User Action";
|
||||
case Job.JobStatusIds.AwaitingAccountingPayment:
|
||||
return "Awaiting Accounting Payment";
|
||||
case Job.JobStatusIds.AwaitingAccountingCharge:
|
||||
return "Awaiting Accounting Charge";
|
||||
case Job.JobStatusIds.AwaitingInsuranceProcessing:
|
||||
return "Awaiting Insurance Processing";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public static string CalculateStatusId(this Job j)
|
||||
{
|
||||
return j.ToJobTableStatusItemModel().CalculateStatusId();
|
||||
}
|
||||
|
||||
public static string CalculateStatusId(this JobTableStatusItemModel j)
|
||||
{
|
||||
if (j.ClosedDate.HasValue)
|
||||
return Job.JobStatusIds.Closed;
|
||||
|
||||
if (j.JobTypeId == JobType.JobTypeIds.HWar)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(j.JobMetaWarranty_ExternalReference) && !j.JobMetaWarranty_ExternalCompletedDate.HasValue)
|
||||
return Job.JobStatusIds.AwaitingWarrantyRepair; // Job Logged - but not marked as completed
|
||||
}
|
||||
|
||||
if (j.JobTypeId == JobType.JobTypeIds.HNWar)
|
||||
{
|
||||
if (j.JobMetaNonWarranty_RepairerLoggedDate.HasValue && !j.JobMetaNonWarranty_RepairerCompletedDate.HasValue)
|
||||
return Job.JobStatusIds.AwaitingRepairs; // Repairs logged - but not complete
|
||||
if (j.JobMetaNonWarranty_AccountingChargeAddedDate.HasValue && !j.JobMetaNonWarranty_AccountingChargePaidDate.HasValue)
|
||||
return Job.JobStatusIds.AwaitingAccountingPayment; // Accounting Charge Added, but not paid
|
||||
if (j.JobMetaNonWarranty_AccountingChargeRequiredDate.HasValue && (!j.JobMetaNonWarranty_AccountingChargePaidDate.HasValue || !j.JobMetaNonWarranty_AccountingChargeAddedDate.HasValue))
|
||||
return Job.JobStatusIds.AwaitingAccountingCharge; // Accounting Charge Required, but not added or paid
|
||||
if (j.JobMetaNonWarranty_RepairerLoggedDate.HasValue && j.JobMetaNonWarranty_IsInsuranceClaim.Value && !j.JobMetaInsurance_ClaimFormSentDate.HasValue)
|
||||
return Job.JobStatusIds.AwaitingInsuranceProcessing; // Is insurance claim, but no Claim Form Sent
|
||||
}
|
||||
|
||||
if (j.WaitingForUserAction.HasValue)
|
||||
return Job.JobStatusIds.AwaitingUserAction; // Awaiting for User
|
||||
|
||||
if (j.DeviceReadyForReturn.HasValue && !j.DeviceReturnedDate.HasValue)
|
||||
return Job.JobStatusIds.AwaitingDeviceReturn; // Device not returned to User
|
||||
|
||||
return Job.JobStatusIds.Open;
|
||||
}
|
||||
|
||||
public static Tuple<string, string> Status(this Job j)
|
||||
{
|
||||
var statusId = j.CalculateStatusId();
|
||||
return new Tuple<string, string>(statusId, JobStatusDescription(statusId, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Users;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services
|
||||
{
|
||||
public static class JobTableExtensions
|
||||
{
|
||||
private static JobTableModel CloneEmptyJobTableModel(JobTableModel Model)
|
||||
{
|
||||
return new JobTableModel()
|
||||
{
|
||||
ShowId = Model.ShowId,
|
||||
ShowDeviceAddress = Model.ShowDeviceAddress,
|
||||
ShowDates = Model.ShowDates,
|
||||
ShowType = Model.ShowType,
|
||||
ShowDevice = Model.ShowDevice,
|
||||
ShowUser = Model.ShowUser,
|
||||
ShowTechnician = Model.ShowTechnician,
|
||||
ShowLocation = Model.ShowLocation,
|
||||
ShowStatus = Model.ShowStatus,
|
||||
IsSmallTable = Model.IsSmallTable,
|
||||
HideClosedJobs = Model.HideClosedJobs,
|
||||
EnablePaging = Model.EnablePaging,
|
||||
EnableFilter = Model.EnableFilter
|
||||
};
|
||||
}
|
||||
|
||||
public static IDictionary<string, JobTableModel> MultiCampusModels(this JobTableModel Model)
|
||||
{
|
||||
var items = Model.Items;
|
||||
if (items == null || items.Count() > 0)
|
||||
{
|
||||
return items.OrderBy(i => i.DeviceAddress).GroupBy(i => i.DeviceAddress).ToDictionary(
|
||||
ig => ig.Key ?? string.Empty,
|
||||
ig =>
|
||||
{
|
||||
var jtm = CloneEmptyJobTableModel(Model);
|
||||
jtm.Items = ig.ToList();
|
||||
return jtm;
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> FilterAllowedTypes(AuthorizationToken Authorization)
|
||||
{
|
||||
if (!Authorization.HasAll(Claims.Job.Types.ShowHMisc, Claims.Job.Types.ShowHNWar, Claims.Job.Types.ShowHWar, Claims.Job.Types.ShowSApp, Claims.Job.Types.ShowSImg, Claims.Job.Types.ShowSOS, Claims.Job.Types.ShowUMgmt))
|
||||
{
|
||||
// Must Filter
|
||||
List<string> allowedTypes = new List<string>(6);
|
||||
if (Authorization.Has(Claims.Job.Types.ShowHMisc))
|
||||
allowedTypes.Add(JobType.JobTypeIds.HMisc);
|
||||
if (Authorization.Has(Claims.Job.Types.ShowHNWar))
|
||||
allowedTypes.Add(JobType.JobTypeIds.HNWar);
|
||||
if (Authorization.Has(Claims.Job.Types.ShowHWar))
|
||||
allowedTypes.Add(JobType.JobTypeIds.HWar);
|
||||
if (Authorization.Has(Claims.Job.Types.ShowSApp))
|
||||
allowedTypes.Add(JobType.JobTypeIds.SApp);
|
||||
if (Authorization.Has(Claims.Job.Types.ShowSImg))
|
||||
allowedTypes.Add(JobType.JobTypeIds.SImg);
|
||||
if (Authorization.Has(Claims.Job.Types.ShowSOS))
|
||||
allowedTypes.Add(JobType.JobTypeIds.SOS);
|
||||
if (Authorization.Has(Claims.Job.Types.ShowUMgmt))
|
||||
allowedTypes.Add(JobType.JobTypeIds.UMgmt);
|
||||
|
||||
return allowedTypes;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IQueryable<Job> FilterPermissions(this JobTableModel model, IQueryable<Job> Jobs, AuthorizationToken Authorization)
|
||||
{
|
||||
var allowedTypes = FilterAllowedTypes(Authorization);
|
||||
|
||||
if (allowedTypes != null)
|
||||
{
|
||||
return Jobs.Where(j => allowedTypes.Contains(j.JobTypeId));
|
||||
}
|
||||
|
||||
return Jobs;
|
||||
}
|
||||
|
||||
public static IEnumerable<JobTableItemModel> PermissionsFiltered(this IEnumerable<JobTableItemModel> Items, AuthorizationToken Authorization)
|
||||
{
|
||||
if (Items != null && Items.Count() > 0)
|
||||
{
|
||||
var allowedTypes = FilterAllowedTypes(Authorization);
|
||||
|
||||
if (allowedTypes != null)
|
||||
{
|
||||
return Items.Where(j => allowedTypes.Contains(j.JobTypeId)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
return Items;
|
||||
}
|
||||
|
||||
public static IEnumerable<JobTableItemModel> DetermineItems(this JobTableModel model, DiscoDataContext Database, IQueryable<Job> Jobs)
|
||||
{
|
||||
List<JobTableItemModel> items;
|
||||
|
||||
// Permissions
|
||||
var auth = UserService.CurrentAuthorization;
|
||||
if (!auth.HasAll(Claims.Job.Types.ShowHMisc, Claims.Job.Types.ShowHNWar, Claims.Job.Types.ShowHWar, Claims.Job.Types.ShowSApp, Claims.Job.Types.ShowSImg, Claims.Job.Types.ShowSOS, Claims.Job.Types.ShowUMgmt))
|
||||
{
|
||||
// Must Filter
|
||||
List<string> allowedTypes = new List<string>(6);
|
||||
if (auth.Has(Claims.Job.Types.ShowHMisc))
|
||||
allowedTypes.Add(JobType.JobTypeIds.HMisc);
|
||||
if (auth.Has(Claims.Job.Types.ShowHNWar))
|
||||
allowedTypes.Add(JobType.JobTypeIds.HNWar);
|
||||
if (auth.Has(Claims.Job.Types.ShowHWar))
|
||||
allowedTypes.Add(JobType.JobTypeIds.HWar);
|
||||
if (auth.Has(Claims.Job.Types.ShowSApp))
|
||||
allowedTypes.Add(JobType.JobTypeIds.SApp);
|
||||
if (auth.Has(Claims.Job.Types.ShowSImg))
|
||||
allowedTypes.Add(JobType.JobTypeIds.SImg);
|
||||
if (auth.Has(Claims.Job.Types.ShowSOS))
|
||||
allowedTypes.Add(JobType.JobTypeIds.SOS);
|
||||
if (auth.Has(Claims.Job.Types.ShowUMgmt))
|
||||
allowedTypes.Add(JobType.JobTypeIds.UMgmt);
|
||||
|
||||
Jobs = Jobs.Where(j => allowedTypes.Contains(j.JobTypeId));
|
||||
}
|
||||
|
||||
if (model.ShowStatus)
|
||||
{
|
||||
|
||||
var jobItems = Jobs.Select(j => new JobTableStatusItemModel()
|
||||
{
|
||||
Id = j.Id,
|
||||
OpenedDate = j.OpenedDate,
|
||||
ClosedDate = j.ClosedDate,
|
||||
JobTypeId = j.JobTypeId,
|
||||
JobTypeDescription = j.JobType.Description,
|
||||
DeviceSerialNumber = j.Device.SerialNumber,
|
||||
DeviceProfileId = j.Device.DeviceProfileId,
|
||||
DeviceModelId = j.Device.DeviceModelId,
|
||||
DeviceModelDescription = j.Device.DeviceModel.Description,
|
||||
DeviceAddressId = j.Device.DeviceProfile.DefaultOrganisationAddress,
|
||||
UserId = j.UserId,
|
||||
UserDisplayName = j.User.DisplayName,
|
||||
OpenedTechUserId = j.OpenedTechUserId,
|
||||
OpenedTechUserDisplayName = j.OpenedTechUser.DisplayName,
|
||||
DeviceHeldLocation = j.DeviceHeldLocation,
|
||||
Flags = j.Flags,
|
||||
|
||||
JobMetaWarranty_ExternalReference = j.JobMetaWarranty.ExternalReference,
|
||||
JobMetaWarranty_ExternalLoggedDate = j.JobMetaWarranty.ExternalLoggedDate,
|
||||
JobMetaWarranty_ExternalCompletedDate = j.JobMetaWarranty.ExternalCompletedDate,
|
||||
JobMetaNonWarranty_RepairerLoggedDate = j.JobMetaNonWarranty.RepairerLoggedDate,
|
||||
JobMetaNonWarranty_RepairerCompletedDate = j.JobMetaNonWarranty.RepairerCompletedDate,
|
||||
JobMetaNonWarranty_AccountingChargeAddedDate = j.JobMetaNonWarranty.AccountingChargeAddedDate,
|
||||
JobMetaNonWarranty_AccountingChargePaidDate = j.JobMetaNonWarranty.AccountingChargePaidDate,
|
||||
JobMetaNonWarranty_AccountingChargeRequiredDate = j.JobMetaNonWarranty.AccountingChargeRequiredDate,
|
||||
JobMetaNonWarranty_IsInsuranceClaim = j.JobMetaNonWarranty.IsInsuranceClaim,
|
||||
JobMetaInsurance_ClaimFormSentDate = j.JobMetaInsurance.ClaimFormSentDate,
|
||||
|
||||
WaitingForUserAction = j.WaitingForUserAction,
|
||||
DeviceReadyForReturn = j.DeviceReadyForReturn,
|
||||
DeviceHeld = j.DeviceHeld,
|
||||
DeviceReturnedDate = j.DeviceReturnedDate,
|
||||
JobMetaWarranty_ExternalName = j.JobMetaWarranty.ExternalName,
|
||||
JobMetaNonWarranty_RepairerName = j.JobMetaNonWarranty.RepairerName,
|
||||
ActiveJobQueues = j.JobQueues.Where(jq => !jq.RemovedDate.HasValue).Select(jq => new JobTableStatusQueueItemModel()
|
||||
{
|
||||
Id = jq.Id,
|
||||
QueueId = jq.JobQueueId,
|
||||
AddedDate = jq.AddedDate,
|
||||
SLAExpiresDate = jq.SLAExpiresDate,
|
||||
Priority = jq.Priority
|
||||
})
|
||||
});
|
||||
|
||||
items = new List<JobTableItemModel>();
|
||||
foreach (var j in jobItems)
|
||||
{
|
||||
j.StatusId = j.CalculateStatusId();
|
||||
j.StatusDescription = JobExtensions.JobStatusDescription(j.StatusId, j);
|
||||
|
||||
items.Add(j);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
items = Jobs.Select(j => new JobTableItemModel()
|
||||
{
|
||||
Id = j.Id,
|
||||
OpenedDate = j.OpenedDate,
|
||||
ClosedDate = j.ClosedDate,
|
||||
JobTypeId = j.JobTypeId,
|
||||
JobTypeDescription = j.JobType.Description,
|
||||
DeviceSerialNumber = j.Device.SerialNumber,
|
||||
DeviceProfileId = j.Device.DeviceProfileId,
|
||||
DeviceModelId = j.Device.DeviceModelId,
|
||||
DeviceModelDescription = j.Device.DeviceModel.Description,
|
||||
DeviceAddressId = j.Device.DeviceProfile.DefaultOrganisationAddress,
|
||||
UserId = j.UserId,
|
||||
UserDisplayName = j.User.DisplayName,
|
||||
OpenedTechUserId = j.OpenedTechUserId,
|
||||
OpenedTechUserDisplayName = j.OpenedTechUser.DisplayName,
|
||||
DeviceHeldLocation = j.DeviceHeldLocation,
|
||||
Flags = j.Flags
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
if (!model.ShowDeviceAddress.HasValue)
|
||||
model.ShowDeviceAddress = Database.DiscoConfiguration.MultiSiteMode;
|
||||
|
||||
foreach (var j in items)
|
||||
if (j.DeviceAddressId.HasValue)
|
||||
j.DeviceAddress = Database.DiscoConfiguration.OrganisationAddresses.GetAddress(j.DeviceAddressId.Value).Name;
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public static void Fill(this JobTableModel model, DiscoDataContext Database, IQueryable<Job> Jobs)
|
||||
{
|
||||
model.Items = model.DetermineItems(Database, Jobs);
|
||||
}
|
||||
|
||||
public static double? SlaPrecentageRemaining(this IEnumerable<JobTableStatusQueueItemModel> queueItems)
|
||||
{
|
||||
return queueItems.Where(i => i.SLAExpiresDate.HasValue).Min<JobTableStatusQueueItemModel, double?>(i =>
|
||||
{
|
||||
var total = (i.SLAExpiresDate.Value - i.AddedDate).Ticks;
|
||||
var remaining = (i.SLAExpiresDate.Value - DateTime.Now).Ticks;
|
||||
return ((double)remaining / total);
|
||||
});
|
||||
}
|
||||
|
||||
public static IEnumerable<JobTableStatusQueueItemModel> UsersQueueItems(this IEnumerable<JobTableStatusQueueItemModel> queueItems, AuthorizationToken Authorization)
|
||||
{
|
||||
var usersQueues = Jobs.JobQueues.JobQueueService.UsersQueues(Authorization).ToDictionary(q => q.JobQueue.Id);
|
||||
return queueItems.Where(qi => usersQueues.ContainsKey(qi.QueueId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Data.Repository.Monitor;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Jobs.JobLists;
|
||||
using Disco.Services.Logging;
|
||||
using Disco.Services.Users;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace Disco.Services.Jobs.JobLists
|
||||
{
|
||||
using FilterFunc = Func<IQueryable<Job>, IQueryable<Job>>;
|
||||
using SortFunc = Func<IEnumerable<JobTableItemModel>, IEnumerable<JobTableItemModel>>;
|
||||
using OpenFilterFunc = Func<IEnumerable<JobTableStatusItemModel>, IEnumerable<JobTableStatusItemModel>>;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Jobs.JobQueues;
|
||||
|
||||
public class ManagedJobList : JobTableModel, IDisposable
|
||||
{
|
||||
private static ManagedJobList openJobs;
|
||||
|
||||
private IDisposable unsubscribeToken;
|
||||
private object updateLock = new object();
|
||||
|
||||
public string Name { get; private set; }
|
||||
public FilterFunc FilterFunction { get; private set; }
|
||||
public SortFunc SortFunction { get; private set; }
|
||||
|
||||
public static OpenFilterFunc AwaitingTechnicianActionFilter = q => q.Where(j => j.ClosedDate == null && !j.WaitingForUserAction.HasValue
|
||||
&& !(j.JobTypeId == JobType.JobTypeIds.HNWar && (j.JobMetaNonWarranty_RepairerLoggedDate.HasValue && j.JobMetaNonWarranty_IsInsuranceClaim.Value && !j.JobMetaInsurance_ClaimFormSentDate.HasValue))
|
||||
&& !(j.JobTypeId == JobType.JobTypeIds.HNWar && (j.JobMetaNonWarranty_RepairerLoggedDate.HasValue && !j.JobMetaNonWarranty_RepairerCompletedDate.HasValue))
|
||||
&& !(j.JobTypeId == JobType.JobTypeIds.HNWar && (j.JobMetaNonWarranty_RepairerLoggedDate.HasValue && j.JobMetaNonWarranty_AccountingChargeAddedDate.HasValue && !j.JobMetaNonWarranty_AccountingChargePaidDate.HasValue))
|
||||
&& !(j.JobTypeId == JobType.JobTypeIds.HWar && (j.JobMetaWarranty_ExternalLoggedDate.HasValue && !j.JobMetaWarranty_ExternalCompletedDate.HasValue))
|
||||
&& (j.DeviceHeld == null || j.DeviceReturnedDate != null || j.DeviceReadyForReturn == null));
|
||||
|
||||
static ManagedJobList()
|
||||
{
|
||||
using (var Database = new DiscoDataContext())
|
||||
{
|
||||
openJobs = new ManagedJobList(
|
||||
Name: "Open Jobs",
|
||||
FilterFunction: q => q.Where(j => !j.ClosedDate.HasValue),
|
||||
SortFunction: q => q)
|
||||
{
|
||||
ShowDeviceAddress = true,
|
||||
ShowLocation = true,
|
||||
ShowStatus = true
|
||||
}.Initialize(Database);
|
||||
}
|
||||
}
|
||||
|
||||
public static JobTableModel OpenJobsTable(OpenFilterFunc FilterFunction)
|
||||
{
|
||||
return new JobTableModel()
|
||||
{
|
||||
ShowStatus = true,
|
||||
Items = FilterFunction(openJobs.Items.Cast<JobTableStatusItemModel>())
|
||||
};
|
||||
}
|
||||
|
||||
public static JobTableModel MyJobsTable(AuthorizationToken AuthToken)
|
||||
{
|
||||
IEnumerable<Tuple<JobTableStatusItemModel, byte, byte, DateTime?>> allJobs = null;
|
||||
|
||||
if (AuthToken.Has(Claims.Job.Lists.MyJobsOrphaned))
|
||||
{
|
||||
allJobs = AwaitingTechnicianActionFilter(
|
||||
openJobs.Items.Cast<JobTableStatusItemModel>()
|
||||
.Where(i => i.ActiveJobQueues == null || i.ActiveJobQueues.Count() == 0)
|
||||
).Select(i => new Tuple<JobTableStatusItemModel, byte, byte, DateTime?>(i, (byte)JobQueuePriority.Normal, (byte)JobQueuePriority.Normal, null));
|
||||
}
|
||||
|
||||
var usersQueues = JobQueueService.UsersQueues(AuthToken).ToDictionary(q => q.JobQueue.Id);
|
||||
|
||||
var queueJobs = openJobs.Items.Cast<JobTableStatusItemModel>()
|
||||
.Where(i => i.ActiveJobQueues != null && i.ActiveJobQueues.Any(jqj => usersQueues.ContainsKey(jqj.QueueId)))
|
||||
.Select(i => new Tuple<JobTableStatusItemModel, byte, byte, DateTime?>(
|
||||
i,
|
||||
(byte)i.ActiveJobQueues.Where(q => usersQueues.ContainsKey(q.QueueId)).Max(q => (int)usersQueues[q.QueueId].JobQueue.Priority),
|
||||
(byte)i.ActiveJobQueues.Where(q => usersQueues.ContainsKey(q.QueueId)).Max(q => (int)q.Priority),
|
||||
i.ActiveJobQueues.Where(q => usersQueues.ContainsKey(q.QueueId) && q.SLAExpiresDate.HasValue).Min(q => q.SLAExpiresDate)
|
||||
));
|
||||
|
||||
if (allJobs != null)
|
||||
allJobs = allJobs.Concat(queueJobs).ToList();
|
||||
else
|
||||
allJobs = queueJobs.ToList();
|
||||
|
||||
var allJobsSorted = allJobs
|
||||
.OrderByDescending(i => i.Item2).ThenByDescending(i => i.Item3).ThenBy(i => i.Item4).ThenBy(i => i.Item1.OpenedDate).Select(q => q.Item1);
|
||||
|
||||
return new JobTableModel()
|
||||
{
|
||||
ShowStatus = true,
|
||||
Items = allJobsSorted.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<JobTableItemModel> Items
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Items.PermissionsFiltered(UserService.CurrentAuthorization);
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new InvalidOperationException("Items cannot be manually set in a Managed Job List");
|
||||
}
|
||||
}
|
||||
|
||||
public ManagedJobList(string Name, FilterFunc FilterFunction, SortFunc SortFunction)
|
||||
{
|
||||
this.Name = Name;
|
||||
this.FilterFunction = FilterFunction;
|
||||
this.SortFunction = SortFunction;
|
||||
}
|
||||
|
||||
public ManagedJobList Initialize(DiscoDataContext Database)
|
||||
{
|
||||
// Can only Initialize once
|
||||
if (base.Items != null)
|
||||
return ReInitialize(Database);
|
||||
|
||||
lock (updateLock)
|
||||
{
|
||||
// Subscribe for Changes
|
||||
// - Job (or Job Meta) Changes
|
||||
// - Device Profile Address Changes (for multi-campus Schools)
|
||||
// - Device Model Description Changes
|
||||
// - Device's Profile or Model Changes
|
||||
unsubscribeToken = RepositoryMonitor.StreamAfterCommit
|
||||
.Where(n => n.EntityType == typeof(Job) ||
|
||||
n.EntityType == typeof(JobQueueJob) ||
|
||||
n.EntityType == typeof(JobMetaWarranty) ||
|
||||
n.EntityType == typeof(JobMetaNonWarranty) ||
|
||||
n.EntityType == typeof(JobMetaInsurance) ||
|
||||
(n.EventType == RepositoryMonitorEventType.Modified && (
|
||||
(n.EntityType == typeof(DeviceProfile) && n.ModifiedProperties.Contains("DefaultOrganisationAddress")) ||
|
||||
(n.EntityType == typeof(DeviceModel) && n.ModifiedProperties.Contains("Description")) ||
|
||||
(n.EntityType == typeof(Device) && n.ModifiedProperties.Contains("DeviceProfileId") || n.ModifiedProperties.Contains("DeviceModelId"))
|
||||
)))
|
||||
.Subscribe(JobNotification, NotificationError);
|
||||
|
||||
// Initially fill table
|
||||
base.Items = this.SortFunction(this.DetermineItems(Database, this.FilterFunction(Database.Jobs))).ToList();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ManagedJobList ReInitialize(DiscoDataContext Database)
|
||||
{
|
||||
return ReInitialize(Database, null, null);
|
||||
}
|
||||
public ManagedJobList ReInitialize(DiscoDataContext Database, FilterFunc FilterFunction)
|
||||
{
|
||||
return ReInitialize(Database, FilterFunction, null);
|
||||
}
|
||||
public ManagedJobList ReInitialize(DiscoDataContext Database, FilterFunc FilterFunction, SortFunc SortFunction)
|
||||
{
|
||||
if (Database == null)
|
||||
throw new ArgumentNullException("Database");
|
||||
|
||||
lock (updateLock)
|
||||
{
|
||||
if (FilterFunction != null)
|
||||
this.FilterFunction = FilterFunction;
|
||||
if (SortFunction != null)
|
||||
this.SortFunction = SortFunction;
|
||||
|
||||
base.Items = this.SortFunction(this.DetermineItems(Database, this.FilterFunction(Database.Jobs))).ToList();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void NotificationError(Exception ex)
|
||||
{
|
||||
SystemLog.LogException(string.Format("Disco.Services.Jobs.JobLists.ManagedJobList: [{0}]", this.Name), ex);
|
||||
}
|
||||
|
||||
private void JobNotification(RepositoryMonitorEvent e)
|
||||
{
|
||||
List<int> jobIds = null;
|
||||
JobTableItemModel[] existingItems = null;
|
||||
|
||||
if (e.EntityType == typeof(Job))
|
||||
jobIds = new List<int>() { ((Job)e.Entity).Id };
|
||||
else if (e.EntityType == typeof(JobQueueJob))
|
||||
jobIds = new List<int>() { ((JobQueueJob)e.Entity).JobId };
|
||||
else if (e.EntityType == typeof(JobMetaWarranty))
|
||||
jobIds = new List<int>() { ((JobMetaWarranty)e.Entity).JobId };
|
||||
else if (e.EntityType == typeof(JobMetaNonWarranty))
|
||||
jobIds = new List<int>() { ((JobMetaNonWarranty)e.Entity).JobId };
|
||||
else if (e.EntityType == typeof(JobMetaInsurance))
|
||||
jobIds = new List<int>() { ((JobMetaInsurance)e.Entity).JobId };
|
||||
else if (e.EntityType == typeof(DeviceProfile))
|
||||
{
|
||||
int deviceProfileId = ((DeviceProfile)e.Entity).Id;
|
||||
existingItems = base.Items.Where(i => i.DeviceProfileId == deviceProfileId).ToArray();
|
||||
}
|
||||
else if (e.EntityType == typeof(DeviceModel))
|
||||
{
|
||||
int deviceModelId = ((DeviceModel)e.Entity).Id;
|
||||
existingItems = base.Items.Where(i => i.DeviceModelId == deviceModelId).ToArray();
|
||||
}
|
||||
else if (e.EntityType == typeof(Device))
|
||||
{
|
||||
string deviceSerialNumber = ((Device)e.Entity).SerialNumber;
|
||||
existingItems = base.Items.Where(i => i.DeviceSerialNumber == deviceSerialNumber).ToArray();
|
||||
}
|
||||
else
|
||||
return; // Subscription should never reach
|
||||
|
||||
if (jobIds == null)
|
||||
{
|
||||
if (existingItems == null)
|
||||
throw new InvalidOperationException("Notification algorithm didn't indicate any Jobs for update");
|
||||
else
|
||||
jobIds = existingItems.Select(i => i.Id).ToList();
|
||||
}
|
||||
|
||||
if (jobIds.Count == 0)
|
||||
return;
|
||||
else
|
||||
UpdateJobs(e.Database, jobIds, existingItems);
|
||||
}
|
||||
|
||||
private void UpdateJobs(DiscoDataContext Database, List<int> jobIds, JobTableItemModel[] existingItems = null)
|
||||
{
|
||||
lock (updateLock)
|
||||
{
|
||||
// Check for existing items, if not handed them
|
||||
if (existingItems == null)
|
||||
existingItems = base.Items.Where(i => jobIds.Contains(i.Id)).ToArray();
|
||||
|
||||
var updatedItems = this.DetermineItems(Database, this.FilterFunction(Database.Jobs.Where(j => jobIds.Contains(j.Id))));
|
||||
|
||||
var refreshedList = base.Items.ToList();
|
||||
|
||||
// Remove Existing
|
||||
if (existingItems.Length > 0)
|
||||
foreach (var existingItem in existingItems)
|
||||
refreshedList.Remove(existingItem);
|
||||
|
||||
// Add Updated Items
|
||||
if (updatedItems.Count() > 0)
|
||||
foreach (var updatedItem in updatedItems)
|
||||
refreshedList.Add(updatedItem);
|
||||
|
||||
// Reorder
|
||||
base.Items = this.SortFunction(refreshedList).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (unsubscribeToken != null)
|
||||
{
|
||||
unsubscribeToken.Dispose();
|
||||
unsubscribeToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Jobs.JobQueues
|
||||
{
|
||||
internal class Cache
|
||||
{
|
||||
private ConcurrentDictionary<int, JobQueueToken> _Cache;
|
||||
private Dictionary<string, List<JobQueueToken>> _SubjectCache;
|
||||
|
||||
private ReadOnlyCollection<KeyValuePair<string, string>> _Icons;
|
||||
private ReadOnlyCollection<KeyValuePair<string, string>> _IconColourCache;
|
||||
private ReadOnlyCollection<KeyValuePair<int, string>> _SlaOptions;
|
||||
|
||||
public Cache(DiscoDataContext Database)
|
||||
{
|
||||
Initialize(Database);
|
||||
}
|
||||
|
||||
private void Initialize(DiscoDataContext Database)
|
||||
{
|
||||
// Queues from Database
|
||||
var queues = Database.JobQueues.ToList();
|
||||
|
||||
// Default System Queue
|
||||
//var defaultQueue = new JobQueue()
|
||||
//{
|
||||
// Id = 0,
|
||||
// Name = "Default Queue",
|
||||
// Description = "Default system queue for orphaned jobs",
|
||||
// Icon = "question-circle",
|
||||
// IconColour = "F0A30A",
|
||||
// DefaultSLAExpiry = null,
|
||||
// Priority = JobQueuePriority.Normal,
|
||||
// SubjectIds = null
|
||||
//};
|
||||
//queues.Add(defaultQueue);
|
||||
|
||||
// Add Queues to In-Memory Cache
|
||||
this._Cache = new ConcurrentDictionary<int, JobQueueToken>(queues.Select(q => new KeyValuePair<int, JobQueueToken>(q.Id, JobQueueToken.FromJobQueue(q))));
|
||||
|
||||
// Calculate Queue Subject Cache
|
||||
CalculateSubjectCache();
|
||||
|
||||
#region Predefined Options
|
||||
// Icons
|
||||
if (this._Icons == null)
|
||||
{
|
||||
this._Icons = new List<KeyValuePair<string, string>>(){
|
||||
new KeyValuePair<string, string>("ambulance" , "Ambulance"),
|
||||
new KeyValuePair<string, string>("anchor" , "Anchor"),
|
||||
new KeyValuePair<string, string>("android" , "Android"),
|
||||
new KeyValuePair<string, string>("apple" , "Apple"),
|
||||
new KeyValuePair<string, string>("archive" , "Archive"),
|
||||
new KeyValuePair<string, string>("arrow-circle-down" , "Arrow Circle Down"),
|
||||
new KeyValuePair<string, string>("arrow-circle-left" , "Arrow Circle Left"),
|
||||
new KeyValuePair<string, string>("arrow-circle-right" , "Arrow Circle Right"),
|
||||
new KeyValuePair<string, string>("arrow-circle-up" , "Arrow Circle Up"),
|
||||
new KeyValuePair<string, string>("asterisk" , "Asterisk"),
|
||||
new KeyValuePair<string, string>("ban" , "Ban"),
|
||||
new KeyValuePair<string, string>("beer" , "Beer"),
|
||||
new KeyValuePair<string, string>("bell" , "Bell"),
|
||||
new KeyValuePair<string, string>("bolt" , "Bolt"),
|
||||
new KeyValuePair<string, string>("book" , "Book"),
|
||||
new KeyValuePair<string, string>("bookmark" , "Bookmark"),
|
||||
new KeyValuePair<string, string>("briefcase" , "Briefcase"),
|
||||
new KeyValuePair<string, string>("bug" , "Bug"),
|
||||
new KeyValuePair<string, string>("building-o" , "Building"),
|
||||
new KeyValuePair<string, string>("bullhorn" , "Bullhorn"),
|
||||
new KeyValuePair<string, string>("bullseye" , "Bullseye"),
|
||||
new KeyValuePair<string, string>("calendar" , "Calendar"),
|
||||
new KeyValuePair<string, string>("calendar-o" , "Calendar"),
|
||||
new KeyValuePair<string, string>("check-circle" , "Check Circle"),
|
||||
new KeyValuePair<string, string>("clock-o" , "Clock"),
|
||||
new KeyValuePair<string, string>("cloud" , "Cloud"),
|
||||
new KeyValuePair<string, string>("coffee" , "Coffee"),
|
||||
new KeyValuePair<string, string>("comments" , "Comments"),
|
||||
new KeyValuePair<string, string>("compass" , "Compass"),
|
||||
new KeyValuePair<string, string>("credit-card" , "Credit Card"),
|
||||
new KeyValuePair<string, string>("crosshairs" , "Crosshairs"),
|
||||
new KeyValuePair<string, string>("desktop" , "Desktop"),
|
||||
new KeyValuePair<string, string>("dollar" , "Dollar"),
|
||||
new KeyValuePair<string, string>("dot-circle-o" , "Dot Circle"),
|
||||
new KeyValuePair<string, string>("envelope" , "Envelope"),
|
||||
new KeyValuePair<string, string>("exclamation" , "Exclamation"),
|
||||
new KeyValuePair<string, string>("eye" , "Eye"),
|
||||
new KeyValuePair<string, string>("female" , "Female"),
|
||||
new KeyValuePair<string, string>("fighter-jet" , "Fighter Jet"),
|
||||
new KeyValuePair<string, string>("film" , "Film"),
|
||||
new KeyValuePair<string, string>("filter" , "Filter"),
|
||||
new KeyValuePair<string, string>("fire" , "Fire"),
|
||||
new KeyValuePair<string, string>("fire-extinguisher" , "Fire Extinguisher"),
|
||||
new KeyValuePair<string, string>("flask" , "Flask"),
|
||||
new KeyValuePair<string, string>("frown-o" , "Frown"),
|
||||
new KeyValuePair<string, string>("gamepad" , "Gamepad"),
|
||||
new KeyValuePair<string, string>("gift" , "Gift"),
|
||||
new KeyValuePair<string, string>("glass" , "Glass"),
|
||||
new KeyValuePair<string, string>("globe" , "Globe"),
|
||||
new KeyValuePair<string, string>("hand-o-down" , "Hand Down"),
|
||||
new KeyValuePair<string, string>("hand-o-left" , "Hand Left"),
|
||||
new KeyValuePair<string, string>("hand-o-right" , "Hand Right"),
|
||||
new KeyValuePair<string, string>("hand-o-up" , "Hand Up"),
|
||||
new KeyValuePair<string, string>("hdd-o" , "Hdd"),
|
||||
new KeyValuePair<string, string>("heart" , "Heart"),
|
||||
new KeyValuePair<string, string>("home" , "Home"),
|
||||
new KeyValuePair<string, string>("info" , "Info"),
|
||||
new KeyValuePair<string, string>("key" , "Key"),
|
||||
new KeyValuePair<string, string>("keyboard-o" , "Keyboard"),
|
||||
new KeyValuePair<string, string>("laptop" , "Laptop"),
|
||||
new KeyValuePair<string, string>("leaf" , "Leaf"),
|
||||
new KeyValuePair<string, string>("legal" , "Legal"),
|
||||
new KeyValuePair<string, string>("lightbulb-o" , "Lightbulb"),
|
||||
new KeyValuePair<string, string>("linux" , "Linux"),
|
||||
new KeyValuePair<string, string>("location-arrow" , "Location Arrow"),
|
||||
new KeyValuePair<string, string>("magnet" , "Magnet"),
|
||||
new KeyValuePair<string, string>("male" , "Male"),
|
||||
new KeyValuePair<string, string>("map-marker" , "Map Marker"),
|
||||
new KeyValuePair<string, string>("medkit" , "Medkit"),
|
||||
new KeyValuePair<string, string>("meh-o" , "Meh"),
|
||||
new KeyValuePair<string, string>("microphone" , "Microphone"),
|
||||
new KeyValuePair<string, string>("microphone-slash" , "Microphone Slash"),
|
||||
new KeyValuePair<string, string>("minus-circle" , "Minus Circle"),
|
||||
new KeyValuePair<string, string>("mobile" , "Mobile"),
|
||||
new KeyValuePair<string, string>("money" , "Money"),
|
||||
new KeyValuePair<string, string>("moon-o" , "Moon"),
|
||||
new KeyValuePair<string, string>("music" , "Music"),
|
||||
new KeyValuePair<string, string>("paperclip" , "Paperclip"),
|
||||
new KeyValuePair<string, string>("pencil" , "Pencil"),
|
||||
new KeyValuePair<string, string>("phone" , "Phone"),
|
||||
new KeyValuePair<string, string>("picture-o" , "Picture"),
|
||||
new KeyValuePair<string, string>("plane" , "Plane"),
|
||||
new KeyValuePair<string, string>("power-off" , "Power Off"),
|
||||
new KeyValuePair<string, string>("print" , "Print"),
|
||||
new KeyValuePair<string, string>("puzzle-piece" , "Puzzle Piece"),
|
||||
new KeyValuePair<string, string>("question" , "Question"),
|
||||
new KeyValuePair<string, string>("question-circle" , "Question Circle"),
|
||||
new KeyValuePair<string, string>("random" , "Random"),
|
||||
new KeyValuePair<string, string>("retweet" , "Retweet"),
|
||||
new KeyValuePair<string, string>("road" , "Road"),
|
||||
new KeyValuePair<string, string>("rocket" , "Rocket"),
|
||||
new KeyValuePair<string, string>("shield" , "Shield"),
|
||||
new KeyValuePair<string, string>("shopping-cart" , "Shopping Cart"),
|
||||
new KeyValuePair<string, string>("smile-o" , "Smile"),
|
||||
new KeyValuePair<string, string>("star" , "Star"),
|
||||
new KeyValuePair<string, string>("suitcase" , "Suitcase"),
|
||||
new KeyValuePair<string, string>("sun-o" , "Sun"),
|
||||
new KeyValuePair<string, string>("tablet" , "Tablet"),
|
||||
new KeyValuePair<string, string>("tachometer" , "Tachometer"),
|
||||
new KeyValuePair<string, string>("tasks" , "Tasks"),
|
||||
new KeyValuePair<string, string>("thumbs-down" , "Thumbs Down"),
|
||||
new KeyValuePair<string, string>("thumbs-o-down" , "Thumbs Down"),
|
||||
new KeyValuePair<string, string>("thumbs-o-up" , "Thumbs Up"),
|
||||
new KeyValuePair<string, string>("thumbs-up" , "Thumbs Up"),
|
||||
new KeyValuePair<string, string>("thumb-tack" , "Thumb Tack"),
|
||||
new KeyValuePair<string, string>("trash-o" , "Trash"),
|
||||
new KeyValuePair<string, string>("trophy" , "Trophy"),
|
||||
new KeyValuePair<string, string>("truck" , "Truck"),
|
||||
new KeyValuePair<string, string>("umbrella" , "Umbrella"),
|
||||
new KeyValuePair<string, string>("wheelchair" , "Wheelchair"),
|
||||
new KeyValuePair<string, string>("windows" , "Windows"),
|
||||
new KeyValuePair<string, string>("wrench" , "Wrench")
|
||||
}.AsReadOnly();
|
||||
}
|
||||
|
||||
// Icon Colours
|
||||
if (this._IconColourCache == null)
|
||||
{
|
||||
this._IconColourCache = new List<KeyValuePair<string, string>>(){
|
||||
new KeyValuePair<string, string>("lime" , "Lime"),
|
||||
new KeyValuePair<string, string>("green" , "Green"),
|
||||
new KeyValuePair<string, string>("emerald" , "Emerald"),
|
||||
new KeyValuePair<string, string>("teal" , "Teal"),
|
||||
new KeyValuePair<string, string>("cyan" , "Cyan"),
|
||||
new KeyValuePair<string, string>("cobalt" , "Cobalt"),
|
||||
new KeyValuePair<string, string>("indigo" , "Indigo"),
|
||||
new KeyValuePair<string, string>("violet" , "Violet"),
|
||||
new KeyValuePair<string, string>("pink" , "Pink"),
|
||||
new KeyValuePair<string, string>("magenta" , "Magenta"),
|
||||
new KeyValuePair<string, string>("crimson" , "Crimson"),
|
||||
new KeyValuePair<string, string>("red" , "Red"),
|
||||
new KeyValuePair<string, string>("orange" , "Orange"),
|
||||
new KeyValuePair<string, string>("amber" , "Amber"),
|
||||
new KeyValuePair<string, string>("yellow" , "Yellow"),
|
||||
new KeyValuePair<string, string>("brown" , "Brown"),
|
||||
new KeyValuePair<string, string>("olive" , "Olive"),
|
||||
new KeyValuePair<string, string>("steel" , "Steel"),
|
||||
new KeyValuePair<string, string>("mauve" , "Mauve"),
|
||||
new KeyValuePair<string, string>("sienna" , "Sienna")
|
||||
}.AsReadOnly();
|
||||
}
|
||||
|
||||
// SLA Options
|
||||
if (this._SlaOptions == null)
|
||||
{
|
||||
this._SlaOptions = new List<KeyValuePair<int, string>>()
|
||||
{
|
||||
new KeyValuePair<int, string>(0, "<None>"),
|
||||
new KeyValuePair<int, string>(15, "15 minutes"),
|
||||
new KeyValuePair<int, string>(30, "30 minutes"),
|
||||
new KeyValuePair<int, string>(60, "1 hour"),
|
||||
new KeyValuePair<int, string>(60 * 2, "2 hours"),
|
||||
new KeyValuePair<int, string>(60 * 4, "4 hours"),
|
||||
new KeyValuePair<int, string>(60 * 8, "8 hours"),
|
||||
new KeyValuePair<int, string>(60 * 24, "1 day"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 2, "2 days"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 3, "3 days"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 4, "4 days"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 5, "5 days"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 6, "6 days"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 7, "1 week"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 7 * 2, "2 weeks"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 7 * 3, "3 weeks"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 7 * 4, "4 weeks"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 7 * 4 * 2, "2 months"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 7 * 4 * 3, "3 months"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 7 * 4 * 4, "4 months"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 7 * 4 * 5, "5 months"),
|
||||
new KeyValuePair<int, string>(60 * 24 * 7 * 4 * 6, "6 months")
|
||||
}.AsReadOnly();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
private void CalculateSubjectCache()
|
||||
{
|
||||
_SubjectCache = (from c in _Cache.Values.ToList()
|
||||
from s in c.SubjectIds
|
||||
group c by s into subjectId
|
||||
select subjectId).ToDictionary(g => g.Key.ToLower(), g => g.ToList());
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<KeyValuePair<string, string>> Icons { get { return this._Icons; } }
|
||||
public ReadOnlyCollection<KeyValuePair<string, string>> IconColours { get { return this._IconColourCache; } }
|
||||
public ReadOnlyCollection<KeyValuePair<int, string>> SlaOptions { get { return this._SlaOptions; } }
|
||||
|
||||
public JobQueueToken UpdateQueue(JobQueue JobQueue)
|
||||
{
|
||||
var token = JobQueueToken.FromJobQueue(JobQueue);
|
||||
JobQueueToken existingToken;
|
||||
|
||||
if (_Cache.TryGetValue(JobQueue.Id, out existingToken))
|
||||
{
|
||||
if (_Cache.TryUpdate(JobQueue.Id, token, existingToken))
|
||||
{
|
||||
if (existingToken.JobQueue.SubjectIds != token.JobQueue.SubjectIds)
|
||||
CalculateSubjectCache();
|
||||
|
||||
return token;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_Cache.TryAdd(JobQueue.Id, token))
|
||||
{
|
||||
CalculateSubjectCache();
|
||||
return token;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public bool RemoveQueue(int JobQueueId)
|
||||
{
|
||||
JobQueueToken token;
|
||||
if (_Cache.TryRemove(JobQueueId, out token))
|
||||
{
|
||||
CalculateSubjectCache();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public JobQueueToken GetQueue(int JobQueueId)
|
||||
{
|
||||
JobQueueToken token;
|
||||
if (_Cache.TryGetValue(JobQueueId, out token))
|
||||
return token;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public ReadOnlyCollection<JobQueueToken> GetQueues()
|
||||
{
|
||||
return _Cache.Values.ToList().AsReadOnly();
|
||||
}
|
||||
private IEnumerable<JobQueueToken> GetQueuesForSubject(string SubjectId)
|
||||
{
|
||||
List<JobQueueToken> tokens;
|
||||
if (_SubjectCache.TryGetValue(SubjectId.ToLower(), out tokens))
|
||||
return tokens;
|
||||
else
|
||||
return Enumerable.Empty<JobQueueToken>();
|
||||
}
|
||||
public ReadOnlyCollection<JobQueueToken> GetQueuesForSubject(IEnumerable<string> SubjectIds)
|
||||
{
|
||||
return SubjectIds.SelectMany(sid => GetQueuesForSubject(sid)).Distinct().ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public void ReInitializeCache(DiscoDataContext Database)
|
||||
{
|
||||
Initialize(Database);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Services.Tasks;
|
||||
using Quartz;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Jobs.JobQueues
|
||||
{
|
||||
public class JobQueueDeleteTask : ScheduledTask
|
||||
{
|
||||
public override string TaskName { get { return "Job Queues - Delete Queue"; } }
|
||||
|
||||
public override bool SingleInstanceTask { get { return false; }}
|
||||
public override bool CancelInitiallySupported { get { return false; } }
|
||||
public override bool LogExceptionsOnly { get { return true; } }
|
||||
|
||||
protected override void ExecuteTask()
|
||||
{
|
||||
int jobQueueId = (int)this.ExecutionContext.JobDetail.JobDataMap["JobQueueId"];
|
||||
|
||||
using (DiscoDataContext Database = new DiscoDataContext())
|
||||
{
|
||||
JobQueueService.DeleteJobQueue(Database, jobQueueId, this.Status);
|
||||
}
|
||||
}
|
||||
|
||||
public static ScheduledTaskStatus ScheduleNow(int JobQueueId)
|
||||
{
|
||||
JobDataMap taskData = new JobDataMap() { { "JobQueueId", JobQueueId } };
|
||||
|
||||
var instance = new JobQueueDeleteTask();
|
||||
|
||||
return instance.ScheduleTask(taskData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Services.Authorization;
|
||||
using Disco.Services.Tasks;
|
||||
using Disco.Services.Users;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Disco.Services.Jobs.JobQueues
|
||||
{
|
||||
public static class JobQueueService
|
||||
{
|
||||
private const string _cacheHttpRequestKey = "Disco_UserQueuesToken_{0}";
|
||||
private static Cache _cache;
|
||||
|
||||
public static void Initialize(DiscoDataContext Database)
|
||||
{
|
||||
_cache = new Cache(Database);
|
||||
}
|
||||
|
||||
public static ReadOnlyCollection<KeyValuePair<string, string>> IconColours { get { return _cache.IconColours; } }
|
||||
public static ReadOnlyCollection<KeyValuePair<string, string>> Icons { get { return _cache.Icons; } }
|
||||
public static ReadOnlyCollection<KeyValuePair<int, string>> SlaOptions { get { return _cache.SlaOptions; } }
|
||||
|
||||
public static ReadOnlyCollection<JobQueueToken> GetQueues() { return _cache.GetQueues(); }
|
||||
public static JobQueueToken GetQueue(int JobQueueId) { return _cache.GetQueue(JobQueueId); }
|
||||
|
||||
#region Job Queues Maintenance
|
||||
public static JobQueueToken CreateJobQueue(DiscoDataContext Database, JobQueue JobQueue)
|
||||
{
|
||||
// Verify
|
||||
if (string.IsNullOrWhiteSpace(JobQueue.Name))
|
||||
throw new ArgumentException("The Job Queue Name is required");
|
||||
if (string.IsNullOrWhiteSpace(JobQueue.Description))
|
||||
throw new ArgumentException("The Job Queue Description is required");
|
||||
|
||||
// Name Unique
|
||||
if (_cache.GetQueues().Count(q => q.JobQueue.Name == JobQueue.Name) > 0)
|
||||
throw new ArgumentException("Another Job Queue already exists with that name", "JobQueue");
|
||||
|
||||
// Clone to break reference
|
||||
var queue = new JobQueue()
|
||||
{
|
||||
Name = JobQueue.Name,
|
||||
Description = JobQueue.Description,
|
||||
Icon = JobQueue.Icon,
|
||||
IconColour = JobQueue.IconColour,
|
||||
DefaultSLAExpiry = JobQueue.DefaultSLAExpiry,
|
||||
Priority = JobQueue.Priority,
|
||||
SubjectIds = JobQueue.SubjectIds
|
||||
};
|
||||
|
||||
Database.JobQueues.Add(queue);
|
||||
Database.SaveChanges();
|
||||
|
||||
return _cache.UpdateQueue(queue);
|
||||
}
|
||||
public static JobQueueToken UpdateJobQueue(DiscoDataContext Database, JobQueue JobQueue)
|
||||
{
|
||||
// Verify
|
||||
if (string.IsNullOrWhiteSpace(JobQueue.Name))
|
||||
throw new ArgumentException("The Job Queue Name is required");
|
||||
if (string.IsNullOrWhiteSpace(JobQueue.Description))
|
||||
throw new ArgumentException("The Job Queue Description is required");
|
||||
|
||||
// Name Unique
|
||||
if (_cache.GetQueues().Count(q => q.JobQueue.Id != JobQueue.Id && q.JobQueue.Name == JobQueue.Name) > 0)
|
||||
throw new ArgumentException("Another Job Queue already exists with that name", "JobQueue");
|
||||
|
||||
Database.SaveChanges();
|
||||
|
||||
return _cache.UpdateQueue(JobQueue);
|
||||
}
|
||||
public static void DeleteJobQueue(DiscoDataContext Database, int JobQueueId, ScheduledTaskStatus Status)
|
||||
{
|
||||
JobQueue queue = Database.JobQueues.Find(JobQueueId);
|
||||
|
||||
// Validate: Current Jobs?
|
||||
int currentJobCount = Database.JobQueueJobs.Count(jqj => jqj.JobQueueId == queue.Id && jqj.RemovedDate.HasValue);
|
||||
if (currentJobCount > 0)
|
||||
throw new InvalidOperationException("The Job Queue cannot be deleted because it contains jobs");
|
||||
|
||||
// Delete History
|
||||
Status.UpdateStatus(0, string.Format("Removing '{0}' [{1}] Job Queue", queue.Name, queue.Id), "Starting");
|
||||
var jobQueueJobs = Database.JobQueueJobs.Include("Job").Where(jsj => jsj.JobQueueId == queue.Id).ToList();
|
||||
if (jobQueueJobs.Count > 0)
|
||||
{
|
||||
double progressInterval = 90 / jobQueueJobs.Count;
|
||||
for (int jqjIndex = 0; jqjIndex < jobQueueJobs.Count; jqjIndex++)
|
||||
{
|
||||
var jqj = jobQueueJobs[jqjIndex];
|
||||
|
||||
Status.UpdateStatus(jqjIndex * progressInterval, string.Format("Merging history into job #{0} logs", jqj.JobId));
|
||||
|
||||
// Write Logs
|
||||
Database.JobLogs.Add(new JobLog()
|
||||
{
|
||||
JobId = jqj.JobId,
|
||||
TechUserId = jqj.AddedUserId,
|
||||
Timestamp = jqj.AddedDate,
|
||||
Comments = string.Format("Added to Job Queue: {1}{0}Priority: {2}{0}Comment: {3}", Environment.NewLine, queue.Name, jqj.Priority.ToString(), string.IsNullOrWhiteSpace(jqj.AddedComment) ? "<none>" : jqj.AddedComment)
|
||||
});
|
||||
Database.JobLogs.Add(new JobLog()
|
||||
{
|
||||
JobId = jqj.JobId,
|
||||
TechUserId = jqj.RemovedUserId,
|
||||
Timestamp = jqj.RemovedDate.Value,
|
||||
Comments = string.Format("Removed from Job Queue: {1}{0}Comment: {2}", Environment.NewLine, queue.Name, string.IsNullOrWhiteSpace(jqj.RemovedComment) ? "<none>" : jqj.RemovedComment)
|
||||
});
|
||||
|
||||
// Delete JQJ
|
||||
Database.JobQueueJobs.Remove(jqj);
|
||||
|
||||
// Save Changes
|
||||
Database.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Queue
|
||||
Status.UpdateStatus(90, "Deleting Queue");
|
||||
Database.JobQueues.Remove(queue);
|
||||
Database.SaveChanges();
|
||||
|
||||
// Remove from Cache
|
||||
_cache.RemoveQueue(JobQueueId);
|
||||
|
||||
Status.Finished(string.Format("Successfully Deleted Job Queue: '{0}' [{1}]", queue.Name, queue.Id));
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static ReadOnlyCollection<JobQueueToken> UsersQueues(User User)
|
||||
{
|
||||
return UsersQueues(User.Id);
|
||||
}
|
||||
public static ReadOnlyCollection<JobQueueToken> UsersQueues(string UserId)
|
||||
{
|
||||
var userAuth = UserService.GetAuthorization(UserId);
|
||||
|
||||
return UsersQueues(userAuth);
|
||||
}
|
||||
public static ReadOnlyCollection<JobQueueToken> UsersQueues(AuthorizationToken UserAuthorization)
|
||||
{
|
||||
string cacheKey = string.Format(_cacheHttpRequestKey, UserAuthorization.User.Id);
|
||||
ReadOnlyCollection<JobQueueToken> tokens = null;
|
||||
|
||||
// Check for ASP.NET
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
tokens = (ReadOnlyCollection<JobQueueToken>)HttpContext.Current.Items[_cacheHttpRequestKey];
|
||||
}
|
||||
|
||||
if (tokens == null)
|
||||
{
|
||||
var subjectIds = (new string[] { UserAuthorization.User.Id }).Concat(UserAuthorization.GroupMembership);
|
||||
tokens = _cache.GetQueuesForSubject(subjectIds);
|
||||
|
||||
HttpContext.Current.Items[_cacheHttpRequestKey] = tokens;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public static string RandomIcon()
|
||||
{
|
||||
var rnd = new Random();
|
||||
var unusedIcons = _cache.Icons.Select(i => i.Key).Except(_cache.GetQueues().Select(q => q.JobQueue.Icon)).ToList();
|
||||
if (unusedIcons.Count > 0)
|
||||
return unusedIcons[rnd.Next(unusedIcons.Count - 1)];
|
||||
else
|
||||
return _cache.Icons[rnd.Next(_cache.Icons.Count - 1)].Key;
|
||||
}
|
||||
public static string RandomIconColour()
|
||||
{
|
||||
var rnd = new Random();
|
||||
var unusedColours = _cache.IconColours.Select(i => i.Key).Except(_cache.GetQueues().Select(q => q.JobQueue.IconColour)).ToList();
|
||||
if (unusedColours.Count > 0)
|
||||
return unusedColours[rnd.Next(unusedColours.Count - 1)];
|
||||
else
|
||||
return _cache.IconColours[rnd.Next(_cache.IconColours.Count - 1)].Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Disco.Models.Repository;
|
||||
using Disco.Models.Services.Jobs.JobQueues;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Disco.Services.Jobs.JobQueues
|
||||
{
|
||||
public class JobQueueToken : IJobQueueToken
|
||||
{
|
||||
public JobQueue JobQueue { get; private set; }
|
||||
internal HashSet<string> SubjectIdHashes { get; private set; }
|
||||
public ReadOnlyCollection<string> SubjectIds { get; private set; }
|
||||
|
||||
public static JobQueueToken FromJobQueue(JobQueue JobQueue)
|
||||
{
|
||||
string[] sg = (JobQueue.SubjectIds == null ? new string[0] : JobQueue.SubjectIds.Split(',').ToArray());
|
||||
|
||||
return new JobQueueToken()
|
||||
{
|
||||
JobQueue = JobQueue,
|
||||
SubjectIdHashes = new HashSet<string>(sg.Select(i => i.ToLower())),
|
||||
SubjectIds = sg.ToList().AsReadOnly()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user