feat: add external ticket storage for Google Sheet items

This commit is contained in:
2026-05-06 14:41:00 +10:00
parent 94f02a2911
commit 1a70f68938
+85 -28
View File
@@ -7,15 +7,12 @@ using System.Linq;
namespace Disco.Plugins.ServiceTracker.Services namespace Disco.Plugins.ServiceTracker.Services
{ {
/// <summary>
/// JSON file-based data store for ServiceTracker metadata.
/// Stores ticket extensions and configuration in the plugin data directory.
/// </summary>
public class ServiceTrackerDataStore public class ServiceTrackerDataStore
{ {
private readonly string _dataDirectory; private readonly string _dataDirectory;
private readonly string _ticketsPath; private readonly string _ticketsPath;
private readonly string _configPath; private readonly string _configPath;
private readonly string _externalPath;
private static readonly object _lock = new object(); private static readonly object _lock = new object();
public ServiceTrackerDataStore(string pluginDataDirectory) public ServiceTrackerDataStore(string pluginDataDirectory)
@@ -23,11 +20,12 @@ namespace Disco.Plugins.ServiceTracker.Services
_dataDirectory = pluginDataDirectory; _dataDirectory = pluginDataDirectory;
_ticketsPath = Path.Combine(_dataDirectory, "tickets.json"); _ticketsPath = Path.Combine(_dataDirectory, "tickets.json");
_configPath = Path.Combine(_dataDirectory, "config.json"); _configPath = Path.Combine(_dataDirectory, "config.json");
_externalPath = Path.Combine(_dataDirectory, "external_tickets.json");
if (!Directory.Exists(_dataDirectory)) if (!Directory.Exists(_dataDirectory)) Directory.CreateDirectory(_dataDirectory);
Directory.CreateDirectory(_dataDirectory);
} }
public string DataDirectory { get { return _dataDirectory; } }
// --- Configuration --- // --- Configuration ---
public ServiceTrackerConfig LoadConfig() public ServiceTrackerConfig LoadConfig()
@@ -36,12 +34,19 @@ namespace Disco.Plugins.ServiceTracker.Services
{ {
if (!File.Exists(_configPath)) if (!File.Exists(_configPath))
{ {
var defaultConfig = ServiceTrackerConfig.CreateDefault(); var def = ServiceTrackerConfig.CreateDefault();
SaveConfig(defaultConfig); SaveConfig(def);
return defaultConfig; return def;
} }
var json = File.ReadAllText(_configPath); var json = File.ReadAllText(_configPath);
return JsonConvert.DeserializeObject<ServiceTrackerConfig>(json) ?? ServiceTrackerConfig.CreateDefault(); var config = JsonConvert.DeserializeObject<ServiceTrackerConfig>(json);
if (config == null) return ServiceTrackerConfig.CreateDefault();
// Ensure new fields have defaults
if (config.Technicians == null) config.Technicians = new List<TechEntry>();
if (config.GoogleSheet == null) config.GoogleSheet = new GoogleSheetConfig();
if (string.IsNullOrEmpty(config.DiscoBaseUrl)) config.DiscoBaseUrl = "http://disco:9292";
if (config.DetailInactivitySeconds <= 0) config.DetailInactivitySeconds = 300;
return config;
} }
} }
@@ -54,15 +59,13 @@ namespace Disco.Plugins.ServiceTracker.Services
} }
} }
// --- Tickets --- // --- Disco Tickets ---
public List<ServiceTicket> LoadAllTickets() public List<ServiceTicket> LoadAllTickets()
{ {
lock (_lock) lock (_lock)
{ {
if (!File.Exists(_ticketsPath)) if (!File.Exists(_ticketsPath)) return new List<ServiceTicket>();
return new List<ServiceTicket>();
var json = File.ReadAllText(_ticketsPath); var json = File.ReadAllText(_ticketsPath);
return JsonConvert.DeserializeObject<List<ServiceTicket>>(json) ?? new List<ServiceTicket>(); return JsonConvert.DeserializeObject<List<ServiceTicket>>(json) ?? new List<ServiceTicket>();
} }
@@ -78,14 +81,9 @@ namespace Disco.Plugins.ServiceTracker.Services
lock (_lock) lock (_lock)
{ {
var tickets = LoadAllTicketsUnsafe(); var tickets = LoadAllTicketsUnsafe();
var existing = tickets.FindIndex(t => t.JobId == ticket.JobId); var idx = tickets.FindIndex(t => t.JobId == ticket.JobId);
ticket.LastModifiedDate = DateTime.Now; ticket.LastModifiedDate = DateTime.Now;
if (idx >= 0) tickets[idx] = ticket; else tickets.Add(ticket);
if (existing >= 0)
tickets[existing] = ticket;
else
tickets.Add(ticket);
SaveAllTicketsUnsafe(tickets); SaveAllTicketsUnsafe(tickets);
} }
} }
@@ -108,8 +106,7 @@ namespace Disco.Plugins.ServiceTracker.Services
var ticket = tickets.FirstOrDefault(t => t.JobId == jobId); var ticket = tickets.FirstOrDefault(t => t.JobId == jobId);
if (ticket != null) if (ticket != null)
{ {
if (ticket.Notes == null) if (ticket.Notes == null) ticket.Notes = new List<TicketNote>();
ticket.Notes = new List<TicketNote>();
ticket.Notes.Add(note); ticket.Notes.Add(note);
ticket.LastModifiedDate = DateTime.Now; ticket.LastModifiedDate = DateTime.Now;
SaveAllTicketsUnsafe(tickets); SaveAllTicketsUnsafe(tickets);
@@ -117,19 +114,79 @@ namespace Disco.Plugins.ServiceTracker.Services
} }
} }
// Internal methods (caller must hold lock) // --- External Tickets (Google Sheet) ---
public List<ServiceTicket> LoadExternalTickets()
{
lock (_lock)
{
if (!File.Exists(_externalPath)) return new List<ServiceTicket>();
var json = File.ReadAllText(_externalPath);
return JsonConvert.DeserializeObject<List<ServiceTicket>>(json) ?? new List<ServiceTicket>();
}
}
public void SaveExternalTickets(List<ServiceTicket> tickets)
{
lock (_lock)
{
var json = JsonConvert.SerializeObject(tickets, Formatting.Indented);
File.WriteAllText(_externalPath, json);
}
}
public ServiceTicket GetExternalTicket(int internalId)
{
return LoadExternalTickets().FirstOrDefault(t => t.JobId == internalId);
}
public void SaveExternalTicket(ServiceTicket ticket)
{
lock (_lock)
{
var tickets = LoadExternalTicketsUnsafe();
var idx = tickets.FindIndex(t => t.JobId == ticket.JobId);
ticket.LastModifiedDate = DateTime.Now;
if (idx >= 0) tickets[idx] = ticket; else tickets.Add(ticket);
var json = JsonConvert.SerializeObject(tickets, Formatting.Indented);
File.WriteAllText(_externalPath, json);
}
}
public void AddExternalNote(int internalId, TicketNote note)
{
lock (_lock)
{
var tickets = LoadExternalTicketsUnsafe();
var ticket = tickets.FirstOrDefault(t => t.JobId == internalId);
if (ticket != null)
{
if (ticket.Notes == null) ticket.Notes = new List<TicketNote>();
ticket.Notes.Add(note);
ticket.LastModifiedDate = DateTime.Now;
var json = JsonConvert.SerializeObject(tickets, Formatting.Indented);
File.WriteAllText(_externalPath, json);
}
}
}
// Internal (caller must hold lock)
private List<ServiceTicket> LoadAllTicketsUnsafe() private List<ServiceTicket> LoadAllTicketsUnsafe()
{ {
if (!File.Exists(_ticketsPath)) if (!File.Exists(_ticketsPath)) return new List<ServiceTicket>();
return new List<ServiceTicket>();
var json = File.ReadAllText(_ticketsPath); var json = File.ReadAllText(_ticketsPath);
return JsonConvert.DeserializeObject<List<ServiceTicket>>(json) ?? new List<ServiceTicket>(); return JsonConvert.DeserializeObject<List<ServiceTicket>>(json) ?? new List<ServiceTicket>();
} }
private void SaveAllTicketsUnsafe(List<ServiceTicket> tickets) private void SaveAllTicketsUnsafe(List<ServiceTicket> tickets)
{ {
var json = JsonConvert.SerializeObject(tickets, Formatting.Indented); var json = JsonConvert.SerializeObject(tickets, Formatting.Indented);
File.WriteAllText(_ticketsPath, json); File.WriteAllText(_ticketsPath, json);
} }
private List<ServiceTicket> LoadExternalTicketsUnsafe()
{
if (!File.Exists(_externalPath)) return new List<ServiceTicket>();
var json = File.ReadAllText(_externalPath);
return JsonConvert.DeserializeObject<List<ServiceTicket>>(json) ?? new List<ServiceTicket>();
}
} }
} }