Support morph relations in workflow record nodes (#21403)
## Support morph (polymorphic) relations in workflow record nodes
Morph relations (e.g. a polymorphic `Owner` on `Pet` targeting `Person`
or `Company`) were not selectable in the workflow **Create / Update /
Upsert Record** nodes. This PR adds full support for setting them.
### What changed
**Frontend**
- `shouldDisplayFormField`: allow `MORPH_RELATION` (many-to-one) so
morph fields appear in record forms.
- New `FormMorphRelationToOneFieldInput`: a polymorphic record picker
across the morph's target objects, storing a self-describing value `{
targetObjectMetadataId, id }`.
- Wired the morph branch into `FormFieldInput`.
**Backend**
- New `formatWorkflowRecordMorphRelationFields` util: resolves the form
value (stored under the base field name, e.g. `owner`) into the correct
per-target join column (`ownerCompanyId`), nulling siblings to keep
exactly one target referenced.
- Wired into the create / update / upsert workflow actions (update also
expands `fieldsToUpdate` to the concrete join columns).
### Permissions handling
- The picker's search is scoped to only the morph targets the user can
read (`canReadObjectRecords`), so it no longer breaks when a target
object is inaccessible.
- If an existing value points to an object the user can't read, the
field shows the reused **"Not shared"** lock display instead of an empty
field, while remaining editable when other targets are readable.
### Notes
- No data schema / migration changes — reuses the existing per-target
morph columns and stores the selection in the existing workflow step
JSON settings.
<img width="607" height="717" alt="Screenshot 2026-06-10 at 14 57 40"
src="https://github.com/user-attachments/assets/496442a1-04a5-40f8-8b56-b28e38b00d5a"
/>
Also handles the case where the selected record is not readable
<img width="596" height="737" alt="image"
src="https://github.com/user-attachments/assets/c5ffb94e-3838-4db5-853e-f8e490331f23"
/>
This commit is contained in:
+2
@@ -63,6 +63,7 @@ export const WorkflowVariablePicker: VariablePickerComponent = ({
|
||||
onVariableSelect,
|
||||
shouldDisplayRecordObjects = false,
|
||||
shouldDisplayRecordFields = true,
|
||||
objectNameSingularsToSelect,
|
||||
}) => {
|
||||
const dropdownId = `${SEARCH_VARIABLES_DROPDOWN_ID}-${instanceId}`;
|
||||
const isDropdownOpen = useAtomComponentStateValue(
|
||||
@@ -82,6 +83,7 @@ export const WorkflowVariablePicker: VariablePickerComponent = ({
|
||||
disabled={disabled}
|
||||
shouldDisplayRecordObjects={shouldDisplayRecordObjects}
|
||||
shouldDisplayRecordFields={shouldDisplayRecordFields}
|
||||
objectNameSingularsToSelect={objectNameSingularsToSelect}
|
||||
/>
|
||||
</StyledSearchVariablesDropdownContainer>
|
||||
);
|
||||
|
||||
+3
@@ -39,6 +39,7 @@ export const WorkflowVariablesDropdown = ({
|
||||
onVariableSelect,
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
objectNameSingularsToSelect,
|
||||
}: {
|
||||
clickableComponent?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
@@ -47,6 +48,7 @@ export const WorkflowVariablesDropdown = ({
|
||||
onVariableSelect: (variableName: string) => void;
|
||||
shouldDisplayRecordFields: boolean;
|
||||
shouldDisplayRecordObjects: boolean;
|
||||
objectNameSingularsToSelect?: string[];
|
||||
}) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const dropdownId = `${SEARCH_VARIABLES_DROPDOWN_ID}-${instanceId}`;
|
||||
@@ -119,6 +121,7 @@ export const WorkflowVariablesDropdown = ({
|
||||
onSelect={handleSubItemSelect}
|
||||
onBack={handleBack}
|
||||
shouldDisplayRecordObjects={shouldDisplayRecordObjects}
|
||||
objectNameSingularsToSelect={objectNameSingularsToSelect}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+13
-1
@@ -29,6 +29,7 @@ type WorkflowVariablesDropdownStepItemsProps = {
|
||||
onSelect: (value: string) => void;
|
||||
onBack: () => void;
|
||||
shouldDisplayRecordObjects: boolean;
|
||||
objectNameSingularsToSelect?: string[];
|
||||
};
|
||||
|
||||
export const WorkflowVariablesDropdownStepItems = ({
|
||||
@@ -36,6 +37,7 @@ export const WorkflowVariablesDropdownStepItems = ({
|
||||
onSelect,
|
||||
onBack,
|
||||
shouldDisplayRecordObjects,
|
||||
objectNameSingularsToSelect,
|
||||
}: WorkflowVariablesDropdownStepItemsProps) => {
|
||||
const { t } = useLingui();
|
||||
const { getIcon } = useIcons();
|
||||
@@ -97,8 +99,18 @@ export const WorkflowVariablesDropdownStepItems = ({
|
||||
: true;
|
||||
|
||||
const objectLabel = displayedSubStepObjectMetadata?.labelSingular;
|
||||
|
||||
const isSubStepObjectSelectable =
|
||||
!isDefined(objectNameSingularsToSelect) ||
|
||||
(isDefined(displayedSubStepObjectMetadata) &&
|
||||
objectNameSingularsToSelect.includes(
|
||||
displayedSubStepObjectMetadata.nameSingular,
|
||||
));
|
||||
|
||||
const shouldDisplaySubStepObject =
|
||||
shouldDisplayRecordObjects && isObjectFoundThroughSearch;
|
||||
shouldDisplayRecordObjects &&
|
||||
isObjectFoundThroughSearch &&
|
||||
isSubStepObjectSelectable;
|
||||
|
||||
const displayedSubStepObjectIconProps = isDefined(
|
||||
displayedSubStepObjectMetadata,
|
||||
|
||||
Reference in New Issue
Block a user