mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-16 16:03:14 +00:00
fix: re-issue a draft delete the server never received instead of assuming it landed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e226143f4c
commit
6fde8e9dce
@@ -407,8 +407,11 @@ class Entry<V> {
|
||||
// copy of that edit, so the item opens on it. One the server *refused* is the other
|
||||
// way round — the response is what won — and `adoptRow` drops it below.
|
||||
const unsent = this.ports.conflicted(key) ? undefined : this.ports.pending(key)
|
||||
const row = unsent === undefined ? res.draft : unsent
|
||||
const draft = (row ?? undefined) as V | undefined
|
||||
// A parked delete is what this tab wants gone, not what is there: the row is still the
|
||||
// server's, so the reconcile at the end re-issues the delete instead of believing it
|
||||
// landed and leaving the draft behind with nothing to remove it.
|
||||
const row = unsent == null ? res.draft : unsent
|
||||
const draft = (unsent === null ? undefined : row) as V | undefined
|
||||
// The deployed side above always advances: the item did change, and a discard after
|
||||
// the user keeps theirs has to land on what the server holds now.
|
||||
if (!standOff) this.adoptRow(key, row, res.draftSavedAt, rowsAtRead)
|
||||
|
||||
@@ -1251,6 +1251,33 @@ describe('item store: conflicts', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('re-issues a draft delete the server never received, on reopening', async () => {
|
||||
const rows = fakeRows()
|
||||
const store = createItemStore(rows.port)
|
||||
const key: ItemKey = { workspace: 'w', kind: 'resource', path: 'u/me/r' }
|
||||
const draft = { ...deployedRes, description: 'a draft to be discarded' }
|
||||
// The server keeps the draft throughout: the delete never reaches it.
|
||||
const a = adapter(async () => ({ deployed: deployedRes, draft }))
|
||||
const first = store.acquire(key, { workspace: 'w', path: 'u/me/r' }, a)
|
||||
await settle()
|
||||
rows.failing.add('u/me/r')
|
||||
expect(await first.handle.discard()).toEqual({ removed: false })
|
||||
expect(rows.sent.has('u/me/r')).toBe(false)
|
||||
first.release()
|
||||
const writesBefore = rows.writes.length
|
||||
|
||||
const second = store.acquire(key, { workspace: 'w', path: 'u/me/r' }, a)
|
||||
await settle()
|
||||
|
||||
// Reopening must not read the parked delete as "there is no draft": the row is still the
|
||||
// server's, so the delete has to go out again rather than be quietly forgotten.
|
||||
expect(second.handle.value).toEqual(deployedRes)
|
||||
expect(rows.writes.slice(writesBefore)).toEqual([{ path: 'u/me/r', value: null }])
|
||||
rows.failing.delete('u/me/r')
|
||||
await rows.port.flush(key)
|
||||
expect(rows.sent.get('u/me/r')).toBeNull()
|
||||
})
|
||||
|
||||
it('opens a conflicted item afresh, past the payload the server refused', async () => {
|
||||
const rows = fakeRows()
|
||||
const store = createItemStore(rows.port)
|
||||
|
||||
Reference in New Issue
Block a user