Refactor job sorting logic, reduce poll interval, and implement SignalR subscriptions for real-time updates in DataInbox and Layers pages
All checks were successful
Build Docker Images / test (map[name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Successful in 1m28s
Build Docker Images / test (map[name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Successful in 1m26s
Build Docker Images / build-and-push (map[image_suffix:morska name:Morska plugin_project:DiunaBI.Plugins.Morska]) (push) Successful in 1m38s
Build Docker Images / build-and-push (map[image_suffix:pedrollopl name:PedrolloPL plugin_project:DiunaBI.Plugins.PedrolloPL]) (push) Successful in 1m38s

This commit is contained in:
2025-12-08 23:08:46 +01:00
parent 151ecaa98f
commit dffbc31432
9 changed files with 95 additions and 13 deletions

View File

@@ -71,11 +71,10 @@ public class JobsController : Controller
var totalCount = await query.CountAsync(); 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 var items = await query
.OrderBy(j => j.Priority) .OrderByDescending(j => j.CreatedAt)
.ThenBy(j => j.JobType) .ThenBy(j => j.Priority)
.ThenByDescending(j => j.CreatedAt)
.Skip(start) .Skip(start)
.Take(limit) .Take(limit)
.AsNoTracking() .AsNoTracking()

View File

@@ -11,7 +11,7 @@ public class JobWorkerService : BackgroundService
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly ILogger<JobWorkerService> _logger; private readonly ILogger<JobWorkerService> _logger;
private readonly TimeSpan _pollInterval = TimeSpan.FromSeconds(10); private readonly TimeSpan _pollInterval = TimeSpan.FromSeconds(5);
private readonly TimeSpan _rateLimitDelay = TimeSpan.FromSeconds(5); private readonly TimeSpan _rateLimitDelay = TimeSpan.FromSeconds(5);
public JobWorkerService(IServiceProvider serviceProvider, ILogger<JobWorkerService> logger) public JobWorkerService(IServiceProvider serviceProvider, ILogger<JobWorkerService> logger)

View File

@@ -1,6 +1,7 @@
@page "/datainbox" @page "/datainbox"
@using MudBlazor.Internal @using MudBlazor.Internal
@using DiunaBI.Application.DTOModels @using DiunaBI.Application.DTOModels
@implements IDisposable
<PageTitle>Data Inbox</PageTitle> <PageTitle>Data Inbox</PageTitle>

View File

@@ -8,9 +8,10 @@ using Microsoft.JSInterop;
namespace DiunaBI.UI.Shared.Pages.DataInbox; 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 DataInboxService DataInboxService { get; set; } = default!;
[Inject] private EntityChangeHubService HubService { get; set; } = default!;
[Inject] private ISnackbar Snackbar { get; set; } = default!; [Inject] private ISnackbar Snackbar { get; set; } = default!;
[Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] private DataInboxFilterStateService FilterStateService { get; set; } = default!; [Inject] private DataInboxFilterStateService FilterStateService { get; set; } = default!;
@@ -27,6 +28,22 @@ public partial class Index : ComponentBase
await DateTimeHelper.InitializeAsync(); await DateTimeHelper.InitializeAsync();
filterRequest = FilterStateService.FilterRequest; filterRequest = FilterStateService.FilterRequest;
await LoadDataInbox(); 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() private async Task LoadDataInbox()
@@ -77,4 +94,9 @@ public partial class Index : ComponentBase
var url = NavigationManager.ToAbsoluteUri($"/datainbox/{dataInboxItem.Id}").ToString(); var url = NavigationManager.ToAbsoluteUri($"/datainbox/{dataInboxItem.Id}").ToString();
await JSRuntime.InvokeVoidAsync("open", url, "_blank"); await JSRuntime.InvokeVoidAsync("open", url, "_blank");
} }
public void Dispose()
{
HubService.EntityChanged -= OnEntityChanged;
}
} }

View File

@@ -69,11 +69,6 @@
</MudMenuItem> </MudMenuItem>
</MudMenu> </MudMenu>
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
OnClick="LoadJobs"
Color="Color.Primary"
Size="Size.Medium"
Title="Refresh"/>
<MudIconButton Icon="@Icons.Material.Filled.Clear" <MudIconButton Icon="@Icons.Material.Filled.Clear"
OnClick="ClearFilters" OnClick="ClearFilters"
Color="Color.Default" Color="Color.Default"

View File

@@ -2,6 +2,7 @@
@using DiunaBI.UI.Shared.Services @using DiunaBI.UI.Shared.Services
@using DiunaBI.Application.DTOModels @using DiunaBI.Application.DTOModels
@using MudBlazor @using MudBlazor
@implements IDisposable
<MudCard> <MudCard>
<MudCardHeader> <MudCardHeader>

View File

@@ -6,7 +6,7 @@ using System.Reflection;
namespace DiunaBI.UI.Shared.Pages.Layers; namespace DiunaBI.UI.Shared.Pages.Layers;
public partial class Details : ComponentBase public partial class Details : ComponentBase, IDisposable
{ {
[Parameter] [Parameter]
public Guid Id { get; set; } public Guid Id { get; set; }
@@ -20,6 +20,9 @@ public partial class Details : ComponentBase
[Inject] [Inject]
private JobService JobService { get; set; } = null!; private JobService JobService { get; set; } = null!;
[Inject]
private EntityChangeHubService HubService { get; set; } = null!;
[Inject] [Inject]
private NavigationManager NavigationManager { get; set; } = null!; private NavigationManager NavigationManager { get; set; } = null!;
@@ -57,6 +60,39 @@ public partial class Details : ComponentBase
{ {
await DateTimeHelper.InitializeAsync(); await DateTimeHelper.InitializeAsync();
await LoadLayer(); 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() protected override async Task OnParametersSetAsync()
@@ -495,4 +531,9 @@ public partial class Details : ComponentBase
isRunningJob = false; isRunningJob = false;
} }
} }
public void Dispose()
{
HubService.EntityChanged -= OnEntityChanged;
}
} }

View File

@@ -1,6 +1,7 @@
@page "/layers" @page "/layers"
@using MudBlazor.Internal @using MudBlazor.Internal
@using DiunaBI.Application.DTOModels @using DiunaBI.Application.DTOModels
@implements IDisposable
<PageTitle>Layers</PageTitle> <PageTitle>Layers</PageTitle>

View File

@@ -8,9 +8,10 @@ using Microsoft.JSInterop;
namespace DiunaBI.UI.Shared.Pages.Layers; 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 LayerService LayerService { get; set; } = default!;
[Inject] private EntityChangeHubService HubService { get; set; } = default!;
[Inject] private ISnackbar Snackbar { get; set; } = default!; [Inject] private ISnackbar Snackbar { get; set; } = default!;
[Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] private LayerFilterStateService FilterStateService { get; set; } = default!; [Inject] private LayerFilterStateService FilterStateService { get; set; } = default!;
@@ -25,6 +26,22 @@ public partial class Index : ComponentBase
{ {
filterRequest = FilterStateService.FilterRequest; filterRequest = FilterStateService.FilterRequest;
await LoadLayers(); 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() private async Task LoadLayers()
@@ -89,4 +106,9 @@ public partial class Index : ComponentBase
var url = NavigationManager.ToAbsoluteUri($"/layers/{layer.Id}").ToString(); var url = NavigationManager.ToAbsoluteUri($"/layers/{layer.Id}").ToString();
await JSRuntime.InvokeVoidAsync("open", url, "_blank"); await JSRuntime.InvokeVoidAsync("open", url, "_blank");
} }
public void Dispose()
{
HubService.EntityChanged -= OnEntityChanged;
}
} }