From 4a1ff75ea91a488ff3ec438b183413334141e7fd Mon Sep 17 00:00:00 2001 From: Stuart Axelbrooke Date: Wed, 26 Nov 2025 10:05:28 +0800 Subject: [PATCH] fix askama template shenanigans --- .gitignore | 1 + databuild/BUILD.bazel | 3 + databuild/askama.toml | 2 + databuild/web/templates.rs | 969 +----------------- databuild/web/templates/home.html | 83 ++ databuild/web/templates/job_runs/detail.html | 127 +++ databuild/web/templates/job_runs/list.html | 129 +++ .../web/templates/partitions/detail.html | 141 +++ databuild/web/templates/partitions/list.html | 136 +++ databuild/web/templates/wants/detail.html | 141 +++ databuild/web/templates/wants/list.html | 127 +++ 11 files changed, 897 insertions(+), 962 deletions(-) create mode 100644 databuild/askama.toml create mode 100644 databuild/web/templates/home.html create mode 100644 databuild/web/templates/job_runs/detail.html create mode 100644 databuild/web/templates/job_runs/list.html create mode 100644 databuild/web/templates/partitions/detail.html create mode 100644 databuild/web/templates/partitions/list.html create mode 100644 databuild/web/templates/wants/detail.html create mode 100644 databuild/web/templates/wants/list.html diff --git a/.gitignore b/.gitignore index 1b0fdd0..bf199a6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules **/node_modules Cargo.toml Cargo.lock +/askama.toml databuild/databuild.rs generated_number target diff --git a/databuild/BUILD.bazel b/databuild/BUILD.bazel index a9f5ab4..7111579 100644 --- a/databuild/BUILD.bazel +++ b/databuild/BUILD.bazel @@ -24,8 +24,11 @@ rust_library( srcs = glob(["**/*.rs"]) + [ ":generate_databuild_rust", ], + compile_data = glob(["web/templates/**"]) + ["askama.toml"], crate_root = "lib.rs", edition = "2021", + # This is required to point to the `askama.toml`, which then points to the appropriate place for templates + rustc_env = {"CARGO_MANIFEST_DIR": "$(BINDIR)/" + package_name()}, visibility = ["//visibility:public"], deps = [ "@crates//:askama", diff --git a/databuild/askama.toml b/databuild/askama.toml new file mode 100644 index 0000000..c1ac760 --- /dev/null +++ b/databuild/askama.toml @@ -0,0 +1,2 @@ +[general] +dirs = ["web/templates"] diff --git a/databuild/web/templates.rs b/databuild/web/templates.rs index 5bae4be..d48d35e 100644 --- a/databuild/web/templates.rs +++ b/databuild/web/templates.rs @@ -1,54 +1,4 @@ //! Askama template structs for the DataBuild dashboard. -//! -//! # TODO: Bazel + Askama Template Integration -//! -//! ## Current State -//! -//! Templates are embedded inline using Askama's `source` attribute rather than -//! file-based `.html` templates. This is a **workaround** due to Bazel/Askama -//! incompatibility, not our preferred approach. -//! -//! ## The Problem -//! -//! Askama's derive macro resolves template files at compile time relative to -//! `CARGO_MANIFEST_DIR`. In Bazel's sandboxed builds, this environment variable -//! either isn't set or points to the wrong location, causing template files to -//! not be found even when included via `compile_data` or `data` attributes. -//! -//! We attempted several fixes: -//! - `rustc_env = {"CARGO_MANIFEST_DIR": "..."}` - doesn't work because the -//! sandbox paths don't align with where Askama looks for files -//! - `compile_data` with template files - files exist but Askama can't find them -//! -//! ## Current Workarounds -//! -//! 1. **Inline templates via `source` attribute**: All template content is embedded -//! directly in Rust source strings. This works but has downsides: -//! - No syntax highlighting or editor support for HTML -//! - Shared components (head, nav, footer) must be duplicated as Askama macros -//! in each template since Rust's `concat!` doesn't work with proc-macro attributes -//! -//! 2. **Double-hash raw strings `r##"..."##`**: HTML color codes like `stroke="#333"` -//! contain `"#` which prematurely closes single-hash raw strings `r#"..."#`. -//! Using double-hash avoids this. **Watch out for this footgun when editing templates!** -//! -//! ## Path Forward -//! -//! Our bias is to move to proper file-based `.html` templates. Options to explore: -//! -//! 1. **Custom Bazel rule or genrule**: Pre-process templates or set up the environment -//! correctly before Askama's proc-macro runs -//! -//! 2. **Askama `source` with shared snippets**: If Askama supports importing/including -//! from other `source`-defined templates, we could reduce duplication while staying -//! inline. (Needs investigation - Askama macros currently require definition in the -//! same template source string) -//! -//! 3. **Build-time template concatenation**: Generate the Rust file with templates -//! from separate `.html` files during the build, before rustc runs -//! -//! Until resolved, this file contains all dashboard templates inline with duplicated -//! macro definitions for head/nav/footer. use askama::Template; @@ -282,93 +232,7 @@ impl Default for BaseContext { // ============================================================================= #[derive(Template)] -#[template( - ext = "html", - source = r##"{% macro head(title) %} - - - - - - - - {{ title }} - - - -{% endmacro %} - -{% macro nav(active, graph_label) %} - -
-{% endmacro %} - -{% macro footer() %} -
- - -{% endmacro %} - -{% call head("Home - DataBuild") %} -{% call nav("", base.graph_label) %} - -

Dashboard

- -
- -
{{ active_wants_count }}
-
Active Wants
-
- -
{{ active_job_runs_count }}
-
Active Job Runs
-
- -
{{ live_partitions_count }}
-
Live Partitions
-
-
- -{% call footer() %} -"## -)] +#[template(ext = "html", path = "home.html")] pub struct HomePage { pub base: BaseContext, pub active_wants_count: u64, @@ -381,137 +245,7 @@ pub struct HomePage { // ============================================================================= #[derive(Template)] -#[template( - ext = "html", - source = r##"{% macro head(title) %} - - - - - - - - {{ title }} - - - -{% endmacro %} - -{% macro nav(active, graph_label) %} - -
-{% endmacro %} - -{% macro footer() %} -
- - -{% endmacro %} - -{% call head("Wants - DataBuild") %} -{% call nav("wants", base.graph_label) %} - -

Wants

- - - - - - - - - - - - {% for want in wants %} - - - - - - - {% endfor %} - {% if wants.is_empty() %} - - - - {% endif %} - -
Want IDStatusPartitionsComment
{{ want.want_id }} - {% match want.status %} - {% when Some with (s) %}{{ s.name }} - {% when None %}Unknown - {% endmatch %} - {{ want.partitions.len() }}{{ want.comment_display }}
No wants found
- - - -{% call footer() %} -"## -)] +#[template(ext = "html", path = "wants/list.html")] pub struct WantsListPage { pub base: BaseContext, pub wants: Vec, @@ -540,151 +274,7 @@ impl WantsListPage { // ============================================================================= #[derive(Template)] -#[template( - ext = "html", - source = r##"{% macro head(title) %} - - - - - - - - {{ title }} - - - -{% endmacro %} - -{% macro nav(active, graph_label) %} - -
-{% endmacro %} - -{% macro footer() %} -
- - -{% endmacro %} - -{% call head("Want - DataBuild") %} -{% call nav("wants", base.graph_label) %} - -
-

Want: {{ want.want_id }}

- {% match want.status %} - {% when Some with (s) %}{{ s.name }} - {% when None %} - {% endmatch %} -
- -
-

Details

-
-
- - {{ want.data_timestamp }} -
-
- - {{ want.ttl_seconds }} -
-
- - {{ want.sla_seconds }} -
-
- - {{ want.last_updated_timestamp }} -
-
-
- -{% match want.comment %} - {% when Some with (c) %} -
-

Comment

-

{{ c }}

-
- {% when None %} -{% endmatch %} - -
-

Requested Partitions ({{ want.partitions.len() }})

-
    - {% for p in want.partitions %} -
  • {{ p.partition_ref }}
  • - {% endfor %} - {% if want.partitions.is_empty() %} -
  • No partitions
  • - {% endif %} -
-
- -{% if !want.upstreams.is_empty() %} -
-

Upstream Dependencies ({{ want.upstreams.len() }})

- -
-{% endif %} - -{% call footer() %} -"## -)] +#[template(ext = "html", path = "wants/detail.html")] pub struct WantDetailPage { pub base: BaseContext, pub want: WantDetailView, @@ -695,146 +285,7 @@ pub struct WantDetailPage { // ============================================================================= #[derive(Template)] -#[template( - ext = "html", - source = r##"{% macro head(title) %} - - - - - - - - {{ title }} - - - -{% endmacro %} - -{% macro nav(active, graph_label) %} - -
-{% endmacro %} - -{% macro footer() %} -
- - -{% endmacro %} - -{% call head("Partitions - DataBuild") %} -{% call nav("partitions", base.graph_label) %} - -

Partitions

- - - - - - - - - - - {% for p in partitions %} - - - - - - {% endfor %} - {% if partitions.is_empty() %} - - - - {% endif %} - -
Partition RefStatusLast Updated
- {% if p.has_partition_ref %} - {{ p.partition_ref }} - {% else %} - - - {% endif %} - - {% match p.status %} - {% when Some with (s) %}{{ s.name }} - {% when None %}Unknown - {% endmatch %} - - {% match p.last_updated_timestamp %} - {% when Some with (ts) %}{{ ts }} - {% when None %}- - {% endmatch %} -
No partitions found
- - - -{% call footer() %} -"## -)] +#[template(ext = "html", path = "partitions/list.html")] pub struct PartitionsListPage { pub base: BaseContext, pub partitions: Vec, @@ -863,151 +314,7 @@ impl PartitionsListPage { // ============================================================================= #[derive(Template)] -#[template( - ext = "html", - source = r##"{% macro head(title) %} - - - - - - - - {{ title }} - - - -{% endmacro %} - -{% macro nav(active, graph_label) %} - -
-{% endmacro %} - -{% macro footer() %} -
- - -{% endmacro %} - -{% call head("Partition - DataBuild") %} -{% call nav("partitions", base.graph_label) %} - -
-

- {% if partition.has_partition_ref %}{{ partition.partition_ref }}{% else %}Unknown{% endif %} -

- {% match partition.status %} - {% when Some with (s) %}{{ s.name }} - {% when None %} - {% endmatch %} -
- -
-

Details

-
-
- - {{ partition.uuid }} -
-
- - - {% match partition.last_updated_timestamp %} - {% when Some with (ts) %}{{ ts }} - {% when None %}- - {% endmatch %} - -
-
-
- -{% if !partition.job_run_ids.is_empty() %} -
-

Job Runs ({{ partition.job_run_ids.len() }})

-
    - {% for id in partition.job_run_ids %} -
  • {{ id }}
  • - {% endfor %} -
-
-{% endif %} - -{% if !partition.want_ids.is_empty() %} -
-

Referenced by Wants ({{ partition.want_ids.len() }})

-
    - {% for id in partition.want_ids %} -
  • {{ id }}
  • - {% endfor %} -
-
-{% endif %} - -{% if !partition.taint_ids.is_empty() %} -
-

Taints ({{ partition.taint_ids.len() }})

-
    - {% for id in partition.taint_ids %} -
  • {{ id }}
  • - {% endfor %} -
-
-{% endif %} - -{% call footer() %} -"## -)] +#[template(ext = "html", path = "partitions/detail.html")] pub struct PartitionDetailPage { pub base: BaseContext, pub partition: PartitionDetailView, @@ -1018,139 +325,7 @@ pub struct PartitionDetailPage { // ============================================================================= #[derive(Template)] -#[template( - ext = "html", - source = r##"{% macro head(title) %} - - - - - - - - {{ title }} - - - -{% endmacro %} - -{% macro nav(active, graph_label) %} - -
-{% endmacro %} - -{% macro footer() %} -
- - -{% endmacro %} - -{% call head("Job Runs - DataBuild") %} -{% call nav("job_runs", base.graph_label) %} - -

Job Runs

- - - - - - - - - - - - {% for jr in job_runs %} - - - - - - - {% endfor %} - {% if job_runs.is_empty() %} - - - - {% endif %} - -
Job Run IDStatusBuilding PartitionsLast Heartbeat
{{ jr.id }} - {% match jr.status %} - {% when Some with (s) %}{{ s.name }} - {% when None %}Unknown - {% endmatch %} - {{ jr.building_partitions.len() }} - {% match jr.last_heartbeat_at %} - {% when Some with (ts) %}{{ ts }} - {% when None %}- - {% endmatch %} -
No job runs found
- - - -{% call footer() %} -"## -)] +#[template(ext = "html", path = "job_runs/list.html")] pub struct JobRunsListPage { pub base: BaseContext, pub job_runs: Vec, @@ -1179,137 +354,7 @@ impl JobRunsListPage { // ============================================================================= #[derive(Template)] -#[template( - ext = "html", - source = r##"{% macro head(title) %} - - - - - - - - {{ title }} - - - -{% endmacro %} - -{% macro nav(active, graph_label) %} - -
-{% endmacro %} - -{% macro footer() %} -
- - -{% endmacro %} - -{% call head("Job Run - DataBuild") %} -{% call nav("job_runs", base.graph_label) %} - -
-

Job Run: {{ job_run.id }}

- {% match job_run.status %} - {% when Some with (s) %}{{ s.name }} - {% when None %} - {% endmatch %} -
- -
-

Details

-
-
- - - {% match job_run.last_heartbeat_at %} - {% when Some with (ts) %}{{ ts }} - {% when None %}- - {% endmatch %} - -
-
-
- -
-

Building Partitions ({{ job_run.building_partitions.len() }})

-
    - {% for p in job_run.building_partitions %} -
  • {{ p.partition_ref }}
  • - {% endfor %} - {% if job_run.building_partitions.is_empty() %} -
  • No partitions
  • - {% endif %} -
-
- -{% if !job_run.servicing_wants.is_empty() %} -
-

Servicing Wants ({{ job_run.servicing_wants.len() }})

- {% for sw in job_run.servicing_wants %} -
- -
    - {% for p in sw.partitions %} -
  • {{ p.partition_ref }}
  • - {% endfor %} -
-
- {% endfor %} -
-{% endif %} - -{% call footer() %} -"## -)] +#[template(ext = "html", path = "job_runs/detail.html")] pub struct JobRunDetailPage { pub base: BaseContext, pub job_run: JobRunDetailView, diff --git a/databuild/web/templates/home.html b/databuild/web/templates/home.html new file mode 100644 index 0000000..1dc79c7 --- /dev/null +++ b/databuild/web/templates/home.html @@ -0,0 +1,83 @@ +{% macro head(title) %} + + + + + + + + {{ title }} + + + +{% endmacro %} + +{% macro nav(active, graph_label) %} + +
+ {% endmacro %} + + {% macro footer() %} +
+ + +{% endmacro %} + +{% call head("Home - DataBuild") %} +{% call nav("", base.graph_label) %} + +

Dashboard

+ + + +{% call footer() %} diff --git a/databuild/web/templates/job_runs/detail.html b/databuild/web/templates/job_runs/detail.html new file mode 100644 index 0000000..a4132fb --- /dev/null +++ b/databuild/web/templates/job_runs/detail.html @@ -0,0 +1,127 @@ +{% macro head(title) %} + + + + + + + + {{ title }} + + + +{% endmacro %} + +{% macro nav(active, graph_label) %} + +
+ {% endmacro %} + + {% macro footer() %} +
+ + +{% endmacro %} + +{% call head("Job Run - DataBuild") %} +{% call nav("job_runs", base.graph_label) %} + +
+

Job Run: {{ job_run.id }}

+ {% match job_run.status %} + {% when Some with (s) %}{{ s.name }} + {% when None %} + {% endmatch %} +
+ +
+

Details

+
+
+ + + {% match job_run.last_heartbeat_at %} + {% when Some with (ts) %}{{ ts }} + {% when None %}- + {% endmatch %} + +
+
+
+ +
+

Building Partitions ({{ job_run.building_partitions.len() }})

+
    + {% for p in job_run.building_partitions %} +
  • {{ p.partition_ref }}
  • + {% endfor %} + {% if job_run.building_partitions.is_empty() %} +
  • No partitions
  • + {% endif %} +
+
+ +{% if !job_run.servicing_wants.is_empty() %} +
+

Servicing Wants ({{ job_run.servicing_wants.len() }})

+ {% for sw in job_run.servicing_wants %} +
+ +
    + {% for p in sw.partitions %} +
  • {{ p.partition_ref }}
  • + {% endfor %} +
+
+ {% endfor %} +
+{% endif %} + +{% call footer() %} diff --git a/databuild/web/templates/job_runs/list.html b/databuild/web/templates/job_runs/list.html new file mode 100644 index 0000000..a0f60e8 --- /dev/null +++ b/databuild/web/templates/job_runs/list.html @@ -0,0 +1,129 @@ +{% macro head(title) %} + + + + + + + + {{ title }} + + + +{% endmacro %} + +{% macro nav(active, graph_label) %} + +
+ {% endmacro %} + + {% macro footer() %} +
+ + +{% endmacro %} + +{% call head("Job Runs - DataBuild") %} +{% call nav("job_runs", base.graph_label) %} + +

Job Runs

+ + + + + + + + + + + + {% for jr in job_runs %} + + + + + + + {% endfor %} + {% if job_runs.is_empty() %} + + + + {% endif %} + +
Job Run IDStatusBuilding PartitionsLast Heartbeat
{{ jr.id }} + {% match jr.status %} + {% when Some with (s) %}{{ s.name }} + {% when None %}Unknown + {% endmatch %} + {{ jr.building_partitions.len() }} + {% match jr.last_heartbeat_at %} + {% when Some with (ts) %}{{ ts }} + {% when None %}- + {% endmatch %} +
No job runs found
+ + + +{% call footer() %} diff --git a/databuild/web/templates/partitions/detail.html b/databuild/web/templates/partitions/detail.html new file mode 100644 index 0000000..554283b --- /dev/null +++ b/databuild/web/templates/partitions/detail.html @@ -0,0 +1,141 @@ +{% macro head(title) %} + + + + + + + + {{ title }} + + + +{% endmacro %} + +{% macro nav(active, graph_label) %} + +
+ {% endmacro %} + + {% macro footer() %} +
+ + +{% endmacro %} + +{% call head("Partition - DataBuild") %} +{% call nav("partitions", base.graph_label) %} + +
+

+ {% if partition.has_partition_ref %}{{ partition.partition_ref }}{% else %}Unknown{% endif %} +

+ {% match partition.status %} + {% when Some with (s) %}{{ s.name }} + {% when None %} + {% endmatch %} +
+ +
+

Details

+
+
+ + {{ partition.uuid }} +
+
+ + + {% match partition.last_updated_timestamp %} + {% when Some with (ts) %}{{ ts }} + {% when None %}- + {% endmatch %} + +
+
+
+ +{% if !partition.job_run_ids.is_empty() %} +
+

Job Runs ({{ partition.job_run_ids.len() }})

+
    + {% for id in partition.job_run_ids %} +
  • {{ id }}
  • + {% endfor %} +
+
+{% endif %} + +{% if !partition.want_ids.is_empty() %} +
+

Referenced by Wants ({{ partition.want_ids.len() }})

+
    + {% for id in partition.want_ids %} +
  • {{ id }}
  • + {% endfor %} +
+
+{% endif %} + +{% if !partition.taint_ids.is_empty() %} +
+

Taints ({{ partition.taint_ids.len() }})

+
    + {% for id in partition.taint_ids %} +
  • {{ id }}
  • + {% endfor %} +
+
+{% endif %} + +{% call footer() %} diff --git a/databuild/web/templates/partitions/list.html b/databuild/web/templates/partitions/list.html new file mode 100644 index 0000000..3d37423 --- /dev/null +++ b/databuild/web/templates/partitions/list.html @@ -0,0 +1,136 @@ +{% macro head(title) %} + + + + + + + + {{ title }} + + + +{% endmacro %} + +{% macro nav(active, graph_label) %} + +
+ {% endmacro %} + + {% macro footer() %} +
+ + +{% endmacro %} + +{% call head("Partitions - DataBuild") %} +{% call nav("partitions", base.graph_label) %} + +

Partitions

+ + + + + + + + + + + {% for p in partitions %} + + + + + + {% endfor %} + {% if partitions.is_empty() %} + + + + {% endif %} + +
Partition RefStatusLast Updated
+ {% if p.has_partition_ref %} + {{ p.partition_ref }} + {% else %} + - + {% endif %} + + {% match p.status %} + {% when Some with (s) %}{{ s.name }} + {% when None %}Unknown + {% endmatch %} + + {% match p.last_updated_timestamp %} + {% when Some with (ts) %}{{ ts }} + {% when None %}- + {% endmatch %} +
No partitions found
+ + + +{% call footer() %} diff --git a/databuild/web/templates/wants/detail.html b/databuild/web/templates/wants/detail.html new file mode 100644 index 0000000..333f420 --- /dev/null +++ b/databuild/web/templates/wants/detail.html @@ -0,0 +1,141 @@ +{% macro head(title) %} + + + + + + + + {{ title }} + + + +{% endmacro %} + +{% macro nav(active, graph_label) %} + +
+ {% endmacro %} + + {% macro footer() %} +
+ + +{% endmacro %} + +{% call head("Want - DataBuild") %} +{% call nav("wants", base.graph_label) %} + +
+

Want: {{ want.want_id }}

+ {% match want.status %} + {% when Some with (s) %}{{ s.name }} + {% when None %} + {% endmatch %} +
+ +
+

Details

+
+
+ + {{ want.data_timestamp }} +
+
+ + {{ want.ttl_seconds }} +
+
+ + {{ want.sla_seconds }} +
+
+ + {{ want.last_updated_timestamp }} +
+
+
+ +{% match want.comment %} +{% when Some with (c) %} +
+

Comment

+

{{ c }}

+
+{% when None %} +{% endmatch %} + +
+

Requested Partitions ({{ want.partitions.len() }})

+
    + {% for p in want.partitions %} +
  • {{ p.partition_ref }}
  • + {% endfor %} + {% if want.partitions.is_empty() %} +
  • No partitions
  • + {% endif %} +
+
+ +{% if !want.upstreams.is_empty() %} +
+

Upstream Dependencies ({{ want.upstreams.len() }})

+ +
+{% endif %} + +{% call footer() %} diff --git a/databuild/web/templates/wants/list.html b/databuild/web/templates/wants/list.html new file mode 100644 index 0000000..3861e80 --- /dev/null +++ b/databuild/web/templates/wants/list.html @@ -0,0 +1,127 @@ +{% macro head(title) %} + + + + + + + + {{ title }} + + + +{% endmacro %} + +{% macro nav(active, graph_label) %} + +
+ {% endmacro %} + + {% macro footer() %} +
+ + +{% endmacro %} + +{% call head("Wants - DataBuild") %} +{% call nav("wants", base.graph_label) %} + +

Wants

+ + + + + + + + + + + + {% for want in wants %} + + + + + + + {% endfor %} + {% if wants.is_empty() %} + + + + {% endif %} + +
Want IDStatusPartitionsComment
{{ want.want_id }} + {% match want.status %} + {% when Some with (s) %}{{ s.name }} + {% when None %}Unknown + {% endmatch %} + {{ want.partitions.len() }}{{ want.comment_display }}
No wants found
+ + + +{% call footer() %}