feat(pipeline): catalog tab templates
- partials/pipeline_catalog.html: accordion list of serving tables with row count badges, column count, click-to-expand lazy-loaded detail - partials/pipeline_table_detail.html: column schema grid + sticky-header sample data table (10 rows, truncated values with title attribute) - JS: toggleCatalogTable() + htmx.trigger(content, 'revealed') for lazy-loading detail only on first open Subtask 4 of 6 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
|||||||
|
<!-- Pipeline Catalog Tab: serving tables with column + sample data expandable -->
|
||||||
|
|
||||||
|
{% if serving_meta %}
|
||||||
|
<p class="text-xs text-slate mb-3">
|
||||||
|
Serving DB last exported: <span class="mono font-semibold">{{ serving_meta.exported_at_utc[:19].replace('T', ' ') }}</span>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if tables %}
|
||||||
|
<div style="display:grid;grid-template-columns:1fr;gap:0.75rem">
|
||||||
|
{% for table in tables %}
|
||||||
|
<div class="card" style="padding:0" id="catalog-table-{{ table.name }}">
|
||||||
|
<!-- Table header row -->
|
||||||
|
<div class="flex items-center justify-between"
|
||||||
|
style="padding:0.875rem 1.25rem;cursor:pointer"
|
||||||
|
onclick="toggleCatalogTable('{{ table.name }}')">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
stroke="currentColor" style="width:16px;height:16px;color:#94A3B8;flex-shrink:0">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0 1 12 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375Z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="font-semibold text-sm text-navy">serving.<span class="mono">{{ table.name }}</span></span>
|
||||||
|
<span class="text-xs text-slate">{{ table.column_count }} col{{ 's' if table.column_count != 1 }}</span>
|
||||||
|
{% if table.row_count is not none %}
|
||||||
|
<span class="badge" style="background:#F0FDF4;color:#16A34A;font-size:10px">
|
||||||
|
{{ "{:,}".format(table.row_count) }} rows
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<svg id="chevron-{{ table.name }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="2" stroke="currentColor"
|
||||||
|
style="width:14px;height:14px;color:#94A3B8;transition:transform 0.2s">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Expandable detail (HTMX lazy-loaded on first open) -->
|
||||||
|
<div id="catalog-detail-{{ table.name }}" style="display:none;border-top:1px solid #E2E8F0">
|
||||||
|
<div id="catalog-content-{{ table.name }}"
|
||||||
|
hx-get="{{ url_for('pipeline.pipeline_table_detail', table_name=table.name) }}"
|
||||||
|
hx-trigger="revealed"
|
||||||
|
hx-target="#catalog-content-{{ table.name }}"
|
||||||
|
hx-swap="outerHTML">
|
||||||
|
<div style="padding:1.5rem;text-align:center">
|
||||||
|
<p class="text-sm text-slate">Loading…</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="card text-center" style="padding:2rem">
|
||||||
|
<p class="text-slate">No serving tables found. Run the pipeline to generate them.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleCatalogTable(name) {
|
||||||
|
var detail = document.getElementById('catalog-detail-' + name);
|
||||||
|
var chevron = document.getElementById('chevron-' + name);
|
||||||
|
var isOpen = detail.style.display !== 'none';
|
||||||
|
detail.style.display = isOpen ? 'none' : 'block';
|
||||||
|
chevron.style.transform = isOpen ? '' : 'rotate(90deg)';
|
||||||
|
// Trigger HTMX revealed for lazy-loading when first opened
|
||||||
|
if (!isOpen) {
|
||||||
|
var content = document.getElementById('catalog-content-' + name);
|
||||||
|
if (content && content.getAttribute('hx-trigger') === 'revealed') {
|
||||||
|
htmx.trigger(content, 'revealed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<!-- Pipeline Table Detail: column schema + 10-row sample data -->
|
||||||
|
<!-- This partial replaces #catalog-content-<table_name> via HTMX -->
|
||||||
|
<div id="catalog-content-{{ table_name }}" style="padding:1.25rem">
|
||||||
|
|
||||||
|
<!-- Column schema -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-xs font-semibold text-slate uppercase mb-2" style="letter-spacing:0.05em">Schema</p>
|
||||||
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:6px">
|
||||||
|
{% for col in columns %}
|
||||||
|
<div style="background:#F8FAFC;border:1px solid #E2E8F0;border-radius:6px;padding:6px 10px">
|
||||||
|
<span class="text-xs font-semibold text-navy mono">{{ col.column_name }}</span>
|
||||||
|
<span class="text-xs text-slate ml-2">{{ col.data_type | upper }}</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sample rows -->
|
||||||
|
{% if sample %}
|
||||||
|
<div>
|
||||||
|
<p class="text-xs font-semibold text-slate uppercase mb-2" style="letter-spacing:0.05em">
|
||||||
|
Sample (first {{ sample | length }} rows)
|
||||||
|
</p>
|
||||||
|
<div style="overflow-x:auto;max-height:320px;overflow-y:auto">
|
||||||
|
<table class="table" style="font-size:0.75rem">
|
||||||
|
<thead style="position:sticky;top:0;background:#fff;z-index:1">
|
||||||
|
<tr>
|
||||||
|
{% for col in columns %}
|
||||||
|
<th style="white-space:nowrap">{{ col.column_name }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in sample %}
|
||||||
|
<tr>
|
||||||
|
{% for col in columns %}
|
||||||
|
<td class="mono" style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
|
||||||
|
title="{{ row[col.column_name] | string }}">
|
||||||
|
{% set val = row[col.column_name] %}
|
||||||
|
{% if val is none %}
|
||||||
|
<span class="text-slate">null</span>
|
||||||
|
{% elif val | string | length > 40 %}
|
||||||
|
{{ val | string | truncate(40, true) }}
|
||||||
|
{% else %}
|
||||||
|
{{ val }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-sm text-slate">Table is empty.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user