diff options
| author | Roy Ben Shabat <roy.mail.net@gmail.com> | 2025-09-11 15:16:06 +0300 |
|---|---|---|
| committer | Roy Ben Shabat <roy.mail.net@gmail.com> | 2025-09-11 15:16:06 +0300 |
| commit | 2d6c47884cb7e9a77cf9cd1c950898fd321bb5f5 (patch) | |
| tree | 35f5ae30b536b6f04e434b847e5c9e5fc000b795 | |
| parent | b3f8ff791b0da2b600b719b745b99029b2df1c32 (diff) | |
| download | Tango-2d6c47884cb7e9a77cf9cd1c950898fd321bb5f5.tar.gz Tango-2d6c47884cb7e9a77cf9cd1c950898fd321bb5f5.zip | |
Alerts & n8n Teams Integration.
5 files changed, 107 insertions, 7 deletions
diff --git a/Software/Visual_Studio/Tango.Emulations/Emulators/MachineEmulator.cs b/Software/Visual_Studio/Tango.Emulations/Emulators/MachineEmulator.cs index 123f72e67..4dffefea2 100644 --- a/Software/Visual_Studio/Tango.Emulations/Emulators/MachineEmulator.cs +++ b/Software/Visual_Studio/Tango.Emulations/Emulators/MachineEmulator.cs @@ -811,7 +811,7 @@ namespace Tango.Emulations.Emulators res.LineNumber = 100; res.Filter = 1; res.Message = "This is a false message " + counter; - res.Category = DebugLogCategory.Info; + res.Category = DebugLogCategory.Error; if (!EmulateCorruption) { diff --git a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Alerts/AlertsWorker.cs b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Alerts/AlertsWorker.cs index c9ad1263a..ad337f4f6 100644 --- a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Alerts/AlertsWorker.cs +++ b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Alerts/AlertsWorker.cs @@ -1,4 +1,7 @@ using System.Data; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; using Tango.Portal.Chat.Web.Models; using Tango.Portal.Chat.Web.Services; @@ -8,6 +11,7 @@ namespace Tango.Portal.Chat.Web.Alerts { private readonly ILogger<AlertsWorker> _logger; private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly Dictionary<string, HashSet<string>> _publishedRowHashes = new(); public AlertsWorker(ILogger<AlertsWorker> logger, IServiceScopeFactory serviceScopeFactory) { @@ -87,8 +91,30 @@ namespace Tango.Portal.Chat.Web.Alerts _logger.LogInformation("Query {Name} returned {RowCount} rows", query.Name, rows.Length); - // Post to n8n - var success = await n8nService.PostAlertDataAsync(query.Name, query.Type, rows, cancellationToken); + // Filter out duplicates if AvoidDuplicates is enabled + if (query.AvoidDuplicates) + { + rows = FilterDuplicateRows(query.Name, rows); + _logger.LogInformation("After duplicate filtering, {RowCount} rows remain for query {Name}", rows.Length, query.Name); + } + + bool success = true; + + if (rows.Count() > 0) + { + // Post to n8n + success = await n8nService.PostAlertDataAsync(query.Name, query.Type, query.ChartType, query.ChartTitle, query.ChartInstructions, rows, cancellationToken); + + // If successful and AvoidDuplicates is enabled, store hashes of published rows + if (success && query.AvoidDuplicates) + { + StorePublishedRowHashes(query.Name, rows); + } + } + else + { + _logger.LogInformation("No data returned for query: {Name}, skipping n8n post", query.Name); + } if (success) { @@ -107,6 +133,72 @@ namespace Tango.Portal.Chat.Web.Alerts } } + private object[] FilterDuplicateRows(string queryName, object[] rows) + { + if (!_publishedRowHashes.TryGetValue(queryName, out var publishedHashes)) + { + return rows; + } + + var filteredRows = new List<object>(); + + foreach (var row in rows) + { + var rowHash = GenerateRowHash(row); + if (!publishedHashes.Contains(rowHash)) + { + filteredRows.Add(row); + } + } + + return filteredRows.ToArray(); + } + + private void StorePublishedRowHashes(string queryName, object[] rows) + { + if (!_publishedRowHashes.ContainsKey(queryName)) + { + _publishedRowHashes[queryName] = new HashSet<string>(); + } + + var publishedHashes = _publishedRowHashes[queryName]; + + foreach (var row in rows) + { + var rowHash = GenerateRowHash(row); + publishedHashes.Add(rowHash); + } + + // Optional: Limit the size of the hash set to prevent memory issues + // Keep only the most recent 10,000 hashes per query + const int maxHashCount = 10000; + if (publishedHashes.Count > maxHashCount) + { + var hashesToRemove = publishedHashes.Take(publishedHashes.Count - maxHashCount).ToList(); + foreach (var hash in hashesToRemove) + { + publishedHashes.Remove(hash); + } + _logger.LogInformation("Pruned {Count} old hashes for query {Name} to maintain memory limits", + hashesToRemove.Count, queryName); + } + } + + private static string GenerateRowHash(object row) + { + // Serialize the row to JSON for consistent hashing + var json = JsonSerializer.Serialize(row, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull + }); + + // Generate SHA256 hash of the JSON + using var sha256 = SHA256.Create(); + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(json)); + return Convert.ToHexString(hashBytes); + } + private static object[] ConvertDataTableToObjectArray(DataTable dataTable) { var rows = new object[dataTable.Rows.Count]; diff --git a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/AlertsQuery.cs b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/AlertsQuery.cs index 9ab787104..318cbe3b0 100644 --- a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/AlertsQuery.cs +++ b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/AlertsQuery.cs @@ -16,5 +16,10 @@ namespace Tango.Portal.Chat.Web.Models public int IntervalMinutes { get; set; } public bool Enable { get; set; } public DateTime ExecutesOn { get; set; } + public bool AvoidDuplicates { get; set; } + public String ChartType { get; set; } = String.Empty; + public String ChartTitle { get; set; } = String.Empty; + public String ChartInstructions { get; set; } = String.Empty; + } }
\ No newline at end of file diff --git a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/N8NService.cs b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/N8NService.cs index 8b4b2edde..c0100d868 100644 --- a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/N8NService.cs +++ b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/N8NService.cs @@ -22,7 +22,7 @@ namespace Tango.Portal.Chat.Web.Services _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials); } - public async Task<bool> PostAlertDataAsync(string name, string type, object[] rows, CancellationToken cancellationToken = default) + public async Task<bool> PostAlertDataAsync(string name, string type, string chartType, string chartTitle, string chartInstructions, object[] rows, CancellationToken cancellationToken = default) { try { @@ -30,7 +30,10 @@ namespace Tango.Portal.Chat.Web.Services { Name = name, Type = type, - Rows = rows + Rows = rows, + ChartType = chartType, + ChartTitle = chartTitle, + ChartInstructions = chartInstructions }; var json = JsonSerializer.Serialize(payload); @@ -45,7 +48,7 @@ namespace Tango.Portal.Chat.Web.Services } else { - _logger.LogWarning("Failed to post alert data for {Name} to n8n. Status: {StatusCode}, Response: {Response}", + _logger.LogWarning("Failed to post alert data for {Name} to n8n. Status: {StatusCode}, Response: {Response}", name, response.StatusCode, await response.Content.ReadAsStringAsync(cancellationToken)); return false; } diff --git a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Views/Shared/_Layout.cshtml b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Views/Shared/_Layout.cshtml index f5aece565..069f79a3d 100644 --- a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Views/Shared/_Layout.cshtml +++ b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Views/Shared/_Layout.cshtml @@ -40,7 +40,7 @@ <li class="nav-item"><a class="nav-link" href="https://twine-srv.com/firmware">Firmware Upgrades</a></li> <li class="nav-item"><a class="nav-link" href="https://twine-srv.com/utilities">Utilities</a></li> <li class="nav-item"><a class="nav-link" href="https://twine-srv.com/docs">Docs</a></li> - <li class="nav-item"><a class="nav-link active" href="https://ai.twine-srv.com">AI</a></li> + <li class="nav-item"><a class="nav-link active" href="">AI</a></li> </ul> <div> <svg class="d-block ratio" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 20 20" fill="none" style="font-size: 53px;"> |
