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
@@ -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 (
<>