Create PageLayoutTab resolver and controller (#14284)
Closes https://github.com/twentyhq/core-team-issues/issues/1394
This commit is contained in:
@@ -709,6 +709,12 @@ export type CreatePageLayoutInput = {
|
||||
type?: InputMaybe<PageLayoutType>;
|
||||
};
|
||||
|
||||
export type CreatePageLayoutTabInput = {
|
||||
pageLayoutId: Scalars['UUID'];
|
||||
position?: InputMaybe<Scalars['Float']>;
|
||||
title: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateRemoteServerInput = {
|
||||
foreignDataWrapperOptions: Scalars['JSON'];
|
||||
foreignDataWrapperType: Scalars['String'];
|
||||
@@ -1389,6 +1395,7 @@ export type Mutation = {
|
||||
createOneRole: Role;
|
||||
createOneServerlessFunction: ServerlessFunction;
|
||||
createPageLayout: PageLayout;
|
||||
createPageLayoutTab: PageLayoutTab;
|
||||
createSAMLIdentityProvider: SetupSsoOutput;
|
||||
createWebhook: Webhook;
|
||||
createWorkflowVersionEdge: WorkflowVersionStepChanges;
|
||||
@@ -1411,6 +1418,7 @@ export type Mutation = {
|
||||
deleteOneRole: Scalars['String'];
|
||||
deleteOneServerlessFunction: ServerlessFunction;
|
||||
deletePageLayout: PageLayout;
|
||||
deletePageLayoutTab: Scalars['Boolean'];
|
||||
deleteSSOIdentityProvider: DeleteSsoOutput;
|
||||
deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput;
|
||||
deleteUser: User;
|
||||
@@ -1425,6 +1433,7 @@ export type Mutation = {
|
||||
destroyCoreViewGroup: Scalars['Boolean'];
|
||||
destroyCoreViewSort: Scalars['Boolean'];
|
||||
destroyPageLayout: Scalars['Boolean'];
|
||||
destroyPageLayoutTab: Scalars['Boolean'];
|
||||
disablePostgresProxy: PostgresCredentials;
|
||||
editSSOIdentityProvider: EditSsoOutput;
|
||||
emailPasswordResetLink: EmailPasswordResetLink;
|
||||
@@ -1449,6 +1458,7 @@ export type Mutation = {
|
||||
resendEmailVerificationToken: ResendEmailVerificationTokenOutput;
|
||||
resendWorkspaceInvitation: SendInvitationsOutput;
|
||||
restorePageLayout: PageLayout;
|
||||
restorePageLayoutTab: PageLayoutTab;
|
||||
revokeApiKey?: Maybe<ApiKey>;
|
||||
runWorkflowVersion: WorkflowRun;
|
||||
saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess;
|
||||
@@ -1482,6 +1492,7 @@ export type Mutation = {
|
||||
updateOneRole: Role;
|
||||
updateOneServerlessFunction: ServerlessFunction;
|
||||
updatePageLayout: PageLayout;
|
||||
updatePageLayoutTab: PageLayoutTab;
|
||||
updatePasswordViaResetToken: InvalidatePassword;
|
||||
updateSubscriptionItemPrice: BillingUpdateOutput;
|
||||
updateWebhook?: Maybe<Webhook>;
|
||||
@@ -1665,6 +1676,11 @@ export type MutationCreatePageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreatePageLayoutTabArgs = {
|
||||
input: CreatePageLayoutTabInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateSamlIdentityProviderArgs = {
|
||||
input: SetupSamlSsoInput;
|
||||
};
|
||||
@@ -1770,6 +1786,11 @@ export type MutationDeletePageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeletePageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteSsoIdentityProviderArgs = {
|
||||
input: DeleteSsoInput;
|
||||
};
|
||||
@@ -1835,6 +1856,11 @@ export type MutationDestroyPageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDestroyPageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationEditSsoIdentityProviderArgs = {
|
||||
input: EditSsoInput;
|
||||
};
|
||||
@@ -1949,6 +1975,11 @@ export type MutationRestorePageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationRestorePageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationRevokeApiKeyArgs = {
|
||||
input: RevokeApiKeyDto;
|
||||
};
|
||||
@@ -2118,6 +2149,12 @@ export type MutationUpdatePageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdatePageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
input: UpdatePageLayoutTabInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdatePasswordViaResetTokenArgs = {
|
||||
newPassword: Scalars['String'];
|
||||
passwordResetToken: Scalars['String'];
|
||||
@@ -2406,6 +2443,7 @@ export type PageLayoutTab = {
|
||||
position: Scalars['Float'];
|
||||
title: Scalars['String'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workspaceId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export enum PageLayoutType {
|
||||
@@ -2533,6 +2571,8 @@ export type Query = {
|
||||
getIndicatorHealthStatus: AdminPanelHealthServiceData;
|
||||
getMeteredProductsUsage: Array<BillingMeteredProductUsageOutput>;
|
||||
getPageLayout?: Maybe<PageLayout>;
|
||||
getPageLayoutTab: PageLayoutTab;
|
||||
getPageLayoutTabs: Array<PageLayoutTab>;
|
||||
getPageLayouts: Array<PageLayout>;
|
||||
getPostgresCredentials?: Maybe<PostgresCredentials>;
|
||||
getPublicWorkspaceDataByDomain: PublicWorkspaceDataOutput;
|
||||
@@ -2747,6 +2787,16 @@ export type QueryGetPageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutTabsArgs = {
|
||||
pageLayoutId: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutsArgs = {
|
||||
objectMetadataId?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
@@ -3374,6 +3424,11 @@ export type UpdatePageLayoutInput = {
|
||||
type?: InputMaybe<PageLayoutType>;
|
||||
};
|
||||
|
||||
export type UpdatePageLayoutTabInput = {
|
||||
position?: InputMaybe<Scalars['Float']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateRemoteServerInput = {
|
||||
foreignDataWrapperOptions?: InputMaybe<Scalars['JSON']>;
|
||||
id: Scalars['UUID'];
|
||||
|
||||
@@ -681,6 +681,12 @@ export type CreatePageLayoutInput = {
|
||||
type?: InputMaybe<PageLayoutType>;
|
||||
};
|
||||
|
||||
export type CreatePageLayoutTabInput = {
|
||||
pageLayoutId: Scalars['UUID'];
|
||||
position?: InputMaybe<Scalars['Float']>;
|
||||
title: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateRoleInput = {
|
||||
canAccessAllTools?: InputMaybe<Scalars['Boolean']>;
|
||||
canBeAssignedToAgents?: InputMaybe<Scalars['Boolean']>;
|
||||
@@ -1345,6 +1351,7 @@ export type Mutation = {
|
||||
createOneRole: Role;
|
||||
createOneServerlessFunction: ServerlessFunction;
|
||||
createPageLayout: PageLayout;
|
||||
createPageLayoutTab: PageLayoutTab;
|
||||
createSAMLIdentityProvider: SetupSsoOutput;
|
||||
createWebhook: Webhook;
|
||||
createWorkflowVersionEdge: WorkflowVersionStepChanges;
|
||||
@@ -1366,6 +1373,7 @@ export type Mutation = {
|
||||
deleteOneRole: Scalars['String'];
|
||||
deleteOneServerlessFunction: ServerlessFunction;
|
||||
deletePageLayout: PageLayout;
|
||||
deletePageLayoutTab: Scalars['Boolean'];
|
||||
deleteSSOIdentityProvider: DeleteSsoOutput;
|
||||
deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput;
|
||||
deleteUser: User;
|
||||
@@ -1380,6 +1388,7 @@ export type Mutation = {
|
||||
destroyCoreViewGroup: Scalars['Boolean'];
|
||||
destroyCoreViewSort: Scalars['Boolean'];
|
||||
destroyPageLayout: Scalars['Boolean'];
|
||||
destroyPageLayoutTab: Scalars['Boolean'];
|
||||
disablePostgresProxy: PostgresCredentials;
|
||||
editSSOIdentityProvider: EditSsoOutput;
|
||||
emailPasswordResetLink: EmailPasswordResetLink;
|
||||
@@ -1404,6 +1413,7 @@ export type Mutation = {
|
||||
resendEmailVerificationToken: ResendEmailVerificationTokenOutput;
|
||||
resendWorkspaceInvitation: SendInvitationsOutput;
|
||||
restorePageLayout: PageLayout;
|
||||
restorePageLayoutTab: PageLayoutTab;
|
||||
revokeApiKey?: Maybe<ApiKey>;
|
||||
runWorkflowVersion: WorkflowRun;
|
||||
saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess;
|
||||
@@ -1433,6 +1443,7 @@ export type Mutation = {
|
||||
updateOneRole: Role;
|
||||
updateOneServerlessFunction: ServerlessFunction;
|
||||
updatePageLayout: PageLayout;
|
||||
updatePageLayoutTab: PageLayoutTab;
|
||||
updatePasswordViaResetToken: InvalidatePassword;
|
||||
updateSubscriptionItemPrice: BillingUpdateOutput;
|
||||
updateWebhook?: Maybe<Webhook>;
|
||||
@@ -1601,6 +1612,11 @@ export type MutationCreatePageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreatePageLayoutTabArgs = {
|
||||
input: CreatePageLayoutTabInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateSamlIdentityProviderArgs = {
|
||||
input: SetupSamlSsoInput;
|
||||
};
|
||||
@@ -1701,6 +1717,11 @@ export type MutationDeletePageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeletePageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteSsoIdentityProviderArgs = {
|
||||
input: DeleteSsoInput;
|
||||
};
|
||||
@@ -1766,6 +1787,11 @@ export type MutationDestroyPageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDestroyPageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationEditSsoIdentityProviderArgs = {
|
||||
input: EditSsoInput;
|
||||
};
|
||||
@@ -1880,6 +1906,11 @@ export type MutationRestorePageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationRestorePageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationRevokeApiKeyArgs = {
|
||||
input: RevokeApiKeyDto;
|
||||
};
|
||||
@@ -2029,6 +2060,12 @@ export type MutationUpdatePageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdatePageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
input: UpdatePageLayoutTabInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdatePasswordViaResetTokenArgs = {
|
||||
newPassword: Scalars['String'];
|
||||
passwordResetToken: Scalars['String'];
|
||||
@@ -2317,6 +2354,7 @@ export type PageLayoutTab = {
|
||||
position: Scalars['Float'];
|
||||
title: Scalars['String'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workspaceId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export enum PageLayoutType {
|
||||
@@ -2441,6 +2479,8 @@ export type Query = {
|
||||
getIndicatorHealthStatus: AdminPanelHealthServiceData;
|
||||
getMeteredProductsUsage: Array<BillingMeteredProductUsageOutput>;
|
||||
getPageLayout?: Maybe<PageLayout>;
|
||||
getPageLayoutTab: PageLayoutTab;
|
||||
getPageLayoutTabs: Array<PageLayoutTab>;
|
||||
getPageLayouts: Array<PageLayout>;
|
||||
getPostgresCredentials?: Maybe<PostgresCredentials>;
|
||||
getPublicWorkspaceDataByDomain: PublicWorkspaceDataOutput;
|
||||
@@ -2629,6 +2669,16 @@ export type QueryGetPageLayoutArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutTabArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutTabsArgs = {
|
||||
pageLayoutId: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutsArgs = {
|
||||
objectMetadataId?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
@@ -3220,6 +3270,11 @@ export type UpdatePageLayoutInput = {
|
||||
type?: InputMaybe<PageLayoutType>;
|
||||
};
|
||||
|
||||
export type UpdatePageLayoutTabInput = {
|
||||
position?: InputMaybe<Scalars['Float']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateRoleInput = {
|
||||
/** The id of the role to update */
|
||||
id: Scalars['UUID'];
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class ChangePageLayoutTabPositionToFloat1756977847652
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'ChangePageLayoutTabPositionToFloat1756977847652';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" DROP COLUMN "position"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" ADD "position" double precision NOT NULL DEFAULT '0'`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" DROP COLUMN "position"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" ADD "position" integer NOT NULL DEFAULT '0'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddWorkspaceIdToPageLayoutTabAndPageLayoutWidget1756997911163
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddWorkspaceIdToPageLayoutTabAndPageLayoutWidget1756997911163';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_PAGE_LAYOUT_WIDGET_PAGE_LAYOUT_TAB_ID"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_PAGE_LAYOUT_TAB_PAGE_LAYOUT_ID"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" ADD "workspaceId" uuid NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" ADD "workspaceId" uuid NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_PAGE_LAYOUT_WIDGET_WORKSPACE_ID_PAGE_LAYOUT_TAB_ID" ON "core"."pageLayoutWidget" ("workspaceId", "pageLayoutTabId") WHERE "deletedAt" IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_PAGE_LAYOUT_TAB_WORKSPACE_ID_PAGE_LAYOUT_ID" ON "core"."pageLayoutTab" ("workspaceId", "pageLayoutId") WHERE "deletedAt" IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" ADD CONSTRAINT "FK_555948f84165dce1fe1f5f955ce" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" ADD CONSTRAINT "FK_2528e67c8c0c953d8303172989e" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" DROP CONSTRAINT "FK_2528e67c8c0c953d8303172989e"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" DROP CONSTRAINT "FK_555948f84165dce1fe1f5f955ce"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_PAGE_LAYOUT_TAB_WORKSPACE_ID_PAGE_LAYOUT_ID"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_PAGE_LAYOUT_WIDGET_WORKSPACE_ID_PAGE_LAYOUT_TAB_ID"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" DROP COLUMN "workspaceId"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" DROP COLUMN "workspaceId"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_PAGE_LAYOUT_TAB_PAGE_LAYOUT_ID" ON "core"."pageLayoutTab" ("pageLayoutId") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_PAGE_LAYOUT_WIDGET_PAGE_LAYOUT_TAB_ID" ON "core"."pageLayoutWidget" ("pageLayoutTabId") `,
|
||||
);
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UseFilters,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { CreatePageLayoutTabInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout-tab.input';
|
||||
import { UpdatePageLayoutTabInput } from 'src/engine/core-modules/page-layout/dtos/inputs/update-page-layout-tab.input';
|
||||
import { type PageLayoutTabDTO } from 'src/engine/core-modules/page-layout/dtos/page-layout-tab.dto';
|
||||
import {
|
||||
generatePageLayoutTabExceptionMessage,
|
||||
PageLayoutTabException,
|
||||
PageLayoutTabExceptionCode,
|
||||
PageLayoutTabExceptionMessageKey,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
import { PageLayoutTabRestApiExceptionFilter } from 'src/engine/core-modules/page-layout/filters/page-layout-tab-rest-api-exception.filter';
|
||||
import { PageLayoutTabService } from 'src/engine/core-modules/page-layout/services/page-layout-tab.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Controller('rest/metadata/page-layout-tabs')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(PageLayoutTabRestApiExceptionFilter)
|
||||
export class PageLayoutTabController {
|
||||
constructor(private readonly pageLayoutTabService: PageLayoutTabService) {}
|
||||
|
||||
@Get()
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Query('pageLayoutId') pageLayoutId: string,
|
||||
): Promise<PageLayoutTabDTO[]> {
|
||||
if (!isDefined(pageLayoutId)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_ID_REQUIRED,
|
||||
),
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return this.pageLayoutTabService.findByPageLayoutId(
|
||||
workspace.id,
|
||||
pageLayoutId,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutTabDTO | null> {
|
||||
return this.pageLayoutTabService.findByIdOrThrow(id, workspace.id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Body() input: CreatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.create(input, workspace.id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() input: UpdatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.delete(id, workspace.id);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -11,6 +11,8 @@ import {
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { CreatePageLayoutInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout.input';
|
||||
import { UpdatePageLayoutInput } from 'src/engine/core-modules/page-layout/dtos/inputs/update-page-layout.input';
|
||||
import { type PageLayoutDTO } from 'src/engine/core-modules/page-layout/dtos/page-layout.dto';
|
||||
@@ -32,7 +34,7 @@ export class PageLayoutController {
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Query('objectMetadataId') objectMetadataId?: string,
|
||||
): Promise<PageLayoutDTO[]> {
|
||||
if (objectMetadataId) {
|
||||
if (isDefined(objectMetadataId)) {
|
||||
return this.pageLayoutService.findByObjectMetadataId(
|
||||
workspace.id,
|
||||
objectMetadataId,
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { Field, Float, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class CreatePageLayoutTabInput {
|
||||
@Field({ nullable: false })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title: string;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
position?: number;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
pageLayoutId: string;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { Field, Float, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNumber, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class UpdatePageLayoutTabInput {
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
title?: string;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
position?: number;
|
||||
}
|
||||
+5
-2
@@ -1,4 +1,4 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
import { Field, Float, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
@@ -12,12 +12,15 @@ export class PageLayoutTabDTO {
|
||||
@Field({ nullable: false })
|
||||
title: string;
|
||||
|
||||
@Field({ nullable: false, defaultValue: 0 })
|
||||
@Field(() => Float, { nullable: false, defaultValue: 0 })
|
||||
position: number;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
pageLayoutId: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
workspaceId: string;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
+16
-2
@@ -14,9 +14,14 @@ import {
|
||||
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-widget.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Entity({ name: 'pageLayoutTab', schema: 'core' })
|
||||
@Index('IDX_PAGE_LAYOUT_TAB_PAGE_LAYOUT_ID', ['pageLayoutId'])
|
||||
@Index(
|
||||
'IDX_PAGE_LAYOUT_TAB_WORKSPACE_ID_PAGE_LAYOUT_ID',
|
||||
['workspaceId', 'pageLayoutId'],
|
||||
{ where: '"deletedAt" IS NULL' },
|
||||
)
|
||||
export class PageLayoutTabEntity implements Required<PageLayoutTabEntity> {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
@@ -24,7 +29,16 @@ export class PageLayoutTabEntity implements Required<PageLayoutTabEntity> {
|
||||
@Column({ nullable: false })
|
||||
title: string;
|
||||
|
||||
@Column({ nullable: false, type: 'int', default: 0 })
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => Workspace, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'workspaceId' })
|
||||
workspace: Relation<Workspace>;
|
||||
|
||||
@Column({ nullable: false, type: 'float', default: 0 })
|
||||
position: number;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
|
||||
+15
-1
@@ -14,10 +14,15 @@ import {
|
||||
import { PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity';
|
||||
import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum';
|
||||
import { GridPosition } from 'src/engine/core-modules/page-layout/types/grid-position.type';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
@Entity({ name: 'pageLayoutWidget', schema: 'core' })
|
||||
@Index('IDX_PAGE_LAYOUT_WIDGET_PAGE_LAYOUT_TAB_ID', ['pageLayoutTabId'])
|
||||
@Index(
|
||||
'IDX_PAGE_LAYOUT_WIDGET_WORKSPACE_ID_PAGE_LAYOUT_TAB_ID',
|
||||
['workspaceId', 'pageLayoutTabId'],
|
||||
{ where: '"deletedAt" IS NULL' },
|
||||
)
|
||||
export class PageLayoutWidgetEntity
|
||||
implements Required<PageLayoutWidgetEntity>
|
||||
{
|
||||
@@ -27,6 +32,15 @@ export class PageLayoutWidgetEntity
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
pageLayoutTabId: string;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => Workspace, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'workspaceId' })
|
||||
workspace: Relation<Workspace>;
|
||||
|
||||
@ManyToOne(() => PageLayoutTabEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export enum PageLayoutTabExceptionCode {
|
||||
PAGE_LAYOUT_TAB_NOT_FOUND = 'PAGE_LAYOUT_TAB_NOT_FOUND',
|
||||
INVALID_PAGE_LAYOUT_TAB_DATA = 'INVALID_PAGE_LAYOUT_TAB_DATA',
|
||||
}
|
||||
|
||||
export enum PageLayoutTabExceptionMessageKey {
|
||||
PAGE_LAYOUT_TAB_NOT_FOUND = 'PAGE_LAYOUT_TAB_NOT_FOUND',
|
||||
TITLE_REQUIRED = 'TITLE_REQUIRED',
|
||||
PAGE_LAYOUT_ID_REQUIRED = 'PAGE_LAYOUT_ID_REQUIRED',
|
||||
PAGE_LAYOUT_NOT_FOUND = 'PAGE_LAYOUT_NOT_FOUND',
|
||||
PAGE_LAYOUT_TAB_NOT_DELETED = 'PAGE_LAYOUT_TAB_NOT_DELETED',
|
||||
}
|
||||
|
||||
export class PageLayoutTabException extends CustomException<PageLayoutTabExceptionCode> {}
|
||||
|
||||
export const generatePageLayoutTabExceptionMessage = (
|
||||
key: PageLayoutTabExceptionMessageKey,
|
||||
value?: string,
|
||||
): string => {
|
||||
switch (key) {
|
||||
case PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND:
|
||||
return `Page layout tab with ID "${value}" not found`;
|
||||
case PageLayoutTabExceptionMessageKey.TITLE_REQUIRED:
|
||||
return 'Page layout tab title is required';
|
||||
case PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_ID_REQUIRED:
|
||||
return 'Page layout ID is required';
|
||||
case PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND:
|
||||
return 'Page layout not found';
|
||||
case PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_DELETED:
|
||||
return 'Page layout tab is not deleted and cannot be restored';
|
||||
default:
|
||||
assertUnreachable(key);
|
||||
}
|
||||
};
|
||||
+3
-4
@@ -11,7 +11,6 @@ import {
|
||||
PageLayoutException,
|
||||
PageLayoutExceptionCode,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
import { type CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
@Catch(PageLayoutException)
|
||||
export class PageLayoutRestApiExceptionFilter implements ExceptionFilter {
|
||||
@@ -26,20 +25,20 @@ export class PageLayoutRestApiExceptionFilter implements ExceptionFilter {
|
||||
switch (exception.code) {
|
||||
case PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
exception,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
exception,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
exception,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import {
|
||||
PageLayoutTabException,
|
||||
PageLayoutTabExceptionCode,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
|
||||
@Catch(PageLayoutTabException)
|
||||
export class PageLayoutTabRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
catch(exception: PageLayoutTabException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { PageLayoutTabController } from 'src/engine/core-modules/page-layout/controllers/page-layout-tab.controller';
|
||||
import { PageLayoutController } from 'src/engine/core-modules/page-layout/controllers/page-layout.controller';
|
||||
import { PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-widget.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import { PageLayoutTabResolver } from 'src/engine/core-modules/page-layout/resolvers/page-layout-tab.resolver';
|
||||
import { PageLayoutResolver } from 'src/engine/core-modules/page-layout/resolvers/page-layout.resolver';
|
||||
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';
|
||||
@@ -17,8 +19,13 @@ import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/
|
||||
PageLayoutWidgetEntity,
|
||||
]),
|
||||
],
|
||||
controllers: [PageLayoutController],
|
||||
providers: [PageLayoutService, PageLayoutTabService, PageLayoutResolver],
|
||||
controllers: [PageLayoutController, PageLayoutTabController],
|
||||
providers: [
|
||||
PageLayoutService,
|
||||
PageLayoutTabService,
|
||||
PageLayoutResolver,
|
||||
PageLayoutTabResolver,
|
||||
],
|
||||
exports: [PageLayoutService, PageLayoutTabService],
|
||||
})
|
||||
export class PageLayoutModule {}
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { CreatePageLayoutTabInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout-tab.input';
|
||||
import { UpdatePageLayoutTabInput } from 'src/engine/core-modules/page-layout/dtos/inputs/update-page-layout-tab.input';
|
||||
import { PageLayoutTabDTO } from 'src/engine/core-modules/page-layout/dtos/page-layout-tab.dto';
|
||||
import { PageLayoutTabService } from 'src/engine/core-modules/page-layout/services/page-layout-tab.service';
|
||||
import { PageLayoutGraphqlApiExceptionFilter } from 'src/engine/core-modules/page-layout/utils/page-layout-graphql-api-exception.filter';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Resolver(() => PageLayoutTabDTO)
|
||||
@UseFilters(PageLayoutGraphqlApiExceptionFilter)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
export class PageLayoutTabResolver {
|
||||
constructor(private readonly pageLayoutTabService: PageLayoutTabService) {}
|
||||
|
||||
@Query(() => [PageLayoutTabDTO])
|
||||
async getPageLayoutTabs(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('pageLayoutId', { type: () => String }) pageLayoutId: string,
|
||||
): Promise<PageLayoutTabDTO[]> {
|
||||
return this.pageLayoutTabService.findByPageLayoutId(
|
||||
workspace.id,
|
||||
pageLayoutId,
|
||||
);
|
||||
}
|
||||
|
||||
@Query(() => PageLayoutTabDTO)
|
||||
async getPageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.findByIdOrThrow(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
async createPageLayoutTab(
|
||||
@Args('input') input: CreatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.create(input, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
async updatePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@Args('input') input: UpdatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async deletePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<boolean> {
|
||||
const deletedPageLayoutTab = await this.pageLayoutTabService.delete(
|
||||
id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return isDefined(deletedPageLayoutTab);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async destroyPageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<boolean> {
|
||||
return this.pageLayoutTabService.destroy(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
async restorePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.restore(id, workspace.id);
|
||||
}
|
||||
}
|
||||
+189
@@ -1,15 +1,29 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity';
|
||||
import {
|
||||
PageLayoutTabException,
|
||||
PageLayoutTabExceptionCode,
|
||||
PageLayoutTabExceptionMessageKey,
|
||||
generatePageLayoutTabExceptionMessage,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
import {
|
||||
PageLayoutException,
|
||||
PageLayoutExceptionCode,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/page-layout.service';
|
||||
|
||||
@Injectable()
|
||||
export class PageLayoutTabService {
|
||||
constructor(
|
||||
@InjectRepository(PageLayoutTabEntity)
|
||||
private readonly pageLayoutTabRepository: Repository<PageLayoutTabEntity>,
|
||||
private readonly pageLayoutService: PageLayoutService,
|
||||
) {}
|
||||
|
||||
async findByPageLayoutId(
|
||||
@@ -26,4 +40,179 @@ export class PageLayoutTabService {
|
||||
relations: ['widgets'],
|
||||
});
|
||||
}
|
||||
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutTabEntity> {
|
||||
const pageLayoutTab = await this.pageLayoutTabRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
relations: ['widgets'],
|
||||
});
|
||||
|
||||
if (!isDefined(pageLayoutTab)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return pageLayoutTab;
|
||||
}
|
||||
|
||||
async create(
|
||||
pageLayoutTabData: Partial<PageLayoutTabEntity>,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutTabEntity> {
|
||||
if (!isDefined(pageLayoutTabData.title)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.TITLE_REQUIRED,
|
||||
),
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(pageLayoutTabData.pageLayoutId)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_ID_REQUIRED,
|
||||
),
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.pageLayoutService.findByIdOrThrow(
|
||||
pageLayoutTabData.pageLayoutId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const pageLayoutTab = this.pageLayoutTabRepository.create({
|
||||
...pageLayoutTabData,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return this.pageLayoutTabRepository.save(pageLayoutTab);
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof PageLayoutException &&
|
||||
error.code === PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND
|
||||
) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: QueryDeepPartialEntity<PageLayoutTabEntity>,
|
||||
): Promise<PageLayoutTabEntity> {
|
||||
const existingTab = await this.pageLayoutTabRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(existingTab)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutTabRepository.update({ id }, updateData);
|
||||
|
||||
return this.findByIdOrThrow(id, workspaceId);
|
||||
}
|
||||
|
||||
async delete(id: string, workspaceId: string): Promise<PageLayoutTabEntity> {
|
||||
const pageLayoutTab = await this.findByIdOrThrow(id, workspaceId);
|
||||
|
||||
await this.pageLayoutTabRepository.softDelete(id);
|
||||
|
||||
return pageLayoutTab;
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
const pageLayoutTab = await this.pageLayoutTabRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
if (!isDefined(pageLayoutTab)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutTabRepository.delete(id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async restore(id: string, workspaceId: string): Promise<PageLayoutTabEntity> {
|
||||
const pageLayoutTab = await this.pageLayoutTabRepository.findOne({
|
||||
select: {
|
||||
id: true,
|
||||
deletedAt: true,
|
||||
},
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
if (!isDefined(pageLayoutTab)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(pageLayoutTab.deletedAt)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_DELETED,
|
||||
),
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutTabRepository.restore(id);
|
||||
|
||||
const restoredPageLayoutTab = await this.findByIdOrThrow(id, workspaceId);
|
||||
|
||||
return restoredPageLayoutTab;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-6
@@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import {
|
||||
@@ -93,14 +94,11 @@ export class PageLayoutService {
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: Partial<PageLayoutEntity>,
|
||||
updateData: QueryDeepPartialEntity<PageLayoutEntity>,
|
||||
): Promise<PageLayoutEntity> {
|
||||
const existingPageLayout = await this.findByIdOrThrow(id, workspaceId);
|
||||
await this.pageLayoutRepository.update({ id, workspaceId }, updateData);
|
||||
|
||||
const updatedPageLayout = await this.pageLayoutRepository.save({
|
||||
...existingPageLayout,
|
||||
...updateData,
|
||||
});
|
||||
const updatedPageLayout = await this.findByIdOrThrow(id, workspaceId);
|
||||
|
||||
return updatedPageLayout;
|
||||
}
|
||||
@@ -139,6 +137,10 @@ export class PageLayoutService {
|
||||
|
||||
async restore(id: string, workspaceId: string): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.pageLayoutRepository.findOne({
|
||||
select: {
|
||||
id: true,
|
||||
deletedAt: true,
|
||||
},
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
|
||||
+380
@@ -6,11 +6,21 @@ import { type Repository } from 'typeorm';
|
||||
import { PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity';
|
||||
import { type PageLayoutWidgetEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-widget.entity';
|
||||
import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum';
|
||||
import {
|
||||
PageLayoutTabException,
|
||||
PageLayoutTabExceptionCode,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
import {
|
||||
PageLayoutException,
|
||||
PageLayoutExceptionCode,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
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';
|
||||
|
||||
describe('PageLayoutTabService', () => {
|
||||
let pageLayoutTabService: PageLayoutTabService;
|
||||
let pageLayoutTabRepository: Repository<PageLayoutTabEntity>;
|
||||
let pageLayoutService: PageLayoutService;
|
||||
|
||||
const mockPageLayoutTab = {
|
||||
id: 'page-layout-tab-id',
|
||||
@@ -18,6 +28,8 @@ describe('PageLayoutTabService', () => {
|
||||
position: 0,
|
||||
pageLayoutId: 'page-layout-id',
|
||||
pageLayout: {} as any,
|
||||
workspaceId: 'workspace-id',
|
||||
workspace: {} as any,
|
||||
widgets: [],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
@@ -37,6 +49,15 @@ 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();
|
||||
|
||||
@@ -47,6 +68,19 @@ describe('PageLayoutTabService', () => {
|
||||
provide: getRepositoryToken(PageLayoutTabEntity),
|
||||
useValue: {
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
update: jest.fn(),
|
||||
softDelete: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
restore: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: PageLayoutService,
|
||||
useValue: {
|
||||
findByIdOrThrow: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -57,6 +91,7 @@ describe('PageLayoutTabService', () => {
|
||||
pageLayoutTabRepository = module.get<Repository<PageLayoutTabEntity>>(
|
||||
getRepositoryToken(PageLayoutTabEntity),
|
||||
);
|
||||
pageLayoutService = module.get<PageLayoutService>(PageLayoutService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
@@ -161,4 +196,349 @@ describe('PageLayoutTabService', () => {
|
||||
expect(result[0].widgets[1].id).toEqual('widget-2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByIdOrThrow', () => {
|
||||
it('should return page layout tab when found', async () => {
|
||||
const id = 'page-layout-tab-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'findOne')
|
||||
.mockResolvedValue(mockPageLayoutTab);
|
||||
|
||||
const result = await pageLayoutTabService.findByIdOrThrow(
|
||||
id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
expect(pageLayoutTabRepository.findOne).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
deletedAt: expect.anything(),
|
||||
},
|
||||
relations: ['widgets'],
|
||||
});
|
||||
expect(result).toEqual(mockPageLayoutTab);
|
||||
});
|
||||
|
||||
it('should throw exception when page layout tab is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest.spyOn(pageLayoutTabRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
pageLayoutTabService.findByIdOrThrow(id, workspaceId),
|
||||
).rejects.toThrow(PageLayoutTabException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new page layout tab successfully', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const pageLayoutTabData = {
|
||||
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);
|
||||
|
||||
const result = await pageLayoutTabService.create(
|
||||
pageLayoutTabData,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
expect(pageLayoutService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
pageLayoutTabData.pageLayoutId,
|
||||
workspaceId,
|
||||
);
|
||||
expect(pageLayoutTabRepository.create).toHaveBeenCalledWith({
|
||||
...pageLayoutTabData,
|
||||
workspaceId,
|
||||
});
|
||||
expect(pageLayoutTabRepository.save).toHaveBeenCalledWith(
|
||||
mockPageLayoutTab,
|
||||
);
|
||||
expect(result).toEqual(mockPageLayoutTab);
|
||||
});
|
||||
|
||||
it('should throw an exception when title is not provided', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const pageLayoutTabData = {
|
||||
pageLayoutId: 'page-layout-id',
|
||||
};
|
||||
|
||||
await expect(
|
||||
pageLayoutTabService.create(pageLayoutTabData, workspaceId),
|
||||
).rejects.toThrow(PageLayoutTabException);
|
||||
await expect(
|
||||
pageLayoutTabService.create(pageLayoutTabData, workspaceId),
|
||||
).rejects.toHaveProperty(
|
||||
'code',
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an exception when page layout does not exist', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const pageLayoutTabData = {
|
||||
title: 'New Tab',
|
||||
pageLayoutId: 'non-existent-page-layout-id',
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockRejectedValue(
|
||||
new PageLayoutException(
|
||||
'Page layout not found',
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
pageLayoutTabService.create(pageLayoutTabData, workspaceId),
|
||||
).rejects.toThrow(PageLayoutTabException);
|
||||
await expect(
|
||||
pageLayoutTabService.create(pageLayoutTabData, workspaceId),
|
||||
).rejects.toHaveProperty(
|
||||
'code',
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an exception when page layout is not found', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const pageLayoutTabData = {
|
||||
title: 'New Tab',
|
||||
pageLayoutId: 'non-existent-page-layout-id',
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockRejectedValue(new Error('Page layout not found'));
|
||||
|
||||
await expect(
|
||||
pageLayoutTabService.create(pageLayoutTabData, workspaceId),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update a page layout tab successfully', async () => {
|
||||
const id = 'page-layout-tab-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const updateData = { title: 'Updated Tab' };
|
||||
const updatedTab = { ...mockPageLayoutTab, title: 'Updated Tab' };
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'findOne')
|
||||
.mockResolvedValue(mockPageLayoutTab);
|
||||
|
||||
jest.spyOn(pageLayoutTabRepository, 'update').mockResolvedValue({
|
||||
affected: 1,
|
||||
generatedMaps: [],
|
||||
raw: {},
|
||||
});
|
||||
jest
|
||||
.spyOn(pageLayoutTabService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(updatedTab);
|
||||
|
||||
const result = await pageLayoutTabService.update(
|
||||
id,
|
||||
workspaceId,
|
||||
updateData,
|
||||
);
|
||||
|
||||
expect(pageLayoutTabRepository.update).toHaveBeenCalledWith(
|
||||
{ id },
|
||||
updateData,
|
||||
);
|
||||
expect(result).toEqual(updatedTab);
|
||||
});
|
||||
|
||||
it('should throw an exception when tab to update is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const updateData = { title: 'Updated Tab' };
|
||||
|
||||
jest.spyOn(pageLayoutTabRepository, 'update').mockResolvedValue({
|
||||
affected: 1,
|
||||
generatedMaps: [],
|
||||
raw: {},
|
||||
});
|
||||
jest
|
||||
.spyOn(pageLayoutTabService, 'findByIdOrThrow')
|
||||
.mockRejectedValue(
|
||||
new PageLayoutTabException(
|
||||
'Page layout tab not found',
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
pageLayoutTabService.update(id, workspaceId, updateData),
|
||||
).rejects.toThrow(PageLayoutTabException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should soft delete a page layout tab successfully', async () => {
|
||||
const id = 'page-layout-tab-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayoutTab);
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'softDelete')
|
||||
.mockResolvedValue({ affected: 1, generatedMaps: [], raw: {} });
|
||||
|
||||
const result = await pageLayoutTabService.delete(id, workspaceId);
|
||||
|
||||
expect(pageLayoutTabRepository.softDelete).toHaveBeenCalledWith(id);
|
||||
expect(result).toEqual(mockPageLayoutTab);
|
||||
});
|
||||
|
||||
it('should throw an exception when tab to delete is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabService, 'findByIdOrThrow')
|
||||
.mockRejectedValue(
|
||||
new PageLayoutTabException(
|
||||
'Page layout tab not found',
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
pageLayoutTabService.delete(id, workspaceId),
|
||||
).rejects.toThrow(PageLayoutTabException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy', () => {
|
||||
it('should permanently delete a page layout tab successfully', async () => {
|
||||
const id = 'page-layout-tab-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'findOne')
|
||||
.mockResolvedValue(mockPageLayoutTab);
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'delete')
|
||||
.mockResolvedValue({ affected: 1, generatedMaps: [], raw: {} });
|
||||
|
||||
const result = await pageLayoutTabService.destroy(id, workspaceId);
|
||||
|
||||
expect(pageLayoutTabRepository.findOne).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
expect(pageLayoutTabRepository.delete).toHaveBeenCalledWith(id);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw an exception when tab to destroy is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest.spyOn(pageLayoutTabRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
pageLayoutTabService.destroy(id, workspaceId),
|
||||
).rejects.toThrow(PageLayoutTabException);
|
||||
await expect(
|
||||
pageLayoutTabService.destroy(id, workspaceId),
|
||||
).rejects.toHaveProperty(
|
||||
'code',
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('restore', () => {
|
||||
it('should restore a deleted page layout tab successfully', async () => {
|
||||
const id = 'page-layout-tab-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const deletedTab = { ...mockPageLayoutTab, deletedAt: new Date() };
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'findOne')
|
||||
.mockResolvedValue(deletedTab);
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'restore')
|
||||
.mockResolvedValue({ affected: 1, generatedMaps: [], raw: {} });
|
||||
jest
|
||||
.spyOn(pageLayoutTabService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayoutTab);
|
||||
|
||||
const result = await pageLayoutTabService.restore(id, workspaceId);
|
||||
|
||||
expect(pageLayoutTabRepository.findOne).toHaveBeenCalledWith({
|
||||
select: {
|
||||
id: true,
|
||||
deletedAt: true,
|
||||
},
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
expect(pageLayoutTabRepository.restore).toHaveBeenCalledWith(id);
|
||||
expect(result).toEqual(mockPageLayoutTab);
|
||||
});
|
||||
|
||||
it('should throw an exception when tab to restore is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest.spyOn(pageLayoutTabRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
pageLayoutTabService.restore(id, workspaceId),
|
||||
).rejects.toThrow(PageLayoutTabException);
|
||||
await expect(
|
||||
pageLayoutTabService.restore(id, workspaceId),
|
||||
).rejects.toHaveProperty(
|
||||
'code',
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an exception when tab is not deleted', async () => {
|
||||
const id = 'page-layout-tab-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const notDeletedTab = { ...mockPageLayoutTab, deletedAt: null };
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'findOne')
|
||||
.mockResolvedValue(notDeletedTab);
|
||||
|
||||
await expect(
|
||||
pageLayoutTabService.restore(id, workspaceId),
|
||||
).rejects.toThrow(PageLayoutTabException);
|
||||
await expect(
|
||||
pageLayoutTabService.restore(id, workspaceId),
|
||||
).rejects.toHaveProperty(
|
||||
'code',
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+10
-7
@@ -42,6 +42,7 @@ describe('PageLayoutService', () => {
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
update: jest.fn(),
|
||||
softDelete: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
restore: jest.fn(),
|
||||
@@ -191,11 +192,9 @@ describe('PageLayoutService', () => {
|
||||
const updateData = { name: 'Updated Page Layout' };
|
||||
const updatedPageLayout = { ...mockPageLayout, ...updateData };
|
||||
|
||||
jest.spyOn(pageLayoutRepository, 'update').mockResolvedValue({} as any);
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayout);
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'save')
|
||||
.mockResolvedValue(updatedPageLayout);
|
||||
|
||||
const result = await pageLayoutService.update(
|
||||
@@ -204,14 +203,14 @@ describe('PageLayoutService', () => {
|
||||
updateData,
|
||||
);
|
||||
|
||||
expect(pageLayoutRepository.update).toHaveBeenCalledWith(
|
||||
{ id, workspaceId },
|
||||
updateData,
|
||||
);
|
||||
expect(pageLayoutService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
id,
|
||||
workspaceId,
|
||||
);
|
||||
expect(pageLayoutRepository.save).toHaveBeenCalledWith({
|
||||
...mockPageLayout,
|
||||
...updateData,
|
||||
});
|
||||
expect(result).toEqual(updatedPageLayout);
|
||||
});
|
||||
|
||||
@@ -364,6 +363,10 @@ describe('PageLayoutService', () => {
|
||||
const result = await pageLayoutService.restore(id, workspaceId);
|
||||
|
||||
expect(pageLayoutRepository.findOne).toHaveBeenCalledWith({
|
||||
select: {
|
||||
id: true,
|
||||
deletedAt: true,
|
||||
},
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
|
||||
+18
@@ -4,6 +4,10 @@ import {
|
||||
NotFoundError,
|
||||
UserInputError,
|
||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import {
|
||||
PageLayoutTabException,
|
||||
PageLayoutTabExceptionCode,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
import {
|
||||
PageLayoutException,
|
||||
PageLayoutExceptionCode,
|
||||
@@ -24,5 +28,19 @@ export const pageLayoutGraphqlApiExceptionHandler = (error: Error) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof PageLayoutTabException) {
|
||||
switch (error.code) {
|
||||
case PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
case PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA:
|
||||
throw new UserInputError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
default: {
|
||||
return assertUnreachable(error.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
|
||||
+6
-2
@@ -1,12 +1,16 @@
|
||||
import { ArgumentsHost, Catch } from '@nestjs/common';
|
||||
import { GqlExceptionFilter } from '@nestjs/graphql';
|
||||
|
||||
import { PageLayoutTabException } from 'src/engine/core-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
import { PageLayoutException } from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
import { pageLayoutGraphqlApiExceptionHandler } from 'src/engine/core-modules/page-layout/utils/page-layout-graphql-api-exception-handler.util';
|
||||
|
||||
@Catch(PageLayoutException)
|
||||
@Catch(PageLayoutException, PageLayoutTabException)
|
||||
export class PageLayoutGraphqlApiExceptionFilter implements GqlExceptionFilter {
|
||||
catch(exception: PageLayoutException, _host: ArgumentsHost) {
|
||||
catch(
|
||||
exception: PageLayoutException | PageLayoutTabException,
|
||||
_host: ArgumentsHost,
|
||||
) {
|
||||
return pageLayoutGraphqlApiExceptionHandler(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export const PAGE_LAYOUT_GQL_FIELDS = `
|
||||
|
||||
export const PAGE_LAYOUT_TAB_GQL_FIELDS = `
|
||||
id
|
||||
workspaceId
|
||||
title
|
||||
position
|
||||
pageLayoutId
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export const TEST_PAGE_LAYOUT_TAB_1_ID = '20202020-6837-414c-a15f-ed2cd033da81';
|
||||
export const TEST_PAGE_LAYOUT_TAB_2_ID = '20202020-e6fa-47af-95f0-07cce1901493';
|
||||
|
||||
export const TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID =
|
||||
'20202020-f22e-417f-ae78-7d74ea2652fe';
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
import { TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID } from 'test/integration/constants/test-page-layout-tab-ids.constants';
|
||||
import { createPageLayoutTabOperationFactory } from 'test/integration/graphql/utils/create-page-layout-tab-operation-factory.util';
|
||||
import { deletePageLayoutTabOperationFactory } from 'test/integration/graphql/utils/delete-page-layout-tab-operation-factory.util';
|
||||
import { destroyPageLayoutTabOperationFactory } from 'test/integration/graphql/utils/destroy-page-layout-tab-operation-factory.util';
|
||||
import { findPageLayoutTabOperationFactory } from 'test/integration/graphql/utils/find-page-layout-tab-operation-factory.util';
|
||||
import { findPageLayoutTabsOperationFactory } from 'test/integration/graphql/utils/find-page-layout-tabs-operation-factory.util';
|
||||
import {
|
||||
assertGraphQLErrorResponse,
|
||||
assertGraphQLSuccessfulResponse,
|
||||
} from 'test/integration/graphql/utils/graphql-test-assertions.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import {
|
||||
cleanupPageLayoutRecordsWithGraphQL,
|
||||
createTestPageLayoutWithGraphQL,
|
||||
} from 'test/integration/graphql/utils/page-layout-graphql.util';
|
||||
import {
|
||||
cleanupPageLayoutTabRecordsWithGraphQL,
|
||||
createTestPageLayoutTabWithGraphQL,
|
||||
} from 'test/integration/graphql/utils/page-layout-tab-graphql.util';
|
||||
import { restorePageLayoutTabOperationFactory } from 'test/integration/graphql/utils/restore-page-layout-tab-operation-factory.util';
|
||||
import { updatePageLayoutTabOperationFactory } from 'test/integration/graphql/utils/update-page-layout-tab-operation-factory.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { assertPageLayoutTabStructure } from 'test/integration/utils/page-layout-tab-test.util';
|
||||
|
||||
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
PageLayoutTabExceptionMessageKey,
|
||||
generatePageLayoutTabExceptionMessage,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
|
||||
describe('Page Layout Tab Resolver', () => {
|
||||
let testObjectMetadataId: string;
|
||||
let testPageLayoutId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestPageLayoutTabObject',
|
||||
namePlural: 'myTestPageLayoutTabObjects',
|
||||
labelSingular: 'My Test Page Layout Tab Object',
|
||||
labelPlural: 'My Test Page Layout Tab Objects',
|
||||
icon: 'IconTab',
|
||||
},
|
||||
});
|
||||
|
||||
testObjectMetadataId = objectMetadataId;
|
||||
|
||||
const pageLayout = await createTestPageLayoutWithGraphQL({
|
||||
name: 'Test Page Layout for Tabs',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testPageLayoutId = pageLayout.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupPageLayoutRecordsWithGraphQL();
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupPageLayoutTabRecordsWithGraphQL(testPageLayoutId);
|
||||
});
|
||||
|
||||
describe('getPageLayoutTabs', () => {
|
||||
it('should return empty array when no page layout tabs exist', async () => {
|
||||
const operation = findPageLayoutTabsOperationFactory({
|
||||
pageLayoutId: testPageLayoutId,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.getPageLayoutTabs).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return all page layout tabs for a specific page layout', async () => {
|
||||
const input1 = {
|
||||
title: 'Tab 1',
|
||||
position: 0,
|
||||
pageLayoutId: testPageLayoutId,
|
||||
};
|
||||
const input2 = {
|
||||
title: 'Tab 2',
|
||||
position: 1,
|
||||
pageLayoutId: testPageLayoutId,
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
createTestPageLayoutTabWithGraphQL(input1),
|
||||
createTestPageLayoutTabWithGraphQL(input2),
|
||||
]);
|
||||
|
||||
const operation = findPageLayoutTabsOperationFactory({
|
||||
pageLayoutId: testPageLayoutId,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.getPageLayoutTabs).toHaveLength(2);
|
||||
|
||||
const tabs = response.body.data.getPageLayoutTabs.sort(
|
||||
(a: { position: number }, b: { position: number }) =>
|
||||
a.position - b.position,
|
||||
);
|
||||
|
||||
assertPageLayoutTabStructure(tabs[0], input1);
|
||||
assertPageLayoutTabStructure(tabs[1], input2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPageLayoutTab', () => {
|
||||
it('should throw when page layout tab does not exist', async () => {
|
||||
const operation = findPageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return page layout tab when it exists', async () => {
|
||||
const tabTitle = 'Tab';
|
||||
|
||||
const input = {
|
||||
title: tabTitle,
|
||||
position: 2,
|
||||
pageLayoutId: testPageLayoutId,
|
||||
};
|
||||
|
||||
const tab = await createTestPageLayoutTabWithGraphQL(input);
|
||||
|
||||
const operation = findPageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: tab.id,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
assertPageLayoutTabStructure(response.body.data.getPageLayoutTab, input);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPageLayoutTab', () => {
|
||||
it('should create a new page layout tab with all properties', async () => {
|
||||
const input = {
|
||||
title: 'New Tab',
|
||||
position: 5,
|
||||
pageLayoutId: testPageLayoutId,
|
||||
};
|
||||
|
||||
const operation = createPageLayoutTabOperationFactory({ data: input });
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
|
||||
const createdTab = response.body.data.createPageLayoutTab;
|
||||
|
||||
assertPageLayoutTabStructure(createdTab, input);
|
||||
});
|
||||
|
||||
it('should create a page layout tab with minimum required fields', async () => {
|
||||
const input = {
|
||||
title: 'Minimal Tab',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
};
|
||||
|
||||
const operation = createPageLayoutTabOperationFactory({ data: input });
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
|
||||
const createdTab = response.body.data.createPageLayoutTab;
|
||||
|
||||
assertPageLayoutTabStructure(createdTab, {
|
||||
title: input.title,
|
||||
position: 0,
|
||||
pageLayoutId: input.pageLayoutId,
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePageLayoutTab', () => {
|
||||
it('should update an existing page layout tab', async () => {
|
||||
const input = {
|
||||
title: 'Original Tab',
|
||||
position: 1,
|
||||
pageLayoutId: testPageLayoutId,
|
||||
};
|
||||
|
||||
const tab = await createTestPageLayoutTabWithGraphQL(input);
|
||||
|
||||
const updateInput = {
|
||||
title: 'Updated Tab',
|
||||
position: 3,
|
||||
};
|
||||
|
||||
const operation = updatePageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: tab.id,
|
||||
data: updateInput,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
assertPageLayoutTabStructure(
|
||||
response.body.data.updatePageLayoutTab,
|
||||
updateInput,
|
||||
);
|
||||
});
|
||||
|
||||
it('should update only provided fields', async () => {
|
||||
const input = {
|
||||
title: 'Original Tab',
|
||||
position: 1,
|
||||
pageLayoutId: testPageLayoutId,
|
||||
};
|
||||
|
||||
const tab = await createTestPageLayoutTabWithGraphQL(input);
|
||||
|
||||
const updateInput = {
|
||||
title: 'Updated Title Only',
|
||||
};
|
||||
|
||||
const operation = updatePageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: tab.id,
|
||||
data: updateInput,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
assertPageLayoutTabStructure(response.body.data.updatePageLayoutTab, {
|
||||
...input,
|
||||
...updateInput,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when updating non-existent page layout tab', async () => {
|
||||
const operation = updatePageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
data: { title: 'Non-existent Tab' },
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deletePageLayoutTab', () => {
|
||||
it('should delete an existing page layout tab (soft delete)', async () => {
|
||||
const tab = await createTestPageLayoutTabWithGraphQL({
|
||||
title: 'Tab to Delete',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
});
|
||||
|
||||
const deleteOperation = deletePageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: tab.id,
|
||||
});
|
||||
const deleteResponse = await makeGraphqlAPIRequest(deleteOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(deleteResponse);
|
||||
expect(deleteResponse.body.data.deletePageLayoutTab).toBe(true);
|
||||
|
||||
const getOperation = findPageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: tab.id,
|
||||
});
|
||||
const getResponse = await makeGraphqlAPIRequest(getOperation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
getResponse,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
tab.id,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error when deleting non-existent page layout tab', async () => {
|
||||
const operation = deletePageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroyPageLayoutTab', () => {
|
||||
it('should destroy an existing page layout tab (hard delete)', async () => {
|
||||
const tab = await createTestPageLayoutTabWithGraphQL({
|
||||
title: 'Tab to Destroy',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
});
|
||||
|
||||
const destroyOperation = destroyPageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: tab.id,
|
||||
});
|
||||
const destroyResponse = await makeGraphqlAPIRequest(destroyOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(destroyResponse);
|
||||
expect(destroyResponse.body.data.destroyPageLayoutTab).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw an error when destroying non-existent page layout tab', async () => {
|
||||
const operation = destroyPageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('restorePageLayoutTab', () => {
|
||||
it('should restore a soft deleted page layout tab', async () => {
|
||||
const input = {
|
||||
title: 'Tab to Restore',
|
||||
position: 2,
|
||||
pageLayoutId: testPageLayoutId,
|
||||
};
|
||||
|
||||
const tab = await createTestPageLayoutTabWithGraphQL(input);
|
||||
|
||||
const deleteOperation = deletePageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: tab.id,
|
||||
});
|
||||
const deleteResponse = await makeGraphqlAPIRequest(deleteOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(deleteResponse);
|
||||
|
||||
const restoreOperation = restorePageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: tab.id,
|
||||
});
|
||||
const restoreResponse = await makeGraphqlAPIRequest(restoreOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(restoreResponse);
|
||||
assertPageLayoutTabStructure(
|
||||
restoreResponse.body.data.restorePageLayoutTab,
|
||||
input,
|
||||
);
|
||||
|
||||
const getOperation = findPageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: tab.id,
|
||||
});
|
||||
const getResponse = await makeGraphqlAPIRequest(getOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(getResponse);
|
||||
assertPageLayoutTabStructure(
|
||||
getResponse.body.data.getPageLayoutTab,
|
||||
input,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error when restoring non-existent page layout tab', async () => {
|
||||
const operation = restorePageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+3
-3
@@ -5,13 +5,13 @@ import { type CreatePageLayoutInput } from 'src/engine/core-modules/page-layout/
|
||||
|
||||
type CreatePageLayoutOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
data?: CreatePageLayoutInput;
|
||||
data: CreatePageLayoutInput;
|
||||
};
|
||||
|
||||
export const createPageLayoutOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_GQL_FIELDS,
|
||||
data = {} as CreatePageLayoutInput,
|
||||
}: CreatePageLayoutOperationFactoryParams = {}) => ({
|
||||
data,
|
||||
}: CreatePageLayoutOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation CreatePageLayout($input: CreatePageLayoutInput!) {
|
||||
createPageLayout(input: $input) {
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_TAB_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
import { type CreatePageLayoutTabInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout-tab.input';
|
||||
|
||||
type CreatePageLayoutTabOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
data: CreatePageLayoutTabInput;
|
||||
};
|
||||
|
||||
export const createPageLayoutTabOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_TAB_GQL_FIELDS,
|
||||
data,
|
||||
}: CreatePageLayoutTabOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation CreatePageLayoutTab($input: CreatePageLayoutTabInput!) {
|
||||
createPageLayoutTab(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: data,
|
||||
},
|
||||
});
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
type DeletePageLayoutTabOperationFactoryParams = {
|
||||
pageLayoutTabId: string;
|
||||
};
|
||||
|
||||
export const deletePageLayoutTabOperationFactory = ({
|
||||
pageLayoutTabId,
|
||||
}: DeletePageLayoutTabOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation DeletePageLayoutTab($id: String!) {
|
||||
deletePageLayoutTab(id: $id)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutTabId,
|
||||
},
|
||||
});
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
type DestroyPageLayoutTabOperationFactoryParams = {
|
||||
pageLayoutTabId: string;
|
||||
};
|
||||
|
||||
export const destroyPageLayoutTabOperationFactory = ({
|
||||
pageLayoutTabId,
|
||||
}: DestroyPageLayoutTabOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation DestroyPageLayoutTab($id: String!) {
|
||||
destroyPageLayoutTab(id: $id)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutTabId,
|
||||
},
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_TAB_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
type FindPageLayoutTabOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
pageLayoutTabId: string;
|
||||
};
|
||||
|
||||
export const findPageLayoutTabOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_TAB_GQL_FIELDS,
|
||||
pageLayoutTabId,
|
||||
}: FindPageLayoutTabOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
query GetPageLayoutTab($id: String!) {
|
||||
getPageLayoutTab(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutTabId,
|
||||
},
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_TAB_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
type FindPageLayoutTabsOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
pageLayoutId: string;
|
||||
};
|
||||
|
||||
export const findPageLayoutTabsOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_TAB_GQL_FIELDS,
|
||||
pageLayoutId,
|
||||
}: FindPageLayoutTabsOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
query GetPageLayoutTabs($pageLayoutId: String!) {
|
||||
getPageLayoutTabs(pageLayoutId: $pageLayoutId) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
pageLayoutId,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { type GraphQLResponse } from 'test/integration/graphql/utils/graphql-test-assertions.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
|
||||
import { type PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity';
|
||||
|
||||
import { createPageLayoutTabOperationFactory } from './create-page-layout-tab-operation-factory.util';
|
||||
import { destroyPageLayoutTabOperationFactory } from './destroy-page-layout-tab-operation-factory.util';
|
||||
import { findPageLayoutTabsOperationFactory } from './find-page-layout-tabs-operation-factory.util';
|
||||
|
||||
interface CreatePageLayoutTabResponse extends Record<string, unknown> {
|
||||
createPageLayoutTab: PageLayoutTabEntity;
|
||||
}
|
||||
|
||||
export const createTestPageLayoutTabWithGraphQL = async (data: {
|
||||
title: string;
|
||||
position?: number;
|
||||
pageLayoutId: string;
|
||||
}): Promise<PageLayoutTabEntity> => {
|
||||
const operation = createPageLayoutTabOperationFactory({
|
||||
data: {
|
||||
title: data.title,
|
||||
position: data.position || 0,
|
||||
pageLayoutId: data.pageLayoutId,
|
||||
},
|
||||
});
|
||||
|
||||
const response = (await makeGraphqlAPIRequest(
|
||||
operation,
|
||||
)) as GraphQLResponse<CreatePageLayoutTabResponse>;
|
||||
|
||||
if (response.body.errors) {
|
||||
throw new Error(
|
||||
`Failed to create test page layout tab: ${JSON.stringify(response.body.errors)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.body.data) {
|
||||
throw new Error('No data returned from createTestPageLayoutTabWithGraphQL');
|
||||
}
|
||||
|
||||
return response.body.data.createPageLayoutTab;
|
||||
};
|
||||
|
||||
export const cleanupPageLayoutTabRecordsWithGraphQL = async (
|
||||
pageLayoutId: string,
|
||||
): Promise<void> => {
|
||||
const operation = findPageLayoutTabsOperationFactory({ pageLayoutId });
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
if (response.body.data?.getPageLayoutTabs) {
|
||||
for (const pageLayoutTab of response.body.data.getPageLayoutTabs) {
|
||||
const destroyOperation = destroyPageLayoutTabOperationFactory({
|
||||
pageLayoutTabId: pageLayoutTab.id,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(destroyOperation);
|
||||
}
|
||||
}
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_TAB_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
type RestorePageLayoutTabOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
pageLayoutTabId: string;
|
||||
};
|
||||
|
||||
export const restorePageLayoutTabOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_TAB_GQL_FIELDS,
|
||||
pageLayoutTabId,
|
||||
}: RestorePageLayoutTabOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation RestorePageLayoutTab($id: String!) {
|
||||
restorePageLayoutTab(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutTabId,
|
||||
},
|
||||
});
|
||||
+2
-2
@@ -6,13 +6,13 @@ import { type UpdatePageLayoutInput } from 'src/engine/core-modules/page-layout/
|
||||
type UpdatePageLayoutOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
pageLayoutId: string;
|
||||
data?: UpdatePageLayoutInput;
|
||||
data: UpdatePageLayoutInput;
|
||||
};
|
||||
|
||||
export const updatePageLayoutOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_GQL_FIELDS,
|
||||
pageLayoutId,
|
||||
data = {} as UpdatePageLayoutInput,
|
||||
data,
|
||||
}: UpdatePageLayoutOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation UpdatePageLayout($id: String!, $input: UpdatePageLayoutInput!) {
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_TAB_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
import { type UpdatePageLayoutTabInput } from 'src/engine/core-modules/page-layout/dtos/inputs/update-page-layout-tab.input';
|
||||
|
||||
type UpdatePageLayoutTabOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
pageLayoutTabId: string;
|
||||
data: UpdatePageLayoutTabInput;
|
||||
};
|
||||
|
||||
export const updatePageLayoutTabOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_TAB_GQL_FIELDS,
|
||||
pageLayoutTabId,
|
||||
data,
|
||||
}: UpdatePageLayoutTabOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation UpdatePageLayoutTab($id: String!, $input: UpdatePageLayoutTabInput!) {
|
||||
updatePageLayoutTab(id: $id, input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutTabId,
|
||||
input: data,
|
||||
},
|
||||
});
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
import { TEST_NOT_EXISTING_PAGE_LAYOUT_ID } from 'test/integration/constants/test-page-layout-ids.constants';
|
||||
import { TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID } from 'test/integration/constants/test-page-layout-tab-ids.constants';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
|
||||
import {
|
||||
createTestPageLayoutWithRestApi,
|
||||
deleteTestPageLayoutWithRestApi,
|
||||
} from 'test/integration/rest/utils/page-layout-rest-api.util';
|
||||
import {
|
||||
createTestPageLayoutTabWithRestApi,
|
||||
deleteTestPageLayoutTabWithRestApi,
|
||||
} from 'test/integration/rest/utils/page-layout-tab-rest-api.util';
|
||||
import {
|
||||
assertRestApiErrorResponse,
|
||||
assertRestApiSuccessfulResponse,
|
||||
} from 'test/integration/rest/utils/rest-test-assertions.util';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
import {
|
||||
assertPageLayoutTabStructure,
|
||||
cleanupPageLayoutTabRecords,
|
||||
} from 'test/integration/utils/page-layout-tab-test.util';
|
||||
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
PageLayoutTabExceptionMessageKey,
|
||||
generatePageLayoutTabExceptionMessage,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
|
||||
describe('Page Layout Tab REST API', () => {
|
||||
let testObjectMetadataId: string;
|
||||
let testPageLayoutId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestPageLayoutTabObject',
|
||||
namePlural: 'myTestPageLayoutTabObjects',
|
||||
labelSingular: 'My Test Page Layout Tab Object',
|
||||
labelPlural: 'My Test Page Layout Tab Objects',
|
||||
icon: 'IconLayout',
|
||||
},
|
||||
});
|
||||
|
||||
testObjectMetadataId = objectMetadataId;
|
||||
|
||||
const testPageLayout = await createTestPageLayoutWithRestApi({
|
||||
name: generateRecordName('Test Page Layout for Tabs'),
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
testPageLayoutId = testPageLayout.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteTestPageLayoutWithRestApi(testPageLayoutId);
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupPageLayoutTabRecords();
|
||||
});
|
||||
|
||||
describe('GET /rest/metadata/page-layout-tabs', () => {
|
||||
it('should return page layout tabs filtered by pageLayoutId', async () => {
|
||||
const input1 = {
|
||||
title: 'Tab 1',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
position: 0,
|
||||
};
|
||||
const input2 = {
|
||||
title: 'Tab 2',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
position: 1,
|
||||
};
|
||||
|
||||
await createTestPageLayoutTabWithRestApi(input1);
|
||||
await createTestPageLayoutTabWithRestApi(input2);
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layout-tabs?pageLayoutId=${testPageLayoutId}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
|
||||
if (response.body.length > 0) {
|
||||
assertPageLayoutTabStructure(response.body[0], input1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return empty array when no page layout tabs match pageLayoutId', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layout-tabs?pageLayoutId=${TEST_NOT_EXISTING_PAGE_LAYOUT_ID}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
expect(response.body).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should return error when pageLayoutId is missing', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: '/metadata/page-layout-tabs',
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
response,
|
||||
400,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_ID_REQUIRED,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /rest/metadata/page-layout-tabs', () => {
|
||||
it('should create a new page layout tab with all properties', async () => {
|
||||
const input = {
|
||||
title: 'Test Tab',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
position: 1,
|
||||
};
|
||||
|
||||
const pageLayoutTab = await createTestPageLayoutTabWithRestApi(input);
|
||||
|
||||
assertPageLayoutTabStructure(pageLayoutTab, input);
|
||||
|
||||
await deleteTestPageLayoutTabWithRestApi(pageLayoutTab.id);
|
||||
});
|
||||
|
||||
it('should create a page layout tab with minimum required fields', async () => {
|
||||
const input = {
|
||||
title: 'Minimal Tab',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
};
|
||||
|
||||
const pageLayoutTab = await createTestPageLayoutTabWithRestApi({
|
||||
title: input.title,
|
||||
pageLayoutId: input.pageLayoutId,
|
||||
});
|
||||
|
||||
assertPageLayoutTabStructure(pageLayoutTab, {
|
||||
...input,
|
||||
position: 0,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutTabWithRestApi(pageLayoutTab.id);
|
||||
});
|
||||
|
||||
it('should return error when creating tab with invalid pageLayoutId', async () => {
|
||||
const pageLayoutTabData = {
|
||||
title: 'Invalid Tab',
|
||||
pageLayoutId: TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'post',
|
||||
path: '/metadata/page-layout-tabs',
|
||||
body: pageLayoutTabData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
response,
|
||||
400,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when creating tab without title', async () => {
|
||||
const pageLayoutTabData = {
|
||||
pageLayoutId: testPageLayoutId,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'post',
|
||||
path: '/metadata/page-layout-tabs',
|
||||
body: pageLayoutTabData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(response, 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /rest/metadata/page-layout-tabs/:id', () => {
|
||||
it('should return a page layout tab by id', async () => {
|
||||
const input = {
|
||||
title: 'Tab',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
position: 2,
|
||||
};
|
||||
const pageLayoutTab = await createTestPageLayoutTabWithRestApi(input);
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layout-tabs/${pageLayoutTab.id}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
assertPageLayoutTabStructure(response.body, {
|
||||
id: pageLayoutTab.id,
|
||||
...input,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutTabWithRestApi(pageLayoutTab.id);
|
||||
});
|
||||
|
||||
it('should return error for non-existent page layout tab', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layout-tabs/${TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
response,
|
||||
404,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /rest/metadata/page-layout-tabs/:id', () => {
|
||||
it('should update an existing page layout tab', async () => {
|
||||
const input = {
|
||||
title: 'Test Tab for Update',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
position: 0,
|
||||
};
|
||||
const pageLayoutTab = await createTestPageLayoutTabWithRestApi(input);
|
||||
|
||||
const updateData = {
|
||||
title: 'Updated Tab',
|
||||
position: 3,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/page-layout-tabs/${pageLayoutTab.id}`,
|
||||
body: updateData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
assertPageLayoutTabStructure(response.body, {
|
||||
id: pageLayoutTab.id,
|
||||
...input,
|
||||
...updateData,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutTabWithRestApi(pageLayoutTab.id);
|
||||
});
|
||||
|
||||
it('should update only provided fields', async () => {
|
||||
const input = {
|
||||
title: 'Original Tab',
|
||||
pageLayoutId: testPageLayoutId,
|
||||
position: 1,
|
||||
};
|
||||
const pageLayoutTab = await createTestPageLayoutTabWithRestApi(input);
|
||||
|
||||
const updatedTitle = 'Updated Title Only';
|
||||
const updateData = {
|
||||
title: updatedTitle,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/page-layout-tabs/${pageLayoutTab.id}`,
|
||||
body: updateData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
assertPageLayoutTabStructure(response.body, {
|
||||
id: pageLayoutTab.id,
|
||||
title: updatedTitle,
|
||||
pageLayoutId: testPageLayoutId,
|
||||
position: 1,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutTabWithRestApi(pageLayoutTab.id);
|
||||
});
|
||||
|
||||
it('should return error when updating non-existent page layout tab', async () => {
|
||||
const updateData = {
|
||||
title: 'Updated Tab',
|
||||
position: 5,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/page-layout-tabs/${TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID}`,
|
||||
body: updateData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
response,
|
||||
404,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /rest/metadata/page-layout-tabs/:id', () => {
|
||||
it('should delete an existing page layout tab', async () => {
|
||||
const pageLayoutTabTitle = generateRecordName('Test Tab for Delete');
|
||||
const pageLayoutTab = await createTestPageLayoutTabWithRestApi({
|
||||
title: pageLayoutTabTitle,
|
||||
pageLayoutId: testPageLayoutId,
|
||||
position: 0,
|
||||
});
|
||||
|
||||
const deleteResponse = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/page-layout-tabs/${pageLayoutTab.id}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(deleteResponse);
|
||||
assertPageLayoutTabStructure(deleteResponse.body, pageLayoutTab);
|
||||
|
||||
const getResponse = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layout-tabs/${pageLayoutTab.id}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
getResponse,
|
||||
404,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
pageLayoutTab.id,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when deleting non-existent page layout tab', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/page-layout-tabs/${TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
response,
|
||||
404,
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_TAB_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
|
||||
import { type PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity';
|
||||
|
||||
export const createTestPageLayoutTabWithRestApi = async (
|
||||
overrides: Partial<PageLayoutTabEntity> = {},
|
||||
): Promise<PageLayoutTabEntity> => {
|
||||
const pageLayoutTabData = {
|
||||
title: generateRecordName('Test Page Layout Tab'),
|
||||
position: 0,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'post',
|
||||
path: '/metadata/page-layout-tabs',
|
||||
body: pageLayoutTabData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
if (response.status !== 201) {
|
||||
throw new Error(
|
||||
`Failed to create test page layout tab: ${response.status} - ${JSON.stringify(response.body)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
};
|
||||
|
||||
export const deleteTestPageLayoutTabWithRestApi = async (
|
||||
pageLayoutTabId: string,
|
||||
): Promise<void> => {
|
||||
await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/page-layout-tabs/${pageLayoutTabId}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { type PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity';
|
||||
|
||||
export const cleanupPageLayoutTabRecords = async (): Promise<void> => {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
await global.testDataSource.query(`DELETE from "core"."pageLayoutTab"`);
|
||||
};
|
||||
|
||||
export const assertPageLayoutTabStructure = (
|
||||
pageLayoutTab: PageLayoutTabEntity,
|
||||
expectedFields?: Partial<PageLayoutTabEntity>,
|
||||
) => {
|
||||
expect(pageLayoutTab).toBeDefined();
|
||||
expect(pageLayoutTab.id).toEqual(expect.any(String));
|
||||
expect(pageLayoutTab.title).toEqual(expect.any(String));
|
||||
expect(pageLayoutTab.position).toEqual(expect.any(Number));
|
||||
expect(pageLayoutTab.pageLayoutId).toEqual(expect.any(String));
|
||||
expect(pageLayoutTab.createdAt).toEqual(expect.any(String));
|
||||
expect(pageLayoutTab.updatedAt).toEqual(expect.any(String));
|
||||
|
||||
if (expectedFields) {
|
||||
expect(pageLayoutTab).toMatchObject(expectedFields);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user