mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 22:12:23 +00:00
Clarify Patrol run-history snapshot affordance
This commit is contained in:
@@ -296,6 +296,11 @@ That same rule applies to run-status badges. A legacy run without findings
|
||||
snapshot ids must not keep a green `healthy` badge when the surrounding UI is
|
||||
saying findings verification is unavailable; the canonical run-status
|
||||
presentation should downgrade that state to a neutral `completed` badge.
|
||||
That same truthfulness rule applies to the run-history shell copy. The `Recent
|
||||
patrol runs` helper text must not promise that every visible run can filter
|
||||
findings to a concrete snapshot; when visible runs include legacy entries
|
||||
without `finding_ids`, or when the selected run itself predates findings
|
||||
snapshots, the shell should say so explicitly.
|
||||
That same findings surface should keep its section chrome functional rather
|
||||
than promotional. Inside the Patrol findings tab, the selected tab already
|
||||
names the surface, so the findings card should not add another in-card product
|
||||
|
||||
@@ -2,7 +2,10 @@ import { For, Show } from 'solid-js';
|
||||
import type { Accessor } from 'solid-js';
|
||||
import type { PatrolRunRecord } from '@/api/patrol';
|
||||
import { getRunHistoryEmptyState } from '@/utils/patrolEmptyStatePresentation';
|
||||
import { getRunHistoryLoadingState } from '@/utils/patrolRunPresentation';
|
||||
import {
|
||||
getRunHistoryLoadingState,
|
||||
getRunHistorySelectionHint,
|
||||
} from '@/utils/patrolRunPresentation';
|
||||
import { RunHistoryEntry } from './RunHistoryEntry';
|
||||
|
||||
import RefreshCwIcon from 'lucide-solid/icons/refresh-cw';
|
||||
@@ -35,7 +38,9 @@ export function RunHistoryPanel(props: RunHistoryPanelProps) {
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold text-base-content">Recent patrol runs</h2>
|
||||
<p class="text-xs text-muted">Select a run to filter findings to that snapshot</p>
|
||||
<p class="text-xs text-muted">
|
||||
{getRunHistorySelectionHint(props.runs, props.selectedRun)}
|
||||
</p>
|
||||
</div>
|
||||
<Show when={props.selectedRun}>
|
||||
<button
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { cleanup, render, screen } from '@solidjs/testing-library';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { PatrolRunRecord } from '@/api/patrol';
|
||||
import { RunHistoryPanel } from '../RunHistoryPanel';
|
||||
|
||||
vi.mock('../RunHistoryEntry', () => ({
|
||||
RunHistoryEntry: () => <div data-testid="run-history-entry" />,
|
||||
}));
|
||||
|
||||
describe('RunHistoryPanel', () => {
|
||||
const patrolStream = {
|
||||
phase: () => '',
|
||||
currentTool: () => '',
|
||||
tokens: () => 0,
|
||||
resynced: () => false,
|
||||
resyncReason: () => '',
|
||||
bufferStartSeq: () => 0,
|
||||
bufferEndSeq: () => 0,
|
||||
outputTruncated: () => false,
|
||||
reconnectCount: () => 0,
|
||||
isStreaming: () => false,
|
||||
errorMessage: () => '',
|
||||
};
|
||||
|
||||
const baseRun: PatrolRunRecord = {
|
||||
id: 'run-1',
|
||||
started_at: '2026-03-12T10:00:00Z',
|
||||
completed_at: '2026-03-12T10:01:00Z',
|
||||
duration_ms: 60000,
|
||||
type: 'patrol',
|
||||
trigger_reason: 'scheduled',
|
||||
scope_resource_ids: [],
|
||||
effective_scope_resource_ids: [],
|
||||
scope_resource_types: [],
|
||||
resources_checked: 58,
|
||||
nodes_checked: 0,
|
||||
guests_checked: 0,
|
||||
docker_checked: 0,
|
||||
storage_checked: 0,
|
||||
hosts_checked: 0,
|
||||
pbs_checked: 0,
|
||||
pmg_checked: 0,
|
||||
kubernetes_checked: 0,
|
||||
new_findings: 0,
|
||||
existing_findings: 0,
|
||||
rejected_findings: 0,
|
||||
resolved_findings: 0,
|
||||
auto_fix_count: 0,
|
||||
findings_summary: 'All clear',
|
||||
finding_ids: [],
|
||||
error_count: 0,
|
||||
status: 'healthy',
|
||||
triage_flags: 0,
|
||||
tool_call_count: 0,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('warns that some older runs do not include findings snapshots', () => {
|
||||
render(() => (
|
||||
<RunHistoryPanel
|
||||
runs={[baseRun, { ...baseRun, id: 'run-legacy', finding_ids: undefined }]}
|
||||
loading={false}
|
||||
selectedRun={null}
|
||||
onSelectRun={vi.fn()}
|
||||
patrolStream={patrolStream}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Select a run to filter findings when available. Some older runs do not include findings snapshots.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('explains when the selected run predates findings snapshots', () => {
|
||||
const legacyRun = { ...baseRun, id: 'run-legacy', finding_ids: undefined };
|
||||
|
||||
render(() => (
|
||||
<RunHistoryPanel
|
||||
runs={[legacyRun]}
|
||||
loading={false}
|
||||
selectedRun={legacyRun}
|
||||
onSelectRun={vi.fn()}
|
||||
patrolStream={patrolStream}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Selected run predates findings snapshots; run-scoped findings cannot be fully verified.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
getPatrolRunStatusPresentation,
|
||||
isPatrolRunHealthy,
|
||||
getRunHistoryLoadingState,
|
||||
getRunHistorySelectionHint,
|
||||
getToolCallsLoadingState,
|
||||
getToolCallsUnavailableState,
|
||||
getToolCallResultBadgeClass,
|
||||
@@ -115,4 +116,23 @@ describe('patrolRunPresentation', () => {
|
||||
expect(getToolCallsLoadingState()).toBe('Loading tool calls...');
|
||||
expect(getToolCallsUnavailableState()).toBe('Tool call details not available for this run.');
|
||||
});
|
||||
|
||||
it('warns when visible runs include legacy findings snapshots', () => {
|
||||
expect(
|
||||
getRunHistorySelectionHint([
|
||||
{ finding_ids: [] },
|
||||
{ finding_ids: undefined },
|
||||
]),
|
||||
).toBe(
|
||||
'Select a run to filter findings when available. Some older runs do not include findings snapshots.',
|
||||
);
|
||||
});
|
||||
|
||||
it('explains selected legacy runs in the run-history shell', () => {
|
||||
expect(
|
||||
getRunHistorySelectionHint([{ finding_ids: [] }], { finding_ids: undefined }),
|
||||
).toBe(
|
||||
'Selected run predates findings snapshots; run-scoped findings cannot be fully verified.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -132,6 +132,21 @@ export function getRunHistoryLoadingState(): string {
|
||||
return 'Loading run history…';
|
||||
}
|
||||
|
||||
export function getRunHistorySelectionHint(
|
||||
runs: Array<Pick<PatrolRunRecord, 'finding_ids'>>,
|
||||
selectedRun?: Pick<PatrolRunRecord, 'finding_ids'> | null,
|
||||
): string {
|
||||
if (selectedRun && selectedRun.finding_ids === undefined) {
|
||||
return 'Selected run predates findings snapshots; run-scoped findings cannot be fully verified.';
|
||||
}
|
||||
|
||||
if (runs.some((run) => run.finding_ids === undefined)) {
|
||||
return 'Select a run to filter findings when available. Some older runs do not include findings snapshots.';
|
||||
}
|
||||
|
||||
return 'Select a run to filter findings to that snapshot';
|
||||
}
|
||||
|
||||
export function getToolCallsLoadingState(): string {
|
||||
return 'Loading tool calls...';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user