Skip to content

feat(dag-viewer): standalone Python render + animated transitions - #255

Merged
zzylol merged 4 commits into
mainfrom
feat/dag-viewer-python-render
Aug 24, 2026
Merged

zzylol merged 4 commits into
mainfrom
feat/dag-viewer-python-render

Conversation

@zzylol

@zzylol zzylol commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #249 (targets that branch, not main, until it merges). Adds a
headless/no-browser rendering path for tools/dag-viewer, plus animated
transitions in the existing page.

What's here

  • render.py — bakes a dag_export WorkloadGraph JSON (or several,
    merged the same way index.html's multi-file drop does — colliding query
    names get the source filename appended) into one self-contained HTML
    file: every vendored script and the query data inlined, so the output
    opens directly with no server and no drag-and-drop needed.

    index.html's own fetch('dag.json') convenience is blocked as
    cross-origin by most browsers when opened over file:// — embedding the
    data directly sidesteps that too, not just the no-browser case.
    --mode compare|union opens the page straight into that view with every
    loaded query pre-selected.

    cargo run -p asap-devtools --bin dag_export -- --sql "..." --name q1 \
      | python3 tools/dag-viewer/render.py -o rendered.html --mode union
  • viewer.jsindex.html's inline <script> extracted verbatim
    into its own file, so index.html and render.py's output share one
    copy of the interaction logic instead of forking it. Pure extraction plus
    the animation/embedded-data additions below — no behavior change to the
    existing single/compare/union element-building logic.

  • Animated transitions — mode switches, the shared-subtree ring
    toggling, and initial layout now animate (dagre's animate: true, a
    transition-property on node/edge style so class/style changes ease
    instead of snapping, and a fade-in on newly built elements) instead of
    hard-cutting to a new static layout on every render() call.

  • test_render.py — unit tests for render.py's merge/inline/escape
    logic (plain unittest.TestCase, matching tools/clickhouse's
    convention; not wired into CI, same as that tool — this repo doesn't run
    Python in CI today).

  • README/RUNNING doc updates for both of the above.

Deliberately out of scope

Query cost. dag_export doesn't emit one today — no cost estimator is
wired into pre-ASAP IR. Nothing renders it because there's nothing to
render; the side panel already dumps a clicked node's detail verbatim, so
a future cost field added there shows up automatically, no viewer change
needed.

