[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
@@ -100,5 +100,16 @@ export const createStandardFieldFlatMetadata = <
mainGroupByFieldMetadataViewIds: [],
createdAt: now,
updatedAt: now,
applicationUniversalIdentifier: twentyStandardApplicationId,
objectMetadataUniversalIdentifier:
STANDARD_OBJECTS[objectName].universalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
};
@@ -83,6 +83,10 @@ export const createStandardRelationFieldFlatMetadata = <
const targetFieldIds =
standardObjectMetadataRelatedEntityIds[targetObjectName].fields;
const targetObjectFields = STANDARD_OBJECTS[targetObjectName].fields;
const targetFieldDefinition =
targetObjectFields[targetFieldName as keyof typeof targetObjectFields];
return {
id: fieldIds[fieldName as keyof typeof fieldIds].id,
universalIdentifier: fieldDefinition.universalIdentifier,
@@ -117,5 +121,18 @@ export const createStandardRelationFieldFlatMetadata = <
mainGroupByFieldMetadataViewIds: [],
createdAt: now,
updatedAt: now,
applicationUniversalIdentifier: twentyStandardApplicationId,
objectMetadataUniversalIdentifier:
STANDARD_OBJECTS[objectName].universalIdentifier,
relationTargetObjectMetadataUniversalIdentifier:
STANDARD_OBJECTS[targetObjectName].universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier:
targetFieldDefinition.universalIdentifier,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
};
@@ -1,4 +1,5 @@
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type';
import { type StandardBuilderArgs } from 'src/engine/workspace-manager/twenty-standard-application/types/metadata-standard-buillder-args.type';
@@ -52,42 +53,56 @@ export const createStandardObjectFlatMetadata = <
standardObjectMetadataRelatedEntityIds,
twentyStandardApplicationId,
now,
}: CreateStandardObjectArgs<O>): FlatObjectMetadata => ({
universalIdentifier,
standardId: universalIdentifier,
applicationId: twentyStandardApplicationId,
workspaceId,
nameSingular,
namePlural,
labelSingular,
labelPlural,
description,
icon,
isCustom: false,
isRemote: false,
isActive: true,
isSystem,
isSearchable,
isAuditLogged,
isUIReadOnly,
isLabelSyncedWithName: false,
standardOverrides: null,
duplicateCriteria,
shortcut,
labelIdentifierFieldMetadataId:
standardObjectMetadataRelatedEntityIds[nameSingular].fields[
}: CreateStandardObjectArgs<O>): FlatObjectMetadata => {
const labelIdentifierFieldMetadataUniversalIdentifier =
// @ts-expect-error ignore
STANDARD_OBJECTS[nameSingular as keyof typeof STANDARD_OBJECTS].fields[
labelIdentifierFieldMetadataName
].id,
imageIdentifierFieldMetadataId: imageIdentifierFieldMetadataName
? standardObjectMetadataRelatedEntityIds[nameSingular].fields[
imageIdentifierFieldMetadataName
].id
: null,
targetTableName: 'DEPRECATED',
fieldIds: [],
indexMetadataIds: [],
viewIds: [],
createdAt: now,
updatedAt: now,
id: standardObjectMetadataRelatedEntityIds[nameSingular].id,
});
].universalIdentifier;
return {
universalIdentifier,
standardId: universalIdentifier,
applicationId: twentyStandardApplicationId,
workspaceId,
nameSingular,
namePlural,
labelSingular,
labelPlural,
description,
icon,
isCustom: false,
isRemote: false,
isActive: true,
isSystem,
isSearchable,
isAuditLogged,
isUIReadOnly,
isLabelSyncedWithName: false,
standardOverrides: null,
duplicateCriteria,
shortcut,
labelIdentifierFieldMetadataId:
standardObjectMetadataRelatedEntityIds[nameSingular].fields[
labelIdentifierFieldMetadataName
].id,
imageIdentifierFieldMetadataId: imageIdentifierFieldMetadataName
? standardObjectMetadataRelatedEntityIds[nameSingular].fields[
imageIdentifierFieldMetadataName
].id
: null,
targetTableName: 'DEPRECATED',
fieldIds: [],
indexMetadataIds: [],
viewIds: [],
createdAt: now,
updatedAt: now,
id: standardObjectMetadataRelatedEntityIds[nameSingular].id,
applicationUniversalIdentifier: twentyStandardApplicationId,
fieldUniversalIdentifiers: [],
viewUniversalIdentifiers: [],
indexMetadataUniversalIdentifiers: [],
labelIdentifierFieldMetadataUniversalIdentifier,
imageIdentifierFieldMetadataUniversalIdentifier: null,
};
};
@@ -1,57 +0,0 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
import { type ExtractJsonbProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type';
export const ALL_JSONB_PROPERTIES_BY_METADATA_NAME = {
fieldMetadata: {
defaultValue: 'defaultValue',
standardOverrides: 'standardOverrides',
options: 'options',
settings: 'settings',
},
objectMetadata: {
standardOverrides: 'standardOverrides',
duplicateCriteria: 'duplicateCriteria',
},
view: {},
viewField: {},
viewGroup: {},
viewFilter: { value: 'value' },
viewFilterGroup: {},
index: {},
role: {},
roleTarget: {},
rowLevelPermissionPredicate: { value: 'value' },
rowLevelPermissionPredicateGroup: {},
logicFunction: {
cronTriggerSettings: 'cronTriggerSettings',
databaseEventTriggerSettings: 'databaseEventTriggerSettings',
httpRouteTriggerSettings: 'httpRouteTriggerSettings',
publishedVersions: 'publishedVersions',
toolInputSchema: 'toolInputSchema',
},
webhook: {},
agent: {
responseFormat: 'responseFormat',
modelConfiguration: 'modelConfiguration',
},
skill: {},
pageLayout: {},
pageLayoutTab: {},
pageLayoutWidget: {
gridPosition: 'gridPosition',
configuration: 'configuration',
conditionalDisplay: 'conditionalDisplay',
},
commandMenuItem: {},
navigationMenuItem: {},
frontComponent: {},
} as const satisfies {
[P in AllMetadataName]: {
[K in ExtractJsonbProperties<MetadataEntity<P>>]: K;
};
};
export type AllJsonbPropertiesForMetadataName<T extends AllMetadataName> =
keyof (typeof ALL_JSONB_PROPERTIES_BY_METADATA_NAME)[T];
@@ -0,0 +1,42 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
import { type ExtractJsonbProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type';
export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = {
fieldMetadata: {
settings: 'settings',
},
objectMetadata: {},
view: {},
viewField: {},
viewGroup: {},
viewFilter: {},
viewFilterGroup: {},
index: {},
role: {},
roleTarget: {},
rowLevelPermissionPredicate: {},
rowLevelPermissionPredicateGroup: {},
logicFunction: {},
webhook: {},
agent: {},
skill: {},
pageLayout: {},
pageLayoutTab: {},
pageLayoutWidget: {
configuration: 'configuration',
},
commandMenuItem: {},
navigationMenuItem: {},
frontComponent: {},
} as const satisfies {
[P in AllMetadataName]: Partial<{
[K in ExtractJsonbProperties<MetadataEntity<P>>]: K;
}>;
};
export type AllJsonbPropertiesWithSerializedPropertiesForMetadataName<
T extends AllMetadataName,
> =
keyof (typeof ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME)[T];
@@ -0,0 +1,146 @@
import { type Equal, type Expect } from 'twenty-shared/testing';
import { type SerializedRelation } from 'twenty-shared/types';
import { type ContainsSerializedRelation } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/contains-serialized-relation.type';
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
type EmptyObject = {};
// ContainsSerializedRelation checks for SerializedRelation in object properties
// It recurses into nested objects and arrays, but stops at primitives
// Direct property tests
// eslint-disable-next-line unused-imports/no-unused-vars
type DirectPropertyAssertions = [
// Direct SerializedRelation property
Expect<
Equal<ContainsSerializedRelation<{ targetId: SerializedRelation }>, true>
>,
// Nullable SerializedRelation property
Expect<
Equal<
ContainsSerializedRelation<{ targetId: SerializedRelation | null }>,
true
>
>,
// Optional SerializedRelation property
Expect<
Equal<ContainsSerializedRelation<{ targetId?: SerializedRelation }>, true>
>,
// Array of SerializedRelation
Expect<
Equal<ContainsSerializedRelation<{ ids: SerializedRelation[] }>, true>
>,
// Plain properties only - no SerializedRelation
Expect<
Equal<ContainsSerializedRelation<{ name: string; count: number }>, false>
>,
// Empty object
Expect<Equal<ContainsSerializedRelation<EmptyObject>, false>>,
];
// Nested object tests - should recurse
// eslint-disable-next-line unused-imports/no-unused-vars
type NestedObjectAssertions = [
// Nested object with SerializedRelation
Expect<
Equal<
ContainsSerializedRelation<{
nested: { targetId: SerializedRelation };
}>,
true
>
>,
// Deeply nested SerializedRelation
Expect<
Equal<
ContainsSerializedRelation<{
level1: { level2: { level3: { id: SerializedRelation } } };
}>,
true
>
>,
// Nested object without SerializedRelation
Expect<
Equal<
ContainsSerializedRelation<{
nested: { name: string; count: number };
}>,
false
>
>,
];
// Nested array tests - should recurse
// eslint-disable-next-line unused-imports/no-unused-vars
type NestedArrayAssertions = [
// Array of objects with SerializedRelation
Expect<
Equal<
ContainsSerializedRelation<{
items: { targetId: SerializedRelation }[];
}>,
true
>
>,
// 2D array of SerializedRelation
Expect<
Equal<ContainsSerializedRelation<{ matrix: SerializedRelation[][] }>, true>
>,
// Array of plain objects
Expect<
Equal<
ContainsSerializedRelation<{
items: { name: string }[];
}>,
false
>
>,
// 2D array of strings
Expect<Equal<ContainsSerializedRelation<{ matrix: string[][] }>, false>>,
];
// Primitive types - should return false (not objects)
// eslint-disable-next-line unused-imports/no-unused-vars
type PrimitiveAssertions = [
Expect<Equal<ContainsSerializedRelation<string>, false>>,
Expect<Equal<ContainsSerializedRelation<number>, false>>,
Expect<Equal<ContainsSerializedRelation<boolean>, false>>,
Expect<Equal<ContainsSerializedRelation<null>, false>>,
Expect<Equal<ContainsSerializedRelation<undefined>, false>>,
];
// Real-world tests
// eslint-disable-next-line unused-imports/no-unused-vars
type RealWorldAssertions = [
// Settings with SerializedRelation
Expect<
Equal<
ContainsSerializedRelation<{
relationType?: string;
junctionTargetFieldId?: SerializedRelation;
}>,
true
>
>,
// Workflow config with nested SerializedRelation in array
Expect<
Equal<
ContainsSerializedRelation<{
steps: { assigneeId: SerializedRelation; action: string }[];
}>,
true
>
>,
];
@@ -4,10 +4,8 @@ import {
type HasAllProperties,
} from 'twenty-shared/testing';
import {
type FieldMetadataDefaultOption,
type FieldMetadataType,
type FieldNumberVariant,
type LinkMetadata,
type NullablePartial,
type NumberDataType,
type RelationOnDeleteAction,
@@ -91,8 +89,9 @@ type UniversalFlatTransformationAssertions = [
>,
];
// JSONB properties are now prefixed with 'universal' in UniversalFlatFieldMetadata
type NarrowedTestCase =
UniversalFlatFieldMetadata<FieldMetadataType.RELATION>['settings'];
UniversalFlatFieldMetadata<FieldMetadataType.RELATION>['universalSettings'];
type NarrowedExpectedResult = {
relationType: RelationType;
@@ -107,7 +106,7 @@ type NarrowedExpectedResult = {
type SettingsTestCase = UniversalFlatFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.NUMBER | FieldMetadataType.TEXT
>['settings'];
>['universalSettings'];
type SettingsExpectedResult =
| {
@@ -132,39 +131,8 @@ type SettingsExpectedResult =
}
| null;
type DefaultValueTestCase = UniversalFlatFieldMetadata<
| FieldMetadataType.RELATION
| FieldMetadataType.NUMBER
| FieldMetadataType.TEXT
| FieldMetadataType.LINKS
| FieldMetadataType.CURRENCY
>['defaultValue'];
type DefaultValueExpectedResult =
| string
| number
| null
| {
amountMicros: string | null;
currencyCode: string | null;
__JsonbPropertyBrand__?: undefined;
}
| {
primaryLinkLabel: string | null;
primaryLinkUrl: string | null;
secondaryLinks: LinkMetadata[] | null;
__JsonbPropertyBrand__?: undefined;
};
type OptionsTestCase =
UniversalFlatFieldMetadata<FieldMetadataType.RATING>['options'];
type OptionsExpectedResult = FieldMetadataDefaultOption[];
// eslint-disable-next-line unused-imports/no-unused-vars
type Assertions = [
Expect<Equal<SettingsTestCase, SettingsExpectedResult>>,
Expect<Equal<NarrowedTestCase, NarrowedExpectedResult>>,
Expect<Equal<DefaultValueTestCase, DefaultValueExpectedResult>>,
Expect<Equal<OptionsTestCase, OptionsExpectedResult>>,
];
@@ -0,0 +1,24 @@
import {
type IsEmptyObject,
type IsNever,
type IsSerializedRelation,
} from 'twenty-shared/types';
type ContainsSerializedRelationInner<T> = T extends unknown
? IsNever<T> extends true
? false
: unknown extends T
? false
: IsSerializedRelation<T> extends true
? true
: T extends readonly (infer U)[]
? ContainsSerializedRelationInner<U>
: T extends object
? IsEmptyObject<T> extends true
? false
: ContainsSerializedRelationInner<T[keyof T]>
: false
: never;
export type ContainsSerializedRelation<T> =
true extends ContainsSerializedRelationInner<T> ? true : false;
@@ -1,12 +1,4 @@
import { type JSONB_PROPERTY_BRAND } from './jsonb-property.type';
export type HasJsonbPropertyBrand<T> =
typeof JSONB_PROPERTY_BRAND extends keyof T ? true : false;
// Distributive check: returns `true` if any member of a union has the brand
type HasJsonbBrandInUnion<T> = T extends unknown
? HasJsonbPropertyBrand<T>
: never;
import { type HasJsonbBrandInUnion } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand-in-union.type';
export type ExtractJsonbProperties<T> = NonNullable<
{
@@ -0,0 +1,5 @@
import { type HasJsonbPropertyBrand } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/has-jsonb-brand.type';
export type HasJsonbBrandInUnion<T> = T extends unknown
? HasJsonbPropertyBrand<T>
: never;
@@ -0,0 +1,4 @@
import { type JSONB_PROPERTY_BRAND } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
export type HasJsonbPropertyBrand<T> =
typeof JSONB_PROPERTY_BRAND extends keyof T ? true : false;
@@ -7,7 +7,8 @@ import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-m
import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modules/flat-entity/types/from-metadata-entity-to-metadata-name.type';
import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { type AllJsonbPropertiesForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-by-metadata-name.constant';
import { type AllJsonbPropertiesWithSerializedPropertiesForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant';
import { type ContainsSerializedRelation } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/contains-serialized-relation.type';
import { type FormatRecordSerializedRelationProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/format-record-serialized-relation-properties.type';
export type UniversalSyncableFlatEntity = Omit<
@@ -19,6 +20,7 @@ export type UniversalSyncableFlatEntity = Omit<
export type UniversalFlatEntityExtraProperties<
TEntity extends SyncableEntity,
// Required to be passed for narrowed type
TMetadataName extends
AllMetadataName = FromMetadataEntityToMetadataName<TEntity>,
> = AddSuffixToEntityOneToManyProperties<TEntity, 'universalIdentifiers'> &
@@ -29,8 +31,13 @@ export type UniversalFlatEntityExtraProperties<
> & {
applicationUniversalIdentifier: string;
} & {
[P in AllJsonbPropertiesForMetadataName<TMetadataName> &
keyof TEntity]: FormatRecordSerializedRelationProperties<TEntity[P]>;
[P in AllJsonbPropertiesWithSerializedPropertiesForMetadataName<TMetadataName> &
keyof TEntity &
string as `universal${Capitalize<P>}`]: true extends ContainsSerializedRelation<
NonNullable<TEntity[P]>
>
? FormatRecordSerializedRelationProperties<TEntity[P]>
: null;
};
export type UniversalFlatEntityFrom<
@@ -46,7 +53,7 @@ export type UniversalFlatEntityFrom<
| ExtractEntityRelatedEntityProperties<TEntity>
| Extract<MetadataManyToOneJoinColumn<TMetadataName>, keyof TEntity>
| keyof CastRecordTypeOrmDatePropertiesToString<TEntity>
| AllJsonbPropertiesForMetadataName<TMetadataName>
| AllJsonbPropertiesWithSerializedPropertiesForMetadataName<TMetadataName>
> &
CastRecordTypeOrmDatePropertiesToString<TEntity> &
UniversalFlatEntityExtraProperties<TEntity, TMetadataName>;
@@ -1,13 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a created entity 1`] = `
{
"createdFlatEntityMaps": {
"byId": {
"field-id-1": {
"__universal": {},
"applicationId": "application-id-1",
"applicationUniversalIdentifier": "application-universal-identifier-1",
"calendarViewIds": [],
"calendarViewUniversalIdentifiers": [],
"createdAt": "2024-01-01T00:00:00.000Z",
"defaultValue": null,
"description": "default flat field metadata description",
@@ -21,22 +22,30 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a crea
"isUIReadOnly": false,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
"kanbanAggregateOperationViewUniversalIdentifiers": [],
"label": "flat field metadata label",
"mainGroupByFieldMetadataViewIds": [],
"mainGroupByFieldMetadataViewUniversalIdentifiers": [],
"morphId": null,
"name": "flatFieldMetadataName",
"objectMetadataId": "object-metadata-id-1",
"objectMetadataUniversalIdentifier": "object-metadata-universal-identifier-1",
"options": null,
"relationTargetFieldMetadataId": null,
"relationTargetFieldMetadataUniversalIdentifier": null,
"relationTargetObjectMetadataId": null,
"relationTargetObjectMetadataUniversalIdentifier": null,
"settings": null,
"standardId": null,
"standardOverrides": null,
"type": "TEXT",
"universalIdentifier": "universal-identifier-1",
"universalSettings": null,
"updatedAt": "2024-01-01T00:00:00.000Z",
"viewFieldIds": [],
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"workspaceId": "workspace-id-1",
},
},
@@ -70,9 +79,10 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a dele
"deletedFlatEntityMaps": {
"byId": {
"field-id-1": {
"__universal": {},
"applicationId": "application-id-1",
"applicationUniversalIdentifier": "application-universal-identifier-1",
"calendarViewIds": [],
"calendarViewUniversalIdentifiers": [],
"createdAt": "2024-01-01T00:00:00.000Z",
"defaultValue": null,
"description": "default flat field metadata description",
@@ -86,22 +96,30 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a dele
"isUIReadOnly": false,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
"kanbanAggregateOperationViewUniversalIdentifiers": [],
"label": "flat field metadata label",
"mainGroupByFieldMetadataViewIds": [],
"mainGroupByFieldMetadataViewUniversalIdentifiers": [],
"morphId": null,
"name": "flatFieldMetadataName",
"objectMetadataId": "object-metadata-id-1",
"objectMetadataUniversalIdentifier": "object-metadata-universal-identifier-1",
"options": null,
"relationTargetFieldMetadataId": null,
"relationTargetFieldMetadataUniversalIdentifier": null,
"relationTargetObjectMetadataId": null,
"relationTargetObjectMetadataUniversalIdentifier": null,
"settings": null,
"standardId": null,
"standardOverrides": null,
"type": "TEXT",
"universalIdentifier": "universal-identifier-1",
"universalSettings": null,
"updatedAt": "2024-01-01T00:00:00.000Z",
"viewFieldIds": [],
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"workspaceId": "workspace-id-1",
},
},
@@ -153,9 +171,10 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"createdFlatEntityMaps": {
"byId": {
"field-id-3": {
"__universal": {},
"applicationId": "application-id-1",
"applicationUniversalIdentifier": "application-universal-identifier-1",
"calendarViewIds": [],
"calendarViewUniversalIdentifiers": [],
"createdAt": "2024-01-01T00:00:00.000Z",
"defaultValue": null,
"description": "default flat field metadata description",
@@ -169,22 +188,30 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"isUIReadOnly": false,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
"kanbanAggregateOperationViewUniversalIdentifiers": [],
"label": "flat field metadata label",
"mainGroupByFieldMetadataViewIds": [],
"mainGroupByFieldMetadataViewUniversalIdentifiers": [],
"morphId": null,
"name": "flatFieldMetadataName",
"objectMetadataId": "object-metadata-id-1",
"objectMetadataUniversalIdentifier": "object-metadata-universal-identifier-1",
"options": null,
"relationTargetFieldMetadataId": null,
"relationTargetFieldMetadataUniversalIdentifier": null,
"relationTargetObjectMetadataId": null,
"relationTargetObjectMetadataUniversalIdentifier": null,
"settings": null,
"standardId": null,
"standardOverrides": null,
"type": "TEXT",
"universalIdentifier": "universal-identifier-3",
"universalSettings": null,
"updatedAt": "2024-01-01T00:00:00.000Z",
"viewFieldIds": [],
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"workspaceId": "workspace-id-1",
},
},
@@ -200,9 +227,10 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"deletedFlatEntityMaps": {
"byId": {
"field-id-2": {
"__universal": {},
"applicationId": "application-id-1",
"applicationUniversalIdentifier": "application-universal-identifier-1",
"calendarViewIds": [],
"calendarViewUniversalIdentifiers": [],
"createdAt": "2024-01-01T00:00:00.000Z",
"defaultValue": null,
"description": "default flat field metadata description",
@@ -216,22 +244,30 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"isUIReadOnly": false,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
"kanbanAggregateOperationViewUniversalIdentifiers": [],
"label": "flat field metadata label",
"mainGroupByFieldMetadataViewIds": [],
"mainGroupByFieldMetadataViewUniversalIdentifiers": [],
"morphId": null,
"name": "flatFieldMetadataName",
"objectMetadataId": "object-metadata-id-1",
"objectMetadataUniversalIdentifier": "object-metadata-universal-identifier-1",
"options": null,
"relationTargetFieldMetadataId": null,
"relationTargetFieldMetadataUniversalIdentifier": null,
"relationTargetObjectMetadataId": null,
"relationTargetObjectMetadataUniversalIdentifier": null,
"settings": null,
"standardId": null,
"standardOverrides": null,
"type": "TEXT",
"universalIdentifier": "universal-identifier-2",
"universalSettings": null,
"updatedAt": "2024-01-01T00:00:00.000Z",
"viewFieldIds": [],
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"workspaceId": "workspace-id-1",
},
},
@@ -30,6 +30,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
applicationId: 'application-id-1',
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
applicationUniversalIdentifier:
'application-universal-identifier-1',
objectMetadataUniversalIdentifier:
'object-metadata-universal-identifier-1',
}),
],
metadataName: 'fieldMetadata',
@@ -52,6 +56,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
applicationId: 'application-id-1',
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
applicationUniversalIdentifier:
'application-universal-identifier-1',
objectMetadataUniversalIdentifier:
'object-metadata-universal-identifier-1',
}),
],
to: [],
@@ -76,6 +84,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
isActive: false,
applicationUniversalIdentifier:
'application-universal-identifier-1',
objectMetadataUniversalIdentifier:
'object-metadata-universal-identifier-1',
}),
],
to: [
@@ -89,6 +101,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
isActive: true,
applicationUniversalIdentifier:
'application-universal-identifier-1',
objectMetadataUniversalIdentifier:
'object-metadata-universal-identifier-1',
}),
],
metadataName: 'fieldMetadata',
@@ -111,6 +127,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
applicationId: 'application-id-1',
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
applicationUniversalIdentifier:
'application-universal-identifier-1',
objectMetadataUniversalIdentifier:
'object-metadata-universal-identifier-1',
isActive: true,
}),
getFlatFieldMetadataMock({
@@ -122,6 +142,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
applicationId: 'application-id-1',
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z',
applicationUniversalIdentifier:
'application-universal-identifier-1',
objectMetadataUniversalIdentifier:
'object-metadata-universal-identifier-1',
}),
],
to: [
@@ -133,6 +157,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
workspaceId: 'workspace-id-1',
applicationId: 'application-id-1',
createdAt: '2024-01-01T00:00:00.000Z',
applicationUniversalIdentifier:
'application-universal-identifier-1',
objectMetadataUniversalIdentifier:
'object-metadata-universal-identifier-1',
updatedAt: '2024-01-01T00:00:00.000Z',
isActive: false,
}),
@@ -144,6 +172,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
workspaceId: 'workspace-id-1',
applicationId: 'application-id-1',
createdAt: '2024-01-01T00:00:00.000Z',
applicationUniversalIdentifier:
'application-universal-identifier-1',
objectMetadataUniversalIdentifier:
'object-metadata-universal-identifier-1',
updatedAt: '2024-01-01T00:00:00.000Z',
}),
],
@@ -167,6 +199,10 @@ describe('flatEntityDeletedCreatedUpdatedMatrixDispatcher', () => {
workspaceId: 'workspace-id-1',
applicationId: 'application-id-1',
createdAt: '2024-01-01T00:00:00.000Z',
applicationUniversalIdentifier:
'application-universal-identifier-1',
objectMetadataUniversalIdentifier:
'object-metadata-universal-identifier-1',
updatedAt: '2024-01-01T00:00:00.000Z',
}),
],
@@ -1,17 +1,11 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
import { type DeleteFieldAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action';
import {
WorkspaceMigrationActionExecutionException,
WorkspaceMigrationActionExecutionExceptionCode,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/exceptions/workspace-migration-action-execution.exception';
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
import { generateColumnDefinitions } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/generate-column-definitions.util';
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/get-workspace-schema-context-for-migration.util';
@@ -70,19 +64,9 @@ export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerAct
universalIdentifier,
});
const objectMetadataUniversalIdentifier =
fieldMetadata.__universal?.objectMetadataUniversalIdentifier;
if (!isDefined(objectMetadataUniversalIdentifier)) {
throw new WorkspaceMigrationActionExecutionException({
message: `objectMetadataUniversalIdentifier is not defined for field metadata ${universalIdentifier}`,
code: WorkspaceMigrationActionExecutionExceptionCode.NOT_SUPPORTED,
});
}
const flatObjectMetadata = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
universalIdentifier: objectMetadataUniversalIdentifier,
universalIdentifier: fieldMetadata.objectMetadataUniversalIdentifier,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
@@ -97,19 +97,10 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
flatEntityMaps: flatFieldMetadataMaps,
});
const objectMetadataUniversalIdentifier =
currentFlatFieldMetadata.__universal?.objectMetadataUniversalIdentifier;
if (!isDefined(objectMetadataUniversalIdentifier)) {
throw new WorkspaceMigrationActionExecutionException({
message: `objectMetadataUniversalIdentifier is not defined for field metadata ${entityId}`,
code: WorkspaceMigrationActionExecutionExceptionCode.NOT_SUPPORTED,
});
}
const flatObjectMetadata = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
universalIdentifier: objectMetadataUniversalIdentifier,
universalIdentifier:
currentFlatFieldMetadata.objectMetadataUniversalIdentifier,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({