mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 08:02:47 +00:00
fix: re-read a live editor before a deploy discards the draft it made
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
994b8dcae1
commit
b4ed4f52c5
@@ -2324,6 +2324,7 @@ describe('global AI tools', () => {
|
||||
registerLiveItemBridge({
|
||||
seed: () => false,
|
||||
read: () => undefined,
|
||||
refresh: () => undefined,
|
||||
// Claims the key and removes nothing, so anything still gone was deleted by the chat.
|
||||
discard: () => {
|
||||
discards++
|
||||
@@ -2339,6 +2340,43 @@ describe('global AI tools', () => {
|
||||
registerLiveItemBridge({
|
||||
seed: () => false,
|
||||
read: () => undefined,
|
||||
refresh: () => undefined,
|
||||
discard: () => undefined,
|
||||
list: () => []
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Deploying a draft writes the item, so an editor open on it is a baseline behind. Its row is
|
||||
// discarded straight after, which resets it to that baseline — so it has to re-read first.
|
||||
it('refreshes a live editor before a deploy discards its draft', async () => {
|
||||
const path = 'u/admin/deployed_res'
|
||||
seedBackendDraft('resource', path, { path, value: { a: 2 } })
|
||||
const calls: string[] = []
|
||||
registerLiveItemBridge({
|
||||
seed: () => false,
|
||||
read: () => undefined,
|
||||
refresh: () => {
|
||||
calls.push('refresh')
|
||||
return Promise.resolve()
|
||||
},
|
||||
discard: () => {
|
||||
calls.push('discard')
|
||||
return Promise.resolve({ removed: true })
|
||||
},
|
||||
list: () => []
|
||||
})
|
||||
try {
|
||||
await deleteGlobalDraft(WORKSPACE, 'resource', path, undefined, {
|
||||
preserveLiveDraft: true,
|
||||
deployed: true
|
||||
})
|
||||
expect(calls).toEqual(['refresh', 'discard'])
|
||||
} finally {
|
||||
registerLiveItemBridge({
|
||||
seed: () => false,
|
||||
read: () => undefined,
|
||||
refresh: () => undefined,
|
||||
discard: () => undefined,
|
||||
list: () => []
|
||||
})
|
||||
|
||||
@@ -7994,7 +7994,10 @@ async function deployDraft(
|
||||
// fork comparisons before the fallible draft cleanup below.
|
||||
invalidateWorkspaceComparison(workspace)
|
||||
|
||||
await deleteGlobalDraft(workspace, type, path, triggerKind, { preserveLiveDraft: true })
|
||||
await deleteGlobalDraft(workspace, type, path, triggerKind, {
|
||||
preserveLiveDraft: true,
|
||||
deployed: true
|
||||
})
|
||||
|
||||
// Move the chat's mask entry to the deployed path: a draft-only item's
|
||||
// synthetic storage key never exists deployed, so the entry would otherwise
|
||||
|
||||
@@ -2,7 +2,12 @@ import type { Flow, NewSchedule, NewScript } from '$lib/gen/types.gen'
|
||||
import { DraftService } from '$lib/gen'
|
||||
import { UserDraftDbSyncer } from '$lib/userDraftDbSyncer.svelte'
|
||||
import { DEFAULT_DATA as DEFAULT_RAW_APP_DATA } from '$lib/components/raw_apps/dataTableRefUtils'
|
||||
import { UserDraft, type UserDraftEntry, type UserDraftItemKind } from '$lib/userDraft.svelte'
|
||||
import {
|
||||
refreshLiveItem,
|
||||
UserDraft,
|
||||
type UserDraftEntry,
|
||||
type UserDraftItemKind
|
||||
} from '$lib/userDraft.svelte'
|
||||
import { invalidateWorkspaceDrafts } from '$lib/workspaceDrafts.svelte'
|
||||
import {
|
||||
getWorkspaceItemKey,
|
||||
@@ -574,6 +579,10 @@ export async function saveGlobalAppDraft(
|
||||
|
||||
type DeleteGlobalDraftOptions = {
|
||||
preserveLiveDraft?: boolean
|
||||
/** The deployed item was just written from this draft. An editor open on it is holding the
|
||||
* baseline from before that, and discarding the draft resets it to exactly that — showing
|
||||
* the pre-deploy value, clean, ready to be saved back over the deployment. */
|
||||
deployed?: boolean
|
||||
}
|
||||
|
||||
export async function deleteGlobalDraft(
|
||||
@@ -586,6 +595,7 @@ export async function deleteGlobalDraft(
|
||||
const itemKind = itemKindFor(type, triggerKind)
|
||||
if (!itemKind) return
|
||||
const storagePath = resolveDraftStoragePath(workspace, itemKind, path)
|
||||
if (options.deployed) await refreshLiveItem(workspace, itemKind, storagePath)
|
||||
const liveDraft = UserDraft.getLiveEditorDraft(itemKind, { workspace })
|
||||
const live =
|
||||
options.preserveLiveDraft && liveDraft?.storagePath === storagePath
|
||||
|
||||
@@ -1089,6 +1089,13 @@ export function createItemStore(ports: ItemRowPort) {
|
||||
if (!entry || !entry.loaded) return undefined
|
||||
return { value: entry.dirty && entry.origin !== 'new' ? snapshot(entry.value) : undefined }
|
||||
},
|
||||
refresh(
|
||||
workspace: string,
|
||||
kind: UserDraftItemKind,
|
||||
path: string
|
||||
): Promise<unknown> | undefined {
|
||||
return find(workspace, kind, path)?.reread()
|
||||
},
|
||||
discard(
|
||||
workspace: string,
|
||||
kind: UserDraftItemKind,
|
||||
|
||||
@@ -141,6 +141,13 @@ export type LiveItemBridge = {
|
||||
itemKind: UserDraftItemKind,
|
||||
path: string
|
||||
): { value: unknown | undefined } | undefined
|
||||
/** Re-read the live item, for a caller that has just written the deployed item under it.
|
||||
* `undefined` when no live item holds the key. */
|
||||
refresh(
|
||||
workspace: string,
|
||||
itemKind: UserDraftItemKind,
|
||||
path: string
|
||||
): Promise<unknown> | undefined
|
||||
/** Discard the live item's draft, resolving once its delete has landed. `undefined` when no
|
||||
* live item holds the key, so the caller issues the delete itself. */
|
||||
discard(
|
||||
@@ -153,6 +160,16 @@ export type LiveItemBridge = {
|
||||
|
||||
let liveItems: LiveItemBridge | undefined
|
||||
|
||||
/** Tell a live item that the deployed item under it has just been written, so its baseline is
|
||||
* current before anything resets to it. Resolves once it has re-read. */
|
||||
export function refreshLiveItem(
|
||||
workspace: string,
|
||||
itemKind: UserDraftItemKind,
|
||||
path: string
|
||||
): Promise<unknown> | undefined {
|
||||
return liveItems?.refresh(workspace, itemKind, path)
|
||||
}
|
||||
|
||||
export function registerLiveItemBridge(bridge: LiveItemBridge): void {
|
||||
liveItems = bridge
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user