feat: refine metadata catalog workflows
This commit is contained in:
@@ -80,6 +80,20 @@ const SYNC_STARTED_MESSAGES: Record<CatalogSyncScope, string> = {
|
||||
relationships: "Relationship synchronization started",
|
||||
all: "Full schema synchronization started",
|
||||
};
|
||||
const DATABASE_SCREEN_STORAGE_KEY = "thothii:database-management-screen";
|
||||
|
||||
function readPersistedDatabaseScreen(): DatabaseScreen {
|
||||
if (import.meta.env.MODE === "test") return { kind: "list" };
|
||||
try {
|
||||
const parsed = JSON.parse(window.localStorage.getItem(DATABASE_SCREEN_STORAGE_KEY) ?? "null") as { kind?: unknown; workspaceId?: unknown } | null;
|
||||
if ((parsed?.kind === "tables" || parsed?.kind === "relationships") && typeof parsed.workspaceId === "string") {
|
||||
return { kind: parsed.kind, workspaceId: parsed.workspaceId };
|
||||
}
|
||||
} catch {
|
||||
// Ignore malformed persisted navigation and return to the safe list view.
|
||||
}
|
||||
return { kind: "list" };
|
||||
}
|
||||
|
||||
interface Props {
|
||||
canManage: boolean;
|
||||
@@ -156,12 +170,13 @@ export function DatabaseManagementPage({
|
||||
(run) => run.status === "running",
|
||||
);
|
||||
|
||||
const [screen, setScreen] = useState<DatabaseScreen>({ kind: "list" });
|
||||
const [screen, setScreen] = useState<DatabaseScreen>(readPersistedDatabaseScreen);
|
||||
const [selectedMetadataModel, setSelectedMetadataModel] = useState("");
|
||||
const [draft, setDraft] = useState<DatabaseFormDraft | null>(null);
|
||||
const [baseline, setBaseline] = useState("");
|
||||
const [formSource, setFormSource] = useState<FormSource | null>(null);
|
||||
const [search, setSearch] = useState("");
|
||||
const [selectedDatabaseId, setSelectedDatabaseId] = useState<string | null>(null);
|
||||
const [busyAction, setBusyAction] = useState<DatabaseBusyAction>(null);
|
||||
const [stale, setStale] = useState(false);
|
||||
const [staleBannerOpen, setStaleBannerOpen] = useState(true);
|
||||
@@ -171,6 +186,11 @@ export function DatabaseManagementPage({
|
||||
busy: false,
|
||||
});
|
||||
const [tableNestedNavigationActive, setTableNestedNavigationActive] = useState(false);
|
||||
const [tableNestedNavigationBack, setTableNestedNavigationBack] = useState<(() => void) | null>(null);
|
||||
const handleTableNestedNavigationChange = useCallback((active: boolean, onBack?: () => void) => {
|
||||
setTableNestedNavigationActive(active);
|
||||
setTableNestedNavigationBack(() => (active ? onBack ?? null : null));
|
||||
}, []);
|
||||
const [activeSyncRun, setActiveSyncRun] = useState<CatalogSyncRun | null>(null);
|
||||
const [syncHistoryDatabaseId, setSyncHistoryDatabaseId] = useState<string | null>(null);
|
||||
const [syncDrawerOpen, setSyncDrawerOpen] = useState(false);
|
||||
@@ -184,6 +204,12 @@ export function DatabaseManagementPage({
|
||||
suggestions: SensitiveDataSuggestion[];
|
||||
} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (import.meta.env.MODE === "test") return;
|
||||
const persisted = screen.kind === "tables" || screen.kind === "relationships" ? screen : { kind: "list" };
|
||||
window.localStorage.setItem(DATABASE_SCREEN_STORAGE_KEY, JSON.stringify(persisted));
|
||||
}, [screen]);
|
||||
|
||||
const originRef = useRef<HTMLElement | null>(null);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const formHeadingRef = useRef<HTMLHeadingElement>(null);
|
||||
@@ -284,6 +310,7 @@ export function DatabaseManagementPage({
|
||||
setPartialSecretFailure(null);
|
||||
setTablesNavigationState({ dirty: false, busy: false });
|
||||
setTableNestedNavigationActive(false);
|
||||
setTableNestedNavigationBack(null);
|
||||
restoreListFocus();
|
||||
}, [restoreListFocus]);
|
||||
|
||||
@@ -346,6 +373,7 @@ export function DatabaseManagementPage({
|
||||
setPartialSecretFailure(null);
|
||||
setTablesNavigationState({ dirty: false, busy: false });
|
||||
setTableNestedNavigationActive(false);
|
||||
setTableNestedNavigationBack(null);
|
||||
setScreen({ kind: "tables", workspaceId: row.workspaceId });
|
||||
}, []);
|
||||
|
||||
@@ -358,6 +386,8 @@ export function DatabaseManagementPage({
|
||||
setStale(false);
|
||||
setPartialSecretFailure(null);
|
||||
setTablesNavigationState({ dirty: false, busy: false });
|
||||
setTableNestedNavigationActive(false);
|
||||
setTableNestedNavigationBack(null);
|
||||
setScreen({ kind: "relationships", workspaceId: row.workspaceId });
|
||||
}, []);
|
||||
|
||||
@@ -856,6 +886,11 @@ export function DatabaseManagementPage({
|
||||
}
|
||||
}, [invalidateCatalogMetrics, queryClient]);
|
||||
|
||||
const clearActiveCatalogTables = useCallback(async () => {
|
||||
if (!activeRow?.id) return;
|
||||
await deleteSelectedMetadata([activeRow], "tables");
|
||||
}, [activeRow, deleteSelectedMetadata]);
|
||||
|
||||
const syncDatabase = useCallback(async (scope: CatalogSyncScope) => {
|
||||
if (!activeRow?.id || !activeRow.configured) return;
|
||||
try {
|
||||
@@ -1038,13 +1073,12 @@ export function DatabaseManagementPage({
|
||||
onOpenOverview={() => openOverview(activeRow)}
|
||||
onOpenRelationships={() => openRelationships(activeRow)}
|
||||
onNavigationStateChange={setTablesNavigationState}
|
||||
onNestedNavigationChange={setTableNestedNavigationActive}
|
||||
onNestedNavigationChange={handleTableNestedNavigationChange}
|
||||
onRunStarted={rememberSyncRun}
|
||||
onOpenSync={() => openSync(activeRow)}
|
||||
onOpenDescriptionHistory={openDescriptionGenerationHistory}
|
||||
onOpenSensitiveHistory={openSensitiveDataSuggestionHistory}
|
||||
onDescriptionGenerationRunStarted={rememberDescriptionGenerationRun}
|
||||
onSuggestSensitive={suggestActiveDatabaseSensitiveFields}
|
||||
onClearCatalogTables={clearActiveCatalogTables}
|
||||
onCatalogMetricsChanged={invalidateCatalogMetrics}
|
||||
/>
|
||||
) : screen.kind === "relationships" && relationshipsVisible ? (
|
||||
@@ -1088,12 +1122,14 @@ export function DatabaseManagementPage({
|
||||
onEdit={editRow}
|
||||
onDelete={deleteRow}
|
||||
onOpenSync={openSync}
|
||||
onSelectionChange={(selected) => setSelectedDatabaseId(selected.length === 1 ? selected[0].id ?? null : null)}
|
||||
onTestSelected={testSelected}
|
||||
onSyncSelected={syncSelected}
|
||||
selectedMetadataModel={selectedMetadataModelAvailable ? selectedMetadataModel : null}
|
||||
descriptionGenerationActive={descriptionGenerationActive}
|
||||
onGenerateDescriptions={generateDatabaseDescriptions}
|
||||
onSuggestSensitive={suggestDatabaseSensitiveFields}
|
||||
sensitiveDataSuggestionRuns={sensitiveDataSuggestionRuns}
|
||||
onDeleteMetadataSelected={deleteSelectedMetadata}
|
||||
onRefresh={refreshList}
|
||||
/>
|
||||
@@ -1125,6 +1161,45 @@ export function DatabaseManagementPage({
|
||||
actions={(
|
||||
<>
|
||||
{fleetMetadataModelControl}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="whitespace-nowrap"
|
||||
aria-label="View schema synchronization logs"
|
||||
title="View schema synchronization progress and history"
|
||||
disabled={!canManage || (!selectedDatabaseId && !activeSyncRun?.databaseId)}
|
||||
onClick={() => openSync(selectedDatabaseId ? rows.find((row) => row.id === selectedDatabaseId) : undefined)}
|
||||
>
|
||||
<RefreshCw aria-hidden="true" />
|
||||
{currentActiveRun ? "Schema sync active" : "Schema sync logs"}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="whitespace-nowrap"
|
||||
aria-label="View AI description generation logs"
|
||||
title="View AI description generation progress and history"
|
||||
disabled={!canManage}
|
||||
onClick={openDescriptionGenerationHistory}
|
||||
>
|
||||
<History aria-hidden="true" />
|
||||
{descriptionGenerationActive ? "AI descriptions active" : "AI description logs"}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="whitespace-nowrap"
|
||||
aria-label="View sensitive analysis progress"
|
||||
title="View sensitive analysis progress and history"
|
||||
disabled={!canManage}
|
||||
onClick={openSensitiveDataSuggestionHistory}
|
||||
>
|
||||
<History aria-hidden="true" />
|
||||
{observedActiveSensitiveDataSuggestionRun ? "Sensitive analysis active" : "Sensitive analysis history"}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
@@ -1144,9 +1219,17 @@ export function DatabaseManagementPage({
|
||||
navigation={(
|
||||
<FleetLedgerBreadcrumb
|
||||
items={fleetBreadcrumb}
|
||||
back={screen.kind === "relationships" || (screen.kind === "tables" && !tableNestedNavigationActive)
|
||||
back={screen.kind === "relationships"
|
||||
? { label: "Back to databases", onBack: showList, disabled: navigationBusy }
|
||||
: undefined}
|
||||
: screen.kind === "tables" && tableNestedNavigationActive
|
||||
? {
|
||||
label: "Back to tables",
|
||||
onBack: () => tableNestedNavigationBack?.(),
|
||||
disabled: navigationBusy || !tableNestedNavigationBack,
|
||||
}
|
||||
: screen.kind === "tables"
|
||||
? { label: "Back to databases", onBack: showList, disabled: navigationBusy }
|
||||
: undefined}
|
||||
/>
|
||||
)}
|
||||
children={<FleetLedgerContent busy={isLoading || isFetching}>{fleetContent}</FleetLedgerContent>}
|
||||
@@ -1246,10 +1329,12 @@ export function DatabaseManagementPage({
|
||||
onEdit={editRow}
|
||||
onDelete={deleteRow}
|
||||
onOpenSync={openSync}
|
||||
onSelectionChange={(selected) => setSelectedDatabaseId(selected.length === 1 ? selected[0].id ?? null : null)}
|
||||
onTestSelected={testSelected}
|
||||
onSyncSelected={syncSelected}
|
||||
selectedMetadataModel={selectedMetadataModelAvailable ? selectedMetadataModel : null}
|
||||
descriptionGenerationActive={descriptionGenerationActive}
|
||||
sensitiveDataSuggestionRuns={sensitiveDataSuggestionRuns}
|
||||
onGenerateDescriptions={generateDatabaseDescriptions}
|
||||
onSuggestSensitive={suggestDatabaseSensitiveFields}
|
||||
onDeleteMetadataSelected={deleteSelectedMetadata}
|
||||
@@ -1304,10 +1389,9 @@ export function DatabaseManagementPage({
|
||||
onNavigationStateChange={setTablesNavigationState}
|
||||
onRunStarted={rememberSyncRun}
|
||||
onOpenSync={() => openSync(activeRow)}
|
||||
onOpenDescriptionHistory={openDescriptionGenerationHistory}
|
||||
onOpenSensitiveHistory={openSensitiveDataSuggestionHistory}
|
||||
onDescriptionGenerationRunStarted={rememberDescriptionGenerationRun}
|
||||
onSuggestSensitive={suggestActiveDatabaseSensitiveFields}
|
||||
onClearCatalogTables={clearActiveCatalogTables}
|
||||
onCatalogMetricsChanged={invalidateCatalogMetrics}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user