mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 08:02:26 +00:00
refactor: drop draft-loaded toast in non-route editors, banner now compares draft vs deployed
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { clearJsonSchemaResourceCache } from './schema/jsonSchemaResource.svelte'
|
||||
import ResourceForm from './ResourceForm.svelte'
|
||||
import { invalidateWorkspacePaths } from './PathNameAutocomplete.svelte'
|
||||
@@ -259,48 +258,24 @@
|
||||
// draft (if any) sits in `.draft` as the editor's internal
|
||||
// `ResourceState` shape — the editor reads it directly.
|
||||
const savedDraftState = (r as any).draft as ResourceState | undefined
|
||||
if (r.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: ws,
|
||||
itemKind: 'resource',
|
||||
path: initialPath ?? '',
|
||||
draftOnly: r.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
const fresh = await ResourceService.getResource({
|
||||
workspace: ws,
|
||||
path: initialPath ?? ''
|
||||
})
|
||||
const deployed: ResourceState = {
|
||||
path: fresh.path,
|
||||
description: fresh.description ?? '',
|
||||
args: (fresh.value ?? {}) as any,
|
||||
labels: fresh.labels ?? undefined,
|
||||
wsSpecific: fresh.ws_specific ?? false
|
||||
}
|
||||
fetchedResources[ws] = fresh
|
||||
fetchedRev[ws] = fresh.edited_at
|
||||
initialStates[ws] = structuredClone(deployed)
|
||||
// Reset the live cell to the deployed state and drop the
|
||||
// DB autosave. Setting the draft to `undefined` would
|
||||
// blank the cell (and the editor) — `discard` reseeds it
|
||||
// with the deployed baseline instead. The rev is re-recorded
|
||||
// by the seeding effect on the user's next edit.
|
||||
UserDraft.discard('resource', initialPath ?? '', deployed, { workspace: ws })
|
||||
}
|
||||
})
|
||||
}
|
||||
fetchedResources[ws] = r
|
||||
fetchedRev[ws] = r.edited_at
|
||||
// Translate the deployed wire shape into the editor's
|
||||
// `ResourceState` shape. When a saved draft exists, use it
|
||||
// directly — it's already a `ResourceState`.
|
||||
const s: ResourceState = savedDraftState ?? {
|
||||
// The deployed baseline, translated into the editor's
|
||||
// `ResourceState` shape. Kept as the dirty-check reference
|
||||
// so the "unsaved changes" banner compares draft-vs-deployed
|
||||
// instead of loaded-vs-current — when a saved draft exists,
|
||||
// the form opens with `draft != deployed` so the banner
|
||||
// fires immediately, exactly as if the user had typed.
|
||||
const deployedState: ResourceState = {
|
||||
path: r.path,
|
||||
description: r.description ?? '',
|
||||
args: (r.value ?? {}) as any,
|
||||
labels: r.labels ?? undefined,
|
||||
wsSpecific: r.ws_specific ?? false
|
||||
}
|
||||
// What the editor opens with: the saved draft if present,
|
||||
// otherwise the deployed.
|
||||
const s: ResourceState = savedDraftState ?? deployedState
|
||||
// Reconcile the local autosave with the backend before the
|
||||
// handle is registered. If the backend moved on since the
|
||||
// autosave was written (recorded rev != current rev) surface
|
||||
@@ -330,7 +305,7 @@
|
||||
}
|
||||
}
|
||||
ensureHandle(ws, s)
|
||||
initialStates[ws] = structuredClone(s)
|
||||
initialStates[ws] = structuredClone(deployedState)
|
||||
existedInitially[ws] = true
|
||||
perWsUser[ws] = user
|
||||
// Keep resource_type in sync for the base workspace (controls the schema)
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
import DefaultScripts from './DefaultScripts.svelte'
|
||||
import { getContext, onMount, setContext, untrack } from 'svelte'
|
||||
import EditorHeader from './EditorHeader.svelte'
|
||||
import AutosaveIndicator from './AutosaveIndicator.svelte'
|
||||
import LabelsInput from './LabelsInput.svelte'
|
||||
|
||||
import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte'
|
||||
@@ -354,6 +355,7 @@
|
||||
// new drafts). Resumed in the async `.finally` so language
|
||||
// switches AFTER bootstrap (which also call `initContent`) sync
|
||||
// normally.
|
||||
console.log('Suspending autosave for script bootstrap', userDraftPath)
|
||||
UserDraft.stopSync('script', userDraftPath)
|
||||
if (template === 'wac_python') {
|
||||
script.modules = {
|
||||
@@ -1837,6 +1839,9 @@
|
||||
onNavigate={(item) => onNavigate?.(item)}
|
||||
/>
|
||||
{/if}
|
||||
{#if $workspaceStore}
|
||||
<AutosaveIndicator workspace={$workspaceStore} itemKind="script" path={userDraftPath} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Separator -->
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import Alert from './common/alert/Alert.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { canWrite } from '$lib/utils'
|
||||
import { Save } from 'lucide-svelte'
|
||||
import VariableForm from './VariableForm.svelte'
|
||||
@@ -172,44 +171,14 @@
|
||||
// draft (if any) sits in `.draft` as the editor's internal
|
||||
// `VariableState` shape — the editor reads it directly.
|
||||
const savedDraftState = (v as any).draft as VariableState | undefined
|
||||
if (v.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: ws,
|
||||
itemKind: 'variable',
|
||||
path: p,
|
||||
draftOnly: v.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
const fresh = await VariableService.getVariable({
|
||||
workspace: ws,
|
||||
path: p,
|
||||
decryptSecret: false
|
||||
})
|
||||
const deployed: VariableState = {
|
||||
path: fresh.path,
|
||||
variable: {
|
||||
value: fresh.value ?? '',
|
||||
is_secret: fresh.is_secret,
|
||||
description: fresh.description ?? ''
|
||||
},
|
||||
labels: fresh.labels ?? undefined,
|
||||
wsSpecific: fresh.ws_specific ?? false
|
||||
}
|
||||
fetchedRev[ws] = fresh.edited_at
|
||||
initialStates[ws] = structuredClone(deployed)
|
||||
// Reset the live cell to the deployed state and drop the
|
||||
// DB autosave. Setting the draft to `undefined` would
|
||||
// blank the cell (and the editor) — `discard` reseeds it
|
||||
// with the deployed baseline instead. The rev is re-recorded
|
||||
// by the seeding effect on the user's next edit.
|
||||
UserDraft.discard('variable', editPath ?? '', deployed, { workspace: ws })
|
||||
}
|
||||
})
|
||||
}
|
||||
fetchedRev[ws] = v.edited_at
|
||||
// Translate the deployed wire shape into the editor's
|
||||
// `VariableState` shape. When a saved draft exists, use it
|
||||
// directly — it's already a `VariableState`.
|
||||
const s: VariableState = savedDraftState ?? {
|
||||
// The deployed baseline, translated into the editor's
|
||||
// `VariableState` shape. Kept as the dirty-check reference
|
||||
// so the "unsaved changes" banner compares draft-vs-deployed
|
||||
// instead of loaded-vs-current — when a saved draft exists,
|
||||
// the form opens with `draft != deployed` so the banner
|
||||
// fires immediately, exactly as if the user had typed.
|
||||
const deployedState: VariableState = {
|
||||
path: v.path,
|
||||
variable: {
|
||||
value: v.value ?? '',
|
||||
@@ -219,6 +188,9 @@
|
||||
labels: v.labels ?? undefined,
|
||||
wsSpecific: v.ws_specific ?? false
|
||||
}
|
||||
// What the editor opens with: the saved draft if present,
|
||||
// otherwise the deployed.
|
||||
const s: VariableState = savedDraftState ?? deployedState
|
||||
// See ResourceEditor for the same pattern: a backend that
|
||||
// moved on since the autosave was written → staleness modal;
|
||||
// otherwise just a "showing your local autosave" toast with
|
||||
@@ -237,7 +209,7 @@
|
||||
}
|
||||
}
|
||||
ensureHandle(ws, s)
|
||||
initialStates[ws] = structuredClone(s)
|
||||
initialStates[ws] = structuredClone(deployedState)
|
||||
existedInitially[ws] = true
|
||||
extraPerms[ws] = v.extra_perms ?? {}
|
||||
perWsUser[ws] = user
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -136,11 +135,12 @@
|
||||
itemKind = isFlow ? 'flow' : 'script'
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
await loadTrigger(defaultValues)
|
||||
const draftOverlay = await loadTrigger(defaultValues)
|
||||
originalConfig = structuredClone($state.snapshot(getAzureConfig()))
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay)
|
||||
if (!defaultValues) {
|
||||
initialConfig = structuredClone($state.snapshot(getAzureConfig()))
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getAzureConfig()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load Azure trigger: ${err.body}`, true)
|
||||
@@ -185,41 +185,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** See `NatsTriggerEditorInner.loadTrigger` for the rationale. */
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Record<string, any>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
const s = await AzureTriggerService.getAzureTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
getDraft: true
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_azure',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed at the field
|
||||
// level so `loadTriggerConfig` sees the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
loadTriggerConfig(effective)
|
||||
loadTriggerConfig(deployedTrigger)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedTrigger, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
} catch (error) {
|
||||
sendUserToast(`Could not load Azure trigger: ${error.body}`, true)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
} from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -132,12 +131,16 @@
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
dirtyLocalPart = false
|
||||
await loadTrigger(defaultConfig)
|
||||
const draftOverlay = await loadTrigger(defaultConfig)
|
||||
// Form holds DEPLOYED here. Capture `originalConfig` as the
|
||||
// deployed baseline so `hasChanged` (= current != originalConfig)
|
||||
// fires whenever a draft exists, not only after the user edits.
|
||||
originalConfig = structuredClone($state.snapshot(getEmailTriggerConfig())) as NewEmailTrigger
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay as Partial<EmailTrigger>)
|
||||
if (!defaultConfig) {
|
||||
// If the email trigger is loaded from the backend, we to set the initial config
|
||||
initialConfig = structuredClone($state.snapshot(getEmailTriggerConfig()))
|
||||
initialConfig = structuredClone($state.snapshot(getEmailTriggerConfig())) as NewEmailTrigger
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getEmailTriggerConfig()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load email trigger: ${err}`, true)
|
||||
@@ -207,40 +210,29 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the deployed config to the form, then return the saved-draft
|
||||
* overlay (if any) so the caller can capture the deployed-only form
|
||||
* state as `originalConfig` BEFORE applying the draft. See
|
||||
* `NatsTriggerEditorInner` for the rationale.
|
||||
*/
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Partial<EmailTrigger>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Partial<EmailTrigger>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
} else {
|
||||
const s = await EmailTriggerService.getEmailTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_email',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed at the field
|
||||
// level so `loadTriggerConfig` sees the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective: any = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
|
||||
loadTriggerConfig(effective)
|
||||
return undefined
|
||||
}
|
||||
const s = await EmailTriggerService.getEmailTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
loadTriggerConfig(deployedTrigger)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedTrigger, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
}
|
||||
|
||||
async function triggerScript(): Promise<void> {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -138,11 +137,12 @@
|
||||
itemKind = isFlow ? 'flow' : 'script'
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
await loadTrigger(defaultValues)
|
||||
const draftOverlay = await loadTrigger(defaultValues)
|
||||
originalConfig = structuredClone($state.snapshot(getGcpConfig()))
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay)
|
||||
if (!defaultValues) {
|
||||
initialConfig = structuredClone($state.snapshot(getGcpConfig()))
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getGcpConfig()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load GCP Pub/Sub trigger: ${err.body}`, true)
|
||||
@@ -189,42 +189,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** See `NatsTriggerEditorInner.loadTrigger` for the rationale. */
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Record<string, any>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
} else {
|
||||
try {
|
||||
const s = await GcpTriggerService.getGcpTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_gcp',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed at the field
|
||||
// level so `loadTriggerConfig` sees the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
loadTriggerConfig(effective)
|
||||
} catch (error) {
|
||||
sendUserToast(`Could not load GCP Pub/Sub trigger: ${error.body}`, true)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
const s = await GcpTriggerService.getGcpTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
loadTriggerConfig(deployedTrigger)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedTrigger, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
} catch (error) {
|
||||
sendUserToast(`Could not load GCP Pub/Sub trigger: ${error.body}`, true)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
generateRandomString,
|
||||
sendUserToast
|
||||
} from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2, Pipette, Plus } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -231,12 +230,13 @@
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
dirtyRoutePath = false
|
||||
await loadTrigger(defaultConfig)
|
||||
const draftOverlay = await loadTrigger(defaultConfig)
|
||||
originalConfig = structuredClone($state.snapshot(getRouteConfig())) as NewHttpTrigger
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay as Partial<HttpTrigger>)
|
||||
if (!defaultConfig) {
|
||||
// If the route is loaded from the backend, we to set the initial config
|
||||
initialConfig = structuredClone($state.snapshot(getRouteConfig()))
|
||||
initialConfig = structuredClone($state.snapshot(getRouteConfig())) as NewHttpTrigger
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getRouteConfig()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load route: ${err}`, true)
|
||||
@@ -340,40 +340,24 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
/** See `NatsTriggerEditorInner.loadTrigger` for the rationale. */
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Partial<HttpTrigger>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Partial<HttpTrigger>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
} else {
|
||||
const s = await HttpTriggerService.getHttpTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_http',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed at the field
|
||||
// level so `loadTriggerConfig` sees the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective: any = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
|
||||
loadTriggerConfig(effective)
|
||||
return undefined
|
||||
}
|
||||
const s = await HttpTriggerService.getHttpTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
loadTriggerConfig(deployedTrigger)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedTrigger, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
}
|
||||
|
||||
async function triggerScript(): Promise<void> {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import { KafkaTriggerService, type ErrorHandler, type Retry, type TriggerMode } from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2, RotateCcw } from 'lucide-svelte'
|
||||
@@ -160,11 +159,12 @@
|
||||
itemKind = isFlow ? 'flow' : 'script'
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
await loadTrigger(defaultConfig)
|
||||
const draftOverlay = await loadTrigger(defaultConfig)
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay)
|
||||
if (!defaultConfig) {
|
||||
initialConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load Kafka trigger: ${err}`, true)
|
||||
@@ -246,39 +246,24 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
/** See `NatsTriggerEditorInner.loadTrigger` for the rationale. */
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Record<string, any>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
} else {
|
||||
const s = await KafkaTriggerService.getKafkaTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_kafka',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed at the field
|
||||
// level so `loadTriggerConfig` sees the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
loadTriggerConfig(effective)
|
||||
return undefined
|
||||
}
|
||||
const s = await KafkaTriggerService.getKafkaTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
loadTriggerConfig(deployedTrigger)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedTrigger, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
}
|
||||
|
||||
function getSaveCfg(): Record<string, any> {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
@@ -156,11 +155,12 @@
|
||||
itemKind = isFlow ? 'flow' : 'script'
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
await loadTrigger(defaultConfig)
|
||||
const draftOverlay = await loadTrigger(defaultConfig)
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay)
|
||||
if (!defaultConfig) {
|
||||
initialConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load mqtt trigger: ${err.body}`, true)
|
||||
@@ -245,42 +245,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** See `NatsTriggerEditorInner.loadTrigger` for the rationale. */
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Record<string, any>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
try {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
} else {
|
||||
const s = await MqttTriggerService.getMqttTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_mqtt',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed at the field
|
||||
// level so `loadTriggerConfig` sees the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
loadTriggerConfig(effective)
|
||||
return undefined
|
||||
}
|
||||
const s = await MqttTriggerService.getMqttTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
loadTriggerConfig(deployedTrigger)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedTrigger, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
} catch (error) {
|
||||
sendUserToast(`Could not load mqtt trigger: ${error.body}`, true)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import { NatsTriggerService, type ErrorHandler, type Retry, type TriggerMode } from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
@@ -144,11 +143,18 @@
|
||||
itemKind = isFlow ? 'flow' : 'script'
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
await loadTrigger(defaultConfig)
|
||||
const draftOverlay = await loadTrigger(defaultConfig)
|
||||
// At this point the form holds the DEPLOYED config (or
|
||||
// `defaultConfig` for new triggers). Capture `originalConfig`
|
||||
// here so `hasChanged` (= `current != originalConfig`) compares
|
||||
// against the deployed baseline; if a draft exists, applying
|
||||
// the overlay below makes `current != originalConfig` fire
|
||||
// the "unsaved changes" banner immediately.
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay)
|
||||
if (!defaultConfig) {
|
||||
initialConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load nats trigger: ${err}`, true)
|
||||
@@ -228,39 +234,32 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the deployed config to the form, then return the saved-draft
|
||||
* overlay (if any) so the caller can capture the deployed-only form
|
||||
* state as `originalConfig` BEFORE applying the draft. The
|
||||
* "unsaved changes" banner compares `current` vs `originalConfig`,
|
||||
* so capturing originalConfig from the deployed-only form makes the
|
||||
* banner fire whenever a draft is present (instead of only after
|
||||
* the user starts editing on top of the draft).
|
||||
*/
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Record<string, any>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
} else {
|
||||
const s = await NatsTriggerService.getNatsTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_nats',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed at the field
|
||||
// level so `loadTriggerConfig` sees the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
loadTriggerConfig(effective)
|
||||
return undefined
|
||||
}
|
||||
const s = await NatsTriggerService.getNatsTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
loadTriggerConfig(deployedTrigger)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedTrigger, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
}
|
||||
|
||||
function getSaveCfg() {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
} from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, emptyString, emptyStringTrimmed, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
@@ -251,11 +250,12 @@
|
||||
relations = []
|
||||
transaction_to_track = []
|
||||
tab = 'basic'
|
||||
await loadTrigger(defaultConfig)
|
||||
const draftOverlay = await loadTrigger(defaultConfig)
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay)
|
||||
if (!defaultConfig) {
|
||||
initialConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load postgres trigger: ${err.body}`, true)
|
||||
@@ -368,52 +368,62 @@
|
||||
preservePermissionedAs = !!cfg?.permissioned_as
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the deployed config to the form (incl. publication fetch),
|
||||
* then return the saved-draft overlay (already merged with the
|
||||
* publication payload) so the caller can capture the deployed-only
|
||||
* form state as `originalConfig` BEFORE applying the draft. See
|
||||
* `NatsTriggerEditorInner.loadTrigger` for the broader rationale.
|
||||
*
|
||||
* Postgres-specific wrinkle: the publication payload is fetched from
|
||||
* the resource — keyed by `postgres_resource_path` /
|
||||
* `publication_name`, which the draft may have changed. Fetch once
|
||||
* using the effective values so the overlay reflects the draft's
|
||||
* publication, not the deployed one.
|
||||
*/
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Record<string, any>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
if (defaultConfig?.publication) {
|
||||
transaction_to_track = [...defaultConfig.publication.transaction_to_track]
|
||||
relations = defaultConfig.publication.table_to_track ?? []
|
||||
}
|
||||
return
|
||||
} else {
|
||||
const s = await PostgresTriggerService.getPostgresTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_postgres',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed before the
|
||||
// publication fetch — the draft might have changed
|
||||
// `postgres_resource_path` / `publication_name`, which are the
|
||||
// keys we look up the publication by.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
|
||||
const publication_data = await PostgresTriggerService.getPostgresPublication({
|
||||
path: effective.postgres_resource_path,
|
||||
workspace: $workspaceStore!,
|
||||
publication: effective.publication_name
|
||||
})
|
||||
|
||||
loadTriggerConfig({ ...effective, publication: publication_data })
|
||||
return undefined
|
||||
}
|
||||
const s = await PostgresTriggerService.getPostgresTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
|
||||
// Fetch deployed publication and apply deployed config — this
|
||||
// becomes the `originalConfig` baseline for the dirty check.
|
||||
const deployedPublication = await PostgresTriggerService.getPostgresPublication({
|
||||
path: deployedTrigger.postgres_resource_path,
|
||||
workspace: $workspaceStore!,
|
||||
publication: deployedTrigger.publication_name
|
||||
})
|
||||
loadTriggerConfig({ ...deployedTrigger, publication: deployedPublication })
|
||||
|
||||
if (!draftFromBackend) return undefined
|
||||
|
||||
// Draft may have changed the resource/publication keys; fetch
|
||||
// the publication that matches the effective values so the
|
||||
// overlay opens on the draft's view.
|
||||
const effective = { ...deployedTrigger, ...draftFromBackend }
|
||||
const effectivePublication =
|
||||
effective.postgres_resource_path === deployedTrigger.postgres_resource_path &&
|
||||
effective.publication_name === deployedTrigger.publication_name
|
||||
? deployedPublication
|
||||
: await PostgresTriggerService.getPostgresPublication({
|
||||
path: effective.postgres_resource_path,
|
||||
workspace: $workspaceStore!,
|
||||
publication: effective.publication_name
|
||||
})
|
||||
return { ...effective, publication: effectivePublication }
|
||||
}
|
||||
|
||||
function getCaptureConfig() {
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
} from '$lib/gen'
|
||||
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, emptyString, formatCron, sendUserToast, cronV1toV2 } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { base } from '$lib/base'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { List, Loader2, Save, AlertTriangle } from 'lucide-svelte'
|
||||
@@ -153,11 +152,15 @@
|
||||
initialPath = ePath
|
||||
itemKind = isFlow ? 'flow' : 'script'
|
||||
path = defaultCfg?.path ?? ePath
|
||||
await loadSchedule(defaultCfg)
|
||||
const draftOverlay = await loadSchedule(defaultCfg)
|
||||
edit = true
|
||||
if (!defaultCfg) {
|
||||
// Form holds DEPLOYED here. Capture `initialConfig` as the
|
||||
// deployed baseline so the dirty check / unsaved banner
|
||||
// fires whenever a saved draft exists.
|
||||
initialConfig = structuredClone($state.snapshot(getScheduleCfg()))
|
||||
}
|
||||
if (draftOverlay) await loadScheduleCfg(draftOverlay)
|
||||
await draftSync.maybeRestore()
|
||||
} finally {
|
||||
clearTimeout(loadingTimeout)
|
||||
@@ -294,23 +297,10 @@
|
||||
path: schedule_path,
|
||||
getDraft
|
||||
})
|
||||
if (resp.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_schedule',
|
||||
path: schedule_path,
|
||||
draftOnly: resp.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await openNew(nis_flow, initial_script_path, defaultValues, schedule_path, {
|
||||
getDraft: false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
// The autosaved draft (when present) sits in `.draft` as the
|
||||
// editor's saved Schedule shape. Layer it over the deployed
|
||||
// at the field level so downstream `loadScheduleCfg` sees
|
||||
// the editor's last-saved state.
|
||||
// at the field level so the inline form assignments below
|
||||
// see the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedSchedule } = resp as any
|
||||
s = draftFromBackend
|
||||
? ({ ...deployedSchedule, ...draftFromBackend } as Schedule)
|
||||
@@ -477,41 +467,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the deployed schedule config to the form, then return the
|
||||
* saved-draft overlay (if any) so the caller can capture the
|
||||
* deployed-only form state as `initialConfig` BEFORE applying the
|
||||
* draft. The "unsaved changes" banner compares `current` vs
|
||||
* `initialConfig` (via `useTriggerDraftSync.deployed`), so capturing
|
||||
* from the deployed-only form makes the banner fire whenever a
|
||||
* draft is present.
|
||||
*/
|
||||
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
|
||||
})
|
||||
if (s.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_schedule',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadSchedule(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed so
|
||||
// `loadScheduleCfg` sees the editor's last-saved fields.
|
||||
const { draft: draftFromBackend, ...deployedSchedule } = s as any
|
||||
const effectiveSchedule = draftFromBackend
|
||||
? { ...deployedSchedule, ...draftFromBackend }
|
||||
: deployedSchedule
|
||||
await loadScheduleCfg(effectiveSchedule)
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load schedule: ${err}`, true)
|
||||
}
|
||||
} else {
|
||||
defaultCfg?: Record<string, any>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
if (defaultCfg) {
|
||||
await loadScheduleCfg(defaultCfg)
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
const s = await ScheduleService.getSchedule({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedSchedule } = s as any
|
||||
await loadScheduleCfg(deployedSchedule)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedSchedule, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load schedule: ${err}`, true)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, capitalize, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Label from '$lib/components/Label.svelte'
|
||||
@@ -139,14 +138,16 @@
|
||||
itemKind = isFlow ? 'flow' : 'script'
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
await loadTrigger(defaultConfig)
|
||||
// Snapshot the *backend* config as the baseline before overlaying
|
||||
// any local autosave, so hasChanged / onConfigChange correctly
|
||||
// flag the local edits as unsaved changes.
|
||||
const draftOverlay = await loadTrigger(defaultConfig)
|
||||
// Snapshot the *deployed* config as the baseline before
|
||||
// overlaying the saved draft, so hasChanged compares
|
||||
// draft-vs-deployed and the banner fires whenever a draft
|
||||
// exists.
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay)
|
||||
if (!defaultConfig) {
|
||||
initialConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load sqs trigger: ${err.body}`, true)
|
||||
@@ -222,42 +223,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** See `NatsTriggerEditorInner.loadTrigger` for the rationale. */
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Record<string, any>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
try {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
} else {
|
||||
const s = await SqsTriggerService.getSqsTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_sqs',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed at the field
|
||||
// level so `loadTriggerConfig` sees the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
loadTriggerConfig(effective)
|
||||
return undefined
|
||||
}
|
||||
const s = await SqsTriggerService.getSqsTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
loadTriggerConfig(deployedTrigger)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedTrigger, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
} catch (error) {
|
||||
sendUserToast(`Could not load SQS trigger: ${error.body}`, true)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
} from '$lib/gen'
|
||||
import { usedTriggerKinds, userStore, workspaceStore } from '$lib/stores'
|
||||
import { canWrite, emptySchema, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { notifyDraftLoaded } from '$lib/userDraftToast'
|
||||
import { withForkConflictRetry } from '$lib/utils/forkConflict'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { Loader2, X, Plus } from 'lucide-svelte'
|
||||
@@ -188,11 +187,12 @@
|
||||
edit = true
|
||||
dirtyPath = false
|
||||
dirtyUrl = false
|
||||
await loadTrigger(defaultConfig)
|
||||
const draftOverlay = await loadTrigger(defaultConfig)
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
if (draftOverlay) loadTriggerConfig(draftOverlay)
|
||||
if (!defaultConfig) {
|
||||
initialConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
}
|
||||
originalConfig = structuredClone($state.snapshot(getSaveCfg()))
|
||||
await draftSync.maybeRestore()
|
||||
} catch (err) {
|
||||
sendUserToast(`Could not load websocket trigger: ${err}`, true)
|
||||
@@ -303,39 +303,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** See `NatsTriggerEditorInner.loadTrigger` for the rationale. */
|
||||
async function loadTrigger(
|
||||
defaultConfig?: Record<string, any>,
|
||||
opts: { getDraft?: boolean } = {}
|
||||
): Promise<void> {
|
||||
const getDraft = opts.getDraft ?? true
|
||||
defaultConfig?: Record<string, any>
|
||||
): Promise<Record<string, any> | undefined> {
|
||||
if (defaultConfig) {
|
||||
loadTriggerConfig(defaultConfig)
|
||||
return
|
||||
} else {
|
||||
const s = await WebsocketTriggerService.getWebsocketTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft
|
||||
})
|
||||
if (s?.is_draft) {
|
||||
notifyDraftLoaded({
|
||||
workspace: $workspaceStore!,
|
||||
itemKind: 'trigger_websocket',
|
||||
path: initialPath,
|
||||
draftOnly: s.no_deployed,
|
||||
onResetToDeployed: async () => {
|
||||
await loadTrigger(undefined, { getDraft: false })
|
||||
}
|
||||
})
|
||||
}
|
||||
// Layer the saved draft (if any) over the deployed at the field
|
||||
// level so `loadTriggerConfig` sees the editor's last-saved state.
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
const effective = draftFromBackend
|
||||
? { ...deployedTrigger, ...draftFromBackend }
|
||||
: deployedTrigger
|
||||
loadTriggerConfig(effective)
|
||||
return undefined
|
||||
}
|
||||
const s = await WebsocketTriggerService.getWebsocketTrigger({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
getDraft: true
|
||||
})
|
||||
const { draft: draftFromBackend, ...deployedTrigger } = (s ?? {}) as any
|
||||
loadTriggerConfig(deployedTrigger)
|
||||
return draftFromBackend
|
||||
? ({ ...deployedTrigger, ...draftFromBackend } as Record<string, any>)
|
||||
: undefined
|
||||
}
|
||||
|
||||
let initialMessageRunnableSchemas: Record<string, Schema> = $state({})
|
||||
|
||||
Reference in New Issue
Block a user