fix: scope and batch sensitive suggestions

This commit is contained in:
Codex
2026-08-30 17:12:20 +02:00
parent 0736983bc5
commit cb40c09d9a
17 changed files with 1179 additions and 139 deletions
@@ -11,11 +11,11 @@ import {
consolidateCatalogDescriptions,
listCatalogColumns,
startDescriptionGenerationRun,
suggestSensitiveFields,
updateCatalogColumnMetadata,
type CatalogColumn,
type CatalogTable,
type DescriptionGenerationRun,
type SensitiveDataSuggestionRequest,
} from "../../api/catalog-databases";
import type { DatabaseNavigationState } from "./model";
@@ -28,6 +28,7 @@ interface Props {
onDescriptionGenerationRunStarted: (run: DescriptionGenerationRun) => void;
onNavigationStateChange: (state: DatabaseNavigationState) => void;
onSync: () => void;
onSuggestSensitive: (selection: SensitiveDataSuggestionRequest, scopeLabel: string) => Promise<void>;
}
interface GridContext {
@@ -84,6 +85,7 @@ export function DatabaseColumns({
onDescriptionGenerationRunStarted,
onNavigationStateChange,
onSync,
onSuggestSensitive,
}: Props) {
const queryClient = useQueryClient();
const queryKey = ["catalog-columns", databaseId, table.id] as const;
@@ -222,23 +224,16 @@ export function DatabaseColumns({
};
const suggestSensitive = async () => {
if (!selectedMetadataModel) return;
if (selectedIds.length === 0) return;
setBusy(true);
setSensitiveAction("suggest");
try {
const result = await suggestSensitiveFields(databaseId, selectedMetadataModel);
const currentById = new Map(data.map((column) => [column.id, column]));
const next: Record<string, boolean> = {};
for (const suggestion of result.suggestions) {
const column = currentById.get(suggestion.columnId);
if (column && suggestion.sensitive !== column.sensitive) {
next[column.id] = suggestion.sensitive;
}
}
setSensitiveDrafts(next);
toast.success("Sensitive field suggestions ready for review");
} catch (error) {
toast.error(apiErrorMessage(error));
await onSuggestSensitive(
{ scope: "selected_columns", targetIds: selectedIds },
`${selectedIds.length} selected column${selectedIds.length === 1 ? "" : "s"} in ${table.name}`,
);
} catch {
// The page-level request reports the safe, specific reason and preserves the selection.
} finally {
setSensitiveAction(null);
setBusy(false);
@@ -351,6 +346,20 @@ export function DatabaseColumns({
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
<Button
type="button"
variant="outline"
disabled={!canManage || descriptionGenerationActive || busy}
title={descriptionGenerationActive ? "Wait for the active description generation to finish" : undefined}
onClick={() => void suggestSensitive()}
>
<Sparkles />{sensitiveAction === "suggest" ? "Suggesting…" : "Suggest sensitive fields"}
</Button>
{changedSensitiveColumns.length > 0 ? (
<Button type="button" disabled={!canManage || busy} onClick={() => void saveSensitive()}>
<Save />{sensitiveAction === "save" ? "Saving…" : "Save sensitive fields"}
</Button>
) : null}
<Button type="button" variant="ghost" onClick={() => { gridRef.current?.api.deselectAll(); setSelectedIds([]); }}><X />Clear</Button>
</>
) : (
@@ -358,14 +367,6 @@ export function DatabaseColumns({
<span className="thot-label whitespace-nowrap">Catalog columns</span>
<input className="h-8 min-w-40 flex-1 rounded-md border border-input bg-background px-2.5 text-sm outline-none focus:border-primary/60 focus:ring-3 focus:ring-ring/15" aria-label="Search columns" placeholder="Search" value={search} onChange={(event) => setSearch(event.target.value)} />
<span className="text-xs tabular-nums text-muted-foreground">{data.length}</span>
<Button
type="button"
variant="outline"
disabled={!canManage || !selectedMetadataModel || descriptionGenerationActive || busy}
onClick={() => void suggestSensitive()}
>
<Sparkles />{sensitiveAction === "suggest" ? "Suggesting…" : "Suggest sensitive fields"}
</Button>
{changedSensitiveColumns.length > 0 ? (
<Button type="button" disabled={!canManage || busy} onClick={() => void saveSensitive()}>
<Save />{sensitiveAction === "save" ? "Saving…" : "Save sensitive fields"}