refactor: stop reading joinColumnName from relation field settings (#20304)

## Summary

`joinColumnName` on relation field settings is always derivable from the
field name (and the target object name for morph relations). This PR
stops reading it from settings anywhere in production code; the stored
value is no longer used.

The settings field is **not** removed from data yet — a follow-up can
drop it once we are confident nothing depends on the stored value.

## Helpers

The helpers are split by layer because frontend and backend hold morph
relations differently: the frontend has a base name plus a
`morphRelations[]` array, the backend has one row per target with the
name already morph-resolved.

| Helper | Layer | When to use |
|---|---|---|
| `computeRelationGqlFieldJoinColumnName` | Shared / frontend
(`gqlField`) | Non-morph relation on the frontend. |
| `computeMorphRelationGqlFieldName` | Shared / frontend (`gqlField`) |
Need the per-target morph gqlField name (e.g. `targetCompany`). |
| `computeMorphRelationGqlFieldJoinColumnName` | Shared / frontend
(`gqlField`) | Per-target morph join column on the frontend. Prefer over
the non-morph helper for any morph field — it forces the per-target
inputs. |
| `computeMorphOrRelationFieldJoinColumnName` | Backend
(`FlatFieldMetadata.name`) | Any backend read or write — the flat name
is already morph-resolved, so one helper covers both cases. |
| `computeMorphRelationFlatFieldName` | Backend
(`FlatFieldMetadata.name`) | **Mutation paths only** (create / update /
object rename). Reads consume the stored `field.name` and never call
this. |

## Test plan

- [x] Typecheck and lint (front, server, shared)
- [x] Existing unit tests pass
- [ ] CI green
This commit is contained in:
Charles Bochet
2026-05-07 09:53:00 +02:00
committed by GitHub
parent 4b56ad0607
commit 10876138d2
73 changed files with 592 additions and 387 deletions
@@ -1,5 +1,5 @@
import { type FieldMorphRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
export const computePossibleMorphGqlFieldForFieldName = ({
fieldMetadata,
}: {
@@ -9,7 +9,7 @@ export const computePossibleMorphGqlFieldForFieldName = ({
>;
}) =>
fieldMetadata.morphRelations.map((morphRelation) => {
return computeMorphRelationFieldName({
return computeMorphRelationGqlFieldName({
fieldName: fieldMetadata.fieldName,
relationType: morphRelation.type,
targetObjectMetadataNameSingular:
@@ -1,6 +1,7 @@
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { computePossibleMorphGqlFieldForFieldName } from '@/object-record/cache/utils/computePossibleMorphGqlFieldForFieldName';
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
import { computeRelationGqlFieldJoinColumnName } from 'twenty-shared/utils';
export const getFieldMetadataFromGqlField = ({
objectMetadataItem,
@@ -12,7 +13,9 @@ export const getFieldMetadataFromGqlField = ({
return (
objectMetadataItem.fields.find((field) => field.name === gqlField) ??
objectMetadataItem.fields.find(
(field) => field.settings?.joinColumnName === gqlField,
(field) =>
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
gqlField,
) ??
objectMetadataItem.fields.filter(isFieldMorphRelation).find((field) => {
const morphRelations = field.morphRelations;
@@ -27,7 +30,9 @@ export const getFieldMetadataFromGqlField = ({
return possibleMorphRelationsNames
.flatMap((possibleMorphRelationName) => [
possibleMorphRelationName,
`${possibleMorphRelationName}Id`,
computeRelationGqlFieldJoinColumnName({
name: possibleMorphRelationName,
}),
])
.includes(gqlField);
})
@@ -1,7 +1,7 @@
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { type FieldMorphRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
export const getMorphRelationFromFieldMetadataAndGqlField = ({
objectMetadataItems,
@@ -24,7 +24,7 @@ export const getMorphRelationFromFieldMetadataAndGqlField = ({
morphRelation: undefined,
};
}
const computedName = computeMorphRelationFieldName({
const computedName = computeMorphRelationGqlFieldName({
fieldName: morphRelation.sourceFieldMetadata.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular: targetObjectMetadata.nameSingular,
@@ -9,7 +9,12 @@ import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getR
import { getRefName } from '@/object-record/cache/utils/getRefName';
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getNodeTypename, isDefined, pascalCase } from 'twenty-shared/utils';
import {
computeRelationGqlFieldJoinColumnName,
getNodeTypename,
isDefined,
pascalCase,
} from 'twenty-shared/utils';
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
export const getRecordNodeFromRecord = <T extends ObjectRecord>({
@@ -132,7 +137,9 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
switch (field.type) {
case FieldMetadataType.RELATION: {
const isJoinColumn = field.settings?.joinColumnName === gqlField;
const isJoinColumn =
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
gqlField;
if (isJoinColumn) {
return [gqlField, value];
}
@@ -11,7 +11,10 @@ import { buildIdentifierGqlFields } from '@/object-record/graphql/record-gql-fie
import { generateActivityTargetGqlFields } from '@/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields';
import { generateJunctionRelationGqlFields } from '@/object-record/graphql/record-gql-fields/utils/generateJunctionRelationGqlFields';
import { isJunctionRelationField } from '@/object-record/record-field/ui/utils/junction/isJunctionRelationField';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
isDefined,
} from 'twenty-shared/utils';
export type GenerateDepthRecordGqlFieldsFromFields = {
objectMetadataItems: Pick<
@@ -138,7 +141,7 @@ export const generateDepthRecordGqlFieldsFromFields = ({
}
return {
gqlField: computeMorphRelationFieldName({
gqlField: computeMorphRelationGqlFieldName({
fieldName: fieldMetadata.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular:
@@ -6,7 +6,10 @@ import {
type JunctionObjectMetadataItem,
} from '@/object-record/record-field/ui/utils/junction/getJunctionConfig';
import { FieldMetadataType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
isDefined,
} from 'twenty-shared/utils';
type JunctionFieldMetadataItem = Pick<
FieldMetadataItem,
@@ -56,7 +59,7 @@ const buildMorphTargetFieldGqlFields = (
continue;
}
const computedFieldName = computeMorphRelationFieldName({
const computedFieldName = computeMorphRelationGqlFieldName({
fieldName: morphRelation.sourceFieldMetadata.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular: targetObjectMetadata.nameSingular,
@@ -33,7 +33,7 @@ import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { createPortal } from 'react-dom';
import {
computeMorphRelationFieldName,
computeMorphRelationGqlFieldName,
CustomError,
} from 'twenty-shared/utils';
import {
@@ -153,7 +153,7 @@ export const RecordDetailRelationRecordsListItem = ({
relationFieldMetadataItem?.type === FieldMetadataType.MORPH_RELATION;
const computedName = relationFieldMetadataItem
? computeMorphRelationFieldName({
? computeMorphRelationGqlFieldName({
fieldName: relationFieldMetadataItem.name,
relationType: relationFieldMetadataItem.settings.relationType,
targetObjectMetadataNameSingular: objectMetadataItem.nameSingular,
@@ -31,7 +31,7 @@ import {
type RecordGqlOperationFilter,
} from 'twenty-shared/types';
import {
computeMorphRelationFieldName,
computeMorphRelationGqlFieldName,
CustomError,
getAppPath,
isDefined,
@@ -138,7 +138,7 @@ export const RecordDetailRelationSection = ({
relationFieldMetadataItem?.type === FieldMetadataType.MORPH_RELATION;
const computedName = isDefined(relationFieldMetadataItem)
? computeMorphRelationFieldName({
? computeMorphRelationGqlFieldName({
fieldName: relationFieldMetadataItem.name,
relationType: relationFieldMetadataItem.settings.relationType,
targetObjectMetadataNameSingular: objectMetadataItem.nameSingular,
@@ -14,7 +14,6 @@ import { useUpdateRelationOneToManyFieldInput } from '@/object-record/record-fie
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
import { type FieldRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { extractTargetRecordsFromJunction } from '@/object-record/record-field/ui/utils/junction/extractTargetRecordsFromJunction';
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
import { getJunctionConfig } from '@/object-record/record-field/ui/utils/junction/getJunctionConfig';
import { getSourceJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getSourceJoinColumnName';
import { hasJunctionConfig } from '@/object-record/record-field/ui/utils/junction/hasJunctionConfig';
@@ -36,7 +35,11 @@ import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/h
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
import { CustomError, isDefined } from 'twenty-shared/utils';
import {
computeRelationGqlFieldJoinColumnName,
CustomError,
isDefined,
} from 'twenty-shared/utils';
import { IconPlus } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
@@ -288,9 +291,11 @@ export const RecordDetailRelationSectionDropdownToMany = ({
sourceObjectMetadata: objectMetadataItem,
});
const targetJoinColumnName = getJoinColumnName(targetField.settings);
const targetJoinColumnName = computeRelationGqlFieldJoinColumnName({
name: targetField.name,
});
if (!sourceJoinColumnName || !targetJoinColumnName) {
if (!sourceJoinColumnName) {
return;
}
@@ -11,9 +11,11 @@ import { generateDefaultRecordChipData } from '@/object-metadata/utils/generateD
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { getJoinColumnNameOrThrow } from '@/object-record/record-field/ui/utils/junction/getJoinColumnNameOrThrow';
import { useRecordFieldValue } from '@/object-record/record-store/hooks/useRecordFieldValue';
import { isDefined } from 'twenty-shared/utils';
import {
computeRelationGqlFieldJoinColumnName,
isDefined,
} from 'twenty-shared/utils';
export const useRelationToOneFieldDisplay = () => {
const { recordId, fieldDefinition, maxWidth } = useContext(FieldContext);
@@ -42,9 +44,9 @@ export const useRelationToOneFieldDisplay = () => {
fieldDefinition,
);
const joinColumnName = getJoinColumnNameOrThrow(
fieldDefinition.metadata.settings,
);
const joinColumnName = computeRelationGqlFieldJoinColumnName({
name: fieldName,
});
const foreignKeyFieldValue = useRecordFieldValue<string | null | undefined>(
recordId,
@@ -22,7 +22,6 @@ import { RecordFieldComponentInstanceContext } from '@/object-record/record-fiel
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/ui/states/recordFieldInputLayoutDirectionComponentState';
import { type FieldDefinition } from '@/object-record/record-field/ui/types/FieldDefinition';
import { type FieldRelationMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
import { getJunctionConfig } from '@/object-record/record-field/ui/utils/junction/getJunctionConfig';
import { getSourceJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getSourceJoinColumnName';
import { hasJunctionConfig } from '@/object-record/record-field/ui/utils/junction/hasJunctionConfig';
@@ -35,7 +34,11 @@ import { buildRecordLabelPayload } from '@/object-record/utils/buildRecordLabelP
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { CustomError, isDefined } from 'twenty-shared/utils';
import {
computeRelationGqlFieldJoinColumnName,
CustomError,
isDefined,
} from 'twenty-shared/utils';
export const RelationOneToManyFieldInput = () => {
const store = useStore();
@@ -218,9 +221,11 @@ export const RelationOneToManyFieldInput = () => {
sourceObjectMetadata: objectMetadataItem,
});
const targetJoinColumnName = getJoinColumnName(targetField.settings);
const targetJoinColumnName = computeRelationGqlFieldJoinColumnName({
name: targetField.name,
});
if (!sourceJoinColumnName || !targetJoinColumnName) {
if (!sourceJoinColumnName) {
return;
}
@@ -13,7 +13,10 @@ import { viewableRecordNameSingularState } from '@/object-record/record-side-pan
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { buildRecordLabelPayload } from '@/object-record/utils/buildRecordLabelPayload';
import { getOperationName } from '~/utils/getOperationName';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
isDefined,
} from 'twenty-shared/utils';
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
type useAddNewRecordAndOpenSidePanelProps = {
@@ -73,7 +76,7 @@ export const useAddNewRecordAndOpenSidePanel = ({
const gqlField =
relationFieldMetadataItem.type === FieldMetadataType.RELATION
? relationFieldMetadataItem.name
: computeMorphRelationFieldName({
: computeMorphRelationGqlFieldName({
fieldName: relationFieldMetadataItem.name,
relationType: relationFieldMetadataItemRelationType,
targetObjectMetadataNameSingular:
@@ -12,7 +12,10 @@ import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePush
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
isDefined,
} from 'twenty-shared/utils';
export const useOpenMorphRelationManyToOneFieldInput = () => {
const store = useStore();
@@ -31,7 +34,7 @@ export const useOpenMorphRelationManyToOneFieldInput = () => {
}) => {
const potentielFieldNames = fieldDefinition.metadata.morphRelations.map(
(morphRelation) => {
return computeMorphRelationFieldName({
return computeMorphRelationGqlFieldName({
fieldName: fieldDefinition.metadata.fieldName,
relationType: fieldDefinition.metadata.relationType,
targetObjectMetadataNameSingular:
@@ -7,7 +7,10 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont
import { assertFieldMetadata } from '@/object-record/record-field/ui/types/guards/assertFieldMetadata';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { type RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
isDefined,
} from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const useUpdateRelationOneToManyFieldInput = () => {
@@ -72,7 +75,7 @@ export const useUpdateRelationOneToManyFieldInput = () => {
let targetGQLFieldName: string;
if (targetFieldMetadata.type === FieldMetadataType.MORPH_RELATION) {
targetGQLFieldName = computeMorphRelationFieldName({
targetGQLFieldName = computeMorphRelationGqlFieldName({
fieldName: fieldDefinition.metadata.targetFieldMetadataName,
relationType: targetFieldMetadata.settings?.relationType,
targetObjectMetadataNameSingular:
@@ -2,7 +2,7 @@ import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMet
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { buildRecordWithAllMorphObjectIdsToNull } from '@/object-record/record-field/ui/meta-types/input/utils/buildRecordWithAllMorphObjectIdsToNull';
import { type RelationType } from 'twenty-shared/types';
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
export const buildMorphRelationUpdateInput = ({
morphRelations,
@@ -45,7 +45,7 @@ export const buildMorphRelationUpdateInput = ({
);
}
const computedFieldName = computeMorphRelationFieldName({
const computedFieldName = computeMorphRelationGqlFieldName({
fieldName,
relationType,
targetObjectMetadataNameSingular: targetObjectMetadataItem.nameSingular,
@@ -1,6 +1,6 @@
import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation';
import { type RelationType } from 'twenty-shared/types';
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
export const buildRecordWithAllMorphObjectIdsToNull = ({
morphRelations,
@@ -13,7 +13,7 @@ export const buildRecordWithAllMorphObjectIdsToNull = ({
}): Record<string, null> => {
return morphRelations.reduce(
(acc, morphRelation) => {
const computedFieldName = computeMorphRelationFieldName({
const computedFieldName = computeMorphRelationGqlFieldName({
fieldName,
relationType,
targetObjectMetadataNameSingular:
@@ -1,30 +0,0 @@
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
describe('getJoinColumnName', () => {
it('should return undefined for undefined settings', () => {
expect(getJoinColumnName(undefined)).toBeUndefined();
});
it('should return undefined for null settings', () => {
expect(getJoinColumnName(null as unknown as undefined)).toBeUndefined();
});
it('should return undefined for non-object settings', () => {
expect(getJoinColumnName('string' as unknown as undefined)).toBeUndefined();
});
it('should return undefined for settings without joinColumnName', () => {
expect(getJoinColumnName({})).toBeUndefined();
});
it('should return undefined for settings with non-string joinColumnName', () => {
expect(getJoinColumnName({ joinColumnName: 123 })).toBeUndefined();
});
it('should return the joinColumnName for valid settings', () => {
expect(getJoinColumnName({ joinColumnName: 'personId' })).toBe('personId');
expect(getJoinColumnName({ joinColumnName: 'companyId' })).toBe(
'companyId',
);
});
});
@@ -4,7 +4,10 @@ import { isObjectWithId } from '@/object-record/record-field/ui/utils/junction/i
import { type ExtractedTargetRecord } from '@/object-record/record-field/ui/utils/junction/types/ExtractedTargetRecord';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { FieldMetadataType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
isDefined,
} from 'twenty-shared/utils';
type ExtractTargetRecordsFromJunctionArgs = {
junctionRecords: ObjectRecord[] | undefined | null;
@@ -85,7 +88,7 @@ const extractFromMorphRelationField = (
}
for (const morphRelation of morphRelations) {
const computedFieldName = computeMorphRelationFieldName({
const computedFieldName = computeMorphRelationGqlFieldName({
fieldName: morphRelation.sourceFieldMetadata.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular:
@@ -1,7 +1,10 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
computeRelationGqlFieldJoinColumnName,
isDefined,
} from 'twenty-shared/utils';
export type TargetFieldInfo = {
fieldName: string;
@@ -34,7 +37,7 @@ const findMorphTargetFieldInfo = (
return undefined;
}
const fieldName = computeMorphRelationFieldName({
const fieldName = computeMorphRelationGqlFieldName({
fieldName: matchingMorphRelation.sourceFieldMetadata.name,
relationType: matchingMorphRelation.type,
targetObjectMetadataNameSingular: targetObjectMetadata.nameSingular,
@@ -45,7 +48,7 @@ const findMorphTargetFieldInfo = (
// e.g., caretakerPerson → caretakerPersonId
return {
fieldName,
joinColumnName: `${fieldName}Id`,
joinColumnName: computeRelationGqlFieldJoinColumnName({ name: fieldName }),
};
};
@@ -68,7 +71,9 @@ export const findTargetFieldInfo = (
if (field.relation?.targetObjectMetadata.id === targetObjectMetadataId) {
return {
fieldName: field.name,
joinColumnName: getJoinColumnName(field.settings),
joinColumnName: computeRelationGqlFieldJoinColumnName({
name: field.name,
}),
};
}
}
@@ -1,16 +0,0 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isDefined } from 'twenty-shared/utils';
export const getJoinColumnName = (
settings: FieldMetadataItem['settings'],
): string | undefined => {
if (
isDefined(settings) &&
typeof settings === 'object' &&
'joinColumnName' in settings &&
typeof settings.joinColumnName === 'string'
) {
return settings.joinColumnName;
}
return undefined;
};
@@ -1,13 +0,0 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
import { isDefined } from 'twenty-shared/utils';
export const getJoinColumnNameOrThrow = (
settings: FieldMetadataItem['settings'],
): string => {
const joinColumnName = getJoinColumnName(settings);
if (!isDefined(joinColumnName)) {
throw new Error('Join column name is not defined');
}
return joinColumnName;
};
@@ -1,11 +1,14 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { getJoinColumnName } from '@/object-record/record-field/ui/utils/junction/getJoinColumnName';
import { FieldMetadataType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldJoinColumnName,
computeRelationGqlFieldJoinColumnName,
isDefined,
} from 'twenty-shared/utils';
type GetSourceJoinColumnNameArgs = {
sourceField: Pick<FieldMetadataItem, 'type' | 'morphRelations' | 'settings'>;
sourceField: Pick<FieldMetadataItem, 'name' | 'type' | 'morphRelations'>;
sourceObjectMetadata: Pick<
EnrichedObjectMetadataItem,
'id' | 'nameSingular' | 'namePlural'
@@ -25,15 +28,13 @@ export const getSourceJoinColumnName = ({
return undefined;
}
const computedFieldName = computeMorphRelationFieldName({
return computeMorphRelationGqlFieldJoinColumnName({
fieldName: morphRelation.sourceFieldMetadata.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular: sourceObjectMetadata.nameSingular,
targetObjectMetadataNamePlural: sourceObjectMetadata.namePlural,
});
return `${computedFieldName}Id`;
}
return getJoinColumnName(sourceField.settings);
return computeRelationGqlFieldJoinColumnName({ name: sourceField.name });
};
@@ -29,6 +29,7 @@ import {
type UUIDFilter,
} from 'twenty-shared/types';
import {
computeRelationGqlFieldJoinColumnName,
isDefined,
isEmptyObject,
isMatchingArrayFilter,
@@ -203,7 +204,8 @@ export const isRecordMatchingFilter = ({
(field) =>
(field.type === FieldMetadataType.RELATION ||
field.type === FieldMetadataType.MORPH_RELATION) &&
field.settings?.joinColumnName === filterKey,
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
filterKey,
) ??
objectMetadataItem.fields.find(
(field) =>
@@ -415,7 +417,9 @@ export const isRecordMatchingFilter = ({
case FieldMetadataType.RELATION:
case FieldMetadataType.MORPH_RELATION: {
const isJoinColumn =
objectMetadataField.settings?.joinColumnName === filterKey ||
computeRelationGqlFieldJoinColumnName({
name: objectMetadataField.name,
}) === filterKey ||
(objectMetadataField.type === FieldMetadataType.MORPH_RELATION &&
isMorphRelationJoinColumnKey({
fieldMetadataItem: objectMetadataField,
@@ -12,7 +12,10 @@ import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guar
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
import { RelationType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
isDefined,
} from 'twenty-shared/utils';
const simpleFieldValueSelector = createAtomFamilySelector<
unknown,
@@ -49,7 +52,7 @@ const getMorphRelationFieldValueAtom = (
const recordStore = get(recordStoreFamilyState.atomFamily(recordId));
const computeMorphFieldName = (morphRelation: FieldMetadataItemRelation) =>
computeMorphRelationFieldName({
computeMorphRelationGqlFieldName({
fieldName: morphRelation.sourceFieldMetadata.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular:
@@ -3,7 +3,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { createAtomFamilySelector } from '@/ui/utilities/state/jotai/utils/createAtomFamilySelector';
import { type RelationType } from 'twenty-shared/types';
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
import { computeMorphRelationGqlFieldName } from 'twenty-shared/utils';
type MorphOneToManyFamilyKey = {
recordId: string;
@@ -24,7 +24,7 @@ export const recordStoreMorphOneToManyValueWithObjectNameFamilySelector =
({ get }) => {
const morphValuesWithObjectName = morphRelations.map(
(morphRelation) => {
const fieldName = computeMorphRelationFieldName({
const fieldName = computeMorphRelationGqlFieldName({
fieldName: morphRelation.sourceFieldMetadata.name,
relationType: morphRelation.type as RelationType,
targetObjectMetadataNameSingular:
@@ -7,6 +7,7 @@ import { isUpdateRecordValueEmpty } from '@/object-record/record-update-multiple
import { shouldDisplayFormMultiEditField } from '@/object-record/record-update-multiple/utils/shouldDisplayFormMultiEditField';
import { styled } from '@linaria/react';
import { FieldMetadataType } from 'twenty-shared/types';
import { computeRelationGqlFieldJoinColumnName } from 'twenty-shared/utils';
import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -59,7 +60,9 @@ export const UpdateMultipleRecordsForm = ({
const isRelation = isFieldRelation(fieldDefinition);
const fieldNameOrRelationIdName =
isRelation && fieldMetadataItem.type === FieldMetadataType.RELATION
? (fieldMetadataItem.settings?.joinColumnName as string)
? computeRelationGqlFieldJoinColumnName({
name: fieldMetadataItem.name,
})
: fieldName;
const value = values[fieldNameOrRelationIdName];
@@ -17,7 +17,11 @@ import { isFieldUuid } from '@/object-record/record-field/ui/types/guards/isFiel
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { buildOptimisticActorFieldValueFromCurrentWorkspaceMember } from '@/object-record/utils/buildOptimisticActorFieldValueFromCurrentWorkspaceMember';
import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
computeRelationGqlFieldJoinColumnName,
isDefined,
} from 'twenty-shared/utils';
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
type ComputeOptimisticCacheRecordInputArgs = {
@@ -46,7 +50,8 @@ export const computeOptimisticRecordFromInput = ({
objectMetadataItem.fields.find(
(field) =>
field.type === FieldMetadataType.RELATION &&
field.settings?.joinColumnName === recordKey,
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
recordKey,
);
const potentialMorphRelationJoinColumnNameFieldMetadataItem =
@@ -200,7 +205,7 @@ export const computeOptimisticRecordFromInput = ({
const relationGqlFields = fieldMetadataItem.morphRelations?.map(
(morphRelation) => {
return computeMorphRelationFieldName({
return computeMorphRelationGqlFieldName({
fieldName: fieldMetadataItem.name,
relationType,
targetObjectMetadataNameSingular:
@@ -212,10 +217,15 @@ export const computeOptimisticRecordFromInput = ({
);
const relationGqlField = relationGqlFields?.find(
(relationGqlField) => recordInput[`${relationGqlField}Id`],
(relationGqlField) =>
recordInput[
computeRelationGqlFieldJoinColumnName({ name: relationGqlField })
],
);
const relationGqlFieldWithId = `${relationGqlField}Id`;
const relationGqlFieldWithId = computeRelationGqlFieldJoinColumnName({
name: relationGqlField ?? '',
});
if (isUndefined(relationGqlField)) {
continue;
@@ -4,7 +4,11 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
computeRelationGqlFieldJoinColumnName,
isDefined,
} from 'twenty-shared/utils';
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
type PrefillRecordArgs = {
@@ -26,13 +30,15 @@ export const prefillRecord = <T extends ObjectRecord>({
fieldMetadataItem.type === FieldMetadataType.RELATION &&
fieldMetadataItem.relation?.type === RelationType.MANY_TO_ONE
) {
const joinColumnValue =
input[fieldMetadataItem.settings?.joinColumnName] ?? null;
const joinColumnName = computeRelationGqlFieldJoinColumnName({
name: fieldMetadataItem.name,
});
const joinColumnValue = input[joinColumnName] ?? null;
throwIfInputRelationDataIsInconsistent(input, fieldMetadataItem);
return [
[fieldMetadataItem.name, fieldValue],
[fieldMetadataItem.settings?.joinColumnName, joinColumnValue],
[joinColumnName, joinColumnValue],
];
}
if (
@@ -41,7 +47,7 @@ export const prefillRecord = <T extends ObjectRecord>({
) {
const gqlFields = fieldMetadataItem.morphRelations?.map(
(morphRelation) => {
return computeMorphRelationFieldName({
return computeMorphRelationGqlFieldName({
fieldName: fieldMetadataItem.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular:
@@ -52,10 +58,15 @@ export const prefillRecord = <T extends ObjectRecord>({
},
);
return gqlFields?.flatMap((gqlField) => [
[gqlField, fieldValue],
[`${gqlField}Id`, input[`${gqlField}Id`] ?? null],
]);
return gqlFields?.flatMap((gqlField) => {
const joinColumnName = computeRelationGqlFieldJoinColumnName({
name: gqlField,
});
return [
[gqlField, fieldValue],
[joinColumnName, input[joinColumnName] ?? null],
];
});
}
return [[fieldMetadataItem.name, fieldValue]];
@@ -70,7 +81,9 @@ const throwIfInputRelationDataIsInconsistent = (
fieldMetadataItem: FieldMetadataItem,
) => {
const inputValue = input[fieldMetadataItem.name];
const relationIdFieldName = `${fieldMetadataItem.name}Id`;
const relationIdFieldName = computeRelationGqlFieldJoinColumnName({
name: fieldMetadataItem.name,
});
if (isDefined(inputValue) && !isDefined(input[relationIdFieldName])) {
throw new Error(
`Inconsistent input: ${fieldMetadataItem.name} is specified but ${relationIdFieldName} is missing`,
@@ -2,7 +2,11 @@ import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/Enriche
import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { isSystemSearchVectorField } from '@/object-record/utils/isSystemSearchVectorField';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationGqlFieldName,
computeRelationGqlFieldJoinColumnName,
isDefined,
} from 'twenty-shared/utils';
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
export const sanitizeRecordInput = ({
@@ -26,13 +30,14 @@ export const sanitizeRecordInput = ({
objectMetadataItem.fields.find(
(field) =>
field.type === FieldMetadataType.RELATION &&
field.settings?.joinColumnName === fieldName,
computeRelationGqlFieldJoinColumnName({ name: field.name }) ===
fieldName,
);
const potentialMorphRelationJoinColumnNameFieldMetadataItem =
objectMetadataItem.fields.find((field) => {
if (!isFieldMorphRelation(field)) return false;
return field.morphRelations?.some((morphRelation) => {
const computedFieldName = computeMorphRelationFieldName({
const computedFieldName = computeMorphRelationGqlFieldName({
fieldName: field.name,
relationType: morphRelation.type,
targetObjectMetadataNameSingular: