fix: keep the schedule's stack selected on prefill and edit (#1496)

Opening the New Schedule modal from a stack's Schedule action, or editing
a stack-targeted task, set the node and the stack, but the node-change
effect then ran and cleared the stack, forcing the user to reselect it.

Move the stack-clear out of that effect into the Node picker's change
handler so it only fires on a user-driven node change. A programmatic node
set from prefill or edit now preserves the stack.
This commit is contained in:
Anso
2026-06-28 03:18:27 -04:00
committed by GitHub
parent 3bece54e72
commit a6d431f0d7
2 changed files with 37 additions and 3 deletions
@@ -201,12 +201,13 @@ export default function ScheduledOperationsView({ filterNodeId, onClearFilter, p
return () => { cancelled = true; };
}, [formAction, formTargetId, formNodeId]);
// Re-fetch stacks when node changes
// Re-fetch stacks when the node changes. Clearing a stale stack selection is
// done in the Node picker's onValueChange (a user-driven change), not here, so
// a prefilled or edited node keeps its stack instead of being wiped on open.
useEffect(() => {
if (!dialogOpen) return;
if (formNodeId) {
fetchStacks(formNodeId);
setFormTargetId('');
} else {
setStacks([]);
}
@@ -790,7 +791,7 @@ export default function ScheduledOperationsView({ filterNodeId, onClearFilter, p
<Combobox
options={nodeOptions}
value={formNodeId}
onValueChange={setFormNodeId}
onValueChange={(val) => { setFormNodeId(val); setFormTargetId(''); }}
placeholder="Select node..."
/>
</div>
@@ -125,6 +125,39 @@ describe('ScheduledOperationsView', () => {
expect(onPrefillConsumed).toHaveBeenCalledTimes(1);
// The prefilled stack drives a node-scoped stack fetch through the proxy.
await waitFor(() => expect(mockedFetchForNode).toHaveBeenCalledWith('/stacks', 1));
// The prefilled stack stays selected; the node-change effect must not wipe it.
// Comboboxes render in order: action, node, stack.
await waitFor(() => expect(screen.getAllByRole('combobox')[2]).toHaveTextContent('web'));
});
it('keeps the stack selected when editing a stack-targeted task', async () => {
tasksFixture = [makeTask({
id: 9, name: 'restart-web', action: 'restart', target_type: 'stack', target_id: 'web', node_id: 1,
})];
render(<ScheduledOperationsView />);
await userEvent.click(await screen.findByRole('button', { name: /All tasks/ }));
await userEvent.click(await screen.findByTitle('Edit'));
await waitFor(() => expect(screen.getAllByRole('combobox')[2]).toHaveTextContent('web'));
});
it('clears the stale stack when the user changes the node', async () => {
render(<ScheduledOperationsView />);
await userEvent.click(await screen.findByRole('button', { name: /New Schedule/ }));
// Default action Restart Stack: choose a node, then a stack on it.
await userEvent.click(screen.getAllByRole('combobox')[1]);
await userEvent.click(await screen.findByRole('button', { name: 'hub' }));
await userEvent.click(screen.getAllByRole('combobox')[2]);
await userEvent.click(await screen.findByRole('button', { name: 'web' }));
expect(screen.getAllByRole('combobox')[2]).toHaveTextContent('web');
// Switching node is a user-driven change, so the now-stale stack clears
// and the user must re-pick one before the schedule can be saved.
await userEvent.click(screen.getAllByRole('combobox')[1]);
await userEvent.click(await screen.findByRole('button', { name: 'edge' }));
expect(screen.getAllByRole('combobox')[2]).toHaveTextContent('Select stack...');
expect(screen.getByRole('button', { name: 'Create' })).toBeDisabled();
});
it('filters the table to the selected node and clears the filter', async () => {