diff --git a/frontend/src/lib/components/assets/AssetGraph/AssetGraphCanvas.svelte b/frontend/src/lib/components/assets/AssetGraph/AssetGraphCanvas.svelte
index 39fc1fa9d2..aef3469414 100644
--- a/frontend/src/lib/components/assets/AssetGraph/AssetGraphCanvas.svelte
+++ b/frontend/src/lib/components/assets/AssetGraph/AssetGraphCanvas.svelte
@@ -25,6 +25,7 @@
assetsOnlyView,
upstreamDeletion,
NO_ASSET_NODE_ID,
+ withoutPassiveReads,
type AssetUpstream,
type AssetUpstreamDelete
} from './assetsOnlyView'
@@ -1359,7 +1360,21 @@
}
return { ...m, nodes, edges }
}
- let view = $derived(assetsOnly ? assetsOnlyModel(model) : model)
+ // Reads that start nothing (lookups, reference tables) are often most of a
+ // graph's edges; hiding them leaves what makes the pipeline run. Remembered
+ // across pipelines.
+ const SHOW_PASSIVE_READS_KEY = 'pipeline-show-passive-reads'
+ let showPassiveReads = $state(true)
+ try {
+ showPassiveReads = localStorage.getItem(SHOW_PASSIVE_READS_KEY) !== 'false'
+ } catch {}
+ let withoutPassive = $derived(withoutPassiveReads(model.nodes, model.edges))
+ let shownModel = $derived(
+ showPassiveReads || withoutPassive.dropped === 0
+ ? model
+ : { ...model, nodes: withoutPassive.nodes, edges: withoutPassive.edges }
+ )
+ let view = $derived(assetsOnly ? assetsOnlyModel(shownModel) : shownModel)
// dbt association, surfaced by emphasis instead of edges. Hovering a model's
// dbt badge lights up the project node that materializes it; hovering the
@@ -1901,7 +1916,7 @@
{#if assetsOnlyToggle}
-
+
+ {#if withoutPassive.dropped > 0}
+ {
+ showPassiveReads = e.detail
+ try {
+ localStorage.setItem(SHOW_PASSIVE_READS_KEY, String(e.detail))
+ } catch {}
+ }}
+ />
+ {/if}
{/if}
{#if showMinimap}
diff --git a/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.test.ts b/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.test.ts
index 8c68991e0b..12a0303178 100644
--- a/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.test.ts
+++ b/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.test.ts
@@ -3,6 +3,7 @@ import {
assetsOnlyView,
upstreamDeletion,
NO_ASSET_NODE_ID,
+ withoutPassiveReads,
type AssetUpstream
} from './assetsOnlyView'
@@ -134,3 +135,28 @@ describe('upstreamDeletion', () => {
expect(upstreamDeletion(u, script, all)).toBeUndefined()
})
})
+
+describe('withoutPassiveReads', () => {
+ it('drops reads that trigger nothing, and the assets only they drew', () => {
+ const nodes = [
+ node('asset:src', 'asset'),
+ node('asset:lookup', 'asset'),
+ node('asset:out', 'asset'),
+ node('script:a', 'runnable')
+ ]
+ const edges = [
+ edge('asset:src', 'script:a', 'lineage-read'),
+ edge('asset:src', 'script:a', 'trigger-asset'),
+ edge('asset:lookup', 'script:a', 'lineage-read'),
+ edge('asset:out', 'script:a', 'lineage-read'),
+ edge('script:a', 'asset:out', 'lineage-write')
+ ]
+ const r = withoutPassiveReads(nodes, edges)
+ expect(r.nodes.map((n) => n.id)).toEqual(['asset:src', 'asset:out', 'script:a'])
+ expect(r.edges.map((e) => e.id)).toEqual([
+ 'lineage-read:asset:src->script:a',
+ 'trigger-asset:asset:src->script:a',
+ 'lineage-write:script:a->asset:out'
+ ])
+ })
+})
diff --git a/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.ts b/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.ts
index 0cef27f8c6..d9c102018c 100644
--- a/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.ts
+++ b/frontend/src/lib/components/assets/AssetGraph/assetsOnlyView.ts
@@ -186,3 +186,32 @@ export function upstreamDeletion(
trigger: ownTrigger ? { kind, path: t.data.ref, draft: !!t.data.draft } : undefined
}
}
+
+/**
+ * The graph without its reads that start nothing: a read edge with no asset
+ * trigger along it, and the assets only such reads drew.
+ */
+export function withoutPassiveReads(
+ nodes: N[],
+ edges: E[]
+): { nodes: N[]; edges: E[]; dropped: number } {
+ const triggers = new Set(
+ edges.filter((e) => e.kind === 'trigger-asset').map((e) => `${e.source}\n${e.target}`)
+ )
+ const passive = (e: E) =>
+ e.kind === 'lineage-read' && !triggers.has(`${e.source}\n${e.target}`)
+ const kept = edges.filter((e) => !passive(e))
+ const dropped = edges.length - kept.length
+ if (dropped === 0) return { nodes, edges, dropped }
+ const linked = new Set(
+ kept.filter((e) => e.kind !== 'add-anchor').flatMap((e) => [e.source, e.target])
+ )
+ const readOnly = new Set(
+ edges.filter(passive).map((e) => e.source).filter((id) => !linked.has(id))
+ )
+ return {
+ nodes: nodes.filter((n) => !readOnly.has(n.id)),
+ edges: kept.filter((e) => !readOnly.has(e.source) && !readOnly.has(e.target)),
+ dropped
+ }
+}