Add datasource quality and schema gates

This commit is contained in:
2026-08-04 11:23:14 +02:00
parent a6d5c9839d
commit 075d2fc087
16 changed files with 1305 additions and 10 deletions
+39 -1
View File
@@ -87,6 +87,44 @@ export type DatasourceMaterialization = {
governance: DatasourceGovernance;
};
export type DatasourceValidationDiagnostic = {
severity: "error" | "warning";
code: string;
message: string;
rule_id?: string;
affected_rows?: number;
row_numbers?: number[];
row_numbers_truncated?: boolean;
details?: Record<string, unknown>;
};
export type DatasourceSchemaChange = {
code: string;
classification: "compatible" | "warning" | "breaking";
message: string;
field?: string;
before?: DatasourceField;
after?: DatasourceField;
};
export type DatasourceStageValidation = {
version?: number;
policy_version?: string;
policy_hash?: string;
valid?: boolean;
errors?: DatasourceValidationDiagnostic[];
warnings?: DatasourceValidationDiagnostic[];
quality?: {
rules_evaluated?: number;
rules_passed?: number;
rules_failed?: number;
};
schema_change?: {
classification?: "new" | "compatible" | "warning" | "breaking";
changes?: DatasourceSchemaChange[];
};
};
export type DatasourceStage = {
ref: string;
name: string;
@@ -100,7 +138,7 @@ export type DatasourceStage = {
schema: DatasourceField[];
row_count?: number | null;
byte_count?: number | null;
validation: Record<string, unknown>;
validation: DatasourceStageValidation;
created_at?: string | null;
promoted_at?: string | null;
promoted_materialization_ref?: string | null;
@@ -56,7 +56,9 @@ import {
type DatasourceMaterialization,
type DatasourceOrigin,
type DatasourcePreview,
type DatasourceStage
type DatasourceSchemaChange,
type DatasourceStage,
type DatasourceValidationDiagnostic
} from "../../api/datasources";
import {
DATASOURCE_FIELDS_DOCUMENTATION,
@@ -717,6 +719,10 @@ function DatasourceDetail({
}
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 (
<>
<div className="datasources-metrics">
@@ -738,8 +744,44 @@ function StageDetail({ stage }: { stage: DatasourceStage }) {
<span><small>Fingerprint</small><strong>{shortFingerprint(stage.fingerprint)}</strong></span>
<span><small>Target</small><strong>{stage.target_datasource_ref || "New datasource"}</strong></span>
<span><small>Promoted revision</small><strong>{stage.promoted_materialization_ref || "Not promoted"}</strong></span>
<span><small>Quality policy</small><strong>{stage.validation.policy_version || "Local default"}</strong></span>
<span><small>Policy hash</small><strong>{shortFingerprint(stage.validation.policy_hash || "")}</strong></span>
<span><small>Schema change</small><strong>{readableToken(schemaClassification)}</strong></span>
</div>
{errors.length || warnings.length ? (
<div className="datasources-validation-alerts">
{errors.length ? (
<DismissibleAlert tone="danger" compact dismissible={false}>
<strong>Promotion blockers</strong>
<ValidationDiagnosticList diagnostics={errors} />
</DismissibleAlert>
) : null}
{warnings.length ? (
<DismissibleAlert tone="warning" compact dismissible={false}>
<strong>Review warnings</strong>
<ValidationDiagnosticList diagnostics={warnings} />
</DismissibleAlert>
) : null}
</div>
) : (
<div className="datasources-validation-ok">
All configured quality rules passed and no blocking schema change was detected.
</div>
)}
</section>
{schemaChanges.length ? (
<section className="datasources-detail-section">
<div className="datasources-section-heading">
<span>Schema comparison</span>
<StatusBadge status={schemaClassification} label={readableToken(schemaClassification)} />
</div>
<ul className="datasources-schema-changes">
{schemaChanges.map((change, index) => (
<SchemaChangeItem key={`${change.code}-${change.field ?? index}`} change={change} />
))}
</ul>
</section>
) : null}
<section className="datasources-detail-section">
<div className="datasources-section-heading">
<span>Detected schema</span>
@@ -751,6 +793,42 @@ function StageDetail({ stage }: { stage: DatasourceStage }) {
);
}
function ValidationDiagnosticList({
diagnostics
}: {
diagnostics: DatasourceValidationDiagnostic[];
}) {
return (
<ul className="datasources-diagnostic-list">
{diagnostics.map((diagnostic, index) => {
const details = [
diagnostic.rule_id ? `Rule ${diagnostic.rule_id}` : "",
diagnostic.affected_rows !== undefined ? `${diagnostic.affected_rows} affected row${diagnostic.affected_rows === 1 ? "" : "s"}` : "",
diagnostic.row_numbers?.length ? `Rows ${diagnostic.row_numbers.join(", ")}${diagnostic.row_numbers_truncated ? ", …" : ""}` : ""
].filter(Boolean);
return (
<li key={`${diagnostic.code}-${diagnostic.rule_id ?? index}`}>
<span>{diagnostic.message}</span>
{details.length ? <small>{details.join(" · ")}</small> : null}
</li>
);
})}
</ul>
);
}
function SchemaChangeItem({ change }: { change: DatasourceSchemaChange }) {
return (
<li>
<StatusBadge status={change.classification} label={readableToken(change.classification)} />
<span>
<strong>{change.message}</strong>
{change.field ? <small>{change.field}</small> : null}
</span>
</li>
);
}
function OriginDetail({ origin }: { origin: DatasourceOrigin }) {
return (
<>
+18 -2
View File
@@ -62,7 +62,15 @@ const en = {
"Publication state": "Publication state",
"Semantic definition": "Semantic definition",
"Freshness policy (JSON)": "Freshness policy (JSON)",
"Quality policy (JSON)": "Quality policy (JSON)"
"Quality policy (JSON)": "Quality policy (JSON)",
"Quality policy": "Quality policy",
"Policy hash": "Policy hash",
"Schema change": "Schema change",
"Local default": "Local default",
"Promotion blockers": "Promotion blockers",
"Review warnings": "Review warnings",
"All configured quality rules passed and no blocking schema change was detected.": "All configured quality rules passed and no blocking schema change was detected.",
"Schema comparison": "Schema comparison"
} as const;
const de: Record<keyof typeof en, string> = {
@@ -127,7 +135,15 @@ const de: Record<keyof typeof en, string> = {
"Publication state": "Veröffentlichungsstatus",
"Semantic definition": "Semantische Definition",
"Freshness policy (JSON)": "Aktualitätsrichtlinie (JSON)",
"Quality policy (JSON)": "Qualitätsrichtlinie (JSON)"
"Quality policy (JSON)": "Qualitätsrichtlinie (JSON)",
"Quality policy": "Qualitätsrichtlinie",
"Policy hash": "Richtlinien-Hash",
"Schema change": "Schemaänderung",
"Local default": "Lokaler Standard",
"Promotion blockers": "Übernahmehindernisse",
"Review warnings": "Prüfhinweise",
"All configured quality rules passed and no blocking schema change was detected.": "Alle konfigurierten Qualitätsregeln wurden erfüllt und es wurde keine blockierende Schemaänderung erkannt.",
"Schema comparison": "Schemavergleich"
};
export const generatedTranslations: PlatformTranslations = { en, de };
+68
View File
@@ -369,6 +369,74 @@
font-weight: 600;
}
.datasources-validation-alerts {
display: grid;
gap: 8px;
padding: 10px;
border-top: var(--border-line);
}
.datasources-validation-alerts .alert {
margin: 0;
}
.datasources-diagnostic-list,
.datasources-schema-changes {
display: grid;
gap: 7px;
margin: 7px 0 0;
padding: 0;
list-style: none;
}
.datasources-diagnostic-list li,
.datasources-diagnostic-list span,
.datasources-diagnostic-list small,
.datasources-schema-changes li,
.datasources-schema-changes span,
.datasources-schema-changes strong,
.datasources-schema-changes small {
display: block;
min-width: 0;
}
.datasources-diagnostic-list small,
.datasources-schema-changes small {
margin-top: 2px;
color: var(--muted);
font-size: 11px;
}
.datasources-validation-ok {
padding: 10px 12px;
border-top: var(--border-line);
color: var(--success);
font-size: 12px;
}
.datasources-schema-changes {
margin: 0;
padding: 8px 10px;
}
.datasources-schema-changes li {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: start;
gap: 9px;
padding: 6px 0;
}
.datasources-schema-changes li + li {
border-top: var(--border-line);
}
.datasources-schema-changes strong {
color: var(--text-strong);
font-size: 12px;
font-weight: 600;
}
.datasources-workspace-empty {
display: grid;
place-items: center;