mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-20 02:42:11 +00:00
Catch up frontend tests with collapsed Patrol details and refactored FindingsPanel
This commit is contained in:
@@ -65,6 +65,7 @@ import {
|
||||
getFindingSubjectPresentation,
|
||||
getFindingTitlePresentation,
|
||||
getFindingRecencyPresentation,
|
||||
findingHasAppliedFix,
|
||||
hasFindingInvestigationDetails,
|
||||
hasFindingInvestigationHandoffPointer,
|
||||
getInvestigationConfidenceBadgeClasses,
|
||||
@@ -632,23 +633,9 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
await openFindingInAssistant(finding, 'verify_fix');
|
||||
};
|
||||
|
||||
// True when the finding has an investigation outcome indicating that
|
||||
// some remediation step has run against it — anything past "fix
|
||||
// queued." For these states, Verify fix is a meaningful action; for
|
||||
// fix_queued (still awaiting approval) and earlier states there is
|
||||
// nothing applied yet to verify, and for fix_failed the fix didn't
|
||||
// complete so verification doesn't apply.
|
||||
const hasAppliedFix = (finding: UnifiedFinding): boolean => {
|
||||
switch (finding.investigationOutcome) {
|
||||
case 'fix_executed':
|
||||
case 'fix_verified':
|
||||
case 'fix_verification_failed':
|
||||
case 'fix_verification_unknown':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// hasAppliedFix logic lives in @/utils/aiFindingPresentation as
|
||||
// findingHasAppliedFix so the investigation-outcome switch stays in a
|
||||
// shared adapter (per the resource-type-boundaries test).
|
||||
|
||||
// Create rule from this is the promotion path: take the operator's
|
||||
// implicit pattern (silencing the same {resource, category} pair
|
||||
@@ -1333,7 +1320,7 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
</svg>
|
||||
Why
|
||||
</button>
|
||||
<Show when={hasAppliedFix(finding) && finding.status === 'active'}>
|
||||
<Show when={findingHasAppliedFix(finding) && finding.status === 'active'}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleVerifyFixFinding(finding, e)}
|
||||
@@ -1428,15 +1415,17 @@ export const FindingsPanel: Component<FindingsPanelProps> = (props) => {
|
||||
Acknowledge
|
||||
</button>
|
||||
</Show>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleResolve(finding, e)}
|
||||
class="px-2 py-1 rounded border border-emerald-200 text-emerald-700 hover:bg-emerald-50 dark:border-emerald-800 dark:text-emerald-300 dark:hover:bg-emerald-900"
|
||||
title="Mark this finding as resolved. Use when you have fixed the issue out-of-band."
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Mark resolved
|
||||
</button>
|
||||
<Show when={manualControls.acknowledge && finding.status === 'active'}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleResolve(finding, e)}
|
||||
class="px-2 py-1 rounded border border-emerald-200 text-emerald-700 hover:bg-emerald-50 dark:border-emerald-800 dark:text-emerald-300 dark:hover:bg-emerald-900"
|
||||
title="Mark this finding as resolved. Use when you have fixed the issue out-of-band."
|
||||
disabled={actionLoading() === finding.id}
|
||||
>
|
||||
Mark resolved
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={manualControls.snooze}>
|
||||
|
||||
@@ -51,9 +51,12 @@ describe('FindingsPanel assistant handoff', () => {
|
||||
expect(findingsPanelSource).toContain('buildPatrolAssistantFindingHandoff');
|
||||
expect(findingsPanelSource).toContain('buildPatrolAssistantApprovalBriefingInput');
|
||||
expect(findingsPanelSource).toContain('buildPatrolAssistantProposedFixBriefingInput');
|
||||
expect(findingsPanelSource).toContain(
|
||||
'aiChatStore.openWithPrompt(handoff.prompt, handoff.context)',
|
||||
);
|
||||
// openWithPrompt receives the handoff context spread inline (so the call
|
||||
// site can layer autoSendInitialPrompt for verb-style entrypoints like
|
||||
// Investigate / Why / Verify fix). The base handoff.context must still
|
||||
// flow through.
|
||||
expect(findingsPanelSource).toContain('aiChatStore.openWithPrompt(handoff.prompt, {');
|
||||
expect(findingsPanelSource).toContain('...handoff.context');
|
||||
expect(findingsPanelSource).toContain('investigationOutcome: finding.investigationOutcome');
|
||||
expect(findingsPanelSource).toContain('investigationStatus: finding.investigationStatus');
|
||||
expect(findingsPanelSource).toContain('remediationId: finding.remediationPlanId');
|
||||
|
||||
@@ -1154,9 +1154,17 @@ describe('AIIntelligence entitlement gating', () => {
|
||||
expect(screen.getByTestId('patrol-recommended-next-step-action')).toHaveTextContent(
|
||||
'Run Patrol',
|
||||
);
|
||||
expect(findingsPanelState.latestProps).not.toBeNull();
|
||||
});
|
||||
|
||||
// Patrol summary verification + activity-mix copy live behind the
|
||||
// "Show details" toggle since commit 174d2b04d. Expand the section so
|
||||
// the deeper assertions can find the text.
|
||||
fireEvent.click(screen.getByTestId('patrol-summary-details-toggle'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No recent full patrol')).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/Last activity/i)).toHaveLength(1);
|
||||
expect(findingsPanelState.latestProps).not.toBeNull();
|
||||
});
|
||||
|
||||
expect(screen.queryByText('No issues found')).not.toBeInTheDocument();
|
||||
@@ -1328,6 +1336,13 @@ describe('AIIntelligence entitlement gating', () => {
|
||||
'Patrol has an active runtime issue: Provider billing or quota issue. Recent coverage is also incomplete, so the rest of your infrastructure is not fully verified.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// "Runtime issues" and "Latest activity" both live in the collapsible
|
||||
// Patrol summary detail section (commit 174d2b04d).
|
||||
fireEvent.click(screen.getByTestId('patrol-summary-details-toggle'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Runtime issues')).toBeInTheDocument();
|
||||
expect(screen.getByText('Latest activity')).toBeInTheDocument();
|
||||
});
|
||||
@@ -1486,6 +1501,13 @@ describe('AIIntelligence entitlement gating', () => {
|
||||
|
||||
render(() => <AIIntelligence />);
|
||||
|
||||
// "Recent activity mix" copy lives in the collapsible Patrol summary
|
||||
// detail section (commit 174d2b04d). Expand before asserting.
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('patrol-summary-details-toggle')).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.click(screen.getByTestId('patrol-summary-details-toggle'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText('Recent activity mix: 1 full, 1 alert-triggered, 1 anomaly-triggered'),
|
||||
|
||||
@@ -617,6 +617,25 @@ export const doesFindingNeedAttention = (
|
||||
);
|
||||
};
|
||||
|
||||
// True when the finding has an investigation outcome indicating that some
|
||||
// remediation step has run against it — anything past "fix queued." For these
|
||||
// states, Verify fix is a meaningful action; for fix_queued (still awaiting
|
||||
// approval) and earlier states there is nothing applied yet to verify, and
|
||||
// for fix_failed the fix didn't complete so verification doesn't apply.
|
||||
export const findingHasAppliedFix = (
|
||||
finding: Pick<UnifiedFinding, 'investigationOutcome'>,
|
||||
): boolean => {
|
||||
switch (finding.investigationOutcome) {
|
||||
case 'fix_executed':
|
||||
case 'fix_verified':
|
||||
case 'fix_verification_failed':
|
||||
case 'fix_verification_unknown':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const getFindingLoopStateBadgeClasses = (loopState: string): string =>
|
||||
FINDING_LOOP_STATE_CLASSES[loopState] || DEFAULT_LOOP_STATE_CLASSES;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user