Migrate page layout widget to v2 of the API (#16323)

This commit is contained in:
Raphaël Bosi
2025-12-05 14:04:06 +01:00
committed by GitHub
parent 56e66315cd
commit 077be7644c
159 changed files with 1760 additions and 308 deletions
@@ -0,0 +1,9 @@
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
export const FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES = [
'title',
'type',
'objectMetadataId',
'gridPosition',
'configuration',
] as const satisfies (keyof FlatPageLayoutWidget)[];
@@ -0,0 +1,8 @@
import { type PageLayoutWidgetEntityRelationProperties } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
export const PAGE_LAYOUT_WIDGET_ENTITY_RELATION_PROPERTIES = [
'workspace',
'pageLayoutTab',
'objectMetadata',
'application',
] as const satisfies PageLayoutWidgetEntityRelationProperties[];
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceFlatPageLayoutWidgetMapCacheService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/workspace-flat-page-layout-widget-map-cache.service';
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-widget.entity';
@Module({
imports: [TypeOrmModule.forFeature([PageLayoutWidgetEntity])],
providers: [WorkspaceFlatPageLayoutWidgetMapCacheService],
exports: [WorkspaceFlatPageLayoutWidgetMapCacheService],
})
export class FlatPageLayoutWidgetModule {}
@@ -0,0 +1,52 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { fromPageLayoutWidgetEntityToFlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util';
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-widget.entity';
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
@Injectable()
@WorkspaceCache('flatPageLayoutWidgetMaps')
export class WorkspaceFlatPageLayoutWidgetMapCacheService extends WorkspaceCacheProvider<FlatPageLayoutWidgetMaps> {
constructor(
@InjectRepository(PageLayoutWidgetEntity)
private readonly pageLayoutWidgetRepository: Repository<PageLayoutWidgetEntity>,
) {
super();
}
async computeForCache(
workspaceId: string,
): Promise<FlatPageLayoutWidgetMaps> {
const existingPageLayoutWidgets =
await this.pageLayoutWidgetRepository.find({
where: {
workspaceId,
},
withDeleted: true,
});
const flatPageLayoutWidgetMaps = createEmptyFlatEntityMaps();
for (const pageLayoutWidgetEntity of existingPageLayoutWidgets) {
const flatPageLayoutWidget =
fromPageLayoutWidgetEntityToFlatPageLayoutWidget(
pageLayoutWidgetEntity,
);
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
flatEntity: flatPageLayoutWidget,
flatEntityMapsToMutate: flatPageLayoutWidgetMaps,
});
}
return flatPageLayoutWidgetMaps;
}
}
@@ -0,0 +1,4 @@
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
export type FlatPageLayoutWidgetMaps = FlatEntityMaps<FlatPageLayoutWidget>;
@@ -0,0 +1,21 @@
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-tab.entity';
import { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-widget.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
export type PageLayoutWidgetEntityRelationProperties =
ExtractRecordTypeOrmRelationProperties<
PageLayoutWidgetEntity,
| PageLayoutTabEntity
| ObjectMetadataEntity
| WorkspaceEntity
| ApplicationEntity
>;
export type FlatPageLayoutWidget = FlatEntityFrom<
PageLayoutWidgetEntity,
PageLayoutWidgetEntityRelationProperties
>;
@@ -0,0 +1,44 @@
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout-widget.input';
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
export type FromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreateArgs =
{
createPageLayoutWidgetInput: CreatePageLayoutWidgetInput;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
createPageLayoutWidgetInput: rawCreatePageLayoutWidgetInput,
workspaceId,
workspaceCustomApplicationId,
}: FromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreateArgs): FlatPageLayoutWidget => {
const { pageLayoutTabId, ...createPageLayoutWidgetInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawCreatePageLayoutWidgetInput,
['pageLayoutTabId'],
);
const createdAt = new Date();
const pageLayoutWidgetId = v4();
return {
id: pageLayoutWidgetId,
pageLayoutTabId,
workspaceId,
createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: pageLayoutWidgetId,
title: createPageLayoutWidgetInput.title,
type: createPageLayoutWidgetInput.type ?? WidgetType.VIEW,
objectMetadataId: createPageLayoutWidgetInput.objectMetadataId ?? null,
gridPosition: createPageLayoutWidgetInput.gridPosition,
configuration: createPageLayoutWidgetInput.configuration ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -0,0 +1,41 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type DeletePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/delete-page-layout-widget.input';
import {
PageLayoutWidgetException,
PageLayoutWidgetExceptionCode,
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
export const fromDeletePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow = ({
deletePageLayoutWidgetInput: rawDeletePageLayoutWidgetInput,
flatPageLayoutWidgetMaps,
}: {
deletePageLayoutWidgetInput: DeletePageLayoutWidgetInput;
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
}): FlatPageLayoutWidget => {
const { id: pageLayoutWidgetId } = extractAndSanitizeObjectStringFields(
rawDeletePageLayoutWidgetInput,
['id'],
);
const existingFlatPageLayoutWidgetToDelete =
flatPageLayoutWidgetMaps.byId[pageLayoutWidgetId];
if (!isDefined(existingFlatPageLayoutWidgetToDelete)) {
throw new PageLayoutWidgetException(
t`Page layout widget to delete not found`,
PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND,
);
}
return {
...existingFlatPageLayoutWidgetToDelete,
deletedAt: new Date(),
};
};
@@ -0,0 +1,38 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type DestroyPageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/destroy-page-layout-widget.input';
import {
PageLayoutWidgetException,
PageLayoutWidgetExceptionCode,
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
export const fromDestroyPageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow = ({
destroyPageLayoutWidgetInput,
flatPageLayoutWidgetMaps,
}: {
destroyPageLayoutWidgetInput: DestroyPageLayoutWidgetInput;
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
}): FlatPageLayoutWidget => {
const { id: pageLayoutWidgetId } = extractAndSanitizeObjectStringFields(
destroyPageLayoutWidgetInput,
['id'],
);
const existingFlatPageLayoutWidgetToDestroy =
flatPageLayoutWidgetMaps.byId[pageLayoutWidgetId];
if (!isDefined(existingFlatPageLayoutWidgetToDestroy)) {
throw new PageLayoutWidgetException(
t`Page layout widget to destroy not found`,
PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND,
);
}
return existingFlatPageLayoutWidgetToDestroy;
};
@@ -0,0 +1,22 @@
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { PAGE_LAYOUT_WIDGET_ENTITY_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-widget/constants/page-layout-widget-entity-relation-properties.constant';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-widget.entity';
export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = (
pageLayoutWidgetEntity: PageLayoutWidgetEntity,
): FlatPageLayoutWidget => {
const pageLayoutWidgetEntityWithoutRelations = removePropertiesFromRecord(
pageLayoutWidgetEntity,
PAGE_LAYOUT_WIDGET_ENTITY_RELATION_PROPERTIES,
);
return {
...pageLayoutWidgetEntityWithoutRelations,
universalIdentifier:
pageLayoutWidgetEntityWithoutRelations.universalIdentifier ??
pageLayoutWidgetEntityWithoutRelations.id,
applicationId: pageLayoutWidgetEntityWithoutRelations.applicationId ?? null,
};
};
@@ -0,0 +1,55 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-widget/constants/flat-page-layout-widget-editable-properties.constant';
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type UpdatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-widget.input';
import {
PageLayoutWidgetException,
PageLayoutWidgetExceptionCode,
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
export type UpdatePageLayoutWidgetInputWithId = {
id: string;
update: UpdatePageLayoutWidgetInput;
};
export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThrow =
({
updatePageLayoutWidgetInput: rawUpdatePageLayoutWidgetInput,
flatPageLayoutWidgetMaps,
}: {
updatePageLayoutWidgetInput: UpdatePageLayoutWidgetInputWithId;
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
}): FlatPageLayoutWidget => {
const { id: pageLayoutWidgetToUpdateId } =
extractAndSanitizeObjectStringFields(rawUpdatePageLayoutWidgetInput, [
'id',
]);
const existingFlatPageLayoutWidgetToUpdate =
flatPageLayoutWidgetMaps.byId[pageLayoutWidgetToUpdateId];
if (!isDefined(existingFlatPageLayoutWidgetToUpdate)) {
throw new PageLayoutWidgetException(
t`Page layout widget to update not found`,
PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND,
);
}
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
rawUpdatePageLayoutWidgetInput.update,
FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES,
);
return mergeUpdateInExistingRecord({
existing: existingFlatPageLayoutWidgetToUpdate,
properties: FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES,
update: updatedEditableFieldProperties,
});
};