Create PageLayout resolver and controller (#14219)
Closes https://github.com/twentyhq/core-team-issues/issues/1393
This commit is contained in:
@@ -695,6 +695,12 @@ export type CreateOneObjectInput = {
|
||||
object: CreateObjectInput;
|
||||
};
|
||||
|
||||
export type CreatePageLayoutInput = {
|
||||
name: Scalars['String'];
|
||||
objectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
type?: InputMaybe<PageLayoutType>;
|
||||
};
|
||||
|
||||
export type CreateRemoteServerInput = {
|
||||
foreignDataWrapperOptions: Scalars['JSON'];
|
||||
foreignDataWrapperType: Scalars['String'];
|
||||
@@ -1371,6 +1377,7 @@ export type Mutation = {
|
||||
createOneRemoteServer: RemoteServer;
|
||||
createOneRole: Role;
|
||||
createOneServerlessFunction: ServerlessFunction;
|
||||
createPageLayout: PageLayout;
|
||||
createSAMLIdentityProvider: SetupSsoOutput;
|
||||
createWebhook: Webhook;
|
||||
createWorkflowVersionEdge: WorkflowVersionStepChanges;
|
||||
@@ -1392,6 +1399,7 @@ export type Mutation = {
|
||||
deleteOneRemoteServer: RemoteServer;
|
||||
deleteOneRole: Scalars['String'];
|
||||
deleteOneServerlessFunction: ServerlessFunction;
|
||||
deletePageLayout: PageLayout;
|
||||
deleteSSOIdentityProvider: DeleteSsoOutput;
|
||||
deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput;
|
||||
deleteUser: User;
|
||||
@@ -1405,6 +1413,7 @@ export type Mutation = {
|
||||
destroyCoreViewFilterGroup: Scalars['Boolean'];
|
||||
destroyCoreViewGroup: Scalars['Boolean'];
|
||||
destroyCoreViewSort: Scalars['Boolean'];
|
||||
destroyPageLayout: Scalars['Boolean'];
|
||||
disablePostgresProxy: PostgresCredentials;
|
||||
editSSOIdentityProvider: EditSsoOutput;
|
||||
emailPasswordResetLink: EmailPasswordResetLink;
|
||||
@@ -1428,6 +1437,7 @@ export type Mutation = {
|
||||
renewToken: AuthTokens;
|
||||
resendEmailVerificationToken: ResendEmailVerificationTokenOutput;
|
||||
resendWorkspaceInvitation: SendInvitationsOutput;
|
||||
restorePageLayout: PageLayout;
|
||||
revokeApiKey?: Maybe<ApiKey>;
|
||||
runWorkflowVersion: WorkflowRun;
|
||||
saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess;
|
||||
@@ -1460,6 +1470,7 @@ export type Mutation = {
|
||||
updateOneRemoteServer: RemoteServer;
|
||||
updateOneRole: Role;
|
||||
updateOneServerlessFunction: ServerlessFunction;
|
||||
updatePageLayout: PageLayout;
|
||||
updatePasswordViaResetToken: InvalidatePassword;
|
||||
updateSubscriptionItemPrice: BillingUpdateOutput;
|
||||
updateWebhook?: Maybe<Webhook>;
|
||||
@@ -1638,6 +1649,11 @@ export type MutationCreateOneServerlessFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreatePageLayoutArgs = {
|
||||
input: CreatePageLayoutInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateSamlIdentityProviderArgs = {
|
||||
input: SetupSamlSsoInput;
|
||||
};
|
||||
@@ -1738,6 +1754,11 @@ export type MutationDeleteOneServerlessFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeletePageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteSsoIdentityProviderArgs = {
|
||||
input: DeleteSsoInput;
|
||||
};
|
||||
@@ -1798,6 +1819,11 @@ export type MutationDestroyCoreViewSortArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDestroyPageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationEditSsoIdentityProviderArgs = {
|
||||
input: EditSsoInput;
|
||||
};
|
||||
@@ -1907,6 +1933,11 @@ export type MutationResendWorkspaceInvitationArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationRestorePageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationRevokeApiKeyArgs = {
|
||||
input: RevokeApiKeyDto;
|
||||
};
|
||||
@@ -2070,6 +2101,12 @@ export type MutationUpdateOneServerlessFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdatePageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
input: UpdatePageLayoutInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdatePasswordViaResetTokenArgs = {
|
||||
newPassword: Scalars['String'];
|
||||
passwordResetToken: Scalars['String'];
|
||||
@@ -2336,6 +2373,36 @@ export type PageInfo = {
|
||||
startCursor?: Maybe<Scalars['ConnectionCursor']>;
|
||||
};
|
||||
|
||||
export type PageLayout = {
|
||||
__typename?: 'PageLayout';
|
||||
createdAt: Scalars['DateTime'];
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['UUID'];
|
||||
name: Scalars['String'];
|
||||
objectMetadataId?: Maybe<Scalars['UUID']>;
|
||||
tabs?: Maybe<Array<PageLayoutTab>>;
|
||||
type: PageLayoutType;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workspaceId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type PageLayoutTab = {
|
||||
__typename?: 'PageLayoutTab';
|
||||
createdAt: Scalars['DateTime'];
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['UUID'];
|
||||
pageLayoutId: Scalars['UUID'];
|
||||
position: Scalars['Float'];
|
||||
title: Scalars['String'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export enum PageLayoutType {
|
||||
DASHBOARD = 'DASHBOARD',
|
||||
RECORD_INDEX = 'RECORD_INDEX',
|
||||
RECORD_PAGE = 'RECORD_PAGE'
|
||||
}
|
||||
|
||||
export type PermissionFlag = {
|
||||
__typename?: 'PermissionFlag';
|
||||
flag: PermissionFlagType;
|
||||
@@ -2454,6 +2521,8 @@ export type Query = {
|
||||
getDatabaseConfigVariable: ConfigVariable;
|
||||
getIndicatorHealthStatus: AdminPanelHealthServiceData;
|
||||
getMeteredProductsUsage: Array<BillingMeteredProductUsageOutput>;
|
||||
getPageLayout?: Maybe<PageLayout>;
|
||||
getPageLayouts: Array<PageLayout>;
|
||||
getPostgresCredentials?: Maybe<PostgresCredentials>;
|
||||
getPublicWorkspaceDataByDomain: PublicWorkspaceDataOutput;
|
||||
getQueueMetrics: QueueMetricsData;
|
||||
@@ -2662,6 +2731,16 @@ export type QueryGetIndicatorHealthStatusArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutsArgs = {
|
||||
objectMetadataId?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPublicWorkspaceDataByDomainArgs = {
|
||||
origin?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
@@ -3273,6 +3352,12 @@ export type UpdateOneObjectInput = {
|
||||
update: UpdateObjectPayload;
|
||||
};
|
||||
|
||||
export type UpdatePageLayoutInput = {
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
objectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
type?: InputMaybe<PageLayoutType>;
|
||||
};
|
||||
|
||||
export type UpdateRemoteServerInput = {
|
||||
foreignDataWrapperOptions?: InputMaybe<Scalars['JSON']>;
|
||||
id: Scalars['UUID'];
|
||||
|
||||
@@ -667,6 +667,12 @@ export type CreateOneFieldMetadataInput = {
|
||||
field: CreateFieldInput;
|
||||
};
|
||||
|
||||
export type CreatePageLayoutInput = {
|
||||
name: Scalars['String'];
|
||||
objectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
type?: InputMaybe<PageLayoutType>;
|
||||
};
|
||||
|
||||
export type CreateRoleInput = {
|
||||
canAccessAllTools?: InputMaybe<Scalars['Boolean']>;
|
||||
canDestroyAllObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
@@ -1327,6 +1333,7 @@ export type Mutation = {
|
||||
createOneObject: Object;
|
||||
createOneRole: Role;
|
||||
createOneServerlessFunction: ServerlessFunction;
|
||||
createPageLayout: PageLayout;
|
||||
createSAMLIdentityProvider: SetupSsoOutput;
|
||||
createWebhook: Webhook;
|
||||
createWorkflowVersionEdge: WorkflowVersionStepChanges;
|
||||
@@ -1347,6 +1354,7 @@ export type Mutation = {
|
||||
deleteOneObject: Object;
|
||||
deleteOneRole: Scalars['String'];
|
||||
deleteOneServerlessFunction: ServerlessFunction;
|
||||
deletePageLayout: PageLayout;
|
||||
deleteSSOIdentityProvider: DeleteSsoOutput;
|
||||
deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput;
|
||||
deleteUser: User;
|
||||
@@ -1360,6 +1368,7 @@ export type Mutation = {
|
||||
destroyCoreViewFilterGroup: Scalars['Boolean'];
|
||||
destroyCoreViewGroup: Scalars['Boolean'];
|
||||
destroyCoreViewSort: Scalars['Boolean'];
|
||||
destroyPageLayout: Scalars['Boolean'];
|
||||
disablePostgresProxy: PostgresCredentials;
|
||||
editSSOIdentityProvider: EditSsoOutput;
|
||||
emailPasswordResetLink: EmailPasswordResetLink;
|
||||
@@ -1383,6 +1392,7 @@ export type Mutation = {
|
||||
renewToken: AuthTokens;
|
||||
resendEmailVerificationToken: ResendEmailVerificationTokenOutput;
|
||||
resendWorkspaceInvitation: SendInvitationsOutput;
|
||||
restorePageLayout: PageLayout;
|
||||
revokeApiKey?: Maybe<ApiKey>;
|
||||
runWorkflowVersion: WorkflowRun;
|
||||
saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess;
|
||||
@@ -1411,6 +1421,7 @@ export type Mutation = {
|
||||
updateOneObject: Object;
|
||||
updateOneRole: Role;
|
||||
updateOneServerlessFunction: ServerlessFunction;
|
||||
updatePageLayout: PageLayout;
|
||||
updatePasswordViaResetToken: InvalidatePassword;
|
||||
updateSubscriptionItemPrice: BillingUpdateOutput;
|
||||
updateWebhook?: Maybe<Webhook>;
|
||||
@@ -1574,6 +1585,11 @@ export type MutationCreateOneServerlessFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreatePageLayoutArgs = {
|
||||
input: CreatePageLayoutInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateSamlIdentityProviderArgs = {
|
||||
input: SetupSamlSsoInput;
|
||||
};
|
||||
@@ -1669,6 +1685,11 @@ export type MutationDeleteOneServerlessFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeletePageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteSsoIdentityProviderArgs = {
|
||||
input: DeleteSsoInput;
|
||||
};
|
||||
@@ -1729,6 +1750,11 @@ export type MutationDestroyCoreViewSortArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDestroyPageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationEditSsoIdentityProviderArgs = {
|
||||
input: EditSsoInput;
|
||||
};
|
||||
@@ -1838,6 +1864,11 @@ export type MutationResendWorkspaceInvitationArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationRestorePageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationRevokeApiKeyArgs = {
|
||||
input: RevokeApiKeyDto;
|
||||
};
|
||||
@@ -1981,6 +2012,12 @@ export type MutationUpdateOneServerlessFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdatePageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
input: UpdatePageLayoutInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdatePasswordViaResetTokenArgs = {
|
||||
newPassword: Scalars['String'];
|
||||
passwordResetToken: Scalars['String'];
|
||||
@@ -2247,6 +2284,36 @@ export type PageInfo = {
|
||||
startCursor?: Maybe<Scalars['ConnectionCursor']>;
|
||||
};
|
||||
|
||||
export type PageLayout = {
|
||||
__typename?: 'PageLayout';
|
||||
createdAt: Scalars['DateTime'];
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['UUID'];
|
||||
name: Scalars['String'];
|
||||
objectMetadataId?: Maybe<Scalars['UUID']>;
|
||||
tabs?: Maybe<Array<PageLayoutTab>>;
|
||||
type: PageLayoutType;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workspaceId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type PageLayoutTab = {
|
||||
__typename?: 'PageLayoutTab';
|
||||
createdAt: Scalars['DateTime'];
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
id: Scalars['UUID'];
|
||||
pageLayoutId: Scalars['UUID'];
|
||||
position: Scalars['Float'];
|
||||
title: Scalars['String'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export enum PageLayoutType {
|
||||
DASHBOARD = 'DASHBOARD',
|
||||
RECORD_INDEX = 'RECORD_INDEX',
|
||||
RECORD_PAGE = 'RECORD_PAGE'
|
||||
}
|
||||
|
||||
export type PermissionFlag = {
|
||||
__typename?: 'PermissionFlag';
|
||||
flag: PermissionFlagType;
|
||||
@@ -2362,6 +2429,8 @@ export type Query = {
|
||||
getDatabaseConfigVariable: ConfigVariable;
|
||||
getIndicatorHealthStatus: AdminPanelHealthServiceData;
|
||||
getMeteredProductsUsage: Array<BillingMeteredProductUsageOutput>;
|
||||
getPageLayout?: Maybe<PageLayout>;
|
||||
getPageLayouts: Array<PageLayout>;
|
||||
getPostgresCredentials?: Maybe<PostgresCredentials>;
|
||||
getPublicWorkspaceDataByDomain: PublicWorkspaceDataOutput;
|
||||
getQueueMetrics: QueueMetricsData;
|
||||
@@ -2544,6 +2613,16 @@ export type QueryGetIndicatorHealthStatusArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPageLayoutsArgs = {
|
||||
objectMetadataId?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetPublicWorkspaceDataByDomainArgs = {
|
||||
origin?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
@@ -3119,6 +3198,12 @@ export type UpdateOneObjectInput = {
|
||||
update: UpdateObjectPayload;
|
||||
};
|
||||
|
||||
export type UpdatePageLayoutInput = {
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
objectMetadataId?: InputMaybe<Scalars['UUID']>;
|
||||
type?: InputMaybe<PageLayoutType>;
|
||||
};
|
||||
|
||||
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 CreatePartialIndexForPageLayout1756816245841
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'CreatePartialIndexForPageLayout1756816245841';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_PAGE_LAYOUT_WORKSPACE_ID_OBJECT_METADATA_ID"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_PAGE_LAYOUT_WORKSPACE_ID_OBJECT_METADATA_ID" ON "core"."pageLayout" ("workspaceId", "objectMetadataId") WHERE "deletedAt" IS NULL`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_PAGE_LAYOUT_WORKSPACE_ID_OBJECT_METADATA_ID"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_PAGE_LAYOUT_WORKSPACE_ID_OBJECT_METADATA_ID" ON "core"."pageLayout" ("workspaceId", "objectMetadataId") `,
|
||||
);
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UseFilters,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
|
||||
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';
|
||||
import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import { PageLayoutRestApiExceptionFilter } from 'src/engine/core-modules/page-layout/filters/page-layout-rest-api-exception.filter';
|
||||
import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/page-layout.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-layouts')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(PageLayoutRestApiExceptionFilter)
|
||||
export class PageLayoutController {
|
||||
constructor(private readonly pageLayoutService: PageLayoutService) {}
|
||||
|
||||
@Get()
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Query('objectMetadataId') objectMetadataId?: string,
|
||||
): Promise<PageLayoutDTO[]> {
|
||||
if (objectMetadataId) {
|
||||
return this.pageLayoutService.findByObjectMetadataId(
|
||||
workspace.id,
|
||||
objectMetadataId,
|
||||
);
|
||||
}
|
||||
|
||||
return this.pageLayoutService.findByWorkspaceId(workspace.id);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutDTO | null> {
|
||||
return this.pageLayoutService.findByIdOrThrow(id, workspace.id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(
|
||||
@Body() input: CreatePageLayoutInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutDTO> {
|
||||
return this.pageLayoutService.create(input, workspace.id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() input: UpdatePageLayoutInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutDTO> {
|
||||
const updatedPageLayout = await this.pageLayoutService.update(
|
||||
id,
|
||||
workspace.id,
|
||||
input,
|
||||
);
|
||||
|
||||
return updatedPageLayout;
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutEntity> {
|
||||
const deletedPageLayout = await this.pageLayoutService.delete(
|
||||
id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return deletedPageLayout;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
@InputType()
|
||||
export class CreatePageLayoutInput {
|
||||
@Field({ nullable: false })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@Field(() => PageLayoutType, {
|
||||
nullable: true,
|
||||
defaultValue: PageLayoutType.RECORD_PAGE,
|
||||
})
|
||||
@IsEnum(PageLayoutType)
|
||||
@IsOptional()
|
||||
type?: PageLayoutType;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
objectMetadataId?: string;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
@InputType()
|
||||
export class UpdatePageLayoutInput {
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
name?: string;
|
||||
|
||||
@Field(() => PageLayoutType, { nullable: true })
|
||||
@IsEnum(PageLayoutType)
|
||||
@IsOptional()
|
||||
type?: PageLayoutType;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
objectMetadataId?: string;
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ObjectType('PageLayoutTab')
|
||||
export class PageLayoutTabDTO {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
title: string;
|
||||
|
||||
@Field({ nullable: false, defaultValue: 0 })
|
||||
position: number;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
pageLayoutId: string;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt?: Date | null;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { PageLayoutTabDTO } from 'src/engine/core-modules/page-layout/dtos/page-layout-tab.dto';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
registerEnumType(PageLayoutType, { name: 'PageLayoutType' });
|
||||
|
||||
@ObjectType('PageLayout')
|
||||
export class PageLayoutDTO {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
name: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
workspaceId: string;
|
||||
|
||||
@Field(() => PageLayoutType, {
|
||||
nullable: false,
|
||||
defaultValue: PageLayoutType.RECORD_PAGE,
|
||||
})
|
||||
type: PageLayoutType;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
objectMetadataId?: string | null;
|
||||
|
||||
@Field(() => [PageLayoutTabDTO], { nullable: true })
|
||||
tabs?: PageLayoutTabDTO[] | null;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt?: Date | null;
|
||||
}
|
||||
+5
-4
@@ -18,10 +18,11 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
@Entity({ name: 'pageLayout', schema: 'core' })
|
||||
@Index('IDX_PAGE_LAYOUT_WORKSPACE_ID_OBJECT_METADATA_ID', [
|
||||
'workspaceId',
|
||||
'objectMetadataId',
|
||||
])
|
||||
@Index(
|
||||
'IDX_PAGE_LAYOUT_WORKSPACE_ID_OBJECT_METADATA_ID',
|
||||
['workspaceId', 'objectMetadataId'],
|
||||
{ where: '"deletedAt" IS NULL' },
|
||||
)
|
||||
export class PageLayoutEntity implements Required<PageLayoutEntity> {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export enum PageLayoutExceptionCode {
|
||||
PAGE_LAYOUT_NOT_FOUND = 'PAGE_LAYOUT_NOT_FOUND',
|
||||
INVALID_PAGE_LAYOUT_DATA = 'INVALID_PAGE_LAYOUT_DATA',
|
||||
}
|
||||
|
||||
export enum PageLayoutExceptionMessageKey {
|
||||
PAGE_LAYOUT_NOT_FOUND = 'PAGE_LAYOUT_NOT_FOUND',
|
||||
NAME_REQUIRED = 'NAME_REQUIRED',
|
||||
}
|
||||
|
||||
export class PageLayoutException extends CustomException<PageLayoutExceptionCode> {}
|
||||
|
||||
export const generatePageLayoutExceptionMessage = (
|
||||
key: PageLayoutExceptionMessageKey,
|
||||
value?: string,
|
||||
): string => {
|
||||
switch (key) {
|
||||
case PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND:
|
||||
return `Page layout with ID "${value}" not found`;
|
||||
case PageLayoutExceptionMessageKey.NAME_REQUIRED:
|
||||
return 'Page layout name is required';
|
||||
default:
|
||||
assertUnreachable(key);
|
||||
}
|
||||
};
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
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 {
|
||||
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 {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
catch(exception: PageLayoutException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
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 { 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';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -13,6 +17,8 @@ import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/p
|
||||
PageLayoutWidgetEntity,
|
||||
]),
|
||||
],
|
||||
exports: [],
|
||||
controllers: [PageLayoutController],
|
||||
providers: [PageLayoutService, PageLayoutTabService, PageLayoutResolver],
|
||||
exports: [PageLayoutService, PageLayoutTabService],
|
||||
})
|
||||
export class PageLayoutModule {}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
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 { PageLayoutDTO } from 'src/engine/core-modules/page-layout/dtos/page-layout.dto';
|
||||
import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/page-layout.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(() => PageLayoutDTO)
|
||||
@UseFilters(PageLayoutGraphqlApiExceptionFilter)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
export class PageLayoutResolver {
|
||||
constructor(private readonly pageLayoutService: PageLayoutService) {}
|
||||
|
||||
@Query(() => [PageLayoutDTO])
|
||||
async getPageLayouts(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('objectMetadataId', { type: () => String, nullable: true })
|
||||
objectMetadataId?: string,
|
||||
): Promise<PageLayoutDTO[]> {
|
||||
if (objectMetadataId) {
|
||||
return this.pageLayoutService.findByObjectMetadataId(
|
||||
workspace.id,
|
||||
objectMetadataId,
|
||||
);
|
||||
}
|
||||
|
||||
return this.pageLayoutService.findByWorkspaceId(workspace.id);
|
||||
}
|
||||
|
||||
@Query(() => PageLayoutDTO, { nullable: true })
|
||||
async getPageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutDTO | null> {
|
||||
return this.pageLayoutService.findByIdOrThrow(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
async createPageLayout(
|
||||
@Args('input') input: CreatePageLayoutInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutDTO> {
|
||||
return this.pageLayoutService.create(input, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
async updatePageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@Args('input') input: UpdatePageLayoutInput,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutDTO> {
|
||||
return this.pageLayoutService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
async deletePageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutDTO> {
|
||||
const deletedPageLayout = await this.pageLayoutService.delete(
|
||||
id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return deletedPageLayout;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async destroyPageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<boolean> {
|
||||
const deletedPageLayout = await this.pageLayoutService.destroy(
|
||||
id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return isDefined(deletedPageLayout);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutDTO)
|
||||
async restorePageLayout(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<PageLayoutDTO> {
|
||||
return this.pageLayoutService.restore(id, workspace.id);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
|
||||
import { PageLayoutTabEntity } from 'src/engine/core-modules/page-layout/entities/page-layout-tab.entity';
|
||||
|
||||
@Injectable()
|
||||
export class PageLayoutTabService {
|
||||
constructor(
|
||||
@InjectRepository(PageLayoutTabEntity)
|
||||
private readonly pageLayoutTabRepository: Repository<PageLayoutTabEntity>,
|
||||
) {}
|
||||
|
||||
async findByPageLayoutId(
|
||||
workspaceId: string,
|
||||
pageLayoutId: string,
|
||||
): Promise<PageLayoutTabEntity[]> {
|
||||
return this.pageLayoutTabRepository.find({
|
||||
where: {
|
||||
pageLayoutId,
|
||||
pageLayout: { workspaceId },
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
order: { position: 'ASC' },
|
||||
relations: ['widgets'],
|
||||
});
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
|
||||
import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import {
|
||||
PageLayoutException,
|
||||
PageLayoutExceptionCode,
|
||||
PageLayoutExceptionMessageKey,
|
||||
generatePageLayoutExceptionMessage,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
|
||||
@Injectable()
|
||||
export class PageLayoutService {
|
||||
constructor(
|
||||
@InjectRepository(PageLayoutEntity)
|
||||
private readonly pageLayoutRepository: Repository<PageLayoutEntity>,
|
||||
) {}
|
||||
|
||||
async findByWorkspaceId(workspaceId: string): Promise<PageLayoutEntity[]> {
|
||||
return this.pageLayoutRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
relations: ['tabs'],
|
||||
});
|
||||
}
|
||||
|
||||
async findByObjectMetadataId(
|
||||
workspaceId: string,
|
||||
objectMetadataId: string,
|
||||
): Promise<PageLayoutEntity[]> {
|
||||
return this.pageLayoutRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
relations: ['tabs'],
|
||||
});
|
||||
}
|
||||
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.pageLayoutRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
relations: ['tabs'],
|
||||
});
|
||||
|
||||
if (!isDefined(pageLayout)) {
|
||||
throw new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return pageLayout;
|
||||
}
|
||||
|
||||
async create(
|
||||
pageLayoutData: Partial<PageLayoutEntity>,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutEntity> {
|
||||
if (!isDefined(pageLayoutData.name)) {
|
||||
throw new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.NAME_REQUIRED,
|
||||
),
|
||||
PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
const pageLayout = this.pageLayoutRepository.create({
|
||||
...pageLayoutData,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return this.pageLayoutRepository.save(pageLayout);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: Partial<PageLayoutEntity>,
|
||||
): Promise<PageLayoutEntity> {
|
||||
const existingPageLayout = await this.findByIdOrThrow(id, workspaceId);
|
||||
|
||||
const updatedPageLayout = await this.pageLayoutRepository.save({
|
||||
...existingPageLayout,
|
||||
...updateData,
|
||||
});
|
||||
|
||||
return updatedPageLayout;
|
||||
}
|
||||
|
||||
async delete(id: string, workspaceId: string): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.findByIdOrThrow(id, workspaceId);
|
||||
|
||||
await this.pageLayoutRepository.softDelete(id);
|
||||
|
||||
return pageLayout;
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.pageLayoutRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
if (!isDefined(pageLayout)) {
|
||||
throw new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutRepository.delete(id);
|
||||
|
||||
return pageLayout;
|
||||
}
|
||||
|
||||
async restore(id: string, workspaceId: string): Promise<PageLayoutEntity> {
|
||||
const pageLayout = await this.pageLayoutRepository.findOne({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
|
||||
if (!isDefined(pageLayout)) {
|
||||
throw new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(pageLayout.deletedAt)) {
|
||||
throw new PageLayoutException(
|
||||
'Page layout is not deleted and cannot be restored',
|
||||
PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
await this.pageLayoutRepository.restore(id);
|
||||
|
||||
const restoredPageLayout = await this.findByIdOrThrow(id, workspaceId);
|
||||
|
||||
return restoredPageLayout;
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
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 { PageLayoutTabService } from 'src/engine/core-modules/page-layout/services/page-layout-tab.service';
|
||||
|
||||
describe('PageLayoutTabService', () => {
|
||||
let pageLayoutTabService: PageLayoutTabService;
|
||||
let pageLayoutTabRepository: Repository<PageLayoutTabEntity>;
|
||||
|
||||
const mockPageLayoutTab = {
|
||||
id: 'page-layout-tab-id',
|
||||
title: 'Test Tab',
|
||||
position: 0,
|
||||
pageLayoutId: 'page-layout-id',
|
||||
pageLayout: {} as any,
|
||||
widgets: [],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
} as PageLayoutTabEntity;
|
||||
|
||||
const mockWidget = {
|
||||
id: 'widget-1',
|
||||
title: 'Test Widget',
|
||||
type: WidgetType.VIEW,
|
||||
pageLayoutTabId: 'page-layout-tab-id',
|
||||
objectMetadataId: 'object-metadata-id',
|
||||
gridPosition: { row: 0, column: 0, rowSpan: 4, columnSpan: 4 },
|
||||
configuration: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
} as PageLayoutWidgetEntity;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PageLayoutTabService,
|
||||
{
|
||||
provide: getRepositoryToken(PageLayoutTabEntity),
|
||||
useValue: {
|
||||
find: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
pageLayoutTabService =
|
||||
module.get<PageLayoutTabService>(PageLayoutTabService);
|
||||
pageLayoutTabRepository = module.get<Repository<PageLayoutTabEntity>>(
|
||||
getRepositoryToken(PageLayoutTabEntity),
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(pageLayoutTabService).toBeDefined();
|
||||
});
|
||||
|
||||
describe('findByPageLayoutId', () => {
|
||||
it('should return page layout tabs for a page layout id', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const pageLayoutId = 'page-layout-id';
|
||||
const expectedTabs = [mockPageLayoutTab];
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'find')
|
||||
.mockResolvedValue(expectedTabs);
|
||||
|
||||
const result = await pageLayoutTabService.findByPageLayoutId(
|
||||
workspaceId,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
expect(pageLayoutTabRepository.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
pageLayoutId,
|
||||
pageLayout: { workspaceId },
|
||||
deletedAt: expect.anything(),
|
||||
},
|
||||
order: { position: 'ASC' },
|
||||
relations: ['widgets'],
|
||||
});
|
||||
expect(result).toEqual(expectedTabs);
|
||||
});
|
||||
|
||||
it('should return empty array when no tabs are found', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const pageLayoutId = 'page-layout-id';
|
||||
|
||||
jest.spyOn(pageLayoutTabRepository, 'find').mockResolvedValue([]);
|
||||
|
||||
const result = await pageLayoutTabService.findByPageLayoutId(
|
||||
workspaceId,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should order tabs by position in ascending order', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const pageLayoutId = 'page-layout-id';
|
||||
const tab1 = { ...mockPageLayoutTab, id: 'tab-1', position: 2 };
|
||||
const tab2 = { ...mockPageLayoutTab, id: 'tab-2', position: 0 };
|
||||
const tab3 = { ...mockPageLayoutTab, id: 'tab-3', position: 1 };
|
||||
const expectedTabs = [tab2, tab3, tab1];
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'find')
|
||||
.mockResolvedValue(expectedTabs);
|
||||
|
||||
const result = await pageLayoutTabService.findByPageLayoutId(
|
||||
workspaceId,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
expect(pageLayoutTabRepository.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
pageLayoutId,
|
||||
pageLayout: { workspaceId },
|
||||
deletedAt: expect.anything(),
|
||||
},
|
||||
order: { position: 'ASC' },
|
||||
relations: ['widgets'],
|
||||
});
|
||||
expect(result).toEqual(expectedTabs);
|
||||
});
|
||||
|
||||
it('should include widgets relation', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const pageLayoutId = 'page-layout-id';
|
||||
const widget1 = { ...mockWidget, id: 'widget-1', type: WidgetType.VIEW };
|
||||
const widget2 = {
|
||||
...mockWidget,
|
||||
id: 'widget-2',
|
||||
type: WidgetType.FIELDS,
|
||||
};
|
||||
const tabWithWidgets = {
|
||||
...mockPageLayoutTab,
|
||||
widgets: [widget1, widget2],
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutTabRepository, 'find')
|
||||
.mockResolvedValue([tabWithWidgets]);
|
||||
|
||||
const result = await pageLayoutTabService.findByPageLayoutId(
|
||||
workspaceId,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
expect(result[0].widgets).toHaveLength(2);
|
||||
expect(result[0].widgets[0].id).toEqual('widget-1');
|
||||
expect(result[0].widgets[1].id).toEqual('widget-2');
|
||||
});
|
||||
});
|
||||
});
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { IsNull, type Repository } from 'typeorm';
|
||||
|
||||
import { PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
PageLayoutException,
|
||||
PageLayoutExceptionCode,
|
||||
PageLayoutExceptionMessageKey,
|
||||
generatePageLayoutExceptionMessage,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
import { PageLayoutService } from 'src/engine/core-modules/page-layout/services/page-layout.service';
|
||||
|
||||
describe('PageLayoutService', () => {
|
||||
let pageLayoutService: PageLayoutService;
|
||||
let pageLayoutRepository: Repository<PageLayoutEntity>;
|
||||
|
||||
const mockPageLayout = {
|
||||
id: 'page-layout-id',
|
||||
name: 'Test Page Layout',
|
||||
workspaceId: 'workspace-id',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: 'object-metadata-id',
|
||||
tabs: [],
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
} as unknown as PageLayoutEntity;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
PageLayoutService,
|
||||
{
|
||||
provide: getRepositoryToken(PageLayoutEntity),
|
||||
useValue: {
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
softDelete: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
restore: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
pageLayoutService = module.get<PageLayoutService>(PageLayoutService);
|
||||
pageLayoutRepository = module.get<Repository<PageLayoutEntity>>(
|
||||
getRepositoryToken(PageLayoutEntity),
|
||||
);
|
||||
});
|
||||
|
||||
describe('findByWorkspaceId', () => {
|
||||
it('should return page layouts for a workspace', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const expectedPageLayouts = [mockPageLayout];
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'find')
|
||||
.mockResolvedValue(expectedPageLayouts);
|
||||
|
||||
const result = await pageLayoutService.findByWorkspaceId(workspaceId);
|
||||
|
||||
expect(pageLayoutRepository.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
workspaceId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
relations: ['tabs'],
|
||||
});
|
||||
expect(result).toEqual(expectedPageLayouts);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByObjectMetadataId', () => {
|
||||
it('should return page layouts for an object metadata id', async () => {
|
||||
const workspaceId = 'workspace-id';
|
||||
const objectMetadataId = 'object-metadata-id';
|
||||
const expectedPageLayouts = [mockPageLayout];
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'find')
|
||||
.mockResolvedValue(expectedPageLayouts);
|
||||
|
||||
const result = await pageLayoutService.findByObjectMetadataId(
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
);
|
||||
|
||||
expect(pageLayoutRepository.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
relations: ['tabs'],
|
||||
});
|
||||
expect(result).toEqual(expectedPageLayouts);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByIdOrThrow', () => {
|
||||
it('should return a page layout by id', async () => {
|
||||
const id = 'page-layout-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'findOne')
|
||||
.mockResolvedValue(mockPageLayout);
|
||||
|
||||
const result = await pageLayoutService.findByIdOrThrow(id, workspaceId);
|
||||
|
||||
expect(result).toEqual(mockPageLayout);
|
||||
});
|
||||
|
||||
it('should throw exception when page layout is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest.spyOn(pageLayoutRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
pageLayoutService.findByIdOrThrow(id, workspaceId),
|
||||
).rejects.toThrow(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
const validPageLayoutData = {
|
||||
name: 'Test Page Layout',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: 'object-metadata-id',
|
||||
};
|
||||
|
||||
it('should create a page layout successfully', async () => {
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'create')
|
||||
.mockReturnValue(mockPageLayout);
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'save')
|
||||
.mockResolvedValue(mockPageLayout);
|
||||
|
||||
const result = await pageLayoutService.create(
|
||||
validPageLayoutData,
|
||||
'workspace-id',
|
||||
);
|
||||
|
||||
expect(pageLayoutRepository.create).toHaveBeenCalledWith({
|
||||
...validPageLayoutData,
|
||||
workspaceId: 'workspace-id',
|
||||
});
|
||||
expect(pageLayoutRepository.save).toHaveBeenCalledWith(mockPageLayout);
|
||||
expect(result).toEqual(mockPageLayout);
|
||||
});
|
||||
|
||||
it('should throw exception when name is missing', async () => {
|
||||
const invalidData = { ...validPageLayoutData, name: undefined };
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
await expect(
|
||||
pageLayoutService.create(invalidData, workspaceId),
|
||||
).rejects.toThrow(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.NAME_REQUIRED,
|
||||
),
|
||||
PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update a page layout successfully', async () => {
|
||||
const id = 'page-layout-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const updateData = { name: 'Updated Page Layout' };
|
||||
const updatedPageLayout = { ...mockPageLayout, ...updateData };
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayout);
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'save')
|
||||
.mockResolvedValue(updatedPageLayout);
|
||||
|
||||
const result = await pageLayoutService.update(
|
||||
id,
|
||||
workspaceId,
|
||||
updateData,
|
||||
);
|
||||
|
||||
expect(pageLayoutService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
id,
|
||||
workspaceId,
|
||||
);
|
||||
expect(pageLayoutRepository.save).toHaveBeenCalledWith({
|
||||
...mockPageLayout,
|
||||
...updateData,
|
||||
});
|
||||
expect(result).toEqual(updatedPageLayout);
|
||||
});
|
||||
|
||||
it('should throw exception when page layout is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const updateData = { name: 'Updated Page Layout' };
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockRejectedValue(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
pageLayoutService.update(id, workspaceId, updateData),
|
||||
).rejects.toThrow(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should delete a page layout successfully', async () => {
|
||||
const id = 'page-layout-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayout);
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'softDelete')
|
||||
.mockResolvedValue({} as any);
|
||||
|
||||
const result = await pageLayoutService.delete(id, workspaceId);
|
||||
|
||||
expect(pageLayoutService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
id,
|
||||
workspaceId,
|
||||
);
|
||||
expect(pageLayoutRepository.softDelete).toHaveBeenCalledWith(id);
|
||||
expect(result).toEqual(mockPageLayout);
|
||||
});
|
||||
|
||||
it('should throw exception when page layout is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockRejectedValue(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
await expect(pageLayoutService.delete(id, workspaceId)).rejects.toThrow(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy', () => {
|
||||
it('should destroy a page layout successfully', async () => {
|
||||
const id = 'page-layout-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'findOne')
|
||||
.mockResolvedValue(mockPageLayout);
|
||||
jest.spyOn(pageLayoutRepository, 'delete').mockResolvedValue({} as any);
|
||||
|
||||
const result = await pageLayoutService.destroy(id, workspaceId);
|
||||
|
||||
expect(pageLayoutRepository.findOne).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
expect(pageLayoutRepository.delete).toHaveBeenCalledWith(id);
|
||||
expect(result).toEqual(mockPageLayout);
|
||||
});
|
||||
|
||||
it('should throw exception when page layout is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'findOne')
|
||||
.mockRejectedValue(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
await expect(pageLayoutService.destroy(id, workspaceId)).rejects.toThrow(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('restore', () => {
|
||||
it('should restore a page layout successfully', async () => {
|
||||
const id = 'page-layout-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
const deletedPageLayout = { ...mockPageLayout, deletedAt: new Date() };
|
||||
|
||||
jest
|
||||
.spyOn(pageLayoutRepository, 'findOne')
|
||||
.mockResolvedValue(deletedPageLayout);
|
||||
jest.spyOn(pageLayoutRepository, 'restore').mockResolvedValue({} as any);
|
||||
jest
|
||||
.spyOn(pageLayoutService, 'findByIdOrThrow')
|
||||
.mockResolvedValue(mockPageLayout);
|
||||
|
||||
const result = await pageLayoutService.restore(id, workspaceId);
|
||||
|
||||
expect(pageLayoutRepository.findOne).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
expect(pageLayoutRepository.restore).toHaveBeenCalledWith(id);
|
||||
expect(pageLayoutService.findByIdOrThrow).toHaveBeenCalledWith(
|
||||
id,
|
||||
workspaceId,
|
||||
);
|
||||
expect(result).toEqual(mockPageLayout);
|
||||
});
|
||||
|
||||
it('should throw exception when page layout is not found', async () => {
|
||||
const id = 'non-existent-id';
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
jest.spyOn(pageLayoutRepository, 'findOne').mockResolvedValue(null);
|
||||
|
||||
await expect(pageLayoutService.restore(id, workspaceId)).rejects.toThrow(
|
||||
new PageLayoutException(
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
NotFoundError,
|
||||
UserInputError,
|
||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import {
|
||||
PageLayoutException,
|
||||
PageLayoutExceptionCode,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
|
||||
export const pageLayoutGraphqlApiExceptionHandler = (error: Error) => {
|
||||
if (error instanceof PageLayoutException) {
|
||||
switch (error.code) {
|
||||
case PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
case PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA:
|
||||
throw new UserInputError(error.message, {
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
});
|
||||
default: {
|
||||
return assertUnreachable(error.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { ArgumentsHost, Catch } from '@nestjs/common';
|
||||
import { GqlExceptionFilter } from '@nestjs/graphql';
|
||||
|
||||
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)
|
||||
export class PageLayoutGraphqlApiExceptionFilter implements GqlExceptionFilter {
|
||||
catch(exception: PageLayoutException, _host: ArgumentsHost) {
|
||||
return pageLayoutGraphqlApiExceptionHandler(exception);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ export class ViewController {
|
||||
constructor(private readonly viewService: ViewService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Query('objectMetadataId') objectMetadataId?: string,
|
||||
|
||||
@@ -45,8 +45,8 @@ export class ViewResolver {
|
||||
private readonly viewFieldService: ViewFieldService,
|
||||
private readonly viewFilterService: ViewFilterService,
|
||||
private readonly viewFilterGroupService: ViewFilterGroupService,
|
||||
private readonly viewGroupService: ViewGroupService,
|
||||
private readonly viewSortService: ViewSortService,
|
||||
private readonly viewGroupService: ViewGroupService,
|
||||
private readonly i18nService: I18nService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export const PAGE_LAYOUT_GQL_FIELDS = `
|
||||
id
|
||||
name
|
||||
workspaceId
|
||||
type
|
||||
objectMetadataId
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
`;
|
||||
|
||||
export const PAGE_LAYOUT_TAB_GQL_FIELDS = `
|
||||
id
|
||||
title
|
||||
position
|
||||
pageLayoutId
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
`;
|
||||
@@ -0,0 +1,5 @@
|
||||
export const TEST_PAGE_LAYOUT_1_ID = '20202020-481a-4d20-b231-74977b830771';
|
||||
export const TEST_PAGE_LAYOUT_2_ID = '20202020-e512-495a-b2b1-6524ad5684d0';
|
||||
|
||||
export const TEST_NOT_EXISTING_PAGE_LAYOUT_ID =
|
||||
'20202020-df6b-4273-9aca-7170b998c175';
|
||||
+464
@@ -0,0 +1,464 @@
|
||||
import { TEST_NOT_EXISTING_PAGE_LAYOUT_ID } from 'test/integration/constants/test-page-layout-ids.constants';
|
||||
import { createPageLayoutOperationFactory } from 'test/integration/graphql/utils/create-page-layout-operation-factory.util';
|
||||
import { deletePageLayoutOperationFactory } from 'test/integration/graphql/utils/delete-page-layout-operation-factory.util';
|
||||
import { destroyPageLayoutOperationFactory } from 'test/integration/graphql/utils/destroy-page-layout-operation-factory.util';
|
||||
import { findPageLayoutOperationFactory } from 'test/integration/graphql/utils/find-page-layout-operation-factory.util';
|
||||
import { findPageLayoutsOperationFactory } from 'test/integration/graphql/utils/find-page-layouts-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 { restorePageLayoutOperationFactory } from 'test/integration/graphql/utils/restore-page-layout-operation-factory.util';
|
||||
import { updatePageLayoutOperationFactory } from 'test/integration/graphql/utils/update-page-layout-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 { assertPageLayoutStructure } from 'test/integration/utils/page-layout-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 {
|
||||
PageLayoutExceptionMessageKey,
|
||||
generatePageLayoutExceptionMessage,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
|
||||
describe('Page Layout Resolver', () => {
|
||||
let testObjectMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestPageLayoutObject',
|
||||
namePlural: 'myTestPageLayoutObjects',
|
||||
labelSingular: 'My Test Page Layout Object',
|
||||
labelPlural: 'My Test Page Layout Objects',
|
||||
icon: 'IconLayout',
|
||||
},
|
||||
});
|
||||
|
||||
testObjectMetadataId = objectMetadataId;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupPageLayoutRecordsWithGraphQL();
|
||||
});
|
||||
|
||||
describe('getPageLayouts', () => {
|
||||
it('should return empty array when no page layouts exist', async () => {
|
||||
const operation = findPageLayoutsOperationFactory();
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.getPageLayouts).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return all page layouts for workspace when no objectMetadataId provided', async () => {
|
||||
const pageLayoutName = 'Test Page Layout for Workspace';
|
||||
|
||||
await createTestPageLayoutWithGraphQL({
|
||||
name: pageLayoutName,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
const operation = findPageLayoutsOperationFactory();
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.getPageLayouts).toHaveLength(1);
|
||||
assertPageLayoutStructure(response.body.data.getPageLayouts[0], {
|
||||
name: pageLayoutName,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter page layouts by objectMetadataId when provided', async () => {
|
||||
const object1PageLayoutName = 'Page Layout for Object 1';
|
||||
const object2PageLayoutName = 'Page Layout for Object 2';
|
||||
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadata2Id },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestPageLayoutObject2',
|
||||
namePlural: 'myTestPageLayoutObjects2',
|
||||
labelSingular: 'My Test Page Layout Object 2',
|
||||
labelPlural: 'My Test Page Layout Objects 2',
|
||||
icon: 'IconLayout2',
|
||||
isLabelSyncedWithName: false,
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
createTestPageLayoutWithGraphQL({
|
||||
name: object1PageLayoutName,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
}),
|
||||
createTestPageLayoutWithGraphQL({
|
||||
name: object2PageLayoutName,
|
||||
objectMetadataId: objectMetadata2Id,
|
||||
}),
|
||||
]);
|
||||
|
||||
const operation = findPageLayoutsOperationFactory({
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.getPageLayouts).toHaveLength(1);
|
||||
assertPageLayoutStructure(response.body.data.getPageLayouts[0], {
|
||||
name: object1PageLayoutName,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: objectMetadata2Id },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPageLayout', () => {
|
||||
it('should throw when page layout does not exist', async () => {
|
||||
const operation = findPageLayoutOperationFactory({
|
||||
pageLayoutId: TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return page layout when it exists', async () => {
|
||||
const pageLayoutName = 'Test Page Layout for Get';
|
||||
|
||||
const pageLayout = await createTestPageLayoutWithGraphQL({
|
||||
name: pageLayoutName,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
const operation = findPageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
assertPageLayoutStructure(response.body.data.getPageLayout, {
|
||||
id: pageLayout.id,
|
||||
name: pageLayoutName,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPageLayout', () => {
|
||||
it('should create a new page layout with all properties', async () => {
|
||||
const input = {
|
||||
name: 'Dashboard Page Layout',
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
};
|
||||
|
||||
const operation = createPageLayoutOperationFactory({ data: input });
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
|
||||
const createdPageLayout = response.body.data.createPageLayout;
|
||||
|
||||
assertPageLayoutStructure(createdPageLayout, {
|
||||
name: input.name,
|
||||
type: input.type,
|
||||
objectMetadataId: input.objectMetadataId,
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a page layout with minimum required fields', async () => {
|
||||
const input = {
|
||||
name: 'Minimal Page Layout',
|
||||
};
|
||||
|
||||
const operation = createPageLayoutOperationFactory({ data: input });
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
|
||||
const createdPageLayout = response.body.data.createPageLayout;
|
||||
|
||||
assertPageLayoutStructure(createdPageLayout, {
|
||||
name: input.name,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: null,
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePageLayout', () => {
|
||||
it('should update an existing page layout', async () => {
|
||||
const pageLayout = await createTestPageLayoutWithGraphQL({
|
||||
name: 'Original Page Layout',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
});
|
||||
|
||||
const updateInput = {
|
||||
name: 'Updated Page Layout',
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
};
|
||||
|
||||
const operation = updatePageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
data: updateInput,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
assertPageLayoutStructure(response.body.data.updatePageLayout, {
|
||||
id: pageLayout.id,
|
||||
name: updateInput.name,
|
||||
type: updateInput.type,
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update only provided fields', async () => {
|
||||
const pageLayout = await createTestPageLayoutWithGraphQL({
|
||||
name: 'Original Page Layout',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
});
|
||||
|
||||
const updateInput = {
|
||||
name: 'Updated Name Only',
|
||||
};
|
||||
|
||||
const operation = updatePageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
data: updateInput,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
assertPageLayoutStructure(response.body.data.updatePageLayout, {
|
||||
id: pageLayout.id,
|
||||
name: updateInput.name,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when updating non-existent page layout', async () => {
|
||||
const operation = updatePageLayoutOperationFactory({
|
||||
pageLayoutId: TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
data: { name: 'Non-existent Page Layout' },
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deletePageLayout', () => {
|
||||
it('should delete an existing page layout (soft delete)', async () => {
|
||||
const pageLayout = await createTestPageLayoutWithGraphQL({
|
||||
name: 'Page Layout to Delete',
|
||||
});
|
||||
|
||||
const deleteOperation = deletePageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
});
|
||||
const deleteResponse = await makeGraphqlAPIRequest(deleteOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(deleteResponse);
|
||||
assertPageLayoutStructure(
|
||||
deleteResponse.body.data.deletePageLayout,
|
||||
pageLayout,
|
||||
);
|
||||
|
||||
const getOperation = findPageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
});
|
||||
const getResponse = await makeGraphqlAPIRequest(getOperation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
getResponse,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
pageLayout.id,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error when deleting non-existent page layout', async () => {
|
||||
const operation = deletePageLayoutOperationFactory({
|
||||
pageLayoutId: TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroyPageLayout', () => {
|
||||
it('should destroy an existing page layout (hard delete)', async () => {
|
||||
const pageLayout = await createTestPageLayoutWithGraphQL({
|
||||
name: 'Page Layout to Destroy',
|
||||
});
|
||||
|
||||
const destroyOperation = destroyPageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
});
|
||||
const destroyResponse = await makeGraphqlAPIRequest(destroyOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(destroyResponse);
|
||||
expect(destroyResponse.body.data.destroyPageLayout).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw an error when destroying non-existent page layout', async () => {
|
||||
const operation = destroyPageLayoutOperationFactory({
|
||||
pageLayoutId: TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('restorePageLayout', () => {
|
||||
it('should restore a soft deleted page layout', async () => {
|
||||
const pageLayout = await createTestPageLayoutWithGraphQL({
|
||||
name: 'Page Layout to Restore',
|
||||
type: PageLayoutType.RECORD_INDEX,
|
||||
});
|
||||
|
||||
const deleteOperation = deletePageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
});
|
||||
const deleteResponse = await makeGraphqlAPIRequest(deleteOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(deleteResponse);
|
||||
|
||||
const restoreOperation = restorePageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
});
|
||||
const restoreResponse = await makeGraphqlAPIRequest(restoreOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(restoreResponse);
|
||||
assertPageLayoutStructure(restoreResponse.body.data.restorePageLayout, {
|
||||
id: pageLayout.id,
|
||||
name: 'Page Layout to Restore',
|
||||
type: PageLayoutType.RECORD_INDEX,
|
||||
deletedAt: null,
|
||||
});
|
||||
|
||||
const getOperation = findPageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
});
|
||||
const getResponse = await makeGraphqlAPIRequest(getOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(getResponse);
|
||||
assertPageLayoutStructure(getResponse.body.data.getPageLayout, {
|
||||
id: pageLayout.id,
|
||||
name: 'Page Layout to Restore',
|
||||
type: PageLayoutType.RECORD_INDEX,
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when restoring non-existent page layout', async () => {
|
||||
const operation = restorePageLayoutOperationFactory({
|
||||
pageLayoutId: TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponse(
|
||||
response,
|
||||
ErrorCode.NOT_FOUND,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tabs resolver field', () => {
|
||||
it('should resolve tabs field for page layout', async () => {
|
||||
const pageLayout = await createTestPageLayoutWithGraphQL({
|
||||
name: 'Page Layout with Tabs',
|
||||
});
|
||||
|
||||
const operation = findPageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
type
|
||||
workspaceId
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
tabs {
|
||||
id
|
||||
title
|
||||
position
|
||||
pageLayoutId
|
||||
}
|
||||
`,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
assertPageLayoutStructure(response.body.data.getPageLayout, {
|
||||
id: pageLayout.id,
|
||||
name: 'Page Layout with Tabs',
|
||||
tabs: expect.any(Array),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
import { type CreatePageLayoutInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout.input';
|
||||
|
||||
type CreatePageLayoutOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
data?: CreatePageLayoutInput;
|
||||
};
|
||||
|
||||
export const createPageLayoutOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_GQL_FIELDS,
|
||||
data = {} as CreatePageLayoutInput,
|
||||
}: CreatePageLayoutOperationFactoryParams = {}) => ({
|
||||
query: gql`
|
||||
mutation CreatePageLayout($input: CreatePageLayoutInput!) {
|
||||
createPageLayout(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: data,
|
||||
},
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
type DeletePageLayoutOperationFactoryParams = {
|
||||
pageLayoutId: string;
|
||||
gqlFields?: string;
|
||||
};
|
||||
|
||||
export const deletePageLayoutOperationFactory = ({
|
||||
pageLayoutId,
|
||||
gqlFields = PAGE_LAYOUT_GQL_FIELDS,
|
||||
}: DeletePageLayoutOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation DeletePageLayout($id: String!) {
|
||||
deletePageLayout(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutId,
|
||||
},
|
||||
});
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
type DestroyPageLayoutOperationFactoryParams = {
|
||||
pageLayoutId: string;
|
||||
};
|
||||
|
||||
export const destroyPageLayoutOperationFactory = ({
|
||||
pageLayoutId,
|
||||
}: DestroyPageLayoutOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation DestroyPageLayout($id: String!) {
|
||||
destroyPageLayout(id: $id)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutId,
|
||||
},
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
type FindPageLayoutOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
pageLayoutId: string;
|
||||
};
|
||||
|
||||
export const findPageLayoutOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_GQL_FIELDS,
|
||||
pageLayoutId,
|
||||
}: FindPageLayoutOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
query GetPageLayout($id: String!) {
|
||||
getPageLayout(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutId,
|
||||
},
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
type FindPageLayoutsOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
objectMetadataId?: string;
|
||||
};
|
||||
|
||||
export const findPageLayoutsOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_GQL_FIELDS,
|
||||
objectMetadataId,
|
||||
}: FindPageLayoutsOperationFactoryParams = {}) => ({
|
||||
query: gql`
|
||||
query GetPageLayouts($objectMetadataId: String) {
|
||||
getPageLayouts(objectMetadataId: $objectMetadataId) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
objectMetadataId,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
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 PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
import { createPageLayoutOperationFactory } from './create-page-layout-operation-factory.util';
|
||||
import { destroyPageLayoutOperationFactory } from './destroy-page-layout-operation-factory.util';
|
||||
import { findPageLayoutsOperationFactory } from './find-page-layouts-operation-factory.util';
|
||||
|
||||
interface CreatePageLayoutResponse extends Record<string, unknown> {
|
||||
createPageLayout: PageLayoutEntity;
|
||||
}
|
||||
|
||||
export const createTestPageLayoutWithGraphQL = async (
|
||||
data: {
|
||||
name: string;
|
||||
type?: PageLayoutType;
|
||||
objectMetadataId?: string;
|
||||
} = { name: 'Test Page Layout' },
|
||||
): Promise<PageLayoutEntity> => {
|
||||
const operation = createPageLayoutOperationFactory({
|
||||
data: {
|
||||
name: data.name,
|
||||
type: data.type || PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: data.objectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
const response = (await makeGraphqlAPIRequest(
|
||||
operation,
|
||||
)) as GraphQLResponse<CreatePageLayoutResponse>;
|
||||
|
||||
if (response.body.errors) {
|
||||
throw new Error(
|
||||
`Failed to create test page layout: ${JSON.stringify(response.body.errors)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.body.data) {
|
||||
throw new Error('No data returned from createTestPageLayoutWithGraphQL');
|
||||
}
|
||||
|
||||
return response.body.data.createPageLayout;
|
||||
};
|
||||
|
||||
export const cleanupPageLayoutRecordsWithGraphQL = async (): Promise<void> => {
|
||||
const operation = findPageLayoutsOperationFactory();
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
if (response.body.data?.getPageLayouts) {
|
||||
for (const pageLayout of response.body.data.getPageLayouts) {
|
||||
const destroyOperation = destroyPageLayoutOperationFactory({
|
||||
pageLayoutId: pageLayout.id,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(destroyOperation);
|
||||
}
|
||||
}
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
type RestorePageLayoutOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
pageLayoutId: string;
|
||||
};
|
||||
|
||||
export const restorePageLayoutOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_GQL_FIELDS,
|
||||
pageLayoutId,
|
||||
}: RestorePageLayoutOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation RestorePageLayout($id: String!) {
|
||||
restorePageLayout(id: $id) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutId,
|
||||
},
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { PAGE_LAYOUT_GQL_FIELDS } from 'test/integration/constants/page-layout-gql-fields.constants';
|
||||
|
||||
import { type UpdatePageLayoutInput } from 'src/engine/core-modules/page-layout/dtos/inputs/update-page-layout.input';
|
||||
|
||||
type UpdatePageLayoutOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
pageLayoutId: string;
|
||||
data?: UpdatePageLayoutInput;
|
||||
};
|
||||
|
||||
export const updatePageLayoutOperationFactory = ({
|
||||
gqlFields = PAGE_LAYOUT_GQL_FIELDS,
|
||||
pageLayoutId,
|
||||
data = {} as UpdatePageLayoutInput,
|
||||
}: UpdatePageLayoutOperationFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation UpdatePageLayout($id: String!, $input: UpdatePageLayoutInput!) {
|
||||
updatePageLayout(id: $id, input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: pageLayoutId,
|
||||
input: data,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,369 @@
|
||||
import { TEST_NOT_EXISTING_PAGE_LAYOUT_ID } from 'test/integration/constants/test-page-layout-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 {
|
||||
assertRestApiErrorResponse,
|
||||
assertRestApiSuccessfulResponse,
|
||||
} from 'test/integration/rest/utils/rest-test-assertions.util';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
import {
|
||||
assertPageLayoutStructure,
|
||||
cleanupPageLayoutRecords,
|
||||
} from 'test/integration/utils/page-layout-test.util';
|
||||
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
PageLayoutExceptionMessageKey,
|
||||
generatePageLayoutExceptionMessage,
|
||||
} from 'src/engine/core-modules/page-layout/exceptions/page-layout.exception';
|
||||
|
||||
describe('Page Layout REST API', () => {
|
||||
let testObjectMetadataId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const {
|
||||
data: {
|
||||
createOneObject: { id: objectMetadataId },
|
||||
},
|
||||
} = await createOneObjectMetadata({
|
||||
input: {
|
||||
nameSingular: 'myTestPageLayoutObject',
|
||||
namePlural: 'myTestPageLayoutObjects',
|
||||
labelSingular: 'My Test Page Layout Object',
|
||||
labelPlural: 'My Test Page Layout Objects',
|
||||
icon: 'IconLayout',
|
||||
},
|
||||
});
|
||||
|
||||
testObjectMetadataId = objectMetadataId;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await deleteOneObjectMetadata({
|
||||
input: { idToDelete: testObjectMetadataId },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupPageLayoutRecords();
|
||||
});
|
||||
|
||||
describe('GET /rest/metadata/page-layouts', () => {
|
||||
it('should return all page layouts for workspace', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: '/metadata/page-layouts',
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return page layouts filtered by objectMetadataId', async () => {
|
||||
await createTestPageLayoutWithRestApi({
|
||||
name: 'Test Page Layout for Filter',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
});
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layouts?objectMetadataId=${testObjectMetadataId}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
|
||||
if (response.body.length > 0) {
|
||||
assertPageLayoutStructure(response.body[0]);
|
||||
expect(response.body[0].objectMetadataId).toBe(testObjectMetadataId);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return empty array when no page layouts match objectMetadataId', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layouts?objectMetadataId=${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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /rest/metadata/page-layouts', () => {
|
||||
it('should create a new page layout with all properties', async () => {
|
||||
const pageLayoutName = generateRecordName('Dashboard Page Layout');
|
||||
const pageLayout = await createTestPageLayoutWithRestApi({
|
||||
name: pageLayoutName,
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
assertPageLayoutStructure(pageLayout, {
|
||||
name: pageLayoutName,
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutWithRestApi(pageLayout.id);
|
||||
});
|
||||
|
||||
it('should create a page layout with minimum required fields', async () => {
|
||||
const pageLayoutName = generateRecordName('Minimal Page Layout');
|
||||
const pageLayout = await createTestPageLayoutWithRestApi({
|
||||
name: pageLayoutName,
|
||||
});
|
||||
|
||||
assertPageLayoutStructure(pageLayout, {
|
||||
name: pageLayoutName,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: null,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutWithRestApi(pageLayout.id);
|
||||
});
|
||||
|
||||
describe('GET /rest/metadata/page-layouts/:id', () => {
|
||||
it('should return a page layout by id', async () => {
|
||||
const pageLayoutName = generateRecordName('Test Page Layout for Get');
|
||||
const pageLayout = await createTestPageLayoutWithRestApi({
|
||||
name: pageLayoutName,
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layouts/${pageLayout.id}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
assertPageLayoutStructure(response.body, {
|
||||
id: pageLayout.id,
|
||||
name: pageLayoutName,
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutWithRestApi(pageLayout.id);
|
||||
});
|
||||
|
||||
it('should return {} for non-existent page layout', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layouts/${TEST_NOT_EXISTING_PAGE_LAYOUT_ID}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
response,
|
||||
404,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /rest/metadata/page-layouts/:id', () => {
|
||||
it('should update an existing page layout', async () => {
|
||||
const pageLayoutName = generateRecordName(
|
||||
'Test Page Layout for Update',
|
||||
);
|
||||
const pageLayout = await createTestPageLayoutWithRestApi({
|
||||
name: pageLayoutName,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
const updatedName = generateRecordName('Updated Page Layout');
|
||||
const updateData = {
|
||||
name: updatedName,
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/page-layouts/${pageLayout.id}`,
|
||||
body: updateData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
assertPageLayoutStructure(response.body, {
|
||||
id: pageLayout.id,
|
||||
name: updatedName,
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutWithRestApi(pageLayout.id);
|
||||
});
|
||||
|
||||
it('should update only provided fields', async () => {
|
||||
const originalName = generateRecordName('Original Page Layout');
|
||||
const pageLayout = await createTestPageLayoutWithRestApi({
|
||||
name: originalName,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
const updatedName = generateRecordName('Updated Name Only');
|
||||
const updateData = {
|
||||
name: updatedName,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/page-layouts/${pageLayout.id}`,
|
||||
body: updateData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
assertPageLayoutStructure(response.body, {
|
||||
id: pageLayout.id,
|
||||
name: updatedName,
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutWithRestApi(pageLayout.id);
|
||||
});
|
||||
|
||||
it('should return 404 error when updating non-existent page layout', async () => {
|
||||
const updateData = {
|
||||
name: 'Updated Page Layout',
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'patch',
|
||||
path: `/metadata/page-layouts/${TEST_NOT_EXISTING_PAGE_LAYOUT_ID}`,
|
||||
body: updateData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
response,
|
||||
404,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /rest/metadata/page-layouts/:id', () => {
|
||||
it('should delete an existing page layout', async () => {
|
||||
const pageLayoutName = generateRecordName(
|
||||
'Test Page Layout for Delete',
|
||||
);
|
||||
const pageLayout = await createTestPageLayoutWithRestApi({
|
||||
name: pageLayoutName,
|
||||
type: PageLayoutType.RECORD_INDEX,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
const deleteResponse = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/page-layouts/${pageLayout.id}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(deleteResponse);
|
||||
assertPageLayoutStructure(deleteResponse.body, pageLayout);
|
||||
|
||||
const getResponse = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layouts/${pageLayout.id}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
getResponse,
|
||||
404,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
pageLayout.id,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 404 error when deleting non-existent page layout', async () => {
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/page-layouts/${TEST_NOT_EXISTING_PAGE_LAYOUT_ID}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiErrorResponse(
|
||||
response,
|
||||
404,
|
||||
generatePageLayoutExceptionMessage(
|
||||
PageLayoutExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND,
|
||||
TEST_NOT_EXISTING_PAGE_LAYOUT_ID,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle multiple page layouts for same object', async () => {
|
||||
const pageLayout1 = await createTestPageLayoutWithRestApi({
|
||||
name: generateRecordName('Page Layout 1'),
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
const pageLayout2 = await createTestPageLayoutWithRestApi({
|
||||
name: generateRecordName('Page Layout 2'),
|
||||
type: PageLayoutType.RECORD_INDEX,
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
});
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'get',
|
||||
path: `/metadata/page-layouts?objectMetadataId=${testObjectMetadataId}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
assertRestApiSuccessfulResponse(response);
|
||||
expect(response.body).toHaveLength(2);
|
||||
|
||||
await Promise.all([
|
||||
deleteTestPageLayoutWithRestApi(pageLayout1.id),
|
||||
deleteTestPageLayoutWithRestApi(pageLayout2.id),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle page layouts with null objectMetadataId', async () => {
|
||||
const pageLayout = await createTestPageLayoutWithRestApi({
|
||||
name: generateRecordName('Global Page Layout'),
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: null,
|
||||
});
|
||||
|
||||
assertPageLayoutStructure(pageLayout, {
|
||||
type: PageLayoutType.DASHBOARD,
|
||||
objectMetadataId: null,
|
||||
});
|
||||
|
||||
await deleteTestPageLayoutWithRestApi(pageLayout.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
|
||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||
|
||||
import { type PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
export const createTestPageLayoutWithRestApi = async (
|
||||
overrides: Partial<PageLayoutEntity> = {},
|
||||
): Promise<PageLayoutEntity> => {
|
||||
const pageLayoutData = {
|
||||
name: generateRecordName('Test Page Layout'),
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: null,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
const response = await makeRestAPIRequest({
|
||||
method: 'post',
|
||||
path: '/metadata/page-layouts',
|
||||
body: pageLayoutData,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
if (response.status !== 201) {
|
||||
throw new Error(
|
||||
`Failed to create test page layout: ${response.status} - ${JSON.stringify(response.body)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return response.body;
|
||||
};
|
||||
|
||||
export const deleteTestPageLayoutWithRestApi = async (
|
||||
pageLayoutId: string,
|
||||
): Promise<void> => {
|
||||
await makeRestAPIRequest({
|
||||
method: 'delete',
|
||||
path: `/metadata/page-layouts/${pageLayoutId}`,
|
||||
bearer: API_KEY_ACCESS_TOKEN,
|
||||
}).catch(() => {});
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { type PageLayoutEntity } from 'src/engine/core-modules/page-layout/entities/page-layout.entity';
|
||||
import { PageLayoutType } from 'src/engine/core-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
export const cleanupPageLayoutRecords = async (): Promise<void> => {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
await global.testDataSource.query(`DELETE from "core"."pageLayout"`);
|
||||
};
|
||||
|
||||
export const assertPageLayoutStructure = (
|
||||
pageLayout: PageLayoutEntity,
|
||||
expectedFields?: Partial<PageLayoutEntity>,
|
||||
) => {
|
||||
expect(pageLayout).toBeDefined();
|
||||
expect(pageLayout.id).toEqual(expect.any(String));
|
||||
expect(pageLayout.name).toEqual(expect.any(String));
|
||||
expect(Object.values(PageLayoutType)).toContain(pageLayout.type);
|
||||
expect(pageLayout.workspaceId).toEqual(expect.any(String));
|
||||
expect(pageLayout.createdAt).toEqual(expect.any(String));
|
||||
expect(pageLayout.updatedAt).toEqual(expect.any(String));
|
||||
|
||||
if (expectedFields) {
|
||||
expect(pageLayout).toMatchObject(expectedFields);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user