From dffbc31432cc8968a819cfe615d207583c170698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Mon, 8 Dec 2025 23:08:46 +0100 Subject: [PATCH] Refactor job sorting logic, reduce poll interval, and implement SignalR subscriptions for real-time updates in DataInbox and Layers pages --- DiunaBI.API/Controllers/JobsController.cs | 7 ++- .../Services/JobWorkerService.cs | 2 +- DiunaBI.UI.Shared/Pages/DataInbox/Index.razor | 1 + .../Pages/DataInbox/Index.razor.cs | 24 ++++++++++- DiunaBI.UI.Shared/Pages/Jobs/Index.razor | 5 --- DiunaBI.UI.Shared/Pages/Layers/Details.razor | 1 + .../Pages/Layers/Details.razor.cs | 43 ++++++++++++++++++- DiunaBI.UI.Shared/Pages/Layers/Index.razor | 1 + DiunaBI.UI.Shared/Pages/Layers/Index.razor.cs | 24 ++++++++++- 9 files changed, 95 insertions(+), 13 deletions(-) diff --git a/DiunaBI.API/Controllers/JobsController.cs b/DiunaBI.API/Controllers/JobsController.cs index 9666476..cca0a7a 100644 --- a/DiunaBI.API/Controllers/JobsController.cs +++ b/DiunaBI.API/Controllers/JobsController.cs @@ -71,11 +71,10 @@ public class JobsController : Controller var totalCount = await query.CountAsync(); - // Sort by: Priority ASC (0=highest), JobType, then CreatedAt DESC + // Sort by: CreatedAt DESC (newest first), then Priority ASC (0=highest) var items = await query - .OrderBy(j => j.Priority) - .ThenBy(j => j.JobType) - .ThenByDescending(j => j.CreatedAt) + .OrderByDescending(j => j.CreatedAt) + .ThenBy(j => j.Priority) .Skip(start) .Take(limit) .AsNoTracking() diff --git a/DiunaBI.Infrastructure/Services/JobWorkerService.cs b/DiunaBI.Infrastructure/Services/JobWorkerService.cs index cfd4f82..580c08e 100644 --- a/DiunaBI.Infrastructure/Services/JobWorkerService.cs +++ b/DiunaBI.Infrastructure/Services/JobWorkerService.cs @@ -11,7 +11,7 @@ public class JobWorkerService : BackgroundService { private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; - private readonly TimeSpan _pollInterval = TimeSpan.FromSeconds(10); + private readonly TimeSpan _pollInterval = TimeSpan.FromSeconds(5); private readonly TimeSpan _rateLimitDelay = TimeSpan.FromSeconds(5); public JobWorkerService(IServiceProvider serviceProvider, ILogger logger) diff --git a/DiunaBI.UI.Shared/Pages/DataInbox/Index.razor b/DiunaBI.UI.Shared/Pages/DataInbox/Index.razor index a3dc0de..55d1557 100644 --- a/DiunaBI.UI.Shared/Pages/DataInbox/Index.razor +++ b/DiunaBI.UI.Shared/Pages/DataInbox/Index.razor @@ -1,6 +1,7 @@ @page "/datainbox" @using MudBlazor.Internal @using DiunaBI.Application.DTOModels +@implements IDisposable Data Inbox diff --git a/DiunaBI.UI.Shared/Pages/DataInbox/Index.razor.cs b/DiunaBI.UI.Shared/Pages/DataInbox/Index.razor.cs index b294995..b3e05d9 100644 --- a/DiunaBI.UI.Shared/Pages/DataInbox/Index.razor.cs +++ b/DiunaBI.UI.Shared/Pages/DataInbox/Index.razor.cs @@ -8,9 +8,10 @@ using Microsoft.JSInterop; namespace DiunaBI.UI.Shared.Pages.DataInbox; -public partial class Index : ComponentBase +public partial class Index : ComponentBase, IDisposable { [Inject] private DataInboxService DataInboxService { get; set; } = default!; + [Inject] private EntityChangeHubService HubService { get; set; } = default!; [Inject] private ISnackbar Snackbar { get; set; } = default!; [Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private DataInboxFilterStateService FilterStateService { get; set; } = default!; @@ -27,6 +28,22 @@ public partial class Index : ComponentBase await DateTimeHelper.InitializeAsync(); filterRequest = FilterStateService.FilterRequest; await LoadDataInbox(); + + // Subscribe to SignalR entity changes + HubService.EntityChanged += OnEntityChanged; + } + + private async void OnEntityChanged(string module, string id, string operation) + { + // Only react if it's a DataInbox change + if (module.Equals("DataInbox", StringComparison.OrdinalIgnoreCase)) + { + await InvokeAsync(async () => + { + await LoadDataInbox(); + StateHasChanged(); + }); + } } private async Task LoadDataInbox() @@ -77,4 +94,9 @@ public partial class Index : ComponentBase var url = NavigationManager.ToAbsoluteUri($"/datainbox/{dataInboxItem.Id}").ToString(); await JSRuntime.InvokeVoidAsync("open", url, "_blank"); } + + public void Dispose() + { + HubService.EntityChanged -= OnEntityChanged; + } } diff --git a/DiunaBI.UI.Shared/Pages/Jobs/Index.razor b/DiunaBI.UI.Shared/Pages/Jobs/Index.razor index 5edcc23..de40f23 100644 --- a/DiunaBI.UI.Shared/Pages/Jobs/Index.razor +++ b/DiunaBI.UI.Shared/Pages/Jobs/Index.razor @@ -69,11 +69,6 @@ - diff --git a/DiunaBI.UI.Shared/Pages/Layers/Details.razor.cs b/DiunaBI.UI.Shared/Pages/Layers/Details.razor.cs index 5251c6f..127f8ec 100644 --- a/DiunaBI.UI.Shared/Pages/Layers/Details.razor.cs +++ b/DiunaBI.UI.Shared/Pages/Layers/Details.razor.cs @@ -6,7 +6,7 @@ using System.Reflection; namespace DiunaBI.UI.Shared.Pages.Layers; -public partial class Details : ComponentBase +public partial class Details : ComponentBase, IDisposable { [Parameter] public Guid Id { get; set; } @@ -20,6 +20,9 @@ public partial class Details : ComponentBase [Inject] private JobService JobService { get; set; } = null!; + [Inject] + private EntityChangeHubService HubService { get; set; } = null!; + [Inject] private NavigationManager NavigationManager { get; set; } = null!; @@ -57,6 +60,39 @@ public partial class Details : ComponentBase { await DateTimeHelper.InitializeAsync(); await LoadLayer(); + + // Subscribe to SignalR entity changes + HubService.EntityChanged += OnEntityChanged; + } + + private async void OnEntityChanged(string module, string id, string operation) + { + // React to Layers or Records changes for this layer + if (module.Equals("Layers", StringComparison.OrdinalIgnoreCase) || + module.Equals("Records", StringComparison.OrdinalIgnoreCase)) + { + // Check if it's this layer or its records that changed + if (Guid.TryParse(id, out var changedId)) + { + if (module.Equals("Layers", StringComparison.OrdinalIgnoreCase) && changedId == Id) + { + await InvokeAsync(async () => + { + await LoadLayer(); + StateHasChanged(); + }); + } + else if (module.Equals("Records", StringComparison.OrdinalIgnoreCase)) + { + // For records, we reload to get the latest data + await InvokeAsync(async () => + { + await LoadLayer(); + StateHasChanged(); + }); + } + } + } } protected override async Task OnParametersSetAsync() @@ -495,4 +531,9 @@ public partial class Details : ComponentBase isRunningJob = false; } } + + public void Dispose() + { + HubService.EntityChanged -= OnEntityChanged; + } } diff --git a/DiunaBI.UI.Shared/Pages/Layers/Index.razor b/DiunaBI.UI.Shared/Pages/Layers/Index.razor index 259793b..51a39e7 100644 --- a/DiunaBI.UI.Shared/Pages/Layers/Index.razor +++ b/DiunaBI.UI.Shared/Pages/Layers/Index.razor @@ -1,6 +1,7 @@ @page "/layers" @using MudBlazor.Internal @using DiunaBI.Application.DTOModels +@implements IDisposable Layers diff --git a/DiunaBI.UI.Shared/Pages/Layers/Index.razor.cs b/DiunaBI.UI.Shared/Pages/Layers/Index.razor.cs index b4f5234..50050cd 100644 --- a/DiunaBI.UI.Shared/Pages/Layers/Index.razor.cs +++ b/DiunaBI.UI.Shared/Pages/Layers/Index.razor.cs @@ -8,9 +8,10 @@ using Microsoft.JSInterop; namespace DiunaBI.UI.Shared.Pages.Layers; -public partial class Index : ComponentBase +public partial class Index : ComponentBase, IDisposable { [Inject] private LayerService LayerService { get; set; } = default!; + [Inject] private EntityChangeHubService HubService { get; set; } = default!; [Inject] private ISnackbar Snackbar { get; set; } = default!; [Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private LayerFilterStateService FilterStateService { get; set; } = default!; @@ -25,6 +26,22 @@ public partial class Index : ComponentBase { filterRequest = FilterStateService.FilterRequest; await LoadLayers(); + + // Subscribe to SignalR entity changes + HubService.EntityChanged += OnEntityChanged; + } + + private async void OnEntityChanged(string module, string id, string operation) + { + // Only react if it's a Layers change + if (module.Equals("Layers", StringComparison.OrdinalIgnoreCase)) + { + await InvokeAsync(async () => + { + await LoadLayers(); + StateHasChanged(); + }); + } } private async Task LoadLayers() @@ -89,4 +106,9 @@ public partial class Index : ComponentBase var url = NavigationManager.ToAbsoluteUri($"/layers/{layer.Id}").ToString(); await JSRuntime.InvokeVoidAsync("open", url, "_blank"); } + + public void Dispose() + { + HubService.EntityChanged -= OnEntityChanged; + } }