mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-18 16:03:24 +00:00
refactor: drop await on draft-delete in reset flows, refetch deployed directly
This commit is contained in:
@@ -58,19 +58,21 @@
|
||||
// separate `.draft` field), so `saved.is_draft` is the signal that
|
||||
// there's actually a draft worth deleting; the syncer's
|
||||
// `value: null` POST is the canonical per-user delete.
|
||||
//
|
||||
// Fire-and-forget: every read here (`saved`, the snapshot we build
|
||||
// below, the UserDraft.discard write) is purely in-memory, so we
|
||||
// don't need the DELETE to have landed to finish the restore. We
|
||||
// flip `is_draft` optimistically so the UI matches the new intent
|
||||
// immediately. A failed DELETE only matters across a hard reload
|
||||
// before it lands — log and move on.
|
||||
if (saved.is_draft) {
|
||||
try {
|
||||
await UserDraftDbSyncer.save({
|
||||
workspace: workspaceId,
|
||||
itemKind: 'script',
|
||||
path: saved.path,
|
||||
value: null
|
||||
})
|
||||
saved.is_draft = false
|
||||
} catch (e: any) {
|
||||
sendUserToast(`Could not delete draft: ${e?.body ?? e}`, true)
|
||||
return
|
||||
}
|
||||
saved.is_draft = false
|
||||
UserDraftDbSyncer.save({
|
||||
workspace: workspaceId,
|
||||
itemKind: 'script',
|
||||
path: saved.path,
|
||||
value: null
|
||||
}).catch((e) => console.error('restoreDeployed: draft delete failed', e))
|
||||
}
|
||||
const deployed = structuredClone($state.snapshot(saved)) as NewScript & { draft?: unknown }
|
||||
delete deployed.draft
|
||||
|
||||
@@ -185,7 +185,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Record<string, any>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
@@ -194,7 +198,7 @@
|
||||
const s = await AzureTriggerService.getAzureTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -203,7 +207,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -207,7 +207,11 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Partial<EmailTrigger>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Partial<EmailTrigger>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
@@ -215,7 +219,7 @@
|
||||
const s = await EmailTriggerService.getEmailTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -224,7 +228,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -189,7 +189,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Record<string, any>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
@@ -198,7 +202,7 @@
|
||||
const s = await GcpTriggerService.getGcpTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -207,7 +211,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -340,7 +340,11 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Partial<HttpTrigger>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Partial<HttpTrigger>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
@@ -348,7 +352,7 @@
|
||||
const s = await HttpTriggerService.getHttpTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -357,7 +361,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -246,7 +246,11 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Record<string, any>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
@@ -254,7 +258,7 @@
|
||||
const s = await KafkaTriggerService.getKafkaTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -263,7 +267,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -245,7 +245,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Record<string, any>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
try {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
@@ -254,7 +258,7 @@
|
||||
const s = await MqttTriggerService.getMqttTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -263,7 +267,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -228,7 +228,11 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Record<string, any>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
@@ -236,7 +240,7 @@
|
||||
const s = await NatsTriggerService.getNatsTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -245,7 +249,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -368,7 +368,11 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Record<string, any>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
if (defaultConfig?.publication) {
|
||||
@@ -380,7 +384,7 @@
|
||||
const s = await PostgresTriggerService.getPostgresTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -389,7 +393,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -278,8 +278,10 @@
|
||||
nis_flow: boolean,
|
||||
initial_script_path?: string,
|
||||
defaultValues?: Schedule,
|
||||
schedule_path?: string
|
||||
schedule_path?: string,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
) {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
let loadingTimeout = setTimeout(() => {
|
||||
showLoading = true
|
||||
}, 100) // Do not show loading spinner for the first 100ms
|
||||
@@ -290,7 +292,7 @@
|
||||
const resp = await ScheduleService.getSchedule({
|
||||
workspace: $workspaceStore!,
|
||||
path: schedule_path,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (resp.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -299,7 +301,9 @@
|
||||
path: schedule_path,
|
||||
draftOnly: resp.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await openNew(nis_flow, initial_script_path, defaultValues, schedule_path)
|
||||
await openNew(nis_flow, initial_script_path, defaultValues, schedule_path, {
|
||||
getDraft: false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -466,13 +470,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSchedule(defaultCfg?: Record<string, any>): Promise<void> {
|
||||
async function loadSchedule(
|
||||
defaultCfg?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
if (!defaultCfg) {
|
||||
try {
|
||||
const s = await ScheduleService.getSchedule({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -481,7 +489,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadSchedule()
|
||||
await loadSchedule(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -222,7 +222,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Record<string, any>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
try {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
@@ -231,7 +235,7 @@
|
||||
const s = await SqsTriggerService.getSqsTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -240,7 +244,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -303,7 +303,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTrigger(defaultConfig?: Record<string, any>): Promise<void> {
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
@@ -311,7 +315,7 @@
|
||||
const s = await WebsocketTriggerService.getWebsocketTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
@@ -320,7 +324,7 @@
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger()
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,11 +43,14 @@ export function notifyRestoredFromLocal(
|
||||
* draft) but omit the action.
|
||||
*
|
||||
* The action:
|
||||
* 1. POSTs `value: null` to delete the user's draft for this entity
|
||||
* 2. invokes `onResetToDeployed` so the editor can re-fetch without
|
||||
* the overlay and apply the pure deployed version
|
||||
* Failures in either step surface a follow-up toast and leave the editor
|
||||
* state untouched.
|
||||
* 1. Fires `value: null` at the syncer (fire-and-forget — the user's
|
||||
* delete races no reader; `onResetToDeployed` doesn't depend on it).
|
||||
* 2. invokes `onResetToDeployed`, which MUST fetch deployed directly
|
||||
* (e.g. `getDraft: false`) instead of trusting that the delete has
|
||||
* already landed in the DB.
|
||||
*
|
||||
* A delete failure logs to console only — there's no UI state that
|
||||
* depends on it. A reset-callback failure surfaces a toast.
|
||||
*/
|
||||
export function notifyDraftLoaded(opts: {
|
||||
workspace: string
|
||||
@@ -64,17 +67,12 @@ export function notifyDraftLoaded(opts: {
|
||||
{
|
||||
label: 'Reset to deployed',
|
||||
callback: async () => {
|
||||
try {
|
||||
await UserDraftDbSyncer.save({
|
||||
workspace: opts.workspace,
|
||||
itemKind: opts.itemKind,
|
||||
path: opts.path,
|
||||
value: null
|
||||
})
|
||||
} catch (e: any) {
|
||||
sendUserToast(`Could not delete draft: ${e?.body ?? e}`, true)
|
||||
return
|
||||
}
|
||||
UserDraftDbSyncer.save({
|
||||
workspace: opts.workspace,
|
||||
itemKind: opts.itemKind,
|
||||
path: opts.path,
|
||||
value: null
|
||||
}).catch((e) => console.error('Reset to deployed: draft delete failed', e))
|
||||
try {
|
||||
await opts.onResetToDeployed()
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -76,7 +76,8 @@
|
||||
* navigation races a draft-discard reload) bail at the next checkpoint
|
||||
* after their captured token no longer matches. */
|
||||
let loadAppToken = 0
|
||||
async function loadApp(): Promise<void> {
|
||||
async function loadApp(opts: { getDraft?: boolean } = {}): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
const tok = ++loadAppToken
|
||||
// `?new_draft=true` (set by `/apps/add`'s redirect) means we landed
|
||||
// on a fresh `u/{user}/draft_{uuid}` path that's never been saved.
|
||||
@@ -115,7 +116,7 @@
|
||||
const backendApp = await AppService.getAppByPath({
|
||||
path: page.params.path ?? '',
|
||||
workspace: $workspaceStore!,
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (tok !== loadAppToken) return
|
||||
if (backendApp.is_draft) {
|
||||
@@ -126,7 +127,7 @@
|
||||
draftOnly: backendApp.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
UserDraft.remove('app', path)
|
||||
await loadApp()
|
||||
await loadApp({ getDraft: false })
|
||||
redraw++
|
||||
}
|
||||
})
|
||||
|
||||
@@ -162,7 +162,8 @@
|
||||
* navigation races a draft-discard reload) bail at the next checkpoint
|
||||
* after their captured token no longer matches. */
|
||||
let loadAppToken = 0
|
||||
async function loadApp(): Promise<void> {
|
||||
async function loadApp(opts: { getDraft?: boolean } = {}): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
const tok = ++loadAppToken
|
||||
// `?new_draft=true` (set by `/apps_raw/add`'s redirect) means we
|
||||
// landed on a fresh `u/{user}/draft_{uuid}` path that's never
|
||||
@@ -194,7 +195,7 @@
|
||||
const backendApp = (await AppService.getAppByPath({
|
||||
path: page.params.path ?? '',
|
||||
workspace: $workspaceStore!,
|
||||
getDraft: true,
|
||||
getDraft,
|
||||
rawApp: true
|
||||
})) as any
|
||||
if (tok !== loadAppToken) return
|
||||
@@ -206,7 +207,7 @@
|
||||
draftOnly: backendApp.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
draftHandle.setDraftAndMeta(undefined, {})
|
||||
await loadApp()
|
||||
await loadApp({ getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -147,7 +147,8 @@
|
||||
* (e.g. picker navigation while a draft-discard reload is in flight),
|
||||
* the older promise no-ops at the next checkpoint. */
|
||||
let loadFlowToken = 0
|
||||
async function loadFlow(): Promise<void> {
|
||||
async function loadFlow(opts: { getDraft?: boolean } = {}): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
const tok = ++loadFlowToken
|
||||
loading = true
|
||||
let flow: Flow
|
||||
@@ -214,7 +215,7 @@
|
||||
const backendFlow = await FlowService.getFlowByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: page.params.path ?? '',
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (tok !== loadFlowToken) return
|
||||
if (backendFlow.is_draft) {
|
||||
@@ -225,7 +226,7 @@
|
||||
draftOnly: backendFlow.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
flowHandle.setDraftAndMeta(undefined, {})
|
||||
await loadFlow()
|
||||
await loadFlow({ getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -135,7 +135,8 @@
|
||||
* navigation races a draft-discard reload) bail at the next checkpoint
|
||||
* after their captured token no longer matches. */
|
||||
let loadScriptToken = 0
|
||||
async function loadScript(): Promise<void> {
|
||||
async function loadScript(opts: { getDraft?: boolean } = {}): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
const tok = ++loadScriptToken
|
||||
fullyLoaded = false
|
||||
// `?new_draft=true` (set by `/scripts/add`'s redirect) means we
|
||||
@@ -179,7 +180,7 @@
|
||||
const backendScript = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: page.params.path ?? '',
|
||||
getDraft: true
|
||||
getDraft
|
||||
})
|
||||
if (tok !== loadScriptToken) return
|
||||
if (backendScript.is_draft) {
|
||||
@@ -189,12 +190,11 @@
|
||||
path: page.params.path ?? '',
|
||||
draftOnly: backendScript.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
// Drop the in-memory draft so loadScript's no-localDraft
|
||||
// branch fires; the next fetch with `getDraft: true` will
|
||||
// return pure deployed because we just deleted the draft
|
||||
// row server-side.
|
||||
// Drop the in-memory draft and refetch *without* the
|
||||
// draft overlay — we don't trust the eventual delete
|
||||
// to have landed, so we read deployed directly.
|
||||
scriptHandle.setDraftAndMeta(undefined, {})
|
||||
await loadScript()
|
||||
await loadScript({ getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user