From 2d6c47884cb7e9a77cf9cd1c950898fd321bb5f5 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Thu, 11 Sep 2025 15:16:06 +0300 Subject: Alerts & n8n Teams Integration. --- .../Tango.Portal.Chat.Web/Alerts/AlertsWorker.cs | 96 +++++++++++++++++++++- .../Tango.Portal.Chat.Web/Models/AlertsQuery.cs | 5 ++ .../Tango.Portal.Chat.Web/Services/N8NService.cs | 9 +- .../Views/Shared/_Layout.cshtml | 2 +- 4 files changed, 106 insertions(+), 6 deletions(-) (limited to 'Software/Visual_Studio_22/Tango.Portal.Chat.Web') 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 _logger; private readonly IServiceScopeFactory _serviceScopeFactory; + private readonly Dictionary> _publishedRowHashes = new(); public AlertsWorker(ILogger 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(); + + 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(); + } + + 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 PostAlertDataAsync(string name, string type, object[] rows, CancellationToken cancellationToken = default) + public async Task 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 @@ - +
-- cgit v1.3.1