mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-31 06:02:22 +00:00
feat(compose-doctor): show All Clear when findings are acknowledged (#1721)
When every finding is acknowledged, the Doctor summary uses the success All Clear banner with distinct copy instead of the muted acknowledged chip, so operators can see at a glance that no active findings remain.
This commit is contained in:
@@ -58,9 +58,9 @@ The summary card at the top of the Doctor tab reflects the overall outcome of th
|
||||
| **High risk** | Highest active finding is high risk | Amber card with a count of active findings by severity |
|
||||
| **Warning** | Highest active finding is a warning | Blue card with a count of active findings by severity |
|
||||
| **Info** | All active findings are informational | Muted card with a count of active findings by severity |
|
||||
| **Acknowledged** | Every finding from the last run has been acknowledged | Muted card noting all findings are acknowledged |
|
||||
| **All clear · findings acknowledged** | Model rendered; every finding from the last run has been acknowledged | Green card with "No active findings remain. One or more detected issues were reviewed and acknowledged by an authorized operator." |
|
||||
|
||||
The summary card also shows when the run happened and who triggered it: "ran 5 minutes ago by admin". If any findings are acknowledged, the line adds a count: "3 active (1 blocker · 2 warning) · 2 acknowledged".
|
||||
The summary card also shows when the run happened and who triggered it: "ran 5 minutes ago by admin". When active findings remain alongside acknowledgements, the line adds a count: "3 active (1 blocker · 2 warning) · 2 acknowledged".
|
||||
|
||||
A small colored dot appears on the **Doctor** tab label when the last run's active findings include a blocker (red) or a high-risk finding (amber). Click the **X** on the summary card to dismiss it; this also clears the tab dot. The dismissal is remembered per stack and node, and clears itself automatically the moment the finding set changes (a rule clearing, a new rule firing, or a severity change), so a stale dismissal can never hide a genuinely new problem. It never clears just by opening the tab or re-running preflight to the same result, and it does not sync across browsers or teammates: it is stored in your browser only.
|
||||
|
||||
|
||||
@@ -81,7 +81,65 @@ describe('PreflightPanel', () => {
|
||||
render(<PreflightPanel stackName="web" />);
|
||||
const status = await screen.findByTestId('preflight-status');
|
||||
expect(status).toHaveAttribute('data-status', 'pass');
|
||||
expect(status).toHaveTextContent(/all clear/i);
|
||||
expect(status).toHaveTextContent('all clear');
|
||||
expect(status).not.toHaveTextContent(/findings acknowledged/i);
|
||||
expect(status).toHaveTextContent('No issues found in the effective model.');
|
||||
});
|
||||
|
||||
it('renders all-clear · findings acknowledged when every finding is acknowledged', async () => {
|
||||
vi.mocked(apiFetch).mockResolvedValue(jsonRes(report({
|
||||
status: 'pass',
|
||||
acknowledgedCount: 1,
|
||||
findings: [{
|
||||
ruleId: 'privileged',
|
||||
severity: 'high',
|
||||
title: 'Privileged container',
|
||||
message: 'runs privileged',
|
||||
service: 'web',
|
||||
acknowledged: true,
|
||||
}],
|
||||
})));
|
||||
render(<PreflightPanel stackName="web" />);
|
||||
const status = await screen.findByTestId('preflight-status');
|
||||
expect(status).toHaveAttribute('data-status', 'pass');
|
||||
expect(status).toHaveTextContent('all clear · findings acknowledged');
|
||||
expect(status).toHaveTextContent(
|
||||
'No active findings remain. One or more detected issues were reviewed and acknowledged by an authorized operator.',
|
||||
);
|
||||
expect(status.className).toContain('border-success/40');
|
||||
expect(status.className).toContain('bg-success/[0.06]');
|
||||
expect(status.className).toContain('text-success');
|
||||
expect(status.querySelector('svg.lucide-check')).not.toBeNull();
|
||||
expect(status.querySelector('svg.lucide-shield-check')).toBeNull();
|
||||
expect(screen.getByTestId('preflight-acknowledged-section')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps the severity summary when active findings remain alongside acknowledgements', async () => {
|
||||
vi.mocked(apiFetch).mockResolvedValue(jsonRes(report({
|
||||
status: 'high',
|
||||
highestSeverity: 'high',
|
||||
acknowledgedCount: 1,
|
||||
findings: [
|
||||
{ ruleId: 'privileged', severity: 'high', title: 'Privileged container', message: 'runs privileged', service: 'web' },
|
||||
{
|
||||
ruleId: 'image-latest',
|
||||
severity: 'warning',
|
||||
title: 'Image uses a moving tag',
|
||||
message: 'latest tag',
|
||||
service: 'web',
|
||||
acknowledged: true,
|
||||
},
|
||||
],
|
||||
})));
|
||||
render(<PreflightPanel stackName="web" />);
|
||||
const status = await screen.findByTestId('preflight-status');
|
||||
expect(status).toHaveAttribute('data-status', 'high');
|
||||
expect(status).toHaveTextContent('high risk');
|
||||
expect(status).not.toHaveTextContent(/all clear/i);
|
||||
expect(status).toHaveTextContent('1 active');
|
||||
expect(status).toHaveTextContent('1 acknowledged');
|
||||
expect(screen.getByText('Privileged container')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('preflight-acknowledged-section')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps All Clear when only inherited-healthcheck notes remain', async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Check, TriangleAlert, ShieldAlert, Info, RefreshCw, Stethoscope, X,
|
||||
ChevronDown, ChevronRight, ShieldCheck, type LucideIcon,
|
||||
ChevronDown, ChevronRight, type LucideIcon,
|
||||
} from 'lucide-react';
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -83,8 +83,16 @@ function summaryMeta(
|
||||
line: report.renderError ?? 'Sencho could not render the effective Compose model.',
|
||||
};
|
||||
}
|
||||
if (report.activeCount === 0 && report.acknowledgedCount === 0) {
|
||||
return { label: 'all clear', icon: Check, tone: 'border-success/40 bg-success/[0.06] text-success', line: 'No issues found in the effective model.' };
|
||||
if (report.activeCount === 0) {
|
||||
const acknowledgedOnly = report.acknowledgedCount > 0;
|
||||
return {
|
||||
label: acknowledgedOnly ? 'all clear · findings acknowledged' : 'all clear',
|
||||
icon: Check,
|
||||
tone: 'border-success/40 bg-success/[0.06] text-success',
|
||||
line: acknowledgedOnly
|
||||
? 'No active findings remain. One or more detected issues were reviewed and acknowledged by an authorized operator.'
|
||||
: 'No issues found in the effective model.',
|
||||
};
|
||||
}
|
||||
const meta = SEVERITY_META[report.activeHighestSeverity ?? 'info'];
|
||||
const activeParts = GROUP_ORDER
|
||||
@@ -95,7 +103,7 @@ function summaryMeta(
|
||||
const line = report.acknowledgedCount > 0
|
||||
? `${report.activeCount} active${activeParts ? ` (${activeParts})` : ''} · ${report.acknowledgedCount} acknowledged`
|
||||
: (activeParts || `${report.activeCount} active`);
|
||||
return { label: report.activeCount === 0 ? 'acknowledged' : meta.label, icon: report.activeCount === 0 ? ShieldCheck : meta.icon, tone: report.activeCount === 0 ? 'border-muted bg-card/40 text-stat-subtitle' : meta.tone, line };
|
||||
return { label: meta.label, icon: meta.icon, tone: meta.tone, line };
|
||||
}
|
||||
|
||||
function expiryComboboxOptions(finding: PreflightFinding): ComboboxOption[] {
|
||||
|
||||
Reference in New Issue
Block a user