Pre hook for dashboard creation + updates on the entity (#14354)
Closes https://github.com/twentyhq/core-team-issues/issues/1417 - The pre-hook creates a page-layout and links it to the dashboard - Added `position`, `createdBy`, `attachments`, `searchVector`, `favorites` to the dashboard entity - Updated the view seed - Updated the page layout services to work within a transaction
This commit is contained in:
+1
-1
@@ -29,5 +29,5 @@ export class CreatePageLayoutInput {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
objectMetadataId?: string;
|
||||
objectMetadataId?: string | null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,5 +20,5 @@ export class UpdatePageLayoutInput {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
objectMetadataId?: string;
|
||||
objectMetadataId?: string | null;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { PageLayoutResolver } from 'src/engine/core-modules/page-layout/resolver
|
||||
import { PageLayoutTabService } from 'src/engine/core-modules/page-layout/services/page-layout-tab.service';
|
||||
import { PageLayoutWidgetService } from 'src/engine/core-modules/page-layout/services/page-layout-widget.service';
|
||||
import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/page-layout.service';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -21,6 +22,7 @@ import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/
|
||||
PageLayoutTabEntity,
|
||||
PageLayoutWidgetEntity,
|
||||
]),
|
||||
TwentyORMModule,
|
||||
],
|
||||
controllers: [
|
||||
PageLayoutController,
|
||||
|
||||
+72
-19
@@ -2,9 +2,10 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { EntityManager, IsNull, Repository } from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { CreatePageLayoutTabInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout-tab.input';
|
||||
import { PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity';
|
||||
import {
|
||||
PageLayoutTabException,
|
||||
@@ -26,11 +27,22 @@ export class PageLayoutTabService {
|
||||
private readonly pageLayoutService: PageLayoutService,
|
||||
) {}
|
||||
|
||||
private getPageLayoutTabRepository(
|
||||
transactionManager?: EntityManager,
|
||||
): Repository<PageLayoutTabEntity> {
|
||||
return transactionManager
|
||||
? transactionManager.getRepository(PageLayoutTabEntity)
|
||||
: this.pageLayoutTabRepository;
|
||||
}
|
||||
|
||||
async findByPageLayoutId(
|
||||
workspaceId: string,
|
||||
pageLayoutId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutTabEntity[]> {
|
||||
return this.pageLayoutTabRepository.find({
|
||||
const repository = this.getPageLayoutTabRepository(transactionManager);
|
||||
|
||||
return repository.find({
|
||||
where: {
|
||||
pageLayoutId,
|
||||
pageLayout: { workspaceId },
|
||||
@@ -44,8 +56,11 @@ export class PageLayoutTabService {
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutTabEntity> {
|
||||
const pageLayoutTab = await this.pageLayoutTabRepository.findOne({
|
||||
const repository = this.getPageLayoutTabRepository(transactionManager);
|
||||
|
||||
const pageLayoutTab = await repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
@@ -68,8 +83,9 @@ export class PageLayoutTabService {
|
||||
}
|
||||
|
||||
async create(
|
||||
pageLayoutTabData: Partial<PageLayoutTabEntity>,
|
||||
pageLayoutTabData: CreatePageLayoutTabInput,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutTabEntity> {
|
||||
if (!isDefined(pageLayoutTabData.title)) {
|
||||
throw new PageLayoutTabException(
|
||||
@@ -93,14 +109,21 @@ export class PageLayoutTabService {
|
||||
await this.pageLayoutService.findByIdOrThrow(
|
||||
pageLayoutTabData.pageLayoutId,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
const pageLayoutTab = this.pageLayoutTabRepository.create({
|
||||
const repository = this.getPageLayoutTabRepository(transactionManager);
|
||||
|
||||
const insertResult = await repository.insert({
|
||||
...pageLayoutTabData,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return this.pageLayoutTabRepository.save(pageLayoutTab);
|
||||
return this.findByIdOrThrow(
|
||||
insertResult.identifiers[0].id,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof PageLayoutException &&
|
||||
@@ -121,8 +144,11 @@ export class PageLayoutTabService {
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: QueryDeepPartialEntity<PageLayoutTabEntity>,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutTabEntity> {
|
||||
const existingTab = await this.pageLayoutTabRepository.findOne({
|
||||
const repository = this.getPageLayoutTabRepository(transactionManager);
|
||||
|
||||
const existingTab = await repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
@@ -140,21 +166,37 @@ export class PageLayoutTabService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutTabRepository.update({ id }, updateData);
|
||||
await repository.update({ id }, updateData);
|
||||
|
||||
return this.findByIdOrThrow(id, workspaceId);
|
||||
return this.findByIdOrThrow(id, workspaceId, transactionManager);
|
||||
}
|
||||
|
||||
async delete(id: string, workspaceId: string): Promise<PageLayoutTabEntity> {
|
||||
const pageLayoutTab = await this.findByIdOrThrow(id, workspaceId);
|
||||
async delete(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutTabEntity> {
|
||||
const pageLayoutTab = await this.findByIdOrThrow(
|
||||
id,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await this.pageLayoutTabRepository.softDelete(id);
|
||||
const repository = this.getPageLayoutTabRepository(transactionManager);
|
||||
|
||||
await repository.softDelete(id);
|
||||
|
||||
return pageLayoutTab;
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
const pageLayoutTab = await this.pageLayoutTabRepository.findOne({
|
||||
async destroy(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<boolean> {
|
||||
const repository = this.getPageLayoutTabRepository(transactionManager);
|
||||
|
||||
const pageLayoutTab = await repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
@@ -172,13 +214,19 @@ export class PageLayoutTabService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutTabRepository.delete(id);
|
||||
await repository.delete(id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async restore(id: string, workspaceId: string): Promise<PageLayoutTabEntity> {
|
||||
const pageLayoutTab = await this.pageLayoutTabRepository.findOne({
|
||||
async restore(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutTabEntity> {
|
||||
const repository = this.getPageLayoutTabRepository(transactionManager);
|
||||
|
||||
const pageLayoutTab = await repository.findOne({
|
||||
select: {
|
||||
id: true,
|
||||
deletedAt: true,
|
||||
@@ -214,6 +262,7 @@ export class PageLayoutTabService {
|
||||
await this.pageLayoutService.findByIdOrThrow(
|
||||
pageLayoutTab.pageLayoutId,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
} catch (error) {
|
||||
if (
|
||||
@@ -230,9 +279,13 @@ export class PageLayoutTabService {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this.pageLayoutTabRepository.restore(id);
|
||||
await repository.restore(id);
|
||||
|
||||
const restoredPageLayoutTab = await this.findByIdOrThrow(id, workspaceId);
|
||||
const restoredPageLayoutTab = await this.findByIdOrThrow(
|
||||
id,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return restoredPageLayoutTab;
|
||||
}
|
||||
|
||||
+61
-17
@@ -2,9 +2,10 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { EntityManager, IsNull, Repository } from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { CreatePageLayoutWidgetInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout-widget.input';
|
||||
import { UpdatePageLayoutWidgetInput } from 'src/engine/core-modules/page-layout/dtos/inputs/update-page-layout-widget.input';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-widget.entity';
|
||||
import {
|
||||
@@ -27,11 +28,22 @@ export class PageLayoutWidgetService {
|
||||
private readonly pageLayoutTabService: PageLayoutTabService,
|
||||
) {}
|
||||
|
||||
private getPageLayoutWidgetRepository(
|
||||
transactionManager?: EntityManager,
|
||||
): Repository<PageLayoutWidgetEntity> {
|
||||
return transactionManager
|
||||
? transactionManager.getRepository(PageLayoutWidgetEntity)
|
||||
: this.pageLayoutWidgetRepository;
|
||||
}
|
||||
|
||||
async findByPageLayoutTabId(
|
||||
workspaceId: string,
|
||||
pageLayoutTabId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutWidgetEntity[]> {
|
||||
return this.pageLayoutWidgetRepository.find({
|
||||
const repository = this.getPageLayoutWidgetRepository(transactionManager);
|
||||
|
||||
return repository.find({
|
||||
where: {
|
||||
pageLayoutTabId,
|
||||
workspaceId,
|
||||
@@ -44,8 +56,11 @@ export class PageLayoutWidgetService {
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutWidgetEntity> {
|
||||
const pageLayoutWidget = await this.pageLayoutWidgetRepository.findOne({
|
||||
const repository = this.getPageLayoutWidgetRepository(transactionManager);
|
||||
|
||||
const pageLayoutWidget = await repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
@@ -67,8 +82,9 @@ export class PageLayoutWidgetService {
|
||||
}
|
||||
|
||||
async create(
|
||||
pageLayoutWidgetData: Partial<PageLayoutWidgetEntity>,
|
||||
pageLayoutWidgetData: CreatePageLayoutWidgetInput,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutWidgetEntity> {
|
||||
if (!isDefined(pageLayoutWidgetData.title)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
@@ -101,14 +117,21 @@ export class PageLayoutWidgetService {
|
||||
await this.pageLayoutTabService.findByIdOrThrow(
|
||||
pageLayoutWidgetData.pageLayoutTabId,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
const pageLayoutWidget = this.pageLayoutWidgetRepository.create({
|
||||
const repository = this.getPageLayoutWidgetRepository(transactionManager);
|
||||
|
||||
const insertResult = await repository.insert({
|
||||
...pageLayoutWidgetData,
|
||||
workspaceId,
|
||||
});
|
||||
} as QueryDeepPartialEntity<PageLayoutWidgetEntity>);
|
||||
|
||||
return this.pageLayoutWidgetRepository.save(pageLayoutWidget);
|
||||
return this.findByIdOrThrow(
|
||||
insertResult.identifiers[0].id,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof PageLayoutTabException &&
|
||||
@@ -129,8 +152,11 @@ export class PageLayoutWidgetService {
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: UpdatePageLayoutWidgetInput,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutWidgetEntity> {
|
||||
const existingWidget = await this.pageLayoutWidgetRepository.findOne({
|
||||
const repository = this.getPageLayoutWidgetRepository(transactionManager);
|
||||
|
||||
const existingWidget = await repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
@@ -148,27 +174,40 @@ export class PageLayoutWidgetService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutWidgetRepository.update(
|
||||
await repository.update(
|
||||
{ id },
|
||||
updateData as QueryDeepPartialEntity<PageLayoutWidgetEntity>,
|
||||
);
|
||||
|
||||
return this.findByIdOrThrow(id, workspaceId);
|
||||
return this.findByIdOrThrow(id, workspaceId, transactionManager);
|
||||
}
|
||||
|
||||
async delete(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutWidgetEntity> {
|
||||
const pageLayoutWidget = await this.findByIdOrThrow(id, workspaceId);
|
||||
const pageLayoutWidget = await this.findByIdOrThrow(
|
||||
id,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await this.pageLayoutWidgetRepository.softDelete(id);
|
||||
const repository = this.getPageLayoutWidgetRepository(transactionManager);
|
||||
|
||||
await repository.softDelete(id);
|
||||
|
||||
return pageLayoutWidget;
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
const pageLayoutWidget = await this.pageLayoutWidgetRepository.findOne({
|
||||
async destroy(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<boolean> {
|
||||
const repository = this.getPageLayoutWidgetRepository(transactionManager);
|
||||
|
||||
const pageLayoutWidget = await repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
@@ -186,7 +225,7 @@ export class PageLayoutWidgetService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutWidgetRepository.delete(id);
|
||||
await repository.delete(id);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -194,8 +233,11 @@ export class PageLayoutWidgetService {
|
||||
async restore(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutWidgetEntity> {
|
||||
const pageLayoutWidget = await this.pageLayoutWidgetRepository.findOne({
|
||||
const repository = this.getPageLayoutWidgetRepository(transactionManager);
|
||||
|
||||
const pageLayoutWidget = await repository.findOne({
|
||||
select: {
|
||||
id: true,
|
||||
deletedAt: true,
|
||||
@@ -231,6 +273,7 @@ export class PageLayoutWidgetService {
|
||||
await this.pageLayoutTabService.findByIdOrThrow(
|
||||
pageLayoutWidget.pageLayoutTabId,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
} catch (error) {
|
||||
if (
|
||||
@@ -247,11 +290,12 @@ export class PageLayoutWidgetService {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this.pageLayoutWidgetRepository.restore(id);
|
||||
await repository.restore(id);
|
||||
|
||||
const restoredPageLayoutWidget = await this.findByIdOrThrow(
|
||||
id,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return restoredPageLayoutWidget;
|
||||
|
||||
+104
-17
@@ -1,27 +1,46 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { EntityManager, IsNull, Repository } from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { CreatePageLayoutInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout.input';
|
||||
import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
PageLayoutException,
|
||||
PageLayoutExceptionCode,
|
||||
PageLayoutExceptionMessageKey,
|
||||
generatePageLayoutExceptionMessage,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Injectable()
|
||||
export class PageLayoutService {
|
||||
private readonly logger = new Logger(PageLayoutService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(PageLayoutEntity)
|
||||
private readonly pageLayoutRepository: Repository<PageLayoutEntity>,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async findByWorkspaceId(workspaceId: string): Promise<PageLayoutEntity[]> {
|
||||
return this.pageLayoutRepository.find({
|
||||
private getPageLayoutRepository(
|
||||
transactionManager?: EntityManager,
|
||||
): Repository<PageLayoutEntity> {
|
||||
return transactionManager
|
||||
? transactionManager.getRepository(PageLayoutEntity)
|
||||
: this.pageLayoutRepository;
|
||||
}
|
||||
|
||||
async findByWorkspaceId(
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutEntity[]> {
|
||||
const repository = this.getPageLayoutRepository(transactionManager);
|
||||
|
||||
return repository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
deletedAt: IsNull(),
|
||||
@@ -33,8 +52,11 @@ export class PageLayoutService {
|
||||
async findByObjectMetadataId(
|
||||
workspaceId: string,
|
||||
objectMetadataId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutEntity[]> {
|
||||
return this.pageLayoutRepository.find({
|
||||
const repository = this.getPageLayoutRepository(transactionManager);
|
||||
|
||||
return repository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
@@ -47,8 +69,11 @@ export class PageLayoutService {
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.pageLayoutRepository.findOne({
|
||||
const repository = this.getPageLayoutRepository(transactionManager);
|
||||
|
||||
const pageLayout = await repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
@@ -71,8 +96,9 @@ export class PageLayoutService {
|
||||
}
|
||||
|
||||
async create(
|
||||
pageLayoutData: Partial<PageLayoutEntity>,
|
||||
pageLayoutData: CreatePageLayoutInput,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutEntity> {
|
||||
if (!isDefined(pageLayoutData.name)) {
|
||||
throw new PageLayoutException(
|
||||
@@ -83,36 +109,65 @@ export class PageLayoutService {
|
||||
);
|
||||
}
|
||||
|
||||
const pageLayout = this.pageLayoutRepository.create({
|
||||
const repository = this.getPageLayoutRepository(transactionManager);
|
||||
|
||||
const insertResult = await repository.insert({
|
||||
...pageLayoutData,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return this.pageLayoutRepository.save(pageLayout);
|
||||
return this.findByIdOrThrow(
|
||||
insertResult.identifiers[0].id,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: QueryDeepPartialEntity<PageLayoutEntity>,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutEntity> {
|
||||
await this.pageLayoutRepository.update({ id, workspaceId }, updateData);
|
||||
const repository = this.getPageLayoutRepository(transactionManager);
|
||||
|
||||
const updatedPageLayout = await this.findByIdOrThrow(id, workspaceId);
|
||||
await repository.update({ id, workspaceId }, updateData);
|
||||
|
||||
const updatedPageLayout = await this.findByIdOrThrow(
|
||||
id,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return updatedPageLayout;
|
||||
}
|
||||
|
||||
async delete(id: string, workspaceId: string): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.findByIdOrThrow(id, workspaceId);
|
||||
async delete(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.findByIdOrThrow(
|
||||
id,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await this.pageLayoutRepository.softDelete(id);
|
||||
const repository = this.getPageLayoutRepository(transactionManager);
|
||||
|
||||
await repository.softDelete(id);
|
||||
|
||||
return pageLayout;
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.pageLayoutRepository.findOne({
|
||||
async destroy(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<PageLayoutEntity> {
|
||||
const repository = this.getPageLayoutRepository(transactionManager);
|
||||
|
||||
const pageLayout = await repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
@@ -130,11 +185,43 @@ export class PageLayoutService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutRepository.delete(id);
|
||||
if (pageLayout.type === PageLayoutType.DASHBOARD) {
|
||||
await this.destroyAssociatedDashboards(id, workspaceId);
|
||||
}
|
||||
|
||||
await repository.delete(id);
|
||||
|
||||
return pageLayout;
|
||||
}
|
||||
|
||||
private async destroyAssociatedDashboards(
|
||||
pageLayoutId: string,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const dashboardRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
workspaceId,
|
||||
'dashboard',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const dashboards = await dashboardRepository.find({
|
||||
where: {
|
||||
pageLayoutId,
|
||||
},
|
||||
});
|
||||
|
||||
for (const dashboard of dashboards) {
|
||||
await dashboardRepository.delete(dashboard.id);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to destroy associated dashboards for page layout ${pageLayoutId}: ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async restore(id: string, workspaceId: string): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.pageLayoutRepository.findOne({
|
||||
select: {
|
||||
|
||||
+20
-21
@@ -51,15 +51,6 @@ describe('PageLayoutTabService', () => {
|
||||
deletedAt: null,
|
||||
} as PageLayoutWidgetEntity;
|
||||
|
||||
const mockPageLayout = {
|
||||
id: 'page-layout-id',
|
||||
workspaceId: 'workspace-id',
|
||||
title: 'Test Layout',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -77,6 +68,7 @@ describe('PageLayoutTabService', () => {
|
||||
softDelete: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
restore: jest.fn(),
|
||||
insert: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -240,20 +232,21 @@ describe('PageLayoutTabService', () => {
|
||||
it('should create a new page layout tab successfully', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const pageLayoutTabData = {
|
||||
id: 'page-layout-tab-id',
|
||||
title: 'New Tab',
|
||||
pageLayoutId: 'page-layout-id',
|
||||
position: 1,
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayout as any);
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'create')
|
||||
.mockReturnValue(mockPageLayoutTab);
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'save')
|
||||
.mockResolvedValue(mockPageLayoutTab);
|
||||
.spyOn(pageLayoutTabService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayoutTab as any);
|
||||
|
||||
jest.spyOn(pageLayoutTabRepository, 'insert').mockResolvedValue({
|
||||
identifiers: [{ id: 'page-layout-tab-id' }],
|
||||
generatedMaps: [],
|
||||
raw: [],
|
||||
});
|
||||
|
||||
const result = await pageLayoutTabService.create(
|
||||
pageLayoutTabData,
|
||||
@@ -263,14 +256,12 @@ describe('PageLayoutTabService', () => {
|
||||
expect(pageLayoutService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
pageLayoutTabData.pageLayoutId,
|
||||
workspaceId,
|
||||
undefined,
|
||||
);
|
||||
expect(pageLayoutTabRepository.create).toHaveBeenCalledWith({
|
||||
expect(pageLayoutTabRepository.insert).toHaveBeenCalledWith({
|
||||
...pageLayoutTabData,
|
||||
workspaceId,
|
||||
});
|
||||
expect(pageLayoutTabRepository.save).toHaveBeenCalledWith(
|
||||
mockPageLayoutTab,
|
||||
);
|
||||
expect(result).toEqual(mockPageLayoutTab);
|
||||
});
|
||||
|
||||
@@ -281,9 +272,11 @@ describe('PageLayoutTabService', () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
// @ts-expect-error - we are testing the exception
|
||||
pageLayoutTabService.create(pageLayoutTabData, workspaceId),
|
||||
).rejects.toThrow(PageLayoutTabException);
|
||||
await expect(
|
||||
// @ts-expect-error - we are testing the exception
|
||||
pageLayoutTabService.create(pageLayoutTabData, workspaceId),
|
||||
).rejects.toHaveProperty(
|
||||
'code',
|
||||
@@ -298,6 +291,11 @@ describe('PageLayoutTabService', () => {
|
||||
pageLayoutId: 'non-existent-page-layout-id',
|
||||
};
|
||||
|
||||
jest.spyOn(pageLayoutTabRepository, 'insert').mockResolvedValue({
|
||||
identifiers: [{ id: 'page-layout-tab-id' }],
|
||||
generatedMaps: [],
|
||||
raw: [],
|
||||
});
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockRejectedValue(
|
||||
@@ -586,6 +584,7 @@ describe('PageLayoutTabService', () => {
|
||||
expect(pageLayoutService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
'deleted-page-layout-id',
|
||||
workspaceId,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+16
-27
@@ -38,18 +38,6 @@ describe('PageLayoutWidgetService', () => {
|
||||
deletedAt: null,
|
||||
} as PageLayoutWidgetEntity;
|
||||
|
||||
const mockPageLayoutTab = {
|
||||
id: 'page-layout-tab-id',
|
||||
title: 'Test Tab',
|
||||
position: 0,
|
||||
pageLayoutId: 'page-layout-id',
|
||||
pageLayout: { workspaceId: 'workspace-id' },
|
||||
widgets: [],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -63,6 +51,7 @@ describe('PageLayoutWidgetService', () => {
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
insert: jest.fn(),
|
||||
update: jest.fn(),
|
||||
softDelete: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
@@ -177,6 +166,7 @@ describe('PageLayoutWidgetService', () => {
|
||||
|
||||
describe('create', () => {
|
||||
const validPageLayoutWidgetData = {
|
||||
id: 'page-layout-widget-id',
|
||||
title: 'New Widget',
|
||||
pageLayoutTabId: 'page-layout-tab-id',
|
||||
gridPosition: { row: 0, column: 0, rowSpan: 4, columnSpan: 4 },
|
||||
@@ -186,14 +176,13 @@ describe('PageLayoutWidgetService', () => {
|
||||
it('should create a new page layout widget successfully', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest.spyOn(pageLayoutWidgetRepository, 'insert').mockResolvedValue({
|
||||
identifiers: [{ id: 'page-layout-widget-id' }],
|
||||
generatedMaps: [],
|
||||
raw: [],
|
||||
});
|
||||
jest
|
||||
.spyOn(pageLayoutTabService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayoutTab as any);
|
||||
jest
|
||||
.spyOn(pageLayoutWidgetRepository, 'create')
|
||||
.mockReturnValue(mockPageLayoutWidget);
|
||||
jest
|
||||
.spyOn(pageLayoutWidgetRepository, 'save')
|
||||
.spyOn(pageLayoutWidgetService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayoutWidget);
|
||||
|
||||
const result = await pageLayoutWidgetService.create(
|
||||
@@ -201,17 +190,10 @@ describe('PageLayoutWidgetService', () => {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
expect(pageLayoutTabService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
validPageLayoutWidgetData.pageLayoutTabId,
|
||||
workspaceId,
|
||||
);
|
||||
expect(pageLayoutWidgetRepository.create).toHaveBeenCalledWith({
|
||||
expect(pageLayoutWidgetRepository.insert).toHaveBeenCalledWith({
|
||||
...validPageLayoutWidgetData,
|
||||
workspaceId,
|
||||
});
|
||||
expect(pageLayoutWidgetRepository.save).toHaveBeenCalledWith(
|
||||
mockPageLayoutWidget,
|
||||
);
|
||||
expect(result).toEqual(mockPageLayoutWidget);
|
||||
});
|
||||
|
||||
@@ -223,9 +205,11 @@ describe('PageLayoutWidgetService', () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
// @ts-expect-error - we are testing the exception
|
||||
pageLayoutWidgetService.create(pageLayoutWidgetData, workspaceId),
|
||||
).rejects.toThrow(PageLayoutWidgetException);
|
||||
await expect(
|
||||
// @ts-expect-error - we are testing the exception
|
||||
pageLayoutWidgetService.create(pageLayoutWidgetData, workspaceId),
|
||||
).rejects.toHaveProperty(
|
||||
'code',
|
||||
@@ -241,9 +225,11 @@ describe('PageLayoutWidgetService', () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
// @ts-expect-error - we are testing the exception
|
||||
pageLayoutWidgetService.create(pageLayoutWidgetData, workspaceId),
|
||||
).rejects.toThrow(PageLayoutWidgetException);
|
||||
await expect(
|
||||
// @ts-expect-error - we are testing the exception
|
||||
pageLayoutWidgetService.create(pageLayoutWidgetData, workspaceId),
|
||||
).rejects.toHaveProperty(
|
||||
'code',
|
||||
@@ -259,9 +245,11 @@ describe('PageLayoutWidgetService', () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
// @ts-expect-error - we are testing the exception
|
||||
pageLayoutWidgetService.create(pageLayoutWidgetData, workspaceId),
|
||||
).rejects.toThrow(PageLayoutWidgetException);
|
||||
await expect(
|
||||
// @ts-expect-error - we are testing the exception
|
||||
pageLayoutWidgetService.create(pageLayoutWidgetData, workspaceId),
|
||||
).rejects.toHaveProperty(
|
||||
'code',
|
||||
@@ -578,6 +566,7 @@ describe('PageLayoutWidgetService', () => {
|
||||
expect(pageLayoutTabService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
'deleted-tab-id',
|
||||
workspaceId,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+72
-7
@@ -3,6 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { IsNull, type Repository } from 'typeorm';
|
||||
|
||||
import { type CreatePageLayoutInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout.input';
|
||||
import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
@@ -12,10 +13,12 @@ import {
|
||||
generatePageLayoutExceptionMessage,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/page-layout.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
describe('PageLayoutService', () => {
|
||||
let pageLayoutService: PageLayoutService;
|
||||
let pageLayoutRepository: Repository<PageLayoutEntity>;
|
||||
let twentyORMGlobalManager: TwentyORMGlobalManager;
|
||||
|
||||
const mockPageLayout = {
|
||||
id: 'page-layout-id',
|
||||
@@ -46,6 +49,13 @@ describe('PageLayoutService', () => {
|
||||
softDelete: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
restore: jest.fn(),
|
||||
insert: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: TwentyORMGlobalManager,
|
||||
useValue: {
|
||||
getRepositoryForWorkspace: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -55,6 +65,9 @@ describe('PageLayoutService', () => {
|
||||
pageLayoutRepository = module.get<Repository<PageLayoutEntity>>(
|
||||
getRepositoryToken(PageLayoutEntity),
|
||||
);
|
||||
twentyORMGlobalManager = module.get<TwentyORMGlobalManager>(
|
||||
TwentyORMGlobalManager,
|
||||
);
|
||||
});
|
||||
|
||||
describe('findByWorkspaceId', () => {
|
||||
@@ -142,17 +155,20 @@ describe('PageLayoutService', () => {
|
||||
|
||||
describe('create', () => {
|
||||
const validPageLayoutData = {
|
||||
id: 'page-layout-id',
|
||||
name: 'Test Page Layout',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: 'object-metadata-id',
|
||||
};
|
||||
|
||||
it('should create a page layout successfully', async () => {
|
||||
jest.spyOn(pageLayoutRepository, 'insert').mockResolvedValue({
|
||||
identifiers: [{ id: 'page-layout-id' }],
|
||||
generatedMaps: [],
|
||||
raw: [],
|
||||
});
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'create')
|
||||
.mockReturnValue(mockPageLayout);
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'save')
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayout);
|
||||
|
||||
const result = await pageLayoutService.create(
|
||||
@@ -160,11 +176,10 @@ describe('PageLayoutService', () => {
|
||||
'workspace-id',
|
||||
);
|
||||
|
||||
expect(pageLayoutRepository.create).toHaveBeenCalledWith({
|
||||
expect(pageLayoutRepository.insert).toHaveBeenCalledWith({
|
||||
...validPageLayoutData,
|
||||
workspaceId: 'workspace-id',
|
||||
});
|
||||
expect(pageLayoutRepository.save).toHaveBeenCalledWith(mockPageLayout);
|
||||
expect(result).toEqual(mockPageLayout);
|
||||
});
|
||||
|
||||
@@ -173,7 +188,10 @@ describe('PageLayoutService', () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
await expect(
|
||||
pageLayoutService.create(invalidData, workspaceId),
|
||||
pageLayoutService.create(
|
||||
invalidData as unknown as CreatePageLayoutInput,
|
||||
workspaceId,
|
||||
),
|
||||
).rejects.toThrow(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
@@ -210,6 +228,7 @@ describe('PageLayoutService', () => {
|
||||
expect(pageLayoutService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
id,
|
||||
workspaceId,
|
||||
undefined,
|
||||
);
|
||||
expect(result).toEqual(updatedPageLayout);
|
||||
});
|
||||
@@ -262,6 +281,7 @@ describe('PageLayoutService', () => {
|
||||
expect(pageLayoutService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
id,
|
||||
workspaceId,
|
||||
undefined,
|
||||
);
|
||||
expect(pageLayoutRepository.softDelete).toHaveBeenCalledWith(id);
|
||||
expect(result).toEqual(mockPageLayout);
|
||||
@@ -344,6 +364,51 @@ describe('PageLayoutService', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should destroy associated dashboards when page layout is a dashboard', async () => {
|
||||
const id = 'page-layout-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const mockDashboardRepository = {
|
||||
find: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
};
|
||||
const mockDashboards = [{ id: 'dashboard', pageLayoutId: id }];
|
||||
|
||||
jest.spyOn(pageLayoutRepository, 'findOne').mockResolvedValue({
|
||||
...mockPageLayout,
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
});
|
||||
jest
|
||||
.spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace')
|
||||
.mockResolvedValue(mockDashboardRepository as any);
|
||||
jest
|
||||
.spyOn(mockDashboardRepository, 'find')
|
||||
.mockResolvedValue(mockDashboards);
|
||||
jest
|
||||
.spyOn(mockDashboardRepository, 'delete')
|
||||
.mockResolvedValue({} as any);
|
||||
jest.spyOn(pageLayoutRepository, 'delete').mockResolvedValue({} as any);
|
||||
|
||||
const result = await pageLayoutService.destroy(id, workspaceId);
|
||||
|
||||
expect(
|
||||
twentyORMGlobalManager.getRepositoryForWorkspace,
|
||||
).toHaveBeenCalledWith(workspaceId, 'dashboard', {
|
||||
shouldBypassPermissionChecks: true,
|
||||
});
|
||||
expect(mockDashboardRepository.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
pageLayoutId: id,
|
||||
},
|
||||
});
|
||||
expect(mockDashboardRepository.delete).toHaveBeenCalledWith('dashboard');
|
||||
|
||||
expect(pageLayoutRepository.delete).toHaveBeenCalledWith(id);
|
||||
expect(result).toEqual({
|
||||
...mockPageLayout,
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('restore', () => {
|
||||
|
||||
Reference in New Issue
Block a user