feat: complete catalog sensitivity enhancements

This commit is contained in:
Codex
2026-09-04 15:11:18 +02:00
parent b891246664
commit 7b1d69a65b
72 changed files with 33866 additions and 358 deletions
@@ -98,7 +98,7 @@ export function DatabaseRelationships({
const [search, setSearch] = useState("");
const [statusFilter, setStatusFilter] = useState<RelationshipStatusFilter>("active");
const [busy, setBusy] = useState(false);
const [selectedAction, setSelectedAction] = useState<"" | "rebuild-generated" | "relationships" | "all">("");
const [selectedAction, setSelectedAction] = useState<"" | "rebuild-generated" | "relationships">("");
const [activeRelationshipId, setActiveRelationshipId] = useState<string | null>(null);
const [addOpen, setAddOpen] = useState(false);
const [sourceTableId, setSourceTableId] = useState("");
@@ -110,7 +110,6 @@ export function DatabaseRelationships({
const [pendingMutation, setPendingMutation] = useState<"exclude" | "delete" | null>(null);
const originRef = useRef<HTMLButtonElement | null>(null);
const statusFilterRef = useRef<HTMLSelectElement | null>(null);
const bindingReady = database.connectionStatus === "reachable" && database.testedVersion === database.version;
const activeRelationship = activeRelationshipId
? data.find((relationship) => relationship.id === activeRelationshipId)
: undefined;
@@ -152,9 +151,7 @@ export function DatabaseRelationships({
? "You do not have permission to manage catalog relationships."
: database.activeSyncRun
? "Wait for the active synchronization to finish."
: selectedAction !== "rebuild-generated" && !bindingReady
? "Test the current database binding first."
: null;
: null;
const addUnavailable = !canManage
? "You do not have permission to add catalog relationships."
: database.activeSyncRun
@@ -163,12 +160,12 @@ export function DatabaseRelationships({
? "Wait for the current relationship operation to finish."
: null;
const synchronize = async (scope: "relationships" | "all") => {
const synchronize = async (scope: "relationships") => {
setBusy(true);
try {
const run = await startCatalogSync(databaseId, database.version, scope);
onRunStarted(run);
toast.success(scope === "all" ? "Full schema synchronization started" : "Relationship synchronization started");
toast.success("Relationship synchronization started");
} catch (error) { toast.error(apiErrorMessage(error)); } finally { setBusy(false); }
};
@@ -203,7 +200,7 @@ export function DatabaseRelationships({
else statusFilterRef.current?.focus();
}, 0);
toast.success(status === "excluded"
? "Relationship excluded. Future rebuilds will leave it excluded."
? "Relationship excluded. Future builds will leave it excluded."
: "Relationship restored.");
} catch (error) {
toast.error(apiErrorMessage(error));
@@ -225,7 +222,7 @@ export function DatabaseRelationships({
if (originRef.current?.isConnected) originRef.current.focus();
else statusFilterRef.current?.focus();
}, 0);
toast.success("Relationship deleted permanently. A future rebuild may infer it again.");
toast.success("Relationship deleted permanently. A future build may infer it again.");
} catch (error) {
toast.error(apiErrorMessage(error));
} finally {
@@ -289,8 +286,8 @@ export function DatabaseRelationships({
&& result.excluded === 0
&& result.ambiguous === 0;
toast.success(noFindings
? "Rebuild complete: no new relationships found."
: `Rebuild complete: ${result.added} added, ${result.alreadyPresent} already present, ${result.excluded} excluded, ${result.ambiguous} ambiguous.`);
? "Build complete: no new relationships found."
: `Build complete: ${result.added} added, ${result.alreadyPresent} already present, ${result.excluded} excluded, ${result.ambiguous} ambiguous.`);
} catch (error) {
toast.error(apiErrorMessage(error));
} finally {
@@ -341,7 +338,7 @@ export function DatabaseRelationships({
</div>
<div className="flex gap-2">
{database.activeSyncRun ? <Button type="button" variant="outline" onClick={onOpenSync}><RefreshCw className="animate-spin" />View sync</Button> : null}
<Button type="button" disabled={!canManage || !bindingReady || busy || Boolean(database.activeSyncRun)} onClick={() => void synchronize("all")}><RefreshCw />Sync all</Button>
<Button type="button" disabled={!canManage || busy || Boolean(database.activeSyncRun)} onClick={() => void synchronize("relationships")}><RefreshCw />Synchronize relationships</Button>
</div>
</div>
<nav aria-label="Database sections" className="flex gap-1 border-b border-border" role="tablist">
@@ -381,18 +378,17 @@ export function DatabaseRelationships({
className="thot-fleet-action-selector__select"
value={selectedAction}
disabled={busy}
onChange={(event) => setSelectedAction(event.target.value as "" | "rebuild-generated" | "relationships" | "all")}
onChange={(event) => setSelectedAction(event.target.value as "" | "rebuild-generated" | "relationships")}
>
<option value="">Choose an action…</option>
<optgroup label="Logical relationships">
<option value="rebuild-generated">Rebuild generated relationships</option>
<option value="rebuild-generated">Build generated relationships</option>
</optgroup>
<optgroup label="Synchronization">
<option value="relationships">Synchronize physical relationships</option>
<option value="all">Synchronize the full database schema</option>
</optgroup>
</select>
<Button type="button" size="sm" disabled={!selectedAction || Boolean(selectedActionUnavailable) || busy} aria-describedby={selectedActionUnavailable ? "relationship-action-reason" : undefined} onClick={() => void runSelectedAction()}><Play aria-hidden="true" />{busy ? selectedAction === "rebuild-generated" ? "Rebuilding…" : "Running…" : "Run action"}</Button>
<Button type="button" size="sm" disabled={!selectedAction || Boolean(selectedActionUnavailable) || busy} aria-describedby={selectedActionUnavailable ? "relationship-action-reason" : undefined} onClick={() => void runSelectedAction()}><Play aria-hidden="true" />{busy ? selectedAction === "rebuild-generated" ? "Building…" : "Running…" : "Run action"}</Button>
<Button type="button" size="sm" variant="outline" className="thot-fleet-nav-action thot-fleet-nav-action--below" data-tooltip="Synchronization history" onClick={onOpenSync}>
{database.activeSyncRun ? <RefreshCw className="animate-spin" aria-hidden="true" /> : <History aria-hidden="true" />} Sync history
</Button>
@@ -414,12 +410,11 @@ export function DatabaseRelationships({
{addUnavailable ? <span id="relationship-add-reason" className="sr-only">{addUnavailable}</span> : null}
{selectedActionUnavailable ? <span id="relationship-action-reason" className="basis-full text-xs text-muted-foreground" role="status">{selectedActionUnavailable}</span> : null}
</div>
) : <Button type="button" disabled={!canManage || !bindingReady || busy || Boolean(database.activeSyncRun)} title={!bindingReady ? "Test the current database binding before synchronizing relationships" : undefined} onClick={() => void synchronize("relationships")}><RefreshCw />Synchronize relationships</Button>}
) : <Button type="button" disabled={!canManage || busy || Boolean(database.activeSyncRun)} onClick={() => void synchronize("relationships")}><RefreshCw />Synchronize relationships</Button>}
</div>
{!bindingReady ? <div className="border-b border-border bg-muted/35 px-4 py-3 text-sm text-muted-foreground">{presentation === "fleet" ? "Test the current database binding in database details before synchronizing relationships." : "Test the current database binding from Overview before synchronizing relationships."}</div> : null}
{fleetQueryError ?? <div className="relative min-h-[280px] flex-1">
<div className={`thot-database-grid ${presentation === "fleet" ? "thot-fleet-ledger-grid " : ""}ag-theme-alpine absolute inset-0 h-full w-full`}>
<AgGridReact<CatalogRelationship> rowData={visibleRelationships} columnDefs={columns} context={context} loading={isLoading} quickFilterText={search} defaultColDef={{ sortable: true, filter: true, resizable: true }} getRowId={({ data: row }) => row.id} rowHeight={44} headerHeight={38} animateRows={false} overlayNoRowsTemplate={statusFilter === "active" ? "No relationships yet. Rebuild from column names or add one manually." : statusFilter === "excluded" ? "No excluded relationships." : "No relationships for this database."} />
<AgGridReact<CatalogRelationship> rowData={visibleRelationships} columnDefs={columns} context={context} loading={isLoading} quickFilterText={search} defaultColDef={{ sortable: true, filter: true, resizable: true }} getRowId={({ data: row }) => row.id} rowHeight={44} headerHeight={38} animateRows={false} overlayNoRowsTemplate={statusFilter === "active" ? "No relationships yet. Build from column names or add one manually." : statusFilter === "excluded" ? "No excluded relationships." : "No relationships for this database."} />
</div>
</div>}
</div>
@@ -549,8 +544,8 @@ export function DatabaseRelationships({
>
<p id="relationship-mutation-confirmation" className="mr-auto max-w-xl text-sm text-muted-foreground">
{pendingMutation === "exclude"
? "The relationship will move to Excluded. Rebuilds will not recreate it, and you can restore it later."
: "The relationship record will be erased. A future rebuild may infer it again."}
? "The relationship will move to Excluded. Builds will not recreate it, and you can restore it later."
: "The relationship record will be erased. A future build may infer it again."}
</p>
<Button type="button" variant="ghost" disabled={busy} onClick={() => setPendingMutation(null)}>Cancel</Button>
<Button