diff --git a/frontend/src/lib/components/assets/AssetGraph/AssetGraphCanvas.svelte b/frontend/src/lib/components/assets/AssetGraph/AssetGraphCanvas.svelte index 4b53b32fb4..8a06ae70f0 100644 --- a/frontend/src/lib/components/assets/AssetGraph/AssetGraphCanvas.svelte +++ b/frontend/src/lib/components/assets/AssetGraph/AssetGraphCanvas.svelte @@ -317,17 +317,15 @@ } let flowEl: HTMLElement | undefined = $state() const nodeUnder = (x: number, y: number) => - [...(flowEl?.querySelectorAll('.svelte-flow__node') ?? [])] + Array.from(flowEl?.querySelectorAll('.svelte-flow__node') ?? []) .find((el) => within(el, x, y)) ?.getAttribute('data-id') ?? undefined const overBlank = (x: number, y: number) => !!flowEl && within(flowEl, x, y) && - ![ - ...flowEl.querySelectorAll( - '.svelte-flow__panel, .svelte-flow__controls, .svelte-flow__minimap' - ) - ].some((el) => within(el, x, y)) + !Array.from( + flowEl.querySelectorAll('.svelte-flow__panel, .svelte-flow__controls, .svelte-flow__minimap') + ).some((el) => within(el, x, y)) const readsAsset = (assetId: string, scriptId: string) => model.edges.some((x) => x.kind === 'lineage-read' && x.source === assetId && x.target === scriptId) const assetOf = (assetId: string) => { @@ -1504,7 +1502,10 @@ // Its scripts' own triggers: a row that starts only them, which // webhooks and data uploads (no row) and missing ones are not. triggerDeletes: scriptIds.flatMap((id) => { - const scriptPath = runnables.get(id)?.data?.path + const runnable = runnables.get(id)?.data + // A flow is not deleted from here, and neither are its triggers. + if (runnable?.runnable_kind !== 'script') return [] + const scriptPath = runnable.path return m.edges .filter((e) => e.kind === 'trigger-native' && e.target === id) .map((e) => m.nodes.find((n) => n.id === e.source)?.data) @@ -1742,7 +1743,11 @@ ...up, selected: up.scripts.some( (sc: { runnableId: string }) => sc.runnableId === selectedId - ) + ), + triggers: (up.triggers ?? []).map((t: { nodeId?: string }) => ({ + ...t, + selected: !!t.nodeId && t.nodeId === activeTriggerId + })) } } : up?.runnableId !== undefined diff --git a/frontend/src/lib/components/assets/AssetGraph/AssetNode.svelte b/frontend/src/lib/components/assets/AssetGraph/AssetNode.svelte index 37006afa0b..6e6c8e03f9 100644 --- a/frontend/src/lib/components/assets/AssetGraph/AssetNode.svelte +++ b/frontend/src/lib/components/assets/AssetGraph/AssetNode.svelte @@ -468,6 +468,12 @@ fix={data.foldedFixes[0]} label={`${data.foldedFixes.length} error${data.foldedFixes.length === 1 ? '' : 's'} ยท Fix`} /> + {:else if upstream && coTriggers.length > 1 && !coTriggers.some((t) => t.onOpen)} + + Multiple triggers {:else if upstream && coTriggers.length > 1} diff --git a/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.ts b/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.ts index ac2b366a93..c7bf85d144 100644 --- a/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.ts +++ b/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.ts @@ -19,7 +19,6 @@ export type ViewEdge = { export const noAssetNodeId = (runnableId: string) => `no-asset:${runnableId}` export const isNoAssetNode = (id: string) => id.startsWith('no-asset:') -/** The script and trigger an assets-only asset's delete removes. */ /** What an assets-only delete removes: the script, its trigger, or both. */ export type AssetUpstreamDelete = { script?: { path: string; unsaved: boolean } diff --git a/frontend/src/routes/(root)/(logged)/pipeline/[folder]/+page.svelte b/frontend/src/routes/(root)/(logged)/pipeline/[folder]/+page.svelte index 5a7fda56c8..bd18ef73f1 100644 --- a/frontend/src/routes/(root)/(logged)/pipeline/[folder]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/pipeline/[folder]/+page.svelte @@ -3562,7 +3562,11 @@ : assetDeleteTarget?.script ? `${canHardDeleteScripts ? 'Delete' : 'Archive'} script?` : 'Delete trigger?'} - confirmationText="Delete" + confirmationText={assetDeleteTarget?.script && !canHardDeleteScripts + ? assetDeleteTarget.trigger + ? 'Archive and delete' + : 'Archive' + : 'Delete'} onConfirmed={confirmAssetUpstreamDelete} onCanceled={() => { if (!assetDeleteLoading) assetDeleteTarget = undefined