feat(ui): add Node column + filter to AuditLog grid
This commit is contained in:
@@ -3,8 +3,11 @@ using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using ScadaLink.CentralUI.Components.Audit;
|
||||
using ScadaLink.CentralUI.Services;
|
||||
using ScadaLink.Commons.Entities.Audit;
|
||||
using ScadaLink.Commons.Entities.Sites;
|
||||
using ScadaLink.Commons.Interfaces.Repositories;
|
||||
using ScadaLink.Commons.Types;
|
||||
using ScadaLink.Commons.Types.Audit;
|
||||
using ScadaLink.Commons.Types.Enums;
|
||||
|
||||
@@ -29,6 +32,7 @@ namespace ScadaLink.CentralUI.Tests.Components.Audit;
|
||||
public class AuditFilterBarTests : BunitContext
|
||||
{
|
||||
private readonly ISiteRepository _siteRepo;
|
||||
private readonly IAuditLogQueryService _auditLogQueryService;
|
||||
|
||||
public AuditFilterBarTests()
|
||||
{
|
||||
@@ -40,6 +44,17 @@ public class AuditFilterBarTests : BunitContext
|
||||
new("Plant B", "plant-b") { Id = 2 },
|
||||
}));
|
||||
Services.AddSingleton(_siteRepo);
|
||||
|
||||
// Task 15: the Node multi-select pulls its options from
|
||||
// IAuditLogQueryService.GetDistinctSourceNodesAsync. The default stub
|
||||
// returns the two central nodes the cluster uses; individual tests can
|
||||
// override via _auditLogQueryService.GetDistinctSourceNodesAsync(...).Returns(...).
|
||||
_auditLogQueryService = Substitute.For<IAuditLogQueryService>();
|
||||
_auditLogQueryService.GetDistinctSourceNodesAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<string>>(new[] { "central-a", "central-b" }));
|
||||
_auditLogQueryService.QueryAsync(Arg.Any<AuditLogQueryFilter>(), Arg.Any<AuditLogPaging>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<AuditEvent>>(Array.Empty<AuditEvent>()));
|
||||
Services.AddSingleton(_auditLogQueryService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -55,6 +70,7 @@ public class AuditFilterBarTests : BunitContext
|
||||
"data-test=\"filter-kind\"",
|
||||
"data-test=\"filter-status\"",
|
||||
"data-test=\"filter-site\"",
|
||||
"data-test=\"filter-node\"",
|
||||
"data-test=\"filter-time-range\"",
|
||||
"data-test=\"filter-custom-range\"",
|
||||
"data-test=\"filter-instance\"",
|
||||
@@ -160,6 +176,30 @@ public class AuditFilterBarTests : BunitContext
|
||||
Assert.Equal(new[] { AuditStatus.Delivered }, captured!.Statuses);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NodeMultiSelect_RendersOptions_FromQueryService_AndMapsThroughToFilter()
|
||||
{
|
||||
// Task 15: the Node filter pulls its option set from
|
||||
// IAuditLogQueryService.GetDistinctSourceNodesAsync and threads the
|
||||
// chip selection into AuditLogQueryFilter.SourceNodes.
|
||||
AuditLogQueryFilter? captured = null;
|
||||
var cut = Render<AuditFilterBar>(p => p
|
||||
.Add(c => c.OnFilterChanged, EventCallback.Factory.Create<AuditLogQueryFilter>(this, f => captured = f)));
|
||||
|
||||
// The bar marker plus the option checkboxes for the two cluster nodes
|
||||
// are present after init (the constructor stubs return two nodes).
|
||||
Assert.Contains("data-test=\"filter-node\"", cut.Markup);
|
||||
Assert.Contains("data-test=\"filter-node-ms-opt-central-a\"", cut.Markup);
|
||||
Assert.Contains("data-test=\"filter-node-ms-opt-central-b\"", cut.Markup);
|
||||
|
||||
cut.Find("[data-test=\"filter-node-ms-opt-central-a\"]").Change(true);
|
||||
cut.Find("[data-test=\"filter-apply\"]").Click();
|
||||
|
||||
Assert.NotNull(captured);
|
||||
Assert.NotNull(captured!.SourceNodes);
|
||||
Assert.Equal(new[] { "central-a" }, captured.SourceNodes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Apply_WithMultipleStatusChips_PassesAllSelectedStatuses()
|
||||
{
|
||||
|
||||
@@ -123,6 +123,28 @@ public class AuditResultsGridTests : BunitContext
|
||||
Assert.Equal(target.EventId, captured!.EventId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_IncludesNodeColumn_BetweenSiteAndChannel()
|
||||
{
|
||||
// Task 15: the grid surfaces SourceNode in a dedicated "Node" column
|
||||
// positioned between Site and Channel.
|
||||
StubPage(new List<AuditEvent>
|
||||
{
|
||||
MakeEvent(DateTime.UtcNow.AddMinutes(-1), AuditStatus.Delivered),
|
||||
});
|
||||
|
||||
var cut = Render<AuditResultsGrid>(p => p.Add(c => c.Filter, new AuditLogQueryFilter()));
|
||||
|
||||
Assert.Contains("data-test=\"col-header-Node\"", cut.Markup);
|
||||
|
||||
// The header order must place Node between Site and Channel.
|
||||
var siteIdx = cut.Markup.IndexOf("data-test=\"col-header-Site\"", StringComparison.Ordinal);
|
||||
var nodeIdx = cut.Markup.IndexOf("data-test=\"col-header-Node\"", StringComparison.Ordinal);
|
||||
var channelIdx = cut.Markup.IndexOf("data-test=\"col-header-Channel\"", StringComparison.Ordinal);
|
||||
Assert.True(siteIdx < nodeIdx, "Node column must follow Site.");
|
||||
Assert.True(nodeIdx < channelIdx, "Node column must precede Channel.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Render_IncludesExecutionIdColumn()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user