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:
+2
@@ -8,6 +8,7 @@ import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-quer
|
||||
import { BlocklistQueryHookModule } from 'src/modules/blocklist/query-hooks/blocklist-query-hook.module';
|
||||
import { CalendarQueryHookModule } from 'src/modules/calendar/common/query-hooks/calendar-query-hook.module';
|
||||
import { ConnectedAccountQueryHookModule } from 'src/modules/connected-account/query-hooks/connected-account-query-hook.module';
|
||||
import { DashboardQueryHookModule } from 'src/modules/dashboard/query-hooks/dashboard-query-hook.module';
|
||||
import { MessagingQueryHookModule } from 'src/modules/messaging/common/query-hooks/messaging-query-hook.module';
|
||||
import { WorkspaceMemberQueryHookModule } from 'src/modules/workspace-member/query-hooks/workspace-member-query-hook.module';
|
||||
|
||||
@@ -16,6 +17,7 @@ import { WorkspaceMemberQueryHookModule } from 'src/modules/workspace-member/que
|
||||
MessagingQueryHookModule,
|
||||
CalendarQueryHookModule,
|
||||
ConnectedAccountQueryHookModule,
|
||||
DashboardQueryHookModule,
|
||||
BlocklistQueryHookModule,
|
||||
WorkspaceMemberQueryHookModule,
|
||||
DiscoveryModule,
|
||||
|
||||
+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', () => {
|
||||
|
||||
+14
-2
@@ -6,6 +6,7 @@ import {
|
||||
DASHBOARD_STANDARD_FIELD_IDS,
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity';
|
||||
|
||||
export const dashboardsAllView = (
|
||||
objectMetadataItems: ObjectMetadataEntity[],
|
||||
@@ -27,6 +28,7 @@ export const dashboardsAllView = (
|
||||
position: 0,
|
||||
icon: 'IconLayoutDashboard',
|
||||
kanbanFieldMetadataId: '',
|
||||
openRecordIn: ViewOpenRecordInType.RECORD_PAGE,
|
||||
filters: [],
|
||||
fields: [
|
||||
{
|
||||
@@ -42,7 +44,7 @@ export const dashboardsAllView = (
|
||||
fieldMetadataId:
|
||||
dashboardObjectMetadata.fields.find(
|
||||
(field) =>
|
||||
field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
|
||||
field.standardId === DASHBOARD_STANDARD_FIELD_IDS.createdBy,
|
||||
)?.id ?? '',
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
@@ -52,12 +54,22 @@ export const dashboardsAllView = (
|
||||
fieldMetadataId:
|
||||
dashboardObjectMetadata.fields.find(
|
||||
(field) =>
|
||||
field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
|
||||
field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
|
||||
)?.id ?? '',
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
dashboardObjectMetadata.fields.find(
|
||||
(field) =>
|
||||
field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
|
||||
)?.id ?? '',
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
+9
@@ -40,6 +40,7 @@ export const ATTACHMENT_STANDARD_FIELD_IDS = {
|
||||
person: '20202020-0158-4aa2-965c-5cdafe21ffa2',
|
||||
company: '20202020-ceab-4a28-b546-73b06b4c08d5',
|
||||
opportunity: '20202020-7374-499d-bea3-9354890755b5',
|
||||
dashboard: '20202020-5324-43f3-9dbf-1a33e7de0ce6',
|
||||
custom: '20202020-302d-43b3-9aea-aa4f89282a9f',
|
||||
} as const;
|
||||
|
||||
@@ -193,6 +194,7 @@ export const TIMELINE_ACTIVITY_STANDARD_FIELD_IDS = {
|
||||
workflow: '20202020-616c-4ad3-a2e9-c477c341e295',
|
||||
workflowVersion: '20202020-74f1-4711-a129-e14ca0ecd744',
|
||||
workflowRun: '20202020-96f0-401b-9186-a3a0759225ac',
|
||||
dashboard: '20202020-7864-48f5-af7c-9e4b60140948',
|
||||
custom: '20202020-4a71-41b0-9f83-9cdcca3f8b14',
|
||||
linkedRecordCachedName: '20202020-cfdb-4bef-bbce-a29f41230934',
|
||||
linkedRecordId: '20202020-2e0e-48c0-b445-ee6c1e61687d',
|
||||
@@ -213,6 +215,7 @@ export const FAVORITE_STANDARD_FIELD_IDS = {
|
||||
view: '20202020-5a93-4fa9-acce-e73481a0bbdf',
|
||||
custom: '20202020-855a-4bc8-9861-79deef37011f',
|
||||
favoriteFolder: '20202020-f658-4d12-8b4d-248356aa4bd9',
|
||||
dashboard: '20202020-6ef9-45e4-b440-cc986f687c91',
|
||||
} as const;
|
||||
|
||||
export const FAVORITE_FOLDER_STANDARD_FIELD_IDS = {
|
||||
@@ -541,7 +544,13 @@ export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = {
|
||||
|
||||
export const DASHBOARD_STANDARD_FIELD_IDS = {
|
||||
title: '20202020-20ee-4091-95dc-44b57eda3a89',
|
||||
position: '20202020-38af-409b-95f0-7f08aa5f420f',
|
||||
pageLayoutId: '20202020-bb53-4648-aa36-1d9d54e6f7f2',
|
||||
createdBy: '20202020-ff32-4fa1-b7ad-407cc6aa0734',
|
||||
timelineActivities: '20202020-9b0c-5d6e-7f8a-9b0c1d2e3f4a',
|
||||
favorites: '20202020-f032-478f-88fa-6426ff6f1e4c',
|
||||
attachments: '20202020-bf6f-4220-8c55-2764f1175870',
|
||||
searchVector: '20202020-0bcc-47a4-8360-2e35a7133f7a',
|
||||
} as const;
|
||||
|
||||
export const STANDARD_OBJECT_FIELD_IDS = {
|
||||
|
||||
+18
-1
@@ -1,9 +1,9 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
|
||||
@@ -18,6 +18,7 @@ import { ATTACHMENT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/work
|
||||
import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||
import { DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity';
|
||||
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity';
|
||||
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
@@ -158,6 +159,22 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceJoinColumn('opportunity')
|
||||
opportunityId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.dashboard,
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
label: msg`Dashboard`,
|
||||
description: msg`Attachment dashboard`,
|
||||
icon: 'IconLayout',
|
||||
inverseSideTarget: () => DashboardWorkspaceEntity,
|
||||
inverseSideFieldKey: 'attachments',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
dashboard: Relation<DashboardWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('dashboard')
|
||||
dashboardId: string | null;
|
||||
|
||||
@WorkspaceDynamicRelation({
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
argsFactory: (oppositeObjectMetadata) => ({
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { PageLayoutTabService } from 'src/engine/core-modules/page-layout/services/page-layout-tab.service';
|
||||
import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/page-layout.service';
|
||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||
import { type DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceQueryHook(`dashboard.createOne`)
|
||||
export class DashboardCreateOnePreQueryHook
|
||||
implements WorkspacePreQueryHookInstance
|
||||
{
|
||||
constructor(
|
||||
private readonly pageLayoutService: PageLayoutService,
|
||||
private readonly pageLayoutTabService: PageLayoutTabService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
authContext: AuthContext,
|
||||
_objectName: string,
|
||||
payload: CreateOneResolverArgs<DashboardWorkspaceEntity>,
|
||||
): Promise<CreateOneResolverArgs<DashboardWorkspaceEntity>> {
|
||||
const workspace = authContext.workspace;
|
||||
|
||||
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
||||
|
||||
return await this.coreDataSource.transaction(async (manager) => {
|
||||
const pageLayout = await this.pageLayoutService.create(
|
||||
{
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: null,
|
||||
name: 'Dashboard Layout',
|
||||
},
|
||||
workspace.id,
|
||||
manager,
|
||||
);
|
||||
|
||||
await this.pageLayoutTabService.create(
|
||||
{
|
||||
title: 'Tab 1',
|
||||
pageLayoutId: pageLayout.id,
|
||||
},
|
||||
workspace.id,
|
||||
manager,
|
||||
);
|
||||
|
||||
payload.data.pageLayoutId = pageLayout.id;
|
||||
|
||||
return payload;
|
||||
});
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { PageLayoutModule } from 'src/engine/core-modules/page-layout/page-layout.module';
|
||||
import { DashboardCreateOnePreQueryHook } from 'src/modules/dashboard/query-hooks/dashboard-create-one.pre-query.hook';
|
||||
|
||||
@Module({
|
||||
imports: [PageLayoutModule],
|
||||
providers: [DashboardCreateOnePreQueryHook],
|
||||
})
|
||||
export class DashboardQueryHookModule {}
|
||||
+90
-1
@@ -1,14 +1,30 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
|
||||
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceGate } from 'src/engine/twenty-orm/decorators/workspace-gate.decorator';
|
||||
import { WorkspaceIsFieldUIReadOnly } from 'src/engine/twenty-orm/decorators/workspace-is-field-ui-readonly.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSearchable } from 'src/engine/twenty-orm/decorators/workspace-is-searchable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
import { DASHBOARD_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.dashboard,
|
||||
@@ -22,6 +38,7 @@ import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync
|
||||
@WorkspaceGate({
|
||||
featureFlag: FeatureFlagKey.IS_PAGE_LAYOUT_ENABLED,
|
||||
})
|
||||
@WorkspaceIsSearchable()
|
||||
export class DashboardWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceField({
|
||||
standardId: DASHBOARD_STANDARD_FIELD_IDS.title,
|
||||
@@ -39,5 +56,77 @@ export class DashboardWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Dashboard page layout`,
|
||||
icon: 'IconLayout',
|
||||
})
|
||||
pageLayoutId: string;
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
pageLayoutId: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: DASHBOARD_STANDARD_FIELD_IDS.position,
|
||||
type: FieldMetadataType.POSITION,
|
||||
label: msg`Position`,
|
||||
description: msg`Dashboard record Position`,
|
||||
icon: 'IconHierarchy2',
|
||||
defaultValue: 0,
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
position: number;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: DASHBOARD_STANDARD_FIELD_IDS.createdBy,
|
||||
type: FieldMetadataType.ACTOR,
|
||||
label: msg`Created by`,
|
||||
icon: 'IconCreativeCommonsSa',
|
||||
description: msg`The creator of the record`,
|
||||
})
|
||||
@WorkspaceIsFieldUIReadOnly()
|
||||
createdBy: ActorMetadata;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: DASHBOARD_STANDARD_FIELD_IDS.timelineActivities,
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: msg`Timeline Activities`,
|
||||
description: msg`Timeline activities linked to the dashboard`,
|
||||
inverseSideTarget: () => TimelineActivityWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: DASHBOARD_STANDARD_FIELD_IDS.favorites,
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: msg`Favorites`,
|
||||
description: msg`Favorites linked to the dashboard`,
|
||||
inverseSideTarget: () => FavoriteWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
favorites: Relation<FavoriteWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: DASHBOARD_STANDARD_FIELD_IDS.attachments,
|
||||
type: RelationType.ONE_TO_MANY,
|
||||
label: msg`Attachments`,
|
||||
description: msg`Attachments linked to the dashboard`,
|
||||
inverseSideTarget: () => AttachmentWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
attachments: Relation<AttachmentWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: DASHBOARD_STANDARD_FIELD_IDS.searchVector,
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
label: SEARCH_VECTOR_FIELD.label,
|
||||
description: SEARCH_VECTOR_FIELD.description,
|
||||
icon: 'IconUser',
|
||||
generatedType: 'STORED',
|
||||
asExpression: `to_tsvector('english', title)`,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceFieldIndex({ indexType: IndexType.GIN })
|
||||
searchVector: string;
|
||||
}
|
||||
|
||||
+17
@@ -18,6 +18,7 @@ import { FAVORITE_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/worksp
|
||||
import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||
import { DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity';
|
||||
import { FavoriteFolderWorkspaceEntity } from 'src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity';
|
||||
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity';
|
||||
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
|
||||
@@ -210,6 +211,22 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceJoinColumn('note')
|
||||
noteId: string;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: FAVORITE_STANDARD_FIELD_IDS.dashboard,
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
label: msg`Dashboard`,
|
||||
description: msg`Favorite dashboard`,
|
||||
icon: 'IconLayoutDashboard',
|
||||
inverseSideTarget: () => DashboardWorkspaceEntity,
|
||||
inverseSideFieldKey: 'favorites',
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
dashboard: Relation<DashboardWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('dashboard')
|
||||
dashboardId: string;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: FAVORITE_STANDARD_FIELD_IDS.view,
|
||||
type: FieldMetadataType.UUID,
|
||||
|
||||
+18
-1
@@ -1,9 +1,9 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
|
||||
@@ -19,6 +19,7 @@ import { TIMELINE_ACTIVITY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manag
|
||||
import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||
import { DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity';
|
||||
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity';
|
||||
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
@@ -243,6 +244,22 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceJoinColumn('workflowRun')
|
||||
workflowRunId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.dashboard,
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
label: msg`Dashboard`,
|
||||
description: msg`Event dashboard`,
|
||||
icon: 'IconTargetArrow',
|
||||
inverseSideTarget: () => DashboardWorkspaceEntity,
|
||||
inverseSideFieldKey: 'timelineActivities',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
dashboard: Relation<DashboardWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('dashboard')
|
||||
dashboardId: string | null;
|
||||
|
||||
@WorkspaceDynamicRelation({
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
argsFactory: (oppositeObjectMetadata) => ({
|
||||
|
||||
+67
@@ -1,3 +1,4 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { TEST_NOT_EXISTING_PAGE_LAYOUT_ID } from 'test/integration/constants/test-page-layout-ids.constants';
|
||||
import { createPageLayoutOperationFactory } from 'test/integration/graphql/utils/create-page-layout-operation-factory.util';
|
||||
import { deletePageLayoutOperationFactory } from 'test/integration/graphql/utils/delete-page-layout-operation-factory.util';
|
||||
@@ -367,6 +368,72 @@ describe('Page Layout Resolver', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should destroy all associated dashboards when page layout is of type dashboard', async () => {
|
||||
const dashboardId = '20202020-304c-44f2-ba7b-070762ff0e8a';
|
||||
|
||||
const pageLayout = await createTestPageLayoutWithGraphQL({
|
||||
name: 'Page Layout to Destroy',
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
});
|
||||
|
||||
const findOneDashboardOperation = {
|
||||
query: gql`
|
||||
query Dashboard($filter: DashboardFilterInput!) {
|
||||
dashboard(filter: $filter) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
filter: { id: { eq: dashboardId } },
|
||||
},
|
||||
};
|
||||
|
||||
await makeGraphqlAPIRequest({
|
||||
query: gql`
|
||||
mutation CreateDashboard($input: CreateDashboardInput!) {
|
||||
createDashboard(input: $input) {
|
||||
pageLayoutId
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
id: dashboardId,
|
||||
name: 'Dashboard to Destroy',
|
||||
pageLayoutId: pageLayout.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const findOneDashboardResponseBeforeDestroy = await makeGraphqlAPIRequest(
|
||||
findOneDashboardOperation,
|
||||
);
|
||||
|
||||
expect(
|
||||
findOneDashboardResponseBeforeDestroy.body.data.dashboard,
|
||||
).toBeDefined();
|
||||
|
||||
const destroyOperation = destroyPageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
});
|
||||
const destroyResponse = await makeGraphqlAPIRequest(destroyOperation);
|
||||
|
||||
const findOneDashboardResponseAfterDestroy = await makeGraphqlAPIRequest(
|
||||
findOneDashboardOperation,
|
||||
);
|
||||
|
||||
assertGraphQLSuccessfulResponse(destroyResponse);
|
||||
expect(destroyResponse.body.data.destroyPageLayout).toBe(true);
|
||||
|
||||
expect(findOneDashboardResponseAfterDestroy.body.errors).toBeDefined();
|
||||
expect(
|
||||
findOneDashboardResponseAfterDestroy.body.errors[0].extensions.code,
|
||||
).toBe(ErrorCode.NOT_FOUND);
|
||||
});
|
||||
});
|
||||
|
||||
describe('restorePageLayout', () => {
|
||||
|
||||
Reference in New Issue
Block a user