mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 08:02:47 +00:00
fix: hide subscribed-only unwritten assets, review fixes for the edit pane and edge actions
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
0024e623c2
commit
5edcb14303
@@ -328,7 +328,14 @@
|
||||
model.edges.some((x) => x.kind === kind && x.source === e.source && x.target === id)
|
||||
const target = { script: script.path as string, asset, reads: has('lineage-read') }
|
||||
if (has('trigger-asset')) stop.push(target)
|
||||
else if (target.reads) listen.push(target)
|
||||
// A script rerunning on its own writes would loop: offered only for inputs.
|
||||
else if (
|
||||
target.reads &&
|
||||
!model.edges.some(
|
||||
(x) => x.kind === 'lineage-write' && x.source === id && x.target === e.source
|
||||
)
|
||||
)
|
||||
listen.push(target)
|
||||
}
|
||||
const where = (ts: EdgeTarget[]) =>
|
||||
ts.length === 1 ? ts[0].script : `${ts.length} scripts: ${ts.map((t) => t.script).join(', ')}`
|
||||
@@ -343,7 +350,10 @@
|
||||
if (stop.length) {
|
||||
out.push({
|
||||
label: 'Stop rerunning on writes',
|
||||
detail: `${stopListeningChangeText(asset, stop.some((t) => t.reads))} in ${where(stop)}; only the annotation header changes. Saved as a draft.`,
|
||||
detail: `${stopListeningChangeText(
|
||||
asset,
|
||||
stop.some((t) => t.reads)
|
||||
)} in ${where(stop)}; only the annotation header changes. Saved as a draft.`,
|
||||
run: () => onEdgeAction?.({ action: 'stop', targets: stop })
|
||||
})
|
||||
}
|
||||
@@ -356,7 +366,8 @@
|
||||
const nameOf = (id: string) => {
|
||||
const n = byId.get(id)
|
||||
if (!n) return id.replace(/^[^:]+:/, '')
|
||||
if (n.type === 'asset') return formatShortAssetPath({ kind: n.data.asset_kind, path: n.data.path })
|
||||
if (n.type === 'asset')
|
||||
return formatShortAssetPath({ kind: n.data.asset_kind, path: n.data.path })
|
||||
if (n.type === 'no-asset') return 'the scripts that build no asset'
|
||||
if (n.type === 'trigger') return n.data.summary || n.data.ref
|
||||
return n.data.summary || n.data.path
|
||||
@@ -1227,13 +1238,20 @@
|
||||
}
|
||||
: onDeleteAssetUpstream && u
|
||||
? {
|
||||
deleteBlocked: !u.multiple
|
||||
? r?.runnable_kind === 'flow'
|
||||
? 'a flow builds it'
|
||||
: 'its script builds other assets too'
|
||||
: u.runnableIds.length > 1
|
||||
? 'several scripts build it'
|
||||
: 'its script has several triggers'
|
||||
// The No-asset card stands for its scripts; an asset is built by them.
|
||||
deleteBlocked: data.noAsset
|
||||
? !u.multiple
|
||||
? 'it stands for a flow'
|
||||
: u.runnableIds.length > 1
|
||||
? 'it stands for several scripts'
|
||||
: 'its script has several triggers'
|
||||
: !u.multiple
|
||||
? r?.runnable_kind === 'flow'
|
||||
? 'a flow builds it'
|
||||
: 'its script builds other assets too'
|
||||
: u.runnableIds.length > 1
|
||||
? 'several scripts build it'
|
||||
: 'its script has several triggers'
|
||||
}
|
||||
: {})
|
||||
}
|
||||
@@ -1903,10 +1921,7 @@
|
||||
{/if}
|
||||
|
||||
{#if scheduleWizard}
|
||||
<div
|
||||
class="fixed w-0 h-0"
|
||||
style="left: {scheduleWizard.at.x}px; top: {scheduleWizard.at.y}px;"
|
||||
>
|
||||
<div class="fixed w-0 h-0" style="left: {scheduleWizard.at.x}px; top: {scheduleWizard.at.y}px;">
|
||||
<PipelineInsertMenu
|
||||
kinds={[{ id: 'schedule', label: 'On schedule', description: '', icon: Clock }]}
|
||||
onPick={() => {}}
|
||||
|
||||
@@ -185,6 +185,8 @@
|
||||
if (pe.folder !== f) {
|
||||
if (pe.folder !== undefined) {
|
||||
pe.reset()
|
||||
// The set-aside edit pane belongs to the folder being left.
|
||||
editPane = undefined
|
||||
// Re-scope the Global chat's pipeline prompt to the new folder. The
|
||||
// helper methods already read the reactive folder, but the system
|
||||
// message string was built for the old one and is only rebuilt when
|
||||
@@ -241,7 +243,7 @@
|
||||
if (m === 'view') url.searchParams.delete('mode')
|
||||
else url.searchParams.set('mode', m)
|
||||
// Same-pathname navigation — the drafts leave-guard skips it.
|
||||
goto(url, { replaceState: opts?.replace ?? false, keepFocus: true, noScroll: true })
|
||||
return goto(url, { replaceState: opts?.replace ?? false, keepFocus: true, noScroll: true })
|
||||
}
|
||||
// View-mode drafts overlay: "what View will show once the drafts are
|
||||
// deployed". A view variant rather than a third mode — ephemeral (not
|
||||
@@ -249,17 +251,6 @@
|
||||
// shared link couldn't reproduce it anyway).
|
||||
let includeDrafts = $state(false)
|
||||
|
||||
// Asset-kind → syntax-prefix for `// on <ref>` reconstruction. Mirrors
|
||||
// ASSET_KINDS in backend/parsers/windmill-parser/src/asset_parser.rs.
|
||||
const ASSET_PREFIX: Record<AssetKind, string> = {
|
||||
s3object: 's3://',
|
||||
resource: '$res:',
|
||||
ducklake: 'ducklake://',
|
||||
datatable: 'datatable://',
|
||||
volume: 'volume://',
|
||||
dbt: 'dbt://'
|
||||
}
|
||||
|
||||
// Path-input split for the insert menu: a read-only `f/<folder>/` chip
|
||||
// on the left the user can't delete, plus an editable suffix seeded
|
||||
// with a placeholder name.
|
||||
@@ -1210,7 +1201,7 @@
|
||||
aiPrompt?: string,
|
||||
options?: PipelineInsertOptions
|
||||
) {
|
||||
const ref = `${ASSET_PREFIX[asset.kind]}${asset.path}`
|
||||
const ref = assetRef(asset)
|
||||
openMaterializerDraft(
|
||||
language,
|
||||
scriptPath,
|
||||
@@ -1306,7 +1297,9 @@
|
||||
): Promise<boolean> {
|
||||
const ws = $workspaceStore
|
||||
if (!ws) return false
|
||||
if (mode !== 'edit') setMode('edit')
|
||||
// Wait for Edit: the switch reopens Edit's set-aside pane when it lands, which
|
||||
// would take the place of the draft opened below.
|
||||
if (mode !== 'edit') await setMode('edit')
|
||||
pe.selection = undefined
|
||||
pe.activeDraftPath = undefined
|
||||
await tick()
|
||||
@@ -1430,6 +1423,15 @@
|
||||
scheduleFor: {
|
||||
script: path,
|
||||
onSchedule: async (schedule) => {
|
||||
// Checked first: a header saying `on schedule` with no schedule
|
||||
// behind it would be a new error, not a fix.
|
||||
if (pe.triggerDrafts.has(triggerDraftKey('schedule', schedule.path))) {
|
||||
sendUserToast(
|
||||
`Another draft schedule already uses the path ${schedule.path}`,
|
||||
true
|
||||
)
|
||||
return
|
||||
}
|
||||
const ok = await editScriptHeader(
|
||||
path,
|
||||
(c) => addTriggerDirective(c, 'schedule'),
|
||||
@@ -1741,22 +1743,29 @@
|
||||
const showDrafts = mode === 'edit' || includeDrafts
|
||||
const errors = nodeErrors
|
||||
const schedules = scheduleInfo.current
|
||||
// An asset drawn only because a script subscribes to it, which nothing ever
|
||||
// writes, stands for nothing: it is left out, and the subscriber carries the
|
||||
// error. One a script reads or writes stays.
|
||||
const used = new Set(g.edges.map((e) => `${e.asset_kind}:${e.asset_path}`))
|
||||
const phantom = new Set([...errors.unwritten.keys()].filter((k) => !used.has(k)))
|
||||
const triggers = g.triggers.filter(
|
||||
(t) => t.trigger_kind !== 'asset' || !phantom.has(`${t.asset_kind}:${t.asset_path}`)
|
||||
)
|
||||
return {
|
||||
...g,
|
||||
triggers: schedules?.size
|
||||
? g.triggers.map((t) =>
|
||||
? triggers.map((t) =>
|
||||
t.trigger_kind === 'schedule' && t.path && !t.draft && schedules.has(t.path)
|
||||
? { ...t, ...schedules.get(t.path) }
|
||||
: t
|
||||
)
|
||||
: g.triggers,
|
||||
assets:
|
||||
errors.assets.size === 0
|
||||
? g.assets
|
||||
: g.assets.map((a) => {
|
||||
const error = errors.assets.get(`${a.kind}:${a.path}`)
|
||||
return error ? { ...a, error } : a
|
||||
}),
|
||||
: triggers,
|
||||
assets: g.assets
|
||||
.filter((a) => !phantom.has(`${a.kind}:${a.path}`))
|
||||
.map((a) => {
|
||||
const error = errors.assets.get(`${a.kind}:${a.path}`)
|
||||
return error ? { ...a, error } : a
|
||||
}),
|
||||
runnables: g.runnables.map((r) => {
|
||||
const draft = showDrafts ? pe.drafts.get(r.path)?.script : undefined
|
||||
const meta = scriptMetaByPath.get(r.path)
|
||||
|
||||
Reference in New Issue
Block a user