diff --git a/pipekit/web/app.py b/pipekit/web/app.py index c852b4b..c38b4b3 100644 --- a/pipekit/web/app.py +++ b/pipekit/web/app.py @@ -13,6 +13,7 @@ wizard, editors, and SSE-driven live run watch come next. from __future__ import annotations from pathlib import Path +from urllib.parse import urlencode from fastapi import APIRouter, FastAPI, HTTPException, Query, Request 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_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( request, "wizard_step3.html", @@ -349,7 +367,8 @@ def wizard_step3(request: Request, table_description=table_description, fetch_error=fetch_error, default_module_name=default_module_name, 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 if c["dest_name"].lower() not in existing_cols] if missing: - raise HTTPException( - 400, - f"dest table {qualified_dest} already exists but is missing " - f"columns: {', '.join(missing)}. Drop the table, choose a " - f"different dest_table, or align your column picks to match " - f"the existing schema.") + back_qs = urlencode( + [("source_connection_id", source_connection_id), + ("table", table), + ("table_schema", qvals.get("schema") or qvals.get("library") or ""), + *qvals.items()]) + 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: jrunner.run_dest_sql( diff --git a/pipekit/web/static/style.css b/pipekit/web/static/style.css index eed6905..24e85b7 100644 --- a/pipekit/web/static/style.css +++ b/pipekit/web/static/style.css @@ -277,3 +277,4 @@ table.picker tbody tr:hover td { background: #1c2128; } } .flash.ok { border-color: #2f6b35; background: #16261a; color: #b6dcb8; } .flash.err { border-color: #6b2f2f; background: #261616; color: #dcb6b6; } +.flash.warn { border-color: #6b5a2f; background: #261f16; color: #e1c98a; } diff --git a/pipekit/web/templates/wizard_error.html b/pipekit/web/templates/wizard_error.html new file mode 100644 index 0000000..5ddfbca --- /dev/null +++ b/pipekit/web/templates/wizard_error.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} +{% set section = "modules" %} +{% block title %}New module — error{% endblock %} + +{% block content %} +
+
+ Wizard error + {{ title }} + + ← back to step 3 + +
+
+
+ Dest table {{ qualified_dest }} 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. +
+ +
+
+
+ Missing from existing table + {{ missing|length }} +
+
+ + + {% for m in missing %} + + {% endfor %} + +
{{ m }}
+
+
+ +
+
+ Columns in existing dest + {{ existing|length }} +
+
+ + + {% for c in existing %} + + {% endfor %} + +
{{ c }}
+
+
+
+
+
+{% endblock %} diff --git a/pipekit/web/templates/wizard_step3.html b/pipekit/web/templates/wizard_step3.html index d8db190..04d9a7d 100644 --- a/pipekit/web/templates/wizard_step3.html +++ b/pipekit/web/templates/wizard_step3.html @@ -18,6 +18,16 @@ +{% if dest_warn %} +
+ Dest table {{ dest_warn.qualified }} already exists on the + default destination. If you proceed, pipekit will not drop + or recreate it — your picks below (dest names) must match the existing + columns, or the create will fail. Existing columns: + {{ dest_warn.columns|join(', ') }} +
+{% endif %} + {% if not fetch_error %}