From 371c26bc9b3f2c3fa7cd143f0f76f64fb8b600d6 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:39:42 +0200 Subject: [PATCH] Flat entity maps cache generic service + runner dynamically retrieving invalidating update cache + view service v2 refactor (#14508) # Introduction Migrate previous runner only iterating on `flatObjectMetadataMaps` to `allFlatEntityMaps`. Refactored the optimistic to be handled inside the actions handler ## Workspace flat map cache Introducing a new service and registry, that will dynamically retrieve and or recompute requested cache when called ## Runner refactor Runner now dynamically invalidate updated cache at the end of the transaction close https://github.com/orgs/twentyhq/projects/1/views/8?pane=issue&itemId=129136356&issue=twentyhq%7Ccore-team-issues%7C1492 close https://github.com/orgs/twentyhq/projects/1/views/8?pane=issue&itemId=129136210&issue=twentyhq%7Ccore-team-issues%7C1494 --- ...ll-flat-entity-maps-properties.constant.ts | 7 + .../empty-all-flat-entity-maps.constant.ts | 2 +- ...ny-or-all-flat-entity-maps-cache.module.ts | 11 + ...-or-all-flat-entity-maps-cache.service..ts | 105 +++++++ ...t-entities-by-metadata-engine-name.type.ts | 10 + .../common/types/all-flat-entities.type.ts | 12 +- .../view/cache/services/view-cache.module.ts | 13 - .../view/cache/services/view-cache.service.ts | 83 ----- .../view/dtos/inputs/create-view.input.ts | 37 +++ .../view/dtos/inputs/delete-view.input.ts | 15 + .../view/dtos/inputs/destroy-view.input.ts | 15 + .../view/dtos/inputs/update-view.input.ts | 34 +++ .../engine/core-modules/view/dtos/view.dto.ts | 10 +- .../core-modules/view/entities/view.entity.ts | 1 + .../flat-view-editable-properties.constant.ts | 17 +- ...view-field-editable-properties.constant.ts | 8 + ...ew-field-properties-to-compare.constant.ts | 4 +- ...flat-view-properies-to-compare.constant.ts | 11 +- ...space-flat-view-field-map-cache.service.ts | 55 ++++ .../workspace-flat-view-map-cache.service.ts | 3 +- ...-view-input-to-flat-view-to-create.util.ts | 48 +++ ...e-view-input-to-flat-view-or-throw.util.ts | 40 +++ ...y-view-input-to-flat-view-or-throw.util.ts | 37 +++ ...flat-view-field-to-update-or-throw.util.ts | 6 +- ...ut-to-flat-view-to-update-or-throw.util.ts | 50 ++++ .../view/resolvers/view.resolver.ts | 61 ++++ .../view/services/view-field-v2.service.ts | 97 +++--- .../view/services/view-v2.service.ts | 283 +++++++++++------- .../engine/core-modules/view/view.module.ts | 4 +- .../field-metadata/field-metadata.module.ts | 2 + .../services/field-metadata.service-v2.ts | 21 +- ...mpty-flat-object-metadata-maps.constant.ts | 1 + .../types/flat-object-metadata-maps.type.ts | 1 + ...flat-object-metadata-maps-or-throw.util.ts | 3 + ...flat-object-metadata-maps-or-throw.util.ts | 4 + ...flat-object-metadata-maps-or-throw.util.ts | 5 + ...flat-object-metadata-maps-or-throw.util.ts | 1 + ...flat-object-metadata-maps-or-throw.util.ts | 7 + ...ject-metadata-with-flat-field-maps.util.ts | 9 + ...-flat-object-metadata-map-cache.service.ts | 59 ++++ .../object-metadata-v2.service.ts | 20 +- .../object-metadata/object-metadata.module.ts | 2 + .../workspace-flat-map-cache.decorator.ts | 7 +- ...rkspace-flat-map-cache-registry.service.ts | 49 +++ .../workspace-flat-map-cache.service.ts | 16 +- .../workspace-flat-map-cache.module.ts | 34 ++- .../workspace-health.module.ts | 3 +- .../workspace-migration-runner.module.ts | 3 +- .../types/validate-error-response.type.ts | 2 +- ...ce-migration-build-orchestrator.service.ts | 5 + .../workspace-migration-orchestrator.type.ts | 2 +- ...iew-field-related-flat-entity-maps.type.ts | 6 + ...n-v2-view-field-actions-builder.service.ts | 6 +- .../types/workspace-migration-v2.ts | 3 +- .../flat-view-field-validator.service.ts | 2 +- .../create-field-action-handler.service.ts | 31 +- .../delete-field-action-handler.service.ts | 34 ++- .../update-field-action-handler.service.ts | 53 +++- .../create-index-action-handler.service.ts | 12 +- .../delete-index-action-handler.service.ts | 12 +- .../create-object-action-handler.service.ts | 35 ++- .../delete-object-action-handler.service.ts | 33 +- .../update-object-action-handler.service.ts | 45 ++- ...reate-view-field-action-handler.service.ts | 24 +- ...elete-view-field-action-handler.service.ts | 24 +- ...pdate-view-field-action-handler.service.ts | 35 ++- .../create-view-action-handler.service.ts | 24 +- .../delete-view-action-handler.service.ts | 24 +- .../update-view-action-handler.service.ts | 35 ++- .../workspace-migration-runner.exception.ts | 1 + ...runner-action-handler-service.interface.ts | 46 ++- ...-runner-action-handler-registry.service.ts | 5 +- .../workspace-migration-runner-v2.service.ts | 97 +++--- ...space-migration-action-runner-args.type.ts | 4 +- ...ion-action-on-flat-object-metadata-maps.ts | 151 ---------- .../workspace-migration-runner-v2.module.ts | 6 +- .../workspace-migration-v2.module.ts | 2 - ...l-create-view-field-v2.integration-spec.ts | 6 +- .../utils/setup-view-field-test-v2.util.ts | 18 +- 79 files changed, 1534 insertions(+), 575 deletions(-) create mode 100644 packages/twenty-server/src/engine/core-modules/common/constant/all-flat-entity-maps-properties.constant.ts create mode 100644 packages/twenty-server/src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module.ts create mode 100644 packages/twenty-server/src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service..ts create mode 100644 packages/twenty-server/src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/view/cache/services/view-cache.module.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/view/cache/services/view-cache.service.ts create mode 100644 packages/twenty-server/src/engine/core-modules/view/dtos/inputs/delete-view.input.ts create mode 100644 packages/twenty-server/src/engine/core-modules/view/dtos/inputs/destroy-view.input.ts create mode 100644 packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-field-editable-properties.constant.ts create mode 100644 packages/twenty-server/src/engine/core-modules/view/flat-view/services/workspace-flat-view-field-map-cache.service.ts create mode 100644 packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-delete-view-input-to-flat-view-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-destroy-view-input-to-flat-view-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts create mode 100644 packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service.ts create mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/view-field-related-flat-entity-maps.type.ts delete mode 100644 packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps.ts diff --git a/packages/twenty-server/src/engine/core-modules/common/constant/all-flat-entity-maps-properties.constant.ts b/packages/twenty-server/src/engine/core-modules/common/constant/all-flat-entity-maps-properties.constant.ts new file mode 100644 index 00000000000..91d8641cd17 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/common/constant/all-flat-entity-maps-properties.constant.ts @@ -0,0 +1,7 @@ +import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; + +export const ALL_FLAT_ENTITY_MAPS_PROPERTIES = [ + 'flatObjectMetadataMaps', + 'flatViewFieldMaps', + 'flatViewMaps', +] as const satisfies (keyof AllFlatEntityMaps)[]; diff --git a/packages/twenty-server/src/engine/core-modules/common/constant/empty-all-flat-entity-maps.constant.ts b/packages/twenty-server/src/engine/core-modules/common/constant/empty-all-flat-entity-maps.constant.ts index 21d599faa87..bb15b85e5e4 100644 --- a/packages/twenty-server/src/engine/core-modules/common/constant/empty-all-flat-entity-maps.constant.ts +++ b/packages/twenty-server/src/engine/core-modules/common/constant/empty-all-flat-entity-maps.constant.ts @@ -3,7 +3,7 @@ import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all export const EMPTY_ALL_FLAT_ENTITY_MAPS = { flatObjectMetadataMaps: { - byId: {}, + ...EMPTY_FLAT_ENTITY_MAPS, idByNameSingular: {}, }, flatViewFieldMaps: EMPTY_FLAT_ENTITY_MAPS, diff --git a/packages/twenty-server/src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module.ts b/packages/twenty-server/src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module.ts new file mode 100644 index 00000000000..b051ec1ae2b --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.'; +import { WorkspaceFlatMapCacheModule } from 'src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module'; + +@Module({ + imports: [WorkspaceFlatMapCacheModule], + providers: [WorkspaceManyOrAllFlatEntityMapsCacheService], + exports: [WorkspaceManyOrAllFlatEntityMapsCacheService], +}) +export class WorkspaceManyOrAllFlatEntityMapsCacheModule {} diff --git a/packages/twenty-server/src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service..ts b/packages/twenty-server/src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service..ts new file mode 100644 index 00000000000..2a4f0291582 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service..ts @@ -0,0 +1,105 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { isDefined } from 'twenty-shared/utils'; + +import { ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/core-modules/common/constant/all-flat-entity-maps-properties.constant'; +import { EMPTY_ALL_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-all-flat-entity-maps.constant'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; +import { + WorkspaceFlatMapCacheException, + WorkspaceFlatMapCacheExceptionCode, +} from 'src/engine/workspace-flat-map-cache/exceptions/workspace-flat-map-cache.exception'; +import { WorkspaceFlatMapCacheRegistryService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service'; + +@Injectable() +export class WorkspaceManyOrAllFlatEntityMapsCacheService { + private readonly logger = new Logger( + WorkspaceManyOrAllFlatEntityMapsCacheService.name, + ); + + constructor( + private readonly cacheRegistry: WorkspaceFlatMapCacheRegistryService, + ) {} + + public async getOrRecomputeManyOrAllFlatEntityMaps< + T extends (keyof AllFlatEntityMaps)[] = (keyof AllFlatEntityMaps)[], + >({ + flatEntities, + workspaceId, + }: { + workspaceId: string; + flatEntities?: T; + }): Promise> { + const allFlatEntityMaps: AllFlatEntityMaps = structuredClone( + EMPTY_ALL_FLAT_ENTITY_MAPS, + ); + + for (const flatEntityName of ALL_FLAT_ENTITY_MAPS_PROPERTIES) { + if (isDefined(flatEntities) && !flatEntities.includes(flatEntityName)) { + delete allFlatEntityMaps[flatEntityName]; + continue; + } + + try { + const service = this.cacheRegistry.getCacheService(flatEntityName); + + if (!isDefined(service)) { + throw new WorkspaceFlatMapCacheException( + `No cache service found for ${flatEntityName}`, + WorkspaceFlatMapCacheExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + const result = await service.getExistingOrRecomputeFlatMaps({ + workspaceId, + }); + + // @ts-expect-error todo prastoin once refactored flat object metadata cache + allFlatEntityMaps[flatEntityName] = result; + } catch (error) { + this.logger.error( + `Failed to get flat entity maps for ${flatEntityName}`, + error, + ); + throw error; + } + } + + return allFlatEntityMaps; + } + + public async invalidateFlatEntityMaps< + T extends (keyof AllFlatEntityMaps)[] = (keyof AllFlatEntityMaps)[], + >({ + flatEntities, + workspaceId, + }: { + workspaceId: string; + flatEntities?: T; + }): Promise { + for (const flatEntityName of ALL_FLAT_ENTITY_MAPS_PROPERTIES) { + if (isDefined(flatEntities) && !flatEntities.includes(flatEntityName)) { + continue; + } + + try { + const service = this.cacheRegistry.getCacheService(flatEntityName); + + if (!isDefined(service)) { + throw new WorkspaceFlatMapCacheException( + `No cache service found for ${flatEntityName}`, + WorkspaceFlatMapCacheExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + await service.invalidateCache({ workspaceId }); + } catch (error) { + this.logger.error( + `Failed to invalidate flat entity maps for ${flatEntityName}`, + error, + ); + throw error; + } + } + } +} diff --git a/packages/twenty-server/src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type.ts b/packages/twenty-server/src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type.ts new file mode 100644 index 00000000000..9b6d252d725 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type.ts @@ -0,0 +1,10 @@ +import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type'; +import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; + +export type AllFlatEntitiesByMetadataEngineName = { + // flatFieldMetadata: FlatFieldMetadata; + objectMetadata: FlatObjectMetadata; + view: FlatView; + viewField: FlatViewField; +}; diff --git a/packages/twenty-server/src/engine/core-modules/common/types/all-flat-entities.type.ts b/packages/twenty-server/src/engine/core-modules/common/types/all-flat-entities.type.ts index 9b6d252d725..73385f9649d 100644 --- a/packages/twenty-server/src/engine/core-modules/common/types/all-flat-entities.type.ts +++ b/packages/twenty-server/src/engine/core-modules/common/types/all-flat-entities.type.ts @@ -1,10 +1,4 @@ -import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type'; -import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type'; -import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { type AllFlatEntitiesByMetadataEngineName } from 'src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type'; -export type AllFlatEntitiesByMetadataEngineName = { - // flatFieldMetadata: FlatFieldMetadata; - objectMetadata: FlatObjectMetadata; - view: FlatView; - viewField: FlatViewField; -}; +export type AllFlatEntities = + AllFlatEntitiesByMetadataEngineName[keyof AllFlatEntitiesByMetadataEngineName]; diff --git a/packages/twenty-server/src/engine/core-modules/view/cache/services/view-cache.module.ts b/packages/twenty-server/src/engine/core-modules/view/cache/services/view-cache.module.ts deleted file mode 100644 index c780af445af..00000000000 --- a/packages/twenty-server/src/engine/core-modules/view/cache/services/view-cache.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { ViewCacheService } from 'src/engine/core-modules/view/cache/services/view-cache.service'; -import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity'; -import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([ViewEntity, ViewFieldEntity])], - providers: [ViewCacheService], - exports: [ViewCacheService], -}) -export class ViewCacheModule {} diff --git a/packages/twenty-server/src/engine/core-modules/view/cache/services/view-cache.service.ts b/packages/twenty-server/src/engine/core-modules/view/cache/services/view-cache.service.ts deleted file mode 100644 index 450e37907e0..00000000000 --- a/packages/twenty-server/src/engine/core-modules/view/cache/services/view-cache.service.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { Repository } from 'typeorm'; - -import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity'; -import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; -import { FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type'; -import { FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type'; -import { fromViewFieldEntityToFlatViewField } from 'src/engine/core-modules/view/flat-view/utils/from-view-field-entity-to-flat-view-field.util'; -import { generateFlatViewMaps } from 'src/engine/core-modules/view/flat-view/utils/generate-flat-view-maps.util'; - -type GetExistingOrRecomputeFlatViewMapsResult = { - flatViewMaps: FlatViewMaps; -}; - -@Injectable() -export class ViewCacheService { - logger = new Logger(ViewCacheService.name); - - constructor( - @InjectRepository(ViewEntity) - private readonly viewRepository: Repository, - @InjectRepository(ViewFieldEntity) - private readonly viewFieldRepository: Repository, - ) {} - - async getExistingOrRecomputeFlatViewMaps({ - workspaceId, - }: { - workspaceId: string; - }): Promise { - // TODO: get from cache later - const existingViews = await this.viewRepository.find({ - where: { - workspaceId, - }, - withDeleted: true, - relations: ['viewFields'], - select: { - viewFields: { - id: true, - }, - }, - }); - - const existingFlatViewMaps = generateFlatViewMaps(existingViews); - - return { - flatViewMaps: existingFlatViewMaps, - }; - } - - public async getExistingFlatViewFieldMapsFromCache({ - workspaceId, - }: { - workspaceId: string; - }): Promise<{ flatViewFieldMaps: FlatViewFieldMaps }> { - // TODO: get from cache later - const existingViewFields = await this.viewFieldRepository.find({ - where: { - workspaceId, - }, - withDeleted: true, - }); - - const flatViewFieldMaps: FlatViewFieldMaps = { - byId: {}, - idByUniversalIdentifier: {}, - }; - - for (const viewFieldEntity of existingViewFields) { - const flatViewField = fromViewFieldEntityToFlatViewField(viewFieldEntity); - - flatViewFieldMaps.byId[flatViewField.id] = flatViewField; - flatViewFieldMaps.idByUniversalIdentifier[ - flatViewField.universalIdentifier - ] = flatViewField.id; - } - - return { flatViewFieldMaps }; - } -} diff --git a/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/create-view.input.ts b/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/create-view.input.ts index a30e2a72923..0ea5f0b38b6 100644 --- a/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/create-view.input.ts +++ b/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/create-view.input.ts @@ -1,56 +1,93 @@ import { Field, InputType } from '@nestjs/graphql'; +import { + IsBoolean, + IsEnum, + IsNotEmpty, + IsNumber, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { ViewCalendarLayout } from 'src/engine/core-modules/view/enums/view-calendar-layout.enum'; import { ViewKey } from 'src/engine/core-modules/view/enums/view-key.enum'; import { ViewOpenRecordIn } from 'src/engine/core-modules/view/enums/view-open-record-in'; import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum'; +import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator'; @InputType() export class CreateViewInput { + @IsOptional() + @IsUUID() @Field(() => UUIDScalarType, { nullable: true }) id?: string; + @IsString() + @IsNotEmpty() + @IsValidMetadataName() @Field({ nullable: false }) name: string; + @IsUUID() @Field(() => UUIDScalarType, { nullable: false }) objectMetadataId: string; + @IsEnum(ViewType) @Field(() => ViewType, { nullable: true, defaultValue: ViewType.TABLE }) type?: ViewType; + @IsOptional() + @IsEnum(ViewKey) @Field(() => ViewKey, { nullable: true }) key?: ViewKey; + @IsString() @Field({ nullable: false }) icon: string; + @IsOptional() + @IsNumber() @Field({ nullable: true, defaultValue: 0 }) position?: number; + @IsOptional() + @IsBoolean() @Field({ nullable: true, defaultValue: false }) isCompact?: boolean; + @IsOptional() + @IsEnum(ViewOpenRecordIn) @Field(() => ViewOpenRecordIn, { nullable: true, defaultValue: ViewOpenRecordIn.SIDE_PANEL, }) openRecordIn?: ViewOpenRecordIn; + @IsOptional() + @IsEnum(AggregateOperations) @Field(() => AggregateOperations, { nullable: true }) kanbanAggregateOperation?: AggregateOperations; + @IsOptional() + @IsUUID() @Field(() => UUIDScalarType, { nullable: true }) kanbanAggregateOperationFieldMetadataId?: string; + @IsOptional() + @IsString() @Field({ nullable: true }) anyFieldFilterValue?: string; + @IsOptional() + @IsEnum(ViewCalendarLayout) @Field(() => ViewCalendarLayout, { nullable: true }) calendarLayout?: ViewCalendarLayout; + @IsOptional() + @IsUUID() @Field(() => UUIDScalarType, { nullable: true }) calendarFieldMetadataId?: string; } diff --git a/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/delete-view.input.ts b/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/delete-view.input.ts new file mode 100644 index 00000000000..371352eb5ff --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/delete-view.input.ts @@ -0,0 +1,15 @@ +import { InputType } from '@nestjs/graphql'; + +import { IDField } from '@ptc-org/nestjs-query-graphql'; +import { IsUUID } from 'class-validator'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; + +@InputType() +export class DeleteViewInput { + @IDField(() => UUIDScalarType, { + description: 'The id of the view to delete.', + }) + @IsUUID() + id: string; +} diff --git a/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/destroy-view.input.ts b/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/destroy-view.input.ts new file mode 100644 index 00000000000..ad3a193091e --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/destroy-view.input.ts @@ -0,0 +1,15 @@ +import { InputType } from '@nestjs/graphql'; + +import { IDField } from '@ptc-org/nestjs-query-graphql'; +import { IsUUID } from 'class-validator'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; + +@InputType() +export class DestroyViewInput { + @IDField(() => UUIDScalarType, { + description: 'The id of the view to destroy.', + }) + @IsUUID() + id: string; +} diff --git a/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/update-view.input.ts b/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/update-view.input.ts index 9ec348f2736..8a5206303fd 100644 --- a/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/update-view.input.ts +++ b/packages/twenty-server/src/engine/core-modules/view/dtos/inputs/update-view.input.ts @@ -1,50 +1,84 @@ import { Field, InputType } from '@nestjs/graphql'; +import { + IsBoolean, + IsEnum, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { ViewCalendarLayout } from 'src/engine/core-modules/view/enums/view-calendar-layout.enum'; import { ViewOpenRecordIn } from 'src/engine/core-modules/view/enums/view-open-record-in'; import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum'; +import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator'; // TODO: this should be refactored like for view-field.input.ts // This is a temporary fix as we were extending the CreateViewInput class which was adding default values for the non filled fields @InputType() export class UpdateViewInput { + @IsUUID() @Field(() => UUIDScalarType, { nullable: true }) id: string; + @IsOptional() + @IsNotEmpty() + @IsValidMetadataName() @Field({ nullable: true }) name?: string; + @IsOptional() + @IsEnum(ViewType) @Field(() => ViewType, { nullable: true }) type?: ViewType; + @IsOptional() + @IsString() @Field({ nullable: true }) icon?: string; + @IsOptional() + @IsBoolean() @Field({ nullable: true }) position?: number; + @IsOptional() + @IsBoolean() @Field({ nullable: true }) isCompact?: boolean; + @IsOptional() + @IsEnum(ViewOpenRecordIn) @Field(() => ViewOpenRecordIn, { nullable: true, }) openRecordIn?: ViewOpenRecordIn; + @IsOptional() + @IsEnum(AggregateOperations) @Field(() => AggregateOperations, { nullable: true }) kanbanAggregateOperation?: AggregateOperations; + @IsOptional() + @IsUUID() @Field(() => UUIDScalarType, { nullable: true }) kanbanAggregateOperationFieldMetadataId?: string; + @IsOptional() + @IsString() @Field({ nullable: true }) anyFieldFilterValue?: string; + @IsOptional() + @IsEnum(ViewCalendarLayout) @Field(() => ViewCalendarLayout, { nullable: true }) calendarLayout?: ViewCalendarLayout; + @IsOptional() + @IsUUID() @Field(() => UUIDScalarType, { nullable: true }) calendarFieldMetadataId?: string; } diff --git a/packages/twenty-server/src/engine/core-modules/view/dtos/view.dto.ts b/packages/twenty-server/src/engine/core-modules/view/dtos/view.dto.ts index 9b51dd8a0c4..ea377ad22d1 100644 --- a/packages/twenty-server/src/engine/core-modules/view/dtos/view.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/view/dtos/view.dto.ts @@ -82,17 +82,17 @@ export class ViewDTO { deletedAt?: Date | null; @Field(() => [ViewFieldDTO]) - viewFields: ViewFieldDTO[]; + viewFields?: ViewFieldDTO[]; @Field(() => [ViewFilterDTO]) - viewFilters: ViewFilterDTO[]; + viewFilters?: ViewFilterDTO[]; @Field(() => [ViewFilterGroupDTO]) - viewFilterGroups: ViewFilterGroupDTO[]; + viewFilterGroups?: ViewFilterGroupDTO[]; @Field(() => [ViewSortDTO]) - viewSorts: ViewSortDTO[]; + viewSorts?: ViewSortDTO[]; @Field(() => [ViewGroupDTO]) - viewGroups: ViewGroupDTO[]; + viewGroups?: ViewGroupDTO[]; } diff --git a/packages/twenty-server/src/engine/core-modules/view/entities/view.entity.ts b/packages/twenty-server/src/engine/core-modules/view/entities/view.entity.ts index 60d92cc87e1..ffd74fe2e28 100644 --- a/packages/twenty-server/src/engine/core-modules/view/entities/view.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/view/entities/view.entity.ts @@ -28,6 +28,7 @@ import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +// We could refactor this type to be dynamic to view type @Entity({ name: 'view', schema: 'core' }) @Index('IDX_VIEW_WORKSPACE_ID_OBJECT_METADATA_ID', [ 'workspaceId', diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-editable-properties.constant.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-editable-properties.constant.ts index 2bf162c04aa..81bf6e63b94 100644 --- a/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-editable-properties.constant.ts +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-editable-properties.constant.ts @@ -1,8 +1,15 @@ -import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type'; +import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type'; export const FLAT_VIEW_EDITABLE_PROPERTIES = [ - 'isVisible', - 'size', + 'name', + 'type', + 'icon', 'position', - 'aggregateOperation', -] as const satisfies (keyof FlatViewField)[]; + 'isCompact', + 'openRecordIn', + 'kanbanAggregateOperation', + 'kanbanAggregateOperationFieldMetadataId', + 'anyFieldFilterValue', + 'calendarLayout', + 'calendarFieldMetadataId', +] as const satisfies (keyof FlatView)[]; diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-field-editable-properties.constant.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-field-editable-properties.constant.ts new file mode 100644 index 00000000000..5a313eaa967 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-field-editable-properties.constant.ts @@ -0,0 +1,8 @@ +import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type'; + +export const FLAT_VIEW_FIELD_EDITABLE_PROPERTIES = [ + 'isVisible', + 'size', + 'position', + 'aggregateOperation', +] as const satisfies (keyof FlatViewField)[]; diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-field-properties-to-compare.constant.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-field-properties-to-compare.constant.ts index 3d27a7ef00a..b62173da8d3 100644 --- a/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-field-properties-to-compare.constant.ts +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-field-properties-to-compare.constant.ts @@ -1,7 +1,7 @@ -import { FLAT_VIEW_EDITABLE_PROPERTIES } from 'src/engine/core-modules/view/flat-view/constants/flat-view-editable-properties.constant'; +import { FLAT_VIEW_FIELD_EDITABLE_PROPERTIES } from 'src/engine/core-modules/view/flat-view/constants/flat-view-field-editable-properties.constant'; import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type'; export const FLAT_VIEW_FIELD_PROPERTIES_TO_COMPARE = [ - ...FLAT_VIEW_EDITABLE_PROPERTIES, + ...FLAT_VIEW_FIELD_EDITABLE_PROPERTIES, 'deletedAt', ] as const satisfies (keyof FlatViewField)[]; diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-properies-to-compare.constant.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-properies-to-compare.constant.ts index 176367a564b..6c719d0b2c6 100644 --- a/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-properies-to-compare.constant.ts +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/constants/flat-view-properies-to-compare.constant.ts @@ -1,14 +1,7 @@ +import { FLAT_VIEW_EDITABLE_PROPERTIES } from 'src/engine/core-modules/view/flat-view/constants/flat-view-editable-properties.constant'; import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type'; export const FLAT_VIEW_PROPERTIES_TO_COMPARE = [ - 'name', - 'type', 'key', - 'isCompact', - 'openRecordIn', - 'kanbanAggregateOperation', - 'kanbanAggregateOperationFieldMetadataId', - 'position', - 'anyFieldFilterValue', - 'icon', + ...FLAT_VIEW_EDITABLE_PROPERTIES, ] as const satisfies (keyof FlatView)[]; diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/services/workspace-flat-view-field-map-cache.service.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/services/workspace-flat-view-field-map-cache.service.ts new file mode 100644 index 00000000000..82116ead4d8 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/services/workspace-flat-view-field-map-cache.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; + +import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; +import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; +import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; +import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity'; +import { FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type'; +import { fromViewFieldEntityToFlatViewField } from 'src/engine/core-modules/view/flat-view/utils/from-view-field-entity-to-flat-view-field.util'; +import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator'; +import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service'; + +@Injectable() +@WorkspaceFlatMapCache('flatViewFieldMaps') +export class WorkspaceFlatViewFieldMapCacheService extends WorkspaceFlatMapCacheService { + constructor( + @InjectCacheStorage(CacheStorageNamespace.EngineWorkspace) + cacheStorageService: CacheStorageService, + @InjectRepository(ViewFieldEntity) + private readonly viewFieldRepository: Repository, + ) { + super(cacheStorageService); + } + + protected async computeFlatMap({ + workspaceId, + }: { + workspaceId: string; + }): Promise { + const existingViewFields = await this.viewFieldRepository.find({ + where: { + workspaceId, + }, + withDeleted: true, + }); + + const flatViewFieldMaps: FlatViewFieldMaps = { + byId: {}, + idByUniversalIdentifier: {}, + }; + + for (const viewFieldEntity of existingViewFields) { + const flatViewField = fromViewFieldEntityToFlatViewField(viewFieldEntity); + + flatViewFieldMaps.byId[flatViewField.id] = flatViewField; + flatViewFieldMaps.idByUniversalIdentifier[ + flatViewField.universalIdentifier + ] = flatViewField.id; + } + + return flatViewFieldMaps; + } +} diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/services/workspace-flat-view-map-cache.service.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/services/workspace-flat-view-map-cache.service.ts index 5d6bfe4afcf..fcf0f36c943 100644 --- a/packages/twenty-server/src/engine/core-modules/view/flat-view/services/workspace-flat-view-map-cache.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/services/workspace-flat-view-map-cache.service.ts @@ -13,7 +13,7 @@ import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decor import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service'; @Injectable() -@WorkspaceFlatMapCache('view') +@WorkspaceFlatMapCache('flatViewMaps') export class WorkspaceFlatViewMapCacheService extends WorkspaceFlatMapCacheService { constructor( @InjectCacheStorage(CacheStorageNamespace.EngineWorkspace) @@ -33,6 +33,7 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceFlatMapCacheServi where: { workspaceId, }, + withDeleted: true, relations: ['viewFields'], select: { viewFields: { diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts new file mode 100644 index 00000000000..fbcdf28f3ba --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-create-view-input-to-flat-view-to-create.util.ts @@ -0,0 +1,48 @@ +import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; +import { v4 } from 'uuid'; + +import { type CreateViewInput } from 'src/engine/core-modules/view/dtos/inputs/create-view.input'; +import { ViewOpenRecordIn } from 'src/engine/core-modules/view/enums/view-open-record-in'; +import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum'; +import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type'; + +export const fromCreateViewInputToFlatViewToCreate = ({ + createViewInput: rawCreateViewInput, + workspaceId, +}: { + createViewInput: CreateViewInput; + workspaceId: string; +}): FlatView => { + const { objectMetadataId, ...createViewInput } = + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( + rawCreateViewInput, + ['id', 'name', 'objectMetadataId'], + ); + + const createdAt = new Date(); + + return { + id: createViewInput.id ?? v4(), + objectMetadataId, + workspaceId, + name: createViewInput.name, + createdAt: createdAt, + updatedAt: createdAt, + deletedAt: null, + isCustom: true, + anyFieldFilterValue: createViewInput.anyFieldFilterValue ?? null, + calendarFieldMetadataId: createViewInput.calendarFieldMetadataId ?? null, + calendarLayout: createViewInput.calendarLayout ?? null, + icon: createViewInput.icon, + isCompact: createViewInput.isCompact ?? false, + kanbanAggregateOperation: createViewInput.kanbanAggregateOperation ?? null, + kanbanAggregateOperationFieldMetadataId: + createViewInput.kanbanAggregateOperationFieldMetadataId ?? null, + key: createViewInput.key ?? null, + openRecordIn: createViewInput.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL, + position: createViewInput.position ?? 0, + type: createViewInput.type ?? ViewType.TABLE, + universalIdentifier: v4(), + viewFieldIds: [], + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-delete-view-input-to-flat-view-or-throw.util.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-delete-view-input-to-flat-view-or-throw.util.ts new file mode 100644 index 00000000000..0228d2e32a5 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-delete-view-input-to-flat-view-or-throw.util.ts @@ -0,0 +1,40 @@ +import { t } from '@lingui/core/macro'; +import { + extractAndSanitizeObjectStringFields, + isDefined, +} from 'twenty-shared/utils'; + +import { type DeleteViewInput } from 'src/engine/core-modules/view/dtos/inputs/delete-view.input'; +import { + ViewException, + ViewExceptionCode, +} from 'src/engine/core-modules/view/exceptions/view.exception'; +import { type FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type'; +import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type'; + +export const fromDeleteViewInputToFlatViewOrThrow = ({ + deleteViewInput: rawDeleteViewInput, + flatViewMaps, +}: { + deleteViewInput: DeleteViewInput; + flatViewMaps: FlatViewMaps; +}): FlatView => { + const { id: viewId } = extractAndSanitizeObjectStringFields( + rawDeleteViewInput, + ['id'], + ); + + const existingFlatViewToDelete = flatViewMaps.byId[viewId]; + + if (!isDefined(existingFlatViewToDelete)) { + throw new ViewException( + t`View to delete not found`, + ViewExceptionCode.VIEW_NOT_FOUND, + ); + } + + return { + ...existingFlatViewToDelete, + deletedAt: new Date(), + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-destroy-view-input-to-flat-view-or-throw.util.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-destroy-view-input-to-flat-view-or-throw.util.ts new file mode 100644 index 00000000000..ca58c1a054e --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-destroy-view-input-to-flat-view-or-throw.util.ts @@ -0,0 +1,37 @@ +import { t } from '@lingui/core/macro'; +import { + extractAndSanitizeObjectStringFields, + isDefined, +} from 'twenty-shared/utils'; + +import { type DestroyViewInput } from 'src/engine/core-modules/view/dtos/inputs/destroy-view.input'; +import { + ViewException, + ViewExceptionCode, +} from 'src/engine/core-modules/view/exceptions/view.exception'; +import { type FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type'; +import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type'; + +export const fromDestroyViewInputToFlatViewOrThrow = ({ + destroyViewInput: rawDestroyViewInput, + flatViewMaps, +}: { + destroyViewInput: DestroyViewInput; + flatViewMaps: FlatViewMaps; +}): FlatView => { + const { id: viewId } = extractAndSanitizeObjectStringFields( + rawDestroyViewInput, + ['id'], + ); + + const existingFlatViewToDestroy = flatViewMaps.byId[viewId]; + + if (!isDefined(existingFlatViewToDestroy)) { + throw new ViewException( + t`View to destroy not found`, + ViewExceptionCode.VIEW_NOT_FOUND, + ); + } + + return existingFlatViewToDestroy; +}; diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-update-view-field-input-to-flat-view-field-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-update-view-field-input-to-flat-view-field-to-update-or-throw.util.ts index d5612006e6a..cc9868cb376 100644 --- a/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-update-view-field-input-to-flat-view-field-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-update-view-field-input-to-flat-view-field-to-update-or-throw.util.ts @@ -10,7 +10,7 @@ import { ViewFieldException, ViewFieldExceptionCode, } from 'src/engine/core-modules/view/exceptions/view-field.exception'; -import { FLAT_VIEW_EDITABLE_PROPERTIES } from 'src/engine/core-modules/view/flat-view/constants/flat-view-editable-properties.constant'; +import { FLAT_VIEW_FIELD_EDITABLE_PROPERTIES } from 'src/engine/core-modules/view/flat-view/constants/flat-view-field-editable-properties.constant'; import { type FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type'; import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type'; import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util'; @@ -39,12 +39,12 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({ } const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields( rawUpdateViewFieldInput.update, - FLAT_VIEW_EDITABLE_PROPERTIES, + FLAT_VIEW_FIELD_EDITABLE_PROPERTIES, ); return mergeUpdateInExistingRecord({ existing: existingFlatViewFieldToUpdate, - properties: FLAT_VIEW_EDITABLE_PROPERTIES, + properties: FLAT_VIEW_FIELD_EDITABLE_PROPERTIES, update: updatedEditableFieldProperties, }); }; diff --git a/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util.ts new file mode 100644 index 00000000000..6a73a4c797b --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/view/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util.ts @@ -0,0 +1,50 @@ +import { t } from '@lingui/core/macro'; +import { + extractAndSanitizeObjectStringFields, + isDefined, + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties, +} from 'twenty-shared/utils'; + +import { type UpdateViewInput } from 'src/engine/core-modules/view/dtos/inputs/update-view.input'; +import { + ViewException, + ViewExceptionCode, +} from 'src/engine/core-modules/view/exceptions/view.exception'; +import { FLAT_VIEW_EDITABLE_PROPERTIES } from 'src/engine/core-modules/view/flat-view/constants/flat-view-editable-properties.constant'; +import { type FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type'; +import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type'; +import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util'; + +export const fromUpdateViewInputToFlatViewToUpdateOrThrow = ({ + updateViewInput: rawUpdateViewInput, + flatViewMaps, +}: { + updateViewInput: UpdateViewInput; + flatViewMaps: FlatViewMaps; +}): FlatView => { + const { id: viewToUpdateId } = + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( + rawUpdateViewInput, + ['id'], + ); + + const existingFlatViewToUpdate = flatViewMaps.byId[viewToUpdateId]; + + if (!isDefined(existingFlatViewToUpdate)) { + throw new ViewException( + t`View to update not found`, + ViewExceptionCode.VIEW_NOT_FOUND, + ); + } + + const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields( + rawUpdateViewInput, + FLAT_VIEW_EDITABLE_PROPERTIES, + ); + + return mergeUpdateInExistingRecord({ + existing: existingFlatViewToUpdate, + properties: FLAT_VIEW_EDITABLE_PROPERTIES, + update: updatedEditableFieldProperties, + }); +}; diff --git a/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts b/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts index 94573eeae91..dfd2151d7ab 100644 --- a/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts @@ -12,6 +12,8 @@ import { import { isArray } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { type I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type'; import { CreateViewInput } from 'src/engine/core-modules/view/dtos/inputs/create-view.input'; import { UpdateViewInput } from 'src/engine/core-modules/view/dtos/inputs/update-view.input'; @@ -26,6 +28,7 @@ import { ViewFilterGroupService } from 'src/engine/core-modules/view/services/vi import { ViewFilterService } from 'src/engine/core-modules/view/services/view-filter.service'; import { ViewGroupService } from 'src/engine/core-modules/view/services/view-group.service'; import { ViewSortService } from 'src/engine/core-modules/view/services/view-sort.service'; +import { ViewV2Service } from 'src/engine/core-modules/view/services/view-v2.service'; import { ViewService } from 'src/engine/core-modules/view/services/view.service'; import { ViewGraphqlApiExceptionFilter } from 'src/engine/core-modules/view/utils/view-graphql-api-exception.filter'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -45,6 +48,8 @@ export class ViewResolver { private readonly viewFilterGroupService: ViewFilterGroupService, private readonly viewSortService: ViewSortService, private readonly viewGroupService: ViewGroupService, + private readonly featureFlagService: FeatureFlagService, + private readonly viewV2Service: ViewV2Service, ) {} @ResolveField(() => String) @@ -119,6 +124,19 @@ export class ViewResolver { @Args('input') input: CreateViewInput, @AuthWorkspace() workspace: Workspace, ): Promise { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + return await this.viewV2Service.createOne({ + createViewInput: input, + workspaceId: workspace.id, + }); + } + return this.viewService.create({ ...input, workspaceId: workspace.id, @@ -131,6 +149,19 @@ export class ViewResolver { @Args('input') input: UpdateViewInput, @AuthWorkspace() workspace: Workspace, ): Promise { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + return await this.viewV2Service.updateOne({ + updateViewInput: input, + workspaceId: workspace.id, + }); + } + return this.viewService.update(id, workspace.id, input); } @@ -139,6 +170,21 @@ export class ViewResolver { @Args('id', { type: () => String }) id: string, @AuthWorkspace() workspace: Workspace, ): Promise { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + const deletedView = await this.viewV2Service.deleteOne({ + deleteViewInput: { id }, + workspaceId: workspace.id, + }); + + return isDefined(deletedView); + } + const deletedView = await this.viewService.delete(id, workspace.id); return isDefined(deletedView); @@ -149,6 +195,21 @@ export class ViewResolver { @Args('id', { type: () => String }) id: string, @AuthWorkspace() workspace: Workspace, ): Promise { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + const deletedView = await this.viewV2Service.destroyOne({ + destroyViewInput: { id }, + workspaceId: workspace.id, + }); + + return isDefined(deletedView); + } + const deletedView = await this.viewService.destroy(id, workspace.id); return isDefined(deletedView); diff --git a/packages/twenty-server/src/engine/core-modules/view/services/view-field-v2.service.ts b/packages/twenty-server/src/engine/core-modules/view/services/view-field-v2.service.ts index e47d5d8613b..a27c3c8c22e 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/view-field-v2.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/view-field-v2.service.ts @@ -2,12 +2,12 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.'; 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 { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-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 { ViewCacheService } from 'src/engine/core-modules/view/cache/services/view-cache.service'; import { CreateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/create-view-field.input'; import { DeleteViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/delete-view-field.input'; import { DestroyViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/destroy-view-field.input'; @@ -17,7 +17,6 @@ import { fromCreateViewFieldInputToFlatViewFieldToCreate } from 'src/engine/core import { fromDeleteViewFieldInputToFlatViewFieldOrThrow } from 'src/engine/core-modules/view/flat-view/utils/from-delete-view-field-input-to-flat-view-field-or-throw.util'; import { fromDestroyViewFieldInputToFlatViewFieldOrThrow } from 'src/engine/core-modules/view/flat-view/utils/from-destroy-view-field-input-to-flat-view-field-or-throw.util'; import { fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow } from 'src/engine/core-modules/view/flat-view/utils/from-update-view-field-input-to-flat-view-field-to-update-or-throw.util'; -import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; @@ -25,8 +24,7 @@ import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspa export class ViewFieldV2Service { constructor( private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, - private readonly viewCacheService: ViewCacheService, - private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} async createOne({ @@ -36,20 +34,20 @@ export class ViewFieldV2Service { createViewFieldInput: CreateViewFieldInput; workspaceId: string; }): Promise { - const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( - { - workspaceId, - }, - ); - const { flatViewFieldMaps: existingFlatViewFieldMaps } = - await this.viewCacheService.getExistingFlatViewFieldMapsFromCache({ + const { + flatObjectMetadataMaps, + flatViewFieldMaps: existingFlatViewFieldMaps, + flatViewMaps: existingFlatViewMaps, + } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { workspaceId, - }); - const { flatViewMaps: existingFlatViewMaps } = - await this.viewCacheService.getExistingOrRecomputeFlatViewMaps({ - workspaceId, - }); + flatEntities: [ + 'flatObjectMetadataMaps', + 'flatViewFieldMaps', + 'flatViewMaps', + ], + }, + ); const flatViewFieldToCreate = fromCreateViewFieldInputToFlatViewFieldToCreate({ @@ -72,7 +70,7 @@ export class ViewFieldV2Service { }, }, dependencyAllFlatEntityMaps: { - flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + flatObjectMetadataMaps, flatViewMaps: existingFlatViewMaps, }, buildOptions: { @@ -91,9 +89,12 @@ export class ViewFieldV2Service { } const { flatViewFieldMaps: recomputedExistingFlatViewFieldMaps } = - await this.viewCacheService.getExistingFlatViewFieldMapsFromCache({ - workspaceId, - }); + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewFieldMaps'], + }, + ); return findFlatEntityByIdInFlatEntityMapsOrThrow({ flatEntityId: flatViewFieldToCreate.id, @@ -109,9 +110,12 @@ export class ViewFieldV2Service { updateViewFieldInput: UpdateViewFieldInput; }): Promise { const { flatViewFieldMaps: existingFlatViewFieldMaps } = - await this.viewCacheService.getExistingFlatViewFieldMapsFromCache({ - workspaceId, - }); + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewFieldMaps'], + }, + ); const optimisticallyUpdatedFlatView = fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow({ @@ -153,9 +157,12 @@ export class ViewFieldV2Service { } const { flatViewFieldMaps: recomputedExistingFlatViewFieldMaps } = - await this.viewCacheService.getExistingFlatViewFieldMapsFromCache({ - workspaceId, - }); + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewFieldMaps'], + }, + ); return findFlatEntityByIdInFlatEntityMapsOrThrow({ flatEntityId: optimisticallyUpdatedFlatView.id, @@ -171,9 +178,12 @@ export class ViewFieldV2Service { workspaceId: string; }): Promise { const { flatViewFieldMaps: existingFlatViewFieldMaps } = - await this.viewCacheService.getExistingFlatViewFieldMapsFromCache({ - workspaceId, - }); + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewFieldMaps'], + }, + ); const optimisticallyUpdatedFlatViewWithDeletedAt = fromDeleteViewFieldInputToFlatViewFieldOrThrow({ @@ -211,9 +221,12 @@ export class ViewFieldV2Service { } const { flatViewFieldMaps: recomputedExistingFlatViewFieldMaps } = - await this.viewCacheService.getExistingFlatViewFieldMapsFromCache({ - workspaceId, - }); + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewFieldMaps'], + }, + ); return findFlatEntityByIdInFlatEntityMapsOrThrow({ flatEntityId: optimisticallyUpdatedFlatViewWithDeletedAt.id, @@ -228,14 +241,16 @@ export class ViewFieldV2Service { destroyViewFieldInput: DestroyViewFieldInput; workspaceId: string; }): Promise { - const { flatViewFieldMaps: existingFlatViewFieldMaps } = - await this.viewCacheService.getExistingFlatViewFieldMapsFromCache({ - workspaceId, - }); - const { flatViewMaps: existingFlatViewMaps } = - await this.viewCacheService.getExistingOrRecomputeFlatViewMaps({ - workspaceId, - }); + const { + flatViewFieldMaps: existingFlatViewFieldMaps, + flatViewMaps: existingFlatViewMaps, + } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewFieldMaps', 'flatViewMaps'], + }, + ); const existingViewFieldToDelete = fromDestroyViewFieldInputToFlatViewFieldOrThrow({ diff --git a/packages/twenty-server/src/engine/core-modules/view/services/view-v2.service.ts b/packages/twenty-server/src/engine/core-modules/view/services/view-v2.service.ts index cd7d95a100b..2e2001a422c 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/view-v2.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/view-v2.service.ts @@ -1,75 +1,53 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils'; -import { Equal, Repository } from 'typeorm'; -import { v4 } from 'uuid'; +import { isDefined } from 'twenty-shared/utils'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.'; 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 { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-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 { ViewCacheService } from 'src/engine/core-modules/view/cache/services/view-cache.service'; -import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; -import { - ViewException, - ViewExceptionCode, - ViewExceptionMessageKey, - generateViewExceptionMessage, - generateViewUserFriendlyExceptionMessage, -} from 'src/engine/core-modules/view/exceptions/view.exception'; -import { VIEW_ENTITY_RELATION_PROPERTIES } from 'src/engine/core-modules/view/flat-view/constants/view-entity-relation-properties.constant'; -import { FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type'; -import { fromPartialFlatViewToFlatViewWithDefault } from 'src/engine/core-modules/view/flat-view/utils/from-partial-flat-view-to-flat-view-to-with-default.util'; -import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; +import { CreateViewInput } from 'src/engine/core-modules/view/dtos/inputs/create-view.input'; +import { DeleteViewInput } from 'src/engine/core-modules/view/dtos/inputs/delete-view.input'; +import { DestroyViewInput } from 'src/engine/core-modules/view/dtos/inputs/destroy-view.input'; +import { UpdateViewInput } from 'src/engine/core-modules/view/dtos/inputs/update-view.input'; +import { ViewDTO } from 'src/engine/core-modules/view/dtos/view.dto'; +import { fromCreateViewInputToFlatViewToCreate } from 'src/engine/core-modules/view/flat-view/utils/from-create-view-input-to-flat-view-to-create.util'; +import { fromDeleteViewInputToFlatViewOrThrow } from 'src/engine/core-modules/view/flat-view/utils/from-delete-view-input-to-flat-view-or-throw.util'; +import { fromDestroyViewInputToFlatViewOrThrow } from 'src/engine/core-modules/view/flat-view/utils/from-destroy-view-input-to-flat-view-or-throw.util'; +import { fromUpdateViewInputToFlatViewToUpdateOrThrow } from 'src/engine/core-modules/view/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util'; import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; @Injectable() export class ViewV2Service { constructor( - @InjectRepository(ViewEntity) - private readonly viewRepository: Repository, private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, - private readonly viewCacheService: ViewCacheService, - private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} - async createOne(viewData: Partial): Promise { - const { workspaceId } = viewData; - - if (!isDefined(workspaceId)) { - throw new ViewException( - generateViewExceptionMessage( - ViewExceptionMessageKey.WORKSPACE_ID_REQUIRED, - ), - ViewExceptionCode.INVALID_VIEW_DATA, - { - userFriendlyMessage: generateViewUserFriendlyExceptionMessage( - ViewExceptionMessageKey.WORKSPACE_ID_REQUIRED, - ), - }, - ); - } - - const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + async createOne({ + createViewInput, + workspaceId, + }: { + createViewInput: CreateViewInput; + workspaceId: string; + }): Promise { + const { flatObjectMetadataMaps, flatViewMaps: existingFlatViewMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, + flatEntities: ['flatObjectMetadataMaps', 'flatViewMaps'], }, ); - const { flatViewMaps: existingFlatViewMaps } = - await this.viewCacheService.getExistingOrRecomputeFlatViewMaps({ - workspaceId, - }); - - const flatViewFromCreateInput = fromPartialFlatViewToFlatViewWithDefault({ - ...viewData, - universalIdentifier: viewData.universalIdentifier ?? v4(), + const flatViewFromCreateInput = fromCreateViewInputToFlatViewToCreate({ + createViewInput, + workspaceId, }); - const toFlatViewMaps: FlatViewMaps = addFlatEntityToFlatEntityMapsOrThrow({ + const toFlatViewMaps = addFlatEntityToFlatEntityMapsOrThrow({ flatEntity: flatViewFromCreateInput, flatEntityMaps: existingFlatViewMaps, }); @@ -84,7 +62,7 @@ export class ViewV2Service { }, }, dependencyAllFlatEntityMaps: { - flatObjectMetadataMaps: existingFlatObjectMetadataMaps, + flatObjectMetadataMaps: flatObjectMetadataMaps, }, buildOptions: { isSystemBuild: false, @@ -101,49 +79,40 @@ export class ViewV2Service { ); } - const [createdView] = await this.viewRepository.find({ - where: { - id: flatViewFromCreateInput.id, - }, - }); + const { flatViewMaps: recomputedExistingFlatViewMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewMaps'], + }, + ); - return createdView; + return findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: flatViewFromCreateInput.id, + flatEntityMaps: recomputedExistingFlatViewMaps, + }); } - async updateOne( - id: string, - workspaceId: string, - updateData: Partial, - ): Promise { + async updateOne({ + updateViewInput, + workspaceId, + }: { + updateViewInput: UpdateViewInput; + workspaceId: string; + }): Promise { const { flatViewMaps: existingFlatViewMaps } = - await this.viewCacheService.getExistingOrRecomputeFlatViewMaps({ - workspaceId, - }); - - const existingView = existingFlatViewMaps.byId[id]; - - if (!isDefined(existingView)) { - throw new ViewException( - generateViewExceptionMessage( - ViewExceptionMessageKey.VIEW_NOT_FOUND, - id, - ), - ViewExceptionCode.VIEW_NOT_FOUND, + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewMaps'], + }, ); - } - const existingViewToUpdate = removePropertiesFromRecord( - { - ...existingView, - ...updateData, - }, - VIEW_ENTITY_RELATION_PROPERTIES, - ); - - const flatViewFromUpdateInput = fromPartialFlatViewToFlatViewWithDefault({ - ...existingViewToUpdate, - universalIdentifier: existingViewToUpdate.universalIdentifier ?? '', - }); + const flatViewFromUpdateInput = + fromUpdateViewInputToFlatViewToUpdateOrThrow({ + updateViewInput, + flatViewMaps: existingFlatViewMaps, + }); const fromFlatViewMaps = getSubFlatEntityMapsOrThrow({ flatEntityIds: [flatViewFromUpdateInput.id], @@ -178,42 +147,48 @@ export class ViewV2Service { ); } - const [updatedView] = await this.viewRepository.find({ - where: { - id: Equal(id), - }, - }); + const { flatViewMaps: recomputedExistingFlatViewMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewMaps'], + }, + ); - return updatedView; + return findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: updateViewInput.id, + flatEntityMaps: recomputedExistingFlatViewMaps, + }); } - async deleteOne(id: string, workspaceId: string): Promise { + async deleteOne({ + deleteViewInput, + workspaceId, + }: { + deleteViewInput: DeleteViewInput; + workspaceId: string; + }): Promise { const { flatViewMaps: existingFlatViewMaps } = - await this.viewCacheService.getExistingOrRecomputeFlatViewMaps({ - workspaceId, - }); - - const existingViewToDelete = existingFlatViewMaps.byId[id]; - - if (!isDefined(existingViewToDelete)) { - throw new ViewException( - generateViewExceptionMessage( - ViewExceptionMessageKey.VIEW_NOT_FOUND, - id, - ), - ViewExceptionCode.VIEW_NOT_FOUND, + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewMaps'], + }, ); - } + + const flatViewFromDeleteInput = fromDeleteViewInputToFlatViewOrThrow({ + deleteViewInput, + flatViewMaps: existingFlatViewMaps, + }); const fromFlatViewMaps = getSubFlatEntityMapsOrThrow({ - flatEntityIds: [existingViewToDelete.id], + flatEntityIds: [flatViewFromDeleteInput.id], flatEntityMaps: existingFlatViewMaps, }); - const toFlatViewMaps: FlatViewMaps = - deleteFlatEntityFromFlatEntityMapsOrThrow({ - flatEntityMaps: fromFlatViewMaps, - entityToDeleteId: existingViewToDelete.id, - }); + const toFlatViewMaps = replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: flatViewFromDeleteInput, + flatEntityMaps: fromFlatViewMaps, + }); const validateAndBuildResult = await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( @@ -239,6 +214,84 @@ export class ViewV2Service { ); } - return true; + const { flatViewMaps: recomputedExistingFlatViewMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewMaps'], + }, + ); + + return findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: deleteViewInput.id, + flatEntityMaps: recomputedExistingFlatViewMaps, + }); + } + + async destroyOne({ + destroyViewInput, + workspaceId, + }: { + destroyViewInput: DestroyViewInput; + workspaceId: string; + }): Promise { + const { flatViewMaps: existingFlatViewMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewMaps'], + }, + ); + + const flatViewFromDestroyInput = fromDestroyViewInputToFlatViewOrThrow({ + destroyViewInput, + flatViewMaps: existingFlatViewMaps, + }); + + const fromFlatViewMaps = getSubFlatEntityMapsOrThrow({ + flatEntityIds: [flatViewFromDestroyInput.id], + flatEntityMaps: existingFlatViewMaps, + }); + const toFlatViewMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({ + flatEntityMaps: fromFlatViewMaps, + entityToDeleteId: flatViewFromDestroyInput.id, + }); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromToAllFlatEntityMaps: { + flatViewMaps: { + from: fromFlatViewMaps, + to: toFlatViewMaps, + }, + }, + buildOptions: { + isSystemBuild: false, + inferDeletionFromMissingEntities: true, + }, + workspaceId, + }, + ); + + if (isDefined(validateAndBuildResult)) { + throw new WorkspaceMigrationBuilderExceptionV2( + validateAndBuildResult, + 'Multiple validation errors occurred while destroying view', + ); + } + + const { flatViewMaps: recomputedExistingFlatViewMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatViewMaps'], + }, + ); + + return findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: destroyViewInput.id, + flatEntityMaps: recomputedExistingFlatViewMaps, + }); } } diff --git a/packages/twenty-server/src/engine/core-modules/view/view.module.ts b/packages/twenty-server/src/engine/core-modules/view/view.module.ts index 63da1a708e0..1150b2baa9b 100644 --- a/packages/twenty-server/src/engine/core-modules/view/view.module.ts +++ b/packages/twenty-server/src/engine/core-modules/view/view.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { I18nModule } from 'src/engine/core-modules/i18n/i18n.module'; -import { ViewCacheModule } from 'src/engine/core-modules/view/cache/services/view-cache.module'; import { ViewFieldController } from 'src/engine/core-modules/view/controllers/view-field.controller'; import { ViewFilterGroupController } from 'src/engine/core-modules/view/controllers/view-filter-group.controller'; import { ViewFilterController } from 'src/engine/core-modules/view/controllers/view-filter.controller'; @@ -50,8 +50,8 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa WorkspaceCacheStorageModule, WorkspaceMetadataCacheModule, WorkspaceMigrationV2Module, - ViewCacheModule, FlatViewModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, ], controllers: [ ViewController, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts index a9909c17dff..637e7657832 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts @@ -9,6 +9,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { ActorModule } from 'src/engine/core-modules/actor/actor.module'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { CoreViewModule } from 'src/engine/core-modules/view/view.module'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; @@ -66,6 +67,7 @@ import { FieldMetadataService } from './services/field-metadata.service'; WorkspaceMigrationV2Module, FlatFieldMetadataModule, IndexMetadataModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, ], services: [ IsFieldMetadataDefaultValue, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts index 5351e5dd379..b33e5bd725e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2.ts @@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; import { In, Repository } from 'typeorm'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.'; import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; @@ -22,7 +23,6 @@ import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/ import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util'; import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util'; import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; -import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; @@ -31,7 +31,7 @@ export class FieldMetadataServiceV2 { constructor( @InjectRepository(FieldMetadataEntity) private readonly fieldMetadataRepository: Repository, - private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, ) {} @@ -65,9 +65,10 @@ export class FieldMetadataServiceV2 { workspaceId: string; }): Promise { const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, + flatEntities: ['flatObjectMetadataMaps'], }, ); @@ -136,8 +137,11 @@ export class FieldMetadataServiceV2 { workspaceId: string; }): Promise { const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( - { workspaceId }, + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatObjectMetadataMaps'], + }, ); const inputTranspilationResult = fromUpdateFieldInputToFlatFieldMetadata({ @@ -221,8 +225,11 @@ export class FieldMetadataServiceV2 { } const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( - { workspaceId }, + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatEntities: ['flatObjectMetadataMaps'], + }, ); const allTranspiledTranspilationInputs = []; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant.ts index 57f24acb01c..e5a2734fb31 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant.ts @@ -3,4 +3,5 @@ import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-ob export const EMPTY_FLAT_OBJECT_METADATA_MAPS = { byId: {}, idByNameSingular: {}, + idByUniversalIdentifier: {}, } as const satisfies FlatObjectMetadataMaps; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type.ts index d38e20b60b0..aae926266ab 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type.ts @@ -3,4 +3,5 @@ import { type FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-mo export type FlatObjectMetadataMaps = { byId: Partial>; idByNameSingular: Partial>; + idByUniversalIdentifier: Record; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts index f6ce385b146..86fbb988993 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util.ts @@ -27,6 +27,9 @@ export const addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow = ({ } return { + idByUniversalIdentifier: { + ...flatObjectMetadataMaps.idByUniversalIdentifier, + }, byId: { ...flatObjectMetadataMaps.byId, [flatFieldMetadata.objectMetadataId]: diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util.ts index 1331007ad4f..71796ea5902 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util.ts @@ -24,6 +24,10 @@ export const addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow = ({ } return { + idByUniversalIdentifier: { + ...flatObjectMetadataMaps.idByUniversalIdentifier, + [flatObjectMetadata.universalIdentifier]: flatObjectMetadata.id, + }, byId: { ...flatObjectMetadataMaps.byId, [flatObjectMetadata.id]: diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-with-field-maps-to-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-with-field-maps-to-flat-object-metadata-maps-or-throw.util.ts index dce0799a2f1..b5191c3e7c8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-with-field-maps-to-flat-object-metadata-maps-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-with-field-maps-to-flat-object-metadata-maps-or-throw.util.ts @@ -29,6 +29,11 @@ export const addFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThr } return { + idByUniversalIdentifier: { + ...flatObjectMetadataMaps.idByUniversalIdentifier, + [flatObjectMetadataWithFlatFieldMaps.universalIdentifier]: + flatObjectMetadataWithFlatFieldMaps.id, + }, byId: { ...flatObjectMetadataMaps.byId, [flatObjectMetadataWithFlatFieldMaps.id]: diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util.ts index 0d6f5c1ee3d..678f8c67430 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util.ts @@ -28,6 +28,7 @@ export const deleteFieldFromFlatObjectMetadataMapsOrThrow = ({ } return { + idByUniversalIdentifier: flatObjectMetadataMaps.idByUniversalIdentifier, byId: { ...flatObjectMetadataMaps.byId, [objectMetadataId]: diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util.ts index 4741ab1411a..92ee02abf70 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util.ts @@ -25,7 +25,14 @@ export const deleteObjectFromFlatObjectMetadataMapsOrThrow = ({ flatObjectMetadataMaps.idByNameSingular, ).filter(([_nameSingular, id]) => id !== objectMetadataId); + const updatedIdByUniversalIdentifierEntries = Object.entries( + flatObjectMetadataMaps.idByUniversalIdentifier, + ).filter(([_universalIdentifier, id]) => id !== objectMetadataId); + return { + idByUniversalIdentifier: Object.fromEntries( + updatedIdByUniversalIdentifierEntries, + ), byId: removePropertiesFromRecord(flatObjectMetadataMaps.byId, [ objectMetadataId, ]), diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts index 66017a1329e..6466ee6a1cd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util.ts @@ -24,6 +24,15 @@ export const fromObjectMetadataMapsToFlatObjectMetadataMaps = ( }, initialAccumulator); return { + idByUniversalIdentifier: Object.values(objectMetadataMaps.byId) + .filter(isDefined) + .reduce( + (acc, objectMetadata) => ({ + ...acc, + [objectMetadata.standardId ?? objectMetadata.id]: objectMetadata.id, + }), + {}, + ), idByNameSingular: objectMetadataMaps.idByNameSingular, byId, }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts new file mode 100644 index 00000000000..114f768fc67 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; + +import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; +import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; +import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; +import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; +import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; +import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; +import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator'; +import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service'; + +@Injectable() +@WorkspaceFlatMapCache('flatObjectMetadataMaps') +export class WorkspaceFlatObjectMetadataMapCacheService extends WorkspaceFlatMapCacheService { + constructor( + @InjectCacheStorage(CacheStorageNamespace.EngineWorkspace) + cacheStorageService: CacheStorageService, + private workspaceMetadataCacheService: WorkspaceMetadataCacheService, + private workspaceMetadataVersionService: WorkspaceMetadataVersionService, + private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, + ) { + super(cacheStorageService); + } + + override async beforeInvalidateCache({ + workspaceId, + }: { + workspaceId: string; + }): Promise { + // Temporarily invalidating old cache too + await this.workspaceMetadataVersionService.incrementMetadataVersion( + workspaceId, + ); + /// + } + + override async afterInvalidateCache({ + workspaceId, + }: { + workspaceId: string; + }): Promise { + await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({ + workspaceId, + }); + } + + protected async computeFlatMap({ + workspaceId, + }: { + workspaceId: string; + }): Promise { + return ( + await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + { workspaceId }, + ) + ).flatObjectMetadataMaps; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts index 2c6413a11ca..996ad4fce44 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata-v2.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.'; import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util'; import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util'; @@ -23,7 +24,6 @@ import { ObjectMetadataException, ObjectMetadataExceptionCode, } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; -import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; @@ -31,7 +31,7 @@ import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspa @Injectable() export class ObjectMetadataServiceV2 { constructor( - private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, ) {} @@ -44,9 +44,10 @@ export class ObjectMetadataServiceV2 { updateObjectInput: UpdateOneObjectInput; }): Promise { const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, + flatEntities: ['flatObjectMetadataMaps'], }, ); @@ -81,6 +82,7 @@ export class ObjectMetadataServiceV2 { flatObjectMetadataMaps: fromFlatObjectMetadataMaps, }), ); + const validateAndBuildResult = await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( { @@ -106,9 +108,10 @@ export class ObjectMetadataServiceV2 { } const { flatObjectMetadataMaps: recomputedFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, + flatEntities: ['flatObjectMetadataMaps'], }, ); @@ -143,9 +146,10 @@ export class ObjectMetadataServiceV2 { workspaceId: string; }): Promise { const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, + flatEntities: ['flatObjectMetadataMaps'], }, ); @@ -225,9 +229,10 @@ export class ObjectMetadataServiceV2 { workspaceId: string; }): Promise { const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, + flatEntities: ['flatObjectMetadataMaps'], }, ); @@ -306,9 +311,10 @@ export class ObjectMetadataServiceV2 { } const { flatObjectMetadataMaps: recomputedFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { workspaceId, + flatEntities: ['flatObjectMetadataMaps'], }, ); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts index 24e6f84e8ae..f8d6205130f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts @@ -9,6 +9,7 @@ import { import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; @@ -72,6 +73,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa FeatureFlagModule, WorkspaceMigrationV2Module, CoreViewModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, ], services: [ ObjectMetadataService, diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator.ts b/packages/twenty-server/src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator.ts index f213492078a..1df1ab13ba6 100644 --- a/packages/twenty-server/src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator.ts +++ b/packages/twenty-server/src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator.ts @@ -1,6 +1,9 @@ import { SetMetadata } from '@nestjs/common'; +import { type ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/core-modules/common/constant/all-flat-entity-maps-properties.constant'; + export const WORKSPACE_FLAT_MAP_CACHE_KEY = 'workspaceFlatMapCacheKey'; -export const WorkspaceFlatMapCache = (cacheKey: string) => - SetMetadata(WORKSPACE_FLAT_MAP_CACHE_KEY, cacheKey); +export const WorkspaceFlatMapCache = ( + cacheKey: (typeof ALL_FLAT_ENTITY_MAPS_PROPERTIES)[number], +) => SetMetadata(WORKSPACE_FLAT_MAP_CACHE_KEY, cacheKey); diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service.ts b/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service.ts new file mode 100644 index 00000000000..7b2dccc0fc7 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service.ts @@ -0,0 +1,49 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { DiscoveryService } from '@nestjs/core'; + +import { AllFlatEntities } from 'src/engine/core-modules/common/types/all-flat-entities.type'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; +import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type'; +import { WORKSPACE_FLAT_MAP_CACHE_KEY } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator'; +import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service'; + +@Injectable() +export class WorkspaceFlatMapCacheRegistryService implements OnModuleInit { + private readonly cacheServiceMap = new Map< + keyof AllFlatEntityMaps, + WorkspaceFlatMapCacheService> + >(); + + constructor(private readonly discoveryService: DiscoveryService) {} + + async onModuleInit() { + this.discoverAndRegisterCacheServices(); + } + + private discoverAndRegisterCacheServices(): void { + const providers = this.discoveryService.getProviders(); + + providers.forEach((wrapper) => { + const { instance, metatype } = wrapper; + + if (!instance || !metatype) { + return; + } + + const cacheKey = Reflect.getMetadata( + WORKSPACE_FLAT_MAP_CACHE_KEY, + metatype, + ); + + if (cacheKey && instance instanceof WorkspaceFlatMapCacheService) { + this.cacheServiceMap.set(cacheKey, instance); + } + }); + } + + getCacheService( + flatEntityName: keyof AllFlatEntityMaps, + ): WorkspaceFlatMapCacheService> | undefined { + return this.cacheServiceMap.get(flatEntityName); + } +} diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service.ts b/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service.ts index ba62a699336..f2c74e083cb 100644 --- a/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service.ts +++ b/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service.ts @@ -99,6 +99,7 @@ export abstract class WorkspaceFlatMapCacheService< }: { workspaceId: string; }): Promise { + await this.beforeInvalidateCache({ workspaceId }); const { flatMapKey, hashKey } = this.buildRemoteCacheKeys({ workspaceId }); await this.cacheStorageService.del(flatMapKey); @@ -108,6 +109,19 @@ export abstract class WorkspaceFlatMapCacheService< this.localCacheHashes.delete(workspaceId); await this.recomputeAndStoreInCache({ workspaceId }); + await this.afterInvalidateCache({ workspaceId }); + } + + protected async beforeInvalidateCache(_args: { + workspaceId: string; + }): Promise { + return; + } + + protected async afterInvalidateCache(_args: { + workspaceId: string; + }): Promise { + return; } private getFlatMapCacheKey(): string { @@ -116,7 +130,7 @@ export abstract class WorkspaceFlatMapCacheService< this.constructor, ); - if (!cacheKey) { + if (!isDefined(cacheKey)) { throw new WorkspaceFlatMapCacheException( `${this.constructor.name} must be decorated with @WorkspaceFlatMapCache('cacheKey')`, WorkspaceFlatMapCacheExceptionCode.MISSING_DECORATOR, diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module.ts b/packages/twenty-server/src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module.ts index 55bd84c1908..cc8b721b5fa 100644 --- a/packages/twenty-server/src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module.ts +++ b/packages/twenty-server/src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module.ts @@ -1,10 +1,36 @@ import { Module } from '@nestjs/common'; +import { DiscoveryModule } from '@nestjs/core'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { CacheStorageModule } from 'src/engine/core-modules/cache-storage/cache-storage.module'; +import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity'; +import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; +import { WorkspaceFlatViewFieldMapCacheService } from 'src/engine/core-modules/view/flat-view/services/workspace-flat-view-field-map-cache.service'; +import { WorkspaceFlatViewMapCacheService } from 'src/engine/core-modules/view/flat-view/services/workspace-flat-view-map-cache.service'; +import { WorkspaceFlatObjectMetadataMapCacheService } from 'src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service'; +import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module'; +import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; +import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; +import { WorkspaceFlatMapCacheRegistryService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service'; @Module({ - imports: [CacheStorageModule], - providers: [], - exports: [], + imports: [ + DiscoveryModule, + WorkspaceMetadataCacheModule, + WorkspaceMetadataVersionModule, + WorkspacePermissionsCacheModule, + TypeOrmModule.forFeature([ViewEntity, ViewFieldEntity]), + ], + providers: [ + WorkspaceFlatMapCacheRegistryService, + WorkspaceFlatObjectMetadataMapCacheService, + WorkspaceFlatViewMapCacheService, + WorkspaceFlatViewFieldMapCacheService, + ], + exports: [ + WorkspaceFlatMapCacheRegistryService, + WorkspaceFlatObjectMetadataMapCacheService, + WorkspaceFlatViewMapCacheService, + WorkspaceFlatViewFieldMapCacheService, + ], }) export class WorkspaceFlatMapCacheModule {} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.module.ts index 421cdbfe881..4cb4db7d2c6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.module.ts @@ -10,11 +10,10 @@ import { ObjectMetadataHealthService } from 'src/engine/workspace-manager/worksp import { WorkspaceHealthService } from 'src/engine/workspace-manager/workspace-health/workspace-health.service'; import { WorkspaceMigrationBuilderModule } from 'src/engine/workspace-manager/workspace-migration-builder/workspace-migration-builder.module'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; +import { WorkspaceFixService } from 'src/engine/workspace-manager/workspace-health/services/workspace-fix.service'; import { workspaceFixers } from './fixer'; -import { WorkspaceFixService } from './services/workspace-fix.service'; - @Module({ imports: [ DataSourceModule, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module.ts index 6ed9294751a..9b0c0a19078 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module.ts @@ -4,11 +4,10 @@ import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace- import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceMigrationColumnService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-column.service'; import { WorkspaceMigrationEnumService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service'; +import { WorkspaceMigrationTypeService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-type.service'; import { WorkspaceMigrationRunnerService } from './workspace-migration-runner.service'; -import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service'; - @Module({ imports: [WorkspaceDataSourceModule, WorkspaceMigrationModule], providers: [ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/types/validate-error-response.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/types/validate-error-response.type.ts index 4a8ad0528b8..075dcc665dd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/types/validate-error-response.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/types/validate-error-response.type.ts @@ -1,4 +1,4 @@ -import { type AllFlatEntitiesByMetadataEngineName } from 'src/engine/core-modules/common/types/all-flat-entities.type'; +import { type AllFlatEntitiesByMetadataEngineName } from 'src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type'; import { type FieldMetadataMinimalInformation } from 'src/engine/metadata-modules/flat-field-metadata/types/field-metadata-minimal-information.type'; import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type'; import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts index 24b77598eba..e2c5b180c83 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts @@ -165,9 +165,14 @@ export class WorkspaceMigrationBuildOrchestratorService { }; } + const relatedFlatEntityMapsKeys = Object.keys( + fromToAllFlatEntityMaps, + ) as (keyof AllFlatEntityMaps)[]; + return { status: 'success', workspaceMigration: { + relatedFlatEntityMapsKeys, actions: allActions, workspaceId, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type.ts index 8da97e89f72..5dd4b65a882 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type.ts @@ -1,6 +1,6 @@ import { type FromTo } from 'twenty-shared/types'; -import { type AllFlatEntitiesByMetadataEngineName } from 'src/engine/core-modules/common/types/all-flat-entities.type'; +import { type AllFlatEntitiesByMetadataEngineName } from 'src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type'; import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type'; import { type WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/view-field-related-flat-entity-maps.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/view-field-related-flat-entity-maps.type.ts new file mode 100644 index 00000000000..ef6c61602ba --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/view-field-related-flat-entity-maps.type.ts @@ -0,0 +1,6 @@ +import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; + +export type ViewFieldRelatedFlatEntityMaps = Pick< + AllFlatEntityMaps, + 'flatObjectMetadataMaps' | 'flatViewMaps' +>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service.ts index 7e3a37360d2..c2637063a6e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type'; import { compareTwoFlatViewField } from 'src/engine/core-modules/view/flat-view/utils/compare-two-flat-view-field.util'; +import { ViewFieldRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/view-field-related-flat-entity-maps.type'; import { FlatEntityUpdateValidationArgs, FlatEntityValidationArgs, @@ -19,10 +19,6 @@ import { } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-view-field-action'; import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service'; -export type ViewFieldRelatedFlatEntityMaps = Pick< - AllFlatEntityMaps, - 'flatObjectMetadataMaps' | 'flatViewMaps' ->; @Injectable() export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service< FlatViewField, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2.ts index 798b50946c0..45fea14831e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2.ts @@ -1,3 +1,4 @@ +import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; export type WorkspaceMigrationV2< @@ -9,5 +10,5 @@ export type WorkspaceMigrationV2< // description?: string; actions: TActions[]; workspaceId: string; - // objectActions: TActions[] // could be cool ? + relatedFlatEntityMapsKeys?: (keyof AllFlatEntityMaps)[]; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service.ts index 5924a858d01..dd65d7f9197 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service.ts @@ -8,7 +8,7 @@ import { FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/ import { FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type'; import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util'; import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type'; -import { ViewFieldRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service'; +import { ViewFieldRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/view-field-related-flat-entity-maps.type'; type ViewFieldValidationArgs = { flatViewFieldToValidate: FlatViewField; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts index efc49a05d8f..9cd3b648510 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts @@ -1,8 +1,13 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; import { type CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2'; @@ -25,6 +30,22 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatObjectMetadataMaps } = allFlatEntityMaps; + const updatedFlatObjectMetadataMaps = + addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ + flatFieldMetadata: action.flatFieldMetadata, + flatObjectMetadataMaps, + }); + + return { + flatObjectMetadataMaps: updatedFlatObjectMetadataMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { @@ -42,8 +63,12 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct async executeForWorkspaceSchema( context: WorkspaceMigrationActionRunnerArgs, ): Promise { - const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = - context; + const { + action, + queryRunner, + allFlatEntityMaps: { flatObjectMetadataMaps }, + workspaceId, + } = context; const { flatFieldMetadata } = action; const flatObjectMetadataWithFlatFieldMaps = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service.ts index fc457379664..203f7c779e6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/delete-field-action-handler.service.ts @@ -2,9 +2,14 @@ import { Injectable } from '@nestjs/common'; import { In } from 'typeorm'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util'; import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; @@ -29,6 +34,25 @@ export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerAct super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatObjectMetadataMaps } = allFlatEntityMaps; + const { fieldMetadataId, objectMetadataId } = action; + + const updatedFlatObjectMetadataMaps = + deleteFieldFromFlatObjectMetadataMapsOrThrow({ + fieldMetadataId, + flatObjectMetadataMaps, + objectMetadataId, + }); + + return { + flatObjectMetadataMaps: updatedFlatObjectMetadataMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ) { @@ -48,8 +72,12 @@ export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerAct async executeForWorkspaceSchema( context: WorkspaceMigrationActionRunnerArgs, ) { - const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = - context; + const { + action, + queryRunner, + allFlatEntityMaps: { flatObjectMetadataMaps }, + workspaceId, + } = context; const { objectMetadataId, fieldMetadataId } = action; const flatObjectMetadataWithFlatFieldMaps = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service.ts index ccde9430df9..f99fab32e9e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/update-field-action-handler.service.ts @@ -4,8 +4,12 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { ColumnType, type QueryRunner } from 'typeorm'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; @@ -17,7 +21,9 @@ import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-f import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util'; import { findFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; +import { findFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps.util'; import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; +import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util'; import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util'; @@ -61,6 +67,43 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatObjectMetadataMaps } = allFlatEntityMaps; + const { fieldMetadataId, objectMetadataId } = action; + + const existingFlatFieldMetadata = + findFlatFieldMetadataInFlatObjectMetadataMaps({ + fieldMetadataId, + objectMetadataId, + flatObjectMetadataMaps, + }); + + if (!isDefined(existingFlatFieldMetadata)) { + throw new WorkspaceMigrationRunnerException( + `Workspace migration failed: Field metadata not found in cache`, + WorkspaceMigrationRunnerExceptionCode.FIELD_METADATA_NOT_FOUND, + ); + } + + const updatedFlatFieldMetadata = { + ...existingFlatFieldMetadata, + ...fromWorkspaceMigrationUpdateActionToPartialEntity(action), + }; + + const updatedFlatObjectMetadataMaps = + replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ + flatFieldMetadata: updatedFlatFieldMetadata, + flatObjectMetadataMaps, + }); + + return { + flatObjectMetadataMaps: updatedFlatObjectMetadataMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { @@ -81,8 +124,12 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct async executeForWorkspaceSchema( context: WorkspaceMigrationActionRunnerArgs, ): Promise { - const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = - context; + const { + action, + queryRunner, + allFlatEntityMaps: { flatObjectMetadataMaps }, + workspaceId, + } = context; const { objectMetadataId, fieldMetadataId, updates } = action; const flatObjectMetadataWithFlatFieldMaps = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service.ts index 6f5f6c21afe..ec1174394b4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/create-index-action-handler.service.ts @@ -1,7 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { type CreateIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2'; import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; @@ -9,6 +13,12 @@ import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-ma export class CreateIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler( 'create_index', ) { + optimisticallyApplyActionOnAllFlatEntityMaps( + _args: OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + ): Partial { + return {}; + } + async executeForMetadata( _context: WorkspaceMigrationActionRunnerArgs, ): Promise { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service.ts index 366e7efc2db..287f2b61315 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/index/services/delete-index-action-handler.service.ts @@ -1,7 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { type DeleteIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2'; import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; @@ -9,6 +13,12 @@ import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-ma export class DeleteIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler( 'delete_index', ) { + optimisticallyApplyActionOnAllFlatEntityMaps( + _args: OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + ): Partial { + return {}; + } + async executeForMetadata( _context: WorkspaceMigrationActionRunnerArgs, ): Promise { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service.ts index 8703ce49f8c..c453fff7f8c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/create-object-action-handler.service.ts @@ -1,13 +1,17 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; 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 { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util'; import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; +import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; import { type CreateObjectAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2'; import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; @@ -25,11 +29,36 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc ) { constructor( private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) { super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatObjectMetadataMaps } = allFlatEntityMaps; + const { flatObjectMetadataWithoutFields, createFieldActions } = action; + + const flatFieldMetadatas = createFieldActions.map( + (createFieldAction) => createFieldAction.flatFieldMetadata, + ); + + const updatedFlatObjectMetadataMaps = + addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({ + flatObjectMetadata: { + ...flatObjectMetadataWithoutFields, + flatIndexMetadatas: [], + flatFieldMetadatas, + }, + flatObjectMetadataMaps, + }); + + return { + flatObjectMetadataMaps: updatedFlatObjectMetadataMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service.ts index 907ab5df8fe..4997e6d4a22 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/delete-object-action-handler.service.ts @@ -1,10 +1,15 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util'; import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; +import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util'; import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; @@ -27,6 +32,24 @@ export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerAc super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatObjectMetadataMaps } = allFlatEntityMaps; + const { objectMetadataId } = action; + + const updatedFlatObjectMetadataMaps = + deleteObjectFromFlatObjectMetadataMapsOrThrow({ + flatObjectMetadataMaps, + objectMetadataId, + }); + + return { + flatObjectMetadataMaps: updatedFlatObjectMetadataMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { @@ -44,8 +67,12 @@ export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerAc async executeForWorkspaceSchema( context: WorkspaceMigrationActionRunnerArgs, ): Promise { - const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = - context; + const { + action, + queryRunner, + allFlatEntityMaps: { flatObjectMetadataMaps }, + workspaceId, + } = context; const { objectMetadataId } = action; const flatObjectMetadataWithFlatFieldMaps = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service.ts index 09272041f75..15a80a704a4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/object/services/update-object-action-handler.service.ts @@ -1,11 +1,17 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util'; import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; +import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; import { findFlatObjectMetadataWithFlatFieldMapsInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-with-flat-field-maps-in-flat-object-metadata-maps-or-throw.util'; +import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; @@ -29,6 +35,35 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatObjectMetadataMaps } = allFlatEntityMaps; + const { objectMetadataId } = action; + + const existingFlatObjectMetadata = + findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + objectMetadataId, + flatObjectMetadataMaps, + }); + + const updatedFlatObjectMetadata = { + ...existingFlatObjectMetadata, + ...fromWorkspaceMigrationUpdateActionToPartialEntity(action), + }; + + const updatedFlatObjectMetadataMaps = + replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ + flatObjectMetadata: updatedFlatObjectMetadata, + flatObjectMetadataMaps, + }); + + return { + flatObjectMetadataMaps: updatedFlatObjectMetadataMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { @@ -48,8 +83,12 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc async executeForWorkspaceSchema( context: WorkspaceMigrationActionRunnerArgs, ): Promise { - const { action, queryRunner, flatObjectMetadataMaps, workspaceId } = - context; + const { + action, + queryRunner, + allFlatEntityMaps: { flatObjectMetadataMaps }, + workspaceId, + } = context; const { objectMetadataId, updates } = action; const flatObjectMetadataWithFlatFieldMaps = diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/create-view-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/create-view-field-action-handler.service.ts index a179f6b034f..6c0ac3e6f02 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/create-view-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/create-view-field-action-handler.service.ts @@ -1,7 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity'; import { CreateViewFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type'; import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; @@ -14,6 +19,23 @@ export class CreateViewFieldActionHandlerService extends WorkspaceMigrationRunne super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatViewFieldMaps } = allFlatEntityMaps; + const { viewField } = action; + + const updatedFlatViewFieldMaps = addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: viewField, + flatEntityMaps: flatViewFieldMaps, + }); + + return { + flatViewFieldMaps: updatedFlatViewFieldMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/delete-view-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/delete-view-field-action-handler.service.ts index 5d47d3f7442..5c1905ae511 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/delete-view-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/delete-view-field-action-handler.service.ts @@ -1,7 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; +import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util'; import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity'; import { DeleteViewFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type'; import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; @@ -14,6 +19,23 @@ export class DeleteViewFieldActionHandlerService extends WorkspaceMigrationRunne super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatViewFieldMaps } = allFlatEntityMaps; + const { viewFieldId } = action; + + const updatedFlatViewFieldMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({ + entityToDeleteId: viewFieldId, + flatEntityMaps: flatViewFieldMaps, + }); + + return { + flatViewFieldMaps: updatedFlatViewFieldMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/update-view-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/update-view-field-action-handler.service.ts index 7808b242b88..b229c6368ea 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/update-view-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-field/services/update-view-field-action-handler.service.ts @@ -1,7 +1,13 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { 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 { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util'; import { ViewFieldEntity } from 'src/engine/core-modules/view/entities/view-field.entity'; import { UpdateViewFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-field-action-v2.type'; import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; @@ -15,6 +21,33 @@ export class UpdateViewFieldActionHandlerService extends WorkspaceMigrationRunne super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatViewFieldMaps } = allFlatEntityMaps; + const { viewFieldId } = action; + + const existingViewField = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: viewFieldId, + flatEntityMaps: flatViewFieldMaps, + }); + + const updatedViewField = { + ...existingViewField, + ...fromWorkspaceMigrationUpdateActionToPartialEntity(action), + }; + + const updatedFlatViewFieldMaps = replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: updatedViewField, + flatEntityMaps: flatViewFieldMaps, + }); + + return { + flatViewFieldMaps: updatedFlatViewFieldMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/create-view-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/create-view-action-handler.service.ts index 41e6aee2256..a3278dd92fa 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/create-view-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/create-view-action-handler.service.ts @@ -1,7 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; import { CreateViewAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type'; import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; @@ -14,6 +19,23 @@ export class CreateViewActionHandlerService extends WorkspaceMigrationRunnerActi super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatViewMaps } = allFlatEntityMaps; + const { view } = action; + + const updatedFlatViewMaps = addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: view, + flatEntityMaps: flatViewMaps, + }); + + return { + flatViewMaps: updatedFlatViewMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/delete-view-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/delete-view-action-handler.service.ts index c460ac496e3..c3d314d15f5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/delete-view-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/delete-view-action-handler.service.ts @@ -1,7 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; +import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util'; import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; import { DeleteViewAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type'; import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; @@ -14,6 +19,23 @@ export class DeleteViewActionHandlerService extends WorkspaceMigrationRunnerActi super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatViewMaps } = allFlatEntityMaps; + const { viewId } = action; + + const updatedFlatViewMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({ + entityToDeleteId: viewId, + flatEntityMaps: flatViewMaps, + }); + + return { + flatViewMaps: updatedFlatViewMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/update-view-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/update-view-action-handler.service.ts index ea72bca1057..226c1e2bda1 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/update-view-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view/services/update-view-action-handler.service.ts @@ -1,7 +1,13 @@ import { Injectable } from '@nestjs/common'; -import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { 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 { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util'; import { ViewEntity } from 'src/engine/core-modules/view/entities/view.entity'; import { UpdateViewAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-view-action-v2.type'; import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; @@ -15,6 +21,33 @@ export class UpdateViewActionHandlerService extends WorkspaceMigrationRunnerActi super(); } + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatViewMaps } = allFlatEntityMaps; + const { viewId } = action; + + const existingView = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: viewId, + flatEntityMaps: flatViewMaps, + }); + + const updatedView = { + ...existingView, + ...fromWorkspaceMigrationUpdateActionToPartialEntity(action), + }; + + const updatedFlatViewMaps = replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: updatedView, + flatEntityMaps: flatViewMaps, + }); + + return { + flatViewMaps: updatedFlatViewMaps, + }; + } + async executeForMetadata( context: WorkspaceMigrationActionRunnerArgs, ): Promise { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts index 566b88f5b44..2d7849f3795 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts @@ -14,4 +14,5 @@ export const WorkspaceMigrationRunnerExceptionCode = appendCommonExceptionCode({ UNSUPPORTED_COMPOSITE_COLUMN_TYPE: 'UNSUPPORTED_COMPOSITE_COLUMN_TYPE', NOT_SUPPORTED: 'NOT_SUPPORTED', INVALID_ACTION_TYPE: 'INVALID_ACTION_TYPE', + FLAT_ENTITY_NOT_FOUND: 'FLAT_ENTITY_NOT_FOUND', } as const); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface.ts index e23a58c03b4..18d7798c7b8 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface.ts @@ -1,46 +1,68 @@ import { SetMetadata } from '@nestjs/common'; +import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { type ExtractAction, type WorkspaceMigrationActionTypeV2, + type WorkspaceMigrationActionV2, } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; import { WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/constants/workspace-migration-action-handler-metadata-key.constant'; import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; export interface WorkspaceMigrationRunnerActionHandlerService< - T extends WorkspaceMigrationActionTypeV2, + TActionType extends WorkspaceMigrationActionTypeV2, > { execute( - context: WorkspaceMigrationActionRunnerArgs>, - ): Promise; + context: WorkspaceMigrationActionRunnerArgs>, + ): Promise>; } +export type OptimisticallyApplyActionOnAllFlatEntityMapsArgs< + TActionType extends WorkspaceMigrationActionV2, +> = Pick< + WorkspaceMigrationActionRunnerArgs, + 'allFlatEntityMaps' | 'action' +>; + export abstract class BaseWorkspaceMigrationRunnerActionHandlerService< - T extends WorkspaceMigrationActionTypeV2, -> implements WorkspaceMigrationRunnerActionHandlerService + TActionType extends WorkspaceMigrationActionTypeV2, +> implements WorkspaceMigrationRunnerActionHandlerService { abstract executeForMetadata( - context: WorkspaceMigrationActionRunnerArgs>, + context: WorkspaceMigrationActionRunnerArgs>, ): Promise; abstract executeForWorkspaceSchema( - context: WorkspaceMigrationActionRunnerArgs>, + context: WorkspaceMigrationActionRunnerArgs>, ): Promise; + abstract optimisticallyApplyActionOnAllFlatEntityMaps( + args: OptimisticallyApplyActionOnAllFlatEntityMapsArgs< + ExtractAction + >, + ): Partial; + async execute( - context: WorkspaceMigrationActionRunnerArgs>, - ): Promise { + context: WorkspaceMigrationActionRunnerArgs>, + ): Promise> { await Promise.all([ this.executeForMetadata(context), this.executeForWorkspaceSchema(context), ]); + + return this.optimisticallyApplyActionOnAllFlatEntityMaps({ + action: context.action, + allFlatEntityMaps: context.allFlatEntityMaps, + }); } } export function WorkspaceMigrationRunnerActionHandler< - T extends WorkspaceMigrationActionTypeV2, ->(actionType: T): typeof BaseWorkspaceMigrationRunnerActionHandlerService { - abstract class ActionHandlerService extends BaseWorkspaceMigrationRunnerActionHandlerService {} + TActionType extends WorkspaceMigrationActionTypeV2, +>( + actionType: TActionType, +): typeof BaseWorkspaceMigrationRunnerActionHandlerService { + abstract class ActionHandlerService extends BaseWorkspaceMigrationRunnerActionHandlerService {} SetMetadata( WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service.ts index 541b206fdf2..c1beb78a636 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service.ts @@ -3,6 +3,7 @@ import { DiscoveryService } from '@nestjs/core'; import { type WorkspaceMigrationRunnerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { type WorkspaceMigrationActionTypeV2, type WorkspaceMigrationActionV2, @@ -54,7 +55,7 @@ export class WorkspaceMigrationRunnerActionHandlerRegistryService async executeActionHandler( actionType: WorkspaceMigrationActionTypeV2, context: WorkspaceMigrationActionRunnerArgs, - ): Promise { + ): Promise> { const handler = this.actionHandlers.get(actionType); if (!handler) { @@ -64,6 +65,6 @@ export class WorkspaceMigrationRunnerActionHandlerRegistryService ); } - await handler.execute(context); + return await handler.execute(context); } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts index 73735b172c3..b5bf2b08c05 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts @@ -3,77 +3,81 @@ import { InjectDataSource } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; -import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; -import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; -import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; -import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; -import { type WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2'; +import { + WorkspaceQueryRunnerException, + WorkspaceQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.'; +import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; +import { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2'; import { WorkspaceMigrationRunnerActionHandlerRegistryService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service'; -import { applyWorkspaceMigrationActionOnFlatObjectMetadataMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps'; @Injectable() export class WorkspaceMigrationRunnerV2Service { constructor( - private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, - private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, - private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, @InjectDataSource() private readonly coreDataSource: DataSource, private readonly workspaceMigrationRunnerActionHandlerRegistry: WorkspaceMigrationRunnerActionHandlerRegistryService, ) {} - run = async ( - workspaceMigration: WorkspaceMigrationV2, - ): Promise => { + run = async ({ + actions, + workspaceId, + relatedFlatEntityMapsKeys, + }: WorkspaceMigrationV2): Promise => { const queryRunner = this.coreDataSource.createQueryRunner(); - const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } = - await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps( + let allFlatEntityMaps = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { - workspaceId: workspaceMigration.workspaceId, + workspaceId, + flatEntities: relatedFlatEntityMapsKeys, }, ); await queryRunner.connect(); await queryRunner.startTransaction(); - let optimisticFlatObjectMetadataMaps = structuredClone( - existingFlatObjectMetadataMaps, - ); + + let flatEntityMapsToInvalidate: (keyof AllFlatEntityMaps)[] = []; try { - for (const action of workspaceMigration.actions) { - await this.workspaceMigrationRunnerActionHandlerRegistry.executeActionHandler( - action.type, - { - action, - flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, - queryRunner, - workspaceId: workspaceMigration.workspaceId, - }, - ); + for (const action of actions) { + const partialOptimisticCache = + await this.workspaceMigrationRunnerActionHandlerRegistry.executeActionHandler( + action.type, + { + action, + allFlatEntityMaps, + queryRunner, + workspaceId, + }, + ); + const optimisticallyUpdatedFlatEntityMapsKeys = Object.keys( + partialOptimisticCache, + ) as (keyof AllFlatEntityMaps)[]; - optimisticFlatObjectMetadataMaps = - applyWorkspaceMigrationActionOnFlatObjectMetadataMaps({ - action, - flatObjectMetadataMaps: optimisticFlatObjectMetadataMaps, - workspaceId: workspaceMigration.workspaceId, - }); + flatEntityMapsToInvalidate = [ + ...new Set([ + ...optimisticallyUpdatedFlatEntityMapsKeys, + ...flatEntityMapsToInvalidate, + ]), + ]; + + allFlatEntityMaps = { + ...allFlatEntityMaps, + ...partialOptimisticCache, + }; } await queryRunner.commitTransaction(); - const { workspaceId } = workspaceMigration; - await this.workspaceMetadataVersionService.incrementMetadataVersion( + await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({ workspaceId, - ); + flatEntities: flatEntityMapsToInvalidate, + }); - await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache( - { - workspaceId, - }, - ); - - return optimisticFlatObjectMetadataMaps; + return allFlatEntityMaps; } catch (error) { if (queryRunner.isTransactionActive) { try { @@ -83,7 +87,10 @@ export class WorkspaceMigrationRunnerV2Service { console.trace(`Failed to rollback transaction: ${error.message}`); } } - throw error; + throw new WorkspaceQueryRunnerException( + error.message, + WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR, + ); } finally { await queryRunner.release(); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type.ts index 0355dc1c1f5..2f00a0ae36e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type.ts @@ -1,6 +1,6 @@ import { type QueryRunner } from 'typeorm'; -import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; +import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type'; import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; export type WorkspaceMigrationActionRunnerArgs< @@ -8,6 +8,6 @@ export type WorkspaceMigrationActionRunnerArgs< > = { queryRunner: QueryRunner; action: T; - flatObjectMetadataMaps: FlatObjectMetadataMaps; + allFlatEntityMaps: AllFlatEntityMaps; workspaceId: string; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps.ts deleted file mode 100644 index 65aa62f1027..00000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/apply-workspace-migration-action-on-flat-object-metadata-maps.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { assertUnreachable, isDefined } from 'twenty-shared/utils'; - -import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type'; -import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; -import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util'; -import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util'; -import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util'; -import { findFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps.util'; -import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util'; -import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util'; -import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util'; -import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2'; -import { - WorkspaceMigrationRunnerException, - WorkspaceMigrationRunnerExceptionCode, -} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception'; -import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; -import { fromWorkspaceMigrationUpdateActionToPartialEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-workspace-migration-update-action-to-partial-field-or-object-entity.util'; - -export const applyWorkspaceMigrationActionOnFlatObjectMetadataMaps = < - T extends WorkspaceMigrationActionV2, ->({ - action, - flatObjectMetadataMaps, -}: Omit< - WorkspaceMigrationActionRunnerArgs, - 'queryRunner' ->): FlatObjectMetadataMaps => { - switch (action.type) { - case 'delete_object': { - return deleteObjectFromFlatObjectMetadataMapsOrThrow({ - flatObjectMetadataMaps, - objectMetadataId: action.objectMetadataId, - }); - } - case 'create_object': { - const flatObjectMetadataWithoutFields = - action.flatObjectMetadataWithoutFields; - const flatFieldMetadatas = action.createFieldActions.map( - (createFieldAction) => createFieldAction.flatFieldMetadata, - ); - - return addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({ - flatObjectMetadata: { - ...flatObjectMetadataWithoutFields, - flatIndexMetadatas: [], - flatFieldMetadatas, - }, - flatObjectMetadataMaps, - }); - } - case 'update_object': { - const { objectMetadataId } = action; - const existingFlatObjectMetadata = - findFlatObjectMetadataInFlatObjectMetadataMaps({ - objectMetadataId, - flatObjectMetadataMaps, - }); - - if (!isDefined(existingFlatObjectMetadata)) { - throw new WorkspaceMigrationRunnerException( - `Workspace migration failed: Object metadata not found in cache`, - WorkspaceMigrationRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND, - ); - } - const updatedFlatObjectMetadata = { - ...existingFlatObjectMetadata, - ...fromWorkspaceMigrationUpdateActionToPartialEntity(action), - }; - - return replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({ - flatObjectMetadata: updatedFlatObjectMetadata, - flatObjectMetadataMaps, - }); - } - case 'create_field': { - const updatedFlatObjectMetadataMaps = - addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ - flatFieldMetadata: action.flatFieldMetadata, - flatObjectMetadataMaps, - }); - - if (!isDefined(updatedFlatObjectMetadataMaps)) { - throw new WorkspaceMigrationRunnerException( - `Workspace migration failed: dispatchAndAddFlatFieldMetadataInFlatObjectMetadataMaps failed`, - WorkspaceMigrationRunnerExceptionCode.FIELD_METADATA_NOT_FOUND, - ); - } - - return updatedFlatObjectMetadataMaps; - } - case 'update_field': { - const { fieldMetadataId, objectMetadataId } = action; - const existingFlatFieldMetadata = - findFlatFieldMetadataInFlatObjectMetadataMaps({ - fieldMetadataId, - objectMetadataId, - flatObjectMetadataMaps, - }); - - if (!isDefined(existingFlatFieldMetadata)) { - throw new WorkspaceMigrationRunnerException( - `Workspace migration failed: Field metadata not found in cache`, - WorkspaceMigrationRunnerExceptionCode.FIELD_METADATA_NOT_FOUND, - ); - } - - const updatedFlatFieldMetadata = { - ...existingFlatFieldMetadata, - ...fromWorkspaceMigrationUpdateActionToPartialEntity(action), - }; - - const updatedFlatObjectMetadataMaps = - replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({ - flatFieldMetadata: updatedFlatFieldMetadata, - flatObjectMetadataMaps, - }); - - if (!isDefined(updatedFlatObjectMetadataMaps)) { - throw new WorkspaceMigrationRunnerException( - `Workspace migration failed: dispatchAndReplaceFlatFieldMetadataInFlatObjectMetadataMaps failed`, - WorkspaceMigrationRunnerExceptionCode.FIELD_METADATA_NOT_FOUND, - ); - } - - return updatedFlatObjectMetadataMaps; - } - case 'delete_field': { - return deleteFieldFromFlatObjectMetadataMapsOrThrow({ - fieldMetadataId: action.fieldMetadataId, - flatObjectMetadataMaps, - objectMetadataId: action.objectMetadataId, - }); - } - case 'create_index': - case 'delete_index': - case 'create_view': - case 'update_view': - case 'delete_view': - case 'create_view_field': - case 'update_view_field': - case 'delete_view_field': - return flatObjectMetadataMaps; - default: { - assertUnreachable( - action, - 'Should never occur, encountered an unsupported workspace migration action type', - ); - } - } -}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts index 13b3b0bfcff..aadd572b4ab 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module.ts @@ -2,11 +2,10 @@ import { Module } from '@nestjs/common'; import { DiscoveryModule } from '@nestjs/core'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module'; -import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; -import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; import { WorkspaceSchemaMigrationRunnerActionHandlersModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module'; import { WorkspaceMigrationRunnerActionHandlerRegistryService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/registry/workspace-migration-runner-action-handler-registry.service'; import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service'; @@ -16,10 +15,9 @@ import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/ FeatureFlagModule, TypeORMModule, DataSourceModule, - WorkspaceMetadataVersionModule, - WorkspacePermissionsCacheModule, WorkspaceMetadataCacheModule, WorkspaceSchemaMigrationRunnerActionHandlersModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, DiscoveryModule, ], providers: [ diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts index f326158ef69..b65ec657fa5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; -import { ViewCacheModule } from 'src/engine/core-modules/view/cache/services/view-cache.module'; import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module'; import { WorkspaceMigrationBuildOrchestratorService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; @@ -12,7 +11,6 @@ import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/w WorkspaceMigrationBuilderV2Module, WorkspaceMigrationRunnerV2Module, WorkspaceMetadataCacheModule, - ViewCacheModule, ], providers: [ WorkspaceMigrationValidateBuildAndRunService, diff --git a/packages/twenty-server/test/integration/graphql/suites/view/successful-create-view-field-v2.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/successful-create-view-field-v2.integration-spec.ts index 5f054e0e5ea..7f3965c087f 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/successful-create-view-field-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/successful-create-view-field-v2.integration-spec.ts @@ -1,15 +1,15 @@ import { createOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/create-one-core-view-field.util'; +import { deleteOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-core-view-field.util'; +import { destroyOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-core-view-field.util'; import { assertViewFieldStructure } from 'test/integration/utils/view-test.util'; import { type EachTestingContext, eachTestingContextFilter, } from 'twenty-shared/testing'; -import { deleteOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-core-view-field.util'; -import { destroyOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-core-view-field.util'; import { isDefined } from 'twenty-shared/utils'; -import { type ViewFieldDTO } from 'src/engine/core-modules/view/dtos/view-field.dto'; import { type CreateViewFieldInput } from 'src/engine/core-modules/view/dtos/inputs/create-view-field.input'; +import { type ViewFieldDTO } from 'src/engine/core-modules/view/dtos/view-field.dto'; import { cleanupViewFieldTestV2, diff --git a/packages/twenty-server/test/integration/graphql/suites/view/utils/setup-view-field-test-v2.util.ts b/packages/twenty-server/test/integration/graphql/suites/view/utils/setup-view-field-test-v2.util.ts index 9f65177daac..bd1005cccbb 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/utils/setup-view-field-test-v2.util.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/utils/setup-view-field-test-v2.util.ts @@ -1,10 +1,10 @@ -import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.util'; import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { FieldMetadataType } from 'twenty-shared/types'; +import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -57,13 +57,21 @@ export const setupViewFieldTestV2 = async (): Promise => { `, }); - const view = await createTestViewWithGraphQL({ - name: 'Test View for Fields', - objectMetadataId, + const { + data: { + createCoreView: { id: testViewId }, + }, + } = await createOneCoreView({ + input: { + icon: 'icon123', + objectMetadataId, + name: 'TestViewForFields', + }, + expectToFail: false, }); return { - testViewId: view.id, + testViewId, testObjectMetadataId: objectMetadataId, testFieldMetadataId: fieldMetadataId, };