import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Archive, Database, DatabaseZap, Download, Eye, Layers3, Pencil, Plus, Snowflake, ShieldCheck, ShieldQuestion, Trash2, Upload } from "lucide-react"; import { FormGrid, DialogSection, ActionToolbar, ActionBlockerHint, Button, ConfirmDialog, ContentSection, Dialog, DocumentationHelpLink, DismissibleAlert, FilterBar, FormField, IconButton, LoadingFrame, MetricCard, MetricGrid, SegmentedControl, SelectionList, SelectionListItem, SelectionListItemContent, StatePanel, StatusBadge, WorkspaceActionBar, WorkspaceFrame, WorkspaceLayout, hasScope, authAuthorityKey, isApiError, useUnsavedChanges, useUnsavedDraftGuard, type ApiSettings, type AuthInfo } from "@govoplan/core-webui"; import { applyDatasourceRetention, createDatasourceStage, decideDatasourceStage, freezeDatasource, listDatasourceMaterializations, listDatasourceOrigins, listDatasourceStages, listDatasources, previewDatasource, previewDatasourceRetention, promoteDatasourceStage, prepareDatasourceRefresh, refreshDatasource, registerDatasourceOrigin, retireDatasource, updateDatasourceGovernance, type Datasource, type DatasourceGovernance, type DatasourceMaterialization, type DatasourceOrigin, type DatasourcePreview, type DatasourceRetentionPlan, type DatasourceSchemaChange, type DatasourceStage, type DatasourceValidationDiagnostic } from "../../api/datasources"; import { DATASOURCE_FIELDS_DOCUMENTATION, DATASOURCE_GOVERNANCE_DOCUMENTATION, DATASOURCE_VISIBILITY_DOCUMENTATION, DATASOURCES_DOCUMENTATION, DATASOURCES_I18N } from "./interfacePatterns"; type CatalogueView = "catalogue" | "staging" | "origins"; type AddKind = "upload" | "origin"; type UploadFormat = "json" | "csv"; export default function DatasourcesPage({ settings, auth }: { settings: ApiSettings; auth: AuthInfo; }) { const [view, setView] = useState("catalogue"); const [loadedDatasources, setDatasources] = useState([]); const [loadedStages, setStages] = useState([]); const [loadedOrigins, setOrigins] = useState([]); const [loadedOriginsAvailable, setOriginsAvailable] = useState(false); const [selectedDatasourceRef, setSelectedDatasourceRef] = useState( initialDatasourceRef ); const [selectedStageRef, setSelectedStageRef] = useState(""); const [selectedOriginRef, setSelectedOriginRef] = useState(""); const [preview, setPreview] = useState(null); const [materializations, setMaterializations] = useState([]); const [search, setSearch] = useState(""); const [loading, setLoading] = useState(true); const [detailLoading, setDetailLoading] = useState(false); const [detailRevision, setDetailRevision] = useState(0); const authorityKey = authAuthorityKey(auth, settings); const authority = useRef(authorityKey); authority.current = authorityKey; const authorityEpoch = useRef({ key: authorityKey, revision: 0 }); if (authorityEpoch.current.key !== authorityKey) { authorityEpoch.current = { key: authorityKey, revision: authorityEpoch.current.revision + 1 }; } const dialogScope = authorityEpoch.current.revision; const isCurrentAuthority = () => authority.current === authorityKey && authorityEpoch.current.revision === dialogScope; const [catalogueAuthority, setCatalogueAuthority] = useState(null); const catalogueReady = catalogueAuthority === dialogScope; const datasources = catalogueReady ? loadedDatasources : []; const stages = catalogueReady ? loadedStages : []; const origins = catalogueReady ? loadedOrigins : []; const originsAvailable = catalogueReady && loadedOriginsAvailable; const detailScope = useMemo(() => ({}), [selectedDatasourceRef, dialogScope, detailRevision]); const currentDetailScope = useRef(detailScope); currentDetailScope.current = detailScope; const [detailResponseScope, setDetailResponseScope] = useState(null); const reloadRequestId = useRef(0); const [working, setWorking] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); const [addOpen, setAddOpen] = useState(false); const [freezeOpen, setFreezeOpen] = useState(false); const [freezeLabel, setFreezeLabel] = useState(""); const [retireOpen, setRetireOpen] = useState(false); const [promoteOpen, setPromoteOpen] = useState(false); const [governanceOpen, setGovernanceOpen] = useState(false); const [decisionOpen, setDecisionOpen] = useState(false); const [retentionOpen, setRetentionOpen] = useState(false); const { requestDiscard } = useUnsavedChanges(); const canManage = hasScope(auth, "datasources:source:write") || hasScope(auth, "datasources:source:admin"); const canStage = hasScope(auth, "datasources:stage:write") || hasScope(auth, "datasources:source:admin"); const canApprove = hasScope(auth, "datasources:stage:approve") || hasScope(auth, "datasources:source:admin"); const canAdmin = hasScope(auth, "datasources:source:admin"); const reload = useCallback(async (preferredDatasourceRef?: string) => { // An old mutation callback can invoke this closure AFTER authority changed. // Compare the closure's scope before any request, state write or generation bump. if (!isCurrentAuthority()) return; const requestId = ++reloadRequestId.current; const isCurrent = () => requestId === reloadRequestId.current && isCurrentAuthority(); setLoading(true); setError(""); try { const [nextDatasources, nextStages, originCatalogue] = await Promise.all([ listDatasources(settings), listDatasourceStages(settings), listDatasourceOrigins(settings) ]); if (!isCurrent()) return; setCatalogueAuthority(dialogScope); setDatasources(nextDatasources); setStages(nextStages); setOrigins(originCatalogue.origins); setOriginsAvailable(originCatalogue.available); // Reload also invalidates history/preview when the selected reference did // not change (refresh, freeze, promotion, governance and manual reload). setDetailRevision((current) => current + 1); setSelectedDatasourceRef((current) => { const preferred = preferredDatasourceRef || current; return nextDatasources.some((item) => item.ref === preferred) ? preferred : nextDatasources[0]?.ref ?? ""; }); setSelectedStageRef((current) => nextStages.some((item) => item.ref === current) ? current : nextStages[0]?.ref ?? ""); setSelectedOriginRef((current) => originCatalogue.origins.some((item) => item.ref === current) ? current : originCatalogue.origins[0]?.ref ?? ""); } catch (loadError) { if (isCurrent()) setError(apiErrorMessage(loadError)); } finally { if (isCurrent()) setLoading(false); } }, [dialogScope]); useEffect(() => { authority.current = authorityKey; setWorking(false); setSuccess(""); // Confirmations and their input must not follow a selection into a new // authority context. Equivalent session refreshes keep this effect stable. setAddOpen(false); setFreezeOpen(false); setFreezeLabel(""); setRetireOpen(false); setPromoteOpen(false); setGovernanceOpen(false); setDecisionOpen(false); setRetentionOpen(false); void reload(); return () => { reloadRequestId.current += 1; if (authority.current === authorityKey) authority.current = ""; }; }, [reload]); useEffect(() => { if (!selectedDatasourceRef || loading || !catalogueReady) { setPreview(null); setMaterializations([]); setDetailLoading(false); return; } let cancelled = false; setDetailLoading(true); Promise.allSettled([ previewDatasource(settings, selectedDatasourceRef), listDatasourceMaterializations(settings, selectedDatasourceRef) ]).then(([previewResult, materializationResult]) => { if (cancelled || currentDetailScope.current !== detailScope) return; setDetailResponseScope(detailScope); if (previewResult.status === "fulfilled") { setPreview(previewResult.value); } else { setPreview(null); setError(apiErrorMessage(previewResult.reason)); } setMaterializations( materializationResult.status === "fulfilled" ? materializationResult.value : [] ); setDetailLoading(false); }); return () => { cancelled = true; }; }, [selectedDatasourceRef, detailScope, loading, catalogueReady]); const selectedDatasource = datasources.find((item) => item.ref === selectedDatasourceRef) ?? null; const selectedStage = stages.find((item) => item.ref === selectedStageRef) ?? null; const selectedOrigin = origins.find((item) => item.ref === selectedOriginRef) ?? null; const visibleDatasources = useMemo( () => filterItems(datasources, search, (item) => `${item.name} ${item.source_name} ${item.description ?? ""} ${item.mode} ${item.kind}`), [datasources, search] ); const visibleStages = useMemo( () => filterItems(stages, search, (item) => `${item.name} ${item.source_name} ${item.state} ${item.mode}`), [stages, search] ); const visibleOrigins = useMemo( () => filterItems(origins, search, (item) => `${item.name} ${item.source_name} ${item.provider} ${item.kind}`), [origins, search] ); const promoteStage = async () => { if (!isCurrentAuthority() || !selectedStage || selectedStage.state !== "ready") return; setWorking(true); setError(""); try { const result = await promoteDatasourceStage(settings, selectedStage.ref); if (!isCurrentAuthority()) return; setSuccess(`Promoted ${result.datasource.name} as revision ${result.materialization.revision}.`); setView("catalogue"); await reload(result.datasource.ref); } catch (operationError) { if (isCurrentAuthority()) setError(apiErrorMessage(operationError)); } finally { if (isCurrentAuthority()) setWorking(false); } }; const refreshSelected = async () => { if (!isCurrentAuthority() || !selectedDatasource) return; setWorking(true); setError(""); try { if (selectedDatasource.governance.approval_policy.required === true) { const stage = await prepareDatasourceRefresh(settings, selectedDatasource.ref); if (!isCurrentAuthority()) return; await reload(selectedDatasource.ref); if (!isCurrentAuthority()) return; setSelectedStageRef(stage.ref); setView("staging"); setSuccess(`Prepared refresh stage ${stage.name}. It must be approved before promotion.`); return; } const result = await refreshDatasource(settings, selectedDatasource.ref); if (!isCurrentAuthority()) return; setSuccess(`Refreshed ${result.datasource.name} as revision ${result.materialization.revision}.`); await reload(result.datasource.ref); } catch (operationError) { if (isCurrentAuthority()) setError(apiErrorMessage(operationError)); } finally { if (isCurrentAuthority()) setWorking(false); } }; const freezeSelected = async (): Promise => { if (!isCurrentAuthority() || !selectedDatasource) return false; setWorking(true); setError(""); try { const materialization = await freezeDatasource( settings, selectedDatasource.ref, freezeLabel ); if (!isCurrentAuthority()) return false; setSuccess(`Created frozen revision ${materialization.revision}.`); setFreezeOpen(false); setFreezeLabel(""); await reload(selectedDatasource.ref); return isCurrentAuthority(); } catch (operationError) { if (isCurrentAuthority()) setError(apiErrorMessage(operationError)); return false; } finally { if (isCurrentAuthority()) setWorking(false); } }; const retireSelected = async () => { if (!isCurrentAuthority() || !selectedDatasource) return; setWorking(true); setError(""); try { await retireDatasource(settings, selectedDatasource.ref); if (!isCurrentAuthority()) return; setSuccess(`Retired ${selectedDatasource.name}.`); setRetireOpen(false); await reload(); } catch (operationError) { if (isCurrentAuthority()) setError(apiErrorMessage(operationError)); } finally { if (isCurrentAuthority()) setWorking(false); } }; useUnsavedDraftGuard({ dirty: Boolean(freezeOpen && freezeLabel.trim()), onSave: freezeSelected, onDiscard: () => { setFreezeOpen(false); setFreezeLabel(""); }, title: "i18n:govoplan-datasources.unsaved_freeze_title", message: "i18n:govoplan-datasources.unsaved_freeze_message" }); const closeFreeze = () => { if (working) return; if (freezeLabel.trim()) requestDiscard(() => setFreezeOpen(false)); else setFreezeOpen(false); }; return ( } scope="workspace" variant="collection" refreshable reloadAction={{ onReload: () => void reload(selectedDatasourceRef), loading: loading || working }} createAction={} variant="primary" onClick={() => setAddOpen(true)} disabled={!canStage && !canManage} disabledReason={!canStage && !canManage ? DATASOURCES_I18N.manageReason : undefined} />} />
ariaLabel="Datasource view" size="equal" width="fill" options={[ { id: "catalogue", label: "Catalogue" }, { id: "staging", label: "Staging" }, { id: "origins", label: "Origins" } ]} value={view} onChange={(next) => { setView(next); setSearch(""); setError(""); }} />
setSearch(event.target.value)} placeholder={`Search ${view}`} aria-label={`Search ${view}`} /> {view === "catalogue" ? visibleDatasources.map((item) => ( setSelectedDatasourceRef(item.ref)} > )) : null} {view === "staging" ? visibleStages.map((item) => ( setSelectedStageRef(item.ref)} > )) : null} {view === "origins" ? visibleOrigins.map((item) => ( setSelectedOriginRef(item.ref)} > )) : null} {view === "catalogue" && !visibleDatasources.length ? ( ) : null} {view === "staging" && !visibleStages.length ? ( ) : null} {view === "origins" && !visibleOrigins.length ? ( ) : null} } > {view === "catalogue" ? : view === "staging" ? : } {view === "catalogue" ? selectedDatasource?.name ?? "Catalogue" : view === "staging" ? selectedStage?.name ?? "Staging" : selectedOrigin?.name ?? "Connector origins"} {view === "catalogue" ? selectedDatasource?.source_name ?? "Governed data" : view === "staging" ? selectedStage?.source_name ?? "Inspect before promotion" : selectedOrigin?.provider ?? "External acquisition"} } primaryActions={<> {view === "catalogue" && selectedDatasource?.mode === "cached" ? ( ) : null} {view === "catalogue" && canAdmin ? ( ) : null} {view === "catalogue" && selectedDatasource ? ( <> } onClick={() => setGovernanceOpen(true)} disabled={!canManage || working} disabledReason={working ? DATASOURCES_I18N.working : !canManage ? DATASOURCES_I18N.manageReason : undefined} /> ) : null} {view === "staging" && selectedStage?.state === "ready" ? ( ) : null} {view === "staging" && selectedStage?.state === "awaiting_approval" ? ( ) : null} {view === "origins" && selectedOrigin ? ( ) : null} } destructiveActions={view === "catalogue" && selectedDatasource ? ( } variant="danger" onClick={() => setRetireOpen(true)} disabled={!canManage || working} disabledReason={working ? DATASOURCES_I18N.working : !canManage ? DATASOURCES_I18N.manageReason : undefined} /> ) : undefined} />
{error ? ( {error} ) : null} {success ? ( {success} ) : null} {!canManage && !canStage ? : null}
{view === "catalogue" ? ( selectedDatasource ? ( ) : } label="No datasource selected" /> ) : null} {view === "staging" ? ( selectedStage ? : } label="No stage selected" /> ) : null} {view === "origins" ? ( selectedOrigin ? : } label={ originsAvailable ? "No origin selected" : "Connectors unavailable" } /> ) : null}
{working ?
Working...
: null}
setAddOpen(false)} onCreated={async (result) => { if (!isCurrentAuthority()) return; setAddOpen(false); if ("state" in result) { setView("staging"); setSelectedStageRef(result.ref); setSuccess(`Created stage ${result.name}. Review it before promotion.`); await reload(); } else { setView("catalogue"); setSuccess(`Registered ${result.name}.`); await reload(result.ref); } }} /> setGovernanceOpen(false)} onSaved={async (updated) => { if (!isCurrentAuthority()) return; setGovernanceOpen(false); setSuccess(`Updated governance for ${updated.name}.`); await reload(updated.ref); }} /> setDecisionOpen(false)} onDecided={async (updated) => { if (!isCurrentAuthority()) return; setDecisionOpen(false); await reload(); if (!isCurrentAuthority()) return; setSelectedStageRef(updated.ref); setSuccess(`Recorded ${updated.approval.state ?? "approval"} decision state for ${updated.name}.`); }} /> setRetentionOpen(false)} onApplied={async (count) => { if (!isCurrentAuthority()) return; setRetentionOpen(false); setSuccess(`Applied retention to ${count} eligible target${count === 1 ? "" : "s"}.`); await reload(selectedDatasourceRef); }} /> )} >

