mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 08:33:03 +00:00
fix(frontend): surface the refusal reason when a container update plan fails
A refused update plan showed only 'Action execution is unavailable'; the actionable explanation (agent disconnected, agent too old, stale inventory) arrives in the API error's details.reason and was dropped. Prefer it in the failure badge and tooltip. Seen in #1564 where the toast gave the reporter nothing to act on and Actions showed no record (refusal is pre-audit).
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
ContainerUpdateBadge,
|
||||
UpdateButton,
|
||||
} from '@/components/shared/ContainerUpdateBadge';
|
||||
import { getUpdatePlanErrorMessage } from '@/components/shared/containerUpdateBadgeModel';
|
||||
|
||||
vi.mock('@/api/monitoring', () => ({
|
||||
MonitoringAPI: {
|
||||
@@ -123,3 +124,29 @@ describe('ContainerUpdateBadge', () => {
|
||||
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUpdatePlanErrorMessage', () => {
|
||||
it('prefers the availability refusal reason over the generic message', () => {
|
||||
const error = Object.assign(new Error('Action execution is unavailable'), {
|
||||
details: {
|
||||
reasonCode: 'operation_receipt_unsupported',
|
||||
reason: 'The Pulse agent on this host is still on an older version.',
|
||||
},
|
||||
});
|
||||
|
||||
expect(getUpdatePlanErrorMessage(error)).toBe(
|
||||
'The Pulse agent on this host is still on an older version.',
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to the error message when no reason detail exists', () => {
|
||||
expect(getUpdatePlanErrorMessage(new Error('Pulse refused the update plan.'))).toBe(
|
||||
'Pulse refused the update plan.',
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to a default when the error is empty', () => {
|
||||
expect(getUpdatePlanErrorMessage(new Error(''))).toBe('Failed to plan the update');
|
||||
expect(getUpdatePlanErrorMessage(null)).toBe('Failed to plan the update');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -146,3 +146,17 @@ export function getUpdateButtonTooltip(options: {
|
||||
return `Click to update\nCurrent: ${current}...\nLatest: ${latest}...`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the user-facing message for a failed update plan request.
|
||||
*
|
||||
* Availability refusals carry the actionable explanation (agent disconnected,
|
||||
* agent too old, stale inventory) in the API error's details.reason; the
|
||||
* top-level message is just "Action execution is unavailable".
|
||||
*/
|
||||
export function getUpdatePlanErrorMessage(error: unknown): string {
|
||||
const apiError = error as (Error & { details?: Record<string, string> }) | null;
|
||||
return (
|
||||
apiError?.details?.reason?.trim() || apiError?.message?.trim() || 'Failed to plan the update'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { ActionDetailResponse } from '@/types/actionAudit';
|
||||
import {
|
||||
getUpdateButtonLabel,
|
||||
getUpdateButtonTooltip,
|
||||
getUpdatePlanErrorMessage,
|
||||
hasContainerUpdate,
|
||||
hasContainerUpdateCurrent,
|
||||
hasContainerUpdateError,
|
||||
@@ -113,7 +114,7 @@ export function useContainerUpdateButtonState(props: UpdateButtonProps) {
|
||||
setReviewDetail(await ResourceActionsAPI.getAction(plan.actionId));
|
||||
setLocalState('idle');
|
||||
} catch (error) {
|
||||
const message = (error as Error).message || 'Failed to plan the update';
|
||||
const message = getUpdatePlanErrorMessage(error);
|
||||
setErrorMessage(message);
|
||||
setLocalState('idle');
|
||||
markContainerUpdateError(props.agentId, props.containerId, message);
|
||||
|
||||
Reference in New Issue
Block a user