[WHEN_RELEASED_REQUIRES_CACHE_FLUSH] Object related record logic in v2 (#14937)

# Introduction
Initial motivation here was to migrate the object related records logic
from v1 to v2, please note that now in v2 views aren't records anymore
but core engine entities

## What's done
- Added specific label identifier targeting view field logic
- Handled side effects on viewField creation with lowest position on
object label identifier mutation
- Added viewField relations in field metadate entity + handled
optimistic in builder v2
- Added view relations in object metadata entity + handled optimistic in
builder v2
- Added integration tests covering the side effects and new validation
exceptions
- Sandardized cache computation
- Coverage on object metadata creation side effect on views and view
fields

## Coverage
```ts
 PASS  test/integration/graphql/suites/view/view-field/object-identifier-update-side-effect-on-view-field.integration-spec.ts
  View Field Resolver - Successful object metadata identifier update side effect on view field
    ✓ should create a view field on label identifier object metadata update if it does not exist on view (7 ms)
    ✓ Should not allow deleting a label identifier view field (17 ms)
    ✓ Should not allow destroying a label identifier view field (6 ms)
    ✓ Should not allow updating a label identifier view field visibility to false (8 ms)
    ✓ Should not allow creating a view field with a position lower than the label idenfitier view field (180 ms)
    ✓ Should not allow updated labelIdentifier view field with a position higher than existing other view field (346 ms)
    ✓ Should allow updated labelIdentifier view field with a position higher than existing other view field (434 ms)

Test Suites: 1 passed, 1 total
Tests:       7 passed, 7 total
Snapshots:   5 passed, 5 total
Time:        4.571 s, estimated 5 s
```

close https://github.com/twentyhq/core-team-issues/issues/1664
This commit is contained in:
Paul Rastoin
2025-10-08 16:23:37 +02:00
committed by GitHub
parent 1c1739ec80
commit 4ecc9c622d
80 changed files with 6858 additions and 627 deletions
@@ -19,6 +19,7 @@ import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-met
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity';
import { type FieldStandardOverridesDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto';
import { AssignIfIsGivenFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/assign-if-is-given-field-metadata-type.type';
import { AssignTypeIfIsMorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/assign-type-if-is-morph-or-relation-field-metadata-type.type';
@@ -133,8 +134,7 @@ export class FieldMetadataEntity<
@OneToOne(
() => FieldMetadataEntity,
(fieldMetadata: FieldMetadataEntity) =>
fieldMetadata.relationTargetFieldMetadataId,
(fieldMetadata) => fieldMetadata.relationTargetFieldMetadataId,
{ nullable: true },
)
@JoinColumn({ name: 'relationTargetFieldMetadataId' })
@@ -151,8 +151,7 @@ export class FieldMetadataEntity<
@ManyToOne(
() => ObjectMetadataEntity,
(objectMetadata: ObjectMetadataEntity) =>
objectMetadata.targetRelationFields,
(objectMetadata) => objectMetadata.targetRelationFields,
{ onDelete: 'CASCADE', nullable: true },
)
@JoinColumn({ name: 'relationTargetObjectMetadataId' })
@@ -170,8 +169,7 @@ export class FieldMetadataEntity<
@OneToMany(
() => IndexFieldMetadataEntity,
(indexFieldMetadata: IndexFieldMetadataEntity) =>
indexFieldMetadata.indexMetadata,
(indexFieldMetadata) => indexFieldMetadata.indexMetadata,
{
cascade: true,
},
@@ -186,7 +184,10 @@ export class FieldMetadataEntity<
@OneToMany(
() => FieldPermissionEntity,
(fieldPermission: FieldPermissionEntity) => fieldPermission.fieldMetadata,
(fieldPermission) => fieldPermission.fieldMetadata,
)
fieldPermissions: Relation<FieldPermissionEntity[]>;
@OneToMany(() => ViewFieldEntity, (viewField) => viewField.fieldMetadata)
viewFields: Relation<ViewFieldEntity[]>;
}
@@ -19,6 +19,7 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date;
return {
viewFieldIds: [],
createdAt,
updatedAt: createdAt,
defaultValue: null,
@@ -32,6 +32,7 @@ export const getRelationTargetFlatFieldMetadataMock = ({
const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date;
return {
viewFieldIds: [],
createdAt,
updatedAt: createdAt,
description: 'default flat field metadata description',
@@ -113,6 +113,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"workspaceId": Any<String>,
},
{
@@ -145,6 +146,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"type": "RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"workspaceId": Any<String>,
},
{
@@ -175,6 +177,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"workspaceId": Any<String>,
},
{
@@ -207,6 +210,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"type": "RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"workspaceId": Any<String>,
},
],
@@ -8,6 +8,7 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { fromFieldMetadataEntityToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util';
@@ -37,28 +38,26 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceFlatMapC
where: {
workspaceId,
},
select: {
viewFields: {
id: true,
},
},
withDeleted: true,
relations: [],
relations: ['viewFields'],
});
const flatFieldMetadataMaps = fieldMetadatas.reduce<
FlatEntityMaps<FlatFieldMetadata>
>((flatEntityMaps, field) => {
const flatFieldMetadata =
fromFieldMetadataEntityToFlatFieldMetadata(field);
return fieldMetadatas.reduce(
(flatFieldMetadataMaps, fieldMetadataEntity) => {
const flatFieldMetadata =
fromFieldMetadataEntityToFlatFieldMetadata(fieldMetadataEntity);
return {
byId: {
...flatEntityMaps.byId,
[flatFieldMetadata.id]: flatFieldMetadata,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatFieldMetadata.universalIdentifier]: flatFieldMetadata.id,
},
};
}, EMPTY_FLAT_ENTITY_MAPS);
return flatFieldMetadataMaps;
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatFieldMetadata,
flatEntityMaps: flatFieldMetadataMaps,
});
},
EMPTY_FLAT_ENTITY_MAPS,
);
}
}
@@ -8,6 +8,7 @@ export const fieldMetadataRelationProperties = [
'fieldPermissions',
'indexFieldMetadatas',
'object',
'viewFields',
] as const satisfies (keyof FieldMetadataEntity)[];
export type FieldMetadataEntityRelationProperties =
@@ -16,5 +17,5 @@ export type FieldMetadataEntityRelationProperties =
export type FlatFieldMetadata<T extends FieldMetadataType = FieldMetadataType> =
Omit<FieldMetadataEntity<T>, FieldMetadataEntityRelationProperties> & {
universalIdentifier: string;
// Could add indexField metadatas and so on ?
viewFieldIds: string[];
};
@@ -6,8 +6,8 @@ import {
FlatEntityMapsExceptionCode,
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-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 { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -30,10 +30,11 @@ export const findAllOthersMorphRelationFlatFieldMetadatasOrThrow = ({
);
}
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
flatFieldMetadataMaps,
flatObjectMetadata,
});
const objectFlatFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
});
return objectFlatFieldMetadatas.filter(
(
@@ -1,30 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
type FindObjectFieldsInFlatFieldMetadataMapsArgs = {
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
flatObjectMetadata: FlatObjectMetadata;
};
export const findObjectFlatFieldMetadatasOrThrow = ({
flatFieldMetadataMaps,
flatObjectMetadata,
}: FindObjectFieldsInFlatFieldMetadataMapsArgs): {
objectFlatFieldMetadatas: FlatFieldMetadata[];
} => {
const objectFlatFieldMetadataMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
flatEntityMaps: flatFieldMetadataMaps,
});
const objectFlatFieldMetadatas = Object.values(
objectFlatFieldMetadataMaps.byId,
).filter(isDefined);
return {
objectFlatFieldMetadatas,
};
};
@@ -6,6 +6,7 @@ import {
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
import {
FieldMetadataException,
@@ -13,7 +14,6 @@ import {
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { computeFlatFieldMetadataRelatedFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-metadata-related-flat-field-metadata.util';
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
@@ -140,10 +140,11 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
flatEntityMaps: existingFlatObjectMetadataMaps,
flatEntityId: flatIndex.objectMetadataId,
});
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatObjectMetadata,
});
const objectFlatFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: existingFlatFieldMetadataMaps,
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
});
const newIndex = generateFlatIndexMetadataWithNameOrThrow({
flatObjectMetadata,
@@ -20,6 +20,9 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = <
return {
...fieldMetadataWithoutRelations,
viewFieldIds: fieldMetadataEntity.viewFields.map(
(viewField) => viewField.id,
),
universalIdentifier:
fieldMetadataWithoutRelations.standardId ??
fieldMetadataWithoutRelations.id,
@@ -23,6 +23,7 @@ export const getDefaultFlatFieldMetadata = ({
const createdAt = new Date();
return {
viewFieldIds: [],
description: createFieldInput.description ?? null,
id: fieldMetadataId,
icon: createFieldInput.icon ?? null,
@@ -1,8 +1,8 @@
import { isDefined } from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-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 { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.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 { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
@@ -35,10 +35,11 @@ export const recomputeIndexOnFlatFieldMetadataNameUpdate = ({
return [];
}
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
flatFieldMetadataMaps,
flatObjectMetadata,
});
const objectFlatFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
});
const optimisticObjectFlatFieldMetadatas = objectFlatFieldMetadatas.map(
(flatFieldMetadata) => {
if (flatFieldMetadata.id === fromFlatFieldMetadata.id) {
@@ -4,13 +4,13 @@ import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -50,10 +50,11 @@ export const validateFlatFieldMetadataNameAvailability = ({
remainingFlatEntityMapsToValidate?: FlatEntityMaps<FlatFieldMetadata>;
}): FlatFieldMetadataValidationError[] => {
const errors: FlatFieldMetadataValidationError[] = [];
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
flatFieldMetadataMaps,
flatObjectMetadata,
});
const objectFlatFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
});
const reservedCompositeFieldsNames = getReservedCompositeFieldNames(
objectFlatFieldMetadatas,
);
@@ -8,6 +8,7 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { fromIndexMetadataEntityToFlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
@@ -38,27 +39,20 @@ export class WorkspaceFlatIndexMapCacheService extends WorkspaceFlatMapCacheServ
workspaceId,
},
withDeleted: true,
select: {
// Note: We need all IndexFieldMetadataEntity in order to build a FlatIndex
indexFieldMetadatas: true,
},
relations: ['indexFieldMetadatas'],
});
const flatIndexMaps = indexes.reduce<FlatEntityMaps<FlatIndexMetadata>>(
(flatEntityMaps, index) => {
const flatIndex = fromIndexMetadataEntityToFlatIndexMetadata(index);
return indexes.reduce((flatIndexMaps, indexEntity) => {
const flatIndex = fromIndexMetadataEntityToFlatIndexMetadata(indexEntity);
return {
byId: {
...flatEntityMaps.byId,
[flatIndex.id]: flatIndex,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatIndex.universalIdentifier]: flatIndex.id,
},
};
},
EMPTY_FLAT_ENTITY_MAPS,
);
return flatIndexMaps;
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatIndex,
flatEntityMaps: flatIndexMaps,
});
}, EMPTY_FLAT_ENTITY_MAPS);
}
}
@@ -12,6 +12,7 @@ export const getFlatObjectMetadataMock = (
const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date;
return {
viewIds: [],
indexMetadataIds: [],
fieldMetadataIds: [],
description: 'default flat object metadata description',
@@ -39,7 +39,18 @@ export class WorkspaceFlatObjectMetadataMapCacheService extends WorkspaceFlatMap
workspaceId,
},
withDeleted: true,
relations: ['fields', 'indexMetadatas'],
select: {
fields: {
id: true,
},
indexMetadatas: {
id: true,
},
views: {
id: true,
},
},
relations: ['fields', 'indexMetadatas', 'views'],
});
const flatObjectMetadataMaps = objectMetadatas.reduce<
@@ -10,6 +10,7 @@ export const objectMetadataEntityRelationProperties = [
'application',
'objectPermissions',
'fieldPermissions',
'views',
] as const satisfies ObjectMetadataRelationProperties[];
type ObjectMetadataRelationProperties = ExtractRecordTypeOrmRelationProperties<
@@ -24,4 +25,5 @@ export type FlatObjectMetadata = Omit<
universalIdentifier: string;
fieldMetadataIds: string[];
indexMetadataIds: string[];
viewIds: string[];
};
@@ -54,6 +54,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
const createdAt = new Date();
const flatObjectMetadataToCreate: FlatObjectMetadata = {
fieldMetadataIds: [],
viewIds: [],
indexMetadataIds: [],
createdAt,
updatedAt: createdAt,
@@ -5,8 +5,8 @@ import {
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-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 { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
@@ -52,10 +52,11 @@ export const fromDeleteObjectInputToFlatFieldMetadatasToDelete = ({
);
}
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
flatFieldMetadataMaps,
flatObjectMetadata: flatObjectMetadataToDelete,
});
const objectFlatFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityIds: flatObjectMetadataToDelete.fieldMetadataIds,
});
const flatFieldMetadatasToDelete = objectFlatFieldMetadatas.flatMap(
(flatFieldMetadata) => {
if (isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
@@ -16,6 +16,7 @@ export const fromObjectMetadataEntityToFlatObjectMetadata = (
return {
...objectMetadataEntityWithoutRelations,
viewIds: objectMetadataEntity.views.map((viewEntity) => viewEntity.id),
indexMetadataIds: objectMetadataEntity.indexMetadatas.map(
(indexEntity) => indexEntity.id,
),
@@ -0,0 +1,231 @@
import {
extractAndSanitizeObjectStringFields,
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-properties-to-compare.constant';
import { type FlatObjectMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-properties-to-compare.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { recomputeIndexAfterFlatObjectMetadataSingularNameUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-index-after-flat-object-metadata-singular-name-update.util';
import { recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util';
import { renameRelatedMorphFieldOnObjectNamesUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util';
import { OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES } from 'src/engine/metadata-modules/object-metadata/constants/object-metadata-standard-overrides-properties.constant';
import { type UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
import {
ObjectMetadataException,
ObjectMetadataExceptionCode,
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
import { type ObjectMetadataStandardOverridesProperties } from 'src/engine/metadata-modules/object-metadata/types/object-metadata-standard-overrides-properties.types';
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
type FromUpdateObjectInputToFlatObjectMetadataArgs = {
updateObjectInput: UpdateOneObjectInput;
} & Pick<
AllFlatEntityMaps,
| 'flatIndexMaps'
| 'flatObjectMetadataMaps'
| 'flatFieldMetadataMaps'
| 'flatViewFieldMaps'
| 'flatViewMaps'
>;
const objectMetadataEditableProperties =
FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE.filter(
(
property,
): property is Exclude<
FlatObjectMetadataPropertiesToCompare,
'standardOverrides'
> => property !== 'standardOverrides',
);
type UpdatedFlatObjectAndRelatedFlatEntities = {
flatObjectMetadata: FlatObjectMetadata;
otherObjectFlatFieldMetadataToUpdate: FlatFieldMetadata[];
flatIndexMetadataToUpdate: FlatIndexMetadata[];
flatViewFieldToUpdate: FlatViewField[];
flatViewFieldToCreate: FlatViewField[];
};
export const fromUpdateObjectInputToFlatObjectMetadataAndRelatedFlatEntities =
({
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
updateObjectInput: rawUpdateObjectInput,
flatIndexMaps,
flatFieldMetadataMaps,
flatViewFieldMaps,
flatViewMaps,
}: FromUpdateObjectInputToFlatObjectMetadataArgs): UpdatedFlatObjectAndRelatedFlatEntities => {
const { id: objectMetadataIdToUpdate } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawUpdateObjectInput,
['id'],
);
const updatedEditableObjectProperties =
extractAndSanitizeObjectStringFields(
rawUpdateObjectInput.update,
objectMetadataEditableProperties,
);
const flatObjectMetadataToUpdate = findFlatEntityByIdInFlatEntityMaps({
flatEntityMaps: existingFlatObjectMetadataMaps,
flatEntityId: objectMetadataIdToUpdate,
});
if (!isDefined(flatObjectMetadataToUpdate)) {
throw new ObjectMetadataException(
'Object to update not found',
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
if (isStandardMetadata(flatObjectMetadataToUpdate)) {
const invalidUpdatedProperties = Object.keys(
updatedEditableObjectProperties,
).filter(
(property) =>
!OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES.includes(
property as ObjectMetadataStandardOverridesProperties,
),
);
if (invalidUpdatedProperties.length > 0) {
throw new ObjectMetadataException(
`Cannot edit standard object metadata properties: ${invalidUpdatedProperties.join(', ')}`,
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
);
}
const updatedStandardFlatObjectMetadata =
OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES.reduce(
(acc, property) => {
const isPropertyUpdated =
updatedEditableObjectProperties[property] !== undefined;
return {
...acc,
standardOverrides: {
...acc.standardOverrides,
...(isPropertyUpdated
? { [property]: updatedEditableObjectProperties[property] }
: {}),
},
};
},
flatObjectMetadataToUpdate,
);
return {
flatObjectMetadata: updatedStandardFlatObjectMetadata,
otherObjectFlatFieldMetadataToUpdate: [],
flatIndexMetadataToUpdate: [],
flatViewFieldToUpdate: [],
flatViewFieldToCreate: [],
};
}
const initialAccumulator: UpdatedFlatObjectAndRelatedFlatEntities = {
flatObjectMetadata: flatObjectMetadataToUpdate,
otherObjectFlatFieldMetadataToUpdate: [],
flatIndexMetadataToUpdate: [],
flatViewFieldToUpdate: [],
flatViewFieldToCreate: [],
};
return objectMetadataEditableProperties.reduce<UpdatedFlatObjectAndRelatedFlatEntities>(
(
{
flatObjectMetadata,
otherObjectFlatFieldMetadataToUpdate,
flatIndexMetadataToUpdate,
flatViewFieldToUpdate,
flatViewFieldToCreate,
},
property,
) => {
const updatedPropertyValue = updatedEditableObjectProperties[property];
const isPropertyUpdated =
updatedPropertyValue !== undefined &&
flatObjectMetadata[property] !== updatedPropertyValue;
if (!isPropertyUpdated) {
return {
flatObjectMetadata,
otherObjectFlatFieldMetadataToUpdate,
flatIndexMetadataToUpdate,
flatViewFieldToUpdate,
flatViewFieldToCreate,
};
}
const updatedFlatObjectMetadata = {
...flatObjectMetadata,
[property]: updatedPropertyValue,
};
const newUpdatedOtherObjectFlatFieldMetadatas =
property === 'nameSingular' || property === 'namePlural'
? renameRelatedMorphFieldOnObjectNamesUpdate({
flatFieldMetadataMaps,
fromFlatObjectMetadata: updatedFlatObjectMetadata,
toFlatObjectMetadata: updatedFlatObjectMetadata,
})
: [];
const newUpdatedFlatIndexMetadatas =
property === 'nameSingular'
? recomputeIndexAfterFlatObjectMetadataSingularNameUpdate({
flatFieldMetadataMaps,
existingFlatObjectMetadata: flatObjectMetadataToUpdate,
flatIndexMaps,
updatedSingularName: updatedFlatObjectMetadata.nameSingular,
})
: [];
const {
flatViewFieldToCreate: newFlatViewFieldToCreate,
flatViewFieldToUpdate: newFlatViewFieldToUpdate,
} =
property === 'labelIdentifierFieldMetadataId' &&
isDefined(updatedFlatObjectMetadata.labelIdentifierFieldMetadataId)
? recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate({
existingFlatObjectMetadata: flatObjectMetadataToUpdate,
flatViewFieldMaps,
flatViewMaps,
updatedLabelIdentifierFieldMetadataId:
updatedFlatObjectMetadata.labelIdentifierFieldMetadataId,
})
: {
flatViewFieldToCreate: [],
flatViewFieldToUpdate: [],
};
return {
flatObjectMetadata: updatedFlatObjectMetadata,
otherObjectFlatFieldMetadataToUpdate: [
...otherObjectFlatFieldMetadataToUpdate,
...newUpdatedOtherObjectFlatFieldMetadatas,
],
flatViewFieldToUpdate: [
...flatViewFieldToUpdate,
...newFlatViewFieldToUpdate,
],
flatViewFieldToCreate: [
...flatViewFieldToCreate,
...newFlatViewFieldToCreate,
],
flatIndexMetadataToUpdate: [
...flatIndexMetadataToUpdate,
...newUpdatedFlatIndexMetadatas,
],
};
},
initialAccumulator,
);
};
@@ -1,182 +0,0 @@
import {
extractAndSanitizeObjectStringFields,
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-properties-to-compare.constant';
import { type FlatObjectMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-properties-to-compare.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { recomputeIndexAfterFlatObjectMetadataSingularNameUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-index-after-flat-object-metadata-singular-name-update.util';
import { renameRelatedMorphFieldOnObjectNamesUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util';
import { OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES } from 'src/engine/metadata-modules/object-metadata/constants/object-metadata-standard-overrides-properties.constant';
import { type UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
import {
ObjectMetadataException,
ObjectMetadataExceptionCode,
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
import { type ObjectMetadataStandardOverridesProperties } from 'src/engine/metadata-modules/object-metadata/types/object-metadata-standard-overrides-properties.types';
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
type FromUpdateObjectInputToFlatObjectMetadataArgs = {
updateObjectInput: UpdateOneObjectInput;
} & Pick<
AllFlatEntityMaps,
'flatIndexMaps' | 'flatObjectMetadataMaps' | 'flatFieldMetadataMaps'
>;
const objectMetadataEditableProperties =
FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE.filter(
(
property,
): property is Exclude<
FlatObjectMetadataPropertiesToCompare,
'standardOverrides'
> => property !== 'standardOverrides',
);
type UpdatedFlatObjectAndOtherObjectFieldMetadatas = {
flatObjectMetadata: FlatObjectMetadata;
otherObjectFlatFieldMetadataToUpdate: FlatFieldMetadata[];
flatIndexMetadataToUpdate: FlatIndexMetadata[];
};
export const fromUpdateObjectInputToFlatObjectMetadata = ({
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
updateObjectInput: rawUpdateObjectInput,
flatIndexMaps,
flatFieldMetadataMaps,
}: FromUpdateObjectInputToFlatObjectMetadataArgs): UpdatedFlatObjectAndOtherObjectFieldMetadatas => {
const { id: objectMetadataIdToUpdate } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawUpdateObjectInput,
['id'],
);
const updatedEditableObjectProperties = extractAndSanitizeObjectStringFields(
rawUpdateObjectInput.update,
objectMetadataEditableProperties,
);
const flatObjectMetadataToUpdate = findFlatEntityByIdInFlatEntityMaps({
flatEntityMaps: existingFlatObjectMetadataMaps,
flatEntityId: objectMetadataIdToUpdate,
});
if (!isDefined(flatObjectMetadataToUpdate)) {
throw new ObjectMetadataException(
'Object to update not found',
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
if (isStandardMetadata(flatObjectMetadataToUpdate)) {
const invalidUpdatedProperties = Object.keys(
updatedEditableObjectProperties,
).filter(
(property) =>
!OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES.includes(
property as ObjectMetadataStandardOverridesProperties,
),
);
if (invalidUpdatedProperties.length > 0) {
throw new ObjectMetadataException(
`Cannot edit standard object metadata properties: ${invalidUpdatedProperties.join(', ')}`,
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
);
}
const updatedStandardFlatObjectdMetadata =
OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES.reduce((acc, property) => {
const isPropertyUpdated =
updatedEditableObjectProperties[property] !== undefined;
return {
...acc,
standardOverrides: {
...acc.standardOverrides,
...(isPropertyUpdated
? { [property]: updatedEditableObjectProperties[property] }
: {}),
},
};
}, flatObjectMetadataToUpdate);
return {
flatObjectMetadata: updatedStandardFlatObjectdMetadata,
otherObjectFlatFieldMetadataToUpdate: [],
flatIndexMetadataToUpdate: [],
};
}
const initialAccumulator: UpdatedFlatObjectAndOtherObjectFieldMetadatas = {
flatObjectMetadata: flatObjectMetadataToUpdate,
otherObjectFlatFieldMetadataToUpdate: [],
flatIndexMetadataToUpdate: [],
};
return objectMetadataEditableProperties.reduce<UpdatedFlatObjectAndOtherObjectFieldMetadatas>(
(
{
flatObjectMetadata,
otherObjectFlatFieldMetadataToUpdate: otherObjectFlatFieldMetadatas,
flatIndexMetadataToUpdate,
},
property,
) => {
const updatedPropertyValue = updatedEditableObjectProperties[property];
const isPropertyUpdated =
updatedPropertyValue !== undefined &&
flatObjectMetadata[property] !== updatedPropertyValue;
if (!isPropertyUpdated) {
return {
flatObjectMetadata,
otherObjectFlatFieldMetadataToUpdate: otherObjectFlatFieldMetadatas,
flatIndexMetadataToUpdate,
};
}
const updatedFlatObjectMetadata = {
...flatObjectMetadata,
[property]: updatedPropertyValue,
};
const newUpdatedOtherObjectFlatFieldMetadatas =
property === 'nameSingular' || property === 'namePlural'
? renameRelatedMorphFieldOnObjectNamesUpdate({
flatFieldMetadataMaps,
fromFlatObjectMetadata: updatedFlatObjectMetadata,
toFlatObjectMetadata: updatedFlatObjectMetadata,
})
: [];
const newUpdatedFlatIndexMetadatas =
property === 'nameSingular'
? recomputeIndexAfterFlatObjectMetadataSingularNameUpdate({
flatFieldMetadataMaps,
existingFlatObjectMetadata: flatObjectMetadataToUpdate,
flatIndexMaps,
updatedSingularName: updatedFlatObjectMetadata.nameSingular,
})
: [];
return {
flatObjectMetadata: updatedFlatObjectMetadata,
otherObjectFlatFieldMetadataToUpdate: [
...otherObjectFlatFieldMetadatas,
...newUpdatedOtherObjectFlatFieldMetadatas,
],
flatIndexMetadataToUpdate: [
...flatIndexMetadataToUpdate,
...newUpdatedFlatIndexMetadatas,
],
};
},
initialAccumulator,
);
};
@@ -1,7 +1,7 @@
import { isDefined } from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.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 { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
@@ -31,10 +31,11 @@ export const recomputeIndexAfterFlatObjectMetadataSingularNameUpdate = ({
nameSingular: updatedSingularName,
};
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
flatFieldMetadataMaps,
flatObjectMetadata: optimisticFlatObjectMetadata,
});
const objectFlatFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityIds: optimisticFlatObjectMetadata.fieldMetadataIds,
});
return allRelatedFlatIndexMetadata.map<FlatIndexMetadata>((flatIndex) => {
const newIndex = generateFlatIndexMetadataWithNameOrThrow({
@@ -0,0 +1,85 @@
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
type RecomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdateArgs = {
existingFlatObjectMetadata: FlatObjectMetadata;
updatedLabelIdentifierFieldMetadataId: string;
} & Pick<AllFlatEntityMaps, 'flatViewFieldMaps' | 'flatViewMaps'>;
type FlatViewFieldToCreateAndUpdate = {
flatViewFieldToCreate: FlatViewField[];
flatViewFieldToUpdate: FlatViewField[];
};
export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({
existingFlatObjectMetadata,
flatViewFieldMaps,
flatViewMaps,
updatedLabelIdentifierFieldMetadataId,
}: RecomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdateArgs): FlatViewFieldToCreateAndUpdate => {
const flatViews = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatViewMaps,
flatEntityIds: existingFlatObjectMetadata.viewIds,
});
const accumulator: FlatViewFieldToCreateAndUpdate = {
flatViewFieldToCreate: [],
flatViewFieldToUpdate: [],
};
for (const flatView of flatViews) {
const flatViewFields = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: flatView.viewFieldIds,
flatEntityMaps: flatViewFieldMaps,
});
const labelMetadataIdentifierViewField = flatViewFields.find(
(viewField) =>
viewField.fieldMetadataId === updatedLabelIdentifierFieldMetadataId,
);
const ascSortedViewFieldPositions = flatViewFields
.map(({ position }) => position)
.sort((a, b) => a - b);
const lowestViewFieldPosition =
ascSortedViewFieldPositions.length > 0
? ascSortedViewFieldPositions[0]
: 0;
if (!isDefined(labelMetadataIdentifierViewField)) {
const viewFieldId = v4();
const createdAt = new Date();
const flatViewFieldToCreate: FlatViewField = {
fieldMetadataId: updatedLabelIdentifierFieldMetadataId,
position: lowestViewFieldPosition - 1,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
viewId: flatView.id,
workspaceId: flatView.workspaceId,
id: viewFieldId,
createdAt: createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: viewFieldId,
aggregateOperation: null,
};
accumulator.flatViewFieldToCreate.push(flatViewFieldToCreate);
} else if (
labelMetadataIdentifierViewField.position > lowestViewFieldPosition
) {
const updatedFlatViewField = {
...labelMetadataIdentifierViewField,
position: lowestViewFieldPosition - 1,
};
accumulator.flatViewFieldToUpdate.push(updatedFlatViewField);
}
}
return accumulator;
};
@@ -6,9 +6,9 @@ import {
import { computeMorphRelationFieldName } from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { getFlatObjectMetadataTargetMorphRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-flat-object-metadata-many-to-one-target-morph-relation-flat-field-metadatas-or-throw.util';
import { getMorphNameFromMorphFieldMetadataName } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-morph-name-from-morph-field-metadata-name.util';
@@ -24,10 +24,11 @@ export const renameRelatedMorphFieldOnObjectNamesUpdate = ({
toFlatObjectMetadata,
flatFieldMetadataMaps,
}: RenameRelatedMorphFieldOnObjectNamesUpdateArgs): FlatFieldMetadata<FieldMetadataType.MORPH_RELATION>[] => {
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
flatFieldMetadataMaps,
flatObjectMetadata: fromFlatObjectMetadata,
});
const objectFlatFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityIds: fromFlatObjectMetadata.fieldMetadataIds,
});
const manyToOneMorphRelationFlatFieldMetadatas =
getFlatObjectMetadataTargetMorphRelationFlatFieldMetadatasOrThrow({
flatFieldMetadataMaps,
@@ -4,8 +4,10 @@ import { isDefined } from 'twenty-shared/utils';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { ViewKey } from 'src/engine/core-modules/view/enums/view-key.enum';
@@ -14,12 +16,11 @@ import { FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view
import { fromCreateViewFieldInputToFlatViewFieldToCreate } from 'src/engine/core-modules/view/flat-view/utils/from-create-view-field-input-to-flat-view-field-to-create.util';
import { fromCreateViewInputToFlatViewToCreate } from 'src/engine/core-modules/view/flat-view/utils/from-create-view-input-to-flat-view-to-create.util';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util';
import { fromDeleteObjectInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util';
import { fromFlatObjectMetadataToObjectMetadataDto } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util';
import { fromUpdateObjectInputToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-update-object-input-to-flat-object-metadata.util';
import { fromUpdateObjectInputToFlatObjectMetadataAndRelatedFlatEntities } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-update-object-input-to-flat-object-metadata-and-related-flat-entities.util';
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
@@ -55,6 +56,8 @@ export class ObjectMetadataServiceV2 {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewMaps: existingFlatViewMaps,
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
@@ -62,6 +65,8 @@ export class ObjectMetadataServiceV2 {
'flatObjectMetadataMaps',
'flatIndexMaps',
'flatFieldMetadataMaps',
'flatViewFieldMaps',
'flatViewMaps',
],
},
);
@@ -70,11 +75,15 @@ export class ObjectMetadataServiceV2 {
flatObjectMetadata: optimisticallyUpdatedFlatObjectMetadata,
otherObjectFlatFieldMetadataToUpdate: otherObjectFlatFieldMetadatas,
flatIndexMetadataToUpdate,
} = fromUpdateObjectInputToFlatObjectMetadata({
flatViewFieldToUpdate,
flatViewFieldToCreate,
} = fromUpdateObjectInputToFlatObjectMetadataAndRelatedFlatEntities({
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
updateObjectInput,
flatIndexMaps: existingFlatIndexMaps,
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewMaps: existingFlatViewMaps,
});
const toFlatObjectMetadataMaps = addFlatEntityToFlatEntityMapsOrThrow({
@@ -106,6 +115,27 @@ export class ObjectMetadataServiceV2 {
}),
);
const flatViewFieldMapsWithUpdated = flatViewFieldToUpdate.reduce(
(flatViewFieldMaps, flatViewField) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatViewField,
flatEntityMaps: flatViewFieldMaps,
}),
getSubFlatEntityMapsOrThrow({
flatEntityIds: flatViewFieldToUpdate.map(({ id }) => id),
flatEntityMaps: existingFlatViewFieldMaps,
}),
);
const toFlatViewFieldMaps = flatViewFieldToCreate.reduce(
(flatViewFieldMaps, flatViewField) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewField,
flatEntityMaps: flatViewFieldMaps,
}),
flatViewFieldMapsWithUpdated,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -122,6 +152,13 @@ export class ObjectMetadataServiceV2 {
from: existingFlatFieldMetadataMaps,
to: toFlatFieldMetadataMaps,
},
flatViewFieldMaps: {
from: existingFlatViewFieldMaps,
to: toFlatViewFieldMaps,
},
},
dependencyAllFlatEntityMaps: {
flatViewMaps: existingFlatViewMaps,
},
buildOptions: {
isSystemBuild: false,
@@ -329,7 +366,7 @@ export class ObjectMetadataServiceV2 {
const toFlatFieldMetadataMaps = [
...flatFieldMetadataToCreateOnObject,
...relationTargetFlatFieldMetadataToCreate,
].reduce(
].reduce<FlatEntityMaps<FlatFieldMetadata>>(
(flatFieldMaps, flatField) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatField,
@@ -353,13 +390,16 @@ export class ObjectMetadataServiceV2 {
flatEntityMaps: existingFlatViewMaps,
});
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
flatFieldMetadataMaps: toFlatFieldMetadataMaps,
flatObjectMetadata: flatObjectMetadataToCreate,
});
const flatDefaultViewFieldsToCreate =
await this.createDefaultFlatViewFields({
objectFlatFieldMetadatas,
objectFlatFieldMetadatas: findManyFlatEntityByIdInFlatEntityMapsOrThrow(
{
flatEntityMaps: toFlatFieldMetadataMaps,
flatEntityIds: flatFieldMetadataToCreateOnObject.map(
({ id }) => id,
),
},
),
viewId: flatDefaultViewToCreate.id,
workspaceId,
});
@@ -13,6 +13,7 @@ import {
import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
@@ -145,8 +146,7 @@ export class ObjectMetadataEntity implements Required<ObjectMetadataEntity> {
@OneToMany(
() => ObjectPermissionEntity,
(objectPermission: ObjectPermissionEntity) =>
objectPermission.objectMetadata,
(objectPermission) => objectPermission.objectMetadata,
{
cascade: true,
},
@@ -155,10 +155,15 @@ export class ObjectMetadataEntity implements Required<ObjectMetadataEntity> {
@OneToMany(
() => FieldPermissionEntity,
(fieldPermission: FieldPermissionEntity) => fieldPermission.objectMetadata,
(fieldPermission) => fieldPermission.objectMetadata,
{
cascade: true,
},
)
fieldPermissions: Relation<FieldPermissionEntity[]>;
@OneToMany(() => ViewEntity, (view) => view.objectMetadata, {
cascade: true,
})
views: Relation<ViewEntity[]>;
}
@@ -27,6 +27,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
const idField: FlatFieldMetadata<FieldMetadataType.UUID> = {
type: FieldMetadataType.UUID,
id: v4(),
viewFieldIds: [],
isLabelSyncedWithName: false,
isUnique: true,
objectMetadataId,
@@ -60,6 +61,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
const nameField: FlatFieldMetadata<FieldMetadataType.TEXT> = {
type: FieldMetadataType.TEXT,
id: v4(),
viewFieldIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -93,6 +95,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
const createdAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
type: FieldMetadataType.DATE_TIME,
id: v4(),
viewFieldIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -126,6 +129,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
const updatedAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
type: FieldMetadataType.DATE_TIME,
id: v4(),
viewFieldIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -159,6 +163,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
const deletedAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
type: FieldMetadataType.DATE_TIME,
id: v4(),
viewFieldIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -192,6 +197,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
const createdByField: FlatFieldMetadata<FieldMetadataType.ACTOR> = {
type: FieldMetadataType.ACTOR,
id: v4(),
viewFieldIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -225,6 +231,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
const positionField: FlatFieldMetadata<FieldMetadataType.POSITION> = {
type: FieldMetadataType.POSITION,
id: v4(),
viewFieldIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -257,6 +264,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
const searchVectorField: FlatFieldMetadata<FieldMetadataType.TS_VECTOR> = {
type: FieldMetadataType.TS_VECTOR,
viewFieldIds: [],
id: v4(),
isLabelSyncedWithName: false,
isUnique: false,
@@ -63,6 +63,7 @@ const generateSourceFlatFieldMetadata = ({
}
return {
viewFieldIds: [],
createdAt,
updatedAt: createdAt,
defaultValue: null,
@@ -127,6 +128,7 @@ const generateTargetFlatFieldMetadata = ({
return {
morphId: null,
viewFieldIds: [],
id: sourceFlatFieldMetadata.relationTargetFieldMetadataId,
name: sourceFlatObjectMetadata.nameSingular,
label: sourceFlatObjectMetadata.labelSingular,
@@ -1,42 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity';
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
export const getFieldMetadataEntityFromCachedObjectMetadataMaps = ({
fieldMetadataId,
objectMetadataId,
objectMetadataMaps,
}: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataId: string;
fieldMetadataId: string;
}): FieldMetadataEntity | undefined => {
const objectMetadataFromCache = objectMetadataMaps.byId[objectMetadataId];
if (!isDefined(objectMetadataFromCache)) {
return undefined;
}
const fieldMetadataFromCache: CachedFieldMetadataEntity =
objectMetadataFromCache.fieldsById[fieldMetadataId];
if (!isDefined(fieldMetadataFromCache)) {
return undefined;
}
// We need to determine if graphql will be able to handle relations crafting byitself
return {
...fieldMetadataFromCache,
object: {} as ObjectMetadataEntity,
relationTargetFieldMetadata: null,
relationTargetObjectMetadata: null,
indexFieldMetadatas: {} as IndexFieldMetadataEntity,
fieldPermissions: [],
createdAt: new Date(fieldMetadataFromCache.createdAt),
updatedAt: new Date(fieldMetadataFromCache.updatedAt),
};
};