Validation

  • python3 -m unittest tools/dag-viewer/test_render.py — 10/10 passing.
  • render.py's output checked against dag.example.json: JSON
    round-trips byte-for-byte through the embedded <script> tag, esprima
    parses every inlined <script> block, no <script src=...> tags remain.
  • The new viewer.js logic (embedded-data loading, --mode override,
    animated-layout config) exercised in a stubbed V8 context via
    py_mini_racer — same validation approach feat(dag-viewer): lane-based Compare/Union DAG-merge view #249's own description used —
    with a capturing cytoscape() stub asserting on the elements/layout
    objects the code hands it (real vendored cytoscape/dagre are unmodified
    and already covered by feat(dag-viewer): lane-based Compare/Union DAG-merge view #249's own validation).
  • Manually rendered dag.example.json through render.py --mode union and
    opened the output.

Screenshot

Per review feedback, edge arrows now point from input to consumer
(data-flow direction) instead of parent to child — render.py --mode union output on dag.example.json, top-to-bottom: scans at top, each
query's root at bottom.

dag-viewer union mode, arrows pointing from inputs down to each query's root

Single-query view, same fix:

dag-viewer single-query mode, arrows pointing from Scan down through Aggregate to the Project root

🤖 Generated with Claude Code

Base automatically changed from feat/dag-viewer-compare-union-186 to main August 23, 2026 04:12
zzylol and others added 2 commits August 22, 2026 22:27
Adds a headless/no-browser path for tools/dag-viewer, on top of #249's
Compare/Union modes:

- `render.py` bakes a dag_export WorkloadGraph JSON (or several, merged
  the same way index.html's multi-file drop does) into one self-contained
  HTML file — every vendored script and the query data inlined, so it
  opens directly with no server and no drag-and-drop. index.html's own
  fetch('dag.json') convenience is blocked as cross-origin by most
  browsers over file://; embedding sidesteps that too, not just the
  no-browser case. `--mode compare|union` opens the page straight into
  that view with every loaded query pre-selected.

- Extracted index.html's inline <script> into `viewer.js`, unchanged
  apart from the additions below, so index.html and render.py's output
  share one copy of the interaction logic instead of forking it.

- Mode switches, the shared-subtree ring, and initial layout now animate
  (dagre's `animate: true`, a `transition-property` on node/edge style so
  class toggles ease instead of snap, and a fade-in on newly built
  elements) rather than hard-cutting to a new static layout on every
  render() call.

- `test_render.py` unit-tests render.py's merge/inline/escape logic
  (plain unittest.TestCase, matching tools/clickhouse's convention; not
  wired into CI, same as that tool).

Query cost isn't rendered — dag_export doesn't emit one yet (no cost
estimator wired into pre-ASAP IR). The side panel dumps a clicked node's
`detail` verbatim, so a future `cost` field there needs no viewer change.

Stacked on #249 (targets that branch, not main, until it merges) — see
issue #186 and PR #249's description for the Compare/Union groundwork
this builds on.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
python3 -m unittest tools/dag-viewer/test_render.py doesn't actually work
run from the repo root -- unittest resolves the file-path form to a bare
'test_render' module but never adds tools/dag-viewer/ to sys.path, so the
file's own 'from render import ...' fails with ModuleNotFoundError. Same
latent bug exists in tools/clickhouse/test_extract_functions.py's docs
(copied from there), left alone since it's pre-existing and out of scope
here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@zzylol
zzylol force-pushed the feat/dag-viewer-python-render branch from 0f30584 to 6ef8197 Compare August 23, 2026 04:27
A nonexistent input file or malformed JSON crashed with a raw Python
traceback (FileNotFoundError / json.JSONDecodeError propagating straight
out of main()) instead of a one-line stderr message + exit 1, matching
the existing 'no queries in input' / '--mode needs 2+ queries' style.
Found by actually running every command tools/dag-viewer's docs mention,
end to end, including through a real cargo-built dag_export.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@milindsrivastava1997 milindsrivastava1997 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the order of arrows should be reversed. Otherwise, lgtm

Structural edges pointed from a node to its children (parent -> child,
i.e. output -> its input), which is backwards for a data-flow diagram:
an arrowhead should point at the consumer, not the input. Swap
source/target for the edges built in all three view modes
(single/compare/union) so arrows now run input -> consumer, matching
the top-to-bottom dagre layout (scans at top, query root at bottom).

Addresses review feedback on #255.
@zzylol

zzylol commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

@milindsrivastava1997 done — reversed the edge direction in all three view modes (single/compare/union), arrows now point from input to consumer instead of parent to child. Pushed in 16310d4. Screenshot added to the PR description showing the union-mode render with the fix.

@zzylol
zzylol merged commit 95fc2e4 into main Aug 24, 2026
2 of 3 checks passed
@zzylol
zzylol deleted the feat/dag-viewer-python-render branch August 24, 2026 02:16
zzylol added a commit that referenced this pull request Aug 24, 2026
…viewer.js split

PR #255 landed on main while this branch was in flight, moving nearly all
of tools/dag-viewer/index.html's inline JS out into viewer.js and adding
render.py/test_render.py for a standalone-page renderer. This branch's
fedf0ed commit had added a replacement-explanation "notes" badge/panel
feature by editing index.html's old inline-script structure, which no
longer exists on main — causing PR #259 to conflict.

- Bring tools/dag-viewer/ up to date with main: pull in viewer.js,
  render.py, test_render.py, and the new (much shorter) index.html shell
  verbatim from origin/main. node-style.js and dag.example.json are
  untouched by main, so both are left exactly as this branch already had
  them (dag.example.json still demonstrates the notes feature).
- Re-port the notes UI onto viewer.js's actual structure: the root-badge
  block in finalizeGraphInteractions() gets a sibling block for the notes
  badge, notesHtml() is spliced into both showDetail() and
  showUnionDetail(), and renderLegend() gets one row per ExplanationKind.
  All of it calls node-style.js's already-committed NOTE_* helpers exactly
  as the root-badge code already does. The .notesBlock/.noteItem/etc CSS
  moves into index.html's <style> block, which main kept in place.
- Bring README.md/RUNNING.md's main-side documentation of render.py into
  this branch too (previously undocumented here since render.py didn't
  exist on this branch before), reapplying the notes-specific bullets and
  the "Replacement explanations (notes)" section on top.

Verified: cargo build/test/fmt/clippy all clean; python3 -m unittest
test_render passes (12/12); render.py runs against dag.example.json
without error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants