Migrate Datasources interface patterns

This commit is contained in:
2026-08-03 13:46:58 +02:00
parent 98d93e707f
commit 6406ce70f1
7 changed files with 516 additions and 37 deletions
@@ -20,9 +20,11 @@ import {
Upload
} from "lucide-react";
import {
ActionBlockerHint,
Button,
ConfirmDialog,
Dialog,
DocumentationHelpLink,
DismissibleAlert,
FormField,
IconButton,
@@ -31,6 +33,8 @@ import {
StatusBadge,
hasScope,
isApiError,
useUnsavedChanges,
useUnsavedDraftGuard,
type ApiSettings,
type AuthInfo
} from "@govoplan/core-webui";
@@ -54,6 +58,12 @@ import {
type DatasourcePreview,
type DatasourceStage
} from "../../api/datasources";
import {
DATASOURCE_FIELDS_DOCUMENTATION,
DATASOURCE_GOVERNANCE_DOCUMENTATION,
DATASOURCES_DOCUMENTATION,
DATASOURCES_I18N
} from "./interfacePatterns";
type CatalogueView = "catalogue" | "staging" | "origins";
type AddKind = "upload" | "origin";
@@ -86,7 +96,9 @@ export default function DatasourcesPage({
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 { requestDiscard } = useUnsavedChanges();
const canManage = hasScope(auth, "datasources:source:write")
|| hasScope(auth, "datasources:source:admin");
@@ -211,8 +223,8 @@ export default function DatasourcesPage({
}
};
const freezeSelected = async () => {
if (!selectedDatasource) return;
const freezeSelected = async (): Promise<boolean> => {
if (!selectedDatasource) return false;
setWorking(true);
setError("");
try {
@@ -225,8 +237,10 @@ export default function DatasourcesPage({
setFreezeOpen(false);
setFreezeLabel("");
await reload(selectedDatasource.ref);
return true;
} catch (operationError) {
setError(apiErrorMessage(operationError));
return false;
} finally {
setWorking(false);
}
@@ -248,6 +262,23 @@ export default function DatasourcesPage({
}
};
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 (
<main className="datasources-page">
<div className="datasources-shell">
@@ -261,6 +292,7 @@ export default function DatasourcesPage({
variant="ghost"
onClick={() => void reload(selectedDatasourceRef)}
disabled={loading || working}
disabledReason={loading ? DATASOURCES_I18N.loading : working ? DATASOURCES_I18N.working : undefined}
/>
<IconButton
label="Add datasource or stage"
@@ -268,6 +300,7 @@ export default function DatasourcesPage({
variant="primary"
onClick={() => setAddOpen(true)}
disabled={!canStage && !canManage}
disabledReason={!canStage && !canManage ? DATASOURCES_I18N.manageReason : undefined}
/>
</span>
</div>
@@ -384,8 +417,9 @@ export default function DatasourcesPage({
</span>
</span>
<span className="datasources-toolbar-actions">
<DocumentationHelpLink reference={DATASOURCES_DOCUMENTATION} />
{view === "catalogue" && selectedDatasource?.mode === "cached" ? (
<Button onClick={() => void refreshSelected()} disabled={!canManage || working}>
<Button onClick={() => void refreshSelected()} disabled={!canManage || working} disabledReason={working ? DATASOURCES_I18N.working : !canManage ? DATASOURCES_I18N.manageReason : undefined}>
<Download size={16} /> Refresh
</Button>
) : null}
@@ -396,8 +430,9 @@ export default function DatasourcesPage({
icon={<Pencil size={16} />}
onClick={() => setGovernanceOpen(true)}
disabled={!canManage || working}
disabledReason={working ? DATASOURCES_I18N.working : !canManage ? DATASOURCES_I18N.manageReason : undefined}
/>
<Button onClick={() => setFreezeOpen(true)} disabled={!canManage || working}>
<Button onClick={() => setFreezeOpen(true)} disabled={!canManage || working} disabledReason={working ? DATASOURCES_I18N.working : !canManage ? DATASOURCES_I18N.manageReason : undefined}>
<Snowflake size={16} /> Freeze
</Button>
<IconButton
@@ -406,14 +441,16 @@ export default function DatasourcesPage({
variant="danger"
onClick={() => setRetireOpen(true)}
disabled={!canManage || working}
disabledReason={working ? DATASOURCES_I18N.working : !canManage ? DATASOURCES_I18N.manageReason : undefined}
/>
</>
) : null}
{view === "staging" && selectedStage?.state === "ready" ? (
<Button
variant="primary"
onClick={() => void promoteStage()}
onClick={() => setPromoteOpen(true)}
disabled={!canStage || working}
disabledReason={working ? DATASOURCES_I18N.working : !canStage ? DATASOURCES_I18N.stageReason : undefined}
>
<Upload size={16} /> Promote
</Button>
@@ -423,6 +460,7 @@ export default function DatasourcesPage({
variant="primary"
onClick={() => setAddOpen(true)}
disabled={!canManage || working}
disabledReason={working ? DATASOURCES_I18N.working : !canManage ? DATASOURCES_I18N.manageReason : undefined}
>
<Plus size={16} /> Register
</Button>
@@ -441,6 +479,18 @@ export default function DatasourcesPage({
{success}
</DismissibleAlert>
) : null}
{!canManage && !canStage ? <ActionBlockerHint
tone="info"
reason={{
summary: "Datasources are read-only",
details: DATASOURCES_I18N.manageReason,
requiredAction: DATASOURCES_I18N.permissionAction,
actor: DATASOURCES_I18N.permissionActor,
target: DATASOURCES_I18N.permissionDestination
}}
labels={{ requiredAction: DATASOURCES_I18N.requiredAction, actor: DATASOURCES_I18N.actor, target: DATASOURCES_I18N.destination }}
documentation={DATASOURCES_DOCUMENTATION}
/> : null}
</div>
<div className="datasources-content">
@@ -510,12 +560,10 @@ export default function DatasourcesPage({
<Dialog
open={freezeOpen}
title="Freeze datasource state"
onClose={() => {
if (!working) setFreezeOpen(false);
}}
onClose={closeFreeze}
footer={(
<>
<Button onClick={() => setFreezeOpen(false)} disabled={working}>Cancel</Button>
<Button onClick={closeFreeze} disabled={working} disabledReason={working ? DATASOURCES_I18N.working : undefined}>Cancel</Button>
<Button variant="primary" onClick={() => void freezeSelected()} disabled={working}>
<Snowflake size={16} /> Freeze
</Button>
@@ -525,7 +573,7 @@ export default function DatasourcesPage({
<p className="datasources-dialog-copy">
Create an immutable, addressable state for reproducible runs and evidence.
</p>
<FormField label="Label">
<FormField label="Label" documentation={DATASOURCE_FIELDS_DOCUMENTATION}>
<input
value={freezeLabel}
onChange={(event) => setFreezeLabel(event.target.value)}
@@ -533,6 +581,18 @@ export default function DatasourcesPage({
/>
</FormField>
</Dialog>
<ConfirmDialog
open={promoteOpen}
title="i18n:govoplan-datasources.promote_title"
message="i18n:govoplan-datasources.promote_message"
confirmLabel="Promote"
busy={working}
onCancel={() => setPromoteOpen(false)}
onConfirm={() => {
setPromoteOpen(false);
void promoteStage();
}}
/>
<ConfirmDialog
open={retireOpen}
title="Retire datasource"
@@ -742,19 +802,27 @@ function GovernanceDialog({
const [draft, setDraft] = useState<DatasourceGovernance | null>(null);
const [freshness, setFreshness] = useState("{}");
const [quality, setQuality] = useState("{}");
const [baselineKey, setBaselineKey] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const { requestDiscard } = useUnsavedChanges();
useEffect(() => {
if (!open || !datasource) return;
setDraft(structuredClone(datasource.governance));
setFreshness(JSON.stringify(datasource.governance.freshness_policy, null, 2));
setQuality(JSON.stringify(datasource.governance.quality_policy, null, 2));
const nextDraft = structuredClone(datasource.governance);
const nextFreshness = JSON.stringify(datasource.governance.freshness_policy, null, 2);
const nextQuality = JSON.stringify(datasource.governance.quality_policy, null, 2);
setDraft(nextDraft);
setFreshness(nextFreshness);
setQuality(nextQuality);
setBaselineKey(JSON.stringify({ draft: nextDraft, freshness: nextFreshness, quality: nextQuality }));
setError("");
}, [datasource, open]);
const save = async () => {
if (!datasource || !draft) return;
const dirty = Boolean(open && draft && JSON.stringify({ draft, freshness, quality }) !== baselineKey);
const save = async (): Promise<boolean> => {
if (!datasource || !draft) return false;
setBusy(true);
setError("");
try {
@@ -764,13 +832,29 @@ function GovernanceDialog({
quality_policy: parseObject(quality, "Quality policy")
});
await onSaved(updated);
return true;
} catch (saveError) {
setError(apiErrorMessage(saveError));
return false;
} finally {
setBusy(false);
}
};
useUnsavedDraftGuard({
dirty,
onSave: save,
onDiscard: onClose,
title: "i18n:govoplan-datasources.unsaved_governance_title",
message: "i18n:govoplan-datasources.unsaved_governance_message"
});
const close = () => {
if (busy) return;
if (dirty) requestDiscard(onClose);
else onClose();
};
const setValue = <K extends keyof DatasourceGovernance>(
key: K,
value: DatasourceGovernance[K]
@@ -781,11 +865,11 @@ function GovernanceDialog({
open={open}
title="Datasource governance"
className="datasources-governance-dialog"
onClose={() => { if (!busy) onClose(); }}
onClose={close}
footer={(
<>
<Button onClick={onClose} disabled={busy}>Cancel</Button>
<Button variant="primary" onClick={() => void save()} disabled={!draft || busy}>
<Button onClick={close} disabled={busy} disabledReason={busy ? DATASOURCES_I18N.working : undefined}>Cancel</Button>
<Button variant="primary" onClick={() => void save()} disabled={!draft || busy} disabledReason={busy ? DATASOURCES_I18N.working : !draft ? DATASOURCES_I18N.incomplete : undefined}>
Save governance
</Button>
</>
@@ -795,7 +879,7 @@ function GovernanceDialog({
{draft ? (
<div className="datasources-dialog-fields">
<div className="datasources-dialog-grid">
<FormField label="Authority mode">
<FormField label="Authority mode" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<select
value={draft.authority_mode}
onChange={(event) => setValue("authority_mode", event.target.value as DatasourceGovernance["authority_mode"])}
@@ -810,13 +894,13 @@ function GovernanceDialog({
].map((value) => <option key={value} value={value}>{readableToken(value)}</option>)}
</select>
</FormField>
<FormField label="Authoritative source">
<FormField label="Authoritative source" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<input value={draft.authoritative_source_ref ?? ""} onChange={(event) => setValue("authoritative_source_ref", event.target.value || null)} />
</FormField>
<FormField label="Classification">
<FormField label="Classification" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<input value={draft.classification} onChange={(event) => setValue("classification", event.target.value)} />
</FormField>
<FormField label="Publication state">
<FormField label="Publication state" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<input value={draft.publication_state} onChange={(event) => setValue("publication_state", event.target.value)} />
</FormField>
<FormField label="Owner reference">
@@ -847,7 +931,7 @@ function GovernanceDialog({
<input value={draft.correction_procedure_ref ?? ""} onChange={(event) => setValue("correction_procedure_ref", event.target.value || null)} />
</FormField>
</div>
<FormField label="Semantic definition">
<FormField label="Semantic definition" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<textarea value={draft.semantic_definition ?? ""} onChange={(event) => setValue("semantic_definition", event.target.value || null)} />
</FormField>
<div className="datasources-dialog-grid">
@@ -860,10 +944,10 @@ function GovernanceDialog({
<GovernanceListField label="Known limits" values={draft.known_limits} onChange={(values) => setValue("known_limits", values)} />
</div>
<div className="datasources-dialog-grid">
<FormField label="Freshness policy (JSON)">
<FormField label="Freshness policy (JSON)" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<textarea value={freshness} onChange={(event) => setFreshness(event.target.value)} spellCheck={false} />
</FormField>
<FormField label="Quality policy (JSON)">
<FormField label="Quality policy (JSON)" documentation={DATASOURCE_GOVERNANCE_DOCUMENTATION}>
<textarea value={quality} onChange={(event) => setQuality(event.target.value)} spellCheck={false} />
</FormField>
</div>
@@ -931,22 +1015,54 @@ function AddDatasourceDialog({
const [rowsText, setRowsText] = useState('[\n { "id": 1 }\n]');
const [csvText, setCsvText] = useState("");
const [delimiter, setDelimiter] = useState(";");
const [baselineKey, setBaselineKey] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const { requestDiscard } = useUnsavedChanges();
useEffect(() => {
if (!open) return;
setKind(initialKind);
setFormat("csv");
setOriginRef(initialOrigin?.ref ?? "");
setName(initialOrigin?.name ?? "");
setSourceName(initialOrigin?.source_name ?? "");
setDescription(initialOrigin?.description ?? "");
setMode(initialKind === "origin" ? "live" : "static");
setTargetRef("");
setRowsText('[\n { "id": 1 }\n]');
setCsvText("");
setDelimiter(";");
setBaselineKey(addDatasourceDraftKey({
kind: initialKind,
format: "csv",
mode: initialKind === "origin" ? "live" : "static",
originRef: initialOrigin?.ref ?? "",
targetRef: "",
name: initialOrigin?.name ?? "",
sourceName: initialOrigin?.source_name ?? "",
description: initialOrigin?.description ?? "",
rowsText: '[\n { "id": 1 }\n]',
csvText: "",
delimiter: ";"
}));
setError("");
}, [initialKind, initialOrigin, open]);
const selectedOrigin = origins.find((item) => item.ref === originRef) ?? null;
const dirty = Boolean(open && addDatasourceDraftKey({
kind,
format,
mode,
originRef,
targetRef,
name,
sourceName,
description,
rowsText,
csvText,
delimiter
}) !== baselineKey);
const chooseOrigin = (ref: string) => {
const origin = origins.find((item) => item.ref === ref);
@@ -968,7 +1084,7 @@ function AddDatasourceDialog({
setMode(target.mode === "cached" ? "cached" : "static");
};
const create = async () => {
const create = async (): Promise<boolean> => {
setBusy(true);
setError("");
try {
@@ -1006,13 +1122,29 @@ function AddDatasourceDialog({
});
await onCreated(stage);
}
return true;
} catch (createError) {
setError(apiErrorMessage(createError));
return false;
} finally {
setBusy(false);
}
};
useUnsavedDraftGuard({
dirty,
onSave: create,
onDiscard: onClose,
title: "i18n:govoplan-datasources.unsaved_add_title",
message: "i18n:govoplan-datasources.unsaved_add_message"
});
const close = () => {
if (busy) return;
if (dirty) requestDiscard(onClose);
else onClose();
};
const loadFile = async (file: File | undefined) => {
if (!file) return;
try {
@@ -1034,12 +1166,10 @@ function AddDatasourceDialog({
open={open}
title="Add datasource"
className="datasources-add-dialog"
onClose={() => {
if (!busy) onClose();
}}
onClose={close}
footer={(
<>
<Button onClick={onClose} disabled={busy}>Cancel</Button>
<Button onClick={close} disabled={busy} disabledReason={busy ? DATASOURCES_I18N.working : undefined}>Cancel</Button>
<Button
variant="primary"
onClick={() => void create()}
@@ -1050,6 +1180,7 @@ function AddDatasourceDialog({
|| (kind === "upload" && !canStage)
|| (kind === "origin" && (!canManage || !originRef))
}
disabledReason={busy ? DATASOURCES_I18N.working : !name.trim() || !sourceName.trim() ? DATASOURCES_I18N.incomplete : kind === "upload" && !canStage ? DATASOURCES_I18N.stageReason : kind === "origin" && !canManage ? DATASOURCES_I18N.manageReason : kind === "origin" && !originRef ? DATASOURCES_I18N.incomplete : undefined}
>
{kind === "upload" ? <Upload size={16} /> : <Database size={16} />}
{kind === "upload" ? "Create stage" : "Register"}
@@ -1081,7 +1212,7 @@ function AddDatasourceDialog({
}}
/>
{kind === "origin" ? (
<FormField label="Connector origin">
<FormField label="Connector origin" documentation={DATASOURCE_FIELDS_DOCUMENTATION}>
<select value={originRef} onChange={(event) => chooseOrigin(event.target.value)}>
<option value="">Choose an origin</option>
{origins.map((origin) => (
@@ -1093,7 +1224,7 @@ function AddDatasourceDialog({
</FormField>
) : (
<>
<FormField label="Update existing datasource">
<FormField label="Update existing datasource" documentation={DATASOURCE_FIELDS_DOCUMENTATION}>
<select value={targetRef} onChange={(event) => chooseTarget(event.target.value)}>
<option value="">Create a new datasource</option>
{datasources.filter((item) => item.mode !== "live" && item.shape === "tabular").map((item) => (
@@ -1111,10 +1242,10 @@ function AddDatasourceDialog({
</>
)}
<div className="datasources-dialog-grid">
<FormField label="Name">
<FormField label="Name" documentation={DATASOURCE_FIELDS_DOCUMENTATION}>
<input value={name} onChange={(event) => setName(event.target.value)} />
</FormField>
<FormField label="Datasource key">
<FormField label="Datasource key" documentation={DATASOURCE_FIELDS_DOCUMENTATION}>
<input
value={sourceName}
onChange={(event) => setSourceName(event.target.value)}
@@ -1127,7 +1258,7 @@ function AddDatasourceDialog({
<FormField label="Description">
<input value={description} onChange={(event) => setDescription(event.target.value)} />
</FormField>
<FormField label="Mode">
<FormField label="Mode" documentation={DATASOURCE_FIELDS_DOCUMENTATION}>
<SegmentedControl
ariaLabel="Datasource mode"
width="fill"
@@ -1285,6 +1416,22 @@ function parseRows(text: string): Record<string, unknown>[] {
return value;
}
function addDatasourceDraftKey(value: {
kind: AddKind;
format: UploadFormat;
mode: "live" | "cached" | "static";
originRef: string;
targetRef: string;
name: string;
sourceName: string;
description: string;
rowsText: string;
csvText: string;
delimiter: string;
}): string {
return JSON.stringify(value);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
@@ -0,0 +1,31 @@
import type { DocumentationHelpReference } from "@govoplan/core-webui";
export const DATASOURCES_DOCUMENTATION = {
topicId: "datasources.lifecycle",
documentationType: "user"
} satisfies DocumentationHelpReference;
export const DATASOURCE_FIELDS_DOCUMENTATION = {
topicId: "datasources.reference.fields-and-consequences",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const DATASOURCE_GOVERNANCE_DOCUMENTATION = {
topicId: "datasources.governance",
documentationType: "admin"
} satisfies DocumentationHelpReference;
export const DATASOURCES_I18N = {
loading: "i18n:govoplan-datasources.loading_reason",
working: "i18n:govoplan-datasources.working_reason",
manageReason: "i18n:govoplan-datasources.manage_permission_reason",
stageReason: "i18n:govoplan-datasources.stage_permission_reason",
noSelection: "i18n:govoplan-datasources.no_selection_reason",
incomplete: "i18n:govoplan-datasources.incomplete_reason",
requiredAction: "i18n:govoplan-datasources.required_action",
actor: "i18n:govoplan-datasources.actor",
destination: "i18n:govoplan-datasources.destination",
permissionAction: "i18n:govoplan-datasources.permission_action",
permissionActor: "i18n:govoplan-datasources.permission_actor",
permissionDestination: "i18n:govoplan-datasources.permission_destination"
} as const;
+133
View File
@@ -0,0 +1,133 @@
import type { PlatformTranslations } from "@govoplan/core-webui";
const en = {
"i18n:govoplan-datasources.datasources": "Datasources",
"i18n:govoplan-datasources.catalogue": "Datasource catalogue",
"i18n:govoplan-datasources.staging": "Datasource staging",
"i18n:govoplan-datasources.origins": "Datasource origins",
"i18n:govoplan-datasources.governance": "Datasource governance",
"i18n:govoplan-datasources.preview": "Datasource preview and materializations",
"i18n:govoplan-datasources.loading_reason": "The Datasource catalogue is still loading.",
"i18n:govoplan-datasources.working_reason": "Another Datasource operation is still running.",
"i18n:govoplan-datasources.manage_permission_reason": "Your account may not register, refresh, freeze, govern, or retire Datasources.",
"i18n:govoplan-datasources.stage_permission_reason": "Your account may not create or promote Datasource stages.",
"i18n:govoplan-datasources.no_selection_reason": "Select the relevant Datasource, stage, or origin first.",
"i18n:govoplan-datasources.incomplete_reason": "Complete the required name, key, source, mode, and content fields first.",
"i18n:govoplan-datasources.required_action": "Required action",
"i18n:govoplan-datasources.actor": "Responsible actor",
"i18n:govoplan-datasources.destination": "Where to continue",
"i18n:govoplan-datasources.permission_action": "Ask for Datasource management or staging permission for the intended operation.",
"i18n:govoplan-datasources.permission_actor": "A tenant administrator or Datasource manager",
"i18n:govoplan-datasources.permission_destination": "Access administration for Datasources",
"i18n:govoplan-datasources.unsaved_add_title": "Uncreated Datasource or stage",
"i18n:govoplan-datasources.unsaved_add_message": "Create this Datasource or stage, or discard its draft before leaving.",
"i18n:govoplan-datasources.unsaved_governance_title": "Unsaved Datasource governance",
"i18n:govoplan-datasources.unsaved_governance_message": "Save or discard the governance draft before leaving.",
"i18n:govoplan-datasources.unsaved_freeze_title": "Unfrozen Datasource state",
"i18n:govoplan-datasources.unsaved_freeze_message": "Freeze this labelled state or discard the label before leaving.",
"i18n:govoplan-datasources.promote_title": "Promote Datasource stage",
"i18n:govoplan-datasources.promote_message": "Promote this reviewed stage? It will create or update the governed Datasource and append an immutable materialization revision.",
"Data": "Data",
"Catalogue": "Catalogue",
"Staging": "Staging",
"Origins": "Origins",
"No datasources": "No datasources",
"No staged data": "No staged data",
"No connector origins": "No connector origins",
"Connectors unavailable": "Connectors unavailable",
"Governed data": "Governed data",
"Inspect before promotion": "Inspect before promotion",
"External acquisition": "External acquisition",
"Edit datasource governance": "Edit Datasource governance",
"Freeze": "Freeze",
"Retire datasource": "Retire Datasource",
"Promote": "Promote",
"Register": "Register",
"Working...": "Working...",
"Freeze datasource state": "Freeze Datasource state",
"Datasource governance": "Datasource governance",
"Add datasource": "Add Datasource",
"Connector origin": "Connector origin",
"Update existing datasource": "Update existing Datasource",
"Datasource key": "Datasource key",
"Mode": "Mode",
"Static": "Static",
"Cached": "Cached",
"Live": "Live",
"Create stage": "Create stage",
"Save governance": "Save governance",
"Authority mode": "Authority mode",
"Authoritative source": "Authoritative source",
"Classification": "Classification",
"Publication state": "Publication state",
"Semantic definition": "Semantic definition",
"Freshness policy (JSON)": "Freshness policy (JSON)",
"Quality policy (JSON)": "Quality policy (JSON)"
} as const;
const de: Record<keyof typeof en, string> = {
"i18n:govoplan-datasources.datasources": "Datenquellen",
"i18n:govoplan-datasources.catalogue": "Datenquellenkatalog",
"i18n:govoplan-datasources.staging": "Datenquellen-Staging",
"i18n:govoplan-datasources.origins": "Datenquellenursprünge",
"i18n:govoplan-datasources.governance": "Datenquellen-Governance",
"i18n:govoplan-datasources.preview": "Datenquellenvorschau und Materialisierungen",
"i18n:govoplan-datasources.loading_reason": "Der Datenquellenkatalog wird noch geladen.",
"i18n:govoplan-datasources.working_reason": "Eine andere Datenquellenoperation läuft noch.",
"i18n:govoplan-datasources.manage_permission_reason": "Ihr Konto darf Datenquellen nicht registrieren, aktualisieren, einfrieren, steuern oder stilllegen.",
"i18n:govoplan-datasources.stage_permission_reason": "Ihr Konto darf keine Datenquellen-Stages erstellen oder übernehmen.",
"i18n:govoplan-datasources.no_selection_reason": "Wählen Sie zuerst die betreffende Datenquelle, Stage oder den Ursprung.",
"i18n:govoplan-datasources.incomplete_reason": "Füllen Sie zuerst Name, Schlüssel, Quelle, Modus und Inhaltsfelder aus.",
"i18n:govoplan-datasources.required_action": "Erforderliche Aktion",
"i18n:govoplan-datasources.actor": "Verantwortliche Stelle",
"i18n:govoplan-datasources.destination": "Fortsetzung",
"i18n:govoplan-datasources.permission_action": "Fordern Sie die für die Operation erforderliche Datenquellen- oder Staging-Berechtigung an.",
"i18n:govoplan-datasources.permission_actor": "Mandantenadministration oder Datenquellenverwaltung",
"i18n:govoplan-datasources.permission_destination": "Zugriffsverwaltung für Datenquellen",
"i18n:govoplan-datasources.unsaved_add_title": "Nicht erstellte Datenquelle oder Stage",
"i18n:govoplan-datasources.unsaved_add_message": "Erstellen Sie diese Datenquelle oder Stage oder verwerfen Sie den Entwurf, bevor Sie fortfahren.",
"i18n:govoplan-datasources.unsaved_governance_title": "Ungespeicherte Datenquellen-Governance",
"i18n:govoplan-datasources.unsaved_governance_message": "Speichern oder verwerfen Sie den Governance-Entwurf, bevor Sie fortfahren.",
"i18n:govoplan-datasources.unsaved_freeze_title": "Nicht eingefrorener Datenquellenstand",
"i18n:govoplan-datasources.unsaved_freeze_message": "Frieren Sie diesen bezeichneten Stand ein oder verwerfen Sie die Bezeichnung.",
"i18n:govoplan-datasources.promote_title": "Datenquellen-Stage übernehmen",
"i18n:govoplan-datasources.promote_message": "Diese geprüfte Stage übernehmen? Die verwaltete Datenquelle wird erstellt oder aktualisiert und eine unveränderliche Materialisierungsrevision angefügt.",
"Data": "Daten",
"Catalogue": "Katalog",
"Staging": "Staging",
"Origins": "Ursprünge",
"No datasources": "Keine Datenquellen",
"No staged data": "Keine bereitgestellten Daten",
"No connector origins": "Keine Konnektorursprünge",
"Connectors unavailable": "Konnektoren nicht verfügbar",
"Governed data": "Verwaltete Daten",
"Inspect before promotion": "Vor Übernahme prüfen",
"External acquisition": "Externe Übernahme",
"Edit datasource governance": "Datenquellen-Governance bearbeiten",
"Freeze": "Einfrieren",
"Retire datasource": "Datenquelle stilllegen",
"Promote": "Übernehmen",
"Register": "Registrieren",
"Working...": "Vorgang läuft...",
"Freeze datasource state": "Datenquellenstand einfrieren",
"Datasource governance": "Datenquellen-Governance",
"Add datasource": "Datenquelle hinzufügen",
"Connector origin": "Konnektorursprung",
"Update existing datasource": "Bestehende Datenquelle aktualisieren",
"Datasource key": "Datenquellenschlüssel",
"Mode": "Modus",
"Static": "Statisch",
"Cached": "Zwischengespeichert",
"Live": "Live",
"Create stage": "Stage erstellen",
"Save governance": "Governance speichern",
"Authority mode": "Autoritätsmodus",
"Authoritative source": "Maßgebliche Quelle",
"Classification": "Klassifizierung",
"Publication state": "Veröffentlichungsstatus",
"Semantic definition": "Semantische Definition",
"Freshness policy (JSON)": "Aktualitätsrichtlinie (JSON)",
"Quality policy (JSON)": "Qualitätsrichtlinie (JSON)"
};
export const generatedTranslations: PlatformTranslations = { en, de };
+13 -2
View File
@@ -1,5 +1,6 @@
import { createElement, lazy } from "react";
import type { PlatformWebModule } from "@govoplan/core-webui";
import { generatedTranslations } from "./i18n/generatedTranslations";
import "./styles/datasources.css";
const DatasourcesPage = lazy(() => import("./features/datasources/DatasourcesPage"));
@@ -8,7 +9,7 @@ const readScopes = ["datasources:catalogue:read", "datasources:source:admin"];
export const datasourcesModule: PlatformWebModule = {
id: "datasources",
label: "Datasources",
label: "i18n:govoplan-datasources.datasources",
version: "0.1.14",
optionalDependencies: [
"access",
@@ -18,10 +19,19 @@ export const datasourcesModule: PlatformWebModule = {
"notifications",
"policy"
],
translations: generatedTranslations,
viewSurfaces: [
{ id: "datasources.page", moduleId: "datasources", kind: "route", label: "i18n:govoplan-datasources.datasources", order: 70 },
{ id: "datasources.catalogue", moduleId: "datasources", kind: "section", label: "i18n:govoplan-datasources.catalogue", parentId: "datasources.page", order: 10 },
{ id: "datasources.staging", moduleId: "datasources", kind: "section", label: "i18n:govoplan-datasources.staging", parentId: "datasources.page", order: 20 },
{ id: "datasources.origins", moduleId: "datasources", kind: "section", label: "i18n:govoplan-datasources.origins", parentId: "datasources.page", order: 30 },
{ id: "datasources.governance", moduleId: "datasources", kind: "action", label: "i18n:govoplan-datasources.governance", parentId: "datasources.catalogue", order: 40 },
{ id: "datasources.preview", moduleId: "datasources", kind: "section", label: "i18n:govoplan-datasources.preview", parentId: "datasources.catalogue", order: 50 }
],
navItems: [
{
to: "/datasources",
label: "Datasources",
label: "i18n:govoplan-datasources.datasources",
iconName: "database-zap",
anyOf: readScopes,
order: 70
@@ -32,6 +42,7 @@ export const datasourcesModule: PlatformWebModule = {
path: "/datasources",
anyOf: readScopes,
order: 70,
surfaceId: "datasources.page",
render: ({ settings, auth }) => createElement(DatasourcesPage, { settings, auth })
}
]