Files
Disco/Disco.Services/Jobs/Noticeboards/HeldDevices.cs
T
Gary Sharp b78ce003a7 Bug Fix: Error when assigning devices
The noticeboard listens for updates to user assignments (including who
was previously assigned the device), but was requesting data which no
longer existed (as updates are delayed and buffered for up to 500ms).
2014-07-26 12:25:20 +10:00

264 lines
10 KiB
C#

using Disco.Data.Repository;
using Disco.Data.Repository.Monitor;
using Disco.Models.Repository;
using Disco.Models.Services.Jobs.Noticeboards;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace Disco.Services.Jobs.Noticeboards
{
public static class HeldDevices
{
public const string Name = "HeldDevices";
private readonly static List<string> MonitorJobProperties = new List<string>() {
"DeviceSerialNumber",
"UserId",
"ExpectedClosedDate",
"ClosedDate",
"WaitingForUserAction",
"DeviceHeld",
"DeviceReadyForReturn",
"DeviceReturnedDate"
};
private readonly static List<string> MonitorJobMetaNonWarrantyProperties = new List<string>(){
"AccountingChargeRequiredDate",
"AccountingChargeAddedDate",
"AccountingChargePaidDate"
};
private readonly static List<string> MonitorDeviceProperties = new List<string>(){
"Location",
"DeviceProfileId",
"DeviceDomainId",
"AssignedUserId",
};
private readonly static List<string> MonitorDeviceProfileProperties = new List<string>(){
"DefaultOrganisationAddress"
};
private readonly static List<string> MonitorUserProperties = new List<string>(){
"DisplayName"
};
private static Subject<Tuple<List<string>, List<string>>> BufferedUpdateStream;
static HeldDevices()
{
BufferedUpdateStream = new Subject<Tuple<List<string>, List<string>>>();
BufferedUpdateStream
.DelayBuffer(TimeSpan.FromMilliseconds(500))
.SubscribeOn(TaskPoolScheduler.Default)
.Subscribe(ProcessUpdates);
// Subscribe to Repository Notifications
RepositoryMonitor.StreamBeforeCommit.Where(e =>
(e.EntityType == typeof(Job) &&
(e.EventType == RepositoryMonitorEventType.Added ||
e.EventType == RepositoryMonitorEventType.Deleted ||
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorJobProperties.Contains(p))))
) ||
(e.EntityType == typeof(JobMetaNonWarranty) &&
(e.EventType == RepositoryMonitorEventType.Added ||
e.EventType == RepositoryMonitorEventType.Deleted ||
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorJobMetaNonWarrantyProperties.Contains(p))))
) ||
(e.EntityType == typeof(Device) &&
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorDeviceProperties.Contains(p)))
) ||
(e.EntityType == typeof(DeviceProfile) &&
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorDeviceProfileProperties.Contains(p)))
) ||
(e.EntityType == typeof(User) &&
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorUserProperties.Contains(p)))
)
)
.Subscribe(RepositoryEvent);
}
private static void RepositoryEvent(RepositoryMonitorEvent i)
{
List<string> deviceSerialNumbers = new List<string>();
List<string> userIds = new List<string>();
if (i.EntityType == typeof(Job))
{
if (i.EventType == RepositoryMonitorEventType.Modified &&
i.ModifiedProperties.Contains("DeviceSerialNumber"))
{
var p = i.GetPreviousPropertyValue<string>("DeviceSerialNumber");
if (p != null)
deviceSerialNumbers.Add(p);
}
var j = (Job)i.Entity;
if (j.DeviceSerialNumber != null)
deviceSerialNumbers.Add(j.DeviceSerialNumber);
}
else if (i.EntityType == typeof(JobMetaNonWarranty))
{
var jmnw = (JobMetaNonWarranty)i.Entity;
if (jmnw.Job != null)
{
if (jmnw.Job.DeviceSerialNumber != null)
deviceSerialNumbers.Add(jmnw.Job.DeviceSerialNumber);
}
else
{
var sn = i.Database.Jobs.Where(j => j.Id == jmnw.JobId).Select(j => j.DeviceSerialNumber).FirstOrDefault();
if (sn != null)
deviceSerialNumbers.Add(sn);
}
}
else if (i.EntityType == typeof(Device))
{
var d = (Device)i.Entity;
deviceSerialNumbers.Add(d.SerialNumber);
if (i.EventType == RepositoryMonitorEventType.Modified &&
i.ModifiedProperties.Contains("AssignedUserId"))
{
var p = i.GetPreviousPropertyValue<string>("AssignedUserId");
if (p != null)
userIds.Add(p);
}
}
else if (i.EntityType == typeof(DeviceProfile))
{
var dp = (DeviceProfile)i.Entity;
deviceSerialNumbers.AddRange(
i.Database.Jobs
.Where(j => !j.ClosedDate.HasValue && j.Device.DeviceProfileId == dp.Id)
.Select(j => j.DeviceSerialNumber)
);
}
else if (i.EntityType == typeof(User))
{
var u = (User)i.Entity;
deviceSerialNumbers.AddRange(
i.Database.Jobs
.Where(j => !j.ClosedDate.HasValue && j.Device.AssignedUserId == u.UserId)
.Select(j => j.DeviceSerialNumber)
);
}
if (deviceSerialNumbers.Count > 0 || userIds.Count > 0)
{
i.ExecuteAfterCommit(e =>
{
BufferedUpdateStream.OnNext(Tuple.Create(deviceSerialNumbers, userIds));
});
}
}
private static void ProcessUpdates(IEnumerable<Tuple<List<string>, List<string>>> e)
{
using (DiscoDataContext Database = new DiscoDataContext())
{
var deviceSerialNumbers = e.SelectMany(i => i.Item1).Distinct().ToList();
var userIds = e.SelectMany(i => i.Item2).Distinct().ToList();
// Determine Held Devices for Users
if (deviceSerialNumbers.Count > 0)
{
userIds.AddRange(
Database.Devices
.Where(d => d.AssignedUserId != null && deviceSerialNumbers.Contains(d.SerialNumber))
.Select(d => d.AssignedUserId)
);
}
if (userIds.Count > 0)
userIds = userIds.Distinct().ToList();
// Notify Held Devices
HeldDevices.BroadcastUpdates(Database, deviceSerialNumbers);
// Notify Held Devices for Users
HeldDevicesForUsers.BroadcastUpdates(Database, userIds);
}
}
internal static void BroadcastUpdates(DiscoDataContext Database, List<string> DeviceSerialNumbers)
{
var jobs = Database.Jobs.Where(j => DeviceSerialNumbers.Contains(j.DeviceSerialNumber));
var items = GetHeldDevices(jobs).ToDictionary(i => i.DeviceSerialNumber, StringComparer.OrdinalIgnoreCase);
for (int skipAmount = 0; skipAmount < DeviceSerialNumbers.Count; skipAmount = skipAmount + 30)
{
var updates = DeviceSerialNumbers
.Skip(skipAmount).Take(30)
.ToDictionary(dsn => dsn,
dsn =>
{
IHeldDeviceItem item;
items.TryGetValue(dsn, out item);
return item;
});
NoticeboardUpdatesHub.HubContext.Clients
.Group(HeldDevices.Name)
.updateHeldDevice(updates);
}
}
private static IEnumerable<IHeldDeviceItem> GetHeldDevices(IQueryable<Job> query)
{
var jobs = query
.Where(j =>
!j.ClosedDate.HasValue &&
j.DeviceSerialNumber != null &&
((j.DeviceHeld.HasValue && !j.DeviceReturnedDate.HasValue) || j.WaitingForUserAction.HasValue)
)
.SelectHeldDeviceItems()
.GroupBy(j => j.DeviceSerialNumber);
foreach (var job in jobs.ToList())
{
if (job.Any(j => j.WaitingForUserAction))
{
var item = job.Where(j => j.WaitingForUserAction).OrderBy(j => j.WaitingForUserActionSince).First();
yield return item;
}
else
{
if (job.All(j => j.ReadyForReturn))
{
var item = job.OrderByDescending(j => j.ReadyForReturnSince).First();
yield return item;
}
else
{
var item = job.Where(j => !j.ReadyForReturn).OrderByDescending(j => j.EstimatedReturnTime).First();
yield return item;
}
}
}
}
public static IEnumerable<IHeldDeviceItem> GetHeldDevices(DiscoDataContext Database)
{
return GetHeldDevices(Database.Jobs);
}
public static IHeldDeviceItem GetHeldDevice(DiscoDataContext Database, string DeviceSerialNumber)
{
return GetHeldDevices(Database.Jobs.Where(j => j.DeviceSerialNumber == DeviceSerialNumber)).FirstOrDefault();
}
internal static IEnumerable<HeldDeviceItem> SelectHeldDeviceItems(this IQueryable<Job> jobs)
{
return HeldDeviceItem.FromJobs(jobs);
}
}
}