aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio_22/Tango.Portal.Chat.Web
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio_22/Tango.Portal.Chat.Web')
-rw-r--r--Software/Visual_Studio_22/Tango.Portal.Chat.Web/Alerts/AlertsWorker.cs96
-rw-r--r--Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/AlertsQuery.cs5
-rw-r--r--Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/N8NService.cs9
-rw-r--r--Software/Visual_Studio_22/Tango.Portal.Chat.Web/Views/Shared/_Layout.cshtml2
4 files changed, 106 insertions, 6 deletions
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;">