From 5b2814a8c9884d1135a2ed131ac8ad49e521bc3b Mon Sep 17 00:00:00 2001 From: jessikitty Date: Thu, 14 May 2026 16:27:43 +1000 Subject: [PATCH] fix: use Contains for status matching - catches Complete/Completed/etc, add Dismiss support --- Services/GoogleSheetService.cs | 78 ++++++---------------------------- 1 file changed, 14 insertions(+), 64 deletions(-) diff --git a/Services/GoogleSheetService.cs b/Services/GoogleSheetService.cs index 2e8d05a..0fde520 100644 --- a/Services/GoogleSheetService.cs +++ b/Services/GoogleSheetService.cs @@ -90,7 +90,6 @@ namespace Disco.Plugins.ServiceTracker.Services for (int j = 0; j < fields.Count; j++) if (!string.IsNullOrWhiteSpace(fields[j])) { allEmpty = false; break; } if (allEmpty) continue; - // Get RAW cell values for stable hashing (before any parsing) var rawTimestamp = RawGet(fields, _config.ColTimestamp); var rawEmail = RawGet(fields, _config.ColEmail); var rawTask = RawGet(fields, _config.ColTask); @@ -113,15 +112,16 @@ namespace Disco.Plugins.ServiceTracker.Services SheetNotes = SafeGet(fields, _config.ColNotes) }; - // Hash uses RAW strings - never changes regardless of date parsing var stableHash = GenerateStableHash(rawTimestamp, rawEmail, rawTask, rawIssue); ticket.InternalId = StableIdFromHash(stableHash, usedIds); ticket.ExternalId = "NTT" + ticket.InternalId.ToString(); usedIds.Add(ticket.InternalId); + // Use Contains for flexible status matching var status = (ticket.RawStatus ?? "").ToLower().Trim(); - ticket.IsOpen = status != "completed" && status != "closed" - && status != "resolved" && status != "done" && status != "cancelled"; + ticket.IsOpen = !status.Contains("complete") && !status.Contains("closed") + && !status.Contains("resolved") && !status.Contains("done") + && !status.Contains("cancel") && !status.Contains("finished"); if (ticket.IsOpen && (!string.IsNullOrWhiteSpace(ticket.TaskTitle) || !string.IsNullOrWhiteSpace(ticket.IssueDescription))) tickets.Add(ticket); @@ -129,36 +129,16 @@ namespace Disco.Plugins.ServiceTracker.Services return tickets; } - /// - /// Hash from RAW cell text - same spreadsheet cells always produce the same number. - /// Uses raw strings, never parsed values, so DateTime.Now never contaminates the hash. - /// private int GenerateStableHash(string rawTimestamp, string rawEmail, string rawTask, string rawIssue) { - var key = (rawTimestamp ?? "").Trim().ToLower() + "|" - + (rawEmail ?? "").Trim().ToLower() + "|" - + (rawTask ?? "").Trim().ToLower() + "|" - + (rawIssue ?? "").Trim().ToLower(); - + var key = (rawTimestamp ?? "").Trim().ToLower() + "|" + (rawEmail ?? "").Trim().ToLower() + "|" + (rawTask ?? "").Trim().ToLower() + "|" + (rawIssue ?? "").Trim().ToLower(); uint hash = 5381; - for (int i = 0; i < key.Length; i++) - hash = ((hash << 5) + hash) + (uint)key[i]; + for (int i = 0; i < key.Length; i++) hash = ((hash << 5) + hash) + (uint)key[i]; return (int)(hash % 800000) + 100000; } - private int StableIdFromHash(int baseId, HashSet used) - { - int id = baseId; - while (used.Contains(id)) id++; - return id; - } - - /// Returns raw untrimmed cell value for hashing. Never returns null - returns empty string. - private string RawGet(List fields, int index) - { - if (index < 0 || index >= fields.Count) return ""; - return fields[index] ?? ""; - } + private int StableIdFromHash(int baseId, HashSet used) { int id = baseId; while (used.Contains(id)) id++; return id; } + private string RawGet(List fields, int index) { if (index < 0 || index >= fields.Count) return ""; return fields[index] ?? ""; } public static string MapPriority(string raw) { @@ -172,45 +152,15 @@ namespace Disco.Plugins.ServiceTracker.Services return "medium"; } - private string SafeGet(List fields, int index) - { - if (index < 0 || index >= fields.Count) return null; - var val = fields[index].Trim(); - return string.IsNullOrEmpty(val) ? null : val; - } - - private DateTime SafeGetDate(List fields, int index) - { - var val = SafeGet(fields, index); - if (val == null) return DateTime.MinValue; - DateTime dt; - if (DateTime.TryParse(val, out dt)) return dt; - return DateTime.MinValue; - } - - private DateTime? SafeGetDateNullable(List fields, int index) - { - var val = SafeGet(fields, index); - if (val == null) return null; - DateTime dt; - if (DateTime.TryParse(val, out dt)) return dt; - return null; - } + private string SafeGet(List fields, int index) { if (index < 0 || index >= fields.Count) return null; var val = fields[index].Trim(); return string.IsNullOrEmpty(val) ? null : val; } + private DateTime SafeGetDate(List fields, int index) { var val = SafeGet(fields, index); if (val == null) return DateTime.MinValue; DateTime dt; if (DateTime.TryParse(val, out dt)) return dt; return DateTime.MinValue; } + private DateTime? SafeGetDateNullable(List fields, int index) { var val = SafeGet(fields, index); if (val == null) return null; DateTime dt; if (DateTime.TryParse(val, out dt)) return dt; return null; } private List ParseCsvLine(string line) { - var fields = new List(); - bool inQuotes = false; - var current = new StringBuilder(); - for (int i = 0; i < line.Length; i++) - { - char c = line[i]; - if (c == '"') { if (inQuotes && i + 1 < line.Length && line[i + 1] == '"') { current.Append('"'); i++; } else inQuotes = !inQuotes; } - else if (c == ',' && !inQuotes) { fields.Add(current.ToString()); current.Clear(); } - else if (c != '\r') current.Append(c); - } - fields.Add(current.ToString()); - return fields; + var fields = new List(); bool inQuotes = false; var current = new StringBuilder(); + for (int i = 0; i < line.Length; i++) { char c = line[i]; if (c == '"') { if (inQuotes && i + 1 < line.Length && line[i + 1] == '"') { current.Append('"'); i++; } else inQuotes = !inQuotes; } else if (c == ',' && !inQuotes) { fields.Add(current.ToString()); current.Clear(); } else if (c != '\r') current.Append(c); } + fields.Add(current.ToString()); return fields; } }