refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
**Goal:** Close the five deferred implementation follow-ups from the Audit Log #23 roadmap so site audit events actually reach central, the audit/SiteCall surfaces are complete, and known tech debt is paid down.
|
||||
|
||||
**Architecture:** Five independent-ish workstreams against the existing ScadaLink codebase. The headline change: site→central audit forwarding moves from the production `NoOpSiteStreamAuditClient` stub to a real **ClusterClient-based push** — the same transport notifications already use (`SiteCommunicationActor` → `ClusterClient.Send("/user/central-communication", …)` → `CentralCommunicationActor`), avoiding a new central-hosted gRPC server. The remaining four follow-ups are scoped tech-debt / UI / contract changes.
|
||||
**Architecture:** Five independent-ish workstreams against the existing ScadaBridge codebase. The headline change: site→central audit forwarding moves from the production `NoOpSiteStreamAuditClient` stub to a real **ClusterClient-based push** — the same transport notifications already use (`SiteCommunicationActor` → `ClusterClient.Send("/user/central-communication", …)` → `CentralCommunicationActor`), avoiding a new central-hosted gRPC server. The remaining four follow-ups are scoped tech-debt / UI / contract changes.
|
||||
|
||||
**Tech Stack:** .NET 10, Akka.NET (ClusterClient, ClusterClientReceptionist, cluster singletons, TestKit), EF Core 10 (MS SQL + SQLite providers), Blazor Server + Bootstrap CSS (no third-party UI libs), System.CommandLine, xUnit + Akka.TestKit.Xunit2 + bUnit + NSubstitute, Playwright.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
- Branch off `main` before any code change; never commit on `main`.
|
||||
- Edit in place. Never touch `infra/*`. The `docker/*` cluster config is touched only if a task explicitly says so (none here do).
|
||||
- Stage with explicit `git add <path>` — never `git add .`, never `git commit -am`.
|
||||
- TDD: failing test → minimal code → green → commit. Full solution stays green (`dotnet build ScadaLink.slnx`, `dotnet test ScadaLink.slnx`).
|
||||
- TDD: failing test → minimal code → green → commit. Full solution stays green (`dotnet build ZB.MOM.WW.ScadaBridge.slnx`, `dotnet test ZB.MOM.WW.ScadaBridge.slnx`).
|
||||
- Additive message-contract evolution where possible; where a contract shape must change (Task 8), update every call site in the same task.
|
||||
- Do not push to origin — the user authorizes pushes separately.
|
||||
|
||||
@@ -32,7 +32,7 @@ git checkout -b feature/audit-log-followups
|
||||
|
||||
**Step 2:** Confirm baseline green:
|
||||
```bash
|
||||
dotnet build ScadaLink.slnx
|
||||
dotnet build ZB.MOM.WW.ScadaBridge.slnx
|
||||
```
|
||||
Expected: build succeeds. (A full `dotnet test` baseline is optional but recommended.)
|
||||
|
||||
@@ -45,10 +45,10 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
**What:** Make the receptionist-registered `CentralCommunicationActor` accept `IngestAuditEventsCommand` (and `IngestCachedTelemetryCommand`) from a site ClusterClient, forward to the `AuditLogIngestActor` cluster-singleton proxy, and pipe the reply back. Mirror the existing `NotificationSubmit` / `RegisterNotificationOutbox` pattern exactly.
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/ScadaLink.Communication/Actors/CentralCommunicationActor.cs` — add `Receive<IngestAuditEventsCommand>` + `Receive<IngestCachedTelemetryCommand>` handlers; add a `RegisterAuditIngest` registration message handler holding the `AuditLogIngestActor` proxy `IActorRef` (mirror `RegisterNotificationOutbox` at line ~120 / `HandleNotificationSubmit` at line ~130).
|
||||
- Create: `src/ScadaLink.Commons/Messages/Audit/RegisterAuditIngest.cs` — `public sealed record RegisterAuditIngest(IActorRef AuditIngestActor);` (mirror `RegisterNotificationOutbox`).
|
||||
- Modify: `src/ScadaLink.Host/Actors/AkkaHostedService.cs` — after the central `AuditLogIngestActor` singleton + proxy are created (~lines 355–379), `Tell` the `RegisterAuditIngest` to the `CentralCommunicationActor` (mirror how the Notification Outbox proxy is registered).
|
||||
- Test: `tests/ScadaLink.Communication.Tests/Actors/CentralCommunicationActorAuditTests.cs` (new).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.Communication/Actors/CentralCommunicationActor.cs` — add `Receive<IngestAuditEventsCommand>` + `Receive<IngestCachedTelemetryCommand>` handlers; add a `RegisterAuditIngest` registration message handler holding the `AuditLogIngestActor` proxy `IActorRef` (mirror `RegisterNotificationOutbox` at line ~120 / `HandleNotificationSubmit` at line ~130).
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Audit/RegisterAuditIngest.cs` — `public sealed record RegisterAuditIngest(IActorRef AuditIngestActor);` (mirror `RegisterNotificationOutbox`).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs` — after the central `AuditLogIngestActor` singleton + proxy are created (~lines 355–379), `Tell` the `RegisterAuditIngest` to the `CentralCommunicationActor` (mirror how the Notification Outbox proxy is registered).
|
||||
- Test: `tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Actors/CentralCommunicationActorAuditTests.cs` (new).
|
||||
|
||||
**Approach:**
|
||||
- Handler `Ask`s the registered audit-ingest proxy and `PipeTo`s the `IngestAuditEventsReply` back to the original `Sender` (the ClusterClient round-trips it to the site). Use the existing audit-ingest Ask-timeout convention (30s — see `SiteStreamGrpcServer` `AuditIngestAskTimeout`); add a bound option if no constant is reachable.
|
||||
@@ -60,7 +60,7 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
2. `IngestAuditEventsCommand` with no audit-ingest registered → sender gets `IngestAuditEventsReply` with empty `AcceptedEventIds`.
|
||||
3. `IngestCachedTelemetryCommand` routes to the same proxy.
|
||||
|
||||
**Steps:** write failing tests → run (fail) → implement record + handlers + Host registration → run (pass) → `dotnet build ScadaLink.slnx` → commit.
|
||||
**Steps:** write failing tests → run (fail) → implement record + handlers + Host registration → run (pass) → `dotnet build ZB.MOM.WW.ScadaBridge.slnx` → commit.
|
||||
|
||||
**Commit:** `feat(communication): route audit ingest commands through CentralCommunicationActor`
|
||||
|
||||
@@ -71,11 +71,11 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
**What:** Replace `NoOpSiteStreamAuditClient` (production binding) with a real `ISiteStreamAuditClient` that pushes over ClusterClient via the site's `SiteCommunicationActor`. After this task the site `auditlog.db` `Pending` backlog drains to central.
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.AuditLog/Site/Telemetry/ClusterClientSiteAuditClient.cs` — implements `ISiteStreamAuditClient`; ctor takes the `SiteCommunicationActor` `IActorRef` + an Ask timeout.
|
||||
- Modify: `src/ScadaLink.Communication/Actors/SiteCommunicationActor.cs` — ensure `IngestAuditEventsCommand` / `IngestCachedTelemetryCommand` are forwarded over `ClusterClient.Send("/user/central-communication", …)` with the reply routed back to the Ask (mirror the `NotificationSubmit` forward at lines ~190/214/224).
|
||||
- Modify: `src/ScadaLink.Host/Actors/AkkaHostedService.cs` — in the site telemetry wiring (~lines 648–681), construct `ClusterClientSiteAuditClient` with the `SiteCommunicationActor` ref and pass it to `SiteAuditTelemetryActor` instead of the DI-resolved `NoOpSiteStreamAuditClient`.
|
||||
- Modify: `src/ScadaLink.AuditLog/ServiceCollectionExtensions.cs` (line ~124–129) — keep `NoOpSiteStreamAuditClient` as the DI default (it remains correct for central/test composition roots that have no `SiteCommunicationActor`); update the stale comment that says "M6's reconciliation work brings the real implementation".
|
||||
- Test: `tests/ScadaLink.AuditLog.Tests/Site/Telemetry/ClusterClientSiteAuditClientTests.cs` (new); extend `tests/ScadaLink.IntegrationTests/AuditLog/` with a ClusterClient-push end-to-end test.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.AuditLog/Site/Telemetry/ClusterClientSiteAuditClient.cs` — implements `ISiteStreamAuditClient`; ctor takes the `SiteCommunicationActor` `IActorRef` + an Ask timeout.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs` — ensure `IngestAuditEventsCommand` / `IngestCachedTelemetryCommand` are forwarded over `ClusterClient.Send("/user/central-communication", …)` with the reply routed back to the Ask (mirror the `NotificationSubmit` forward at lines ~190/214/224).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.Host/Actors/AkkaHostedService.cs` — in the site telemetry wiring (~lines 648–681), construct `ClusterClientSiteAuditClient` with the `SiteCommunicationActor` ref and pass it to `SiteAuditTelemetryActor` instead of the DI-resolved `NoOpSiteStreamAuditClient`.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.AuditLog/ServiceCollectionExtensions.cs` (line ~124–129) — keep `NoOpSiteStreamAuditClient` as the DI default (it remains correct for central/test composition roots that have no `SiteCommunicationActor`); update the stale comment that says "M6's reconciliation work brings the real implementation".
|
||||
- Test: `tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Site/Telemetry/ClusterClientSiteAuditClientTests.cs` (new); extend `tests/ZB.MOM.WW.ScadaBridge.IntegrationTests/AuditLog/` with a ClusterClient-push end-to-end test.
|
||||
|
||||
**Approach:**
|
||||
- `IngestAuditEventsAsync(AuditEventBatch, ct)` maps the batch to `IngestAuditEventsCommand(IReadOnlyList<AuditEvent>)`, `Ask`s the `SiteCommunicationActor` for `IngestAuditEventsReply`, maps the reply's `AcceptedEventIds` back into the `IngestAck` the `SiteAuditTelemetryActor` expects.
|
||||
@@ -95,18 +95,18 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
|
||||
## Task 3: Consolidate the duplicated audit DTO mappers
|
||||
|
||||
**What:** Collapse the 4 near-duplicate `AuditEvent`↔`AuditEventDto` mapping copies into one canonical mapper. The project-reference cycle (`AuditLog → Communication`, never the reverse) is resolved by hosting the canonical mapper **in `ScadaLink.Communication`** — it owns the generated `AuditEventDto` and references `Commons` for `AuditEvent`, and `AuditLog` already references `Communication`.
|
||||
**What:** Collapse the 4 near-duplicate `AuditEvent`↔`AuditEventDto` mapping copies into one canonical mapper. The project-reference cycle (`AuditLog → Communication`, never the reverse) is resolved by hosting the canonical mapper **in `ZB.MOM.WW.ScadaBridge.Communication`** — it owns the generated `AuditEventDto` and references `Commons` for `AuditEvent`, and `AuditLog` already references `Communication`.
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.Communication/Grpc/AuditEventDtoMapper.cs` — `public static class` with `ToDto(AuditEvent) → AuditEventDto` and `FromDto(AuditEventDto) → AuditEvent` (lift the canonical logic from `AuditLog/Telemetry/AuditEventMapper.cs`).
|
||||
- Modify: `src/ScadaLink.Communication/Grpc/SiteStreamGrpcServer.cs` — replace the inlined `IngestAuditEvents` loop (~lines 265–295), `AuditEventToDto` (~490–517) and `MapAuditEventFromDto` (~537–561) with calls to `AuditEventDtoMapper`.
|
||||
- Delete: `src/ScadaLink.AuditLog/Telemetry/AuditEventMapper.cs`; update its callers in `ScadaLink.AuditLog` to use `Communication`'s `AuditEventDtoMapper`.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/AuditEventDtoMapper.cs` — `public static class` with `ToDto(AuditEvent) → AuditEventDto` and `FromDto(AuditEventDto) → AuditEvent` (lift the canonical logic from `AuditLog/Telemetry/AuditEventMapper.cs`).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.Communication/Grpc/SiteStreamGrpcServer.cs` — replace the inlined `IngestAuditEvents` loop (~lines 265–295), `AuditEventToDto` (~490–517) and `MapAuditEventFromDto` (~537–561) with calls to `AuditEventDtoMapper`.
|
||||
- Delete: `src/ZB.MOM.WW.ScadaBridge.AuditLog/Telemetry/AuditEventMapper.cs`; update its callers in `ZB.MOM.WW.ScadaBridge.AuditLog` to use `Communication`'s `AuditEventDtoMapper`.
|
||||
- Leave untouched: `SqliteAuditWriter.MapRow` (SQLite `DataReader` → `AuditEvent`, not a DTO mapper — different source type) and `MapSiteCallFromDto` (SiteCall, not audit). Note this in the commit body.
|
||||
- Test: move/merge `tests/ScadaLink.AuditLog.Tests/Telemetry/AuditEventMapperTests.cs` into `tests/ScadaLink.Communication.Tests/Grpc/AuditEventDtoMapperTests.cs`; keep round-trip coverage (`FromDto(ToDto(x)) == x`).
|
||||
- Test: move/merge `tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Telemetry/AuditEventMapperTests.cs` into `tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/Grpc/AuditEventDtoMapperTests.cs`; keep round-trip coverage (`FromDto(ToDto(x)) == x`).
|
||||
|
||||
**Approach:** Pure refactor — no behaviour change. Verify field-by-field parity against all 3 inlined copies before deleting them (null handling, enum parsing, `Int32Value`/`Timestamp` wrapping).
|
||||
|
||||
**Steps:** create mapper + tests → run → swap call sites → delete old copies → `dotnet build` + `dotnet test ScadaLink.slnx` (all green, no behaviour drift) → commit.
|
||||
**Steps:** create mapper + tests → run → swap call sites → delete old copies → `dotnet build` + `dotnet test ZB.MOM.WW.ScadaBridge.slnx` (all green, no behaviour drift) → commit.
|
||||
|
||||
**Commit:** `refactor(auditlog): consolidate AuditEvent DTO mappers into Communication`
|
||||
|
||||
@@ -117,16 +117,16 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
**What:** Build the missing read-side backend for the Site Calls UI: Commons message contracts, `SiteCallAuditActor` query/KPI/detail handlers, and `CommunicationService` methods. Mirror `NotificationOutboxQueries.cs` + the Notification Outbox actor/service shape. Spec: `Component-SiteCallAudit.md` §KPIs and §queryable list.
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.Commons/Messages/Audit/SiteCallQueries.cs` — records mirroring `NotificationOutboxQueries.cs`:
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Audit/SiteCallQueries.cs` — records mirroring `NotificationOutboxQueries.cs`:
|
||||
- `SiteCallQueryRequest` (CorrelationId, status/site/kind/target filters, date range, page cursor fields, PageSize)
|
||||
- `SiteCallSummary` (TrackedOperationId, SourceSite, Kind, TargetSummary, Status, RetryCount, LastError, provenance, CreatedAtUtc, UpdatedAtUtc, TerminalAtUtc)
|
||||
- `SiteCallQueryResponse` (CorrelationId, Success, ErrorMessage, IReadOnlyList<SiteCallSummary>, next-cursor fields)
|
||||
- `SiteCallKpiRequest` / `SiteCallKpiResponse` (BufferedCount, ParkedCount, FailedLastInterval, DeliveredLastInterval, OldestPendingAge, StuckCount — mirror the Notification Outbox KPI shape; also a per-site variant)
|
||||
- `SiteCallDetailRequest` / `SiteCallDetailResponse` / `SiteCallDetail` (full row incl. LastError, all timestamps).
|
||||
- Modify: `src/ScadaLink.SiteCallAudit/SiteCallAuditActor.cs` — add `ReceiveAsync` handlers for the query / KPI / detail requests; query handler calls `ISiteCallAuditRepository.QueryAsync` (keyset paging on `(CreatedAtUtc DESC, TrackedOperationId DESC)`); KPI handler computes point-in-time counts from the `SiteCalls` table (stuck = `Pending`/`Retrying` older than the configurable threshold, default 10 min). Use the per-message DI scope pattern already in the actor.
|
||||
- Add repo support if needed: `src/ScadaLink.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs` may need a KPI-count method + a detail `GetAsync` (a `GetAsync(TrackedOperationId)` already exists).
|
||||
- Modify: `src/ScadaLink.Communication/CommunicationService.cs` — add `QuerySiteCallsAsync`, `GetSiteCallKpisAsync`, `GetPerSiteSiteCallKpisAsync`, `GetSiteCallDetailAsync` (mirror `QueryNotificationOutboxAsync` etc.: `Ask` the `SiteCallAuditActor` proxy with `_options.QueryTimeout`).
|
||||
- Test: `tests/ScadaLink.SiteCallAudit.Tests/` (actor handlers), `tests/ScadaLink.Commons.Tests/` (contract shape), `tests/ScadaLink.ConfigurationDatabase.Tests/Repositories/SiteCallAuditRepositoryTests.cs` (extend for KPI counts).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/SiteCallAuditActor.cs` — add `ReceiveAsync` handlers for the query / KPI / detail requests; query handler calls `ISiteCallAuditRepository.QueryAsync` (keyset paging on `(CreatedAtUtc DESC, TrackedOperationId DESC)`); KPI handler computes point-in-time counts from the `SiteCalls` table (stuck = `Pending`/`Retrying` older than the configurable threshold, default 10 min). Use the per-message DI scope pattern already in the actor.
|
||||
- Add repo support if needed: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/SiteCallAuditRepository.cs` may need a KPI-count method + a detail `GetAsync` (a `GetAsync(TrackedOperationId)` already exists).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs` — add `QuerySiteCallsAsync`, `GetSiteCallKpisAsync`, `GetPerSiteSiteCallKpisAsync`, `GetSiteCallDetailAsync` (mirror `QueryNotificationOutboxAsync` etc.: `Ask` the `SiteCallAuditActor` proxy with `_options.QueryTimeout`).
|
||||
- Test: `tests/ZB.MOM.WW.ScadaBridge.SiteCallAudit.Tests/` (actor handlers), `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/` (contract shape), `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Repositories/SiteCallAuditRepositoryTests.cs` (extend for KPI counts).
|
||||
|
||||
**Commit:** `feat(sitecallaudit): query, KPI and detail backend for the Site Calls page`
|
||||
|
||||
@@ -137,12 +137,12 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
**What:** Central UI Retry/Discard on a parked Site Call must relay `RetryParkedOperation` / `DiscardParkedOperation` to the **owning site** (sites are the source of truth — central never mutates the `SiteCalls` row directly; the corrected row arrives back via telemetry). Spec: `Component-SiteCallAudit.md` §actions-on-parked-rows.
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.Commons/Messages/Audit/SiteCallRelayMessages.cs` — `RetryParkedOperationRequest`/`Response`, `DiscardParkedOperationRequest`/`Response` (carry `TrackedOperationId`, `SourceSite`, `CorrelationId`; response carries Success + a "site unreachable" error case).
|
||||
- Modify: `src/ScadaLink.SiteCallAudit/SiteCallAuditActor.cs` (or a small relay collaborator) — on a relay request, look up the owning site and forward `RetryParkedOperation`/`DiscardParkedOperation` to that site over the central→site ClusterClient (the central side already maintains one ClusterClient per site; reuse the `CentralCommunicationActor` site-addressing path). On no/late reply → respond "site unreachable".
|
||||
- Modify: `src/ScadaLink.Communication/Actors/SiteCommunicationActor.cs` — receive `RetryParkedOperation`/`DiscardParkedOperation` and hand to the site operation-tracking subsystem.
|
||||
- Modify the site operation-tracking owner (S&F operation-tracking store / `ParkedMessageHandlerActor` in `src/ScadaLink.StoreAndForward/`) — Retry resets a parked tracked operation to `Pending` for the retry loop; Discard marks it `Discarded`. Reuse the parked-message handling that already backs notification Retry/Discard.
|
||||
- Modify: `src/ScadaLink.Communication/CommunicationService.cs` — add `RetrySiteCallAsync` / `DiscardSiteCallAsync`.
|
||||
- Test: `tests/ScadaLink.SiteCallAudit.Tests/` (relay routing + unreachable path), `tests/ScadaLink.StoreAndForward.Tests/` (site-side parked op reset/discard), `tests/ScadaLink.Communication.Tests/`.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Audit/SiteCallRelayMessages.cs` — `RetryParkedOperationRequest`/`Response`, `DiscardParkedOperationRequest`/`Response` (carry `TrackedOperationId`, `SourceSite`, `CorrelationId`; response carries Success + a "site unreachable" error case).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/SiteCallAuditActor.cs` (or a small relay collaborator) — on a relay request, look up the owning site and forward `RetryParkedOperation`/`DiscardParkedOperation` to that site over the central→site ClusterClient (the central side already maintains one ClusterClient per site; reuse the `CentralCommunicationActor` site-addressing path). On no/late reply → respond "site unreachable".
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs` — receive `RetryParkedOperation`/`DiscardParkedOperation` and hand to the site operation-tracking subsystem.
|
||||
- Modify the site operation-tracking owner (S&F operation-tracking store / `ParkedMessageHandlerActor` in `src/ZB.MOM.WW.ScadaBridge.StoreAndForward/`) — Retry resets a parked tracked operation to `Pending` for the retry loop; Discard marks it `Discarded`. Reuse the parked-message handling that already backs notification Retry/Discard.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationService.cs` — add `RetrySiteCallAsync` / `DiscardSiteCallAsync`.
|
||||
- Test: `tests/ZB.MOM.WW.ScadaBridge.SiteCallAudit.Tests/` (relay routing + unreachable path), `tests/ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests/` (site-side parked op reset/discard), `tests/ZB.MOM.WW.ScadaBridge.Communication.Tests/`.
|
||||
|
||||
**Note for implementer:** this is the meatiest backend task — the central→site relay direction and the site-side parked-operation mutation are both required. If the site operation-tracking Retry/Discard primitive already exists for cached calls, reuse it; only add the message plumbing.
|
||||
|
||||
@@ -155,14 +155,14 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
**What:** Build the Central UI Site Calls page — a near-mirror of `NotificationReport.razor`. Spec: `Component-SiteCallAudit.md`.
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor` (+ `.razor.cs`) — route `@page "/site-calls/report"`, `RequireDeployment` (or `OperationalAudit`) auth to match the Notifications report gating. Structure (per the form-layout memory: header, filter card, results table, paging, modal):
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor` (+ `.razor.cs`) — route `@page "/site-calls/report"`, `RequireDeployment` (or `OperationalAudit`) auth to match the Notifications report gating. Structure (per the form-layout memory: header, filter card, results table, paging, modal):
|
||||
- Filter card: Status, Kind, Source site, Target keyword, date range, "Stuck only" checkbox, Clear/Query.
|
||||
- Results table columns: TrackedOperationId, Source site, Kind, Target, Status (badge + Stuck indicator), Retries, Last error, Created, Updated, Actions.
|
||||
- Actions column: a **"View audit history"** link `href="/audit/log?correlationId=@row.TrackedOperationId"` (the `TrackedOperationId` is the audit `CorrelationId`) — mirror `NotificationReport.razor:172`; plus **Retry/Discard** buttons shown only on `Parked` rows (none on `Failed`).
|
||||
- Keyset Previous/Next paging; double-click row → detail modal (body shows full row + LastError; reuse the Notifications detail-modal idiom — never `MarkupString`).
|
||||
- Modify: `src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor` — register the Site Calls page (own "Site Calls" section, or under an existing group, consistent with the `Notifications` / `Audit` section pattern at lines ~65–129).
|
||||
- Modify: `src/ScadaLink.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs` — confirm `?correlationId=` drill-in already covers this (it does); no change expected — just verify.
|
||||
- Test: `tests/ScadaLink.CentralUI.Tests/Pages/` (bUnit — scaffold, paging, parked-only actions, drill-in link), `tests/ScadaLink.CentralUI.PlaywrightTests/SiteCalls/SiteCallsPageTests.cs` (new).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavMenu.razor` — register the Site Calls page (own "Site Calls" section, or under an existing group, consistent with the `Notifications` / `Audit` section pattern at lines ~65–129).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs` — confirm `?correlationId=` drill-in already covers this (it does); no change expected — just verify.
|
||||
- Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/` (bUnit — scaffold, paging, parked-only actions, drill-in link), `tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/SiteCalls/SiteCallsPageTests.cs` (new).
|
||||
|
||||
**Use the `frontend-design` skill** for page/component styling guidance. Blazor Server + Bootstrap only; custom components; clean corporate aesthetic.
|
||||
|
||||
@@ -175,9 +175,9 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
**What:** Surface Site Call Audit KPIs on the Health dashboard, mirroring the Notification Outbox tiles + `AuditKpiTiles`.
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.CentralUI/Components/Health/SiteCallKpiTiles.razor` (+ `.razor.cs`) — mirror `Components/Health/AuditKpiTiles.razor`; tiles for Buffered, Parked (danger border if >0), Stuck (warning border if >0); each tile navigates to `/site-calls/report` with a query-string filter.
|
||||
- Modify: `src/ScadaLink.CentralUI/Components/Pages/Monitoring/Health.razor` (+ code-behind) — add a "Site Calls" KPI section between the Notification Outbox and Audit Log sections; load via `CommunicationService.GetSiteCallKpisAsync` (Task 4).
|
||||
- Test: `tests/ScadaLink.CentralUI.Tests/` (bUnit — tile rendering, threshold borders, navigation targets).
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Health/SiteCallKpiTiles.razor` (+ `.razor.cs`) — mirror `Components/Health/AuditKpiTiles.razor`; tiles for Buffered, Parked (danger border if >0), Stuck (warning border if >0); each tile navigates to `/site-calls/report` with a query-string filter.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Monitoring/Health.razor` (+ code-behind) — add a "Site Calls" KPI section between the Notification Outbox and Audit Log sections; load via `CommunicationService.GetSiteCallKpisAsync` (Task 4).
|
||||
- Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/` (bUnit — tile rendering, threshold borders, navigation targets).
|
||||
|
||||
**Commit:** `feat(centralui): Site Call KPI tiles on the Health dashboard`
|
||||
|
||||
@@ -188,10 +188,10 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
**What:** Widen `AuditLogQueryFilter` from single-value to multi-value on the `Channel`, `Kind`, `Status`, `SourceSiteId` dimensions, and translate them to `IN (...)` in the repository. `Target`, `Actor`, `CorrelationId`, `FromUtc`, `ToUtc` stay as-is. Keyset paging must not change.
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/ScadaLink.Commons/Types/Audit/AuditLogQueryFilter.cs` — change `Channel`/`Kind`/`Status`/`SourceSiteId` to `IReadOnlyList<…>?` (e.g. `IReadOnlyList<AuditChannel>? Channels`). Keep the record's other params. This is a **breaking shape change** — update every call site in this task.
|
||||
- Modify: `src/ScadaLink.ConfigurationDatabase/Repositories/AuditLogRepository.cs` (`QueryAsync`, ~lines 119–165) — each widened dimension becomes `if (filter.Channels is { Count: > 0 }) query = query.Where(e => filter.Channels.Contains(e.Channel));`. Empty/null list = no filter. Keyset predicate + `OrderByDescending` untouched.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Audit/AuditLogQueryFilter.cs` — change `Channel`/`Kind`/`Status`/`SourceSiteId` to `IReadOnlyList<…>?` (e.g. `IReadOnlyList<AuditChannel>? Channels`). Keep the record's other params. This is a **breaking shape change** — update every call site in this task.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/AuditLogRepository.cs` (`QueryAsync`, ~lines 119–165) — each widened dimension becomes `if (filter.Channels is { Count: > 0 }) query = query.Where(e => filter.Channels.Contains(e.Channel));`. Empty/null list = no filter. Keyset predicate + `OrderByDescending` untouched.
|
||||
- Update all other `AuditLogQueryFilter` constructors in this task so the solution compiles (ManagementService `ParseFilter`, CentralUI `AuditQueryModel.ToFilter`, CLI helpers, tests) — the deep behaviour of those consumers is Task 9; here just make them compile (e.g. wrap a single value in a one-element list).
|
||||
- Test: `tests/ScadaLink.ConfigurationDatabase.Tests/Repositories/AuditLogRepositoryTests.cs` — add `QueryAsync_FilterByMultipleChannels_ReturnsUnion`, multi-status, multi-site; keep the existing single-value and keyset tests green.
|
||||
- Test: `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Repositories/AuditLogRepositoryTests.cs` — add `QueryAsync_FilterByMultipleChannels_ReturnsUnion`, multi-status, multi-site; keep the existing single-value and keyset tests green.
|
||||
|
||||
**Commit:** `feat(auditlog): multi-value AuditLogQueryFilter dimensions`
|
||||
|
||||
@@ -202,11 +202,11 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
**What:** Make the three consumers actually emit/accept multiple values per dimension instead of collapsing to the first.
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/ScadaLink.ManagementService/AuditEndpoints.cs` (`ParseFilter`, ~lines 369–414) — read repeated query params with `.ToArray()` (not `.ToString()`); parse each into the enum list; unparseable values silently dropped (keep the existing lax contract).
|
||||
- Modify: `src/ScadaLink.CentralUI/Components/Audit/AuditQueryModel.cs` (`ToFilter`, ~lines 110–126) — stop collapsing to `.First()`; pass the full `Channels`/`Kinds`/`Statuses`/`SiteIdentifiers` sets. Adjust the `ErrorsOnly` logic (lines ~128–145) for multi-value `Status`. The chip UI already supports multi-select — no `.razor` change expected; verify.
|
||||
- Modify: `src/ScadaLink.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs` export-URL builder (~lines 175–227) — emit repeated query-string params per selected value.
|
||||
- Modify: `src/ScadaLink.CLI/Commands/AuditCommands.cs` (~lines 29–41) — make `--channel`/`--kind`/`--status`/`--site` accept multiple values (System.CommandLine multi-arity options; keep `AcceptOnlyFromAmong` for the enum-like ones). Modify `src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs` — `AuditQueryArgs` fields become arrays; `BuildQueryString` emits one key per value.
|
||||
- Test: extend `tests/ScadaLink.ManagementService.Tests/AuditEndpointsTests.cs`, `tests/ScadaLink.CLI.Tests/Commands/AuditQueryCommandTests.cs`, `tests/ScadaLink.CentralUI.Tests/` filter-model tests for multi-value round-trips.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.ManagementService/AuditEndpoints.cs` (`ParseFilter`, ~lines 369–414) — read repeated query params with `.ToArray()` (not `.ToString()`); parse each into the enum list; unparseable values silently dropped (keep the existing lax contract).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/AuditQueryModel.cs` (`ToFilter`, ~lines 110–126) — stop collapsing to `.First()`; pass the full `Channels`/`Kinds`/`Statuses`/`SiteIdentifiers` sets. Adjust the `ErrorsOnly` logic (lines ~128–145) for multi-value `Status`. The chip UI already supports multi-select — no `.razor` change expected; verify.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs` export-URL builder (~lines 175–227) — emit repeated query-string params per selected value.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditCommands.cs` (~lines 29–41) — make `--channel`/`--kind`/`--status`/`--site` accept multiple values (System.CommandLine multi-arity options; keep `AcceptOnlyFromAmong` for the enum-like ones). Modify `src/ZB.MOM.WW.ScadaBridge.CLI/Commands/AuditQueryHelpers.cs` — `AuditQueryArgs` fields become arrays; `BuildQueryString` emits one key per value.
|
||||
- Test: extend `tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/AuditEndpointsTests.cs`, `tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/Commands/AuditQueryCommandTests.cs`, `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/` filter-model tests for multi-value round-trips.
|
||||
|
||||
**Commit:** `feat(audit): multi-value filters across ManagementService, CLI and Central UI`
|
||||
|
||||
@@ -217,11 +217,11 @@ Expected: build succeeds. (A full `dotnet test` baseline is optional but recomme
|
||||
**What:** Add drag-to-resize and drag-to-reorder column UX to `AuditResultsGrid`, persisted in `sessionStorage`. Blazor + Bootstrap + minimal JS interop only (no third-party libs).
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.CentralUI/wwwroot/js/audit-grid.js` — a `window.auditGrid` namespace: column-resize drag handlers, header drag-reorder handlers, and `save(key,json)` / `load(key)` over `sessionStorage` (mirror `treeview-storage.js`).
|
||||
- Modify: `src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor` (+ `.razor.cs`) — render a resize handle in each `<th>`; make headers draggable; apply persisted widths (inline style/CSS var) and column order (the `ColumnOrder` parameter + `OrderedColumns()` already exist — wire it to persisted state); `IJSRuntime` calls to load on first render and save on change.
|
||||
- Create: `src/ScadaLink.CentralUI/Components/Audit/AuditResultsGrid.razor.css` — resize-handle styling, drag-over feedback (mirror `AuditDrilldownDrawer.razor.css` / `TreeView.razor.css` idioms).
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/wwwroot/js/audit-grid.js` — a `window.auditGrid` namespace: column-resize drag handlers, header drag-reorder handlers, and `save(key,json)` / `load(key)` over `sessionStorage` (mirror `treeview-storage.js`).
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/AuditResultsGrid.razor` (+ `.razor.cs`) — render a resize handle in each `<th>`; make headers draggable; apply persisted widths (inline style/CSS var) and column order (the `ColumnOrder` parameter + `OrderedColumns()` already exist — wire it to persisted state); `IJSRuntime` calls to load on first render and save on change.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Audit/AuditResultsGrid.razor.css` — resize-handle styling, drag-over feedback (mirror `AuditDrilldownDrawer.razor.css` / `TreeView.razor.css` idioms).
|
||||
- Reference the script from the host page (`App.razor` / `_Host` / layout — match where `monaco-init.js` / `session-expiry.js` are referenced).
|
||||
- Test: extend `tests/ScadaLink.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs` (or new `AuditGridColumnTests.cs`) — resize changes a column width, reorder changes header order, both survive a reload via `sessionStorage`.
|
||||
- Test: extend `tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Audit/AuditLogPageTests.cs` (or new `AuditGridColumnTests.cs`) — resize changes a column width, reorder changes header order, both survive a reload via `sessionStorage`.
|
||||
|
||||
**Use the `frontend-design` skill** for the resize-handle / drag-feedback visual treatment.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user