[REQUIRES_CACHE_FLUSH_FOR_FIELD_AND_OBJECT]FlatFieldMetadata and FlatObjectMetadata required universal (#17557)

# Introduction
In this PR we're migrating both the `field` and `object` metadata to be
using the new `FlatEntityFromV2` that requires all the
`UniversalFlatEntityExtraProperties` to be spread at the flat entity
root.

This means that we have to update all of their flat declaration

This type swap allows to isole a specific entity migration into his own
type scope and avoid to have everything handled at once

```ts
/**
 * Currently under migration but aims to replace FlatEntity afterwards
 */
export type FlatEntityFromV2<
  TEntity,
  TMetadataName extends AllMetadataName | undefined = undefined,
  TInnerFlatEntity extends { __universal?: unknown } = FlatEntityFrom<
    TEntity,
    TMetadataName
  >,
> = Omit<TInnerFlatEntity, '__universal'> & TInnerFlatEntity['__universal'];

```

## Impact
Both object and field:
- Create input transpilation utils
- from entity to flat tools
- mocks

## Note
Removed from the universal extra properties the jsonb properties that do
not contain a serialized

## Next
Next step is to incrementally make the builder and runner expect
`UniversalFlatEntity` for both of these metadata
This way we will be able to fully migrate an entity e2e typesafely
This commit is contained in:
Paul Rastoin
2026-01-29 19:11:19 +01:00
committed by GitHub
parent e91457a47e
commit fbbd8fe967
69 changed files with 1244 additions and 498 deletions
@@ -6,6 +6,7 @@ import {
RelationType,
} from 'twenty-shared/types';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import {
generateMorphOrRelationFlatFieldMetadataPair,
type SourceTargetMorphOrRelationFlatFieldAndFlatIndex,
@@ -13,6 +14,24 @@ import {
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';
const MOCK_FLAT_APPLICATION: FlatApplication = {
id: '20202020-81ee-42da-a281-668632f32fe7',
universalIdentifier: '20202020-81ee-42da-a281-668632f32fe7',
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
name: 'Workspace Custom Application',
description: null,
version: null,
sourceType: 'local',
sourcePath: '',
logicFunctionLayerId: null,
defaultLogicFunctionRoleId: null,
defaultLogicFunctionRole: null,
canBeUninstalled: false,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
};
type GenerateMorphOrRelationFlatFieldMetadataPairTestInput = Parameters<
typeof generateMorphOrRelationFlatFieldMetadataPair
>[0];
@@ -28,8 +47,6 @@ type TestCase = EachTestingContext<{
describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () => {
const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
const mockWorkspaceCustomApplicationId =
'20202020-81ee-42da-a281-668632f32fe7';
describe('Success cases', () => {
const testCases: TestCase[] = [
@@ -43,7 +60,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'petId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'pets',
label: 'Pets',
@@ -79,7 +96,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'petId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'pets',
label: 'Pets',
@@ -115,7 +132,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'petId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'pets',
label: 'Pets',
@@ -151,7 +168,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'targetPetId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
createFieldInput: {
name: 'targetPet',
@@ -188,7 +205,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'targetPetId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
createFieldInput: {
name: 'targetPet',
@@ -225,7 +242,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.MORPH_RELATION,
sourceFlatObjectMetadataJoinColumnName: 'targetPetId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
createFieldInput: {
name: 'targetPet',
@@ -4,17 +4,19 @@ import {
} from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util';
import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { getMorphNameFromMorphFieldMetadataName } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-morph-name-from-morph-field-metadata-name.util';
type ComputeFlatFieldToUpdateFromMorphRelationUpdatePayloadArgs = {
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
morphRelationsUpdatePayload?: RelationUpdatePayload[];
fieldMetadataToUpdate: FlatFieldMetadata<FieldMetadataType.MORPH_RELATION>;
@@ -22,7 +24,7 @@ type ComputeFlatFieldToUpdateFromMorphRelationUpdatePayloadArgs = {
};
export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({
workspaceCustomApplicationId,
flatApplication,
flatFieldMetadataMaps,
morphRelationsUpdatePayload,
fieldMetadataToUpdate,
@@ -68,6 +70,13 @@ export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({
const commonTargetFieldName = initialTargetFieldMetadata.name;
const commonObjectMetadataId = initialTargetFieldMetadata.objectMetadataId;
const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings(
fieldMetadataToUpdate.settings,
);
const junctionTargetFlatFieldMetadata = isDefined(junctionTargetFieldId)
? flatFieldMetadataMaps.byId[junctionTargetFieldId]
: undefined;
morphRelationsUpdatePayload.forEach((morphRelationUpdatePayload) => {
const { targetObjectMetadataId } = morphRelationUpdatePayload;
@@ -101,13 +110,14 @@ export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({
targetFlatObjectMetadata: newTargetObjectMetadata,
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
workspaceId: fieldMetadataToUpdate.workspaceId,
workspaceCustomApplicationId,
flatApplication,
sourceFlatObjectMetadataJoinColumnName:
computeMorphOrRelationFieldJoinColumnName({
name: computedMorphName,
}),
morphId: fieldMetadataToUpdate.morphId,
targetFieldName: commonTargetFieldName,
junctionTargetFlatFieldMetadata,
});
flatFieldMetadatasToCreate.push(...flatFieldMetadatas);
@@ -5,6 +5,7 @@ type JunctionTargetSettings = {
junctionTargetFieldId?: string;
};
// TODO refactor using either type predicate or FieldMetadataType discriminating union
// Extracts junction target settings from untyped settings input
// This function handles the boundary where settings come from external API input
export const extractJunctionTargetSettingsFromSettings = (
@@ -10,6 +10,7 @@ import {
} from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { generateRatingOptions } from 'src/engine/metadata-modules/field-metadata/utils/generate-rating-optionts.util';
@@ -25,14 +26,15 @@ import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-m
export type FromCreateFieldInputToFlatObjectMetadataArgs = {
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
workspaceCustomApplicationId: string;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps'>;
flatApplication: FlatApplication;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps' | 'flatFieldMetadataMaps'>;
export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
createFieldInput: rawCreateFieldInput,
workspaceId,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
workspaceCustomApplicationId,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatApplication,
}: FromCreateFieldInputToFlatObjectMetadataArgs): Promise<
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
@@ -77,7 +79,9 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
createFieldInput,
workspaceId,
fieldMetadataId,
workspaceCustomApplicationId,
flatApplication,
objectMetadataUniversalIdentifier:
parentFlatObjectMetadata.universalIdentifier,
});
switch (createFieldInput.type) {
@@ -88,21 +92,23 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
type: createFieldInput.type,
},
existingFlatObjectMetadataMaps,
existingFlatFieldMetadataMaps,
sourceFlatObjectMetadata: parentFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
});
}
case FieldMetadataType.RELATION: {
return await fromRelationCreateFieldInputToFlatFieldMetadatas({
existingFlatObjectMetadataMaps,
existingFlatFieldMetadataMaps,
sourceFlatObjectMetadata: parentFlatObjectMetadata,
createFieldInput: {
...createFieldInput,
type: createFieldInput.type,
},
workspaceId,
workspaceCustomApplicationId,
flatApplication,
});
}
case FieldMetadataType.RATING: {
@@ -106,33 +106,27 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = ({
[],
viewFieldIds: fieldMetadataEntity.viewFields.map(({ id }) => id),
viewFilterIds: fieldMetadataEntity.viewFilters.map(({ id }) => id),
__universal: {
universalIdentifier: fieldMetadataWithoutRelations.universalIdentifier,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier,
relationTargetFieldMetadataUniversalIdentifier,
viewFieldUniversalIdentifiers: fieldMetadataEntity.viewFields.map(
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier,
relationTargetFieldMetadataUniversalIdentifier,
viewFieldUniversalIdentifiers: fieldMetadataEntity.viewFields.map(
({ universalIdentifier }) => universalIdentifier,
),
viewFilterUniversalIdentifiers: fieldMetadataEntity.viewFilters.map(
({ universalIdentifier }) => universalIdentifier,
),
kanbanAggregateOperationViewUniversalIdentifiers:
fieldMetadataEntity.kanbanAggregateOperationViews.map(
({ universalIdentifier }) => universalIdentifier,
),
viewFilterUniversalIdentifiers: fieldMetadataEntity.viewFilters.map(
calendarViewUniversalIdentifiers: fieldMetadataEntity.calendarViews.map(
({ universalIdentifier }) => universalIdentifier,
),
mainGroupByFieldMetadataViewUniversalIdentifiers:
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(
({ universalIdentifier }) => universalIdentifier,
),
kanbanAggregateOperationViewUniversalIdentifiers:
fieldMetadataEntity.kanbanAggregateOperationViews.map(
({ universalIdentifier }) => universalIdentifier,
),
calendarViewUniversalIdentifiers: fieldMetadataEntity.calendarViews.map(
({ universalIdentifier }) => universalIdentifier,
),
mainGroupByFieldMetadataViewUniversalIdentifiers:
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(
({ universalIdentifier }) => universalIdentifier,
) ?? [],
settings: settingsWithUniversalIdentifiers,
defaultValue: fieldMetadataWithoutRelations.defaultValue,
options: fieldMetadataWithoutRelations.options,
standardOverrides: fieldMetadataWithoutRelations.standardOverrides,
},
) ?? [],
universalSettings: settingsWithUniversalIdentifiers,
};
};
@@ -3,12 +3,14 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { 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 { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util';
import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-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 FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
@@ -19,16 +21,18 @@ type FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs = {
type: FieldMetadataType.MORPH_RELATION;
};
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
existingFlatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
};
export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
createFieldInput,
existingFlatObjectMetadataMaps,
existingFlatFieldMetadataMaps,
sourceFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
}: FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs): Promise<
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
@@ -66,6 +70,13 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
return morphRelationCreationPayloadValidation;
}
const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings(
createFieldInput.settings,
);
const junctionTargetFlatFieldMetadata = isDefined(junctionTargetFieldId)
? existingFlatFieldMetadataMaps.byId[junctionTargetFieldId]
: undefined;
const morphRelationCreationPayload =
morphRelationCreationPayloadValidation.result;
const morphId = v4();
@@ -95,7 +106,8 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
workspaceId,
morphId,
workspaceCustomApplicationId,
flatApplication,
junctionTargetFlatFieldMetadata,
});
return {
@@ -2,11 +2,14 @@ import { msg } from '@lingui/core/macro';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { 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 { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util';
import {
generateMorphOrRelationFlatFieldMetadataPair,
type SourceTargetMorphOrRelationFlatFieldAndFlatIndex,
@@ -19,16 +22,18 @@ type FromRelationCreateFieldInputToFlatFieldMetadataArgs = {
type: FieldMetadataType.RELATION;
};
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
existingFlatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
};
export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
existingFlatObjectMetadataMaps,
existingFlatFieldMetadataMaps,
sourceFlatObjectMetadata,
createFieldInput,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
FieldInputTranspilationResult<SourceTargetMorphOrRelationFlatFieldAndFlatIndex>
> => {
@@ -59,6 +64,13 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
const { relationCreationPayload, targetFlatObjectMetadata } =
relationValidationResult.result;
const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings(
createFieldInput.settings,
);
const junctionTargetFlatFieldMetadata = isDefined(junctionTargetFieldId)
? existingFlatFieldMetadataMaps.byId[junctionTargetFieldId]
: undefined;
const generateResult = generateMorphOrRelationFlatFieldMetadataPair({
createFieldInput: {
...createFieldInput,
@@ -72,7 +84,8 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
targetFlatObjectMetadata,
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
junctionTargetFlatFieldMetadata,
});
return {
@@ -5,6 +5,7 @@ import {
isDefined,
} from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
@@ -23,7 +24,7 @@ import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-fiel
type FromUpdateFieldInputToFlatFieldMetadataArgs = {
updateFieldInput: UpdateFieldInput;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
isSystemBuild: boolean;
} & Pick<
AllFlatEntityMaps,
@@ -41,7 +42,7 @@ type FlatFieldMetadataAndIndexToUpdate = {
flatFieldMetadatasToCreate: FlatFieldMetadata[];
} & FlatFieldMetadataUpdateSideEffects;
export const fromUpdateFieldInputToFlatFieldMetadata = ({
workspaceCustomApplicationId,
flatApplication,
flatIndexMaps,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatFieldMetadataMaps,
@@ -109,7 +110,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
FieldMetadataType.MORPH_RELATION,
)
? computeFlatFieldToUpdateFromMorphRelationUpdatePayload({
workspaceCustomApplicationId,
flatApplication,
morphRelationsUpdatePayload:
rawUpdateFieldInput?.morphRelationsUpdatePayload,
flatFieldMetadataMaps: flatFieldMetadataMaps,
@@ -149,7 +150,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
toFlatFieldMetadata,
flatViewMaps,
flatViewFieldMaps,
workspaceCustomApplicationId,
flatApplication,
});
if (sideEffectResult.status === 'fail') {
@@ -6,11 +6,11 @@ import {
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util';
import { generateIndexForFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util';
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
@@ -22,23 +22,39 @@ type ComputeFieldMetadataRelationSettingsForRelationTypeArgs = {
relationType: RelationType;
joinColumnName: string;
junctionTargetFieldId?: string;
junctionTargetFieldUniversalIdentifier?: string;
};
const computeFieldMetadataRelationSettingsForRelationType = ({
relationType,
joinColumnName,
junctionTargetFieldId,
}: ComputeFieldMetadataRelationSettingsForRelationTypeArgs) => {
junctionTargetFieldUniversalIdentifier,
}: ComputeFieldMetadataRelationSettingsForRelationTypeArgs): Pick<
FlatFieldMetadata<MorphOrRelationFieldMetadataType>,
'settings' | 'universalSettings'
> => {
if (relationType === RelationType.MANY_TO_ONE) {
return {
const settings = {
relationType: RelationType.MANY_TO_ONE,
onDelete: RelationOnDeleteAction.SET_NULL,
joinColumnName,
};
return { settings, universalSettings: settings };
}
return {
relationType: RelationType.ONE_TO_MANY,
...(junctionTargetFieldId && { junctionTargetFieldId }),
settings: {
relationType: RelationType.ONE_TO_MANY,
...(junctionTargetFieldId && { junctionTargetFieldId }),
},
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
...(junctionTargetFieldUniversalIdentifier && {
junctionTargetFieldUniversalIdentifier,
}),
},
};
};
@@ -53,8 +69,9 @@ type GenerateMorphOrRelationFlatFieldMetadataPairArgs = {
> & { type: MorphOrRelationFieldMetadataType };
workspaceId: string;
morphId?: string | null;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
targetFieldName?: string;
junctionTargetFlatFieldMetadata?: FlatFieldMetadata;
};
export type SourceTargetMorphOrRelationFlatFieldAndFlatIndex = {
@@ -68,27 +85,30 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
targetFlatObjectMetadata,
targetFlatFieldMetadataType,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
sourceFlatObjectMetadataJoinColumnName,
morphId = null,
targetFieldName,
junctionTargetFlatFieldMetadata,
}: GenerateMorphOrRelationFlatFieldMetadataPairArgs): SourceTargetMorphOrRelationFlatFieldAndFlatIndex => {
const sourceFlatFieldMetadataType = createFieldInput.type;
const { relationCreationPayload } = createFieldInput;
const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings(
createFieldInput.settings,
);
const sourceFlatFieldMetadataSettings =
computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: sourceFlatObjectMetadataJoinColumnName,
relationType: relationCreationPayload.type,
junctionTargetFieldId,
});
const {
settings: sourceFlatFieldMetadataSettings,
universalSettings: sourceFlatFieldMetadataUniversalSettings,
} = computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: sourceFlatObjectMetadataJoinColumnName,
relationType: relationCreationPayload.type,
junctionTargetFieldId: junctionTargetFlatFieldMetadata?.id,
junctionTargetFieldUniversalIdentifier:
junctionTargetFlatFieldMetadata?.universalIdentifier,
});
const targetRelationTargetFieldMetadataId = v4();
const sourceRelationTargetFieldMetadataId = v4();
const sourceFieldUniversalIdentifier = v4();
const targetFieldUniversalIdentifier = v4();
const defaultDescriptionFromField =
buildDescriptionForRelationFieldMetadataOnFromField({
@@ -105,10 +125,15 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
'flatRelationTargetFieldMetadata'
> = {
...getDefaultFlatFieldMetadata({
createFieldInput,
createFieldInput: {
...createFieldInput,
universalIdentifier: sourceFieldUniversalIdentifier,
},
workspaceId,
fieldMetadataId: sourceRelationTargetFieldMetadataId,
workspaceCustomApplicationId,
flatApplication,
objectMetadataUniversalIdentifier:
sourceFlatObjectMetadata.universalIdentifier,
}),
morphId:
sourceFlatFieldMetadataType === FieldMetadataType.MORPH_RELATION
@@ -121,9 +146,14 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
createFieldInput.description ?? defaultDescriptionFromField.description,
defaultValue: null,
settings: sourceFlatFieldMetadataSettings,
universalSettings: sourceFlatFieldMetadataUniversalSettings,
options: null,
relationTargetFieldMetadataId: targetRelationTargetFieldMetadataId,
relationTargetObjectMetadataId: targetFlatObjectMetadata.id,
relationTargetObjectMetadataUniversalIdentifier:
targetFlatObjectMetadata.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier:
targetFieldUniversalIdentifier,
};
const targetCreateFieldInput: CreateFieldInput = {
@@ -141,25 +171,32 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
workspaceId,
isSystem: createFieldInput.isSystem ?? false,
};
const targetFlatFieldMetadataSettings =
computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: computeMorphOrRelationFieldJoinColumnName({
name: targetCreateFieldInput.name,
}),
relationType:
relationCreationPayload.type === RelationType.ONE_TO_MANY
? RelationType.MANY_TO_ONE
: RelationType.ONE_TO_MANY,
});
const {
settings: targetFlatFieldMetadataSettings,
universalSettings: targetFlatFieldMetadataUniversalSettings,
} = computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: computeMorphOrRelationFieldJoinColumnName({
name: targetCreateFieldInput.name,
}),
relationType:
relationCreationPayload.type === RelationType.ONE_TO_MANY
? RelationType.MANY_TO_ONE
: RelationType.ONE_TO_MANY,
});
const targetFlatFieldMetadata: FlatFieldMetadata<
typeof targetFlatFieldMetadataType
> = {
...getDefaultFlatFieldMetadata({
createFieldInput: targetCreateFieldInput,
createFieldInput: {
...targetCreateFieldInput,
universalIdentifier: targetFieldUniversalIdentifier,
},
workspaceId,
fieldMetadataId: targetRelationTargetFieldMetadataId,
workspaceCustomApplicationId,
flatApplication,
objectMetadataUniversalIdentifier:
targetFlatObjectMetadata.universalIdentifier,
}),
morphId:
targetFlatFieldMetadataType === FieldMetadataType.MORPH_RELATION
@@ -168,9 +205,14 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
type: targetFlatFieldMetadataType,
defaultValue: null,
settings: targetFlatFieldMetadataSettings,
universalSettings: targetFlatFieldMetadataUniversalSettings,
options: null,
relationTargetFieldMetadataId: sourceRelationTargetFieldMetadataId,
relationTargetObjectMetadataId: sourceFlatObjectMetadata.id,
relationTargetObjectMetadataUniversalIdentifier:
sourceFlatObjectMetadata.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier:
sourceFieldUniversalIdentifier,
};
const indexMetadata: FlatIndexMetadata = generateIndexForFlatFieldMetadata({
@@ -1,6 +1,7 @@
import { extractAndSanitizeObjectStringFields } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { generateDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/generate-default-value';
import { generateNullable } from 'src/engine/metadata-modules/field-metadata/utils/generate-nullable';
@@ -10,13 +11,15 @@ type GetDefaultFlatFieldMetadataArgs = {
fieldMetadataId: string;
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
objectMetadataUniversalIdentifier: string;
};
export const getDefaultFlatFieldMetadata = ({
createFieldInput,
fieldMetadataId,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
objectMetadataUniversalIdentifier,
}: GetDefaultFlatFieldMetadataArgs) => {
const { defaultValue, settings } = extractAndSanitizeObjectStringFields(
createFieldInput,
@@ -58,9 +61,18 @@ export const getDefaultFlatFieldMetadata = ({
updatedAt: createdAt,
isUIReadOnly: createFieldInput.isUIReadOnly ?? false,
morphId: null,
applicationId:
createFieldInput.applicationId ?? workspaceCustomApplicationId,
applicationId: flatApplication.id,
viewFilterIds: [],
kanbanAggregateOperationViewIds: [],
applicationUniversalIdentifier: flatApplication.universalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
} as const satisfies FlatFieldMetadata;
};
@@ -1,6 +1,7 @@
import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
@@ -41,7 +42,7 @@ type HandleFlatFieldMetadataUpdateSideEffectArgs = FromTo<
| 'flatViewMaps'
| 'flatViewFieldMaps'
> & {
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
};
export const FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS: FlatFieldMetadataUpdateSideEffects =
@@ -70,7 +71,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatViewGroupMaps,
flatViewMaps,
flatViewFieldMaps,
workspaceCustomApplicationId,
flatApplication,
}: HandleFlatFieldMetadataUpdateSideEffectArgs): FieldInputTranspilationResult<FlatFieldMetadataUpdateSideEffects> => {
const sideEffectResult = structuredClone(
FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS,
@@ -130,7 +131,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatIndexMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
workspaceCustomApplicationId,
flatApplication,
});
if (indexChangesSideEffectResult.status === 'fail') {
@@ -2,6 +2,7 @@ import { msg } from '@lingui/core/macro';
import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -28,7 +29,7 @@ type FromToFlatFieldMetadataAndFlatEntityMaps = FromTo<
AllFlatEntityMaps,
'flatObjectMetadataMaps' | 'flatFieldMetadataMaps' | 'flatIndexMaps'
> & {
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
};
const FIELD_METADATA_UPDATE_INDEX_SIDE_EFFECT: FieldMetadataUpdateIndexSideEffect =
{
@@ -43,7 +44,7 @@ export const handleIndexChangesDuringFieldUpdate = ({
flatIndexMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
workspaceCustomApplicationId,
flatApplication,
}: FromToFlatFieldMetadataAndFlatEntityMaps): FieldInputTranspilationResult<FieldMetadataUpdateIndexSideEffect> => {
if (
!hasIndexRelevantChanges({
@@ -81,7 +82,7 @@ export const handleIndexChangesDuringFieldUpdate = ({
relatedIndexes,
flatObjectMetadata,
flatFieldMetadataMaps,
workspaceCustomApplicationId,
flatApplication,
});
};
@@ -127,13 +128,13 @@ const handleExistingIndexes = ({
relatedIndexes,
flatObjectMetadata,
flatFieldMetadataMaps,
workspaceCustomApplicationId,
flatApplication,
}: {
relatedIndexes: FlatIndexMetadata[];
flatObjectMetadata: FlatObjectMetadata;
flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps'];
} & FromTo<FlatFieldMetadata, 'flatFieldMetadata'> & {
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
}): FieldInputTranspilationResult<FieldMetadataUpdateIndexSideEffect> => {
if (
toFlatFieldMetadata.isUnique === false &&
@@ -155,7 +156,7 @@ const handleExistingIndexes = ({
if (
isDefined(uniqueIndexToDelete) &&
((isDefined(uniqueIndexToDelete.applicationId) &&
uniqueIndexToDelete.applicationId !== workspaceCustomApplicationId) ||
uniqueIndexToDelete.applicationId !== flatApplication.id) ||
!uniqueIndexToDelete.isCustom)
) {
return {