{# Vendored from opportunities/detail.html. Same shell, stat strip,
issue cards, and improvements catalogue. Spec 149 deltas:
- breadcrumb back to /failures
- eyebrow "Failure detail" (vs "Recommendation")
- "Errors observed" section heading (vs "Issues found")
- "Slot Wasted" stat replaces "Cost (this execution)" (failures
are billed $0)
- new "Failed query" code block before the improvements section
so the broken SQL is always visible. #}
{% extends "layout.html" %}
{% block content %}
{# Hoisted from per-section conditionals - see opportunities/detail.html
for the same fix. Jinja {% from %} scope is the enclosing block, so
when ``issue_cards`` is empty (suggestion-only failure) the
section_title import never fires but the Available improvements
block at the bottom still calls section_title and crashes. #}
{% from "components/_stat_card.html" import stat_card %}
{% from "components/_section_title.html" import section_title %}
{% from "components/_sql_panel.html" import sql_panel %}
Failures›
{% if row.dbt_model_name %}{{ row.dbt_model_name }}{% else %}{{ row.affected_object or row.affected_table }}{% endif %}
{# Title includes the dataset prefix so readers can locate the
failed object without scanning a subtitle. The full
project.dataset.table path lives in affected_table; the
project context is in the global header strip. #}
{% set _path_parts = (row.affected_table or '').split('.') %}
{% set _dataset = _path_parts[1] if _path_parts | length >= 2 else '' %}
{% set _table = _path_parts[2] if _path_parts | length >= 3 else (row.affected_object or row.affected_table or '') %}
{% if _dataset %}{{ _dataset }}.{% endif %}{{ row.dbt_model_name or _table }}
{# Title-row chips carry only context that isn't restated below
(dbt origin, author filter-pivot). Error-category + suggestion
counts removed - they appear in the stat strip and in the
section headings below. #}
{% if row.dbt_model_name %}
dbt
{% endif %}
{% if row.author_email %}
{{ row.author_email }}
{% endif %}
{# Stat strip - three operational numbers for the failed query
group: Failed executions / Bytes / Slot wasted. The previous
fourth card listed every error-category rule name as plain
text, duplicating the "Errors observed" section directly below.
Dropped to three; each remaining card carries more weight. #}
{{ stat_card("Failed executions", (visible_evidence.executions if visible_evidence else "-"), accent="rose", padding="p-6") }}
{% if job and job.total_bytes_processed is not none %}
{{ stat_card(
"Bytes Processed",
job.total_bytes_processed | format_bytes,
meta=("billed: " ~ (job.total_bytes_billed | format_bytes)) if job.total_bytes_billed is not none else None,
accent="brand",
padding="p-6"
) }}
{% else %}
{{ stat_card("Bytes Processed", "-", accent="slate", padding="p-6") }}
{% endif %}
{% if visible_evidence and visible_evidence.slot_wasted %}
{{ stat_card("Slot Wasted", visible_evidence.slot_wasted, meta="summed across every failed execution", accent="rose", padding="p-6") }}
{% else %}
{{ stat_card("Slot Wasted", "-", accent="slate", padding="p-6") }}
{% endif %}
{# Cost trend collapsible. "Cost trend" (not "for this table") because
the same partial is used for consumption queries that have no
destination table. History link is rendered inside the partial via
``chart_history_href``. #}
{% if chart_points_payload %}
{% set chart_title = "Cost trend" %}
{% set chart_subtitle = "One point per day across the scan window. Days without executions are skipped." %}
{% set chart_collapsible = true %}
{% set chart_history_href = "/jobs/" ~ row.affected_table %}
{% include "jobs/_cost_chart.html" %}
{% endif %}
{# Issues found - one bordered card per issue rule that fired on
this table. Each card has a 2-column Why-this-fired / Recommended-
fix split, so readers can pair the cause with the fix per rule.
When a rule has no template SQL diff, Recommended-fix points to
the Available improvements section below rather than repeating
the Why-this-fired text verbatim. When zero issues fired
(suggestion-only object), this section is hidden. #}
{% if issue_cards %}
{{ section_title(
"Errors observed",
description="Distinct BigQuery error reasons across this group's executions. The reason code links back to Google's docs; the message is the verbatim runtime error from the most-recent failure.",
aside=((issue_cards | length) ~ (' reason' if issue_cards | length == 1 else ' reasons'))
) }}
{% for issue in issue_cards %}
{{ issue.row.opportunity_type }}
{# Each column gets its own subtle sub-card so "BigQuery error" and
"Try this fix" are visually distinct blocks instead of running
together inside the outer card. #}
BigQuery error
{{ issue.row.explanation }}
{% if issue.rule_meta and issue.rule_meta.description %}
{{ issue.rule_meta.description }}
{% endif %}
Try this fix
{% if improvements %}
See Available improvements below - audit found AST rewrites that may be the underlying fix.
{% else %}
No deterministic AST rewrite is available for this query. Inspect the failed SQL below and the BigQuery console for the failed job ID.
{% endif %}
{% endfor %}
{% endif %}
{# Failed query block removed per user feedback - the broken SQL is
already visible in the Original SQL tab of the Available
improvements cards below. #}
{# Available improvements - every SQL-rewrite suggestion that
fired on any opportunity for this table, deduped by rule_type.
Each is a deterministic rewrite (no LLM) and renders inline as
its own green/red diff. #}
{% if improvements %}
{{ section_title(
"Available improvements",
description="Deterministic SQL rewrites that fire on the failed query body. Each card carries a green/red diff plus a rollback path.",
aside=((improvements | length) ~ (' suggestion' if improvements | length == 1 else ' suggestions'))
) }}
{% for imp in improvements %}
{{ imp.display_name }}
{% if imp.template_id %}
deterministic rewrite
{% endif %}
{% if imp.occurrences and imp.occurrences > 1 %}
×{{ imp.occurrences }}
{% endif %}
{# Only show explanation prose when no diff renders below. See
opportunities/detail.html for the same rationale (the diff
tab already shows what changed). #}
{% if imp.explanation and not (imp.diff_lines or imp.before_content) %}
{{ imp.explanation }}
{% endif %}
{% if imp.diff_lines or imp.before_content %}
{% if imp.after_content %}
{% endif %}
{% if imp.diff_lines %}
{% set diff_lines = imp.diff_lines %}
{% set file_path = imp.template_id %}
{% include "components/code_diff.html" %}
{% else %}
No diff available - template did not produce a change.
{% endif %}
{# Copy button - sits in the top-right of the SQL pane.
The full SQL text lives in a hidden ``data-copy-source``
sibling so the JS handler can read it without scraping
the rendered DOM (which has line-number prefixes). #}
{% for line in (imp.before_content or '').split('\n') %}{{ loop.index }}{{ line }}
{% endfor %}
{% if imp.after_content %}
{# Suggested SQL - the full source with the rule's
recommended changes applied (i.e. the "after" side of the
diff, rendered as a complete file). Same structure as the
Original tab so the copy button reads the raw SQL from
the hidden textarea. #}
{% for line in (imp.after_content or '').split('\n') %}{{ loop.index }}{{ line }}
{% endfor %}
{% endif %}
{% endif %}
{% if imp.rollback %}
Rollback instructions
{{ imp.rollback }}
{% endif %}
{% endfor %}
{% endif %}
{# Evidence + BigQuery query lookup - two side-by-side cards on
>=lg, stacked on smaller screens. Each card collapses
independently. The right-hand card renders the
``INFORMATION_SCHEMA.JOBS_BY_PROJECT`` SELECT pre-filtered by
job_id so the user can paste it into the BigQuery console and
inspect the row themselves. Evidence + lookup come from the
representative opportunity (highest-priority for this table). #}
{% if visible_evidence or job_lookup_sql %}
{% if visible_evidence %}
Evidence
{% for key, value in visible_evidence.items() %}
{{ key }}
{% if value is mapping or (value is iterable and value is not string) %}
{{ value | tojson }}
{% else %}
{{ value }}
{% endif %}
{% endfor %}
{% endif %}
{% if job_lookup_sql %}
{{ sql_panel("job-lookup-sql", "BigQuery query", job_lookup_sql,
caption="Paste this into the BigQuery console to inspect the underlying job row.") }}
{% endif %}
{% endif %}
{# Generic tab toggler - picks up every ``[data-tab]`` /
``[data-tab-pane]`` pair inside an ancestor with
``[data-active-tab]``. Same script as the cohort page so
suggestion cards' Diff / Original SQL tabs work without coupling
to the class name. #}
{% endblock %}