Handle input transpilation morph relation v2 (#14124)

# Introduction
From `morphRelationCreationPayload` to `flatFieldMetadatas`

close https://github.com/twentyhq/core-team-issues/issues/1406

## Unit testing
I'm not a fan of covering things this way, where we could have some
strong integration testing tests in the first place
But I wanted to freeze the optimistic path computation, also covered the
exception because it was cheap, doesn't mean I won't cover them through
integration tests later anw
This commit is contained in:
Paul Rastoin
2025-08-28 17:22:03 +02:00
committed by GitHub
parent e264d7f32b
commit 7c481abf0c
14 changed files with 1581 additions and 298 deletions
@@ -2,7 +2,10 @@ import { Injectable } from '@nestjs/common';
import omit from 'lodash.omit';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
computeMorphRelationFieldJoinColumnName,
isDefined,
} from 'twenty-shared/utils';
import { type Repository } from 'typeorm';
import { v4 } from 'uuid';
@@ -15,7 +18,6 @@ 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 { computeMorphRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-join-column-name.util';
import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-relation-field-join-column-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';
@@ -1,13 +0,0 @@
import { capitalize } from 'twenty-shared/utils';
type ComputeMorphRelationFieldJoinColumnNameArgs = {
name: string;
targetObjectMetadataNameSingular: string;
};
export const computeMorphRelationFieldJoinColumnName = ({
name,
targetObjectMetadataNameSingular,
}: ComputeMorphRelationFieldJoinColumnNameArgs) => {
return `${name}${capitalize(targetObjectMetadataNameSingular)}Id`;
};
@@ -0,0 +1,693 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Failure cases should fail when morphRelationsCreationPayload has different relation types 1`] = `
{
"error": {
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Morph relation relations must have the same relation type",
"userFriendlyMessage": "Morph relation relations must have the same relation type",
},
"status": "fail",
}
`;
exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Failure cases should fail when morphRelationsCreationPayload has invalid relation payload 1`] = `
{
"error": {
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Morph relation input transpilation failed",
"userFriendlyMessage": "Invalid morph relation input",
"value": [
{
"targetObjectMetadataId": Any<String>,
"type": "ONE_TO_MANY",
},
],
},
"status": "fail",
}
`;
exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Failure cases should fail when morphRelationsCreationPayload has several references to same object metadata 1`] = `
{
"error": {
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Morph relation relations must have only relation to the same object metadata",
"userFriendlyMessage": "Morph relation relations must have only relation to the same object metadata",
},
"status": "fail",
}
`;
exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Failure cases should fail when morphRelationsCreationPayload is empty array 1`] = `
{
"error": {
"code": "INVALID_FIELD_INPUT",
"message": "Relation creation payload is required",
"userFriendlyMessage": "Relation creation payload is required",
"value": [],
},
"status": "fail",
}
`;
exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Failure cases should fail when morphRelationsCreationPayload is missing 1`] = `
{
"error": {
"code": "INVALID_FIELD_INPUT",
"message": "Relation creation payload is required",
"userFriendlyMessage": "Relation creation payload is required",
"value": undefined,
},
"status": "fail",
}
`;
exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Failure cases should fail when target object metadata is not found 1`] = `
{
"error": {
"code": "FIELD_METADATA_RELATION_MALFORMED",
"message": "Morph relation input transpilation failed",
"userFriendlyMessage": "Invalid morph relation input",
"value": [
{
"targetFieldIcon": "IconPet",
"targetFieldLabel": "Pet",
"targetObjectMetadataId": Any<String>,
"type": "ONE_TO_MANY",
},
],
},
"status": "fail",
}
`;
exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Success cases should create morph relation field metadata with valid input on rocket object to pet object 1`] = `
{
"result": [
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": null,
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": null,
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconCat",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Pets",
"labelSingular": "Pet",
"namePlural": "pets",
"nameSingular": "pet",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"name": "newField",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconPet",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "Pet",
"name": "pet",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"joinColumnName": "petId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": null,
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconCat",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Pets",
"labelSingular": "Pet",
"namePlural": "pets",
"nameSingular": "pet",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"name": "newField",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": null,
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": null,
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconCat",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Pets",
"labelSingular": "Pet",
"namePlural": "pets",
"nameSingular": "pet",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"name": "newField",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconPet",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "Pet",
"name": "pet",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"joinColumnName": "petId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": null,
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A company",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconBuildingSkyscraper",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": false,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Companies",
"labelSingular": "Company",
"namePlural": "companies",
"nameSingular": "company",
"shortcut": "C",
"standardId": Any<String>,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"name": "newField",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconBuilding",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "Company",
"name": "company",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"joinColumnName": "companyId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A company",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconBuildingSkyscraper",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": false,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Companies",
"labelSingular": "Company",
"namePlural": "companies",
"nameSingular": "company",
"shortcut": "C",
"standardId": Any<String>,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"name": "newField",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": null,
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A company",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconBuildingSkyscraper",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": false,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Companies",
"labelSingular": "Company",
"namePlural": "companies",
"nameSingular": "company",
"shortcut": "C",
"standardId": Any<String>,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"name": "newField",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconBuilding",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "Company",
"name": "company",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"joinColumnName": "companyId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
],
"status": "success",
}
`;
@@ -0,0 +1,273 @@
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import { 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';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { fromCreateFieldInputToFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util';
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
import { COMPANY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/company-flat-object.mock';
import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock';
import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock';
const existingFlatObjectMetadataMaps = [
COMPANY_FLAT_OBJECT_MOCK,
ROCKET_FLAT_OBJECT_MOCK,
PET_FLAT_OBJECT_MOCK,
].reduce((flatObjectMetadataMaps, flatObjectMetadata) => {
return replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadata: {
...flatObjectMetadata,
flatFieldMetadatas: [],
},
flatObjectMetadataMaps,
});
}, FLAT_OBJECT_METADATA_MAPS_MOCKS);
type TestCase = EachTestingContext<{
input: {
rawCreateFieldInput: CreateFieldInput &
Required<Pick<CreateFieldInput, 'morphRelationsCreationPayload'>>;
workspaceId: string;
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
expected: 'success' | 'fail';
}>;
describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite', () => {
const mockWorkspaceId = 'mock-workspace-id';
describe('Success cases', () => {
const successTestCases: TestCase[] = [
{
title:
'should create morph relation field metadata with valid input on rocket object to pet object',
context: {
input: {
rawCreateFieldInput: {
name: 'newField',
label: 'newFieldLabel',
description: 'new field description',
type: FieldMetadataType.MORPH_RELATION,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
workspaceId: mockWorkspaceId,
morphRelationsCreationPayload: [
{
type: RelationType.ONE_TO_MANY,
targetObjectMetadataId: PET_FLAT_OBJECT_MOCK.id,
targetFieldLabel: 'Pet',
targetFieldIcon: 'IconPet',
},
{
type: RelationType.ONE_TO_MANY,
targetObjectMetadataId: COMPANY_FLAT_OBJECT_MOCK.id,
targetFieldLabel: 'Company',
targetFieldIcon: 'IconBuilding',
},
],
},
workspaceId: mockWorkspaceId,
existingFlatObjectMetadataMaps,
},
expected: 'success',
},
},
];
test.each(successTestCases)(
'$title',
async ({ context: { input, expected } }) => {
const result =
await fromCreateFieldInputToFlatFieldMetadatasToCreate(input);
expect(result.status).toEqual(expected);
if (result.status !== 'success') {
throw new Error('Should never occur, typecheck');
}
expect(result.result.length).toBe(
input.rawCreateFieldInput.morphRelationsCreationPayload.length * 2,
);
expect(result).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(result),
);
},
);
});
describe('Failure cases', () => {
const failureTestCases: TestCase[] = [
{
title: 'should fail when morphRelationsCreationPayload is missing',
context: {
input: {
rawCreateFieldInput: {
name: 'newField',
label: 'newFieldLabel',
description: 'new field description',
type: FieldMetadataType.MORPH_RELATION,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
workspaceId: mockWorkspaceId,
morphRelationsCreationPayload: undefined as any,
},
workspaceId: mockWorkspaceId,
existingFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expected: 'fail',
},
},
{
title: 'should fail when morphRelationsCreationPayload is empty array',
context: {
input: {
rawCreateFieldInput: {
name: 'newField',
label: 'newFieldLabel',
description: 'new field description',
type: FieldMetadataType.MORPH_RELATION,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
workspaceId: mockWorkspaceId,
morphRelationsCreationPayload: [],
},
workspaceId: mockWorkspaceId,
existingFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expected: 'fail',
},
},
{
title:
'should fail when morphRelationsCreationPayload has different relation types',
context: {
input: {
rawCreateFieldInput: {
name: 'newField',
label: 'newFieldLabel',
description: 'new field description',
type: FieldMetadataType.MORPH_RELATION,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
workspaceId: mockWorkspaceId,
morphRelationsCreationPayload: [
{
type: RelationType.ONE_TO_MANY,
targetObjectMetadataId: PET_FLAT_OBJECT_MOCK.id,
targetFieldLabel: 'Pet',
targetFieldIcon: 'IconPet',
},
{
type: RelationType.MANY_TO_ONE,
targetObjectMetadataId: COMPANY_FLAT_OBJECT_MOCK.id,
targetFieldLabel: 'Company',
targetFieldIcon: 'IconPet',
},
],
},
workspaceId: mockWorkspaceId,
existingFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expected: 'fail',
},
},
{
title:
'should fail when morphRelationsCreationPayload has several references to same object metadata',
context: {
input: {
rawCreateFieldInput: {
name: 'newField',
label: 'newFieldLabel',
description: 'new field description',
type: FieldMetadataType.MORPH_RELATION,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
workspaceId: mockWorkspaceId,
morphRelationsCreationPayload: [
{
type: RelationType.MANY_TO_ONE,
targetObjectMetadataId: PET_FLAT_OBJECT_MOCK.id,
targetFieldLabel: 'Pet',
targetFieldIcon: 'IconPet',
},
{
type: RelationType.MANY_TO_ONE,
targetObjectMetadataId: PET_FLAT_OBJECT_MOCK.id,
targetFieldLabel: 'Pet',
targetFieldIcon: 'IconPet',
},
],
},
workspaceId: mockWorkspaceId,
existingFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expected: 'fail',
},
},
{
title:
'should fail when morphRelationsCreationPayload has invalid relation payload',
context: {
input: {
rawCreateFieldInput: {
name: 'newField',
label: 'newFieldLabel',
description: 'new field description',
type: FieldMetadataType.MORPH_RELATION,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
workspaceId: mockWorkspaceId,
morphRelationsCreationPayload: [
{
type: RelationType.ONE_TO_MANY,
targetObjectMetadataId: PET_FLAT_OBJECT_MOCK.id,
// Missing required fields
} as any,
],
},
workspaceId: mockWorkspaceId,
existingFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expected: 'fail',
},
},
{
title: 'should fail when target object metadata is not found',
context: {
input: {
rawCreateFieldInput: {
name: 'newField',
label: 'newFieldLabel',
description: 'new field description',
type: FieldMetadataType.MORPH_RELATION,
objectMetadataId: ROCKET_FLAT_OBJECT_MOCK.id,
workspaceId: mockWorkspaceId,
morphRelationsCreationPayload: [
{
type: RelationType.ONE_TO_MANY,
targetObjectMetadataId: 'non-existent-id',
targetFieldLabel: 'Pet',
targetFieldIcon: 'IconPet',
},
],
},
workspaceId: mockWorkspaceId,
existingFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expected: 'fail',
},
},
];
test.each(failureTestCases)(
'$title',
async ({ context: { input, expected } }) => {
const result =
await fromCreateFieldInputToFlatFieldMetadatasToCreate(input);
expect(result.status).toEqual(expected);
expect(result).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny(result),
);
},
);
});
});
@@ -9,15 +9,16 @@ import { v4 } from 'uuid';
import { type FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
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 { generateRatingOptions } from 'src/engine/metadata-modules/field-metadata/utils/generate-rating-optionts.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 { fromRelationCreateFieldInputToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util';
import { fromMorphRelationCreateFieldInputToFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util';
import { fromRelationCreateFieldInputToFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util';
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
type FromCreateFieldInputToFlatObjectMetadataArgs = {
rawCreateFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
@@ -46,10 +47,10 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
rawCreateFieldInput,
['description', 'icon', 'label', 'name', 'objectMetadataId', 'type'],
);
const parentFlatObjectMetadata =
const parentFlatObjectMetadataWithFlatFieldMaps =
existingFlatObjectMetadataMaps.byId[createFieldInput.objectMetadataId];
if (!isDefined(parentFlatObjectMetadata)) {
if (!isDefined(parentFlatObjectMetadataWithFlatFieldMaps)) {
return {
status: 'fail',
error: {
@@ -60,6 +61,11 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
};
}
const parentFlatObjectMetadata =
fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata(
parentFlatObjectMetadataWithFlatFieldMaps,
);
const fieldMetadataId = v4();
const commonFlatFieldMetadata = getDefaultFlatFieldMetadata({
createFieldInput,
@@ -69,15 +75,24 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
switch (createFieldInput.type) {
case FieldMetadataType.MORPH_RELATION: {
throw new UserInputError(
'Morph relation feature is not migrated to workspace migration v2 yet',
);
return await fromMorphRelationCreateFieldInputToFlatFieldMetadatas({
createFieldInput: {
...createFieldInput,
type: createFieldInput.type,
},
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata: parentFlatObjectMetadata,
workspaceId,
});
}
case FieldMetadataType.RELATION: {
return await fromRelationCreateFieldInputToFlatFieldMetadata({
return await fromRelationCreateFieldInputToFlatFieldMetadatas({
existingFlatObjectMetadataMaps,
sourceParentFlatObjectMetadata: parentFlatObjectMetadata,
createFieldInput,
sourceFlatObjectMetadata: parentFlatObjectMetadata,
createFieldInput: {
...createFieldInput,
type: createFieldInput.type,
},
workspaceId,
});
}
@@ -0,0 +1,91 @@
import { t } from '@lingui/core/macro';
import { type FieldMetadataType } from 'twenty-shared/types';
import {
computeMorphRelationFieldJoinColumnName,
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 { 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 { generateRelationOrMorphRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-relation-or-morph-relation-flat-field-metadata-pair.util';
import { validateMorphRelationCreationPayload } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-creation-payload.util';
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';
type FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs = {
createFieldInput: Omit<CreateFieldInput, 'workspaceId'> & {
type: FieldMetadataType.MORPH_RELATION;
};
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
};
export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
createFieldInput,
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata,
workspaceId,
}: FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs): Promise<
FieldInputTranspilationResult<FlatFieldMetadata[]>
> => {
const rawMorphCreationPayload =
createFieldInput.morphRelationsCreationPayload;
if (
!isDefined(rawMorphCreationPayload) ||
!Array.isArray(rawMorphCreationPayload) ||
rawMorphCreationPayload.length === 0
) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message: `Relation creation payload is required`,
userFriendlyMessage: t`Relation creation payload is required`,
value: rawMorphCreationPayload,
},
};
}
const morphRelationCreationPayloadValidation =
await validateMorphRelationCreationPayload({
existingFlatObjectMetadataMaps,
morphRelationCreationPayload: rawMorphCreationPayload,
});
if (morphRelationCreationPayloadValidation.status === 'fail') {
return morphRelationCreationPayloadValidation;
}
const morphRelationCreationPayload =
morphRelationCreationPayloadValidation.result;
const flatFieldMetadatas = morphRelationCreationPayload.flatMap(
({ relationCreationPayload, targetFlatObjectMetadata }) => {
const sourceFlatObjectMetadataJoinColumnName =
computeMorphRelationFieldJoinColumnName({
name: createFieldInput.name,
targetObjectMetadataNameSingular:
targetFlatObjectMetadata.nameSingular,
});
return generateRelationOrMorphRelationFlatFieldMetadataPair({
createFieldInput: {
...createFieldInput,
relationCreationPayload,
},
sourceFlatObjectMetadataJoinColumnName,
sourceFlatObjectMetadata,
targetFlatObjectMetadata,
workspaceId,
});
},
);
return {
status: 'success',
result: flatFieldMetadatas,
};
};
@@ -1,186 +0,0 @@
import { t } from '@lingui/core/macro';
import { FieldMetadataType } from 'twenty-shared/types';
import {
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload-or-throw.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 { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
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 { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
type ComputeFieldMetadataRelationSettingsForRelationTypeArgs = {
relationType: RelationType;
fieldMetadataName: string;
};
const computeFieldMetadataRelationSettingsForRelationType = ({
fieldMetadataName,
relationType,
}: ComputeFieldMetadataRelationSettingsForRelationTypeArgs) => {
if (relationType === RelationType.MANY_TO_ONE) {
return {
relationType: RelationType.MANY_TO_ONE,
onDelete: RelationOnDeleteAction.SET_NULL,
joinColumnName: `${fieldMetadataName}Id`,
};
}
return {
relationType: RelationType.ONE_TO_MANY,
};
};
type FromRelationCreateFieldInputToFlatFieldMetadataArgs = {
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
sourceParentFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
};
export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
existingFlatObjectMetadataMaps,
sourceParentFlatObjectMetadata,
createFieldInput,
workspaceId,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
FieldInputTranspilationResult<FlatFieldMetadata[]>
> => {
const rawCreationPayload = createFieldInput.relationCreationPayload;
if (!isDefined(rawCreationPayload)) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message: `Relation creation payload is required`,
userFriendlyMessage: t`Relation creation payload is required`,
value: rawCreationPayload,
},
};
}
const relationCreationPayload =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawCreationPayload,
['targetFieldIcon', 'targetFieldLabel', 'targetObjectMetadataId', 'type'],
);
try {
await validateRelationCreationPayloadOrThrow(relationCreationPayload);
} catch (error) {
if (error instanceof FieldMetadataException) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
message: `Relation creation payload is invalid ${JSON.stringify(relationCreationPayload)}`,
userFriendlyMessage: t`Invalid relation creation payload`,
value: relationCreationPayload,
},
};
} else {
throw error;
}
}
const targetParentFlatObjectMetadata =
existingFlatObjectMetadataMaps.byId[
relationCreationPayload.targetObjectMetadataId
];
if (!isDefined(targetParentFlatObjectMetadata)) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
message: `Object metadata relation target not found for relation creation payload`,
userFriendlyMessage: t`Object targeted by field to create not found`,
value: relationCreationPayload,
},
};
}
const sourceFlatFieldMetadataSettings =
computeFieldMetadataRelationSettingsForRelationType({
fieldMetadataName: createFieldInput.name,
relationType: relationCreationPayload.type,
});
const targetRelationTargetFieldMetadataId = v4();
const sourceRelationTargetFieldMetadataId = v4();
const sourceFlatFieldMetadata: Omit<
FlatFieldMetadata<FieldMetadataType.RELATION>,
'flatRelationTargetFieldMetadata'
> = {
...getDefaultFlatFieldMetadata({
createFieldInput,
workspaceId,
fieldMetadataId: sourceRelationTargetFieldMetadataId,
}),
icon: createFieldInput.icon ?? 'IconRelationOneToMany',
type: FieldMetadataType.RELATION,
defaultValue: null,
settings: sourceFlatFieldMetadataSettings,
options: null,
relationTargetFieldMetadataId: targetRelationTargetFieldMetadataId, // Note: this won't work until we enable deferred transaction
relationTargetObjectMetadataId: targetParentFlatObjectMetadata.id,
flatRelationTargetObjectMetadata: targetParentFlatObjectMetadata,
};
const targetCreateFieldInput: CreateFieldInput = {
icon: relationCreationPayload.targetFieldIcon ?? 'Icon123',
label: relationCreationPayload.targetFieldLabel,
name: computeMetadataNameFromLabel(
relationCreationPayload.targetFieldLabel,
),
objectMetadataId: targetParentFlatObjectMetadata.id,
type: FieldMetadataType.RELATION,
workspaceId,
};
const targetFlatFieldMetadataSettings =
computeFieldMetadataRelationSettingsForRelationType({
fieldMetadataName: targetCreateFieldInput.name,
relationType:
relationCreationPayload.type === RelationType.ONE_TO_MANY
? RelationType.MANY_TO_ONE
: RelationType.ONE_TO_MANY,
});
const targetFlatFieldMetadata: FlatFieldMetadata<FieldMetadataType.RELATION> =
{
...getDefaultFlatFieldMetadata({
createFieldInput: targetCreateFieldInput,
workspaceId,
fieldMetadataId: targetRelationTargetFieldMetadataId,
}),
type: FieldMetadataType.RELATION,
defaultValue: null,
settings: targetFlatFieldMetadataSettings,
options: null,
relationTargetFieldMetadataId: sourceRelationTargetFieldMetadataId,
relationTargetObjectMetadataId: sourceParentFlatObjectMetadata.id,
flatRelationTargetFieldMetadata: sourceFlatFieldMetadata,
flatRelationTargetObjectMetadata: sourceParentFlatObjectMetadata,
};
return {
status: 'success',
result: [
{
...sourceFlatFieldMetadata,
flatRelationTargetFieldMetadata: targetFlatFieldMetadata,
},
targetFlatFieldMetadata,
] satisfies FlatFieldMetadata<FieldMetadataType.RELATION>[],
};
};
@@ -0,0 +1,76 @@
import { t } from '@lingui/core/macro';
import { type FieldMetadataType } from 'twenty-shared/types';
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 { type RelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/relation-field-metadata-type.type';
import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-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 { generateRelationOrMorphRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-relation-or-morph-relation-flat-field-metadata-pair.util';
import { validateRelationCreationPayload } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util';
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';
type FromRelationCreateFieldInputToFlatFieldMetadataArgs = {
createFieldInput: Omit<CreateFieldInput, 'workspaceId'> & {
type: FieldMetadataType.RELATION;
};
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
};
export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata,
createFieldInput,
workspaceId,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
FieldInputTranspilationResult<FlatFieldMetadata<RelationFieldMetadataType>[]>
> => {
const rawCreationPayload = createFieldInput.relationCreationPayload;
if (!isDefined(rawCreationPayload)) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message: `Relation creation payload is required`,
userFriendlyMessage: t`Relation creation payload is required`,
value: rawCreationPayload,
},
};
}
const relationValidationResult = await validateRelationCreationPayload({
existingFlatObjectMetadataMaps,
relationCreationPayload: rawCreationPayload,
});
if (relationValidationResult.status === 'fail') {
return relationValidationResult;
}
const { relationCreationPayload, targetFlatObjectMetadata } =
relationValidationResult.result;
const flatFieldMetadatas =
generateRelationOrMorphRelationFlatFieldMetadataPair({
createFieldInput: {
...createFieldInput,
relationCreationPayload,
},
sourceFlatObjectMetadataJoinColumnName:
computeRelationFieldJoinColumnName({
name: createFieldInput.name,
}),
sourceFlatObjectMetadata,
targetFlatObjectMetadata,
workspaceId,
});
return {
status: 'success',
result: flatFieldMetadatas,
};
};
@@ -0,0 +1,124 @@
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { v4 } from 'uuid';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { type RelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/relation-field-metadata-type.type';
import { computeRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-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';
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
type ComputeFieldMetadataRelationSettingsForRelationTypeArgs = {
relationType: RelationType;
joinColumnName: string;
};
const computeFieldMetadataRelationSettingsForRelationType = ({
relationType,
joinColumnName,
}: ComputeFieldMetadataRelationSettingsForRelationTypeArgs) => {
if (relationType === RelationType.MANY_TO_ONE) {
return {
relationType: RelationType.MANY_TO_ONE,
onDelete: RelationOnDeleteAction.SET_NULL,
joinColumnName,
};
}
return {
relationType: RelationType.ONE_TO_MANY,
};
};
type GenerateRelationOrMorphRelationFlatFieldMetadataPairArgs = {
targetFlatObjectMetadata: FlatObjectMetadata;
sourceFlatObjectMetadata: FlatObjectMetadata;
sourceFlatObjectMetadataJoinColumnName: string;
createFieldInput: Omit<CreateFieldInput, 'workspaceId'> &
Required<
Pick<CreateFieldInput, 'relationCreationPayload' | 'type' | 'name'>
> & { type: RelationFieldMetadataType };
workspaceId: string;
};
export const generateRelationOrMorphRelationFlatFieldMetadataPair = ({
createFieldInput,
sourceFlatObjectMetadata,
targetFlatObjectMetadata,
workspaceId,
sourceFlatObjectMetadataJoinColumnName,
}: GenerateRelationOrMorphRelationFlatFieldMetadataPairArgs): FlatFieldMetadata<RelationFieldMetadataType>[] => {
const { relationCreationPayload } = createFieldInput;
const sourceFlatFieldMetadataSettings =
computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: sourceFlatObjectMetadataJoinColumnName,
relationType: relationCreationPayload.type,
});
const targetRelationTargetFieldMetadataId = v4();
const sourceRelationTargetFieldMetadataId = v4();
const sourceFlatFieldMetadata: Omit<
FlatFieldMetadata<RelationFieldMetadataType>,
'flatRelationTargetFieldMetadata'
> = {
...getDefaultFlatFieldMetadata({
createFieldInput,
workspaceId,
fieldMetadataId: sourceRelationTargetFieldMetadataId,
}),
objectMetadataId: sourceFlatObjectMetadata.id,
icon: createFieldInput.icon ?? 'IconRelationOneToMany',
type: createFieldInput.type,
defaultValue: null,
settings: sourceFlatFieldMetadataSettings,
options: null,
relationTargetFieldMetadataId: targetRelationTargetFieldMetadataId,
relationTargetObjectMetadataId: targetFlatObjectMetadata.id,
flatRelationTargetObjectMetadata: targetFlatObjectMetadata,
};
const targetCreateFieldInput: CreateFieldInput = {
icon: relationCreationPayload.targetFieldIcon ?? 'Icon123',
label: relationCreationPayload.targetFieldLabel,
name: computeMetadataNameFromLabel(
relationCreationPayload.targetFieldLabel,
),
objectMetadataId: targetFlatObjectMetadata.id,
type: FieldMetadataType.RELATION,
workspaceId,
};
const targetFlatFieldMetadataSettings =
computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: computeRelationFieldJoinColumnName({
name: targetCreateFieldInput.name,
}),
relationType:
relationCreationPayload.type === RelationType.ONE_TO_MANY
? RelationType.MANY_TO_ONE
: RelationType.ONE_TO_MANY,
});
const targetFlatFieldMetadata: FlatFieldMetadata<FieldMetadataType.RELATION> =
{
...getDefaultFlatFieldMetadata({
createFieldInput: targetCreateFieldInput,
workspaceId,
fieldMetadataId: targetRelationTargetFieldMetadataId,
}),
type: FieldMetadataType.RELATION,
defaultValue: null,
settings: targetFlatFieldMetadataSettings,
options: null,
relationTargetFieldMetadataId: sourceRelationTargetFieldMetadataId,
relationTargetObjectMetadataId: sourceFlatObjectMetadata.id,
flatRelationTargetFieldMetadata: sourceFlatFieldMetadata,
flatRelationTargetObjectMetadata: sourceFlatObjectMetadata,
};
return [
{
...sourceFlatFieldMetadata,
flatRelationTargetFieldMetadata: targetFlatFieldMetadata,
},
targetFlatFieldMetadata,
] satisfies FlatFieldMetadata<RelationFieldMetadataType>[];
};
@@ -0,0 +1,122 @@
import { type RelationCreationPayload } from 'twenty-shared/types';
import { t } from '@lingui/core/macro';
import { isDefined } from 'class-validator';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import {
type FailedFieldInputTranspilation,
type SuccessfulFieldInputTranspilation,
type FieldInputTranspilationResult,
} from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { validateRelationCreationPayload } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util';
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';
type RelationCreationPayloadAndObjectMetadata = {
relationCreationPayload: RelationCreationPayload;
targetFlatObjectMetadata: FlatObjectMetadata;
};
type ValidateMorphRelationCreationPayloadUtilArgs = {
morphRelationCreationPayload: RelationCreationPayload[];
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
export const validateMorphRelationCreationPayload = async ({
existingFlatObjectMetadataMaps,
morphRelationCreationPayload,
}: ValidateMorphRelationCreationPayloadUtilArgs): Promise<
FieldInputTranspilationResult<RelationCreationPayloadAndObjectMetadata[]>
> => {
const allRelationType = [
...new Set(
morphRelationCreationPayload.map(
(relationCreationPayload) => relationCreationPayload.type,
),
),
];
if (allRelationType.length > 1) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
message: 'Morph relation relations must have the same relation type',
userFriendlyMessage: t`Morph relation relations must have the same relation type`,
},
};
}
const allRelatedObjectMetadataIds = morphRelationCreationPayload.map(
(relationCreationPayload) => relationCreationPayload.targetObjectMetadataId,
);
const allRelatedObjectMetadataIdsSet = [
...new Set(allRelatedObjectMetadataIds),
];
if (
allRelatedObjectMetadataIds.length !== allRelatedObjectMetadataIdsSet.length
) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
message:
'Morph relation relations must have only relation to the same object metadata',
userFriendlyMessage: t`Morph relation relations must have only relation to the same object metadata`,
},
};
}
const relationCreationPayloadReport: {
success: SuccessfulFieldInputTranspilation<RelationCreationPayloadAndObjectMetadata>[];
failed: FailedFieldInputTranspilation[];
} = {
success: [],
failed: [],
};
for (const rawRelationCreationPayload of morphRelationCreationPayload) {
const relationValidationResult = await validateRelationCreationPayload({
existingFlatObjectMetadataMaps,
relationCreationPayload: rawRelationCreationPayload,
});
if (relationValidationResult.status === 'fail') {
relationCreationPayloadReport.failed.push(relationValidationResult);
continue;
}
const { relationCreationPayload, targetFlatObjectMetadata } =
relationValidationResult.result;
relationCreationPayloadReport.success.push({
status: 'success',
result: {
relationCreationPayload,
targetFlatObjectMetadata,
},
});
}
if (relationCreationPayloadReport.failed.length > 0) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
message: 'Morph relation input transpilation failed',
userFriendlyMessage: t`Invalid morph relation input`,
value: relationCreationPayloadReport.failed
.map((failedTranspilation) => failedTranspilation.error.value)
.filter(isDefined),
},
};
}
return {
status: 'success',
result: relationCreationPayloadReport.success.map(
(relationCreationPayloadValidation) =>
relationCreationPayloadValidation.result,
),
};
};
@@ -0,0 +1,82 @@
import { t } from '@lingui/core/macro';
import { type RelationCreationPayload } from 'twenty-shared/types';
import {
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload-or-throw.util';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.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 { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
type ValidateRelationCreationPayloadUtilArgs = {
relationCreationPayload: RelationCreationPayload;
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
export const validateRelationCreationPayload = async ({
existingFlatObjectMetadataMaps,
relationCreationPayload: rawRelationCreationPayload,
}: ValidateRelationCreationPayloadUtilArgs): Promise<
FieldInputTranspilationResult<{
relationCreationPayload: RelationCreationPayload;
targetFlatObjectMetadata: FlatObjectMetadata;
}>
> => {
const relationCreationPayload =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawRelationCreationPayload,
['targetFieldIcon', 'targetFieldLabel', 'targetObjectMetadataId', 'type'],
);
try {
await validateRelationCreationPayloadOrThrow(relationCreationPayload);
} catch (error) {
if (error instanceof FieldMetadataException) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
message: `Relation creation payload is invalid ${JSON.stringify(relationCreationPayload)}`,
userFriendlyMessage: t`Invalid relation creation payload`,
value: relationCreationPayload,
},
};
} else {
throw error;
}
}
const targetFlatObjectMetadataWithFlatFieldMaps =
existingFlatObjectMetadataMaps.byId[
relationCreationPayload.targetObjectMetadataId
];
if (!isDefined(targetFlatObjectMetadataWithFlatFieldMaps)) {
return {
status: 'fail',
error: {
code: FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
message: `Object metadata relation target not found for relation creation payload`,
userFriendlyMessage: t`Object targeted by field to create not found`,
value: relationCreationPayload,
},
};
}
return {
status: 'success',
result: {
relationCreationPayload,
targetFlatObjectMetadata:
fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata(
targetFlatObjectMetadataWithFlatFieldMaps,
),
},
};
};
@@ -2,14 +2,17 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { FieldMetadataType } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import {
capitalize,
computeMorphRelationFieldJoinColumnName,
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 { computeMorphRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-join-column-name.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import {
ObjectMetadataException,
@@ -40,7 +40,7 @@ exports[`Workspace migration builder field actions test suite It should build an
"standardId": null,
"standardOverrides": null,
"type": "TEXT",
"uniqueIdentifier": "field-metadata-unique-identifier-1",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -79,7 +79,7 @@ exports[`Workspace migration builder field actions test suite It should build an
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "field-metadata-unique-identifier-2",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -107,7 +107,7 @@ exports[`Workspace migration builder field actions test suite It should build an
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": "d78ec657-74a4-4652-a350-1f44ff62970a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -130,7 +130,7 @@ exports[`Workspace migration builder field actions test suite It should build an
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "field-metadata-unique-identifier-1",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -168,7 +168,7 @@ exports[`Workspace migration builder field actions test suite It should build an
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "field-metadata-unique-identifier-2",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -196,7 +196,7 @@ exports[`Workspace migration builder field actions test suite It should build an
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": "d78ec657-74a4-4652-a350-1f44ff62970a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -219,7 +219,7 @@ exports[`Workspace migration builder field actions test suite It should build an
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "field-metadata-unique-identifier-1",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -247,7 +247,7 @@ exports[`Workspace migration builder field actions test suite It should build an
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": "d34e0f07-1b8c-4de0-938e-599cf05e1f7f",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -270,7 +270,7 @@ exports[`Workspace migration builder field actions test suite It should build an
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "field-metadata-unique-identifier-2",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -370,7 +370,7 @@ exports[`Workspace migration builder index actions test suite It should build an
"isUnique": false,
"name": "defaultFlatIndexMetadataName",
"objectMetadataId": Any<String>,
"uniqueIdentifier": "field-metadata-unique-identifier-1",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
@@ -396,7 +396,7 @@ exports[`Workspace migration builder index actions test suite It should build an
"isUnique": false,
"name": "new index name",
"objectMetadataId": Any<String>,
"uniqueIdentifier": "field-metadata-unique-identifier-1",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
@@ -437,7 +437,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "UUID",
"uniqueIdentifier": "20202020-eda0-4cee-9577-3eb357e3c22b",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -469,7 +469,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "TEXT",
"uniqueIdentifier": "20202020-ba07-4ffd-ba63-009491f5749c",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -501,7 +501,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "DATE_TIME",
"uniqueIdentifier": "20202020-66ac-4502-9975-e4d959c50311",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -533,7 +533,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "DATE_TIME",
"uniqueIdentifier": "20202020-d767-4622-bdcf-d8a084834d86",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -565,7 +565,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "DATE_TIME",
"uniqueIdentifier": "20202020-b9a7-48d8-8387-b9a3090a50ec",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -600,7 +600,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "ACTOR",
"uniqueIdentifier": "20202020-be0e-4971-865b-32ca87cbb315",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -632,7 +632,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "POSITION",
"uniqueIdentifier": "20202020-c2bd-4e16-bb9a-c8b0411bf49d",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -672,7 +672,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-0be6-444e-88f0-12e0c64ac27d",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -698,7 +698,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-f1ef-4ba4-8f33-1a4577afa477",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -738,7 +738,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-78f7-4d7d-86e9-7562ced0b381",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -764,7 +764,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-a4a7-4686-b296-1c6c3482ee21",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -804,7 +804,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-ca2d-47c0-8253-9d0b662fb01a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -830,7 +830,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-8d59-46ca-b7b2-73d167712134",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -870,7 +870,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-52b6-4d5e-8f21-ed8b7c09c4a9",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -896,7 +896,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-01fd-4f37-99dc-9427a444018a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -936,7 +936,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-b98f-473d-85fb-8cebc40b9a50",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -962,7 +962,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-0860-4566-b865-bff3c626c303",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -994,7 +994,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "TS_VECTOR",
"uniqueIdentifier": "70e56537-18ef-4811-b1c7-0a444006b815",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1025,7 +1025,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": "d78ec657-74a4-4652-a350-1f44ff62970a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1151,7 +1151,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-c54e-4804-8b0d-8795e5166432",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1177,7 +1177,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-01fd-4f37-99dc-9427a444018a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1209,7 +1209,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "UUID",
"uniqueIdentifier": "20202020-eda0-4cee-9577-3eb357e3c22b",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1241,7 +1241,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "TEXT",
"uniqueIdentifier": "20202020-ba07-4ffd-ba63-009491f5749c",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1273,7 +1273,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "DATE_TIME",
"uniqueIdentifier": "20202020-66ac-4502-9975-e4d959c50311",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1305,7 +1305,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "DATE_TIME",
"uniqueIdentifier": "20202020-d767-4622-bdcf-d8a084834d86",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1337,7 +1337,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "DATE_TIME",
"uniqueIdentifier": "20202020-b9a7-48d8-8387-b9a3090a50ec",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1372,7 +1372,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "ACTOR",
"uniqueIdentifier": "20202020-be0e-4971-865b-32ca87cbb315",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1404,7 +1404,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "POSITION",
"uniqueIdentifier": "20202020-c2bd-4e16-bb9a-c8b0411bf49d",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1444,7 +1444,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-d88f-4020-8c85-d6c600903762",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1470,7 +1470,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-f1ef-4ba4-8f33-1a4577afa477",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1510,7 +1510,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-1bd7-4d10-80dc-968b9f5d1a8a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1536,7 +1536,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-a4a7-4686-b296-1c6c3482ee21",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1576,7 +1576,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-b715-4658-8b6a-5359824dbddd",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1602,7 +1602,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-8d59-46ca-b7b2-73d167712134",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1642,7 +1642,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-07d9-4d04-8b73-a39ab65943f1",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1668,7 +1668,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-0860-4566-b865-bff3c626c303",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1700,7 +1700,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "TS_VECTOR",
"uniqueIdentifier": "70e56537-18ef-4811-b1c7-0a444006b815",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1775,7 +1775,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "SELECT",
"uniqueIdentifier": "0292cc51-246f-40b8-99e8-3345dac0e688",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1850,7 +1850,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "MULTI_SELECT",
"uniqueIdentifier": "5aaf6be0-b9f5-4222-9810-941d5ab58527",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1882,7 +1882,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "TEXT",
"uniqueIdentifier": "2e726631-d80a-4594-9a5a-3cca0c2a50a9",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1914,7 +1914,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "NUMBER",
"uniqueIdentifier": "ed1573cf-b29e-4d4f-a8e3-19d472d28c0d",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1955,7 +1955,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "ADDRESS",
"uniqueIdentifier": "7b05e127-d0ea-422a-8f98-ef48f41d4388",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -1992,7 +1992,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "PHONES",
"uniqueIdentifier": "92c856ef-5b4b-4202-8c23-b2f22f959eda",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2027,7 +2027,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "EMAILS",
"uniqueIdentifier": "2c89697d-925a-485c-a0ed-adf2f1228e0f",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2059,7 +2059,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "DATE",
"uniqueIdentifier": "13c5b4ed-5746-494f-856b-081bdbb4a8ec",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2091,7 +2091,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "BOOLEAN",
"uniqueIdentifier": "ec3bf27e-70e6-4d4a-932e-6ef026013603",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2127,7 +2127,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "LINKS",
"uniqueIdentifier": "8aa0a700-c308-4d8b-8fbd-f545daa8980a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2162,7 +2162,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "CURRENCY",
"uniqueIdentifier": "37421848-b64d-4827-be33-da4e337acbb6",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2197,7 +2197,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "FULL_NAME",
"uniqueIdentifier": "84b874dc-983d-47eb-9c67-54f557a99f60",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2260,7 +2260,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "RATING",
"uniqueIdentifier": "42e6c0d2-6d00-4c77-b179-890af8c56236",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2292,7 +2292,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "RICH_TEXT",
"uniqueIdentifier": "fa0ec409-0f29-4b19-b304-31d1018a2344",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2324,7 +2324,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "ARRAY",
"uniqueIdentifier": "2b806a74-e7a5-4ec7-b902-67cf811e8bda",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2356,7 +2356,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"type": "RAW_JSON",
"uniqueIdentifier": "a79bd7c8-c7ce-416a-9d12-a05335c9c596",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2387,7 +2387,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": "d34e0f07-1b8c-4de0-938e-599cf05e1f7f",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2601,7 +2601,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "UUID",
"uniqueIdentifier": "20202020-eda0-4cee-9577-3eb357e3c22b",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2633,7 +2633,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "TEXT",
"uniqueIdentifier": "20202020-ba07-4ffd-ba63-009491f5749c",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2665,7 +2665,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "DATE_TIME",
"uniqueIdentifier": "20202020-66ac-4502-9975-e4d959c50311",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2697,7 +2697,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "DATE_TIME",
"uniqueIdentifier": "20202020-d767-4622-bdcf-d8a084834d86",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2729,7 +2729,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "DATE_TIME",
"uniqueIdentifier": "20202020-b9a7-48d8-8387-b9a3090a50ec",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2764,7 +2764,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "ACTOR",
"uniqueIdentifier": "20202020-be0e-4971-865b-32ca87cbb315",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2796,7 +2796,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "POSITION",
"uniqueIdentifier": "20202020-c2bd-4e16-bb9a-c8b0411bf49d",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2836,7 +2836,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-0be6-444e-88f0-12e0c64ac27d",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2862,7 +2862,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-f1ef-4ba4-8f33-1a4577afa477",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2902,7 +2902,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-78f7-4d7d-86e9-7562ced0b381",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2928,7 +2928,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-a4a7-4686-b296-1c6c3482ee21",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2968,7 +2968,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-ca2d-47c0-8253-9d0b662fb01a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -2994,7 +2994,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-8d59-46ca-b7b2-73d167712134",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -3034,7 +3034,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-52b6-4d5e-8f21-ed8b7c09c4a9",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -3060,7 +3060,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-01fd-4f37-99dc-9427a444018a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -3100,7 +3100,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-b98f-473d-85fb-8cebc40b9a50",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -3126,7 +3126,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "RELATION",
"uniqueIdentifier": "20202020-0860-4566-b865-bff3c626c303",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -3158,7 +3158,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": Any<String>,
"standardOverrides": null,
"type": "TS_VECTOR",
"uniqueIdentifier": "70e56537-18ef-4811-b1c7-0a444006b815",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -3189,7 +3189,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"uniqueIdentifier": "d78ec657-74a4-4652-a350-1f44ff62970a",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
@@ -3206,7 +3206,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"isUnique": false,
"name": "defaultFlatIndexMetadataName",
"objectMetadataId": Any<String>,
"uniqueIdentifier": "field-metadata-unique-identifier-1",
"uniqueIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
@@ -27,6 +27,7 @@ export const extractRecordIdsAndDatesAsExpectAny = (
if (
key.endsWith('Id') ||
key === 'uniqueIdentifier' ||
key === 'id' ||
key === 'updatedAt' ||
key === 'deletedAt' ||