Fixes #29667: preserve autoClassificationConfig on classification PUT#29668
Conversation
Metadata ingestion re-creates source classifications with a bare PUT (name + description only). The ClassificationUpdater treated the absent autoClassificationConfig as a deletion, wiping it on every ingestion and silently disabling the AutoClassification Agent for PII/PersonalData. Preserve the existing autoClassificationConfig when a PUT omits it, mirroring the existing style/lifeCycle handling. Explicit PATCH clearing still deletes it.
✅ PR checks passedThe linked issue has a description and all required Shipping project fields set. Thanks! |
There was a problem hiding this comment.
Pull request overview
This PR fixes a regression in the Java backend update path for Classification so that a full-replace PUT /v1/classifications that omits autoClassificationConfig no longer wipes an existing configuration (which can silently disable auto-classification after ingestion upserts).
Changes:
- Preserve
autoClassificationConfigonOperation.PUTwhen the incoming value isnull, by copying it from the original entity before change comparison. - Add an integration test that reproduces the ingestion “bare PUT” scenario and verifies that
PUTpreserves the config while an explicitPATCHclearing still deletes it.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ClassificationRepository.java | Preserves autoClassificationConfig during PUT updates to avoid unintended deletion when omitted. |
| openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/ClassificationResourceIT.java | Adds an IT covering the ingestion-style PUT and confirms PATCH can still clear the config. |
🟡 Playwright Results — all passed (34 flaky)✅ 4469 passed · ❌ 0 failed · 🟡 34 flaky · ⏭️ 38 skipped
🟡 34 flaky test(s) (passed on retry)
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |
| compareAndUpdate("name", () -> updateName(updated)); | ||
| } | ||
|
|
||
| private void preserveAutoClassificationConfigOnPut() { |
There was a problem hiding this comment.
Nit: The method name is not great, preserveAutoClassificationConfig() is enough I feel
There was a problem hiding this comment.
To be fair it is only on put, right? a patch would not preserve it
|
Code Review ✅ ApprovedRestores autoClassificationConfig from the existing entity during PUT operations to prevent accidental deletion by metadata connectors. No issues found. OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |



What
Preserve a classification's
autoClassificationConfigwhen aPUT /v1/classificationsomits it.Why
Metadata connectors re-create source classifications with a full-replace
PUTcarrying onlyname+description(noautoClassificationConfig).ClassificationUpdater.entitySpecificUpdaterecorded the absent field as a deletion (fieldDeleted), wiping any existing config on every metadata ingestion. The AutoClassification Agent then skips those classifications (autoClassificationConfig.enabledis gone), silently disabling auto-classification forPII,PersonalData, etc.The updater already force-preserves
mutuallyExclusiveon update;autoClassificationConfiglacked the equivalent guard.Change
In
ClassificationUpdater, onOperation.PUT, restoreautoClassificationConfigfrom the original entity when the incoming value isnull— mirroring the existingstyle/lifeCyclehandling. ExplicitPATCHclearing still deletes it.Fixes #29667
Greptile Summary
This PR fixes a regression where a bare
PUT /v1/classifications(carrying onlyname+description, as emitted by metadata connectors during ingestion) would silently wipe an existingautoClassificationConfig, disabling auto-classification forPII,PersonalData, etc. after every metadata sync.ClassificationRepository.java): InClassificationUpdater.entitySpecificUpdate, a newpreserveAutoClassificationConfigOnPut()method restoresautoClassificationConfigfrom the original entity whenever the incoming PUT has a null value, mirroring the long-standingmutuallyExclusiveguard.ClassificationResourceIT.java): A new integration test verifies both the preservation semantics (bare PUT leaves config intact) and the intentional-deletion semantics (explicit PATCH with null still removes the config), using the SDK's snapshot-based JSON Patch diff to correctly model the PATCH field-removal case.Confidence Score: 5/5
Safe to merge — the change is a two-line guard that restores a single field on PUT, isolated to the Classification updater, with no side-effects on PATCH or other operations.
The fix is minimal and precisely targeted: it only activates on Operation.PUT with a null incoming value, cannot affect PATCH flows, and mirrors an identical guard already in place for mutuallyExclusive. The integration test covers both the bug scenario and the intentional-delete path using the SDK's snapshot-based JSON Patch, which correctly generates a remove operation when a field is nulled after a get().
No files require special attention.
Important Files Changed
preserveAutoClassificationConfigOnPut()to guard against bare-PUT erasure ofautoClassificationConfig; implementation is minimal, correctly scoped toOperation.PUTwith a null check, and follows the existingmutuallyExclusiveprecedent.removeop whenautoClassificationConfigis set to null after aget(), so both assertions are reliable.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Connector as Metadata Connector participant API as PUT /v1/classifications participant Updater as ClassificationUpdater participant DB as Database Connector->>API: "PUT {name, description} (no autoClassificationConfig)" API->>Updater: entitySpecificUpdate(original, updated) Note over Updater: updated.autoClassificationConfig == null Updater->>Updater: preserveAutoClassificationConfigOnPut() Note over Updater: operation==PUT && incoming null → restore from original Updater->>DB: save with original.autoClassificationConfig preserved Note over Connector,DB: PATCH explicit clear path Connector->>API: PATCH remove autoClassificationConfig API->>Updater: entitySpecificUpdate(original, updated) Note over Updater: operation==PATCH → no override Updater->>DB: "save with autoClassificationConfig = null (deleted)"%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Connector as Metadata Connector participant API as PUT /v1/classifications participant Updater as ClassificationUpdater participant DB as Database Connector->>API: "PUT {name, description} (no autoClassificationConfig)" API->>Updater: entitySpecificUpdate(original, updated) Note over Updater: updated.autoClassificationConfig == null Updater->>Updater: preserveAutoClassificationConfigOnPut() Note over Updater: operation==PUT && incoming null → restore from original Updater->>DB: save with original.autoClassificationConfig preserved Note over Connector,DB: PATCH explicit clear path Connector->>API: PATCH remove autoClassificationConfig API->>Updater: entitySpecificUpdate(original, updated) Note over Updater: operation==PATCH → no override Updater->>DB: "save with autoClassificationConfig = null (deleted)"Reviews (4): Last reviewed commit: "Merge branch 'main' into fix/preserve-au..." | Re-trigger Greptile