fix(audit): chart colours + Charts-tab layout regressions
CI / scan (pull_request) Successful in 4m47s
CI / commits (pull_request) Successful in 4m48s
CI / check (pull_request) Successful in 5m9s
CI / a11y (pull_request) Successful in 4m14s
CI / perf (pull_request) Successful in 9m53s

- Bar chart now uses a single fixed fill (DEFAULT_BAR_FILL = #1d4ed8)
  instead of cycling Set2 across the X axis. Time-bucket bars carry
  no per-day semantics; one colour reads correctly.
- Donut chart accepts an optional colorMap so the audit page can map
  success → green, denied → orange, failure → red. New
  semanticStatusColors export keeps consumers inside the lib's
  curated palette per ADR-0023.
- Chart envelope and audit grid items get min-width: 0 + max-width:
  100% on the inner SVG/figure, fixing the body scrollbar + footer
  gap caused by Plot's fixed-width fallback render on a hidden tab
  panel.
This commit is contained in:
Julien Gautier
2026-05-17 01:16:37 +02:00
parent 9f5106b805
commit 56760bd6ba
10 changed files with 201 additions and 17 deletions
@@ -181,8 +181,9 @@
categoryKey="outcome"
valueKey="count"
[centerLabel]="s.total.toString()"
[colorMap]="outcomeColorMap"
caption="Outcome breakdown"
description="Donut chart of audit event outcomes (success, failure, denied) across the full filtered set."
description="Donut chart of audit event outcomes (success in green, denied in orange, failure in red) across the full filtered set."
ariaLabel="Outcome breakdown — donut chart"
/>
</div>
@@ -307,6 +307,14 @@
}
}
// Grid items default to `min-width: auto`, which prevents the column
// from shrinking below the intrinsic width of its content. Without
// the override, Plot's fixed-width SVG could push the column wider
// than 1fr, busting the layout and forcing a horizontal scrollbar.
.chart-tile {
min-width: 0;
}
// The stacked-bar chart carries a legend and benefits from the
// full width; flag it via a modifier and span both columns.
.chart-tile--wide {
+15 -1
View File
@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { firstValueFrom } from 'rxjs';
import { BarChart, DonutChart, StackedBarChart } from 'shared-charts';
import { BarChart, DonutChart, StackedBarChart, semanticStatusColors } from 'shared-charts';
import { environment } from '../../../environments/environment';
import {
AuditEventsService,
@@ -47,6 +47,20 @@ export class AuditPage {
protected readonly pageSizes = PAGE_SIZES;
/**
* Outcome → fill mapping for the donut chart. Semantic colours
* resolved from the shared-charts curated set: success = green,
* denied = orange (`warning` intent), failure = red. Keeps the
* legend "obvious without reading" — admins eyeballing the donut
* see the green slice and know it's success without parsing the
* tooltip.
*/
protected readonly outcomeColorMap: Readonly<Record<string, string>> = {
success: semanticStatusColors.success,
failure: semanticStatusColors.error,
denied: semanticStatusColors.warning,
};
// Filter form state. Each field is a separate signal so a change
// to one doesn't churn the others through ngModel's reference
// equality. All start empty — the BFF returns the most recent