Create an immutable, addressable state for reproducible runs and evidence.

setFreezeLabel(event.target.value)} placeholder="Optional evidence label" />
setPromoteOpen(false)} onConfirm={() => { setPromoteOpen(false); void promoteStage(); }} /> setRetireOpen(false)} onConfirm={() => void retireSelected()} />
); } function DatasourceDetail({ datasource, preview, materializations, loading }: { datasource: Datasource; preview: DatasourcePreview | null; materializations: DatasourceMaterialization[]; loading: boolean; }) { return ( <> {datasource.description ? (
{datasource.description}
) : null} Governance
Authority{readableToken(datasource.governance.authority_mode)} Classification{datasource.governance.classification} Owner{datasource.governance.owner_ref || "Not assigned"} Steward{datasource.governance.steward_ref || "Not assigned"} Responsible organization{datasource.governance.responsible_organization_ref || "Not assigned"} Purposes{datasource.governance.purposes.join(", ") || "Not declared"} Approval gate{datasource.governance.approval_policy.required === true ? "Required" : "Not required"} Retention{datasource.governance.retention_policy.enabled === true ? "Configured" : "Disabled"}
{datasource.governance.semantic_definition ? (

{datasource.governance.semantic_definition}

) : null}
Preview {preview ? `${formatNumber(preview.total_rows)} total rows` : ""} {loading ? (
Loading preview...
) : preview ? ( ) : ( )}
Materializations Immutable revisions
{materializations.map((item) => ( ))} {loading ? ( ) : !materializations.length ? ( ) : null}
Revision Created Rows State Fingerprint
{item.revision} {formatDate(item.created_at)} {formatNumber(item.row_count)} {item.disposed_at ? ( ) : item.frozen_at ? ( ) : } {shortFingerprint(item.fingerprint)}
Loading materializations...
Live source without materializations
Schema Version {datasource.schema_version} ); } function StageDetail({ stage }: { stage: DatasourceStage }) { const errors = stage.validation.errors ?? []; const warnings = stage.validation.warnings ?? []; const schemaChanges = stage.validation.schema_change?.changes ?? []; const schemaClassification = stage.validation.schema_change?.classification ?? "new"; return ( <> Validation
Fingerprint{shortFingerprint(stage.fingerprint)} Target{stage.target_datasource_ref || "New datasource"} Promoted revision{stage.promoted_materialization_ref || "Not promoted"} Quality policy{stage.validation.policy_version || "Local default"} Policy hash{shortFingerprint(stage.validation.policy_hash || "")} Schema change{readableToken(schemaClassification)}
{errors.length || warnings.length ? (
{errors.length ? ( Promotion blockers ) : null} {warnings.length ? ( Review warnings ) : null}
) : (
All configured quality rules passed and no blocking schema change was detected.
)}
Promotion approval
Progress{stage.approval.approval_count ?? 0} / {stage.approval.required_approvals ?? 1} Policy hash{shortFingerprint(stage.approval.policy_hash ?? "")} Subject digest{shortFingerprint(stage.approval.subject_digest ?? "")} Expires{formatDate(stage.approval.expires_at)}
{stage.approval.approvals?.length ? (
    {stage.approval.approvals.map((item, index) => (
  • {item.actor_ref ?? "Unknown actor"} · {readableToken(item.decision ?? "decision")} {item.reason ?? "No reason recorded"} · {formatDate(item.decided_at)}
  • ))}
) :
No approval decision has been recorded.
}
{schemaChanges.length ? ( Schema comparison
    {schemaChanges.map((change, index) => ( ))}
) : null} Detected schema {stage.shape} ); } function StageDecisionDialog({ open, settings, stage, onClose, onDecided }: { open: boolean; settings: ApiSettings; stage: DatasourceStage | null; onClose: () => void; onDecided: (stage: DatasourceStage) => void | Promise; }) { const [decision, setDecision] = useState<"approve" | "reject">("approve"); const [reason, setReason] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); useEffect(() => { if (!open) return; setDecision("approve"); setReason(""); setError(""); }, [open, stage?.ref]); const submit = async () => { if (!stage?.approval.policy_hash || !stage.approval.subject_digest) return; setBusy(true); setError(""); try { const updated = await decideDatasourceStage(settings, stage.ref, { decision, reason, expected_policy_hash: stage.approval.policy_hash, expected_subject_digest: stage.approval.subject_digest }); await onDecided(updated); } catch (operationError) { setError(apiErrorMessage(operationError)); } finally { setBusy(false); } }; return ( !busy && onClose()} footer={<> } > {error ? {error} : null}

The decision is bound to this exact stage, validation result, policy hash, and your account authority.

Stage{stage?.name ?? "Unavailable"} Approval progress{stage?.approval.approval_count ?? 0} / {stage?.approval.required_approvals ?? 1} Policy hash{shortFingerprint(stage?.approval.policy_hash ?? "")} Expires{formatDate(stage?.approval.expires_at)}
ariaLabel="Approval decision" width="fill" size="equal" options={[ { id: "approve", label: "Approve" }, { id: "reject", label: "Reject" } ]} value={decision} onChange={setDecision} />