feat(scheduler): add helper text and risk badges to scheduled action picker (#1449)

* feat(scheduler): add helper text and risk badges to scheduled action picker

Add a concise helper text and risk level badge to every scheduled action
in the create/edit modal. The six risk levels (Safe, Read-only, Interruptive,
Runtime change, Removes containers, Destructive) map to the four existing
design-system tones and render as a small dot+label chip next to the helper
text, following the same pattern as SeverityBadge.

Fix an ambiguous mobile label: update + target_type: fleet now resolves
through resolveTaskAction and renders 'update node stacks' instead of the
misleading 'update fleet'.

Add exact helper-text and risk-level assertions for all 10 actions, plus
component tests for default modal state, action-switch scenarios, and
mobile update+fleet rendering.

* docs: update stale scheduled-operations alt text for changed helper text
This commit is contained in:
Anso
2026-06-25 02:37:12 -04:00
committed by GitHub
parent 79f840ab6e
commit 7982251dc6
7 changed files with 240 additions and 27 deletions
@@ -10,8 +10,13 @@ import {
DEFAULT_SCHEDULED_ACTION_ID,
getActionById,
resolveTaskAction,
RISK_LABEL,
RISK_TONE,
RISK_BADGE_CLASSES,
RISK_DOT_CLASSES,
type BackendAction,
type ScheduledActionCategory,
type ScheduledActionRiskLevel,
} from '../scheduledActions';
const BACKEND_ACTIONS: BackendAction[] = [
@@ -93,4 +98,79 @@ describe('scheduledActions registry', () => {
expect(fleetUpdate!.backendAction).toBe('update');
expect(fleetUpdate!.targetType).toBe('fleet');
});
describe('helperText', () => {
const expected: Record<string, string> = {
'auto_backup': 'Backs up compose and env files only. This does not back up application volumes.',
'auto_start': 'Creates containers if they do not exist, or starts existing stopped containers.',
'restart': 'Restarts containers in place. Running services are stopped and started again on the same configuration.',
'auto_stop': 'Stops containers but keeps them in place for a faster start later.',
'auto_down': 'Runs compose down. Containers are removed, but compose files remain on disk.',
'update': "Checks this stack's images and recreates the stack only when newer images are available.",
'update-fleet': 'Checks every stack on the selected node and updates stacks with newer images.',
'scan': 'Runs Trivy against images on the selected local node and records the findings.',
'prune': 'Removes unused Docker resources on the selected node. Be careful when pruning volumes.',
'snapshot': 'Creates a versioned snapshot of compose and env files across the fleet.',
};
for (const [id, text] of Object.entries(expected)) {
it(`${id} helper text matches the specified wording`, () => {
expect(getActionById(id)?.helperText).toBe(text);
});
}
});
describe('riskLevel', () => {
const expected: Record<string, ScheduledActionRiskLevel> = {
'auto_backup': 'safe',
'auto_start': 'runtime-change',
'restart': 'interruptive',
'auto_stop': 'interruptive',
'auto_down': 'removes-containers',
'update': 'runtime-change',
'update-fleet': 'runtime-change',
'scan': 'read-only',
'prune': 'destructive',
'snapshot': 'safe',
};
for (const [id, level] of Object.entries(expected)) {
it(`${id} risk level is ${level}`, () => {
expect(getActionById(id)?.riskLevel).toBe(level);
});
}
it('every action has a RISK_LABEL entry', () => {
for (const def of SCHEDULED_ACTIONS) {
expect(RISK_LABEL[def.riskLevel]).toBeTruthy();
}
});
});
describe('risk metadata maps', () => {
it('RISK_TONE maps every action risk level', () => {
for (const def of SCHEDULED_ACTIONS) {
expect(RISK_TONE[def.riskLevel]).toBeTruthy();
}
});
it('RISK_BADGE_CLASSES maps every action risk level', () => {
for (const def of SCHEDULED_ACTIONS) {
expect(RISK_BADGE_CLASSES[def.riskLevel]).toBeTruthy();
}
});
it('RISK_DOT_CLASSES maps every action risk level', () => {
for (const def of SCHEDULED_ACTIONS) {
expect(RISK_DOT_CLASSES[def.riskLevel]).toBeTruthy();
}
});
});
it('resolveTaskAction for update+fleet returns correct metadata', () => {
const def = resolveTaskAction({ action: 'update', target_type: 'fleet' });
expect(def?.id).toBe('update-fleet');
expect(def?.riskLevel).toBe('runtime-change');
expect(def?.helperText).toBe('Checks every stack on the selected node and updates stacks with newer images.');
});
});