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:
+7
@@ -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)[];
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+11
@@ -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 {}
|
||||
+105
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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;
|
||||
};
|
||||
+3
-9
@@ -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];
|
||||
|
||||
-13
@@ -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 {}
|
||||
-83
@@ -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',
|
||||
|
||||
+12
-5
@@ -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)[];
|
||||
|
||||
+8
@@ -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)[];
|
||||
+2
-2
@@ -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)[];
|
||||
|
||||
+2
-9
@@ -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)[];
|
||||
|
||||
+55
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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: {
|
||||
|
||||
+48
@@ -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: [],
|
||||
};
|
||||
};
|
||||
+40
@@ -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(),
|
||||
};
|
||||
};
|
||||
+37
@@ -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;
|
||||
};
|
||||
+3
-3
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
+50
@@ -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);
|
||||
|
||||
+56
-41
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user