fix: correct column mapping, emoji priority parsing, add RequestedBy/PreferredDate/Notes

This commit is contained in:
2026-05-07 09:18:44 +10:00
parent 2472894704
commit a7e0a62faa
+60 -41
View File
@@ -41,45 +41,30 @@ namespace Disco.Plugins.ServiceTracker.Services
{ {
try try
{ {
// Build URL based on ID format
string url; string url;
var id = _config.SpreadsheetId.Trim(); var id = _config.SpreadsheetId.Trim();
if (id.StartsWith("2PACX", StringComparison.OrdinalIgnoreCase)) if (id.StartsWith("2PACX", StringComparison.OrdinalIgnoreCase))
{ url = "https://docs.google.com/spreadsheets/d/e/" + id + "/pub?output=csv&gid=" + _config.GId;
// 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);
}
else else
{ url = "https://docs.google.com/spreadsheets/d/" + id + "/export?format=csv&gid=" + _config.GId;
// 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);
}
using (var client = new WebClient()) using (var client = new WebClient())
{ {
client.Encoding = Encoding.UTF8; client.Encoding = Encoding.UTF8;
// Some Google responses require a user-agent
client.Headers.Add("User-Agent", "Mozilla/5.0 DiscoServiceTracker/1.0"); client.Headers.Add("User-Agent", "Mozilla/5.0 DiscoServiceTracker/1.0");
csvData = client.DownloadString(url); csvData = client.DownloadString(url);
} }
// Validate we got actual CSV, not an HTML error page
if (csvData != null && csvData.TrimStart().StartsWith("<!DOCTYPE", StringComparison.OrdinalIgnoreCase)) if (csvData != null && csvData.TrimStart().StartsWith("<!DOCTYPE", StringComparison.OrdinalIgnoreCase))
{ {
if (File.Exists(_cachePath)) if (File.Exists(_cachePath))
{ {
csvData = File.ReadAllText(_cachePath); csvData = File.ReadAllText(_cachePath);
result.Warning = "Got HTML instead of CSV. Using cached data. Check Publish to Web settings (must publish as CSV)."; result.Warning = "Got HTML instead of CSV. Using cache. Publish sheet as CSV format.";
} }
else else
{ {
result.Error = "Google returned HTML instead of CSV. Ensure the sheet is published to web. " result.Error = "Google returned HTML not CSV. Publish the sheet tab as CSV: File > Share > Publish to web > select tab > CSV > Publish.";
+ "Go to File > Share > Publish to web > select the tab > choose 'Comma-separated values (.csv)' > Publish.";
return result; return result;
} }
} }
@@ -93,24 +78,17 @@ namespace Disco.Plugins.ServiceTracker.Services
if (File.Exists(_cachePath)) if (File.Exists(_cachePath))
{ {
csvData = File.ReadAllText(_cachePath); csvData = File.ReadAllText(_cachePath);
result.Warning = "Using cached data. Fetch error: " + ex.Message; result.Warning = "Using cache. Error: " + ex.Message;
} }
else else
{ {
result.Error = "Could not fetch Google Sheet. Error: " + ex.Message result.Error = "Fetch failed: " + ex.Message + ". Publish the sheet tab as CSV.";
+ "\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-...)";
return result; return result;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Error = "Sheet fetch error: " + ex.Message; result.Error = "Error: " + ex.Message;
return result; return result;
} }
} }
@@ -119,11 +97,11 @@ namespace Disco.Plugins.ServiceTracker.Services
{ {
result.Tickets = ParseCsv(csvData); result.Tickets = ParseCsv(csvData);
if (result.Tickets.Count == 0 && string.IsNullOrEmpty(result.Warning)) 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) catch (Exception ex)
{ {
result.Error = "CSV parse error: " + ex.Message; result.Error = "Parse error: " + ex.Message;
} }
return result; return result;
@@ -140,44 +118,76 @@ namespace Disco.Plugins.ServiceTracker.Services
var fields = ParseCsvLine(lines[i]); var fields = ParseCsvLine(lines[i]);
if (fields.Count == 0) continue; if (fields.Count == 0) continue;
// Skip completely empty rows
bool allEmpty = true; bool allEmpty = true;
for (int j = 0; j < fields.Count; j++) for (int j = 0; j < fields.Count; j++)
{
if (!string.IsNullOrWhiteSpace(fields[j])) { allEmpty = false; break; } if (!string.IsNullOrWhiteSpace(fields[j])) { allEmpty = false; break; }
}
if (allEmpty) continue; 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 var ticket = new ExternalTicket
{ {
InternalId = nextId++, InternalId = nextId++,
Source = "ntt", Source = "ntt",
Timestamp = SafeGetDate(fields, _config.ColTimestamp), Timestamp = SafeGetDate(fields, _config.ColTimestamp),
RequesterEmail = SafeGet(fields, _config.ColEmail), RequesterEmail = SafeGet(fields, _config.ColEmail),
DeviceName = SafeGet(fields, _config.ColDeviceName), RequesterName = requestedBy,
TaskTitle = taskTitle,
DeviceName = taskTitle,
Location = SafeGet(fields, _config.ColLocation), Location = SafeGet(fields, _config.ColLocation),
IssueDescription = SafeGet(fields, _config.ColIssue), IssueDescription = issueDesc ?? taskTitle,
RawPriority = SafeGet(fields, _config.ColPriority), RawPriority = SafeGet(fields, _config.ColPriority),
RawStatus = SafeGet(fields, _config.ColStatus), 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); ticket.ExternalId = GenerateStableId(ticket);
// Determine if open
var status = (ticket.RawStatus ?? "").ToLower().Trim(); 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(taskTitle) || !string.IsNullOrWhiteSpace(issueDesc)))
if (ticket.IsOpen && (!string.IsNullOrWhiteSpace(ticket.IssueDescription) || !string.IsNullOrWhiteSpace(ticket.DeviceName)))
tickets.Add(ticket); tickets.Add(ticket);
} }
return tickets; 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 (12 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) 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; int hash = 0;
for (int i = 0; i < key.Length; i++) for (int i = 0; i < key.Length; i++)
hash = ((hash << 5) - hash) + key[i]; hash = ((hash << 5) - hash) + key[i];
@@ -200,6 +210,15 @@ namespace Disco.Plugins.ServiceTracker.Services
return DateTime.Now; return DateTime.Now;
} }
private DateTime? SafeGetDateNullable(List<string> 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<string> ParseCsvLine(string line) private List<string> ParseCsvLine(string line)
{ {
var fields = new List<string>(); var fields = new List<string>();