mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-12 16:02:56 +00:00
fix: lineage follows the editor that inferred it; upload runs preview unsaved edits of deployed nodes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
66e4f0072e
commit
d4a93d1e4f
@@ -304,12 +304,13 @@
|
||||
// its editor. `onRunInputs` runs it with them, drafts included, and
|
||||
// `onShowScript` leaves this view for the script.
|
||||
inputsOnly?: boolean
|
||||
/** `content` is the pane's live buffer, newer than the stored draft when it was
|
||||
* edited before switching to the inputs view. */
|
||||
/** `live` is the pane's buffer when it differs from what is stored: always
|
||||
* for a draft, and for a deployed script edited before switching to the
|
||||
* inputs view. A run with it previews that body. */
|
||||
onRunInputs?: (
|
||||
path: string,
|
||||
args: Record<string, any>,
|
||||
content?: string
|
||||
live?: { content: string; language: ScriptLang }
|
||||
) => Promise<string | undefined>
|
||||
onShowScript?: () => void
|
||||
}
|
||||
@@ -525,15 +526,12 @@
|
||||
// alongside `liveBodyAssets` and forwarded so the live graph can show
|
||||
// inferred column lineage on the edited script before it deploys.
|
||||
let liveColumnLineage = $state<ColumnLineage[] | undefined>(undefined)
|
||||
// The script `liveBodyAssets` was inferred for: they stay bound after its editor
|
||||
// unmounts, so a script with no editor of its own (the inputs-only view) must
|
||||
// not report or persist them as its lineage. Not reset on a switch: the
|
||||
// previous script's save-on-switch cleanup still reads its own.
|
||||
// The script whose editor last bound `liveBodyAssets` (undefined there means it
|
||||
// inferred nothing). They stay bound after that editor unmounts, so a script
|
||||
// that never had one (the inputs-only view) must not report or persist them as
|
||||
// its lineage. Set by an effect declared after the save-on-switch one, whose
|
||||
// cleanup still needs the previous owner when the selection moves.
|
||||
let liveAssetsOwner: string | undefined = undefined
|
||||
$effect.pre(() => {
|
||||
const assets = liveBodyAssets
|
||||
untrack(() => (liveAssetsOwner = assets === undefined ? undefined : script?.path))
|
||||
})
|
||||
|
||||
// Bumped when the runs panel reports a watched job has reached a
|
||||
// terminal state. Drives S3FilePreview's refreshKey so the preview
|
||||
@@ -651,7 +649,7 @@
|
||||
return () => {
|
||||
// No editor inferred this script (the inputs-only view): keep its stored
|
||||
// lineage rather than persisting it as empty.
|
||||
const inferred = liveBodyAssets !== undefined && liveAssetsOwner === captured.path
|
||||
const inferred = liveAssetsOwner === captured.path
|
||||
const writes = !inferred
|
||||
? (writesAtRegister ?? [])
|
||||
: (liveBodyAssets ?? [])
|
||||
@@ -712,12 +710,23 @@
|
||||
}
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (script && !readOnly && !inputsOnly) liveAssetsOwner = script.path
|
||||
})
|
||||
|
||||
let saving = $state(false)
|
||||
// What this pane last deployed, so the persist-back cleanup can tell "the
|
||||
// buffer equals the new deployed head" from real unsaved edits (plain
|
||||
// variable: only read inside the untracked cleanup).
|
||||
let deployedFromPane: { path: string; content: string } | undefined = undefined
|
||||
let isDraft = $derived(draftScript != undefined)
|
||||
function liveBody(): { content: string; language: ScriptLang } | undefined {
|
||||
if (!script) return undefined
|
||||
const live = { content: script.content ?? '', language: script.language as ScriptLang }
|
||||
if (isDraft) return live
|
||||
const orig = scriptRes.current
|
||||
return orig?.path === script.path && (orig.content ?? '') !== live.content ? live : undefined
|
||||
}
|
||||
// A draft's stored schema is empty until an editor infers it, and the inputs
|
||||
// view mounts none: infer from the body here so its fields (the file picker)
|
||||
// show up.
|
||||
@@ -1396,7 +1405,7 @@
|
||||
runsDrafts
|
||||
inputsOnly
|
||||
{onShowScript}
|
||||
onRun={onRunInputs && ((path, args) => onRunInputs(path, args, script?.content))}
|
||||
onRun={onRunInputs && ((path, args) => onRunInputs(path, args, liveBody()))}
|
||||
script={inputsScript ?? script}
|
||||
{runsRefreshKey}
|
||||
{runsPendingJobId}
|
||||
|
||||
@@ -221,7 +221,7 @@
|
||||
onRunInputs?: (
|
||||
path: string,
|
||||
args: Record<string, any>,
|
||||
content?: string
|
||||
live?: { content: string; language: ScriptLang }
|
||||
) => Promise<string | undefined>
|
||||
onShowScript?: () => void
|
||||
// Data-upload entry scripts whose staged upload is ready (green node).
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
AssetGraphSelection,
|
||||
NativeTriggerKind
|
||||
} from '$lib/components/assets/AssetGraph/types'
|
||||
import { AssetService, JobService } from '$lib/gen'
|
||||
import { AssetService, JobService, type ScriptLang } from '$lib/gen'
|
||||
import { DATA_ASSET_KINDS } from '$lib/components/assets/AssetGraph/cascadeRun'
|
||||
import { usePipelineAssetPrefetch } from '$lib/components/assets/AssetGraph/pipelineAssetPrefetch.svelte'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
@@ -263,25 +263,27 @@
|
||||
let runsRefreshKey = $state(0)
|
||||
let runsPendingJobId = $state<string | undefined>(undefined)
|
||||
|
||||
// `content` overrides the stored draft body with a newer one (an open editor's).
|
||||
// `live` is an open editor's body, newer than what is stored: it is previewed,
|
||||
// a deployed node included.
|
||||
async function runNode(
|
||||
nodePath: string,
|
||||
args: Record<string, any> = {},
|
||||
cascade = false,
|
||||
content?: string
|
||||
live?: { content: string; language: ScriptLang }
|
||||
): Promise<string | undefined> {
|
||||
const draft = pe.drafts.get(nodePath)
|
||||
const body = live ?? (draft && { content: draft.script.content, language: draft.script.language })
|
||||
activeRunnables.arm(`script:${nodePath}`)
|
||||
try {
|
||||
let jobId: string
|
||||
if (draft) {
|
||||
// Preview-run the draft content; a preview never dispatches downstream.
|
||||
if (body) {
|
||||
// Preview-run the unsaved body; a preview never dispatches downstream.
|
||||
jobId = await JobService.runScriptPreview({
|
||||
workspace: workspaceId,
|
||||
requestBody: {
|
||||
path: nodePath,
|
||||
content: content ?? draft.script.content,
|
||||
language: draft.script.language,
|
||||
content: body.content,
|
||||
language: body.language,
|
||||
args
|
||||
}
|
||||
})
|
||||
@@ -513,7 +515,7 @@
|
||||
onRunByPath={(path, args) => runNode(path, args)}
|
||||
inputsOnly={uploadInputsPath !== undefined &&
|
||||
(pe.openScriptPath === undefined || uploadInputsPath === pe.openScriptPath)}
|
||||
onRunInputs={(path, args, content) => runNode(path, args, false, content)}
|
||||
onRunInputs={(path, args, live) => runNode(path, args, false, live)}
|
||||
onShowScript={() => (uploadInputsPath = undefined)}
|
||||
canRunByPath
|
||||
onTestStateChange={(running) => {
|
||||
|
||||
@@ -2504,24 +2504,27 @@
|
||||
let uploadInputsPath = $state<string | undefined>(undefined)
|
||||
// Runs the open node with its form inputs: a draft previews its own content, a
|
||||
// deployed script runs for real (in view mode, cascading like any legit run).
|
||||
// `content` is the pane's live buffer, newer than the stored draft when it was
|
||||
// edited before switching to the inputs view.
|
||||
// `live` is the pane's buffer when it differs from what is stored (a draft, or a
|
||||
// deployed node edited before switching to the inputs view): it is previewed.
|
||||
async function runUploadInputs(
|
||||
path: string,
|
||||
args: Record<string, any>,
|
||||
content?: string
|
||||
live?: { content: string; language: ScriptLang }
|
||||
): Promise<string | undefined> {
|
||||
const draft = mode === 'edit' ? pe.drafts.get(path) : undefined
|
||||
if (!draft) return mode === 'edit' ? runDeployedWithArgs(path, args) : runByPathLegit(path, args)
|
||||
const body = content ?? draft.script.content
|
||||
if (!$workspaceStore || !body || !draft.script.language) return undefined
|
||||
const body =
|
||||
mode === 'edit'
|
||||
? (live ?? (draft && { content: draft.script.content, language: draft.script.language }))
|
||||
: undefined
|
||||
if (!body) return mode === 'edit' ? runDeployedWithArgs(path, args) : runByPathLegit(path, args)
|
||||
if (!$workspaceStore || !body.content || !body.language) return undefined
|
||||
activeRunnables.arm(`script:${path}`)
|
||||
try {
|
||||
const jobId = await JobService.runScriptPreview({
|
||||
workspace: $workspaceStore,
|
||||
requestBody: {
|
||||
content: body,
|
||||
language: draft.script.language,
|
||||
content: body.content,
|
||||
language: body.language,
|
||||
path,
|
||||
args: { ...args, _wmill_skip_asset_dispatch: true }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user