Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ jobs:
# - 14 after check_selection (cute-dbt#171)
# - 16 after explore_cli + explore_full_manifest (cute-dbt#100)
# - 17 after explore_lineage_dag (cute-dbt#101)
# - 18 after explore_view_toggle (cute-dbt#102)
# The README is not a feature. The job name deliberately carries no
# number (cute-dbt#68 — the old `==6` display label went stale across
# several bumps): the enforced count lives ONLY in the `expected=`
Expand All @@ -343,7 +344,7 @@ jobs:
- name: features/*.feature count matches expected
run: |
set -euo pipefail
expected=17
expected=18
count=$(find features -maxdepth 1 -name '*.feature' | wc -l | tr -d ' ')
echo "feature-count: expected ${expected}, found ${count}"
if [ "$count" != "$expected" ]; then
Expand Down
330 changes: 328 additions & 2 deletions examples/explore/dag.html

Large diffs are not rendered by default.

372 changes: 359 additions & 13 deletions examples/explore/tests.html

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions features/explore_view_toggle.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Maps: cute-dbt#102 — the explore view toggle (CTE ⇄ model) on dag.html
# and the tests.html unit-test viewer (epic cute-dbt#99 V3).
#
# dag.html: the toolbar gains a two-arm view toggle. The lineage arm is
# the boot view; the CTE arm is GATED on a highlighted model (disabled at
# render time — selection is a runtime act) and renders that model's CTE
# DAG with the same vendored Cytoscape + dagre engine as the lineage
# view. The carrier's `cte_dags` map ships one DagPayload per
# CTE-bearing compiled model — the SAME role-classified, join-typed
# graph the report renders (the build_payload reuse seam). An uncompiled
# model ships NO entry: the client renders the labeled fail-open
# degraded view off the lineage node's not_compiled flag, never an
# error, and PreflightError keeps its four variants.
#
# tests.html: the unit-test viewer renders through the SHARED askama
# partial (templates/partials/test-card.html) — the exact test-card +
# Given/Expected markup report.html renders (the include is
# output-preserving there, gated by the byte-identity goldens). The page
# embeds NO Cytoscape, no dagre, no Mermaid, no DataTables and no
# jQuery; the explore-tests.js engine fills the card from the embedded
# cute-dbt-data payload, and each index row wires its test id so a click
# selects it in the viewer.
#
# Scenarios are self-contained subprocess wire round-trips (the
# explore_full_manifest.rs pattern): Givens accumulate a synthetic
# manifest plan, the When runs the real `cute-dbt explore` subprocess,
# and the Thens assert emitted-page + payload facts. The RUNTIME toggle
# interaction (click-to-enable, view flip, selection persistence) is
# exercised by the headless Chromium suite
# (tests/headless_zero_egress.rs), which these render-time assertions
# complement, not replace.
Feature: explore's dag.html toggles between model lineage and a CTE view, and tests.html serves the unit-test viewer

Background:
Given an explore scenario

@no-baseline-usage-error
Scenario: dag.html renders the view toggle with the CTE arm gated on selection
Given the explore manifest declares the model "orders"
When I run cute-dbt explore on the synthetic manifest
Then the exit code is 0
And dag.html renders the lineage and CTE view toggle arms
And dag.html renders the CTE view host hidden at render time
And dag.html embeds the explore CTE engine

@no-baseline-usage-error
Scenario: The dag carrier embeds a CTE DAG per CTE-bearing model and none for an uncompiled one
Given the explore manifest declares the model "dim_orders"
And the explore model "dim_orders" compiles to SQL with the CTE "src_orders"
And the explore manifest declares the model "mart_orders"
And the explore model "mart_orders" has no compiled SQL
When I run cute-dbt explore on the synthetic manifest
Then the exit code is 0
And the dag carrier embeds a CTE DAG for "dim_orders" containing the node "src_orders"
And the dag carrier embeds no CTE DAG for "mart_orders"
And dag.html marks "mart_orders" as not compiled

@no-baseline-usage-error
Scenario: tests.html renders the shared unit-test card and wires the index to the viewer
Given the explore manifest declares the model "dim_orders"
And the explore model "dim_orders" declares unit test "test_dim_orders_grain"
When I run cute-dbt explore on the synthetic manifest
Then the exit code is 0
And tests.html renders the shared unit-test card
And tests.html embeds the explore tests viewer
And tests.html wires the unit test "test_dim_orders_grain" to the viewer

@no-baseline-usage-error
Scenario: tests.html embeds no graph engine and keeps the zero-egress invariant
Given the explore manifest declares the model "dim_orders"
And the explore model "dim_orders" declares unit test "test_dim_orders_grain"
When I run cute-dbt explore on the synthetic manifest
Then the exit code is 0
And tests.html embeds no Cytoscape engine
And tests.html carries no external resource references
9 changes: 5 additions & 4 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,18 @@ pre-push:
feature-count:
# Asserts the .feature-count invariant; the enforced count lives ONLY
# in the `expected=` line below (cute-dbt#68 — numbers in labels and
# comments go stale). History: bumped to 17 in cute-dbt#101 when
# explore_lineage_dag.feature joined the contract; previously bumped
# in cute-dbt#100 (explore_cli + explore_full_manifest),
# comments go stale). History: bumped to 18 in cute-dbt#102 when
# explore_view_toggle.feature joined the contract; previously bumped
# in cute-dbt#101 (explore_lineage_dag),
# cute-dbt#100 (explore_cli + explore_full_manifest),
# cute-dbt#171 (check_selection), cute-dbt#169 (coverage_checks),
# cute-dbt#145 (incremental_models), cute-dbt#98 (cell_table_diff),
# cute-dbt#84 (pr_diff_scoping), cute-dbt#71 (consumer_report_contract),
# cute-dbt#69 (unit_test_yaml).
# Mirrors the `feature-count` CI job; the README is not a feature.
run: |
set -euo pipefail
expected=17
expected=18
count=$(find features -maxdepth 1 -name '*.feature' | wc -l | tr -d ' ')
if [ "$count" != "$expected" ]; then
echo "feature-count: expected ${expected} .feature files, found ${count}"
Expand Down
76 changes: 76 additions & 0 deletions src/adapters/asset_embed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,40 @@ pub const THEME_JS: &str = include_str!("../../templates/theme.js");
/// [`LineagePayload`]: crate::adapters::explore::LineagePayload
pub const EXPLORE_LINEAGE_JS: &str = include_str!("../../templates/explore-lineage.js");

/// The explore page's first-party CTE-view engine (cute-dbt#102) — the
/// CTE ⇄ model view toggle on `dag.html` plus the per-model Cytoscape
/// CTE DAG it renders for the highlighted model: dagre left-to-right
/// layout (the vendored extension is already on the page), role-styled
/// nodes (final / import / transform — the report's vocabulary),
/// join-type-labeled edges, and the fail-open degraded view for a
/// `not_compiled` model (labeled, never an error). Same init hygiene as
/// [`EXPLORE_LINEAGE_JS`]: canvas-text labels, system `fontFamily`,
/// measured node widths (no `width: label`), no workers, handlers bound
/// from our JS, in-place class mutation per interaction (a view switch
/// or model retarget is a view change and may rebuild the CTE
/// instance; the lineage instance is never rebuilt).
///
/// First-party, NOT vendored: lives at `templates/explore-cte.js`.
/// Integrity gates: the banner-pin + end-of-file sentinel test in this
/// module.
pub const EXPLORE_CTE_JS: &str = include_str!("../../templates/explore-cte.js");

/// The explore page's first-party unit-test viewer engine (cute-dbt#102)
/// — renders the selected test's description, given fixtures and
/// expected table into the shared test-card partial on `tests.html`,
/// reading the embedded `cute-dbt-data` [`ReportPayload`] (the
/// `build_payload` reuse seam). DOM built with `createElement` +
/// `textContent` only; no jQuery, no `DataTables`, no graph engine —
/// the page stays a light static-plus-viewer surface and passes the
/// zero-egress gate on its own.
///
/// First-party, NOT vendored: lives at `templates/explore-tests.js`.
/// Integrity gates: the banner-pin + end-of-file sentinel test in this
/// module.
///
/// [`ReportPayload`]: crate::adapters::render::ReportPayload
pub const EXPLORE_TESTS_JS: &str = include_str!("../../templates/explore-tests.js");

/// The report's first-party Cytoscape DAG engine (cute-dbt#180) — the
/// opt-in interactive alternative behind the settings-panel engine
/// picker: longest-path preset layout (no layout plugin), canvas-text
Expand Down Expand Up @@ -238,6 +272,48 @@ mod tests {
);
}

#[test]
fn the_explore_cte_js_carries_its_banner_and_is_not_truncated() {
assert!(
EXPLORE_CTE_JS.contains("cute-dbt explore CTE engine v1"),
"explore-cte.js head banner",
);
assert!(
EXPLORE_CTE_JS
.trim_end()
.ends_with("/* end of cute-dbt explore CTE engine v1 (cute-dbt#102) */"),
"explore-cte.js end-of-file sentinel (truncation guard)",
);
// The focus-commit contract (epic #99): the CTE view must never
// grow a second selectedModel write site — the Space commit in
// explore-lineage.js stays the only one.
assert!(
!EXPLORE_CTE_JS.contains("dataset.selectedModel ="),
"explore-cte.js must not write the focus-commit signal",
);
}

#[test]
fn the_explore_tests_js_carries_its_banner_and_is_not_truncated() {
assert!(
EXPLORE_TESTS_JS.contains("cute-dbt explore tests viewer v1"),
"explore-tests.js head banner",
);
assert!(
EXPLORE_TESTS_JS
.trim_end()
.ends_with("/* end of cute-dbt explore tests viewer v1 (cute-dbt#102) */"),
"explore-tests.js end-of-file sentinel (truncation guard)",
);
// The viewer builds DOM with createElement + textContent only —
// innerHTML over payload-derived strings would reopen the XSS
// surface the canvas-label discipline closed.
assert!(
!EXPLORE_TESTS_JS.contains("innerHTML"),
"explore-tests.js must never assign innerHTML",
);
}

#[test]
fn the_favicon_is_an_empty_data_uri() {
assert_eq!(FAVICON_DATA_URI, "data:,");
Expand Down
Loading
Loading