Front Extensibility: Introduce Front Component Entity (#17175)
As part of the extensibility effort, we are introducing a new engine entity called "Front Component". This represents a dynamic react component that will be rendered in CommandMenu actions or in PageLayout widgets This PR introduce the entity and all the necessary boilerplate to make it syncable and cachable in the engine
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
|
||||
export const FLAT_FRONT_COMPONENT_EDITABLE_PROPERTIES = [
|
||||
'name',
|
||||
] as const satisfies (keyof FlatFrontComponent)[];
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { WorkspaceFlatFrontComponentMapCacheService } from 'src/engine/metadata-modules/flat-front-component/services/workspace-flat-front-component-map-cache.service';
|
||||
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([FrontComponentEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [WorkspaceFlatFrontComponentMapCacheService],
|
||||
exports: [WorkspaceFlatFrontComponentMapCacheService],
|
||||
})
|
||||
export class FlatFrontComponentModule {}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
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 { type FlatFrontComponentMaps } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component-maps.type';
|
||||
import { fromFrontComponentEntityToFlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/utils/from-front-component-entity-to-flat-front-component.util';
|
||||
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceCache('flatFrontComponentMaps')
|
||||
export class WorkspaceFlatFrontComponentMapCacheService extends WorkspaceCacheProvider<FlatFrontComponentMaps> {
|
||||
constructor(
|
||||
@InjectRepository(FrontComponentEntity)
|
||||
private readonly frontComponentRepository: Repository<FrontComponentEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<FlatFrontComponentMaps> {
|
||||
const frontComponents = await this.frontComponentRepository.find({
|
||||
where: { workspaceId },
|
||||
});
|
||||
|
||||
const flatFrontComponentMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const frontComponentEntity of frontComponents) {
|
||||
const flatFrontComponent =
|
||||
fromFrontComponentEntityToFlatFrontComponent(frontComponentEntity);
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: flatFrontComponent,
|
||||
flatEntityMapsToMutate: flatFrontComponentMaps,
|
||||
});
|
||||
}
|
||||
|
||||
return flatFrontComponentMaps;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
|
||||
export type FlatFrontComponentMaps = FlatEntityMaps<FlatFrontComponent>;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
|
||||
import { type FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
|
||||
export type FlatFrontComponent = FlatEntityFrom<FrontComponentEntity>;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
import { type CreateFrontComponentInput } from 'src/engine/metadata-modules/front-component/dtos/create-front-component.input';
|
||||
|
||||
export const fromCreateFrontComponentInputToFlatFrontComponentToCreate = ({
|
||||
createFrontComponentInput,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
}: {
|
||||
createFrontComponentInput: CreateFrontComponentInput;
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
}): FlatFrontComponent => {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const { name } = trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
createFrontComponentInput,
|
||||
['name'],
|
||||
);
|
||||
|
||||
const id = createFrontComponentInput.id ?? v4();
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
workspaceId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
universalIdentifier: id,
|
||||
applicationId,
|
||||
};
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
import {
|
||||
FrontComponentException,
|
||||
FrontComponentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/front-component/front-component.exception';
|
||||
|
||||
export const fromDeleteFrontComponentInputToFlatFrontComponentOrThrow = ({
|
||||
flatFrontComponentMaps,
|
||||
frontComponentId,
|
||||
}: {
|
||||
flatFrontComponentMaps: FlatEntityMaps<FlatFrontComponent>;
|
||||
frontComponentId: string;
|
||||
}): FlatFrontComponent => {
|
||||
const existingFlatFrontComponent = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: frontComponentId,
|
||||
flatEntityMaps: flatFrontComponentMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingFlatFrontComponent)) {
|
||||
throw new FrontComponentException(
|
||||
'Front component not found',
|
||||
FrontComponentExceptionCode.FRONT_COMPONENT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return existingFlatFrontComponent;
|
||||
};
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
import { type FrontComponentDTO } from 'src/engine/metadata-modules/front-component/dtos/front-component.dto';
|
||||
|
||||
export const fromFlatFrontComponentToFrontComponentDto = (
|
||||
flatFrontComponent: FlatFrontComponent,
|
||||
): FrontComponentDTO => ({
|
||||
id: flatFrontComponent.id,
|
||||
name: flatFrontComponent.name,
|
||||
workspaceId: flatFrontComponent.workspaceId,
|
||||
applicationId: flatFrontComponent.applicationId,
|
||||
createdAt: new Date(flatFrontComponent.createdAt),
|
||||
updatedAt: new Date(flatFrontComponent.updatedAt),
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
import { type FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
|
||||
export const fromFrontComponentEntityToFlatFrontComponent = (
|
||||
frontComponentEntity: FrontComponentEntity,
|
||||
): FlatFrontComponent => {
|
||||
return {
|
||||
id: frontComponentEntity.id,
|
||||
name: frontComponentEntity.name,
|
||||
workspaceId: frontComponentEntity.workspaceId,
|
||||
universalIdentifier: frontComponentEntity.universalIdentifier,
|
||||
applicationId: frontComponentEntity.applicationId,
|
||||
createdAt: frontComponentEntity.createdAt.toISOString(),
|
||||
updatedAt: frontComponentEntity.updatedAt.toISOString(),
|
||||
};
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { FLAT_FRONT_COMPONENT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-front-component/constants/flat-front-component-editable-properties.constant';
|
||||
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
|
||||
import { type UpdateFrontComponentInput } from 'src/engine/metadata-modules/front-component/dtos/update-front-component.input';
|
||||
import {
|
||||
FrontComponentException,
|
||||
FrontComponentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/front-component/front-component.exception';
|
||||
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
|
||||
|
||||
export const fromUpdateFrontComponentInputToFlatFrontComponentToUpdateOrThrow =
|
||||
({
|
||||
flatFrontComponentMaps,
|
||||
updateFrontComponentInput,
|
||||
}: {
|
||||
flatFrontComponentMaps: FlatEntityMaps<FlatFrontComponent>;
|
||||
updateFrontComponentInput: UpdateFrontComponentInput;
|
||||
}): FlatFrontComponent => {
|
||||
const existingFlatFrontComponent = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: updateFrontComponentInput.id,
|
||||
flatEntityMaps: flatFrontComponentMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingFlatFrontComponent)) {
|
||||
throw new FrontComponentException(
|
||||
'Front component not found',
|
||||
FrontComponentExceptionCode.FRONT_COMPONENT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...mergeUpdateInExistingRecord({
|
||||
existing: existingFlatFrontComponent,
|
||||
properties: [...FLAT_FRONT_COMPONENT_EDITABLE_PROPERTIES],
|
||||
update: updateFrontComponentInput.update,
|
||||
}),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user