mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-27 10:46:51 +00:00
feat(scheduler): group schedule action picker by operator intent (#1446)
* feat(scheduler): group schedule action picker by operator intent Reorganize the New Schedule action picker from a flat dropdown to a category-grouped list (Lifecycle, Updates, Security, Maintenance, Backups). - Extend Combobox component with optional group field on ComboboxOption, rendering grouped sections with non-interactive headers when groups are present. Flat rendering is unchanged for all other callers. - Reorder SCHEDULED_ACTIONS by category group and update seven action labels per the operator-intent spec. - Add DEFAULT_SCHEDULED_ACTION_ID constant so picker order and form defaults are independently controllable. - Wire grouped actionOptions into ScheduledOperationsView. - Update all label references in docs and tests. - Add Combobox grouping tests, registry order test, and default-constant test. * fix(scheduler): correct Combobox grouping for interleaved groups, docs labels - Replace last-group-append with Map-based group partitioning so interleaved or mixed-group options land in the correct group. - Add interleaved-groups test and restore non-interactivity test. - Update stale "Start Stack" references to "Start / Bring Up Stack" in doc action-label contexts. - Update action-picker alt text to describe the new grouped order.
This commit is contained in:
@@ -7,6 +7,7 @@ import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
SCHEDULED_ACTIONS,
|
||||
SCHEDULED_ACTION_CATEGORIES,
|
||||
DEFAULT_SCHEDULED_ACTION_ID,
|
||||
getActionById,
|
||||
resolveTaskAction,
|
||||
type BackendAction,
|
||||
@@ -69,4 +70,27 @@ describe('scheduledActions registry', () => {
|
||||
expect(resolveTaskAction({ action: 'bogus' as BackendAction, target_type: 'system' })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('exports DEFAULT_SCHEDULED_ACTION_ID as restart', () => {
|
||||
expect(DEFAULT_SCHEDULED_ACTION_ID).toBe('restart');
|
||||
});
|
||||
|
||||
it('orders actions by category group', () => {
|
||||
const ids = SCHEDULED_ACTIONS.map(a => a.id);
|
||||
// Verify the exact order: lifecycle first, then updates, security, maintenance, backups.
|
||||
expect(ids).toEqual([
|
||||
'auto_backup', 'auto_start', 'restart', 'auto_stop', 'auto_down',
|
||||
'update', 'update-fleet',
|
||||
'scan',
|
||||
'prune',
|
||||
'snapshot',
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves the update-fleet alias after resorting', () => {
|
||||
const fleetUpdate = SCHEDULED_ACTIONS.find(a => a.id === 'update-fleet');
|
||||
expect(fleetUpdate).toBeDefined();
|
||||
expect(fleetUpdate!.backendAction).toBe('update');
|
||||
expect(fleetUpdate!.targetType).toBe('fleet');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,18 +41,26 @@ export interface ScheduledActionDefinition {
|
||||
helperText?: string;
|
||||
}
|
||||
|
||||
/** Ordered for the create-flow action picker. */
|
||||
/** Action pre-selected when the create modal opens. Decoupled from picker order. */
|
||||
export const DEFAULT_SCHEDULED_ACTION_ID: ScheduledActionId = 'restart';
|
||||
|
||||
/** Ordered for the create-flow action picker, grouped by category. */
|
||||
export const SCHEDULED_ACTIONS: ScheduledActionDefinition[] = [
|
||||
// Lifecycle
|
||||
{ id: 'auto_backup', backendAction: 'auto_backup', label: 'Backup Stack Compose Files', shortLabel: 'backup', category: 'lifecycle', targetType: 'stack', tone: 'brand', requiresNode: true, requiresStack: true, supportsServiceSelection: false },
|
||||
{ id: 'auto_start', backendAction: 'auto_start', label: 'Start / Bring Up Stack', shortLabel: 'start', category: 'lifecycle', targetType: 'stack', tone: 'success', requiresNode: true, requiresStack: true, supportsServiceSelection: false },
|
||||
{ id: 'restart', backendAction: 'restart', label: 'Restart Stack', shortLabel: 'restart', category: 'lifecycle', targetType: 'stack', tone: 'brand', requiresNode: true, requiresStack: true, supportsServiceSelection: true },
|
||||
{ id: 'auto_stop', backendAction: 'auto_stop', label: 'Stop Stack', shortLabel: 'stop', category: 'lifecycle', targetType: 'stack', tone: 'warning', requiresNode: true, requiresStack: true, supportsServiceSelection: false },
|
||||
{ id: 'auto_down', backendAction: 'auto_down', label: 'Take Stack Down', shortLabel: 'down', category: 'lifecycle', targetType: 'stack', tone: 'destructive', requiresNode: true, requiresStack: true, supportsServiceSelection: false },
|
||||
// Updates
|
||||
{ id: 'update', backendAction: 'update', label: 'Auto-update Stack', shortLabel: 'update', category: 'updates', targetType: 'stack', tone: 'success', requiresNode: true, requiresStack: true, supportsServiceSelection: false },
|
||||
{ id: 'update-fleet', backendAction: 'update', label: 'Auto-update All Stacks', shortLabel: 'update', category: 'updates', targetType: 'fleet', tone: 'success', requiresNode: true, requiresStack: false, supportsServiceSelection: false, helperText: 'Every stack on the selected node will be checked and updated when new images are available.' },
|
||||
{ id: 'snapshot', backendAction: 'snapshot', label: 'Fleet Snapshot', shortLabel: 'snapshot', category: 'backups', targetType: 'fleet', tone: 'warning', requiresNode: false, requiresStack: false, supportsServiceSelection: false },
|
||||
{ id: 'prune', backendAction: 'prune', label: 'System Prune', shortLabel: 'prune', category: 'maintenance', targetType: 'system', tone: 'warning', requiresNode: true, requiresStack: false, supportsServiceSelection: false, nodeScope: 'local', helperText: 'Resources are pruned on the selected node. Prunes run on local nodes only.' },
|
||||
{ id: 'scan', backendAction: 'scan', label: 'Vulnerability Scan', shortLabel: 'scan', category: 'security', targetType: 'system', tone: 'success', requiresNode: true, requiresStack: false, supportsServiceSelection: false, nodeScope: 'local', helperText: 'Every image on the selected node will be scanned. Scans run on local nodes only.' },
|
||||
{ id: 'auto_backup', backendAction: 'auto_backup', label: 'Backup Stack Files', shortLabel: 'backup', category: 'lifecycle', targetType: 'stack', tone: 'brand', requiresNode: true, requiresStack: true, supportsServiceSelection: false },
|
||||
{ id: 'auto_stop', backendAction: 'auto_stop', label: 'Stop Stack (keep containers)', shortLabel: 'stop', category: 'lifecycle', targetType: 'stack', tone: 'warning', requiresNode: true, requiresStack: true, supportsServiceSelection: false },
|
||||
{ id: 'auto_down', backendAction: 'auto_down', label: 'Take Stack Down (remove containers)', shortLabel: 'down', category: 'lifecycle', targetType: 'stack', tone: 'destructive', requiresNode: true, requiresStack: true, supportsServiceSelection: false },
|
||||
{ id: 'auto_start', backendAction: 'auto_start', label: 'Start Stack', shortLabel: 'start', category: 'lifecycle', targetType: 'stack', tone: 'success', requiresNode: true, requiresStack: true, supportsServiceSelection: false },
|
||||
{ id: 'update-fleet', backendAction: 'update', label: 'Auto-update All Stacks on Node', shortLabel: 'update', category: 'updates', targetType: 'fleet', tone: 'success', requiresNode: true, requiresStack: false, supportsServiceSelection: false, helperText: 'Every stack on the selected node will be checked and updated when new images are available.' },
|
||||
// Security
|
||||
{ id: 'scan', backendAction: 'scan', label: 'Scan Node Images', shortLabel: 'scan', category: 'security', targetType: 'system', tone: 'success', requiresNode: true, requiresStack: false, supportsServiceSelection: false, nodeScope: 'local', helperText: 'Every image on the selected node will be scanned. Scans run on local nodes only.' },
|
||||
// Maintenance
|
||||
{ id: 'prune', backendAction: 'prune', label: 'Prune Node Resources', shortLabel: 'prune', category: 'maintenance', targetType: 'system', tone: 'warning', requiresNode: true, requiresStack: false, supportsServiceSelection: false, nodeScope: 'local', helperText: 'Resources are pruned on the selected node. Prunes run on local nodes only.' },
|
||||
// Backups
|
||||
{ id: 'snapshot', backendAction: 'snapshot', label: 'Create Fleet Snapshot', shortLabel: 'snapshot', category: 'backups', targetType: 'fleet', tone: 'warning', requiresNode: false, requiresStack: false, supportsServiceSelection: false },
|
||||
];
|
||||
|
||||
const ACTION_BY_ID = new Map<string, ScheduledActionDefinition>(SCHEDULED_ACTIONS.map(a => [a.id, a]));
|
||||
|
||||
Reference in New Issue
Block a user