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';