Wizard: warn in-UI when default dest table already exists.
Previously the existing-dest check fired on submit and surfaced as a raw JSON 400. Now step 3 introspects the default dest up front and renders a yellow banner listing existing columns; submit-time mismatches render wizard_error.html (409) with missing vs. existing side-by-side and a back link that re-plumbs the form qvals. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
bb0b493d18
commit
f18ea55a12
@ -13,6 +13,7 @@ wizard, editors, and SSE-driven live run watch come next.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from fastapi import APIRouter, FastAPI, HTTPException, Query, Request
|
from fastapi import APIRouter, FastAPI, HTTPException, Query, Request
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
@ -340,6 +341,23 @@ def wizard_step3(request: Request,
|
|||||||
default_dest_conn_id = conn.get("default_dest_connection_id")
|
default_dest_conn_id = conn.get("default_dest_connection_id")
|
||||||
default_dest_schema = conn.get("default_dest_schema") or ""
|
default_dest_schema = conn.get("default_dest_schema") or ""
|
||||||
|
|
||||||
|
# Proactive warning — if the default dest table already exists, surface
|
||||||
|
# its columns now so the user can align picks before submit.
|
||||||
|
dest_warn: dict | None = None
|
||||||
|
if not fetch_error and default_dest_conn_id and default_dest_schema:
|
||||||
|
dest_conn_row = repo.get_connection(default_dest_conn_id)
|
||||||
|
if dest_conn_row is not None:
|
||||||
|
try:
|
||||||
|
existing = _existing_dest_columns(
|
||||||
|
dest_conn_row, default_dest_schema, default_module_name)
|
||||||
|
except jrunner.JrunnerError:
|
||||||
|
existing = None
|
||||||
|
if existing is not None:
|
||||||
|
dest_warn = {
|
||||||
|
"qualified": f"{default_dest_schema}.{default_module_name}",
|
||||||
|
"columns": sorted(existing),
|
||||||
|
}
|
||||||
|
|
||||||
return _templates.TemplateResponse(
|
return _templates.TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"wizard_step3.html",
|
"wizard_step3.html",
|
||||||
@ -349,7 +367,8 @@ def wizard_step3(request: Request,
|
|||||||
table_description=table_description,
|
table_description=table_description,
|
||||||
fetch_error=fetch_error, default_module_name=default_module_name,
|
fetch_error=fetch_error, default_module_name=default_module_name,
|
||||||
default_dest_conn_id=default_dest_conn_id,
|
default_dest_conn_id=default_dest_conn_id,
|
||||||
default_dest_schema=default_dest_schema),
|
default_dest_schema=default_dest_schema,
|
||||||
|
dest_warn=dest_warn),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -448,12 +467,23 @@ async def wizard_create(request: Request):
|
|||||||
missing = [c["dest_name"] for c in chosen
|
missing = [c["dest_name"] for c in chosen
|
||||||
if c["dest_name"].lower() not in existing_cols]
|
if c["dest_name"].lower() not in existing_cols]
|
||||||
if missing:
|
if missing:
|
||||||
raise HTTPException(
|
back_qs = urlencode(
|
||||||
400,
|
[("source_connection_id", source_connection_id),
|
||||||
f"dest table {qualified_dest} already exists but is missing "
|
("table", table),
|
||||||
f"columns: {', '.join(missing)}. Drop the table, choose a "
|
("table_schema", qvals.get("schema") or qvals.get("library") or ""),
|
||||||
f"different dest_table, or align your column picks to match "
|
*qvals.items()])
|
||||||
f"the existing schema.")
|
return _templates.TemplateResponse(
|
||||||
|
request,
|
||||||
|
"wizard_error.html",
|
||||||
|
_ctx(
|
||||||
|
title="Dest table column mismatch",
|
||||||
|
qualified_dest=qualified_dest,
|
||||||
|
missing=missing,
|
||||||
|
existing=sorted(existing_cols),
|
||||||
|
back_qs=back_qs,
|
||||||
|
),
|
||||||
|
status_code=409,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
jrunner.run_dest_sql(
|
jrunner.run_dest_sql(
|
||||||
|
|||||||
@ -277,3 +277,4 @@ table.picker tbody tr:hover td { background: #1c2128; }
|
|||||||
}
|
}
|
||||||
.flash.ok { border-color: #2f6b35; background: #16261a; color: #b6dcb8; }
|
.flash.ok { border-color: #2f6b35; background: #16261a; color: #b6dcb8; }
|
||||||
.flash.err { border-color: #6b2f2f; background: #261616; color: #dcb6b6; }
|
.flash.err { border-color: #6b2f2f; background: #261616; color: #dcb6b6; }
|
||||||
|
.flash.warn { border-color: #6b5a2f; background: #261f16; color: #e1c98a; }
|
||||||
|
|||||||
57
pipekit/web/templates/wizard_error.html
Normal file
57
pipekit/web/templates/wizard_error.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% set section = "modules" %}
|
||||||
|
{% block title %}New module — error{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="panel">
|
||||||
|
<header>
|
||||||
|
Wizard error
|
||||||
|
<span class="subtitle">{{ title }}</span>
|
||||||
|
<span style="margin-left:auto">
|
||||||
|
<a href="/wizard/columns?{{ back_qs }}">← back to step 3</a>
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
<div class="body">
|
||||||
|
<div class="flash err">
|
||||||
|
Dest table <code>{{ qualified_dest }}</code> already exists, but your
|
||||||
|
picks don't match its schema. Drop the table, choose a different
|
||||||
|
dest table, or align your column picks (dest names) with the
|
||||||
|
existing columns below.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="two-col">
|
||||||
|
<div class="panel">
|
||||||
|
<header>
|
||||||
|
Missing from existing table
|
||||||
|
<span class="subtitle">{{ missing|length }}</span>
|
||||||
|
</header>
|
||||||
|
<div class="body tight">
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
{% for m in missing %}
|
||||||
|
<tr><td class="mono">{{ m }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<header>
|
||||||
|
Columns in existing dest
|
||||||
|
<span class="subtitle">{{ existing|length }}</span>
|
||||||
|
</header>
|
||||||
|
<div class="body tight">
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
{% for c in existing %}
|
||||||
|
<tr><td class="mono">{{ c }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -18,6 +18,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if dest_warn %}
|
||||||
|
<div class="flash warn">
|
||||||
|
Dest table <code>{{ dest_warn.qualified }}</code> already exists on the
|
||||||
|
default destination. If you proceed, pipekit will <strong>not</strong> drop
|
||||||
|
or recreate it — your picks below (dest names) must match the existing
|
||||||
|
columns, or the create will fail. Existing columns:
|
||||||
|
<span class="mono">{{ dest_warn.columns|join(', ') }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if not fetch_error %}
|
{% if not fetch_error %}
|
||||||
<form method="post" action="/wizard/create">
|
<form method="post" action="/wizard/create">
|
||||||
<input type="hidden" name="source_connection_id" value="{{ connection.id }}">
|
<input type="hidden" name="source_connection_id" value="{{ connection.id }}">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user