Files
apf_portal/apps/portal-admin
Julien Gautier 06ea1feabf
CI / scan (pull_request) Successful in 3m6s
CI / commits (pull_request) Successful in 3m27s
CI / check (pull_request) Successful in 3m41s
CI / a11y (pull_request) Successful in 2m22s
CI / perf (pull_request) Successful in 5m56s
feat(portal-admin): jaeger deep link on trace_id + actor-pivot on actor_id_hash
The audit-log table carries `trace_id` + `actor_id_hash` columns
already populated by the BFF (ADR-0013) but they were plain text —
an investigator could see the values but had to copy them out of the
table to do anything with them. This PR turns both into interactive
affordances:

* **`trace_id` becomes a Jaeger deep link.** Each non-null
  `traceId` renders as `<a target="_blank" rel="noopener
  noreferrer">` pointing at `${jaegerBaseUrl}/trace/<id>`. The
  base URL is per-env in `apps/portal-admin/src/environments/
  environment.ts` (dev: `localhost:16686` matching the compose
  observability profile; prod: the future infra ADR will swap to
  whatever trace backend is settled on — Tempo, Grafana Cloud,
  on-prem Jaeger…). Anonymous events (no traceId) keep the dash
  placeholder. Closes the "join audit + traces by trace_id"
  promise of ADR-0012 / ADR-0013 with the lowest-coupling option
  available today (no new BFF endpoint, no log aggregator
  required).

* **`actor_id_hash` becomes a click-to-filter button.** Each
  non-null hash renders as a `<button>` styled to read inline
  like the hash text but with a hover affordance + focus ring +
  underline. Clicking it sets `actorIdHash.set(hash)`, resets
  the offset to 0, and re-runs the query — pivoting the table on
  that single actor's events. Anonymous rows keep the
  `(anonymous)` plain-text rendering since there's no filter
  value to apply. Each pivot still emits its own
  `admin.audit.query` audit row server-side (ADR-0020) so the
  drill is auditable.

Why not inline-expand Pino logs under the row? Considered, deferred
to a future chantier — the BFF's Pino output goes to stdout today
with no queryable backend; standing up a log aggregator (Loki /
OpenSearch / …) is a separate infra ADR. Jaeger jump-off carries
~99 % of the investigator's needs because the trace already
contains span attributes (db.statement, http.status_code, exception
events) for the same scope.

Implementation:
  * `audit.ts` gains `jaegerUrl(traceId)` (builds the URL with
    encodeURIComponent) and `filterByActor(hash)` (mutates the
    actor filter + offset + re-fetches). Imports
    `environment.jaegerBaseUrl`.
  * `audit.html` swaps the trace cell to an anchor (or dash) and
    the actor cell to a clickable button (or anonymous text),
    each with a `title` attribute for hover hint.
  * `audit.scss` adds `.actor-hash--clickable` (button reset +
    dotted-underline hover) and `.trace-link` (brand-coloured
    underlined link), both with focus rings + dark-mode swaps.

Verification:
  * 54 portal-admin specs pass (was 50; +4 for the four new
    behaviours: trace anchor + dash + actor pivot + anonymous
    plain text).
  * `pnpm nx build portal-admin` clean; lazy `audit` chunk grows
    marginally (4.36 → 4.44 KB gzip) — well under the per-chunk
    budget.
2026-05-16 03:34:58 +02:00
..