fix(alerts/ui): bell position, content tabs hidden, filters, novice labels

Surfaced during second smoke:

1. Notification bell moved — was first child of TopBar (left of
   breadcrumb); now rendered inside the `environment` slot so it
   sits between the env selector and the user menu, matching user
   expectations.

2. Content tabs (Exchanges/Dashboard/Runtime/Deployments) hidden on
   `/alerts/*` — the operational tabs don't apply there.

3. Inbox / All alerts filters now actually filter. `AlertController.list`
   accepts only `limit` — `state`/`severity` query params are dropped
   server-side. Move `useAlerts` to fetch once per env (limit 200) and
   apply filters client-side via react-query `select`, with a stable
   queryKey so filter toggles are instant and don't re-request. True
   server-side filter needs a backend change (follow-up).

4. Novice-friendly labels:
   - Inbox subtitle: "99 firing · 100 total" → "99 need attention ·
     100 total in inbox"
   - All alerts filter: Open/Firing/Acked/All →
     "Currently open"/"Firing now"/"Acknowledged"/"All states"
   - All alerts subtitle: "N shown" → "N matching your filter"
   - History subtitle: "N resolved" → "N resolved alert(s) in range"
   - Rules subtitle: "N total" → "N rule(s) configured"
   - Silences subtitle: "N active" → "N active silence(s)" or
     "Nothing silenced right now"
   - Column headers: "State" → "Status", rules "Kind" → "Type",
     rules "Targets" → "Notifies"
   - Buttons: "Ack" → "Acknowledge", silence "End" → "End early"

Updated alerts.test.tsx and e2e selector to match new behavior/labels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-21 11:48:33 +02:00
parent 05f420d162
commit c443fc606a
9 changed files with 87 additions and 39 deletions

View File

@@ -22,7 +22,10 @@ describe('useAlerts', () => {
useEnvironmentStore.setState({ environment: 'dev' });
});
it('fetches alerts for selected env and passes filter query params', async () => {
it('fetches up to 200 alerts for selected env (no server-side filter params)', async () => {
// Backend AlertController.list accepts only `limit`; state/severity are
// dropped server-side. We therefore fetch once per env and filter
// client-side via react-query `select`.
(apiClient.GET as any).mockResolvedValue({ data: [], error: null });
const { result } = renderHook(
() => useAlerts({ state: 'FIRING', severity: ['CRITICAL', 'WARNING'] }),
@@ -34,16 +37,29 @@ describe('useAlerts', () => {
expect.objectContaining({
params: expect.objectContaining({
path: { envSlug: 'dev' },
query: expect.objectContaining({
state: ['FIRING'],
severity: ['CRITICAL', 'WARNING'],
limit: 100,
}),
query: { limit: 200 },
}),
}),
);
});
it('applies state + severity filters client-side via select', async () => {
const dataset = [
{ id: '1', state: 'FIRING', severity: 'CRITICAL', title: 'a' },
{ id: '2', state: 'FIRING', severity: 'WARNING', title: 'b' },
{ id: '3', state: 'ACKNOWLEDGED', severity: 'CRITICAL', title: 'c' },
{ id: '4', state: 'RESOLVED', severity: 'INFO', title: 'd' },
];
(apiClient.GET as any).mockResolvedValue({ data: dataset, error: null });
const { result } = renderHook(
() => useAlerts({ state: ['FIRING'], severity: ['CRITICAL', 'WARNING'] }),
{ wrapper },
);
await waitFor(() => expect(result.current.isSuccess).toBe(true));
const ids = (result.current.data ?? []).map((a: any) => a.id);
expect(ids).toEqual(['1', '2']);
});
it('does not fetch when no env is selected', () => {
useEnvironmentStore.setState({ environment: undefined });
const { result } = renderHook(() => useAlerts(), { wrapper });