diff --git a/Services/GoogleSheetService.cs b/Services/GoogleSheetService.cs index 6e97e29..f1eeab8 100644 --- a/Services/GoogleSheetService.cs +++ b/Services/GoogleSheetService.cs @@ -41,45 +41,30 @@ namespace Disco.Plugins.ServiceTracker.Services { try { - // Build URL based on ID format string url; var id = _config.SpreadsheetId.Trim(); - if (id.StartsWith("2PACX", StringComparison.OrdinalIgnoreCase)) - { - // Published key format: /d/e/{publishedKey}/pub?output=csv&gid={gid} - url = string.Format( - "https://docs.google.com/spreadsheets/d/e/{0}/pub?output=csv&gid={1}", - id, _config.GId); - } + url = "https://docs.google.com/spreadsheets/d/e/" + id + "/pub?output=csv&gid=" + _config.GId; else - { - // Original spreadsheet ID format: /d/{id}/export?format=csv&gid={gid} - url = string.Format( - "https://docs.google.com/spreadsheets/d/{0}/export?format=csv&gid={1}", - id, _config.GId); - } + url = "https://docs.google.com/spreadsheets/d/" + id + "/export?format=csv&gid=" + _config.GId; using (var client = new WebClient()) { client.Encoding = Encoding.UTF8; - // Some Google responses require a user-agent client.Headers.Add("User-Agent", "Mozilla/5.0 DiscoServiceTracker/1.0"); csvData = client.DownloadString(url); } - // Validate we got actual CSV, not an HTML error page if (csvData != null && csvData.TrimStart().StartsWith(" Share > Publish to web > select the tab > choose 'Comma-separated values (.csv)' > Publish."; + result.Error = "Google returned HTML not CSV. Publish the sheet tab as CSV: File > Share > Publish to web > select tab > CSV > Publish."; return result; } } @@ -93,24 +78,17 @@ namespace Disco.Plugins.ServiceTracker.Services if (File.Exists(_cachePath)) { csvData = File.ReadAllText(_cachePath); - result.Warning = "Using cached data. Fetch error: " + ex.Message; + result.Warning = "Using cache. Error: " + ex.Message; } else { - result.Error = "Could not fetch Google Sheet. Error: " + ex.Message - + "\n\nTroubleshooting:" - + "\n1. Go to your Google Sheet" - + "\n2. File > Share > Publish to web" - + "\n3. Select the correct tab" - + "\n4. Choose 'Comma-separated values (.csv)'" - + "\n5. Click Publish" - + "\n6. In Config, set Spreadsheet ID to the published key (starts with 2PACX-...)"; + result.Error = "Fetch failed: " + ex.Message + ". Publish the sheet tab as CSV."; return result; } } catch (Exception ex) { - result.Error = "Sheet fetch error: " + ex.Message; + result.Error = "Error: " + ex.Message; return result; } } @@ -119,11 +97,11 @@ namespace Disco.Plugins.ServiceTracker.Services { result.Tickets = ParseCsv(csvData); if (result.Tickets.Count == 0 && string.IsNullOrEmpty(result.Warning)) - result.Warning = "Sheet fetched OK but no open tickets found. Check column indices and status values."; + result.Warning = "Sheet OK but no open tickets. All may be Completed/Closed."; } catch (Exception ex) { - result.Error = "CSV parse error: " + ex.Message; + result.Error = "Parse error: " + ex.Message; } return result; @@ -140,44 +118,76 @@ namespace Disco.Plugins.ServiceTracker.Services var fields = ParseCsvLine(lines[i]); if (fields.Count == 0) continue; - // Skip completely empty rows bool allEmpty = true; for (int j = 0; j < fields.Count; j++) - { if (!string.IsNullOrWhiteSpace(fields[j])) { allEmpty = false; break; } - } if (allEmpty) continue; + var taskTitle = SafeGet(fields, _config.ColTask); + var issueDesc = SafeGet(fields, _config.ColIssue); + var requestedBy = SafeGet(fields, _config.ColRequestedBy); + var ticket = new ExternalTicket { InternalId = nextId++, Source = "ntt", Timestamp = SafeGetDate(fields, _config.ColTimestamp), RequesterEmail = SafeGet(fields, _config.ColEmail), - DeviceName = SafeGet(fields, _config.ColDeviceName), + RequesterName = requestedBy, + TaskTitle = taskTitle, + DeviceName = taskTitle, Location = SafeGet(fields, _config.ColLocation), - IssueDescription = SafeGet(fields, _config.ColIssue), + IssueDescription = issueDesc ?? taskTitle, RawPriority = SafeGet(fields, _config.ColPriority), RawStatus = SafeGet(fields, _config.ColStatus), - AssignedTo = SafeGet(fields, _config.ColAssignedTo) + AssignedTo = SafeGet(fields, _config.ColAssignedTo), + PreferredDate = SafeGetDateNullable(fields, _config.ColPreferredDate), + SheetNotes = SafeGet(fields, _config.ColNotes) }; ticket.ExternalId = GenerateStableId(ticket); + // Determine if open var status = (ticket.RawStatus ?? "").ToLower().Trim(); - ticket.IsOpen = status != "closed" && status != "resolved" && status != "completed" && status != "done"; + ticket.IsOpen = status != "completed" && status != "closed" + && status != "resolved" && status != "done" && status != "cancelled"; - // Need at least an issue description or device name to be a valid ticket - if (ticket.IsOpen && (!string.IsNullOrWhiteSpace(ticket.IssueDescription) || !string.IsNullOrWhiteSpace(ticket.DeviceName))) + if (ticket.IsOpen && (!string.IsNullOrWhiteSpace(taskTitle) || !string.IsNullOrWhiteSpace(issueDesc))) tickets.Add(ticket); } return tickets; } + public static string MapPriority(string raw) + { + if (string.IsNullOrEmpty(raw)) return "medium"; + var lower = raw.ToLower().Trim(); + + // Handle emoji priority format: "🔴 Urgent (Same Day)", "🟠 High (1–2 days)" etc. + if (lower.Contains("urgent") || lower.Contains("same day") || lower.Contains("critical")) + return "critical"; + if (lower.Contains("high") || lower.Contains("1-2 day") || lower.Contains("1\u20132 day")) + return "high"; + if (lower.Contains("medium") || lower.Contains("3-5 day") || lower.Contains("3\u20135 day")) + return "medium"; + if (lower.Contains("low") || lower.Contains("1 week") || lower.Contains("week+")) + return "low"; + if (lower.Contains("scheduled") || lower.Contains("planned")) + return "scheduled"; + + // Try matching by emoji colour + if (raw.Contains("\U0001F534") || raw.Contains("\uD83D\uDD34")) return "critical"; // red circle + if (raw.Contains("\U0001F7E0") || raw.Contains("\uD83D\uDFE0")) return "high"; // orange circle + if (raw.Contains("\U0001F7E1") || raw.Contains("\uD83D\uDFE1")) return "medium"; // yellow circle + if (raw.Contains("\U0001F7E2") || raw.Contains("\uD83D\uDFE2")) return "low"; // green circle + + return "medium"; + } + private string GenerateStableId(ExternalTicket t) { - var key = (t.Timestamp.ToString("yyyyMMddHHmm") + "|" + (t.RequesterEmail ?? "") + "|" + (t.DeviceName ?? "")).ToLower(); + var key = (t.Timestamp.ToString("yyyyMMddHHmm") + "|" + (t.RequesterEmail ?? "") + "|" + (t.TaskTitle ?? "")).ToLower(); int hash = 0; for (int i = 0; i < key.Length; i++) hash = ((hash << 5) - hash) + key[i]; @@ -200,6 +210,15 @@ namespace Disco.Plugins.ServiceTracker.Services return DateTime.Now; } + 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();