mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-27 16:02:43 +00:00
fix: re-read the session's drafts once a new fork exists
Creating the fork takes seconds, during which an edit in the session's preview can save a draft in the parent that the move's list did not have. The move now flushes and lists the drafts again before the transfer, and reopens the dialog on the created fork when they changed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8a8442ec73
commit
21240cf6f9
@@ -567,13 +567,26 @@
|
||||
}
|
||||
return
|
||||
}
|
||||
let relisted: Awaited<ReturnType<typeof listMoveItems>> | undefined
|
||||
const res = await moveSessionToFork(session.id, req.parent, req.target, {
|
||||
items: req.items.map(({ kind, path }) => ({ kind, path })),
|
||||
removeFromParent: moveRemoveFromParent,
|
||||
isBusy: () => !canMoveToFork
|
||||
isBusy: () => !canMoveToFork,
|
||||
itemsChanged: async () => {
|
||||
relisted = await listMoveItems(req.parent, req.target)
|
||||
return typeof relisted === 'string' || itemKeys(relisted.items) !== itemKeys(req.items)
|
||||
}
|
||||
})
|
||||
if (!res.ok) {
|
||||
if (!req.open) {
|
||||
if (res.changed && relisted !== undefined) {
|
||||
moveRequest = {
|
||||
...req,
|
||||
...(typeof relisted === 'string' ? {} : relisted),
|
||||
target: res.retry ?? req.target,
|
||||
open: true,
|
||||
error: typeof relisted === 'string' ? relisted : res.error
|
||||
}
|
||||
} else if (!req.open) {
|
||||
sendUserToast(res.error, true)
|
||||
moveRequest = undefined
|
||||
} else {
|
||||
|
||||
@@ -117,4 +117,23 @@ describe('moveSessionToFork', () => {
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
it('stops before transferring when the drafts changed while the fork was created', async () => {
|
||||
vi.mocked(materializeForkOutcome).mockResolvedValue({ id: 'wm-fork-new', created: true })
|
||||
vi.mocked(DraftService.transferDrafts).mockClear()
|
||||
const s = addSession()
|
||||
const res = await moveSessionToFork(
|
||||
s.id,
|
||||
'root_ws',
|
||||
{ kind: 'new', fork: { id: 'wm-fork-new', name: 'new', parent_workspace_id: 'root_ws' } },
|
||||
{ items: [], removeFromParent: false, itemsChanged: async () => true }
|
||||
)
|
||||
expect(res).toMatchObject({
|
||||
ok: false,
|
||||
changed: true,
|
||||
retry: { kind: 'existing', id: 'wm-fork-new', replace: true }
|
||||
})
|
||||
expect(DraftService.transferDrafts).not.toHaveBeenCalled()
|
||||
expect(sessionState.sessions.find((x) => x.id === s.id)?.workspace_id).toBe('root_ws')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,7 +24,14 @@ export type MoveTarget =
|
||||
export type MoveSessionResult =
|
||||
| { ok: true; forkId: string; copied: number; removed: number }
|
||||
// `retry` is the target to retry with once the fork exists, so a retry never creates it again.
|
||||
| { ok: false; error: string; conflicts?: DraftItemRef[]; retry?: MoveTarget }
|
||||
// `changed`: the session's drafts are no longer the ones listed, so the caller lists them again.
|
||||
| {
|
||||
ok: false
|
||||
error: string
|
||||
conflicts?: DraftItemRef[]
|
||||
retry?: MoveTarget
|
||||
changed?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Send every debounced draft save for `workspace` to the server. Returns an error message
|
||||
@@ -89,6 +96,8 @@ export async function moveSessionToFork(
|
||||
removeFromParent: boolean
|
||||
// A turn running in the parent keeps writing there while its drafts move away.
|
||||
isBusy?: () => boolean
|
||||
// Saves pending in the parent and tells whether the session's drafts differ from `items`.
|
||||
itemsChanged?: () => Promise<boolean>
|
||||
}
|
||||
): Promise<MoveSessionResult> {
|
||||
const unsaved = await flushPendingSaves(parent)
|
||||
@@ -103,6 +112,16 @@ export async function moveSessionToFork(
|
||||
fork = { kind: 'existing', id: made.id, replace: made.created }
|
||||
// Creating the fork takes seconds, long enough for a job's auto-resume to start a turn.
|
||||
if (opts.isBusy?.()) return { ok: false, error: busy, retry: fork }
|
||||
// So does an edit in the session's preview: it lands in the parent, possibly as a draft the
|
||||
// list did not have.
|
||||
if (await opts.itemsChanged?.()) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "This session's drafts changed. Check the list and confirm again.",
|
||||
retry: fork,
|
||||
changed: true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fork = target
|
||||
// An editor open in the fork may hold edits not yet saved. Saved now, they become the
|
||||
|
||||
Reference in New Issue
Block a user