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 4060cd8c55..80a83bb336 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapFieldMetadataToGraphQLQuery.ts @@ -6,11 +6,7 @@ import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObject import { type RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields'; import { isNonCompositeField } from '@/object-record/object-filter-dropdown/utils/isNonCompositeField'; import { type ObjectPermissions } from 'twenty-shared/types'; -import { - computeMorphRelationFieldJoinColumnName, - computeMorphRelationFieldName, - isDefined, -} from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { type FieldMetadataItem } from '../types/FieldMetadataItem'; type MapFieldMetadataToGraphQLQueryArgs = { @@ -44,6 +40,8 @@ export const mapFieldMetadataToGraphQLQuery = ({ return gqlField; } + // We could factorize morph relation fields mapping to be passing through the RELATION handler too as now they share + // the same name and join column name logic if ( fieldType === FieldMetadataType.MORPH_RELATION && (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY || @@ -51,12 +49,7 @@ export const mapFieldMetadataToGraphQLQuery = ({ ) { let gqlMorphField = ''; for (const morphRelation of fieldMetadata.morphRelations ?? []) { - const relationFieldName = computeMorphRelationFieldName({ - fieldName: fieldMetadata.name, - relationDirection: fieldMetadata.settings?.relationType, - nameSingular: morphRelation.targetObjectMetadata.nameSingular, - namePlural: morphRelation.targetObjectMetadata.namePlural, - }); + const relationFieldName = morphRelation.sourceFieldMetadata.name; const relationMetadataItem = objectMetadataItems.find( (objectMetadataItem) => objectMetadataItem.id === morphRelation.targetObjectMetadata.id, @@ -103,12 +96,7 @@ export const mapFieldMetadataToGraphQLQuery = ({ } if (fieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE) { - const joinColumnName = computeMorphRelationFieldJoinColumnName({ - name: fieldMetadata.name, - targetObjectMetadataNameSingular: - morphRelation.targetObjectMetadata.nameSingular, - }); - if (gqlField === joinColumnName) { + if (gqlField === fieldMetadata.settings?.joinColumnName) { gqlMorphField += `${gqlField} `; continue; 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 32faf894d0..cd9ac793a7 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts @@ -5,7 +5,7 @@ import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueri import { type RecordGqlFields } from '@/object-record/graphql/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 { isDefined } from 'twenty-shared/utils'; type MapObjectMetadataToGraphQLQueryArgs = { objectMetadataItems: ObjectMetadataItem[]; @@ -54,29 +54,25 @@ export const mapObjectMetadataToGraphQLQuery = ({ .filter((field) => isDefined(field.settings?.joinColumnName)); const manyToOneRelationGqlFieldWithFieldMetadata = - manyToOneRelationFields.flatMap((field) => { - const isMorphRelation = field.type === FieldMetadataType.MORPH_RELATION; + manyToOneRelationFields.flatMap((fieldMetadata) => { + const isMorphRelation = + fieldMetadata.type === FieldMetadataType.MORPH_RELATION; if (!isMorphRelation) { return { - gqlField: field.settings?.joinColumnName, - fieldMetadata: field, + gqlField: fieldMetadata.settings?.joinColumnName, + fieldMetadata: fieldMetadata, }; } - if (!isDefined(field.morphRelations)) { + if (!isDefined(fieldMetadata.morphRelations)) { throw new Error( - `Field ${field.name} is missing, please refresh the page. If the problem persists, please contact support.`, + `Field ${fieldMetadata.name} is missing, please refresh the page. If the problem persists, please contact support.`, ); } - return field.morphRelations.map((morphRelation) => ({ - gqlField: computeMorphRelationFieldName({ - fieldName: field.name, - relationDirection: morphRelation.type, - nameSingular: morphRelation.targetObjectMetadata.nameSingular, - namePlural: morphRelation.targetObjectMetadata.namePlural, - }), - fieldMetadata: field, + return fieldMetadata.morphRelations.map((morphRelation) => ({ + gqlField: morphRelation.sourceFieldMetadata.name, + fieldMetadata: fieldMetadata, })); }); @@ -103,12 +99,7 @@ export const mapObjectMetadataToGraphQLQuery = ({ } return fieldMetadata.morphRelations.map((morphRelation) => ({ - gqlField: computeMorphRelationFieldName({ - fieldName: fieldMetadata.name, - relationDirection: morphRelation.type, - nameSingular: morphRelation.targetObjectMetadata.nameSingular, - namePlural: morphRelation.targetObjectMetadata.namePlural, - }), + gqlField: morphRelation.sourceFieldMetadata.name, fieldMetadata, })); }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useAttachMorphRelatedRecordFromRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useAttachMorphRelatedRecordFromRecord.ts index 02df02bb21..ec18f90264 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useAttachMorphRelatedRecordFromRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useAttachMorphRelatedRecordFromRecord.ts @@ -12,7 +12,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useContext } from 'react'; import { useRecoilCallback } from 'recoil'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; export const useAttachMorphRelatedRecordFromRecord = () => { const apolloCoreClient = useApolloCoreClient(); @@ -61,14 +61,8 @@ export const useAttachMorphRelatedRecordFromRecord = () => { if (isDefined(parentRecord)) { relatedObjectMetadataItems.forEach((relatedObjectMetadataItem) => { - const computedFieldName = computeMorphRelationFieldName({ - fieldName: fieldDefinition.metadata.fieldName, - relationDirection: fieldDefinition.metadata.relationType, - nameSingular: relatedObjectMetadataItem.nameSingular, - namePlural: relatedObjectMetadataItem.namePlural, - }); - - const currentMorphFieldValue = parentRecord[computedFieldName]; + const currentMorphFieldValue = + parentRecord[fieldDefinition.metadata.fieldName]; const objectRecordFromCache = getRecordFromCache({ objectMetadataItem: relatedObjectMetadataItem, @@ -84,7 +78,7 @@ export const useAttachMorphRelatedRecordFromRecord = () => { set(recordStoreFamilyState(recordId), { ...parentRecord, - [computedFieldName]: [ + [fieldDefinition.metadata.fieldName]: [ ...currentMorphFieldValue, objectRecordFromCache, ], diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDetachMorphRelatedRecordFromRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useDetachMorphRelatedRecordFromRecord.ts index 50307e3b1e..fca0b20b18 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDetachMorphRelatedRecordFromRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDetachMorphRelatedRecordFromRecord.ts @@ -13,7 +13,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useContext } from 'react'; import { useRecoilCallback } from 'recoil'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; export const useDetachMorphRelatedRecordFromRecord = () => { const apolloCoreClient = useApolloCoreClient(); @@ -61,15 +61,8 @@ export const useDetachMorphRelatedRecordFromRecord = () => { if (isDefined(parentRecord)) { relatedObjectMetadataItems.forEach((relatedObjectMetadataItem) => { - const computedFieldName = computeMorphRelationFieldName({ - fieldName: fieldDefinition.metadata.fieldName, - relationDirection: fieldDefinition.metadata.relationType, - nameSingular: relatedObjectMetadataItem.nameSingular, - namePlural: relatedObjectMetadataItem.namePlural, - }); - const currentMorphFieldValue = parentRecord[ - computedFieldName + fieldDefinition.metadata.fieldName ] as ObjectRecord[]; const objectRecordFromCache = getRecordFromCache({ @@ -87,9 +80,10 @@ export const useDetachMorphRelatedRecordFromRecord = () => { if (Array.isArray(currentMorphFieldValue)) { set(recordStoreFamilyState(recordId), { ...parentRecord, - [computedFieldName]: currentMorphFieldValue.filter( - (record) => record.id !== relatedRecordId, - ), + [fieldDefinition.metadata.fieldName]: + currentMorphFieldValue.filter( + (record) => record.id !== relatedRecordId, + ), }); } }); diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts index fcc85e3b8e..a396b04b5f 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts @@ -7,7 +7,7 @@ import { visibleRecordFieldsComponentSelector } from '@/object-record/record-fie import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { FieldMetadataType } from 'twenty-shared/types'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; export const useRecordTableRecordGqlFields = ({ objectMetadataItem, @@ -58,12 +58,7 @@ export const useRecordTableRecordGqlFields = ({ } return fieldMetadataItem.morphRelations.map((morphRelation) => [ - computeMorphRelationFieldName({ - fieldName: fieldMetadataItem.name, - relationDirection: morphRelation.type, - nameSingular: morphRelation.targetObjectMetadata.nameSingular, - namePlural: morphRelation.targetObjectMetadata.namePlural, - }), + morphRelation.sourceFieldMetadata.name, true, ]); }), diff --git a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphManyToOneValueWithObjectNameFamilySelector.ts b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphManyToOneValueWithObjectNameFamilySelector.ts index a65303f5f5..af89493aaa 100644 --- a/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphManyToOneValueWithObjectNameFamilySelector.ts +++ b/packages/twenty-front/src/modules/object-record/record-store/states/selectors/recordStoreMorphManyToOneValueWithObjectNameFamilySelector.ts @@ -3,8 +3,7 @@ import { selectorFamily } from 'recoil'; import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { RelationType } from 'twenty-shared/types'; -import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; export const recordStoreMorphManyToOneValueWithObjectNameFamilySelector = selectorFamily({ @@ -22,17 +21,11 @@ export const recordStoreMorphManyToOneValueWithObjectNameFamilySelector = ({ get }) => { const morphValuesWithObjectName = morphRelations.map( (morphRelation) => { - const computedFieldName = computeMorphRelationFieldName({ - fieldName, - relationDirection: RelationType.MANY_TO_ONE, - nameSingular: morphRelation.targetObjectMetadata.nameSingular, - namePlural: morphRelation.targetObjectMetadata.namePlural, - }); return { objectNameSingular: morphRelation.targetObjectMetadata.nameSingular, value: get(recordStoreFamilyState(recordId))?.[ - computedFieldName + fieldName ] as ObjectRecord, }; }, 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 101b1c453e..5d746e095f 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,8 +3,6 @@ import { selectorFamily } from 'recoil'; import { type FieldMetadataItemRelation } from '@/object-metadata/types/FieldMetadataItemRelation'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { RelationType } from 'twenty-shared/types'; -import { computeMorphRelationFieldName } from 'twenty-shared/utils'; export const recordStoreMorphOneToManyValueWithObjectNameFamilySelector = selectorFamily({ @@ -22,18 +20,11 @@ export const recordStoreMorphOneToManyValueWithObjectNameFamilySelector = ({ get }) => { const morphValuesWithObjectName = morphRelations.map( (morphRelation) => { - const computedFieldName = computeMorphRelationFieldName({ - fieldName, - relationDirection: RelationType.ONE_TO_MANY, - nameSingular: morphRelation.targetObjectMetadata.nameSingular, - namePlural: morphRelation.targetObjectMetadata.namePlural, - }); return { objectNameSingular: morphRelation.targetObjectMetadata.nameSingular, - value: (get(recordStoreFamilyState(recordId))?.[ - computedFieldName - ] || []) as ObjectRecord[], + value: (get(recordStoreFamilyState(recordId))?.[fieldName] || + []) as ObjectRecord[], }; }, ); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/SettingsDataModelRelationFieldPreview.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/SettingsDataModelRelationFieldPreview.tsx index 7a1926474e..ee4cfe1d25 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/SettingsDataModelRelationFieldPreview.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/components/SettingsDataModelRelationFieldPreview.tsx @@ -9,7 +9,6 @@ import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldCont import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; import { SettingsDataModelSetFieldValueEffect } from '@/settings/data-model/fields/preview/components/SettingsDataModelSetFieldValueEffect'; import { useFieldPreviewValue } from '@/settings/data-model/fields/preview/hooks/useFieldPreviewValue'; -import { computeMorphRelationFieldName } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; import { v4 } from 'uuid'; import { FieldMetadataType } from '~/generated-metadata/graphql'; @@ -118,16 +117,7 @@ export const SettingsDataModelRelationFieldPreview = ({ > diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1756976545860-unique-field-metadata-name-for-workspace-object-metadata.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1756976545860-unique-field-metadata-name-for-workspace-object-metadata.ts new file mode 100644 index 0000000000..602e4b054e --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1756976545860-unique-field-metadata-name-for-workspace-object-metadata.ts @@ -0,0 +1,19 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class UniqueFieldMetadataNameForWorkspaceObjectMetadata1756976545860 + implements MigrationInterface +{ + name = 'UniqueFieldMetadataNameForWorkspaceObjectMetadata1756976545860'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" ADD CONSTRAINT "IDX_FIELD_METADATA_NAME_OBJECT_METADATA_ID_WORKSPACE_ID_UNIQUE" UNIQUE ("name", "objectMetadataId", "workspaceId")`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."fieldMetadata" DROP CONSTRAINT "IDX_FIELD_METADATA_NAME_OBJECT_METADATA_ID_WORKSPACE_ID_UNIQUE"`, + ); + } +} 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 480ab83907..79de6f9985 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 @@ -5,7 +5,6 @@ import { GraphqlQuerySelectedFieldsAggregateParser } from 'src/engine/api/graphq import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; @@ -134,14 +133,8 @@ export class GraphqlQuerySelectedFieldsParser { accumulator.select[joinColumnName] = true; } - const morphRelationFieldName = computeMorphRelationFieldName({ - fieldName: fieldMetadata.name, - relationDirection: fieldMetadata.settings.relationType, - targetObjectMetadata, - }); - const graphqlSelectedFieldValue = - graphqlSelectedFields[morphRelationFieldName]; + graphqlSelectedFields[fieldMetadata.name]; if (!isDefined(graphqlSelectedFieldValue)) { continue; @@ -149,7 +142,7 @@ export class GraphqlQuerySelectedFieldsParser { this.graphqlQuerySelectedFieldsRelationParser.parseRelationField( fieldMetadata, - morphRelationFieldName, + fieldMetadata.name, graphqlSelectedFieldValue, accumulator, ); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts index baaee0b5f8..91cbd87fef 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts @@ -17,7 +17,6 @@ import { getTargetObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-q import { type AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { isFieldMetadataTypeMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-morph-relation.util'; import { isFieldMetadataTypeRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-relation.util'; @@ -255,52 +254,44 @@ export class ObjectRecordsToGraphqlConnectionHelper { continue; } - const morphRelationFieldName = computeMorphRelationFieldName({ - fieldName: fieldMetadata.name, - relationDirection: fieldMetadata.settings.relationType, - targetObjectMetadata, - }); - - const fieldMetadataNameWithId = `${morphRelationFieldName}Id`; + const fieldMetadataNameWithId = `${fieldMetadata.name}Id`; if (isDefined(objectRecord[fieldMetadataNameWithId])) { processedObjectRecord[fieldMetadataNameWithId] = objectRecord[fieldMetadataNameWithId]; } - const objectValue = objectRecord[morphRelationFieldName]; + const objectValue = objectRecord[fieldMetadata.name]; if (!isDefined(objectValue)) { continue; } if (Array.isArray(objectValue)) { - processedObjectRecord[morphRelationFieldName] = this.createConnection( - { - objectRecords: objectValue, - parentObjectRecord: objectRecord, - objectRecordsAggregatedValues: - objectRecordsAggregatedValues[fieldMetadata.name], - selectedAggregatedFields: - selectedAggregatedFields[fieldMetadata.name], - objectName: targetObjectMetadata.nameSingular, - take, - totalCount: - objectRecordsAggregatedValues[fieldMetadata.name]?.totalCount ?? - objectValue.length, - order, - hasNextPage: false, - hasPreviousPage: false, - depth: depth + 1, - }, - ); + processedObjectRecord[fieldMetadata.name] = this.createConnection({ + objectRecords: objectValue, + parentObjectRecord: objectRecord, + objectRecordsAggregatedValues: + objectRecordsAggregatedValues[fieldMetadata.name], + selectedAggregatedFields: + selectedAggregatedFields[fieldMetadata.name], + objectName: targetObjectMetadata.nameSingular, + take, + totalCount: + objectRecordsAggregatedValues[fieldMetadata.name]?.totalCount ?? + objectValue.length, + order, + hasNextPage: false, + hasPreviousPage: false, + depth: depth + 1, + }); } else if (isPlainObject(objectValue)) { const targetObjectMetadata = getTargetObjectMetadataOrThrow( fieldMetadata, this.objectMetadataMaps, ); - processedObjectRecord[morphRelationFieldName] = this.processRecord({ + processedObjectRecord[fieldMetadata.name] = this.processRecord({ objectRecord: objectValue, objectRecordsAggregatedValues: objectRecordsAggregatedValues[fieldMetadata.name], 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 0669f3c253..ad025dcd7e 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 @@ -2,11 +2,9 @@ import { isDefined } from 'twenty-shared/utils'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util'; import { isFieldMetadataTypeMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-morph-relation.util'; import { isFieldMetadataTypeRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-relation.util'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; - export const buildColumnsToSelect = ({ select, relations, @@ -84,13 +82,7 @@ const getRequiredRelationColumns = ( continue; } - const morphRelationFieldName = computeMorphRelationFieldName({ - fieldName: fieldMetadata.name, - relationDirection: fieldMetadata.settings.relationType, - targetObjectMetadata, - }); - - const relationValue = relations[morphRelationFieldName]; + const relationValue = relations[fieldMetadata.name]; if ( !isDefined(relationValue) || diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-field-metadata-from-graphql-field.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-field-metadata-from-graphql-field.util.ts index b7827ccff4..48819f701e 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-field-metadata-from-graphql-field.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/get-field-metadata-from-graphql-field.util.ts @@ -3,7 +3,6 @@ import { isDefined } from 'twenty-shared/utils'; import { getTargetObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-target-object-metadata.util'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util'; import { isFieldMetadataTypeMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-morph-relation.util'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; @@ -58,14 +57,8 @@ export function getFieldMetadataFromGraphQLField({ return; } - const name = computeMorphRelationFieldName({ - fieldName: morphRelation.fieldMetadata.name, - relationDirection: morphRelation.fieldMetadata.settings.relationType, - targetObjectMetadata: morphRelation.targetObjectMetadata, - }); - possibleGraphQLFieldNames.push({ - graphQLField: name, + graphQLField: morphRelation.fieldMetadata.name, fieldMetadata: morphRelation.fieldMetadata, targetObjectMetadata: morphRelation.targetObjectMetadata, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory.ts index dc6e2aa339..af10041623 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory.ts @@ -14,7 +14,6 @@ import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-b import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage'; import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util'; import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field'; -import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-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'; @@ -169,16 +168,7 @@ export class ExtendObjectTypeDefinitionV2Factory { ); } - const fieldName = - fieldMetadata.type === FieldMetadataType.MORPH_RELATION - ? computeMorphRelationFieldName({ - fieldName: fieldMetadata.name, - relationDirection: fieldMetadata.settings.relationType, - targetObjectMetadata: objectMetadataTarget, - }) - : fieldMetadata.name; - - fields[fieldName] = { + fields[fieldMetadata.name] = { type: relationType, args: argsType, description: fieldMetadata.description, diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts index 231c3c2c05..3f9c3acde2 100644 --- a/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts @@ -9,31 +9,17 @@ import { type RelationLoaderPayload, } from 'src/engine/dataloaders/dataloader.service'; import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; -import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { type RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto'; import { type IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto'; import { type IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto'; -import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; export interface IDataloaders { - relationLoader: DataLoader< - RelationLoaderPayload, - { - sourceObjectMetadata: ObjectMetadataEntity; - targetObjectMetadata: ObjectMetadataEntity; - sourceFieldMetadata: FieldMetadataEntity; - targetFieldMetadata: FieldMetadataEntity; - } - >; + relationLoader: DataLoader; morphRelationLoader: DataLoader< MorphRelationLoaderPayload, - { - sourceObjectMetadata: ObjectMetadataEntity; - targetObjectMetadata: ObjectMetadataEntity; - sourceFieldMetadata: FieldMetadataEntity; - targetFieldMetadata: FieldMetadataEntity; - }[] + RelationDTO[] | null >; fieldMetadataLoader: DataLoader< diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts index e5a79b62a5..cee30ba4e2 100644 --- a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts @@ -2,19 +2,24 @@ import { Injectable } from '@nestjs/common'; import DataLoader from 'dataloader'; import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations'; +import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type IndexMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-metadata.interface'; import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; -import { filterMorphRelationDuplicateFieldsDTO } from 'src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util'; +import { filterMorphRelationDuplicateFields } from 'src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util'; import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; +import { RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FieldMetadataMorphRelationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-morph-relation.service'; -import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service'; import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util'; import { resolveFieldMetadataStandardOverride } from 'src/engine/metadata-modules/field-metadata/utils/resolve-field-metadata-standard-override.util'; +import { findAllOthersMorphRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-all-others-morph-relation-flat-field-metadatas-or-throw.util'; +import { fromMorphOrRelationFlatFieldMetadataToRelationDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-morph-or-relation-flat-field-metadata-to-relation-dto.util'; +import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; +import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; +import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; import { type IndexFieldMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-field-metadata.dto'; import { type IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -28,27 +33,14 @@ export type RelationMetadataLoaderPayload = { export type RelationLoaderPayload = { workspaceId: string; - fieldMetadata: Pick< - FieldMetadataEntity, - | 'type' - | 'id' - | 'objectMetadataId' - | 'relationTargetFieldMetadataId' - | 'relationTargetObjectMetadataId' - >; + fieldMetadataId: string; + objectMetadataId: string; }; export type MorphRelationLoaderPayload = { workspaceId: string; - fieldMetadata: Pick< - FieldMetadataEntity, - | 'type' - | 'id' - | 'objectMetadataId' - | 'relationTargetFieldMetadataId' - | 'relationTargetObjectMetadataId' - | 'name' - >; + fieldMetadataId: string; + objectMetadataId: string; }; export type FieldMetadataLoaderPayload = { @@ -77,8 +69,6 @@ export type ObjectMetadataLoaderPayload = { export class DataloaderService { constructor( private readonly i18nService: I18nService, - private readonly fieldMetadataRelationService: FieldMetadataRelationService, - private readonly fieldMetadataMorphRelationService: FieldMetadataMorphRelationService, private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, ) {} @@ -101,60 +91,139 @@ export class DataloaderService { } private createRelationLoader() { - return new DataLoader< - RelationLoaderPayload, - { - sourceObjectMetadata: ObjectMetadataEntity; - targetObjectMetadata: ObjectMetadataEntity; - sourceFieldMetadata: FieldMetadataEntity; - targetFieldMetadata: FieldMetadataEntity; - } - >(async (dataLoaderParams: RelationLoaderPayload[]) => { - const workspaceId = dataLoaderParams[0].workspaceId; - const fieldMetadataItems = dataLoaderParams.map( - (dataLoaderParam) => dataLoaderParam.fieldMetadata, - ); + return new DataLoader( + async (dataLoaderParams: RelationLoaderPayload[]) => { + const relationDtos: Array = []; + const workspaceId = dataLoaderParams[0].workspaceId; + const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = + await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + { + workspaceId, + }, + ); - const fieldMetadataRelationCollection = - await this.fieldMetadataRelationService.findCachedFieldMetadataRelation( - fieldMetadataItems, - workspaceId, - ); + for (const { fieldMetadataId, objectMetadataId } of dataLoaderParams) { + const sourceFlatFieldMetadata = + findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ + fieldMetadataId, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataId, + }); - return fieldMetadataRelationCollection; - }); + if ( + !isFlatFieldMetadataOfType( + sourceFlatFieldMetadata, + FieldMetadataType.RELATION, + ) + ) { + relationDtos.push(null); + continue; + } + + const sourceFlatObjectMetadata = + findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataId: sourceFlatFieldMetadata.objectMetadataId, + }); + + if ( + isFlatFieldMetadataOfType( + sourceFlatFieldMetadata.flatRelationTargetFieldMetadata, + FieldMetadataType.MORPH_RELATION, + ) + ) { + const allMorphFlatFieldMetadatas = + findAllOthersMorphRelationFlatFieldMetadatasOrThrow({ + flatFieldMetadata: + sourceFlatFieldMetadata.flatRelationTargetFieldMetadata, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + }).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); + + relationDtos.push( + fromMorphOrRelationFlatFieldMetadataToRelationDto({ + flatFieldMetadata: { + ...sourceFlatFieldMetadata, + flatRelationTargetFieldMetadata: + allMorphFlatFieldMetadatas[0], + }, + sourceFlatObjectMetadata, + }), + ); + continue; + } + + relationDtos.push( + fromMorphOrRelationFlatFieldMetadataToRelationDto({ + flatFieldMetadata: sourceFlatFieldMetadata, + sourceFlatObjectMetadata, + }), + ); + } + + return relationDtos; + }, + ); } private createMorphRelationLoader() { - return new DataLoader< - MorphRelationLoaderPayload, - { - sourceObjectMetadata: ObjectMetadataEntity; - targetObjectMetadata: ObjectMetadataEntity; - sourceFieldMetadata: FieldMetadataEntity; - targetFieldMetadata: FieldMetadataEntity; - }[] - >(async (dataLoaderParams: MorphRelationLoaderPayload[]) => { - const workspaceId = dataLoaderParams[0].workspaceId; + return new DataLoader( + async (dataLoaderParams: MorphRelationLoaderPayload[]) => { + const workspaceId = dataLoaderParams[0].workspaceId; + const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = + await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + { + workspaceId, + }, + ); + const relationDtos: Array = []; - const fieldMetadataItems = dataLoaderParams.map( - (dataLoaderParam) => dataLoaderParam.fieldMetadata, - ); + for (const { fieldMetadataId, objectMetadataId } of dataLoaderParams) { + const morphFlatFieldMetadata = + findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ + fieldMetadataId, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataId, + }); - const fieldMetadataMorphRelationCollection = - await this.fieldMetadataMorphRelationService.findCachedFieldMetadataMorphRelation( - fieldMetadataItems, - workspaceId, - ); + if ( + !isFlatFieldMetadataOfType( + morphFlatFieldMetadata, + FieldMetadataType.MORPH_RELATION, + ) + ) { + relationDtos.push(null); + continue; + } - return fieldMetadataItems.map((fieldMetadataItem) => { - return fieldMetadataMorphRelationCollection.filter( - (fieldMetadataMorphRelation) => - fieldMetadataItem.name === - fieldMetadataMorphRelation.sourceFieldMetadata.name, - ); - }); - }); + const sourceFlatObjectMetadata = + findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + objectMetadataId: morphFlatFieldMetadata.objectMetadataId, + }); + + const relatedMorphFlatFieldMetadatas = + findAllOthersMorphRelationFlatFieldMetadatasOrThrow({ + flatFieldMetadata: morphFlatFieldMetadata, + flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + }); + const allMorphFlatFieldMetadatas = [ + morphFlatFieldMetadata, + ...relatedMorphFlatFieldMetadatas, + ]; + + relationDtos.push( + allMorphFlatFieldMetadatas.map((flatFieldMetadata) => + fromMorphOrRelationFlatFieldMetadataToRelationDto({ + flatFieldMetadata, + sourceFlatObjectMetadata, + }), + ), + ); + } + + return relationDtos; + }, + ); } private createIndexMetadataLoader() { @@ -221,9 +290,9 @@ export class DataloaderService { return []; } - const fields = Object.values( + const overriddenFieldMetadataEntities = Object.values( objectMetadata.fieldsById, - ).map((fieldMetadata) => { + ).map((fieldMetadata) => { const overridesFieldToCompute = [ 'icon', 'label', @@ -252,14 +321,19 @@ export class DataloaderService { {}, ); - return fromFieldMetadataEntityToFieldMetadataDto({ + return { ...fieldMetadata, ...overrides, - }); + }; }); - return filterMorphRelationDuplicateFieldsDTO( - fields, + const filteredFieldMetadataEntities = + filterMorphRelationDuplicateFields( + overriddenFieldMetadataEntities, + ); + + return filteredFieldMetadataEntities.map( + fromFieldMetadataEntityToFieldMetadataDto, ); }); diff --git a/packages/twenty-server/src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util.ts b/packages/twenty-server/src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util.ts index e300ac6296..b07275b4d6 100644 --- a/packages/twenty-server/src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util.ts +++ b/packages/twenty-server/src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util.ts @@ -1,13 +1,13 @@ -export const filterMorphRelationDuplicateFieldsDTO = < - T extends { name: string; id: string }, +export const filterMorphRelationDuplicateFields = < + T extends { createdAt: Date; morphId: string | null }, >( fields: T[], ) => { return fields.filter((currentField) => { return !fields.some( (otherField) => - otherField.name === currentField.name && - otherField.id > currentField.id, + otherField.morphId === currentField.morphId && + otherField.createdAt.getTime() > currentField.createdAt.getTime(), ); }); }; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts index 9257450de0..b31dc9ffa9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts @@ -148,7 +148,10 @@ export class FieldMetadataDTO { @Field({ nullable: true }) isLabelSyncedWithName?: boolean; - @IsDateString() + @IsDateString(undefined, { + message: ({ value }) => + `Field metadata created at is invalid got ${JSON.stringify(value)} isDate: ${value instanceof Date}`, + }) @Field() createdAt: Date; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts index 17f771800d..edcb4ccc2e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts @@ -11,6 +11,7 @@ import { OneToOne, PrimaryGeneratedColumn, Relation, + Unique, UpdateDateColumn, } from 'typeorm'; @@ -36,6 +37,11 @@ import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permis @Index('IDX_FIELD_METADATA_RELATION_TARGET_OBJECT_METADATA_ID', [ 'relationTargetObjectMetadataId', ]) +@Unique('IDX_FIELD_METADATA_NAME_OBJECT_METADATA_ID_WORKSPACE_ID_UNIQUE', [ + 'name', + 'objectMetadataId', + 'workspaceId', +]) @Index('IDX_FIELD_METADATA_OBJECT_METADATA_ID_WORKSPACE_ID', [ 'objectMetadataId', 'workspaceId', diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts index 81a8e65595..4cc5b93517 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts @@ -8,7 +8,6 @@ import { Resolver, } from '@nestjs/graphql'; -import { type FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -33,21 +32,12 @@ import { UpdateOneFieldMetadataInput, type UpdateFieldInput, } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input'; -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { - FieldMetadataException, - FieldMetadataExceptionCode, -} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { BeforeUpdateOneField } from 'src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook'; import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service'; import { FieldMetadataServiceV2 } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2'; import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util'; -import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util'; -import { fromObjectMetadataEntityToObjectMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util'; import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; -import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util'; -import { isMorphRelationFieldMetadataType } from 'src/engine/utils/is-morph-relation-field-metadata-type.util'; @UseGuards(WorkspaceAuthGuard) @UsePipes(ResolverValidationPipe) @@ -76,7 +66,7 @@ export class FieldMetadataResolver { workspaceId, }); } catch (error) { - fieldMetadataGraphqlApiExceptionHandler(error); + return fieldMetadataGraphqlApiExceptionHandler(error); } } @@ -171,44 +161,22 @@ export class FieldMetadataResolver { @ResolveField(() => RelationDTO, { nullable: true }) async relation( @AuthWorkspace() workspace: Workspace, - @Parent() fieldMetadata: FieldMetadataEntity, - @Context() context: { loaders: IDataloaders }, - ): Promise { - if (!isMorphOrRelationFieldMetadataType(fieldMetadata.type)) { - return null; - } + @Parent() + { + id: fieldMetadataId, + objectMetadataId, + }: Pick, + @Context() context: { loaders: IDataloaders }, + ): Promise { try { - const { - sourceObjectMetadata, - targetObjectMetadata, - sourceFieldMetadata, - targetFieldMetadata, - } = await context.loaders.relationLoader.load({ - fieldMetadata, + return await context.loaders.relationLoader.load({ + fieldMetadataId, + objectMetadataId, workspaceId: workspace.id, }); - - if (!isDefined(fieldMetadata.settings)) { - throw new FieldMetadataException( - 'Relation settings are required', - FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, - ); - } - - return { - type: fieldMetadata.settings.relationType, - sourceObjectMetadata: - fromObjectMetadataEntityToObjectMetadataDto(sourceObjectMetadata), - targetObjectMetadata: - fromObjectMetadataEntityToObjectMetadataDto(targetObjectMetadata), - sourceFieldMetadata: - fromFieldMetadataEntityToFieldMetadataDto(sourceFieldMetadata), - targetFieldMetadata: - fromFieldMetadataEntityToFieldMetadataDto(targetFieldMetadata), - }; } catch (error) { - fieldMetadataGraphqlApiExceptionHandler(error); + return fieldMetadataGraphqlApiExceptionHandler(error); } } @@ -216,46 +184,20 @@ export class FieldMetadataResolver { async morphRelations( @AuthWorkspace() workspace: Workspace, @Parent() - fieldMetadata: FieldMetadataEntity, + { + id: fieldMetadataId, + objectMetadataId, + }: Pick, @Context() context: { loaders: IDataloaders }, - ): Promise { - if (!isMorphRelationFieldMetadataType(fieldMetadata.type)) { - return null; - } - + ): Promise { try { - const morphRelations = await context.loaders.morphRelationLoader.load({ - fieldMetadata, + return await context.loaders.morphRelationLoader.load({ + fieldMetadataId, + objectMetadataId, workspaceId: workspace.id, }); - - // typescript issue, it's not possible to use the fieldMetadata.settings directly in morphRelations.map - const settings = fieldMetadata.settings; - - if (!isDefined(settings) || !isDefined(settings.relationType)) { - throw new FieldMetadataException( - `Morph relation settings ${isDefined(settings) && 'relationType'} are required`, - FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, - ); - } - - return morphRelations.map((morphRelation) => ({ - type: settings.relationType, - sourceObjectMetadata: fromObjectMetadataEntityToObjectMetadataDto( - morphRelation.sourceObjectMetadata, - ), - targetObjectMetadata: fromObjectMetadataEntityToObjectMetadataDto( - morphRelation.targetObjectMetadata, - ), - sourceFieldMetadata: fromFieldMetadataEntityToFieldMetadataDto( - morphRelation.sourceFieldMetadata, - ), - targetFieldMetadata: fromFieldMetadataEntityToFieldMetadataDto( - morphRelation.targetFieldMetadata, - ), - })); } catch (error) { - fieldMetadataGraphqlApiExceptionHandler(error); + return fieldMetadataGraphqlApiExceptionHandler(error); } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-morph-relation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-morph-relation.service.ts index 10a4d2ae60..d38a870144 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-morph-relation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-morph-relation.service.ts @@ -2,10 +2,7 @@ import { Injectable } from '@nestjs/common'; import omit from 'lodash.omit'; import { FieldMetadataType } from 'twenty-shared/types'; -import { - computeMorphRelationFieldJoinColumnName, - isDefined, -} from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { type Repository } from 'typeorm'; import { v4 } from 'uuid'; @@ -18,20 +15,17 @@ import { FieldMetadataExceptionCode, } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service'; -import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util'; import { prepareCustomFieldMetadataForCreation } from 'src/engine/metadata-modules/field-metadata/utils/prepare-field-metadata-for-creation.util'; -import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util'; -import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; @Injectable() export class FieldMetadataMorphRelationService { constructor( private readonly fieldMetadataRelationService: FieldMetadataRelationService, - private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, ) {} async createMorphRelationFieldMetadataItems({ @@ -67,9 +61,9 @@ export class FieldMetadataMorphRelationService { const fieldsCreated: FieldMetadataEntity[] = []; const morphId = v4(); - for (const relation of morphRelationsCreationPayload) { + for (const relationCreationPayload of morphRelationsCreationPayload) { const targetObjectMetadata = - objectMetadataMaps.byId[relation.targetObjectMetadataId]; + objectMetadataMaps.byId[relationCreationPayload.targetObjectMetadataId]; if (!isDefined(targetObjectMetadata)) { throw new FieldMetadataException( @@ -78,15 +72,21 @@ export class FieldMetadataMorphRelationService { ); } + const currentMorphRelationFieldName = computeMorphRelationFieldName({ + fieldName: fieldMetadataForCreate.name, + relationType: relationCreationPayload.type, + targetObjectMetadata, + }); const relationFieldMetadataForCreate = this.fieldMetadataRelationService.computeCustomRelationFieldMetadataForCreation( { - fieldMetadataInput: fieldMetadataForCreate, - relationCreationPayload: relation, - joinColumnName: computeMorphRelationFieldJoinColumnName({ - name: fieldMetadataForCreate.name, - targetObjectMetadataNameSingular: - targetObjectMetadata.nameSingular, + fieldMetadataInput: { + ...fieldMetadataForCreate, + name: currentMorphRelationFieldName, + }, + relationCreationPayload: relationCreationPayload, + joinColumnName: computeMorphOrRelationFieldJoinColumnName({ + name: currentMorphRelationFieldName, }), }, ); @@ -106,16 +106,16 @@ export class FieldMetadataMorphRelationService { ); const targetFieldMetadataName = computeMetadataNameFromLabel( - relation.targetFieldLabel, + relationCreationPayload.targetFieldLabel, ); const targetFieldMetadataToCreate = prepareCustomFieldMetadataForCreation( { - objectMetadataId: relation.targetObjectMetadataId, + objectMetadataId: relationCreationPayload.targetObjectMetadataId, type: FieldMetadataType.RELATION, name: targetFieldMetadataName, - label: relation.targetFieldLabel, - icon: relation.targetFieldIcon, + label: relationCreationPayload.targetFieldLabel, + icon: relationCreationPayload.targetFieldIcon, workspaceId: fieldMetadataForCreate.workspaceId, settings: fieldMetadataForCreate.settings, }, @@ -130,11 +130,11 @@ export class FieldMetadataMorphRelationService { targetFieldLabel: fieldMetadataForCreate.label, targetFieldIcon: fieldMetadataForCreate.icon ?? 'Icon123', type: - relation.type === RelationType.ONE_TO_MANY + relationCreationPayload.type === RelationType.ONE_TO_MANY ? RelationType.MANY_TO_ONE : RelationType.ONE_TO_MANY, }, - joinColumnName: computeRelationFieldJoinColumnName({ + joinColumnName: computeMorphOrRelationFieldJoinColumnName({ name: targetFieldMetadataToCreate.name, }), }, @@ -163,117 +163,4 @@ export class FieldMetadataMorphRelationService { return fieldsCreated; } - - async findCachedFieldMetadataMorphRelation( - fieldMetadataItems: Array< - Pick< - FieldMetadataEntity, - | 'id' - | 'type' - | 'objectMetadataId' - | 'relationTargetFieldMetadataId' - | 'relationTargetObjectMetadataId' - | 'name' - > - >, - workspaceId: string, - ): Promise< - Array<{ - sourceObjectMetadata: ObjectMetadataEntity; - sourceFieldMetadata: FieldMetadataEntity; - targetObjectMetadata: ObjectMetadataEntity; - targetFieldMetadata: FieldMetadataEntity; - }> - > { - const objectMetadataMaps = - await this.workspaceCacheStorageService.getObjectMetadataMapsOrThrow( - workspaceId, - ); - - const fieldMetadataItemsAndMorphSiblings: Pick< - FieldMetadataEntity, - | 'id' - | 'type' - | 'objectMetadataId' - | 'relationTargetFieldMetadataId' - | 'relationTargetObjectMetadataId' - | 'name' - >[] = fieldMetadataItems.flatMap((fieldMetadataItem) => { - const fieldsById = - objectMetadataMaps.byId[fieldMetadataItem.objectMetadataId]?.fieldsById; - - if (!isDefined(fieldsById)) { - throw new FieldMetadataException( - `Fields by id not found for object metadata ${fieldMetadataItem.objectMetadataId}`, - FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, - ); - } - - return Object.values(fieldsById) - .filter( - (fieldMetadataById) => - fieldMetadataItem.name === fieldMetadataById.name, - ) - .map((fieldMetadataById) => { - return { - id: fieldMetadataById.id, - type: fieldMetadataById.type, - objectMetadataId: fieldMetadataById.objectMetadataId, - relationTargetFieldMetadataId: - fieldMetadataById.relationTargetFieldMetadataId, - relationTargetObjectMetadataId: - fieldMetadataById.relationTargetObjectMetadataId, - name: fieldMetadataById.name, - }; - }); - }); - - return fieldMetadataItemsAndMorphSiblings.map((fieldMetadataItem) => { - const { - id, - objectMetadataId, - relationTargetFieldMetadataId, - relationTargetObjectMetadataId, - } = fieldMetadataItem; - - if (!relationTargetObjectMetadataId || !relationTargetFieldMetadataId) { - throw new FieldMetadataException( - `Relation target object metadata id or relation target field metadata id not found for field metadata ${id}`, - FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, - ); - } - - const sourceObjectMetadata = objectMetadataMaps.byId[objectMetadataId]; - const targetObjectMetadata = - objectMetadataMaps.byId[relationTargetObjectMetadataId]; - const sourceFieldMetadata = sourceObjectMetadata?.fieldsById[id]; - const targetFieldMetadata = - targetObjectMetadata?.fieldsById[relationTargetFieldMetadataId]; - - if ( - !sourceObjectMetadata || - !targetObjectMetadata || - !sourceFieldMetadata || - !targetFieldMetadata - ) { - throw new FieldMetadataException( - `Field relation metadata not found for field metadata ${id}`, - FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, - ); - } - - return { - sourceObjectMetadata: - getObjectMetadataFromObjectMetadataItemWithFieldMaps( - sourceObjectMetadata, - ) as ObjectMetadataEntity, - sourceFieldMetadata: sourceFieldMetadata as FieldMetadataEntity, - targetObjectMetadata: - getObjectMetadataFromObjectMetadataItemWithFieldMaps( - targetObjectMetadata, - ) as ObjectMetadataEntity, - targetFieldMetadata: targetFieldMetadata as FieldMetadataEntity, - }; - }); - } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts index 94761c77b6..26a25f0e88 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service.ts @@ -10,7 +10,6 @@ import { v4 } from 'uuid'; import { type FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { filterMorphRelationDuplicateFieldsDTO } from 'src/engine/dataloaders/utils/filter-morph-relation-duplicate-fields.util'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -18,15 +17,12 @@ import { FieldMetadataException, FieldMetadataExceptionCode, } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; -import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util'; -import { isFieldMetadataTypeMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-morph-relation.util'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { prepareCustomFieldMetadataForCreation } from 'src/engine/metadata-modules/field-metadata/utils/prepare-field-metadata-for-creation.util'; import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload-or-throw.util'; -import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils'; import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils'; import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util'; @@ -108,7 +104,7 @@ export class FieldMetadataRelationService { ? RelationType.MANY_TO_ONE : RelationType.ONE_TO_MANY, }, - joinColumnName: computeRelationFieldJoinColumnName({ + joinColumnName: computeMorphOrRelationFieldJoinColumnName({ name: targetFieldMetadataToCreate.name, }), }); @@ -215,111 +211,6 @@ export class FieldMetadataRelationService { return fieldMetadataInput; } - async findCachedFieldMetadataRelation( - fieldMetadataItems: Array< - Pick< - FieldMetadataEntity, - | 'id' - | 'type' - | 'objectMetadataId' - | 'relationTargetFieldMetadataId' - | 'relationTargetObjectMetadataId' - > - >, - workspaceId: string, - ): Promise< - Array<{ - sourceObjectMetadata: ObjectMetadataEntity; - sourceFieldMetadata: FieldMetadataEntity; - targetObjectMetadata: ObjectMetadataEntity; - targetFieldMetadata: FieldMetadataEntity; - }> - > { - const objectMetadataMaps = - await this.workspaceCacheStorageService.getObjectMetadataMapsOrThrow( - workspaceId, - ); - - return fieldMetadataItems.map((fieldMetadataItem) => { - const { - id, - objectMetadataId, - relationTargetFieldMetadataId, - relationTargetObjectMetadataId, - } = fieldMetadataItem; - - if (!relationTargetObjectMetadataId || !relationTargetFieldMetadataId) { - throw new FieldMetadataException( - `Relation target object metadata id or relation target field metadata id not found for field metadata ${id}`, - FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, - ); - } - - const sourceObjectMetadata = objectMetadataMaps.byId[objectMetadataId]; - const targetObjectMetadata = - objectMetadataMaps.byId[relationTargetObjectMetadataId]; - const sourceFieldMetadata = sourceObjectMetadata?.fieldsById[id]; - const targetFieldMetadata = - targetObjectMetadata?.fieldsById[relationTargetFieldMetadataId]; - - if ( - !sourceObjectMetadata || - !targetObjectMetadata || - !sourceFieldMetadata || - !targetFieldMetadata - ) { - throw new FieldMetadataException( - `Field relation metadata not found for field metadata ${id}`, - FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, - ); - } - - // Morph relation specific: - // to keep the fieldMetadata API simple - // we decided to override the targetFieldMetadata with the existing one - // in case the relation target is a morph relation - const isRelationTargetMorphRelation = - isFieldMetadataTypeMorphRelation(targetFieldMetadata); - - const targetObjectMetadataFields = Object.values( - targetObjectMetadata.fieldsById, - ); - - const targetObjectMetadataWithoutDuplicates = - filterMorphRelationDuplicateFieldsDTO( - targetObjectMetadataFields, - ); - const targetFieldMetadataSelected = - targetObjectMetadataWithoutDuplicates.find( - (fieldMetadata) => fieldMetadata.name === targetFieldMetadata.name, - ); - - if (!isDefined(targetFieldMetadataSelected)) { - throw new FieldMetadataException( - `Target field metadata not found for field metadata ${id}`, - FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, - ); - } - - const targetFieldMetadataOverride = isRelationTargetMorphRelation - ? targetFieldMetadataSelected - : targetFieldMetadata; - - return { - sourceObjectMetadata: - getObjectMetadataFromObjectMetadataItemWithFieldMaps( - sourceObjectMetadata, - ) as ObjectMetadataEntity, - sourceFieldMetadata: sourceFieldMetadata as FieldMetadataEntity, - targetObjectMetadata: - getObjectMetadataFromObjectMetadataItemWithFieldMaps( - targetObjectMetadata, - ) as ObjectMetadataEntity, - targetFieldMetadata: targetFieldMetadataOverride as FieldMetadataEntity, - }; - }); - } - // TODO refactor and strictly type computeCustomRelationFieldMetadataForCreation({ fieldMetadataInput, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts index 794d745db9..4b5a5f82d0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts @@ -1,10 +1,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { - isDefined, - trimAndRemoveDuplicatedWhitespacesFromString, -} from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { In, Repository } from 'typeorm'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; @@ -39,15 +36,15 @@ export class FieldMetadataServiceV2 { ) {} async createOne({ - fieldMetadataInput, + createFieldInput, workspaceId, }: { - fieldMetadataInput: Omit; + createFieldInput: Omit; workspaceId: string; }): Promise { const [createdFieldMetadata] = await this.createMany({ - fieldMetadataInputs: [fieldMetadataInput], workspaceId, + createFieldInputs: [createFieldInput], }); if (!isDefined(createdFieldMetadata)) { @@ -206,13 +203,13 @@ export class FieldMetadataServiceV2 { } async createMany({ - fieldMetadataInputs, + createFieldInputs, workspaceId, }: { - fieldMetadataInputs: Omit[]; + createFieldInputs: Omit[]; workspaceId: string; }): Promise { - if (!fieldMetadataInputs.length) { + if (createFieldInputs.length === 0) { return []; } @@ -221,24 +218,24 @@ export class FieldMetadataServiceV2 { { workspaceId }, ); - const allInputTranspilationsResults = ( - await Promise.all( - fieldMetadataInputs.map( - async (fieldMetadataInput) => - await fromCreateFieldInputToFlatFieldMetadatasToCreate({ - existingFlatObjectMetadataMaps, - workspaceId, - rawCreateFieldInput: fieldMetadataInput, - }), - ), - ) - ).flat(); + const allTranspiledTranspilationInputs = []; + + for (const createInput of createFieldInputs) { + allTranspiledTranspilationInputs.push( + await fromCreateFieldInputToFlatFieldMetadatasToCreate({ + existingFlatObjectMetadataMaps, + workspaceId, + rawCreateFieldInput: createInput, + }), + ); + } throwOnFieldInputTranspilationsError( - allInputTranspilationsResults, + allTranspiledTranspilationInputs, 'Multiple validation errors occurred while creating field', ); - const flatFieldMetadatasToCreate = allInputTranspilationsResults.flatMap( + + const flatFieldMetadatasToCreate = allTranspiledTranspilationInputs.flatMap( ({ result }) => result, ); @@ -289,10 +286,8 @@ export class FieldMetadataServiceV2 { return this.fieldMetadataRepository.find({ where: { name: In( - fieldMetadataInputs.map((flatFieldMetadata) => - trimAndRemoveDuplicatedWhitespacesFromString( - flatFieldMetadata.name, - ), + allTranspiledTranspilationInputs.map( + ({ result: flatFieldMetadatas }) => flatFieldMetadatas[0].name, ), ), workspaceId, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts index 5e4131b117..4e576d27b1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service.ts @@ -31,7 +31,7 @@ import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/ import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service'; import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-validation.service'; import { FieldMetadataServiceV2 } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2'; -import { areFieldMetadatasTypeRelationOrMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/are-field-metadatas-type-relation-or-morph-relation.util'; +import { areFieldMetadatasOfType } from 'src/engine/metadata-modules/field-metadata/utils/are-field-metadatas-of-type.util'; import { assertDoesNotNullifyDefaultValueForNonNullableField } from 'src/engine/metadata-modules/field-metadata/utils/assert-does-not-nullify-default-value-for-non-nullable-field.util'; import { buildUpdatableStandardFieldInput } from 'src/engine/metadata-modules/field-metadata/utils/build-updatable-standard-field-input.util'; import { checkCanDeactivateFieldOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/check-can-deactivate-field-or-throw'; @@ -39,7 +39,7 @@ import { computeColumnName, computeCompositeColumnName, } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; -import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { createMigrationActions } from 'src/engine/metadata-modules/field-metadata/utils/create-migration-actions.util'; import { generateRatingOptions } from 'src/engine/metadata-modules/field-metadata/utils/generate-rating-optionts.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; @@ -74,9 +74,9 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; type GenerateMigrationArgs = { - fieldMetadata: FieldMetadataEntity< - FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION - >; + fieldMetadata: + | FieldMetadataEntity + | FieldMetadataEntity; workspaceId: string; queryRunner: QueryRunner; }; @@ -116,7 +116,7 @@ export class FieldMetadataService extends TypeOrmQueryService { - fieldMetadataIdsToDelete.push( - morphRelation.id, - morphRelation.relationTargetFieldMetadataId, - ); - }); - - await fieldMetadataRepository.delete({ - id: In(fieldMetadataIdsToDelete), - }); - - for (const morphRelation of morphRelationsWithSameName) { - await this.generateDeleteRelationMigration({ - fieldMetadata: morphRelation, - workspaceId, - queryRunner, - }); - } - } else { - fieldMetadataIdsToDelete.push( - fieldMetadata.id, - fieldMetadata.relationTargetFieldMetadataId, - ); - - await fieldMetadataRepository.delete({ - id: In(fieldMetadataIdsToDelete), - }); - - await this.generateDeleteRelationMigration({ - fieldMetadata, - workspaceId, - queryRunner, - }); - } + await this.generateDeleteRelationMigration({ + fieldMetadata, + workspaceId, + queryRunner, + }); } else if (isFieldMetadataTypeMorphRelation(fieldMetadata)) { const fieldMetadataIdsToDelete: string[] = []; - const morphRelationsWithSameName = - await this.getMorphRelationsWithSameName({ - fieldMetadataName: fieldMetadata.name, + const allMophFieldMetadatas = + await this.getAllMorphFieldMetadatasByMorphId({ + morphId: fieldMetadata.morphId, objectMetadataId: fieldMetadata.objectMetadataId, workspaceId, fieldMetadataRepository, }); - morphRelationsWithSameName.forEach((morphRelation) => { + allMophFieldMetadatas.forEach((morphRelation) => { fieldMetadataIdsToDelete.push( morphRelation.id, morphRelation.relationTargetFieldMetadataId, @@ -542,7 +509,7 @@ export class FieldMetadataService extends TypeOrmQueryService; - }): Promise< - FieldMetadataEntity< - FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION - >[] - > { + }): Promise[]> { const fieldMetadatas = await fieldMetadataRepository.find({ where: { - name: fieldMetadataName, + morphId, objectMetadataId, workspaceId, }, @@ -1100,9 +1063,11 @@ export class FieldMetadataService extends TypeOrmQueryService( + fieldMetadatas: FieldMetadataEntity[], + fieldMetadataType: T, +): fieldMetadatas is Array> => + fieldMetadatas.every((fieldMetadata) => + isFlatFieldMetadataOfType(fieldMetadata, fieldMetadataType), + ); diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/are-field-metadatas-type-relation-or-morph-relation.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/are-field-metadatas-type-relation-or-morph-relation.util.ts deleted file mode 100644 index 7de21a78fc..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/are-field-metadatas-type-relation-or-morph-relation.util.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { type FieldMetadataType } from 'twenty-shared/types'; - -import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { isFieldMetadataTypeMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-morph-relation.util'; -import { isFieldMetadataTypeRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-relation.util'; - -export const areFieldMetadatasTypeRelationOrMorphRelation = ( - fieldMetadatas: FieldMetadataEntity[], -): fieldMetadatas is Array< - FieldMetadataEntity & - FieldMetadataEntity< - FieldMetadataType.MORPH_RELATION | FieldMetadataType.RELATION - > -> => { - return fieldMetadatas.every( - (fieldMetadata) => - isFieldMetadataTypeRelation(fieldMetadata) || - isFieldMetadataTypeMorphRelation(fieldMetadata), - ); -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-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 similarity index 71% rename from packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util.ts rename to packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util.ts index 8aa2002794..d581178e0b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-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 @@ -2,7 +2,7 @@ type ComputeRelationFieldJoinColumnNameArgs = { name: string; }; -export const computeRelationFieldJoinColumnName = ({ +export const computeMorphOrRelationFieldJoinColumnName = ({ name, }: ComputeRelationFieldJoinColumnNameArgs) => { return `${name}Id`; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util.ts index b0987a9cb6..160cb0edcf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util.ts @@ -6,7 +6,7 @@ import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-me type ComputeMorphRelationFieldNameArgs = { fieldName: string; - relationDirection: RelationType; + relationType: RelationType; targetObjectMetadata: Pick< ObjectMetadataEntity, 'nameSingular' | 'namePlural' @@ -15,18 +15,12 @@ type ComputeMorphRelationFieldNameArgs = { export const computeMorphRelationFieldName = ({ fieldName, - relationDirection, + relationType, targetObjectMetadata, }: ComputeMorphRelationFieldNameArgs): string => { - if (relationDirection === RelationType.MANY_TO_ONE) { + if (relationType === RelationType.MANY_TO_ONE) { return `${fieldName}${capitalize(targetObjectMetadata.nameSingular)}`; } - if (relationDirection === RelationType.ONE_TO_MANY) { - return `${fieldName}${capitalize(targetObjectMetadata.namePlural)}`; - } - - throw new Error( - `Invalid relation direction: ${relationDirection} for field ${fieldName}`, - ); + return `${fieldName}${capitalize(targetObjectMetadata.namePlural)}`; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/resolve-field-metadata-standard-override.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/resolve-field-metadata-standard-override.util.ts index d175c0b16d..807bcc70d2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/resolve-field-metadata-standard-override.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/resolve-field-metadata-standard-override.util.ts @@ -6,6 +6,7 @@ import { isDefined } from 'twenty-shared/utils'; import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId'; import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; +// TODO simplify export const resolveFieldMetadataStandardOverride = ( fieldMetadata: Pick< FieldMetadataDTO, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap index fd8faf6e62..49a30d81dd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/__tests__/__snapshots__/morph-relation-from-create-field-input-to-flat-field-metadatas-to-create.spec.ts.snap @@ -138,7 +138,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUnique": null, "label": "newFieldLabel", "morphId": Any, - "name": "newField", + "name": "newFieldPets", "objectMetadataId": Any, "options": null, "relationTargetFieldMetadataId": Any, @@ -252,7 +252,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUnique": null, "label": "newFieldLabel", "morphId": Any, - "name": "newField", + "name": "newFieldPets", "objectMetadataId": Any, "options": null, "relationTargetFieldMetadataId": Any, @@ -317,7 +317,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUnique": null, "label": "newFieldLabel", "morphId": Any, - "name": "newField", + "name": "newFieldPets", "objectMetadataId": Any, "options": null, "relationTargetFieldMetadataId": Any, @@ -444,7 +444,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUnique": null, "label": "newFieldLabel", "morphId": Any, - "name": "newField", + "name": "newFieldCompanies", "objectMetadataId": Any, "options": null, "relationTargetFieldMetadataId": Any, @@ -558,7 +558,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUnique": null, "label": "newFieldLabel", "morphId": Any, - "name": "newField", + "name": "newFieldCompanies", "objectMetadataId": Any, "options": null, "relationTargetFieldMetadataId": Any, @@ -623,7 +623,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su "isUnique": null, "label": "newFieldLabel", "morphId": Any, - "name": "newField", + "name": "newFieldCompanies", "objectMetadataId": Any, "options": null, "relationTargetFieldMetadataId": Any, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-all-others-morph-relation-flat-field-metadatas-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-all-others-morph-relation-flat-field-metadatas-or-throw.util.ts new file mode 100644 index 0000000000..366f71e3c9 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-all-others-morph-relation-flat-field-metadatas-or-throw.util.ts @@ -0,0 +1,56 @@ +import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +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'; +import { + FlatObjectMetadataMapsException, + FlatObjectMetadataMapsExceptionCode, +} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception'; +import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; + +export type FindAllMorphRelationFlatFieldMetadatasOrThrowArgs = { + flatObjectMetadataMaps: FlatObjectMetadataMaps; + flatFieldMetadata: FlatFieldMetadata; +}; +export const findAllOthersMorphRelationFlatFieldMetadatasOrThrow = ({ + flatObjectMetadataMaps, + flatFieldMetadata: morphRelationFlatFieldMetadata, +}: FindAllMorphRelationFlatFieldMetadatasOrThrowArgs): FlatFieldMetadata[] => { + const flatObjectMetadataWithFlatFieldMaps = + flatObjectMetadataMaps.byId[ + morphRelationFlatFieldMetadata.objectMetadataId + ]; + + if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) { + throw new FlatObjectMetadataMapsException( + 'Morph field relation object metadata not found', + FlatObjectMetadataMapsExceptionCode.OBJECT_METADATA_NOT_FOUND, + ); + } + + if ( + !isDefined( + flatObjectMetadataWithFlatFieldMaps.fieldsById[ + morphRelationFlatFieldMetadata.id + ], + ) + ) { + throw new FlatObjectMetadataMapsException( + 'Morph relation field not found in related object metadata', + FlatObjectMetadataMapsExceptionCode.FIELD_METADATA_NOT_FOUND, + ); + } + + return flatObjectMetadataWithFlatFieldMaps.flatFieldMetadatas.filter( + ( + flatFieldMetadata, + ): flatFieldMetadata is FlatFieldMetadata => + isFlatFieldMetadataOfType( + flatFieldMetadata, + FieldMetadataType.MORPH_RELATION, + ) && + flatFieldMetadata.morphId === morphRelationFlatFieldMetadata.morphId && + flatFieldMetadata.id !== morphRelationFlatFieldMetadata.id, + ); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-flat-field-metadatas-related-to-morph-relation-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-flat-field-metadatas-related-to-morph-relation-or-throw.util.ts index 83d00782b5..38732b23b1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-flat-field-metadatas-related-to-morph-relation-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/find-flat-field-metadatas-related-to-morph-relation-or-throw.util.ts @@ -1,14 +1,9 @@ -import { FieldMetadataType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; +import { type FieldMetadataType } from 'twenty-shared/types'; import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { findAllOthersMorphRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-all-others-morph-relation-flat-field-metadatas-or-throw.util'; import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util'; -import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util'; -import { - FlatObjectMetadataMapsException, - FlatObjectMetadataMapsExceptionCode, -} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception'; import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; export type FindFlatFieldMetadatasRelatedToMorphRelationOrThrowArgs = { @@ -19,54 +14,26 @@ export const findFlatFieldMetadatasRelatedToMorphRelationOrThrow = ({ flatObjectMetadataMaps, flatFieldMetadata: morphRelationFlatFieldMetadata, }: FindFlatFieldMetadatasRelatedToMorphRelationOrThrowArgs): FlatFieldMetadata[] => { - const flatObjectMetadataWithFlatFieldMaps = - flatObjectMetadataMaps.byId[ - morphRelationFlatFieldMetadata.objectMetadataId - ]; + const allMorphFlatFieldMetadatas = + findAllOthersMorphRelationFlatFieldMetadatasOrThrow({ + flatFieldMetadata: morphRelationFlatFieldMetadata, + flatObjectMetadataMaps, + }); - if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) { - throw new FlatObjectMetadataMapsException( - 'Morph field relation object metadata not found', - FlatObjectMetadataMapsExceptionCode.OBJECT_METADATA_NOT_FOUND, - ); - } + return [ + morphRelationFlatFieldMetadata, + ...allMorphFlatFieldMetadatas, + ].flatMap((flatFieldMetadata) => { + const relationTargetFlatFieldMetadata = + findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow({ + flatFieldMetadata, + flatObjectMetadataMaps, + }); - if ( - !isDefined( - flatObjectMetadataWithFlatFieldMaps.fieldsById[ - morphRelationFlatFieldMetadata.id - ], - ) - ) { - throw new FlatObjectMetadataMapsException( - 'Morph relation field not found in related object metadata', - FlatObjectMetadataMapsExceptionCode.FIELD_METADATA_NOT_FOUND, - ); - } + if (flatFieldMetadata.id === morphRelationFlatFieldMetadata.id) { + return [relationTargetFlatFieldMetadata]; + } - return flatObjectMetadataWithFlatFieldMaps.flatFieldMetadatas.flatMap( - (flatFieldMetadata) => { - if ( - !isFlatFieldMetadataOfType( - flatFieldMetadata, - FieldMetadataType.MORPH_RELATION, - ) || - flatFieldMetadata.name !== morphRelationFlatFieldMetadata.name - ) { - return []; - } - - const relationTargetFlatFieldMetadata = - findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow({ - flatFieldMetadata, - flatObjectMetadataMaps, - }); - - if (flatFieldMetadata.id === morphRelationFlatFieldMetadata.id) { - return [relationTargetFlatFieldMetadata]; - } - - return [flatFieldMetadata, relationTargetFlatFieldMetadata]; - }, - ); + return [flatFieldMetadata, relationTargetFlatFieldMetadata]; + }); }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util.ts index e0d34cc43f..23af2a4061 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util.ts @@ -1,8 +1,9 @@ import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { type FlatRelationTargetFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type'; export const fromFlatFieldMetadataToFieldMetadataDto = ( - flatFieldMetadata: FlatFieldMetadata, + flatFieldMetadata: FlatFieldMetadata | FlatRelationTargetFieldMetadata, ): FieldMetadataDTO => { const { createdAt, @@ -13,11 +14,35 @@ export const fromFlatFieldMetadataToFieldMetadataDto = ( isNullable, isUnique, settings, - ...rest + id, + label, + name, + objectMetadataId, + type, + workspaceId, + defaultValue, + isActive, + isCustom, + isLabelSyncedWithName, + isSystem, + isUIReadOnly, + options, } = flatFieldMetadata; return { - ...rest, + id, + label, + name, + objectMetadataId, + type, + workspaceId, + defaultValue, + isActive, + isCustom, + isLabelSyncedWithName, + isSystem, + isUIReadOnly, + options, createdAt: new Date(createdAt), updatedAt: new Date(updatedAt), description: description ?? undefined, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-or-relation-flat-field-metadata-to-relation-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-or-relation-flat-field-metadata-to-relation-dto.util.ts new file mode 100644 index 0000000000..4c83ef9afa --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-morph-or-relation-flat-field-metadata-to-relation-dto.util.ts @@ -0,0 +1,26 @@ +import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { fromFlatFieldMetadataToFieldMetadataDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { fromFlatObjectMetadataToObjectMetadataDto } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util'; + +export const fromMorphOrRelationFlatFieldMetadataToRelationDto = ({ + flatFieldMetadata, + sourceFlatObjectMetadata, +}: { + sourceFlatObjectMetadata: FlatObjectMetadata; + flatFieldMetadata: FlatFieldMetadata; +}) => ({ + type: flatFieldMetadata.settings.relationType, + sourceObjectMetadata: fromFlatObjectMetadataToObjectMetadataDto( + sourceFlatObjectMetadata, + ), + sourceFieldMetadata: + fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata), + targetObjectMetadata: fromFlatObjectMetadataToObjectMetadataDto( + flatFieldMetadata.flatRelationTargetObjectMetadata, + ), + targetFieldMetadata: fromFlatFieldMetadataToFieldMetadataDto( + flatFieldMetadata.flatRelationTargetFieldMetadata, + ), +}); 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 022af18258..1e06ee6e50 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,14 +1,13 @@ import { t } from '@lingui/core/macro'; import { type FieldMetadataType } from 'twenty-shared/types'; -import { - computeMorphRelationFieldJoinColumnName, - isDefined, -} from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; 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 { 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 { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util'; import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util'; @@ -68,17 +67,21 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({ const morphId = v4(); const flatFieldMetadatas = morphRelationCreationPayload.flatMap( ({ relationCreationPayload, targetFlatObjectMetadata }) => { + const currentMorphRelationFieldName = computeMorphRelationFieldName({ + fieldName: createFieldInput.name, + relationType: relationCreationPayload.type, + targetObjectMetadata: targetFlatObjectMetadata, + }); const sourceFlatObjectMetadataJoinColumnName = - computeMorphRelationFieldJoinColumnName({ - name: createFieldInput.name, - targetObjectMetadataNameSingular: - targetFlatObjectMetadata.nameSingular, + computeMorphOrRelationFieldJoinColumnName({ + name: currentMorphRelationFieldName, }); return generateMorphOrRelationFlatFieldMetadataPair({ createFieldInput: { ...createFieldInput, relationCreationPayload, + name: currentMorphRelationFieldName, }, sourceFlatObjectMetadataJoinColumnName, sourceFlatObjectMetadata, 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 ce4d6ece0f..f780e5d964 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 @@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils'; 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 { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util'; @@ -58,9 +58,10 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({ ...createFieldInput, relationCreationPayload, }, - sourceFlatObjectMetadataJoinColumnName: computeRelationFieldJoinColumnName({ - name: createFieldInput.name, - }), + sourceFlatObjectMetadataJoinColumnName: + computeMorphOrRelationFieldJoinColumnName({ + name: createFieldInput.name, + }), sourceFlatObjectMetadata, targetFlatObjectMetadata, workspaceId, 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 3226f25a26..86eeb60dcd 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 @@ -3,7 +3,7 @@ import { v4 } from 'uuid'; 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 { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-name.util'; +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 { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; @@ -92,7 +92,7 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({ }; const targetFlatFieldMetadataSettings = computeFieldMetadataRelationSettingsForRelationType({ - joinColumnName: computeRelationFieldJoinColumnName({ + joinColumnName: computeMorphOrRelationFieldJoinColumnName({ name: targetCreateFieldInput.name, }), relationType: diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util.ts index 65f2c14bb1..890486f6fc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util.ts @@ -1,8 +1,11 @@ -import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { + type FlatObjectMetadataWithoutFields, + type FlatObjectMetadata, +} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { type ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto'; export const fromFlatObjectMetadataToObjectMetadataDto = ( - flatObjectMetadata: FlatObjectMetadata, + flatObjectMetadata: FlatObjectMetadata | FlatObjectMetadataWithoutFields, ): ObjectMetadataDTO => { const { createdAt, @@ -12,11 +15,39 @@ export const fromFlatObjectMetadataToObjectMetadataDto = ( standardOverrides, shortcut, duplicateCriteria, - ...rest + id, + isActive, + isCustom, + isLabelSyncedWithName, + isRemote, + isSearchable, + isSystem, + isUIReadOnly, + labelPlural, + labelSingular, + namePlural, + nameSingular, + workspaceId, + imageIdentifierFieldMetadataId, + labelIdentifierFieldMetadataId, } = flatObjectMetadata; return { - ...rest, + id, + isActive, + isCustom, + isLabelSyncedWithName, + isRemote, + isSearchable, + isSystem, + isUIReadOnly, + labelPlural, + labelSingular, + namePlural, + nameSingular, + workspaceId, + imageIdentifierFieldMetadataId, + labelIdentifierFieldMetadataId, createdAt: new Date(createdAt), updatedAt: new Date(updatedAt), description: description ?? undefined, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-update-object-input-to-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-update-object-input-to-flat-object-metadata.util.ts index cc81c7e3fb..2f08e5dac5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-update-object-input-to-flat-object-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-update-object-input-to-flat-object-metadata.util.ts @@ -10,7 +10,7 @@ import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metad import { FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-properties-to-compare.constant'; import { type FlatObjectMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-properties-to-compare.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -import { renameFlatObjectMetadataManyToOneMorphRelationTargetFlatFieldMetadatasSettingsJoinColumnName } from 'src/engine/metadata-modules/flat-object-metadata/utils/rename-flat-object-metadata-morph-relation-flat-field-metadatas-settings-join-column-name.util'; +import { renameRelatedMorphFieldOnObjectNamesUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util'; import { OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES } from 'src/engine/metadata-modules/object-metadata/constants/object-metadata-standard-overrides-properties.constant'; import { type UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; import { @@ -130,14 +130,12 @@ export const fromUpdateObjectInputToFlatObjectMetadata = ({ [property]: updatedPropertyValue, }; const newUpdatedOtherObjectFlatFieldMetadatas = - property === 'nameSingular' - ? renameFlatObjectMetadataManyToOneMorphRelationTargetFlatFieldMetadatasSettingsJoinColumnName( - { - existingFlatObjectMetadataMaps, - fromFlatObjectMetadata: updatedFlatObjectMetadata, - toFlatObjectMetadata: updatedFlatObjectMetadata, - }, - ) + property === 'nameSingular' || property === 'namePlural' + ? renameRelatedMorphFieldOnObjectNamesUpdate({ + existingFlatObjectMetadataMaps, + fromFlatObjectMetadata: updatedFlatObjectMetadata, + toFlatObjectMetadata: updatedFlatObjectMetadata, + }) : []; return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/get-flat-object-metadata-many-to-one-target-morph-relation-flat-field-metadatas-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/get-flat-object-metadata-many-to-one-target-morph-relation-flat-field-metadatas-or-throw.util.ts index 01876338c0..2643065069 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/get-flat-object-metadata-many-to-one-target-morph-relation-flat-field-metadatas-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/get-flat-object-metadata-many-to-one-target-morph-relation-flat-field-metadatas-or-throw.util.ts @@ -1,22 +1,20 @@ import { FieldMetadataType } from 'twenty-shared/types'; -import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; - 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'; import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -type GetFlatObjectMetadataMorphRelationFlatFieldMetadatasArgs = { +type GetFlatObjectMetadataTargetMorphRelationFlatFieldMetadatasOrThrowArgs = { flatObjectMetadata: FlatObjectMetadata; existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; }; -export const getFlatObjectMetadataManyToOneTargetMorphRelationFlatFieldMetadatasOrThrow = +export const getFlatObjectMetadataTargetMorphRelationFlatFieldMetadatasOrThrow = ({ flatObjectMetadata, existingFlatObjectMetadataMaps, - }: GetFlatObjectMetadataMorphRelationFlatFieldMetadatasArgs) => { + }: GetFlatObjectMetadataTargetMorphRelationFlatFieldMetadatasOrThrowArgs): FlatFieldMetadata[] => { const manyToOneMorphRelationTargetFlatFieldMetadataIds = flatObjectMetadata.flatFieldMetadatas .filter( @@ -27,8 +25,6 @@ export const getFlatObjectMetadataManyToOneTargetMorphRelationFlatFieldMetadatas flatFieldMetadata, FieldMetadataType.RELATION, ) && - flatFieldMetadata.settings.relationType === - RelationType.MANY_TO_ONE && isFlatFieldMetadataOfType( flatFieldMetadata.flatRelationTargetFieldMetadata, FieldMetadataType.MORPH_RELATION, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-flat-object-metadata-morph-relation-flat-field-metadatas-settings-join-column-name.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-flat-object-metadata-morph-relation-flat-field-metadatas-settings-join-column-name.util.ts deleted file mode 100644 index fef6765d05..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-flat-object-metadata-morph-relation-flat-field-metadatas-settings-join-column-name.util.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { type FieldMetadataType, type FromTo } from 'twenty-shared/types'; -import { computeMorphRelationFieldJoinColumnName } from 'twenty-shared/utils'; - -import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; -import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -import { getFlatObjectMetadataManyToOneTargetMorphRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-flat-object-metadata-many-to-one-target-morph-relation-flat-field-metadatas-or-throw.util'; - -type RenameFlatObjectMetadataManyToOneMorphRelationTargetFlatFieldMetadatasSettingsJoinColumnNameArgs = - FromTo & { - existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; - }; -export const renameFlatObjectMetadataManyToOneMorphRelationTargetFlatFieldMetadatasSettingsJoinColumnName = - ({ - fromFlatObjectMetadata, - toFlatObjectMetadata, - existingFlatObjectMetadataMaps, - }: RenameFlatObjectMetadataManyToOneMorphRelationTargetFlatFieldMetadatasSettingsJoinColumnNameArgs): FlatFieldMetadata[] => { - const manyToOneMorphRelationFlatFieldMetadatas = - getFlatObjectMetadataManyToOneTargetMorphRelationFlatFieldMetadatasOrThrow( - { - flatObjectMetadata: fromFlatObjectMetadata, - existingFlatObjectMetadataMaps, - }, - ); - - const updatedFlatFieldMetadatas = - manyToOneMorphRelationFlatFieldMetadatas.map( - (morphRelationFlatFieldMetadata) => { - const newJoinColumnName = computeMorphRelationFieldJoinColumnName({ - name: morphRelationFlatFieldMetadata.name, - targetObjectMetadataNameSingular: toFlatObjectMetadata.nameSingular, - }); - - return { - ...morphRelationFlatFieldMetadata, - settings: { - ...morphRelationFlatFieldMetadata.settings, - joinColumnName: newJoinColumnName, - }, - }; - }, - ); - - return updatedFlatFieldMetadatas; - }; 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 new file mode 100644 index 0000000000..f9a73a3846 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util.ts @@ -0,0 +1,87 @@ +import { + RelationType, + type FieldMetadataType, + type FromTo, +} from 'twenty-shared/types'; + +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { getFlatObjectMetadataTargetMorphRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-flat-object-metadata-many-to-one-target-morph-relation-flat-field-metadatas-or-throw.util'; + +const searchAndReplaceLast = ({ + replace, + search, + source, +}: { + source: string; + search: string; + replace: string; +}) => { + const lastIndex = source.lastIndexOf(search); + + if (lastIndex === -1) return source; + + return ( + source.slice(0, lastIndex) + + replace + + source.slice(lastIndex + search.length) + ); +}; + +type RenameRelatedMorphFieldOnObjectNamesUpdateArgs = FromTo< + FlatObjectMetadata, + 'flatObjectMetadata' +> & { + existingFlatObjectMetadataMaps: FlatObjectMetadataMaps; +}; +export const renameRelatedMorphFieldOnObjectNamesUpdate = ({ + fromFlatObjectMetadata, + existingFlatObjectMetadataMaps, + toFlatObjectMetadata, +}: RenameRelatedMorphFieldOnObjectNamesUpdateArgs): FlatFieldMetadata[] => { + const manyToOneMorphRelationFlatFieldMetadatas = + getFlatObjectMetadataTargetMorphRelationFlatFieldMetadatasOrThrow({ + flatObjectMetadata: fromFlatObjectMetadata, + existingFlatObjectMetadataMaps, + }); + + const updatedFlatFieldMetadatas = + manyToOneMorphRelationFlatFieldMetadatas.map( + (morphRelationFlatFieldMetadata) => { + const isManyToOneRelationType = + morphRelationFlatFieldMetadata.settings.relationType === + RelationType.MANY_TO_ONE; + const initialMorphRelationFieldName = searchAndReplaceLast({ + source: morphRelationFlatFieldMetadata.name, + replace: '', + search: isManyToOneRelationType + ? fromFlatObjectMetadata.nameSingular + : fromFlatObjectMetadata.namePlural, + }); + const newMorphFieldName = computeMorphRelationFieldName({ + fieldName: initialMorphRelationFieldName, + relationType: morphRelationFlatFieldMetadata.settings.relationType, + targetObjectMetadata: toFlatObjectMetadata, + }); + const newJoinColumnName = isManyToOneRelationType + ? computeMorphOrRelationFieldJoinColumnName({ + name: newMorphFieldName, + }) + : undefined; + + return { + ...morphRelationFlatFieldMetadata, + name: newMorphFieldName, + settings: { + ...morphRelationFlatFieldMetadata.settings, + joinColumnName: newJoinColumnName, + }, + }; + }, + ); + + return updatedFlatFieldMetadatas; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts index 96af092730..794e079d81 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service.ts @@ -2,17 +2,14 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FieldMetadataType } from 'twenty-shared/types'; -import { - capitalize, - computeMorphRelationFieldJoinColumnName, - isDefined, -} from 'twenty-shared/utils'; +import { capitalize, isDefined } from 'twenty-shared/utils'; import { type QueryRunner, Repository } from 'typeorm'; import { v4 as uuidV4 } from 'uuid'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; import { 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataException, @@ -522,9 +519,12 @@ export class ObjectMetadataFieldRelationService { return fieldMetadatas; } + // Not maintained on v1, this side effect is broken and will duplicated field name references on + // object update + // It's functional on v2 public async updateMorphRelationsJoinColumnName({ existingObjectMetadata, - objectMetadataForUpdate, + objectMetadataForUpdate: _, queryRunner, }: { existingObjectMetadata: Pick< @@ -567,10 +567,8 @@ export class ObjectMetadataFieldRelationService { if (morphRelationFieldMetadataToUpdate.length > 0) { for (const morphRelationFieldMetadata of morphRelationFieldMetadataToUpdate) { - const newJoinColumnName = computeMorphRelationFieldJoinColumnName({ + const newJoinColumnName = computeMorphOrRelationFieldJoinColumnName({ name: morphRelationFieldMetadata.name, - targetObjectMetadataNameSingular: - objectMetadataForUpdate.nameSingular, }); await fieldMetadataRepository.save({ diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service.ts index 0f16b4caf7..a5c0b0a650 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service.ts @@ -1,8 +1,8 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-relation.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-relation.factory.ts index 69b7b7d8e6..194f6bb3b9 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-relation.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/entity-schema-relation.factory.ts @@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common'; import { FieldMetadataType } from 'twenty-shared/types'; import { type EntitySchemaRelationOptions } from 'typeorm'; -import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { determineSchemaRelationDetails } from 'src/engine/twenty-orm/utils/determine-schema-relation-details.util'; @@ -62,16 +61,7 @@ export class EntitySchemaRelationFactory { ); } - const fieldName = - fieldMetadata.type === FieldMetadataType.MORPH_RELATION - ? computeMorphRelationFieldName({ - fieldName: fieldMetadata.name, - relationDirection: fieldMetadata.settings.relationType, - targetObjectMetadata, - }) - : fieldMetadata.name; - - entitySchemaRelationMap[fieldName] = { + entitySchemaRelationMap[fieldMetadata.name] = { type: schemaRelationDetails.relationType, target: schemaRelationDetails.target, inverseSide: schemaRelationDetails.inverseSide, diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/pet-custom-relation-field-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/pet-custom-relation-field-seeds.constant.ts index abe444c2c7..adc5323ad6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/pet-custom-relation-field-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/metadata/custom-fields/constants/pet-custom-relation-field-seeds.constant.ts @@ -20,4 +20,19 @@ export const PET_CUSTOM_RELATION_FIELD_SEEDS: (FieldMetadataSeed & { ], targetObjectMetadataNames: ['surveyResult', 'rocket'], }, + { + type: FieldMetadataType.MORPH_RELATION, + label: 'Helper of the Pet (rocket or survey)', + name: 'helper', + icon: 'IconRelationOneToMany', + morphRelationsCreationPayload: [ + { + type: RelationType.ONE_TO_MANY, + targetObjectMetadataId: 'to-be-resolved-later', + targetFieldLabel: 'Helped by', + targetFieldIcon: 'IconRelationOneToMany', + }, + ], + targetObjectMetadataNames: ['surveyResult', 'rocket'], + }, ]; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/create-one-field-metadata-morph-relation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/create-one-field-metadata-morph-relation.integration-spec.ts index e8bee11b06..19b1494b15 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/create-one-field-metadata-morph-relation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/create-one-field-metadata-morph-relation.integration-spec.ts @@ -1,8 +1,10 @@ -import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util'; import { createMorphRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; -import { type EachTestingContext } from 'twenty-shared/testing'; +import { + eachTestingContextFilter, + type EachTestingContext, +} from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; @@ -18,6 +20,7 @@ describe('createOne FieldMetadataService morph relation fields', () => { createOneObject: { id: objectMetadataPersonId }, }, } = await createOneObjectMetadata({ + expectToFail: false, input: { nameSingular: 'personForMorphRelation', namePlural: 'peopleForMorphRelation', @@ -34,6 +37,7 @@ describe('createOne FieldMetadataService morph relation fields', () => { createOneObject: { id: objectMetadataCompanyId }, }, } = await createOneObjectMetadata({ + expectToFail: false, input: { nameSingular: 'companyForMorphRelation', namePlural: 'companiesForMorphRelation', @@ -50,6 +54,7 @@ describe('createOne FieldMetadataService morph relation fields', () => { createOneObject: { id: objectMetadataOpportunityId }, }, } = await createOneObjectMetadata({ + expectToFail: false, input: { nameSingular: 'opportunityForMorphRelation', namePlural: 'opportunitiesForMorphRelation', @@ -63,12 +68,15 @@ describe('createOne FieldMetadataService morph relation fields', () => { }); afterEach(async () => { await deleteOneObjectMetadata({ + expectToFail: false, input: { idToDelete: createdObjectMetadataPersonId }, }); await deleteOneObjectMetadata({ + expectToFail: false, input: { idToDelete: createdObjectMetadataOpportunityId }, }); await deleteOneObjectMetadata({ + expectToFail: false, input: { idToDelete: createdObjectMetadataCompanyId }, }); }); @@ -118,51 +126,52 @@ describe('createOne FieldMetadataService morph relation fields', () => { }, ]; - it.each(eachTestingContextArray)('$title', async ({ context }) => { - const contextPayload = - typeof context === 'function' - ? context({ - objectMetadataId: createdObjectMetadataOpportunityId, - firstTargetObjectMetadataId: createdObjectMetadataPersonId, - secondTargetObjectMetadataId: createdObjectMetadataCompanyId, - }) - : context; + it.each(eachTestingContextFilter(eachTestingContextArray))( + '$title', + async ({ context }) => { + const contextPayload = + typeof context === 'function' + ? context({ + objectMetadataId: createdObjectMetadataOpportunityId, + firstTargetObjectMetadataId: createdObjectMetadataPersonId, + secondTargetObjectMetadataId: createdObjectMetadataCompanyId, + }) + : context; - const createdField = await createMorphRelationBetweenObjects({ - objectMetadataId: contextPayload.objectMetadataId, - firstTargetObjectMetadataId: contextPayload.firstTargetObjectMetadataId, - secondTargetObjectMetadataId: contextPayload.secondTargetObjectMetadataId, - type: contextPayload.type, - relationType: contextPayload.relationType, - }); + const createdField = await createMorphRelationBetweenObjects({ + objectMetadataId: contextPayload.objectMetadataId, + firstTargetObjectMetadataId: contextPayload.firstTargetObjectMetadataId, + secondTargetObjectMetadataId: + contextPayload.secondTargetObjectMetadataId, + type: contextPayload.type, + relationType: contextPayload.relationType, + }); - expect(createdField.id).toBeDefined(); - expect(createdField.name).toBe('owner'); + expect(createdField.id).toBeDefined(); - const morphRelationTargetIds = createdField.morphRelations.map( - (relation) => relation.targetObjectMetadata.id, - ); - - expect(morphRelationTargetIds).toContain( - contextPayload.firstTargetObjectMetadataId, - ); - expect(morphRelationTargetIds).toContain( - contextPayload.secondTargetObjectMetadataId, - ); - - const isManyToOne = - contextPayload.relationType === RelationType.MANY_TO_ONE; - - if (isManyToOne) { - expect(createdField.settings?.joinColumnName).toBe( - 'ownerPersonForMorphRelationId', + const morphRelationTargetIds = createdField.morphRelations.map( + (relation) => relation.targetObjectMetadata.id, ); - } else { - expect(createdField.settings?.joinColumnName).toBeUndefined(); - } - await deleteOneFieldMetadata({ - input: { idToDelete: createdField.id }, - }).catch(); - }); + expect(morphRelationTargetIds).toContain( + contextPayload.firstTargetObjectMetadataId, + ); + expect(morphRelationTargetIds).toContain( + contextPayload.secondTargetObjectMetadataId, + ); + + const isManyToOne = + contextPayload.relationType === RelationType.MANY_TO_ONE; + + if (isManyToOne) { + expect(createdField.settings?.joinColumnName).toBe( + 'ownerPersonForMorphRelationId', + ); + expect(createdField.name).toBe('ownerPersonForMorphRelation'); + } else { + expect(createdField.settings?.joinColumnName).toBeUndefined(); + expect(createdField.name).toBe('ownerPeopleForMorphRelation'); + } + }, + ); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/create-one-field-metadata-relation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/create-one-field-metadata-relation.integration-spec.ts index 0f49158f02..8ef06661f3 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/create-one-field-metadata-relation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/create-one-field-metadata-relation.integration-spec.ts @@ -1,12 +1,11 @@ import { deleteOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util'; -import { findManyFieldsMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata-query-factory.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { createRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-relation-between-objects.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; -import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; import { type EachTestingContext } from 'twenty-shared/testing'; import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { findManyFieldsMetadata } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; @@ -155,7 +154,14 @@ const findFieldMetadata = async ({ }: { fieldMetadataId: string; }) => { - const operation = findManyFieldsMetadataQueryFactory({ + const { fields } = await findManyFieldsMetadata({ + input: { + filter: { + id: { eq: fieldMetadataId }, + }, + paging: { first: 10 }, + }, + expectToFail: false, gqlFields: ` id name @@ -174,16 +180,8 @@ const findFieldMetadata = async ({ } settings `, - input: { - filter: { - id: { eq: fieldMetadataId }, - }, - paging: { first: 10 }, - }, }); - - const fields = await makeMetadataAPIRequest(operation); - const field = fields.body.data.fields.edges?.[0]?.node; + const field = fields[0]?.node; return field; }; diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation-v2.integration-spec.ts similarity index 53% rename from packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation.integration-spec.ts rename to packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation-v2.integration-spec.ts index 90ec223f35..fce0561c36 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/morph-relation/rename-object-metadata-with-morph-relation-v2.integration-spec.ts @@ -1,88 +1,119 @@ import { findManyFieldsMetadataQueryFactory } from 'test/integration/metadata/suites/field-metadata/utils/find-many-fields-metadata-query-factory.util'; import { createMorphRelationBetweenObjects } from 'test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; +import { forceCreateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { FieldMetadataType } from 'twenty-shared/types'; -import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; -import { type RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto'; -import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; describe('Rename an object metadata with morph relation should succeed', () => { - let opportunityId = ''; - let personId = ''; - let companyId = ''; - let morphRelationField: FieldMetadataEntity & { - morphRelations: RelationDTO[]; - }; + let createdObjectMetadataPersonId: string; + let createdObjectMetadataOpportunityId: string; + let createdObjectMetadataCompanyId: string; + + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: true, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + value: false, + }); + }); beforeEach(async () => { const { data: { createOneObject: { id: aId }, }, - } = await createOneObjectMetadata({ + } = await forceCreateOneObjectMetadata({ input: { - nameSingular: 'opportunityForRename', - namePlural: 'opportunitiesForRename', + nameSingular: 'opportunityForRenameSecond', + namePlural: 'opportunitiesForRenameSecond', labelSingular: 'Opportunity For Rename', labelPlural: 'Opportunities For Rename', icon: 'IconOpportunity', }, }); - opportunityId = aId; + createdObjectMetadataOpportunityId = aId; const { data: { createOneObject: { id: bId }, }, - } = await createOneObjectMetadata({ + } = await forceCreateOneObjectMetadata({ input: { - nameSingular: 'personForRename', - namePlural: 'peopleForRename', + nameSingular: 'personForRenameSecond', + namePlural: 'peopleForRenameSecond', labelSingular: 'Person For Rename', labelPlural: 'People For Rename', icon: 'IconPerson', }, }); - personId = bId; + createdObjectMetadataPersonId = bId; const { data: { createOneObject: { id: cId }, }, - } = await createOneObjectMetadata({ + } = await forceCreateOneObjectMetadata({ input: { - nameSingular: 'companyForRename', - namePlural: 'companiesForRename', + nameSingular: 'companyForRenameSecond', + namePlural: 'companiesForRenameSecond', labelSingular: 'Company For Rename', labelPlural: 'Companies For Rename', icon: 'IconCompany', }, }); - companyId = cId; + createdObjectMetadataCompanyId = cId; }); afterEach(async () => { - await deleteOneObjectMetadata({ input: { idToDelete: opportunityId } }); - await deleteOneObjectMetadata({ input: { idToDelete: personId } }); - await deleteOneObjectMetadata({ input: { idToDelete: companyId } }); + const createdObjectMetadataIds = [ + createdObjectMetadataPersonId, + createdObjectMetadataOpportunityId, + createdObjectMetadataCompanyId, + ]; + + for (const objectMetadataId of createdObjectMetadataIds) { + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { isActive: false }, + }, + }); + await deleteOneObjectMetadata({ + expectToFail: false, + input: { idToDelete: objectMetadataId }, + }); + } }); - it('should rename custom object, and update the join column name of the morph relation that contains the object name', async () => { - morphRelationField = await createMorphRelationBetweenObjects({ + it('should rename custom object, and update both the field name and join column name of the morph relation that contains the object name', async () => { + const morphRelationField = await createMorphRelationBetweenObjects({ name: 'owner', - objectMetadataId: opportunityId, - firstTargetObjectMetadataId: personId, - secondTargetObjectMetadataId: companyId, + objectMetadataId: createdObjectMetadataOpportunityId, + firstTargetObjectMetadataId: createdObjectMetadataPersonId, + secondTargetObjectMetadataId: createdObjectMetadataCompanyId, type: FieldMetadataType.MORPH_RELATION, relationType: RelationType.MANY_TO_ONE, }); + expect(morphRelationField.morphRelations.length).toBe(2); + const { data } = await updateOneObjectMetadata({ gqlFields: ` nameSingular @@ -91,20 +122,21 @@ describe('Rename an object metadata with morph relation should succeed', () => { labelPlural `, input: { - idToUpdate: personId, + idToUpdate: createdObjectMetadataPersonId, updatePayload: { - nameSingular: 'personForRename2', - namePlural: 'peopleForRename2', + nameSingular: 'personForRenameSecond2', + namePlural: 'peopleForRenameSecond2', labelSingular: 'Person For Rename2', labelPlural: 'People For Rename2', }, }, }); - expect(data.updateOneObject.nameSingular).toBe('personForRename2'); + expect(data.updateOneObject.nameSingular).toBe('personForRenameSecond2'); const ownerFieldMetadataOnPersonId = morphRelationField.morphRelations.find( - (morphRelation) => morphRelation.targetObjectMetadata.id === personId, + (morphRelation) => + morphRelation.targetObjectMetadata.id === createdObjectMetadataPersonId, )?.sourceFieldMetadata.id; if (!ownerFieldMetadataOnPersonId) { @@ -118,7 +150,7 @@ describe('Rename an object metadata with morph relation should succeed', () => { }); expect(fieldAfterRenaming.settings.joinColumnName).toBe( - 'ownerPersonForRename2Id', + 'ownerPersonForRenameSecondId', ); }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util.ts index 539b558ba7..d39fa5f663 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-morph-relation-between-objects.util.ts @@ -58,6 +58,7 @@ export const createMorphRelationBetweenObjects = async ({ input: createFieldInput, gqlFields: ` id + type name label isLabelSyncedWithName diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util.ts index 36278f0295..450a901de5 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/force-create-one-object-metadata.util.ts @@ -35,7 +35,7 @@ export const forceCreateOneObjectMetadata = async ({ }, }); - if (!isDefined(result.errors)) { + if (!isDefined(result.errors) || result.errors.length === 0) { return result; } diff --git a/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-field-join-column-name.util.ts b/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-field-join-column-name.util.ts deleted file mode 100644 index 6c73895785..0000000000 --- a/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-field-join-column-name.util.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { capitalize } from '@/utils/strings'; - -type ComputeMorphRelationFieldJoinColumnNameArgs = { - name: string; - targetObjectMetadataNameSingular: string; -}; - -export const computeMorphRelationFieldJoinColumnName = ({ - name, - targetObjectMetadataNameSingular, -}: ComputeMorphRelationFieldJoinColumnNameArgs) => { - return `${name}${capitalize(targetObjectMetadataNameSingular)}Id`; -}; diff --git a/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-field-name.util.ts b/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-field-name.util.ts deleted file mode 100644 index 04e8fdc956..0000000000 --- a/packages/twenty-shared/src/utils/fieldMetadata/compute-morph-relation-field-name.util.ts +++ /dev/null @@ -1,34 +0,0 @@ - -import { capitalize } from '@/utils/strings'; - -enum RelationType { - MANY_TO_ONE = 'MANY_TO_ONE', - ONE_TO_MANY = 'ONE_TO_MANY' -} - - -type ComputeMorphRelationFieldNameArgs = { - fieldName: string; - relationDirection: RelationType; - nameSingular: string; - namePlural: string; -}; - -export const computeMorphRelationFieldName = ({ - fieldName, - relationDirection, - nameSingular, - namePlural, -}: ComputeMorphRelationFieldNameArgs): string => { - if (relationDirection === RelationType.MANY_TO_ONE) { - return `${fieldName}${capitalize(nameSingular)}`; - } - - if (relationDirection === RelationType.ONE_TO_MANY) { - return `${fieldName}${capitalize(namePlural)}`; - } - - throw new Error( - `Invalid relation direction: ${relationDirection} for field ${fieldName}`, - ); -}; diff --git a/packages/twenty-shared/src/utils/index.ts b/packages/twenty-shared/src/utils/index.ts index cecf87a800..2d20512d96 100644 --- a/packages/twenty-shared/src/utils/index.ts +++ b/packages/twenty-shared/src/utils/index.ts @@ -14,8 +14,6 @@ export { sumByProperty } from './array/sumByProperty'; export { assertUnreachable } from './assertUnreachable'; export { deepMerge } from './deepMerge'; export { extractAndSanitizeObjectStringFields } from './extractAndSanitizeObjectStringFields'; -export { computeMorphRelationFieldJoinColumnName } from './fieldMetadata/compute-morph-relation-field-join-column-name.util'; -export { computeMorphRelationFieldName } from './fieldMetadata/compute-morph-relation-field-name.util'; export { isFieldMetadataDateKind } from './fieldMetadata/isFieldMetadataDateKind'; export { fromArrayToUniqueKeyRecord } from './from-array-to-unique-key-record.util'; export { fromArrayToValuesByKeyRecord } from './fromArrayToValuesByKeyRecord.util';