import { useCallback, useEffect, useId, useState } from 'react'; import { AssessorApiClient } from '../../api/assessor-api-client'; import type { DbStructureResponse, DbStructureTable } from '../../domain/db-structure'; import { TriangleIcon } from '../../components/TriangleIcon'; import { PanelLoadingState } from '../../components/PanelLoadingState/PanelLoadingState'; const assessorApi = new AssessorApiClient(); type BackendStatus = 'connected' | 'checking' | 'unavailable'; interface DbStructurePanelProps { backendStatus: BackendStatus; } const LAYER_ORDER = ['import', 'mapping', 'canonical', 'result', 'governance', 'core', 'other'] as const; type LayerId = (typeof LAYER_ORDER)[number]; const LAYER_INFO: Record = { import: { kicker: 'Layer 1', label: 'Import & ingestion', description: 'Source files, ingestion jobs and immutable import evidence.' }, mapping: { kicker: 'Layer 2', label: 'Mapping & normalization', description: 'Recognition, mapping and normalization contracts between source and domain data.' }, canonical: { kicker: 'Layer 3', label: 'Canonical model', description: 'Stable assessments, findings and environment context independent of source tools.' }, result: { kicker: 'Layer 4', label: 'Result layer', description: 'Read models and result projections used by Work, filters and exports.' }, governance: { kicker: 'Supporting layer', label: 'Governance & quality', description: 'Reviews, validation, processing compatibility and quality controls.' }, core: { kicker: 'Foundation', label: 'Core management', description: 'Projects, scan runs and shared lifecycle state.' }, other: { kicker: 'Supporting layer', label: 'Other', description: 'Additional relational structures outside the primary processing layers.' } }; function groupByLayer(tables: DbStructureTable[]): Array<{ layer: LayerId; tables: DbStructureTable[] }> { return LAYER_ORDER .map(layer => ({ layer, tables: tables.filter(table => table.layer === layer) })) .filter(group => group.tables.length > 0); } export function summarizeDbStructure(tables: DbStructureTable[]) { const countedTables = tables.filter(table => table.approximateRowCount !== null); return { tables: tables.length, columns: tables.reduce((total, table) => total + table.columns.length, 0), relationships: tables.reduce((total, table) => total + table.foreignKeys.length, 0), approximateRows: countedTables.reduce((total, table) => total + (table.approximateRowCount ?? 0), 0), countedTables: countedTables.length }; } function DbStructureTableCard({ table, isOpen, onToggle }: { table: DbStructureTable; isOpen: boolean; onToggle: () => void }) { const detailsId = useId(); return
{isOpen &&

Columns

{table.columns.length.toLocaleString('en-US')}
    {table.columns.map(column =>
  • {column.name} {column.dataType} {column.nullable ? 'Nullable' : 'Required'}
  • )}

Relationships

{table.foreignKeys.length.toLocaleString('en-US')}
{table.foreignKeys.length > 0 ?
    {table.foreignKeys.map(fk =>
  • {fk.column}{fk.referencedTable}.{fk.referencedColumn}
  • )}
:

No outgoing relationships.

}
}
; } export function DbStructurePanel({ backendStatus }: DbStructurePanelProps) { const [structure, setStructure] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [expanded, setExpanded] = useState>({}); const load = useCallback(async () => { if (backendStatus !== 'connected') { setStructure(null); setLoading(false); return; } setLoading(true); setError(null); try { setStructure(await assessorApi.getDatabaseStructure()); } catch (reason) { setError(reason instanceof Error ? reason.message : 'Database structure could not be loaded.'); } finally { setLoading(false); } }, [backendStatus]); useEffect(() => { void load(); }, [load]); if (backendStatus !== 'connected') { return

{backendStatus === 'checking' ? 'Checking server connection' : 'Backend is unreachable'}

The database structure will be available again once the local API is connected.

; } const groups = structure ? groupByLayer(structure.tables) : []; const metrics = structure ? summarizeDbStructure(structure.tables) : undefined; const allExpanded = Boolean(structure?.tables.length) && structure!.tables.every(table => expanded[table.name]); const setAllExpanded = (open: boolean) => { if (!structure) return; setExpanded(Object.fromEntries(structure.tables.map(table => [table.name, open]))); }; return
{error &&
{error}
} {loading && !structure ? : structure && <>
Database dictionary

Relational schema

Explore how imported evidence moves through normalization into canonical and result data. Open any table to inspect its columns, constraints and relationships.