mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 08:02:47 +00:00
fix: give a refused patch back field by field, not all or nothing
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f8ebd281c8
commit
8f027091eb
@@ -716,17 +716,18 @@ class Entry<V> {
|
||||
this.serverDeployed = { ...this.serverDeployed, ...sent } as V
|
||||
}
|
||||
release()
|
||||
// The value is the user's copy, so it is given back by hand — to the baseline as it
|
||||
// stands now, which is the server's value or a later patch's, never this one's. Not
|
||||
// given back at all if something has written the value since: that is newer than this
|
||||
// patch and rolling it back would be this patch overwriting it.
|
||||
// The value is the user's copy, so it is given back by hand, field by field, to the
|
||||
// baseline as it stands now — the server's value or a later patch's, never this one's.
|
||||
// Only a field still carrying what this patch put there, so an edit made meanwhile to
|
||||
// any other field is left alone. And none of them once an outside write has landed:
|
||||
// that wrote the value wholesale and is newer than this patch, and a field of it
|
||||
// happening to match what this patch sent is not this patch's to take back.
|
||||
if (failed) {
|
||||
const base = (onTemplate ? this.template : this.deployed) as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
const moved =
|
||||
this.edits !== appliedAt.edits || this.externals !== appliedAt.externals
|
||||
if (base !== undefined && this.value !== undefined && !moved) {
|
||||
const written = this.externals !== appliedAt.externals
|
||||
if (base !== undefined && this.value !== undefined && !written) {
|
||||
const out = snapshot(this.value) as Record<string, unknown>
|
||||
let changed = false
|
||||
for (const [k, v] of Object.entries(sent)) {
|
||||
|
||||
@@ -843,6 +843,34 @@ describe('item store: origins', () => {
|
||||
expect(item.dirty).toBe(true)
|
||||
})
|
||||
|
||||
it('gives a refused toggle its field back while an edit made meanwhile stays', async () => {
|
||||
type Sched = { path: string; enabled: boolean; summary: string }
|
||||
const rows = fakeRows()
|
||||
const store = createItemStore(rows.port)
|
||||
const refusal = deferred()
|
||||
const { handle: item } = store.acquire(
|
||||
{ workspace: 'w', kind: 'trigger_schedule', path: 's' },
|
||||
{ workspace: 'w', path: 's' },
|
||||
{
|
||||
load: async () => ({ deployed: { path: 's', enabled: true, summary: 'a' } })
|
||||
} as ItemAdapter<Sched>
|
||||
)
|
||||
await settle()
|
||||
const toggling = item.patch({ enabled: false }, () => refusal.promise)
|
||||
// The user types in another field while the request is out.
|
||||
item.value = { path: 's', enabled: false, summary: 'typed while disabling' }
|
||||
refusal.reject(new Error('refused'))
|
||||
|
||||
expect(await toggling).toMatchObject({ ok: false })
|
||||
// The server stayed enabled, so showing it disabled — and drafting that — would be an
|
||||
// editor claiming a schedule is off while it is still running. The summary is untouched.
|
||||
expect(item.value).toEqual({ path: 's', enabled: true, summary: 'typed while disabling' })
|
||||
expect(rows.writes.at(-1)).toEqual({
|
||||
path: 's',
|
||||
value: { path: 's', enabled: true, summary: 'typed while disabling' }
|
||||
})
|
||||
})
|
||||
|
||||
it('does not roll a refused toggle back over a draft written while it was out', async () => {
|
||||
type Sched = { path: string; enabled: boolean; summary: string }
|
||||
const rows = fakeRows()
|
||||
|
||||
Reference in New Issue
Block a user