[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:
+11
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+17
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+53
-38
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
-57
@@ -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];
|
||||
+42
@@ -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];
|
||||
+146
@@ -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
|
||||
>
|
||||
>,
|
||||
];
|
||||
+3
-35
@@ -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>>,
|
||||
];
|
||||
|
||||
+24
@@ -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
-9
@@ -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<
|
||||
{
|
||||
|
||||
+5
@@ -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;
|
||||
+4
@@ -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;
|
||||
+11
-4
@@ -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>;
|
||||
|
||||
+41
-5
@@ -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",
|
||||
},
|
||||
},
|
||||
|
||||
+36
@@ -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,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({
|
||||
|
||||
+2
-11
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user