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
This commit is contained in:
Paul Rastoin
2025-09-16 14:39:42 +02:00
committed by GitHub
parent 8f42c33bd4
commit 371c26bc9b
79 changed files with 1534 additions and 575 deletions
@@ -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)[];
@@ -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,
@@ -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 {}
@@ -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<Pick<AllFlatEntityMaps, T[number]>> {
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<void> {
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;
}
}
}
}
@@ -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;
};
@@ -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];
@@ -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 {}
@@ -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<ViewEntity>,
@InjectRepository(ViewFieldEntity)
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
) {}
async getExistingOrRecomputeFlatViewMaps({
workspaceId,
}: {
workspaceId: string;
}): Promise<GetExistingOrRecomputeFlatViewMapsResult> {
// 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 };
}
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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[];
}
@@ -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',
@@ -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)[];
@@ -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)[];
@@ -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)[];
@@ -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)[];
@@ -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<FlatViewFieldMaps> {
constructor(
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
cacheStorageService: CacheStorageService,
@InjectRepository(ViewFieldEntity)
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
) {
super(cacheStorageService);
}
protected async computeFlatMap({
workspaceId,
}: {
workspaceId: string;
}): Promise<FlatViewFieldMaps> {
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;
}
}
@@ -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<FlatViewMaps> {
constructor(
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
@@ -33,6 +33,7 @@ export class WorkspaceFlatViewMapCacheService extends WorkspaceFlatMapCacheServi
where: {
workspaceId,
},
withDeleted: true,
relations: ['viewFields'],
select: {
viewFields: {
@@ -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: [],
};
};
@@ -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(),
};
};
@@ -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;
};
@@ -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,
});
};
@@ -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,
});
};
@@ -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<ViewDTO> {
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<ViewDTO> {
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<boolean> {
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<boolean> {
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);
@@ -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<ViewFieldDTO> {
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<ViewFieldDTO> {
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<ViewFieldDTO> {
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<ViewFieldDTO> {
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({
@@ -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<ViewEntity>,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly viewCacheService: ViewCacheService,
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
async createOne(viewData: Partial<ViewEntity>): Promise<ViewEntity> {
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<ViewDTO> {
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<ViewEntity>,
): Promise<ViewEntity> {
async updateOne({
updateViewInput,
workspaceId,
}: {
updateViewInput: UpdateViewInput;
workspaceId: string;
}): Promise<ViewDTO> {
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<boolean> {
async deleteOne({
deleteViewInput,
workspaceId,
}: {
deleteViewInput: DeleteViewInput;
workspaceId: string;
}): Promise<ViewDTO> {
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<ViewDTO> {
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,
});
}
}
@@ -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,
@@ -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,
@@ -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<FieldMetadataEntity>,
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
) {}
@@ -65,9 +65,10 @@ export class FieldMetadataServiceV2 {
workspaceId: string;
}): Promise<FieldMetadataDTO> {
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatObjectMetadataMaps'],
},
);
@@ -136,8 +137,11 @@ export class FieldMetadataServiceV2 {
workspaceId: string;
}): Promise<FieldMetadataEntity> {
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 = [];
@@ -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;
@@ -3,4 +3,5 @@ import { type FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-mo
export type FlatObjectMetadataMaps = {
byId: Partial<Record<string, FlatObjectMetadataWithFlatFieldMaps>>;
idByNameSingular: Partial<Record<string, string>>;
idByUniversalIdentifier: Record<string, string>;
};
@@ -27,6 +27,9 @@ export const addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow = ({
}
return {
idByUniversalIdentifier: {
...flatObjectMetadataMaps.idByUniversalIdentifier,
},
byId: {
...flatObjectMetadataMaps.byId,
[flatFieldMetadata.objectMetadataId]:
@@ -24,6 +24,10 @@ export const addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow = ({
}
return {
idByUniversalIdentifier: {
...flatObjectMetadataMaps.idByUniversalIdentifier,
[flatObjectMetadata.universalIdentifier]: flatObjectMetadata.id,
},
byId: {
...flatObjectMetadataMaps.byId,
[flatObjectMetadata.id]:
@@ -29,6 +29,11 @@ export const addFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThr
}
return {
idByUniversalIdentifier: {
...flatObjectMetadataMaps.idByUniversalIdentifier,
[flatObjectMetadataWithFlatFieldMaps.universalIdentifier]:
flatObjectMetadataWithFlatFieldMaps.id,
},
byId: {
...flatObjectMetadataMaps.byId,
[flatObjectMetadataWithFlatFieldMaps.id]:
@@ -28,6 +28,7 @@ export const deleteFieldFromFlatObjectMetadataMapsOrThrow = ({
}
return {
idByUniversalIdentifier: flatObjectMetadataMaps.idByUniversalIdentifier,
byId: {
...flatObjectMetadataMaps.byId,
[objectMetadataId]:
@@ -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,
]),
@@ -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,
};
@@ -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<FlatObjectMetadataMaps> {
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<void> {
// Temporarily invalidating old cache too
await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);
///
}
override async afterInvalidateCache({
workspaceId,
}: {
workspaceId: string;
}): Promise<void> {
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
workspaceId,
});
}
protected async computeFlatMap({
workspaceId,
}: {
workspaceId: string;
}): Promise<FlatObjectMetadataMaps> {
return (
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
{ workspaceId },
)
).flatObjectMetadataMaps;
}
}
@@ -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<ObjectMetadataDTO> {
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<ObjectMetadataDTO> {
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatObjectMetadataMaps'],
},
);
@@ -225,9 +229,10 @@ export class ObjectMetadataServiceV2 {
workspaceId: string;
}): Promise<FlatObjectMetadata> {
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'],
},
);
@@ -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,
@@ -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);
@@ -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<FlatEntityMaps<AllFlatEntities>>
>();
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<FlatEntityMaps<AllFlatEntities>> | undefined {
return this.cacheServiceMap.get(flatEntityName);
}
}
@@ -99,6 +99,7 @@ export abstract class WorkspaceFlatMapCacheService<
}: {
workspaceId: string;
}): Promise<void> {
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<void> {
return;
}
protected async afterInvalidateCache(_args: {
workspaceId: string;
}): Promise<void> {
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,
@@ -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 {}
@@ -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,
@@ -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: [
@@ -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';
@@ -165,9 +165,14 @@ export class WorkspaceMigrationBuildOrchestratorService {
};
}
const relatedFlatEntityMapsKeys = Object.keys(
fromToAllFlatEntityMaps,
) as (keyof AllFlatEntityMaps)[];
return {
status: 'success',
workspaceMigration: {
relatedFlatEntityMapsKeys,
actions: allActions,
workspaceId,
},
@@ -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';
@@ -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'
>;
@@ -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,
@@ -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)[];
};
@@ -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;
@@ -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<CreateFieldAction>): Partial<AllFlatEntityMaps> {
const { flatObjectMetadataMaps } = allFlatEntityMaps;
const updatedFlatObjectMetadataMaps =
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata: action.flatFieldMetadata,
flatObjectMetadataMaps,
});
return {
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>,
): Promise<void> {
@@ -42,8 +63,12 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
async executeForWorkspaceSchema(
context: WorkspaceMigrationActionRunnerArgs<CreateFieldAction>,
): Promise<void> {
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
context;
const {
action,
queryRunner,
allFlatEntityMaps: { flatObjectMetadataMaps },
workspaceId,
} = context;
const { flatFieldMetadata } = action;
const flatObjectMetadataWithFlatFieldMaps =
@@ -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<DeleteFieldAction>): Partial<AllFlatEntityMaps> {
const { flatObjectMetadataMaps } = allFlatEntityMaps;
const { fieldMetadataId, objectMetadataId } = action;
const updatedFlatObjectMetadataMaps =
deleteFieldFromFlatObjectMetadataMapsOrThrow({
fieldMetadataId,
flatObjectMetadataMaps,
objectMetadataId,
});
return {
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>,
) {
@@ -48,8 +72,12 @@ export class DeleteFieldActionHandlerService extends WorkspaceMigrationRunnerAct
async executeForWorkspaceSchema(
context: WorkspaceMigrationActionRunnerArgs<DeleteFieldAction>,
) {
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
context;
const {
action,
queryRunner,
allFlatEntityMaps: { flatObjectMetadataMaps },
workspaceId,
} = context;
const { objectMetadataId, fieldMetadataId } = action;
const flatObjectMetadataWithFlatFieldMaps =
@@ -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<UpdateFieldAction>): Partial<AllFlatEntityMaps> {
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<UpdateFieldAction>,
): Promise<void> {
@@ -81,8 +124,12 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
async executeForWorkspaceSchema(
context: WorkspaceMigrationActionRunnerArgs<UpdateFieldAction>,
): Promise<void> {
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
context;
const {
action,
queryRunner,
allFlatEntityMaps: { flatObjectMetadataMaps },
workspaceId,
} = context;
const { objectMetadataId, fieldMetadataId, updates } = action;
const flatObjectMetadataWithFlatFieldMaps =
@@ -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<CreateIndexAction>,
): Partial<AllFlatEntityMaps> {
return {};
}
async executeForMetadata(
_context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
): Promise<void> {
@@ -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<DeleteIndexAction>,
): Partial<AllFlatEntityMaps> {
return {};
}
async executeForMetadata(
_context: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
): Promise<void> {
@@ -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<CreateObjectAction>): Partial<AllFlatEntityMaps> {
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<CreateObjectAction>,
): Promise<void> {
@@ -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<DeleteObjectAction>): Partial<AllFlatEntityMaps> {
const { flatObjectMetadataMaps } = allFlatEntityMaps;
const { objectMetadataId } = action;
const updatedFlatObjectMetadataMaps =
deleteObjectFromFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId,
});
return {
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>,
): Promise<void> {
@@ -44,8 +67,12 @@ export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerAc
async executeForWorkspaceSchema(
context: WorkspaceMigrationActionRunnerArgs<DeleteObjectAction>,
): Promise<void> {
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
context;
const {
action,
queryRunner,
allFlatEntityMaps: { flatObjectMetadataMaps },
workspaceId,
} = context;
const { objectMetadataId } = action;
const flatObjectMetadataWithFlatFieldMaps =
@@ -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<UpdateObjectAction>): Partial<AllFlatEntityMaps> {
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<UpdateObjectAction>,
): Promise<void> {
@@ -48,8 +83,12 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
async executeForWorkspaceSchema(
context: WorkspaceMigrationActionRunnerArgs<UpdateObjectAction>,
): Promise<void> {
const { action, queryRunner, flatObjectMetadataMaps, workspaceId } =
context;
const {
action,
queryRunner,
allFlatEntityMaps: { flatObjectMetadataMaps },
workspaceId,
} = context;
const { objectMetadataId, updates } = action;
const flatObjectMetadataWithFlatFieldMaps =
@@ -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<CreateViewFieldAction>): Partial<AllFlatEntityMaps> {
const { flatViewFieldMaps } = allFlatEntityMaps;
const { viewField } = action;
const updatedFlatViewFieldMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: viewField,
flatEntityMaps: flatViewFieldMaps,
});
return {
flatViewFieldMaps: updatedFlatViewFieldMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateViewFieldAction>,
): Promise<void> {
@@ -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<DeleteViewFieldAction>): Partial<AllFlatEntityMaps> {
const { flatViewFieldMaps } = allFlatEntityMaps;
const { viewFieldId } = action;
const updatedFlatViewFieldMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: viewFieldId,
flatEntityMaps: flatViewFieldMaps,
});
return {
flatViewFieldMaps: updatedFlatViewFieldMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteViewFieldAction>,
): Promise<void> {
@@ -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<UpdateViewFieldAction>): Partial<AllFlatEntityMaps> {
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<UpdateViewFieldAction>,
): Promise<void> {
@@ -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<CreateViewAction>): Partial<AllFlatEntityMaps> {
const { flatViewMaps } = allFlatEntityMaps;
const { view } = action;
const updatedFlatViewMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: view,
flatEntityMaps: flatViewMaps,
});
return {
flatViewMaps: updatedFlatViewMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateViewAction>,
): Promise<void> {
@@ -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<DeleteViewAction>): Partial<AllFlatEntityMaps> {
const { flatViewMaps } = allFlatEntityMaps;
const { viewId } = action;
const updatedFlatViewMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: viewId,
flatEntityMaps: flatViewMaps,
});
return {
flatViewMaps: updatedFlatViewMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteViewAction>,
): Promise<void> {
@@ -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<UpdateViewAction>): Partial<AllFlatEntityMaps> {
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<UpdateViewAction>,
): Promise<void> {
@@ -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);
@@ -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<ExtractAction<T>>,
): Promise<void>;
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
): Promise<Partial<AllFlatEntityMaps>>;
}
export type OptimisticallyApplyActionOnAllFlatEntityMapsArgs<
TActionType extends WorkspaceMigrationActionV2,
> = Pick<
WorkspaceMigrationActionRunnerArgs<TActionType>,
'allFlatEntityMaps' | 'action'
>;
export abstract class BaseWorkspaceMigrationRunnerActionHandlerService<
T extends WorkspaceMigrationActionTypeV2,
> implements WorkspaceMigrationRunnerActionHandlerService<T>
TActionType extends WorkspaceMigrationActionTypeV2,
> implements WorkspaceMigrationRunnerActionHandlerService<TActionType>
{
abstract executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
): Promise<void>;
abstract executeForWorkspaceSchema(
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
): Promise<void>;
abstract optimisticallyApplyActionOnAllFlatEntityMaps(
args: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<
ExtractAction<TActionType>
>,
): Partial<AllFlatEntityMaps>;
async execute(
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<T>>,
): Promise<void> {
context: WorkspaceMigrationActionRunnerArgs<ExtractAction<TActionType>>,
): Promise<Partial<AllFlatEntityMaps>> {
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<T> {
abstract class ActionHandlerService extends BaseWorkspaceMigrationRunnerActionHandlerService<T> {}
TActionType extends WorkspaceMigrationActionTypeV2,
>(
actionType: TActionType,
): typeof BaseWorkspaceMigrationRunnerActionHandlerService<TActionType> {
abstract class ActionHandlerService extends BaseWorkspaceMigrationRunnerActionHandlerService<TActionType> {}
SetMetadata(
WORKSPACE_MIGRATION_ACTION_HANDLER_METADATA_KEY,
@@ -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<T extends WorkspaceMigrationActionV2>(
actionType: WorkspaceMigrationActionTypeV2,
context: WorkspaceMigrationActionRunnerArgs<T>,
): Promise<void> {
): Promise<Partial<AllFlatEntityMaps>> {
const handler = this.actionHandlers.get(actionType);
if (!handler) {
@@ -64,6 +65,6 @@ export class WorkspaceMigrationRunnerActionHandlerRegistryService
);
}
await handler.execute(context);
return await handler.execute(context);
}
}
@@ -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<FlatObjectMetadataMaps> => {
run = async ({
actions,
workspaceId,
relatedFlatEntityMapsKeys,
}: WorkspaceMigrationV2): Promise<AllFlatEntityMaps> => {
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();
}
@@ -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;
};
@@ -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<T>,
'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',
);
}
}
};
@@ -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: [
@@ -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,
@@ -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,
@@ -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<ViewFieldTestSetup> => {
`,
});
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,
};