From 10876138d2fff5f8b6d6fcfb85d884eadcc9f963 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 7 May 2026 09:53:00 +0200 Subject: [PATCH] refactor: stop reading joinColumnName from relation field settings (#20304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 --- .../utils/getActivityTargetObjectRecords.ts | 9 ++- .../triggerUpdateRelationsOptimisticEffect.ts | 6 +- .../__tests__/shouldFieldBeQueried.test.ts | 2 +- .../utils/mapFieldMetadataToGraphQLQuery.ts | 13 +++- .../utils/mapObjectMetadataToGraphQLQuery.ts | 38 +++++++--- .../utils/shouldFieldBeQueried.ts | 34 ++++++++- ...omputePossibleMorphGqlFieldForFieldName.ts | 4 +- .../utils/getFieldMetadataFromGqlField.ts | 9 ++- ...rphRelationFromFieldMetadataAndGqlField.ts | 4 +- .../cache/utils/getRecordNodeFromRecord.ts | 11 ++- .../generateDepthRecordGqlFieldsFromFields.ts | 7 +- .../generateJunctionRelationGqlFields.ts | 7 +- .../RecordDetailRelationRecordsListItem.tsx | 4 +- .../RecordDetailRelationSection.tsx | 4 +- ...ordDetailRelationSectionDropdownToMany.tsx | 13 +++- .../hooks/useRelationToOneFieldDisplay.ts | 12 +-- .../RelationOneToManyFieldInput.tsx | 13 +++- .../hooks/useAddNewRecordAndOpenSidePanel.ts | 7 +- ...seOpenMorphRelationManyToOneFieldInput.tsx | 7 +- .../useUpdateRelationOneToManyFieldInput.tsx | 7 +- .../utils/buildMorphRelationUpdateInput.ts | 4 +- .../buildRecordWithAllMorphObjectIdsToNull.ts | 4 +- .../utils/__tests__/getJoinColumnName.test.ts | 30 -------- .../extractTargetRecordsFromJunction.ts | 7 +- .../ui/utils/junction/findTargetFieldInfo.ts | 15 ++-- .../ui/utils/junction/getJoinColumnName.ts | 16 ---- .../junction/getJoinColumnNameOrThrow.ts | 13 ---- .../utils/junction/getSourceJoinColumnName.ts | 15 ++-- .../utils/isRecordMatchingFilter.ts | 8 +- .../recordStoreFieldValueSelector.ts | 7 +- ...ToManyValueWithObjectNameFamilySelector.ts | 4 +- .../components/UpdateMultipleRecordsForm.tsx | 5 +- .../utils/computeOptimisticRecordFromInput.ts | 20 +++-- .../object-record/utils/prefillRecord.ts | 33 +++++--- .../utils/sanitizeRecordInput.ts | 11 ++- .../data-arg-processor.service.ts | 14 +--- .../filter-arg-processor.service.ts | 2 +- .../process-nested-relations-v2.helper.ts | 34 ++++++--- .../common-merge-many-query-runner.service.ts | 10 +-- .../utils/get-all-selectable-fields.util.ts | 2 +- ...is-flat-field-a-junction-relation-field.ts | 3 +- .../graphql-selected-fields.parser.ts | 19 +++-- .../__tests__/build-columns-to-select.spec.ts | 50 ------------ .../utils/build-columns-to-select.ts | 17 +++-- ...tract-graphql-relation-field-names.util.ts | 11 +-- ...or-relation-field-join-column-name.util.ts | 4 +- ...ute-morph-relation-flat-field-name.util.ts | 34 +++++++++ ...eld-maps-from-flat-object-metadata.util.ts | 2 +- ...from-morph-relation-update-payload.util.ts | 7 +- ...ield-input-to-flat-field-metadatas.util.ts | 7 +- ...ield-input-to-flat-field-metadatas.util.ts | 2 +- ...-relation-flat-field-metadata-pair.util.ts | 3 +- ...-field-names-and-join-column-names.util.ts | 9 ++- ...morph-field-on-object-names-update.util.ts | 4 +- .../utils/generate-flat-index.util.ts | 16 ++-- ...-field-metadatas-for-custom-object.util.ts | 2 +- .../factories/entity-schema-column.factory.ts | 15 +--- .../determine-schema-relation-details.util.ts | 18 ++++- ...mat-column-name-for-relation-field.util.ts | 8 +- ...rls-row-level-permission-predicate.util.ts | 13 ++-- ...ject-metadata-to-schema-properties.util.ts | 2 +- ...field-by-name-and-join-column-maps.util.ts | 19 +++-- .../create-field-action-handler.service.ts | 11 +-- .../update-field-action-handler.service.ts | 62 +++++++-------- .../index/utils/index-action-handler.utils.ts | 13 +++- .../utils/generate-column-definitions.util.ts | 17 +++-- ...at-workflow-record-relation-fields.util.ts | 2 +- ...-test-objects-with-all-field-types.util.ts | 5 +- ... computeMorphRelationGqlFieldName.test.ts} | 10 +-- ...puteRelationGqlFieldJoinColumnName.test.ts | 76 +++++++++++++++++++ ... compute-morph-relation-gql-field-name.ts} | 8 +- ...ute-relation-gql-field-join-column-name.ts | 39 ++++++++++ packages/twenty-shared/src/utils/index.ts | 6 +- 73 files changed, 592 insertions(+), 387 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-record/record-field/ui/utils/__tests__/getJoinColumnName.test.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getJoinColumnName.ts delete mode 100644 packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getJoinColumnNameOrThrow.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util.ts rename packages/twenty-shared/src/utils/fieldMetadata/__tests__/{computeMorphRelationFieldName.test.ts => computeMorphRelationGqlFieldName.test.ts} (73%) create mode 100644 packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeRelationGqlFieldJoinColumnName.test.ts rename packages/twenty-shared/src/utils/fieldMetadata/{compute-morph-relation-field-name.ts => compute-morph-relation-gql-field-name.ts} (77%) create mode 100644 packages/twenty-shared/src/utils/fieldMetadata/compute-relation-gql-field-join-column-name.ts diff --git a/packages/twenty-front/src/modules/activities/utils/getActivityTargetObjectRecords.ts b/packages/twenty-front/src/modules/activities/utils/getActivityTargetObjectRecords.ts index 41cf52e9bd..53a92178d1 100644 --- a/packages/twenty-front/src/modules/activities/utils/getActivityTargetObjectRecords.ts +++ b/packages/twenty-front/src/modules/activities/utils/getActivityTargetObjectRecords.ts @@ -10,7 +10,10 @@ import { FieldMetadataType, type Nullable, } from 'twenty-shared/types'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { + computeMorphRelationGqlFieldName, + isDefined, +} from 'twenty-shared/utils'; type GetActivityTargetObjectRecordsProps = { activityRecord: Note | Task; @@ -94,7 +97,7 @@ export const getActivityTargetObjectRecords = ({ ) { const matchingMorphRelation = field.morphRelations.find( (morphRelation) => { - const morphFieldName = computeMorphRelationFieldName({ + const morphFieldName = computeMorphRelationGqlFieldName({ fieldName: field.name, relationType: morphRelation.type, targetObjectMetadataNameSingular: @@ -108,7 +111,7 @@ export const getActivityTargetObjectRecords = ({ ); if (isDefined(matchingMorphRelation)) { - matchingFieldName = computeMorphRelationFieldName({ + matchingFieldName = computeMorphRelationGqlFieldName({ fieldName: field.name, relationType: matchingMorphRelation.type, targetObjectMetadataNameSingular: diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts index 24bdee2ee2..8325506af3 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts @@ -20,7 +20,7 @@ import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { type ApolloCache } from '@apollo/client'; import { isArray } from '@sniptt/guards'; import { - computeMorphRelationFieldName, + computeMorphRelationGqlFieldName, CustomError, isDefined, } from 'twenty-shared/utils'; @@ -186,7 +186,7 @@ const triggerUpdateRelationOptimisticEffect = ({ const gqlFieldNameOnTargetRecord = targetFieldMetadataFullObject.type === FieldMetadataType.RELATION ? targetFieldMetadataFullObject.name - : computeMorphRelationFieldName({ + : computeMorphRelationGqlFieldName({ fieldName: targetFieldMetadataFullObject.name, relationType: targetFieldMetadataFullObject.settings?.relationType, targetObjectMetadataNameSingular: @@ -276,7 +276,7 @@ const triggerUpdateMorphRelationOptimisticEffect = ({ } morphRelations.forEach((morphRelation) => { - const gqlFieldMorphRelation = computeMorphRelationFieldName({ + const gqlFieldMorphRelation = computeMorphRelationGqlFieldName({ fieldName: fieldMetadataItemOnSourceRecord.name, relationType: morphRelation.type, targetObjectMetadataNameSingular: diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/shouldFieldBeQueried.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/shouldFieldBeQueried.test.ts index 49bd759316..b8f37ac8ad 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/shouldFieldBeQueried.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/shouldFieldBeQueried.test.ts @@ -44,7 +44,7 @@ describe('shouldFieldBeQueried', () => { const res = shouldFieldBeQueried({ gqlField: 'fieldNameId', fieldMetadata: { - name: 'fieldNameId', + name: 'fieldName', type: FieldMetadataType.RELATION, settings: { relationType: RelationType.MANY_TO_ONE, diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts index e436c817c4..f3ac20e58e 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts @@ -10,7 +10,11 @@ import { type ObjectPermissions, RelationType, } from 'twenty-shared/types'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { + computeMorphRelationGqlFieldName, + computeRelationGqlFieldJoinColumnName, + isDefined, +} from 'twenty-shared/utils'; type MapFieldMetadataToGraphQLQueryArgs = { objectMetadataItems: EnrichedObjectMetadataItem[]; @@ -52,7 +56,7 @@ export const mapFieldMetadataToGraphQLQuery = ({ ) { let gqlMorphField = ''; for (const morphRelation of fieldMetadata.morphRelations ?? []) { - const relationFieldName = computeMorphRelationFieldName({ + const relationFieldName = computeMorphRelationGqlFieldName({ fieldName: fieldMetadata.name, relationType: fieldMetadata.settings?.relationType, targetObjectMetadataNameSingular: @@ -168,7 +172,10 @@ ${mapObjectMetadataToGraphQLQuery({ } } - if (gqlField === fieldMetadata.settings?.joinColumnName) { + if ( + gqlField === + computeRelationGqlFieldJoinColumnName({ name: fieldMetadata.name }) + ) { return `${gqlField}`; } diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts index b63dcafdb9..dcf37acd81 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts @@ -4,8 +4,17 @@ import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapField import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried'; import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields'; import { isRecordGqlFieldsNode } from '@/object-record/graphql/utils/isRecordGraphlFieldsNode'; -import { FieldMetadataType, type ObjectPermissions } from 'twenty-shared/types'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { + FieldMetadataType, + RelationType, + type ObjectPermissions, +} from 'twenty-shared/types'; +import { + computeMorphRelationGqlFieldJoinColumnName, + computeMorphRelationGqlFieldName, + computeRelationGqlFieldJoinColumnName, + isDefined, +} from 'twenty-shared/utils'; type MapObjectMetadataToGraphQLQueryArgs = { objectMetadataItems: EnrichedObjectMetadataItem[]; @@ -46,12 +55,15 @@ export const mapObjectMetadataToGraphQLQuery = ({ const manyToOneRelationFields = objectMetadataItem?.readableFields .filter((field) => field.isActive) - .filter( - (field) => - field.type === FieldMetadataType.RELATION || - field.type === FieldMetadataType.MORPH_RELATION, - ) - .filter((field) => isDefined(field.settings?.joinColumnName)); + .filter((field) => { + if (field.type === FieldMetadataType.RELATION) { + return field.relation?.type === RelationType.MANY_TO_ONE; + } + if (field.type === FieldMetadataType.MORPH_RELATION) { + return field.settings?.relationType === RelationType.MANY_TO_ONE; + } + return false; + }); const manyToOneRelationGqlFieldWithFieldMetadata = manyToOneRelationFields.flatMap((fieldMetadata) => { @@ -59,7 +71,9 @@ export const mapObjectMetadataToGraphQLQuery = ({ fieldMetadata.type === FieldMetadataType.MORPH_RELATION; if (!isMorphRelation) { return { - gqlField: fieldMetadata.settings?.joinColumnName, + gqlField: computeRelationGqlFieldJoinColumnName({ + name: fieldMetadata.name, + }), fieldMetadata: fieldMetadata, }; } @@ -69,14 +83,14 @@ export const mapObjectMetadataToGraphQLQuery = ({ } return fieldMetadata.morphRelations.map((morphRelation) => ({ - gqlField: `${computeMorphRelationFieldName({ + gqlField: computeMorphRelationGqlFieldJoinColumnName({ fieldName: fieldMetadata.name, relationType: morphRelation.type, targetObjectMetadataNameSingular: morphRelation.targetObjectMetadata.nameSingular, targetObjectMetadataNamePlural: morphRelation.targetObjectMetadata.namePlural, - })}Id`, + }), fieldMetadata: fieldMetadata, })); }); @@ -102,7 +116,7 @@ export const mapObjectMetadataToGraphQLQuery = ({ } return fieldMetadata.morphRelations.map((morphRelation) => ({ - gqlField: computeMorphRelationFieldName({ + gqlField: computeMorphRelationGqlFieldName({ fieldName: fieldMetadata.name, relationType: morphRelation.type, targetObjectMetadataNameSingular: diff --git a/packages/twenty-front/src/modules/object-metadata/utils/shouldFieldBeQueried.ts b/packages/twenty-front/src/modules/object-metadata/utils/shouldFieldBeQueried.ts index 88fb6a7f96..7b319a7332 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/shouldFieldBeQueried.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/shouldFieldBeQueried.ts @@ -4,7 +4,11 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation'; -import { isDefined } from 'twenty-shared/utils'; +import { + computeMorphRelationGqlFieldJoinColumnName, + computeRelationGqlFieldJoinColumnName, + isDefined, +} from 'twenty-shared/utils'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; export const shouldFieldBeQueried = ({ @@ -13,13 +17,35 @@ export const shouldFieldBeQueried = ({ recordGqlFields, }: { gqlField: string; - fieldMetadata: Pick; + fieldMetadata: Pick< + FieldMetadataItem, + 'name' | 'type' | 'settings' | 'morphRelations' + >; objectRecord?: ObjectRecord; recordGqlFields?: RecordGqlOperationGqlRecordFields; }): any => { + const isRelationJoinColumn = + isFieldRelation(fieldMetadata) && + computeRelationGqlFieldJoinColumnName({ name: fieldMetadata.name }) === + gqlField; + + const isMorphRelationJoinColumn = + isFieldMorphRelation(fieldMetadata) && + isDefined(fieldMetadata.morphRelations) && + fieldMetadata.morphRelations.some( + (morphRelation) => + computeMorphRelationGqlFieldJoinColumnName({ + fieldName: fieldMetadata.name, + relationType: morphRelation.type, + targetObjectMetadataNameSingular: + morphRelation.targetObjectMetadata.nameSingular, + targetObjectMetadataNamePlural: + morphRelation.targetObjectMetadata.namePlural, + }) === gqlField, + ); + const isJoinColumn: boolean = - (isFieldRelation(fieldMetadata) || isFieldMorphRelation(fieldMetadata)) && - fieldMetadata.settings.joinColumnName === gqlField; + isRelationJoinColumn || isMorphRelationJoinColumn; if ( isUndefinedOrNull(recordGqlFields) && diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/computePossibleMorphGqlFieldForFieldName.ts b/packages/twenty-front/src/modules/object-record/cache/utils/computePossibleMorphGqlFieldForFieldName.ts index 48940e4a4d..0cfce5b297 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/computePossibleMorphGqlFieldForFieldName.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/computePossibleMorphGqlFieldForFieldName.ts @@ -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: diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/getFieldMetadataFromGqlField.ts b/packages/twenty-front/src/modules/object-record/cache/utils/getFieldMetadataFromGqlField.ts index 4a80fe3add..49717d72b6 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/getFieldMetadataFromGqlField.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/getFieldMetadataFromGqlField.ts @@ -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); }) diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/getMorphRelationFromFieldMetadataAndGqlField.ts b/packages/twenty-front/src/modules/object-record/cache/utils/getMorphRelationFromFieldMetadataAndGqlField.ts index f9556a0684..97c79ecf4a 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/getMorphRelationFromFieldMetadataAndGqlField.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/getMorphRelationFromFieldMetadataAndGqlField.ts @@ -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, diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts index f0f081247f..5b76e266c8 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts @@ -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 = ({ @@ -132,7 +137,9 @@ export const getRecordNodeFromRecord = ({ switch (field.type) { case FieldMetadataType.RELATION: { - const isJoinColumn = field.settings?.joinColumnName === gqlField; + const isJoinColumn = + computeRelationGqlFieldJoinColumnName({ name: field.name }) === + gqlField; if (isJoinColumn) { return [gqlField, value]; } diff --git a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields.ts b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields.ts index 93da5b1a77..ed9a98bba3 100644 --- a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields.ts +++ b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields.ts @@ -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: diff --git a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateJunctionRelationGqlFields.ts b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateJunctionRelationGqlFields.ts index fee5a97fa3..b9bcbfae25 100644 --- a/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateJunctionRelationGqlFields.ts +++ b/packages/twenty-front/src/modules/object-record/graphql/record-gql-fields/utils/generateJunctionRelationGqlFields.ts @@ -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, diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx index a66722c91f..276cd2ddbf 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx @@ -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, diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx index 6462c0eac0..e31ab585ba 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx @@ -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, diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSectionDropdownToMany.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSectionDropdownToMany.tsx index f29f7168d9..82b66f828f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSectionDropdownToMany.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSectionDropdownToMany.tsx @@ -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; } diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts index 7956827c0d..e12c3209bf 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/hooks/useRelationToOneFieldDisplay.ts @@ -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( recordId, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/RelationOneToManyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/RelationOneToManyFieldInput.tsx index 03a121fe5a..3d6d103a31 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/RelationOneToManyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/components/RelationOneToManyFieldInput.tsx @@ -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; } diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useAddNewRecordAndOpenSidePanel.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useAddNewRecordAndOpenSidePanel.ts index 72f648e2fb..848fb209ec 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useAddNewRecordAndOpenSidePanel.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useAddNewRecordAndOpenSidePanel.ts @@ -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: diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenMorphRelationManyToOneFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenMorphRelationManyToOneFieldInput.tsx index cc0d270452..888df62a09 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenMorphRelationManyToOneFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useOpenMorphRelationManyToOneFieldInput.tsx @@ -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: diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useUpdateRelationOneToManyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useUpdateRelationOneToManyFieldInput.tsx index fe60fd9ea0..fb0df3a4cd 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useUpdateRelationOneToManyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/hooks/useUpdateRelationOneToManyFieldInput.tsx @@ -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: diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/utils/buildMorphRelationUpdateInput.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/utils/buildMorphRelationUpdateInput.ts index 65d75d209b..2e4ac1c07d 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/utils/buildMorphRelationUpdateInput.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/utils/buildMorphRelationUpdateInput.ts @@ -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, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/utils/buildRecordWithAllMorphObjectIdsToNull.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/utils/buildRecordWithAllMorphObjectIdsToNull.ts index 7cb590e40e..c6f5d6e82f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/utils/buildRecordWithAllMorphObjectIdsToNull.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/input/utils/buildRecordWithAllMorphObjectIdsToNull.ts @@ -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 => { return morphRelations.reduce( (acc, morphRelation) => { - const computedFieldName = computeMorphRelationFieldName({ + const computedFieldName = computeMorphRelationGqlFieldName({ fieldName, relationType, targetObjectMetadataNameSingular: diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/__tests__/getJoinColumnName.test.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/__tests__/getJoinColumnName.test.ts deleted file mode 100644 index 5df6828411..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/__tests__/getJoinColumnName.test.ts +++ /dev/null @@ -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', - ); - }); -}); diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/extractTargetRecordsFromJunction.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/extractTargetRecordsFromJunction.ts index b8c1d72e92..d0394f9ed2 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/extractTargetRecordsFromJunction.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/extractTargetRecordsFromJunction.ts @@ -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: diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/findTargetFieldInfo.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/findTargetFieldInfo.ts index 4190ed7068..7007d5c0a0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/findTargetFieldInfo.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/findTargetFieldInfo.ts @@ -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, + }), }; } } diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getJoinColumnName.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getJoinColumnName.ts deleted file mode 100644 index 6986073e29..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getJoinColumnName.ts +++ /dev/null @@ -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; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getJoinColumnNameOrThrow.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getJoinColumnNameOrThrow.ts deleted file mode 100644 index 4326a3e4bd..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getJoinColumnNameOrThrow.ts +++ /dev/null @@ -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; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getSourceJoinColumnName.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getSourceJoinColumnName.ts index ca54d3f779..7004676d86 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getSourceJoinColumnName.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/getSourceJoinColumnName.ts @@ -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; + sourceField: Pick; 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 }); }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts index f992ad995d..3db5fe5825 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts @@ -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, diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelector.ts b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelector.ts index b97f4e20eb..fa3f9b3eea 100644 --- a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelector.ts +++ b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreFieldValueSelector.ts @@ -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: diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphOneToManyValueWithObjectNameFamilySelector.ts b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphOneToManyValueWithObjectNameFamilySelector.ts index ff05997a76..aaea937526 100644 --- a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphOneToManyValueWithObjectNameFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphOneToManyValueWithObjectNameFamilySelector.ts @@ -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: diff --git a/packages/twenty-front/src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsForm.tsx b/packages/twenty-front/src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsForm.tsx index cc4e7f3000..48fa94c369 100644 --- a/packages/twenty-front/src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsForm.tsx +++ b/packages/twenty-front/src/modules/object-record/record-update-multiple/components/UpdateMultipleRecordsForm.tsx @@ -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]; diff --git a/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts b/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts index a32b18b1de..efe8f99789 100644 --- a/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts +++ b/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts @@ -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; diff --git a/packages/twenty-front/src/modules/object-record/utils/prefillRecord.ts b/packages/twenty-front/src/modules/object-record/utils/prefillRecord.ts index 8358091969..f843ca0e08 100644 --- a/packages/twenty-front/src/modules/object-record/utils/prefillRecord.ts +++ b/packages/twenty-front/src/modules/object-record/utils/prefillRecord.ts @@ -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 = ({ 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 = ({ ) { const gqlFields = fieldMetadataItem.morphRelations?.map( (morphRelation) => { - return computeMorphRelationFieldName({ + return computeMorphRelationGqlFieldName({ fieldName: fieldMetadataItem.name, relationType: morphRelation.type, targetObjectMetadataNameSingular: @@ -52,10 +58,15 @@ export const prefillRecord = ({ }, ); - 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`, diff --git a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts index b8c7e97cfe..7baea23ea2 100644 --- a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts +++ b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts @@ -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: diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/data-arg-processor.service.ts b/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/data-arg-processor.service.ts index 43a3ac9cd7..5c1e415dac 100644 --- a/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/data-arg-processor.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/data-arg-processor.service.ts @@ -14,6 +14,7 @@ import { isDefined, } from 'twenty-shared/utils'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { transformActorField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-actor-field.util'; import { transformAddressField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-address-field.util'; import { transformArrayField } from 'src/engine/api/common/common-args-processors/data-arg-processor/transformer-utils/transform-array-field.util'; @@ -56,12 +57,10 @@ import { transformLinksValue } from 'src/engine/core-modules/record-transformer/ import { transformPhonesValue } from 'src/engine/core-modules/record-transformer/utils/transform-phones-value.util'; import { transformRichTextValue } from 'src/engine/core-modules/record-transformer/utils/transform-rich-text.util'; import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; -import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util'; -import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; @Injectable() @@ -245,14 +244,9 @@ export class DataArgProcessorService { ); } - const joinColumnName = isFlatFieldMetadataOfType( - fieldMetadata, - FieldMetadataType.MORPH_RELATION, - ) - ? computeMorphOrRelationFieldJoinColumnName({ - name: fieldMetadata.name, - }) - : relationSettings.joinColumnName; + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: fieldMetadata.name, + }); if (key === joinColumnName) { return validateUUIDFieldOrThrow(value, key); diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/filter-arg-processor.service.ts b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/filter-arg-processor.service.ts index 39735e1a8f..8b96628608 100644 --- a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/filter-arg-processor.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/filter-arg-processor.service.ts @@ -8,6 +8,7 @@ import { } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { validateAndTransformOperatorAndValue } from 'src/engine/api/common/common-args-processors/filter-arg-processor/utils/validate-and-transform-operator-and-value.util'; @@ -17,7 +18,6 @@ import { } from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception'; import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant'; import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/composite-field-metadata-type.type'; -import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; diff --git a/packages/twenty-server/src/engine/api/common/common-nested-relations-processor/process-nested-relations-v2.helper.ts b/packages/twenty-server/src/engine/api/common/common-nested-relations-processor/process-nested-relations-v2.helper.ts index af9f8d5de7..361163c151 100644 --- a/packages/twenty-server/src/engine/api/common/common-nested-relations-processor/process-nested-relations-v2.helper.ts +++ b/packages/twenty-server/src/engine/api/common/common-nested-relations-processor/process-nested-relations-v2.helper.ts @@ -4,6 +4,7 @@ import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type FindOptionsRelations, type ObjectLiteral } from 'typeorm'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant'; @@ -191,8 +192,9 @@ export class ProcessNestedRelationsV2Helper { select: columnsToSelect, }); - const joinColumnName = - sourceFieldMetadata.settings.joinColumnName ?? `${sourceFieldName}Id`; + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: sourceFieldName, + }); const relationIds = this.getUniqueIds({ records: parentObjectRecords, @@ -200,14 +202,28 @@ export class ProcessNestedRelationsV2Helper { relationType === RelationType.ONE_TO_MANY ? 'id' : joinColumnName, }); + if ( + relationType === RelationType.ONE_TO_MANY && + !isDefined(targetRelationName) + ) { + throw new GraphqlQueryRunnerException( + `Could not resolve target relation for one-to-many field ${sourceFieldName}`, + GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND, + { userFriendlyMessage: STANDARD_ERROR_MESSAGE }, + ); + } + const fieldMetadataTargetRelationColumnName = - targetRelation && - isFieldMetadataEntityOfType( - targetRelation, - FieldMetadataType.MORPH_RELATION, - ) - ? `${targetRelation.settings?.joinColumnName}` - : `${targetRelationName}Id`; + computeMorphOrRelationFieldJoinColumnName({ + name: + targetRelation && + isFieldMetadataEntityOfType( + targetRelation, + FieldMetadataType.MORPH_RELATION, + ) + ? targetRelation.name + : (targetRelationName as string), + }); const { relationResults, relationAggregatedFieldsResult } = await this.findRelations({ diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts index 63088bdaaa..a4dd260f45 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts @@ -15,6 +15,7 @@ import { isDefined } from 'twenty-shared/utils'; import { FindOptionsRelations, In, ObjectLiteral } from 'typeorm'; import { v4 as uuidv4 } from 'uuid'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, @@ -385,10 +386,7 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ | FieldMetadataSettingsMapping['RELATION'] | undefined; - if ( - relationSettings?.relationType !== RelationType.MANY_TO_ONE || - !relationSettings?.joinColumnName - ) { + if (relationSettings?.relationType !== RelationType.MANY_TO_ONE) { continue; } @@ -405,7 +403,9 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ objectMetadata: objMetadata, fieldName: field.name, fieldId: field.id, - joinColumnName: relationSettings.joinColumnName, + joinColumnName: computeMorphOrRelationFieldJoinColumnName({ + name: field.name, + }), }); } diff --git a/packages/twenty-server/src/engine/api/common/common-select-fields/utils/get-all-selectable-fields.util.ts b/packages/twenty-server/src/engine/api/common/common-select-fields/utils/get-all-selectable-fields.util.ts index d8ab4e345a..40d12d5052 100644 --- a/packages/twenty-server/src/engine/api/common/common-select-fields/utils/get-all-selectable-fields.util.ts +++ b/packages/twenty-server/src/engine/api/common/common-select-fields/utils/get-all-selectable-fields.util.ts @@ -9,8 +9,8 @@ import { compositeTypeDefinitions, } from 'twenty-shared/types'; -import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; diff --git a/packages/twenty-server/src/engine/api/common/common-select-fields/utils/get-is-flat-field-a-junction-relation-field.ts b/packages/twenty-server/src/engine/api/common/common-select-fields/utils/get-is-flat-field-a-junction-relation-field.ts index ef9d9bbbd2..e2ff213aeb 100644 --- a/packages/twenty-server/src/engine/api/common/common-select-fields/utils/get-is-flat-field-a-junction-relation-field.ts +++ b/packages/twenty-server/src/engine/api/common/common-select-fields/utils/get-is-flat-field-a-junction-relation-field.ts @@ -11,8 +11,7 @@ export const getIsFlatFieldAJunctionRelationField = ({ const isJunctionRelationField = isDefined(flatField.settings) && 'relationType' in flatField.settings && - flatField.settings.relationType === RelationType.MANY_TO_ONE && - isDefined(flatField.settings.joinColumnName); + flatField.settings.relationType === RelationType.MANY_TO_ONE; // TODO: refactor this when we remove hard-coded activity relations const isActivityRelationField = diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts index e586fe8858..1b8a8fb60a 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts @@ -4,6 +4,7 @@ import { } from 'twenty-shared/types'; import { capitalize, isDefined } from 'twenty-shared/utils'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { GraphqlQuerySelectedFieldsAggregateParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-aggregate.parser'; import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser'; import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/composite-field-metadata-type.type'; @@ -104,12 +105,11 @@ export class GraphqlQuerySelectedFieldsParser { if ( isFlatFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION) ) { - const joinColumnName = fieldMetadata.settings?.joinColumnName; + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: fieldMetadata.name, + }); - if ( - isDefined(joinColumnName) && - isDefined(graphqlSelectedFields[joinColumnName]) - ) { + if (isDefined(graphqlSelectedFields[joinColumnName])) { accumulator.select[joinColumnName] = true; } @@ -149,12 +149,11 @@ export class GraphqlQuerySelectedFieldsParser { continue; } - const joinColumnName = fieldMetadata.settings?.joinColumnName; + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: fieldMetadata.name, + }); - if ( - isDefined(joinColumnName) && - isDefined(graphqlSelectedFields[joinColumnName]) - ) { + if (isDefined(graphqlSelectedFields[joinColumnName])) { accumulator.select[joinColumnName] = true; } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/build-columns-to-select.spec.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/build-columns-to-select.spec.ts index 63ba14a2b6..3441a9a55e 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/build-columns-to-select.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/build-columns-to-select.spec.ts @@ -365,54 +365,4 @@ describe('buildColumnsToSelect', () => { id: true, }); }); - - it('should handle relation field without joinColumnName', () => { - const noJoinColumnCompanyField = createMockField({ - id: companyFieldId, - type: FieldMetadataType.RELATION, - name: 'company', - label: 'Company', - objectMetadataId: personObjectId, - defaultValue: null, - settings: { - relationType: RelationType.MANY_TO_ONE, - joinColumnName: null, - }, - relationTargetObjectMetadataId: companyObjectId, - }); - - const flatObjectMetadata = buildMockFlatObjectMetadata([ - nameFieldId, - companyFieldId, - ]); - const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([ - nameField, - noJoinColumnCompanyField, - ]); - const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([ - flatObjectMetadata, - companyObjectMetadata, - ]); - - const select = { - nameFirstName: true, - }; - - const relations = { - company: {}, - }; - - const result = buildColumnsToSelect({ - select, - relations, - flatObjectMetadata, - flatObjectMetadataMaps, - flatFieldMetadataMaps, - }); - - expect(result).toEqual({ - nameFirstName: true, - id: true, - }); - }); }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select.ts index 660de07efb..af87e16260 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select.ts @@ -1,6 +1,7 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; @@ -64,13 +65,14 @@ const getRequiredRelationColumns = ( if ( !isDefined(relationValue) || - !isDefined(fieldMetadata?.settings?.joinColumnName) || fieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE ) { continue; } - requiredColumns.push(fieldMetadata.settings.joinColumnName); + requiredColumns.push( + computeMorphOrRelationFieldJoinColumnName({ name: fieldMetadata.name }), + ); } if ( @@ -84,7 +86,7 @@ const getRequiredRelationColumns = ( : undefined; if ( - !fieldMetadata.settings?.relationType || + fieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE || !isDefined(targetObjectMetadata) ) { continue; @@ -92,14 +94,13 @@ const getRequiredRelationColumns = ( const relationValue = relations[fieldMetadata.name]; - if ( - !isDefined(relationValue) || - !isDefined(fieldMetadata?.settings?.joinColumnName) - ) { + if (!isDefined(relationValue)) { continue; } - requiredColumns.push(fieldMetadata.settings.joinColumnName); + requiredColumns.push( + computeMorphOrRelationFieldJoinColumnName({ name: fieldMetadata.name }), + ); } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util.ts index 7426cafd88..4a3b46bec4 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util.ts @@ -1,5 +1,6 @@ import { type FieldMetadataType } from 'twenty-shared/types'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; export const extractGraphQLRelationFieldNames = ( @@ -7,14 +8,10 @@ export const extractGraphQLRelationFieldNames = ( FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION >, ) => { - const settings = fieldMetadata.settings; - const joinColumnName = settings?.joinColumnName; - - if (!joinColumnName) { - throw new Error('Join column name is not defined'); - } - const fieldMetadataName = fieldMetadata.name; + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: fieldMetadataName, + }); return { joinColumnName, fieldMetadataName }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util.ts index d581178e0b..75f1635c51 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util.ts @@ -1,9 +1,9 @@ -type ComputeRelationFieldJoinColumnNameArgs = { +type ComputeMorphOrRelationFieldJoinColumnNameArgs = { name: string; }; export const computeMorphOrRelationFieldJoinColumnName = ({ name, -}: ComputeRelationFieldJoinColumnNameArgs) => { +}: ComputeMorphOrRelationFieldJoinColumnNameArgs): string => { return `${name}Id`; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util.ts new file mode 100644 index 0000000000..08c2cc363f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util.ts @@ -0,0 +1,34 @@ +import { RelationType } from 'twenty-shared/types'; +import { capitalize } from 'twenty-shared/utils'; + +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; + +type ComputeMorphRelationFlatFieldNameArgs = { + fieldName: string; + relationType: RelationType; + targetObjectMetadataNameSingular: string; + targetObjectMetadataNamePlural: string; +}; + +export const computeMorphRelationFlatFieldName = ({ + fieldName, + relationType, + targetObjectMetadataNameSingular: nameSingular, + targetObjectMetadataNamePlural: namePlural, +}: ComputeMorphRelationFlatFieldNameArgs): string => { + if (relationType === RelationType.MANY_TO_ONE) { + return `${fieldName}${capitalize(nameSingular)}`; + } + + if (relationType === RelationType.ONE_TO_MANY) { + return `${fieldName}${capitalize(namePlural)}`; + } + + throw new FieldMetadataException( + `Invalid relation type (${relationType}) for field ${fieldName} on ${nameSingular}`, + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util.ts index 6488a8ce8e..b4ddc1c32e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util.ts @@ -1,7 +1,7 @@ import { FieldMetadataType, RelationType } from 'twenty-shared/types'; -import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts index 32884db8ff..75a612f3dc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-to-update-from-morph-relation-update-payload.util.ts @@ -2,10 +2,11 @@ import { FieldMetadataType, type RelationUpdatePayload, } from 'twenty-shared/types'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; -import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { computeMorphRelationFlatFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; @@ -89,7 +90,7 @@ export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({ flatEntityMaps: flatObjectMetadataMaps, }); - const computedMorphName = computeMorphRelationFieldName({ + const computedMorphName = computeMorphRelationFlatFieldName({ fieldName: morphNameWithoutObjectName, relationType: fieldMetadataToUpdate.settings.relationType, targetObjectMetadataNameSingular: newTargetObjectMetadata.nameSingular, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts index d0650c5f09..67ae08c1f1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util.ts @@ -1,12 +1,13 @@ import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { computeMorphRelationFlatFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util'; import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; -import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type'; @@ -86,7 +87,7 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({ const morphId = v4(); const flatFieldsAndIndexes = morphRelationCreationPayload.reduce( (acc, { relationCreationPayload, targetFlatObjectMetadata }) => { - const currentMorphRelationFieldName = computeMorphRelationFieldName({ + const currentMorphRelationFieldName = computeMorphRelationFlatFieldName({ fieldName: createFieldInput.name, relationType: relationCreationPayload.type, targetObjectMetadataNameSingular: targetFlatObjectMetadata.nameSingular, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts index c544a86701..2e4ef63328 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util.ts @@ -2,10 +2,10 @@ import { msg } from '@lingui/core/macro'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; -import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type'; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts index cf0ab772f2..6bf92648ae 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util.ts @@ -4,12 +4,13 @@ import { RelationOnDeleteAction, RelationType, } from 'twenty-shared/types'; + import { v4 } from 'uuid'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type'; -import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { generateIndexForFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util'; import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util'; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-object-field-names-and-join-column-names.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-object-field-names-and-join-column-names.util.ts index 5f43026db8..ee19bbbef0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-object-field-names-and-join-column-names.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/get-object-field-names-and-join-column-names.util.ts @@ -1,6 +1,6 @@ import { RelationType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps.util'; import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util'; import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type'; @@ -39,15 +39,16 @@ export const getObjectFieldNamesAndJoinColumnNames = ({ universalFlatFieldMetadata, ) && universalFlatFieldMetadata.universalSettings.relationType === - RelationType.MANY_TO_ONE && - isDefined(universalFlatFieldMetadata.universalSettings.joinColumnName) + RelationType.MANY_TO_ONE ) { return { ...acc, fieldNames: [...acc.fieldNames, universalFlatFieldMetadata.name], joinColumnNames: [ ...acc.joinColumnNames, - universalFlatFieldMetadata.universalSettings.joinColumnName, + computeMorphOrRelationFieldJoinColumnName({ + name: universalFlatFieldMetadata.name, + }), ], }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util.ts index 43a06bb679..1f58d68a37 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util.ts @@ -3,9 +3,9 @@ import { type FieldMetadataType, type FromTo, } from 'twenty-shared/types'; -import { computeMorphRelationFieldName } from 'twenty-shared/utils'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { computeMorphRelationFlatFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; @@ -37,7 +37,7 @@ const updateMorphFlatFieldName = ({ namePlural: fromRelationTargetFlatObjectMetadata.namePlural, }); - const newMorphFieldName = computeMorphRelationFieldName({ + const newMorphFieldName = computeMorphRelationFlatFieldName({ fieldName: initialMorphRelationFieldName, relationType: fromMorphFlatFieldMetadata.universalSettings.relationType, targetObjectMetadataNameSingular: diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util.ts index fdf5767c12..c12dd044d8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util.ts @@ -1,9 +1,11 @@ +import { RelationType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FlatEntityMapsException, FlatEntityMapsExceptionCode, } from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util'; import { generateDeterministicIndexNameV2 } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name-v2'; import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; @@ -36,11 +38,15 @@ export const generateFlatIndexMetadataWithNameOrThrow = ({ ); } - const name = isMorphOrRelationUniversalFlatFieldMetadata( - relatedFlatFieldMetadata, - ) - ? (relatedFlatFieldMetadata.universalSettings.joinColumnName ?? - relatedFlatFieldMetadata.name) + const isManyToOneRelation = + isMorphOrRelationUniversalFlatFieldMetadata(relatedFlatFieldMetadata) && + relatedFlatFieldMetadata.universalSettings?.relationType === + RelationType.MANY_TO_ONE; + + const name = isManyToOneRelation + ? computeMorphOrRelationFieldJoinColumnName({ + name: relatedFlatFieldMetadata.name, + }) : relatedFlatFieldMetadata.name; return { diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts index e0fb0c9538..0a0f3841d0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts @@ -5,10 +5,10 @@ import { import { FieldMetadataType } from 'twenty-shared/types'; import { capitalize, isDefined } from 'twenty-shared/utils'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; -import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util'; diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts index e96cc6b06e..ce9db2cd79 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-column.factory.ts @@ -7,16 +7,13 @@ import { import { isDefined } from 'twenty-shared/utils'; import { type ColumnType, type EntitySchemaColumnOptions } from 'typeorm'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value'; -import { - TwentyORMException, - TwentyORMExceptionCode, -} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { type EntitySchemaFieldMetadata, type EntitySchemaFieldMetadataMaps, @@ -57,18 +54,14 @@ export class EntitySchemaColumnFactory { if (isRelation) { const isManyToOneRelation = fieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE; - const joinColumnName = fieldMetadata.settings?.joinColumnName; if (!isManyToOneRelation) { continue; } - if (!isDefined(joinColumnName)) { - throw new TwentyORMException( - `Field ${fieldMetadata.id} of type ${fieldMetadata.type} is a many to one relation but does not have a join column name`, - TwentyORMExceptionCode.MALFORMED_METADATA, - ); - } + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: fieldMetadata.name, + }); entitySchemaColumnMap[joinColumnName] = { name: joinColumnName, diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/determine-schema-relation-details.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/determine-schema-relation-details.util.ts index 62f333a6cf..5be82c5cc4 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/determine-schema-relation-details.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/determine-schema-relation-details.util.ts @@ -1,4 +1,8 @@ -import { type FieldMetadataType } from 'twenty-shared/types'; +import { + type FieldMetadataType, + RelationType as TwentyRelationType, +} from 'twenty-shared/types'; + import { type RelationType } from 'typeorm/metadata/types/RelationTypes'; import { @@ -10,6 +14,7 @@ import { type EntitySchemaFieldMetadataMaps, type EntitySchemaObjectMetadataMaps, } from 'src/engine/twenty-orm/global-workspace-datasource/types/entity-schema-metadata.type'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { converRelationTypeToTypeORMRelationType } from 'src/engine/twenty-orm/utils/convert-relation-type-to-typeorm-relation-type.util'; interface RelationDetails { @@ -65,12 +70,19 @@ export function determineSchemaRelationDetails( throw new Error('Target field metadata not found'); } + const isManyToOne = + fieldMetadata.settings.relationType === TwentyRelationType.MANY_TO_ONE; + return { relationType, target: targetObjectMetadata.nameSingular, inverseSide: targetFieldMetadata.name, - joinColumn: fieldMetadata.settings.joinColumnName - ? { name: fieldMetadata.settings.joinColumnName } + joinColumn: isManyToOne + ? { + name: computeMorphOrRelationFieldJoinColumnName({ + name: fieldMetadata.name, + }), + } : undefined, }; } diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-column-name-for-relation-field.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-column-name-for-relation-field.util.ts index e6c039edea..825efc3a75 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/format-column-name-for-relation-field.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-column-name-for-relation-field.util.ts @@ -1,8 +1,8 @@ +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FieldMetadataSettingsMapping, RelationType, } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; export const formatColumnNameForRelationField = ( fieldName: string, @@ -13,11 +13,7 @@ export const formatColumnNameForRelationField = ( } if (fieldMetadataSettings.relationType === RelationType.MANY_TO_ONE) { - if (!isDefined(fieldMetadataSettings.joinColumnName)) { - throw new Error(`Join column name is not defined for field ${fieldName}`); - } - - return fieldMetadataSettings.joinColumnName; + return computeMorphOrRelationFieldJoinColumnName({ name: fieldName }); } return fieldName; diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/is-record-matching-rls-row-level-permission-predicate.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/is-record-matching-rls-row-level-permission-predicate.util.ts index 4166e5e03e..71f06a8198 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/is-record-matching-rls-row-level-permission-predicate.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/is-record-matching-rls-row-level-permission-predicate.util.ts @@ -47,6 +47,7 @@ import { isMatchingUUIDFilter, } from 'twenty-shared/utils'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; @@ -208,8 +209,8 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({ objectFields.find( (field) => field.type === FieldMetadataType.RELATION && - (field.settings as { joinColumnName?: string } | undefined) - ?.joinColumnName === filterKey, + computeMorphOrRelationFieldJoinColumnName({ name: field.name }) === + filterKey, ); if (!isDefined(objectMetadataField)) { @@ -412,11 +413,9 @@ export const isRecordMatchingRLSRowLevelPermissionPredicate = ({ } case FieldMetadataType.RELATION: { const isJoinColumn = - ( - objectMetadataField.settings as - | { joinColumnName?: string } - | undefined - )?.joinColumnName === filterKey; + computeMorphOrRelationFieldJoinColumnName({ + name: objectMetadataField.name, + }) === filterKey; if (isJoinColumn) { return isMatchingUUIDFilter({ diff --git a/packages/twenty-server/src/engine/utils/convert-object-metadata-to-schema-properties.util.ts b/packages/twenty-server/src/engine/utils/convert-object-metadata-to-schema-properties.util.ts index 32a1d557e4..b98ee47054 100644 --- a/packages/twenty-server/src/engine/utils/convert-object-metadata-to-schema-properties.util.ts +++ b/packages/twenty-server/src/engine/utils/convert-object-metadata-to-schema-properties.util.ts @@ -5,10 +5,10 @@ import { } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/utils/build-universal-flat-object-field-by-name-and-join-column-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/utils/build-universal-flat-object-field-by-name-and-join-column-maps.util.ts index ee2ea45751..5c5893ae66 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/utils/build-universal-flat-object-field-by-name-and-join-column-maps.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/utils/build-universal-flat-object-field-by-name-and-join-column-maps.util.ts @@ -1,5 +1,6 @@ -import { isDefined } from 'twenty-shared/utils'; +import { RelationType } from 'twenty-shared/types'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util'; import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util'; import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type'; @@ -24,12 +25,16 @@ export const buildUniversalFlatObjectFieldByNameAndJoinColumnMaps = ({ for (const field of objectFields) { fieldUniversalIdentifierByName[field.name] = field.universalIdentifier; - if (isMorphOrRelationUniversalFlatFieldMetadata(field)) { - if (isDefined(field.universalSettings.joinColumnName)) { - fieldUniversalIdentifierByJoinColumnName[ - field.universalSettings.joinColumnName - ] = field.universalIdentifier; - } + if ( + isMorphOrRelationUniversalFlatFieldMetadata(field) && + field.universalSettings.relationType === RelationType.MANY_TO_ONE + ) { + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: field.name, + }); + + fieldUniversalIdentifierByJoinColumnName[joinColumnName] = + field.universalIdentifier; } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/create-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/create-field-action-handler.service.ts index 31b8455185..e72c5a40b9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/create-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/create-field-action-handler.service.ts @@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils'; import { type QueryRunner } from 'typeorm'; import { v4 } from 'uuid'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { type MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type'; @@ -214,13 +215,9 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct targetFlatObjectMetadata, ); - const joinColumnName = flatFieldMetadata.settings?.joinColumnName; - - if (!isDefined(joinColumnName)) { - throw new Error( - 'Join column name is not defined in a MANY_TO_ONE relation', - ); - } + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: flatFieldMetadata.name, + }); await this.workspaceSchemaManagerService.foreignKeyManager.createForeignKey( { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts index a108901583..8c60c2f83b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts @@ -3,10 +3,12 @@ import { Injectable } from '@nestjs/common'; import { FieldMetadataSettingsMapping, FieldMetadataType, + RelationType, } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { ColumnType, type QueryRunner } from 'typeorm'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -223,35 +225,6 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct } if (isDefined(update.settings)) { - // Handle joinColumnName rename - if (isMorphOrRelationFlatFieldMetadata(optimisticFlatFieldMetadata)) { - const fromSettings = optimisticFlatFieldMetadata.settings; - const toSettings = update.settings as - | FieldMetadataSettingsMapping['MORPH_RELATION'] - | FieldMetadataSettingsMapping['RELATION']; - - if ( - isDefined(fromSettings?.joinColumnName) && - isDefined(toSettings?.joinColumnName) && - fromSettings.joinColumnName !== toSettings.joinColumnName - ) { - await this.workspaceSchemaManagerService.columnManager.renameColumn({ - queryRunner, - schemaName, - tableName, - oldColumnName: fromSettings.joinColumnName, - newColumnName: toSettings.joinColumnName, - }); - optimisticFlatFieldMetadata = { - ...optimisticFlatFieldMetadata, - settings: { - ...optimisticFlatFieldMetadata.settings, - joinColumnName: toSettings.joinColumnName, - }, - }; - } - } - // Handle asExpression/generatedType change (for TS_VECTOR fields) if ( isFlatFieldMetadataOfType( @@ -302,19 +275,26 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct | FieldMetadataSettingsMapping['MORPH_RELATION'] | FieldMetadataSettingsMapping['RELATION']; + const isManyToOne = + optimisticFlatFieldMetadata.settings?.relationType === + RelationType.MANY_TO_ONE; + if ( - isDefined(optimisticFlatFieldMetadata.settings.joinColumnName) && + isManyToOne && isDefined(fromSettings?.onDelete) && isDefined(toSettings?.onDelete) && toSettings.onDelete !== fromSettings.onDelete ) { + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: optimisticFlatFieldMetadata.name, + }); const foreignKeyName = await this.workspaceSchemaManagerService.foreignKeyManager.getForeignKeyName( { queryRunner, schemaName, tableName, - columnName: optimisticFlatFieldMetadata.settings.joinColumnName, + columnName: joinColumnName, }, ); @@ -351,7 +331,7 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct schemaName, foreignKey: { tableName, - columnName: optimisticFlatFieldMetadata.settings.joinColumnName, + columnName: joinColumnName, referencedTableName, referencedColumnName: 'id', onDelete: @@ -403,7 +383,23 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct newColumnName: toCompositeColumnName, }); } - } else if (!isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) { + } else if (isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) { + if ( + flatFieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE + ) { + await this.workspaceSchemaManagerService.columnManager.renameColumn({ + queryRunner, + schemaName, + tableName, + oldColumnName: computeMorphOrRelationFieldJoinColumnName({ + name: fromName, + }), + newColumnName: computeMorphOrRelationFieldJoinColumnName({ + name: toName, + }), + }); + } + } else { await this.workspaceSchemaManagerService.columnManager.renameColumn({ queryRunner, schemaName, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/index/utils/index-action-handler.utils.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/index/utils/index-action-handler.utils.ts index 5ce7589553..2aa38e9a31 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/index/utils/index-action-handler.utils.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/index/utils/index-action-handler.utils.ts @@ -1,7 +1,8 @@ -import { compositeTypeDefinitions } from 'twenty-shared/types'; +import { compositeTypeDefinitions, RelationType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type QueryRunner } from 'typeorm'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { @@ -41,14 +42,18 @@ export const computeFlatIndexFieldColumnNames = ({ } if (isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) { - if (!isDefined(flatFieldMetadata.settings?.joinColumnName)) { + if ( + flatFieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE + ) { throw new FlatEntityMapsException( - 'Join column name is not defined for relation field', + 'Cannot index a relation field that has no join column', FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, ); } - return flatFieldMetadata.settings.joinColumnName; + return computeMorphOrRelationFieldJoinColumnName({ + name: flatFieldMetadata.name, + }); } if (isCompositeFieldMetadataType(flatFieldMetadata.type)) { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/generate-column-definitions.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/generate-column-definitions.util.ts index 6e9421acde..2bd077f9c4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/generate-column-definitions.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/generate-column-definitions.util.ts @@ -1,6 +1,12 @@ -import { type CompositeProperty, FieldMetadataType } from 'twenty-shared/types'; +import { + type CompositeProperty, + FieldMetadataType, + RelationType, +} from 'twenty-shared/types'; + import { type ColumnType } from 'typeorm'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type CompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/composite-field-metadata-type.type'; import { computeColumnName, @@ -109,14 +115,13 @@ const generateRelationColumnDefinition = ( FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION >, ): WorkspaceSchemaColumnDefinition | null => { - if ( - !flatFieldMetadata.settings || - !flatFieldMetadata.settings.joinColumnName - ) { + if (flatFieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE) { return null; } - const joinColumnName = flatFieldMetadata.settings.joinColumnName; + const joinColumnName = computeMorphOrRelationFieldJoinColumnName({ + name: flatFieldMetadata.name, + }); return { name: joinColumnName, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/utils/format-workflow-record-relation-fields.util.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/utils/format-workflow-record-relation-fields.util.ts index 03ae757176..4fa1c125c4 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/utils/format-workflow-record-relation-fields.util.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/utils/format-workflow-record-relation-fields.util.ts @@ -2,8 +2,8 @@ import { isObject, isString } from '@sniptt/guards'; import { FieldMetadataType, RelationType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util'; import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; import { type ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; diff --git a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/utils/setup-test-objects-with-all-field-types.util.ts b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/utils/setup-test-objects-with-all-field-types.util.ts index 670a4dfb91..e5ef3e5935 100644 --- a/packages/twenty-server/test/integration/graphql/suites/inputs-validation/utils/setup-test-objects-with-all-field-types.util.ts +++ b/packages/twenty-server/test/integration/graphql/suites/inputs-validation/utils/setup-test-objects-with-all-field-types.util.ts @@ -6,9 +6,10 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-m import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { makeMetadataAPIRequestWithFileUpload } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-file-upload.util'; import { FieldMetadataType, RelationType } from 'twenty-shared/types'; -import { computeMorphRelationFieldName } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { computeMorphRelationFlatFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-flat-field-name.util'; + const TEST_OBJECT_METADATA_NAME_SINGULAR = 'apiInputValidationTestObject'; const TEST_OBJECT_METADATA_NAME_PLURAL = 'apiInputValidationTestObjects'; const TEST_TARGET_OBJECT_METADATA_NAME_SINGULAR_1 = @@ -29,7 +30,7 @@ export const TEST_TARGET_OBJECT_RECORD_ID_FIELD_VALUE = '20202020-b21e-4ec2-873b-de4264d89021'; export const joinColumnNameForManyToOneMorphRelationField1 = - computeMorphRelationFieldName({ + computeMorphRelationFlatFieldName({ fieldName: 'manyToOneMorphRelationField', relationType: RelationType.MANY_TO_ONE, targetObjectMetadataNameSingular: diff --git a/packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeMorphRelationFieldName.test.ts b/packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeMorphRelationGqlFieldName.test.ts similarity index 73% rename from packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeMorphRelationFieldName.test.ts rename to packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeMorphRelationGqlFieldName.test.ts index c16b815b1a..c602437a66 100644 --- a/packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeMorphRelationFieldName.test.ts +++ b/packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeMorphRelationGqlFieldName.test.ts @@ -1,8 +1,8 @@ -import { computeMorphRelationFieldName } from '@/utils/fieldMetadata/compute-morph-relation-field-name'; +import { computeMorphRelationGqlFieldName } from '@/utils/fieldMetadata/compute-morph-relation-gql-field-name'; -describe('computeMorphRelationFieldName', () => { +describe('computeMorphRelationGqlFieldName', () => { it('should return singular-based name for MANY_TO_ONE', () => { - const result = computeMorphRelationFieldName({ + const result = computeMorphRelationGqlFieldName({ fieldName: 'assigned', relationType: 'MANY_TO_ONE' as any, targetObjectMetadataNameSingular: 'person', @@ -13,7 +13,7 @@ describe('computeMorphRelationFieldName', () => { }); it('should return plural-based name for ONE_TO_MANY', () => { - const result = computeMorphRelationFieldName({ + const result = computeMorphRelationGqlFieldName({ fieldName: 'assigned', relationType: 'ONE_TO_MANY' as any, targetObjectMetadataNameSingular: 'person', @@ -25,7 +25,7 @@ describe('computeMorphRelationFieldName', () => { it('should throw for invalid relation type', () => { expect(() => - computeMorphRelationFieldName({ + computeMorphRelationGqlFieldName({ fieldName: 'assigned', relationType: 'INVALID' as any, targetObjectMetadataNameSingular: 'person', diff --git a/packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeRelationGqlFieldJoinColumnName.test.ts b/packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeRelationGqlFieldJoinColumnName.test.ts new file mode 100644 index 0000000000..d78cca5818 --- /dev/null +++ b/packages/twenty-shared/src/utils/fieldMetadata/__tests__/computeRelationGqlFieldJoinColumnName.test.ts @@ -0,0 +1,76 @@ +import { + computeMorphRelationGqlFieldJoinColumnName, + computeRelationGqlFieldJoinColumnName, +} from '@/utils/fieldMetadata/compute-relation-gql-field-join-column-name'; + +describe('computeRelationGqlFieldJoinColumnName', () => { + it('should append `Id` to a simple field name', () => { + expect(computeRelationGqlFieldJoinColumnName({ name: 'company' })).toBe( + 'companyId', + ); + }); + + it('should preserve camelCase field names', () => { + expect( + computeRelationGqlFieldJoinColumnName({ name: 'pointOfContact' }), + ).toBe('pointOfContactId'); + }); + + it('should append `Id` to a morph-aware field name', () => { + expect( + computeRelationGqlFieldJoinColumnName({ name: 'targetOpportunity' }), + ).toBe('targetOpportunityId'); + }); + + it('should not strip an existing trailing `Id` (it just appends)', () => { + expect(computeRelationGqlFieldJoinColumnName({ name: 'companyId' })).toBe( + 'companyIdId', + ); + }); +}); + +describe('computeMorphRelationGqlFieldJoinColumnName', () => { + it('should combine field name and capitalized singular target for MANY_TO_ONE', () => { + expect( + computeMorphRelationGqlFieldJoinColumnName({ + fieldName: 'target', + relationType: 'MANY_TO_ONE' as any, + targetObjectMetadataNameSingular: 'opportunity', + targetObjectMetadataNamePlural: 'opportunities', + }), + ).toBe('targetOpportunityId'); + }); + + it('should combine field name and capitalized plural target for ONE_TO_MANY', () => { + expect( + computeMorphRelationGqlFieldJoinColumnName({ + fieldName: 'caretaker', + relationType: 'ONE_TO_MANY' as any, + targetObjectMetadataNameSingular: 'person', + targetObjectMetadataNamePlural: 'people', + }), + ).toBe('caretakerPeopleId'); + }); + + it('should handle simple plurals (e.g. `companies`)', () => { + expect( + computeMorphRelationGqlFieldJoinColumnName({ + fieldName: 'parent', + relationType: 'ONE_TO_MANY' as any, + targetObjectMetadataNameSingular: 'company', + targetObjectMetadataNamePlural: 'companies', + }), + ).toBe('parentCompaniesId'); + }); + + it('should throw on an invalid relation type', () => { + expect(() => + computeMorphRelationGqlFieldJoinColumnName({ + fieldName: 'target', + relationType: 'INVALID' as any, + targetObjectMetadataNameSingular: 'opportunity', + targetObjectMetadataNamePlural: 'opportunities', + }), + ).toThrow(); + }); +}); diff --git a/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-field-name.ts b/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-gql-field-name.ts similarity index 77% rename from packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-field-name.ts rename to packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-gql-field-name.ts index 59ddf3acb5..0d16c9a316 100644 --- a/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-field-name.ts +++ b/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-gql-field-name.ts @@ -6,19 +6,19 @@ enum RelationType { ONE_TO_MANY = 'ONE_TO_MANY', } -type ComputeMorphRelationFieldNameArgs = { +type ComputeMorphRelationGqlFieldNameArgs = { fieldName: string; relationType: RelationType; targetObjectMetadataNameSingular: string; targetObjectMetadataNamePlural: string; }; -export const computeMorphRelationFieldName = ({ +export const computeMorphRelationGqlFieldName = ({ fieldName, relationType, targetObjectMetadataNameSingular: nameSingular, targetObjectMetadataNamePlural: namePlural, -}: ComputeMorphRelationFieldNameArgs): string => { +}: ComputeMorphRelationGqlFieldNameArgs): string => { if (relationType === RelationType.MANY_TO_ONE) { return `${fieldName}${capitalize(nameSingular)}`; } @@ -29,6 +29,6 @@ export const computeMorphRelationFieldName = ({ throw new CustomError( `Invalid relation type (${relationType}) for field ${fieldName} on ${nameSingular}`, - 'INVALID_RELATION_TYPE_FOR_COMPUTE_MORPH_RELATION_FIELD_NAME', + 'INVALID_RELATION_TYPE_FOR_COMPUTE_MORPH_RELATION_GQL_FIELD_NAME', ); }; diff --git a/packages/twenty-shared/src/utils/fieldMetadata/compute-relation-gql-field-join-column-name.ts b/packages/twenty-shared/src/utils/fieldMetadata/compute-relation-gql-field-join-column-name.ts new file mode 100644 index 0000000000..cb0da8d137 --- /dev/null +++ b/packages/twenty-shared/src/utils/fieldMetadata/compute-relation-gql-field-join-column-name.ts @@ -0,0 +1,39 @@ +import { computeMorphRelationGqlFieldName } from '@/utils/fieldMetadata/compute-morph-relation-gql-field-name'; + +enum RelationType { + MANY_TO_ONE = 'MANY_TO_ONE', + ONE_TO_MANY = 'ONE_TO_MANY', +} + +type ComputeRelationGqlFieldJoinColumnNameArgs = { + name: string; +}; + +export const computeRelationGqlFieldJoinColumnName = ({ + name, +}: ComputeRelationGqlFieldJoinColumnNameArgs): string => { + return `${name}Id`; +}; + +type ComputeMorphRelationGqlFieldJoinColumnNameArgs = { + fieldName: string; + relationType: RelationType; + targetObjectMetadataNameSingular: string; + targetObjectMetadataNamePlural: string; +}; + +export const computeMorphRelationGqlFieldJoinColumnName = ({ + fieldName, + relationType, + targetObjectMetadataNameSingular, + targetObjectMetadataNamePlural, +}: ComputeMorphRelationGqlFieldJoinColumnNameArgs): string => { + const morphGqlFieldName = computeMorphRelationGqlFieldName({ + fieldName, + relationType, + targetObjectMetadataNameSingular, + targetObjectMetadataNamePlural, + }); + + return computeRelationGqlFieldJoinColumnName({ name: morphGqlFieldName }); +}; diff --git a/packages/twenty-shared/src/utils/index.ts b/packages/twenty-shared/src/utils/index.ts index 7838da40f4..cd16ebc16c 100644 --- a/packages/twenty-shared/src/utils/index.ts +++ b/packages/twenty-shared/src/utils/index.ts @@ -44,7 +44,11 @@ export { deepMerge } from './deepMerge'; export { CustomError } from './errors/CustomError'; export { evalFromContext } from './evalFromContext'; export { extractAndSanitizeObjectStringFields } from './extractAndSanitizeObjectStringFields'; -export { computeMorphRelationFieldName } from './fieldMetadata/compute-morph-relation-field-name'; +export { computeMorphRelationGqlFieldName } from './fieldMetadata/compute-morph-relation-gql-field-name'; +export { + computeRelationGqlFieldJoinColumnName, + computeMorphRelationGqlFieldJoinColumnName, +} from './fieldMetadata/compute-relation-gql-field-join-column-name'; export { isFieldMetadataArrayKind } from './fieldMetadata/isFieldMetadataArrayKind'; export { isFieldMetadataDateKind } from './fieldMetadata/isFieldMetadataDateKind'; export { isFieldMetadataEligibleForFieldsWidget } from './fieldMetadata/isFieldMetadataEligibleForFieldsWidget';