[Data Object Editor] Restore (inherited) changes - #4026
Draft
ValeriaMaltseva wants to merge 2 commits into
Draft
Conversation
Adds a per-field Restore action that undoes the change which broke a field's inheritance, so the field takes its value from the origin object again. The value a field would inherit is only known for fields that were inherited when the editor was opened - inheritanceData carries the origin id and a flag, never a value - so the action is offered for those and not for fields that already carried an own value. Where the value lives decides how it is put back: - Plain fields are held by the Ant form store, so the form resets them to the loaded value and the field is separately persisted as empty, since an auto save may already have written the own value into the draft. The empty value comes from the field type, because the block API takes a list of items and rejects null. - Inside a Form.KeyedList - object bricks and the classification store - the value is held by the list, and each of the two encodes inherited fields in its own payload (null for bricks, absence for the store), so its owner drops its own bookkeeping for the field and re-emits the payload. A synchronous marker is needed there because restoring the inheritance state only schedules a React update. Also fixes getMergedValue dropping the inherited values of a classification store group once that group holds no own values any more. filterInheritedFields encodes such a group as an empty list, which getFieldList reports as a field of its own and the merge then wrote over the values merged in for the keys of the group. Restoring the last own key of a group made that reachable. Verified: tsc, eslint and the jest suite pass, and the SDK builds. Not verified in a browser. Resolves #1045 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Contributor
There was a problem hiding this comment.
Pull request overview
Adds per-field inheritance restoration to the Data Object Editor.
Changes:
- Adds Restore UI, translations, and inheritance-state handling.
- Supports plain fields, blocks, object bricks, and classification stores.
- Fixes classification-store merging and adds tests.
Reviewed changes
Copilot reviewed 24 out of 25 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
translations/studio.sv.yaml |
Adds Swedish label. |
translations/studio.no.yaml |
Adds Norwegian label. |
translations/studio.it.yaml |
Adds Italian label. |
translations/studio.fr.yaml |
Adds French label. |
translations/studio.es.yaml |
Adds Spanish label. |
translations/studio.en.yaml |
Adds English label. |
translations/studio.de.yaml |
Adds German label. |
dynamic-type-object-data-block.tsx |
Defines block empty value. |
use-restore-inheritance.ts |
Implements restore behavior. |
use-restore-inheritance.test.tsx |
Tests restore branches. |
field-label.tsx |
Displays Restore action. |
dynamic-type-object-data-abstract.tsx |
Adds empty-value API. |
object-brick.tsx |
Restores brick fields. |
group-value.ts |
Fixes inherited group merging. |
group-value.test.ts |
Tests group merging. |
classification-store.tsx |
Restores classification keys. |
inheritance-state-provider.tsx |
Adds restore state transition. |
inheritance-state-provider.test.tsx |
Tests state restoration. |
clear-form-field.ts |
Builds cleared payloads. |
clear-form-field.test.ts |
Tests cleared payloads. |
edit-form-provider.tsx |
Exposes field-clearing support. |
restore-inheritance-button.tsx |
Adds translated Restore button. |
keyed-list-provider.tsx |
Exposes restore callback. |
keyed-list.tsx |
Routes field restoration. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // field has to be persisted as empty for the backend to resolve it from the | ||
| // parent again. | ||
| editFormContext?.clearDataObjectAttribute(name, emptyValue) | ||
| editFormContext?.updateDraft().catch((error) => { console.error(error) }) |
|
|
||
| export const InheritanceStateContext = React.createContext<IInheritanceStateContext | undefined>(undefined) | ||
|
|
||
| const getStateKey = (name: NamePath): string => Array.isArray(name) ? name.join('.') : name.toString() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Resolves #1045, the remaining unchecked item of #291.
Adds a per-field Restore action that undoes the change which broke a field's inheritance, so the field takes its value from the origin object again.
Scope
inheritanceDatacarries only the origin id and a flag per field, never a value, so the value a field would inherit is known only for fields that were inherited when the editor was opened. The action is therefore offered for those, and not for fields that already carried an own value — covering the latter needs a backend endpoint that returns the inherited value plus origin id, which does not exist today.Field collections, calculated values, consent, URL slugs and reverse relations never get the action, because
supportsInheritance()is false for them.How it works
useRestoreInheritancepicks the branch by where the value actually lives.Plain fields are held by the Ant form store:
form.resetFields([name])puts the loaded (inherited) value back. Ant does not report a reset throughonValuesChange, so the field is not counted as changed again.DynamicTypeObjectDataAbstract.getEmptyValue) —nullfor almost everything, but[]for blocks, whose adapter takesarrayand would throw aTypeErroronnull. I checked every data adapter;BlockAdapteris the only one that cannot takenull.Object bricks and the classification store hold their values in a
Form.KeyedList, which the Ant form store never sees, and each encodes inherited fields in its own payload —nullfor bricks, absence for the store. So the owner of the list restores the field instead: it drops its own "field was changed" bookkeeping and re-emits the payload, and the displayed value fixes itself becausegetMergedValuethen reads the field from the loaded data. A synchronous marker (restoredFieldsRef, mirroring the existingchangedFieldsRef) is needed because restoring the inheritance state only schedules a React update, which the payload built right after must not wait for.KeyedListgains one optionalonFieldRestoreprop, the counterpart of the existingonFieldChange. It is the channel the label uses to reach the owner.components/pipeline/*passes nothing and is unaffected.Blocks
Block inheritance is all-or-nothing at field level —
BlockAdapterdoes not implementDataInheritanceInterface, so the generic path yields one state for the whole block field. Restore therefore sits on the block's own label, not on its inner fields, which matches that granularity.Drive-by fix
getMergedValuedropped the inherited values of a classification store group once that group held no own values any more.filterInheritedFieldsdeliberately encodes such a group as an empty list,getFieldListreports that as a field of its own, and the merge then wrote over the values already merged in for the keys of the group. Restoring the last own key of a group made this reachable.getMergedValuehad no tests; it has three now.Verified
tsc --noEmit,eslint, and the jest suite (562 tests) pass;npm run dev-sdkbuilds.useRestoreInheritance, and thegetMergedValueregression.Follow-ups, not in here
activeGroups,groupCollectionMapping,DELETEDmarkers). Different semantics, and destructive enough to want a confirmation.getMergedValueingroup-value.tsthrows iforiginalValueholds a group that the current value does not (the siblingbrick-value.tsguards this); andDataComponentbuildsvirtualFieldNamewithout theForm.Listpath, so a field inside a block can pick up the inheritance overlay of a same-named top-level field.