fix: display current object name in morph relation picker after rename (#17209)

**Summary**

Issue #16963: When an object is renamed, the morph relation picker still
shows the old name.

**Root cause**

The picker used searchRecord.objectNameSingular from the GraphQL search
response, which can be stale after a rename.
The search record stores the object name at query time, not the current
metadata.

**Solution**

- Updated SingleRecordPickerMenuItem:
- Added useObjectMetadataItems to access current object metadata.
- Look up the current object metadata by morphItem.objectMetadataId.
- Use labelSingular (or nameSingular as fallback) instead of
searchRecord.objectNameSingular for display.
- Updated MultipleRecordPickerMenuItemContent:
- Use objectMetadataItem.labelSingular (already available as a prop)
instead of searchRecord.objectNameSingular.

---------

Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
This commit is contained in:
Ayesha Waseem
2026-01-22 20:40:57 +05:00
committed by GitHub
parent 1e9cd2f5c6
commit 669da1a87d
16 changed files with 146 additions and 35 deletions
@@ -62,17 +62,27 @@ export type SettingsDataModelFieldMorphRelationFormValues = z.infer<
>;
type SettingsDataModelFieldRelationFormProps = {
sourceObjectMetadataId: string;
existingFieldMetadataId: string;
disabled?: boolean;
};
export const SettingsDataModelFieldRelationForm = ({
existingFieldMetadataId,
sourceObjectMetadataId,
disabled = false,
}: SettingsDataModelFieldRelationFormProps) => {
const { t } = useLingui();
const { control } =
useFormContext<SettingsDataModelFieldMorphRelationFormValues>();
const { control, watch } = useFormContext();
const currentIds = watch('morphRelationObjectMetadataIds') as
| string[]
| undefined;
const isSelfInDestinationForMorphRelation =
isDefined(currentIds) &&
currentIds.length > 1 &&
currentIds.includes(sourceObjectMetadataId);
const { fieldMetadataItem: existingFieldMetadataItem } =
useFieldMetadataItemById(existingFieldMetadataId);
@@ -85,6 +95,7 @@ export const SettingsDataModelFieldRelationForm = ({
const initialRelationObjectMetadataItems =
useRelationSettingsFormInitialTargetObjectMetadatas({
fieldMetadataItem: existingFieldMetadataItem,
sourceObjectMetadataId,
});
const initialRelationType =
@@ -136,6 +147,11 @@ export const SettingsDataModelFieldRelationForm = ({
selectedObjectMetadataIds={value}
withSearchInput={true}
onChange={onChange}
error={
isSelfInDestinationForMorphRelation
? t`Relations cannot include the source object when multiple destinations are selected.`
: undefined
}
/>
)}
/>
@@ -40,6 +40,14 @@ export const SettingsDataModelFieldRelationFormCard = ({
const { objectMetadataItems } = useObjectMetadataItems();
const sourceObjectMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === objectNameSingular,
);
if (!sourceObjectMetadataItem) {
throw new Error('Object not found.');
}
const relationObjectMetadataIds: string[] = watch(
'morphRelationObjectMetadataIds',
[],
@@ -116,6 +124,7 @@ export const SettingsDataModelFieldRelationFormCard = ({
<>
<SettingsDataModelFieldRelationForm
existingFieldMetadataId={existingFieldMetadataId}
sourceObjectMetadataId={sourceObjectMetadataItem?.id}
disabled={disabled}
/>
{isJunctionRelationsEnabled && (
@@ -5,8 +5,10 @@ import { fieldMetadataItemHasMorphRelations } from '@/settings/data-model/fields
import { isDefined } from 'twenty-shared/utils';
export const useRelationSettingsFormInitialTargetObjectMetadatas = ({
sourceObjectMetadataId,
fieldMetadataItem,
}: {
sourceObjectMetadataId: string;
fieldMetadataItem?: Pick<
FieldMetadataItem,
'type' | 'morphRelations' | 'relation'
@@ -36,7 +38,14 @@ export const useRelationSettingsFormInitialTargetObjectMetadatas = ({
const availableItems = activeObjectMetadataItems
.filter(isObjectMetadataAvailableForRelation)
.sort((a, b) => a.labelSingular.localeCompare(b.labelSingular));
.filter((item) => item.id !== sourceObjectMetadataId)
.sort((a, b) => {
if (a.isCustom === b.isCustom) {
return 0;
}
return a.isCustom ? -1 : 1;
});
const firstInitialObjectCandidate = availableItems[0];
if (!isDefined(firstInitialObjectCandidate)) {
throw new Error(
@@ -1,16 +1,47 @@
import { settingsDataModelFieldDescriptionFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldDescriptionForm';
import { settingsDataModelFieldIconLabelFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm';
import { settingsDataModelFieldSettingsFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
import { isDefined } from 'twenty-shared/utils';
import { z } from 'zod';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { settingsDataModelFieldTypeFormSchema } from '~/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect';
export const settingsFieldFormSchema = (existingOtherLabels?: string[]) => {
return z
type SettingsFieldFormSchemaOptions = {
existingOtherLabels?: string[];
sourceObjectMetadataId?: string;
};
export const settingsFieldFormSchema = (
options: SettingsFieldFormSchemaOptions = {},
) => {
const { existingOtherLabels, sourceObjectMetadataId } = options;
const baseSchema = z
.object({})
.extend(
settingsDataModelFieldIconLabelFormSchema(existingOtherLabels).shape,
)
.extend(settingsDataModelFieldDescriptionFormSchema().shape)
.extend(settingsDataModelFieldTypeFormSchema.shape)
.and(settingsDataModelFieldSettingsFormSchema);
.and(settingsDataModelFieldSettingsFormSchema)
.refine((data) => {
const formData = data as {
type?: FieldMetadataType;
morphRelationObjectMetadataIds?: string[];
};
if (formData.type !== FieldMetadataType.MORPH_RELATION) return true;
if (!isDefined(sourceObjectMetadataId)) return true;
if (
!isDefined(formData.morphRelationObjectMetadataIds) ||
formData.morphRelationObjectMetadataIds.length <= 1
)
return true;
if (
formData.morphRelationObjectMetadataIds.includes(sourceObjectMetadataId)
)
return false;
return true;
});
return baseSchema;
};