Metadata modules for PageLayoutTab PageLayoutWidget (#16662)
# Introduction Creating dedicated folders and module for both `page-layout-tab` and `page-layout-widget` The addition diff with deletion is due to the module being added
This commit is contained in:
-1
@@ -1 +0,0 @@
|
||||
export const WIDGET_GRID_MAX_COLUMNS = 12;
|
||||
-1
@@ -1 +0,0 @@
|
||||
export const WIDGET_GRID_MAX_ROWS = 100;
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UseFilters,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { CreatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout-tab.input';
|
||||
import { UpdatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-tab.input';
|
||||
import { type PageLayoutTabDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-tab.dto';
|
||||
import {
|
||||
generatePageLayoutTabExceptionMessage,
|
||||
PageLayoutTabException,
|
||||
PageLayoutTabExceptionCode,
|
||||
PageLayoutTabExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
import { PageLayoutTabRestApiExceptionFilter } from 'src/engine/metadata-modules/page-layout/filters/page-layout-tab-rest-api-exception.filter';
|
||||
import { PageLayoutTabService } from 'src/engine/metadata-modules/page-layout/services/page-layout-tab.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
@Controller('rest/metadata/pageLayoutTabs')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(PageLayoutTabRestApiExceptionFilter)
|
||||
export class PageLayoutTabController {
|
||||
constructor(private readonly pageLayoutTabService: PageLayoutTabService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('pageLayoutId') pageLayoutId: string,
|
||||
): Promise<PageLayoutTabDTO[]> {
|
||||
if (!isDefined(pageLayoutId)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_ID_REQUIRED,
|
||||
),
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return this.pageLayoutTabService.findByPageLayoutId(
|
||||
workspace.id,
|
||||
pageLayoutId,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO | null> {
|
||||
return this.pageLayoutTabService.findByIdOrThrow(id, workspace.id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async create(
|
||||
@Body() input: CreatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.create(input, workspace.id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() input: UpdatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async delete(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.delete(id, workspace.id);
|
||||
}
|
||||
}
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UseFilters,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout-widget.input';
|
||||
import { UpdatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-widget.input';
|
||||
import { type PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-widget.dto';
|
||||
import {
|
||||
generatePageLayoutWidgetExceptionMessage,
|
||||
PageLayoutWidgetException,
|
||||
PageLayoutWidgetExceptionCode,
|
||||
PageLayoutWidgetExceptionMessageKey,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
|
||||
import { PageLayoutWidgetRestApiExceptionFilter } from 'src/engine/metadata-modules/page-layout/filters/page-layout-widget-rest-api-exception.filter';
|
||||
import { PageLayoutWidgetService } from 'src/engine/metadata-modules/page-layout/services/page-layout-widget.service';
|
||||
|
||||
@Controller('rest/metadata/pageLayoutWidgets')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(PageLayoutWidgetRestApiExceptionFilter)
|
||||
export class PageLayoutWidgetController {
|
||||
constructor(
|
||||
private readonly pageLayoutWidgetService: PageLayoutWidgetService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findMany(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Query('pageLayoutTabId') pageLayoutTabId: string,
|
||||
): Promise<PageLayoutWidgetDTO[]> {
|
||||
if (!isDefined(pageLayoutTabId)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_TAB_ID_REQUIRED,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return this.pageLayoutWidgetService.findByPageLayoutTabId(
|
||||
workspace.id,
|
||||
pageLayoutTabId,
|
||||
);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutWidgetDTO | null> {
|
||||
return this.pageLayoutWidgetService.findByIdOrThrow(id, workspace.id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async create(
|
||||
@Body() input: CreatePageLayoutWidgetInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.create(input, workspace.id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Body() input: UpdatePageLayoutWidgetInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async delete(
|
||||
@Param('id') id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.delete(id, workspace.id);
|
||||
}
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsTimeZone,
|
||||
IsUUID,
|
||||
Max,
|
||||
Min,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import { CalendarStartDay } from 'twenty-shared/constants';
|
||||
|
||||
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { RatioAggregateConfigDTO } from 'src/engine/metadata-modules/page-layout/dtos/ratio-aggregate-config.dto';
|
||||
import { GraphType } from 'src/engine/metadata-modules/page-layout/enums/graph-type.enum';
|
||||
|
||||
@ObjectType('AggregateChartConfiguration')
|
||||
export class AggregateChartConfigurationDTO {
|
||||
@Field(() => GraphType)
|
||||
@IsEnum(GraphType)
|
||||
@IsNotEmpty()
|
||||
graphType: GraphType.AGGREGATE;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
aggregateFieldMetadataId: string;
|
||||
|
||||
@Field(() => AggregateOperations)
|
||||
@IsEnum(AggregateOperations)
|
||||
@IsNotEmpty()
|
||||
aggregateOperation: AggregateOperations;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
label?: string;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: false })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
displayDataLabel?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
format?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
filter?: ObjectRecordFilter;
|
||||
|
||||
@Field(() => String, { nullable: true, defaultValue: 'UTC' })
|
||||
@IsTimeZone()
|
||||
@IsOptional()
|
||||
timezone?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true, defaultValue: CalendarStartDay.MONDAY })
|
||||
@IsOptional()
|
||||
@Min(0)
|
||||
@Max(7)
|
||||
firstDayOfTheWeek?: number;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
prefix?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
suffix?: string;
|
||||
|
||||
@Field(() => RatioAggregateConfigDTO, { nullable: true })
|
||||
@ValidateNested()
|
||||
@Type(() => RatioAggregateConfigDTO)
|
||||
@IsOptional()
|
||||
ratioAggregateConfig?: RatioAggregateConfigDTO;
|
||||
}
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsIn,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsTimeZone,
|
||||
IsUUID,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import { CalendarStartDay } from 'twenty-shared/constants';
|
||||
|
||||
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AxisNameDisplay } from 'src/engine/metadata-modules/page-layout/enums/axis-name-display.enum';
|
||||
import { BarChartGroupMode } from 'src/engine/metadata-modules/page-layout/enums/bar-chart-group-mode.enum';
|
||||
import { ObjectRecordGroupByDateGranularity } from 'src/engine/metadata-modules/page-layout/enums/date-granularity.enum';
|
||||
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout/enums/graph-order-by.enum';
|
||||
import { GraphType } from 'src/engine/metadata-modules/page-layout/enums/graph-type.enum';
|
||||
|
||||
@ObjectType('BarChartConfiguration')
|
||||
export class BarChartConfigurationDTO {
|
||||
@Field(() => GraphType)
|
||||
@IsIn([GraphType.VERTICAL_BAR, GraphType.HORIZONTAL_BAR])
|
||||
@IsNotEmpty()
|
||||
graphType: GraphType.VERTICAL_BAR | GraphType.HORIZONTAL_BAR;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
aggregateFieldMetadataId: string;
|
||||
|
||||
@Field(() => AggregateOperations)
|
||||
@IsEnum(AggregateOperations)
|
||||
@IsNotEmpty()
|
||||
aggregateOperation: AggregateOperations;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
primaryAxisGroupByFieldMetadataId: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
primaryAxisGroupBySubFieldName?: string;
|
||||
|
||||
@Field(() => ObjectRecordGroupByDateGranularity, {
|
||||
nullable: true,
|
||||
defaultValue: ObjectRecordGroupByDateGranularity.DAY,
|
||||
})
|
||||
@IsEnum(ObjectRecordGroupByDateGranularity)
|
||||
@IsOptional()
|
||||
primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity;
|
||||
|
||||
@Field(() => GraphOrderBy, { nullable: true })
|
||||
@IsEnum(GraphOrderBy)
|
||||
@IsOptional()
|
||||
primaryAxisOrderBy?: GraphOrderBy;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
secondaryAxisGroupByFieldMetadataId?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
secondaryAxisGroupBySubFieldName?: string;
|
||||
|
||||
@Field(() => ObjectRecordGroupByDateGranularity, {
|
||||
nullable: true,
|
||||
defaultValue: ObjectRecordGroupByDateGranularity.DAY,
|
||||
})
|
||||
@IsEnum(ObjectRecordGroupByDateGranularity)
|
||||
@IsOptional()
|
||||
secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity;
|
||||
|
||||
@Field(() => GraphOrderBy, { nullable: true })
|
||||
@IsEnum(GraphOrderBy)
|
||||
@IsOptional()
|
||||
secondaryAxisOrderBy?: GraphOrderBy;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
omitNullValues?: boolean;
|
||||
|
||||
@Field(() => AxisNameDisplay, {
|
||||
nullable: true,
|
||||
defaultValue: AxisNameDisplay.NONE,
|
||||
})
|
||||
@IsEnum(AxisNameDisplay)
|
||||
@IsOptional()
|
||||
axisNameDisplay?: AxisNameDisplay;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: false })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
displayDataLabel?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
displayLegend?: boolean;
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
rangeMin?: number;
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
rangeMax?: number;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
color?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
filter?: ObjectRecordFilter;
|
||||
|
||||
@Field(() => BarChartGroupMode, {
|
||||
nullable: true,
|
||||
})
|
||||
@IsEnum(BarChartGroupMode)
|
||||
@IsOptional()
|
||||
groupMode?: BarChartGroupMode;
|
||||
|
||||
@Field(() => Boolean, {
|
||||
nullable: true,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isCumulative?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true, defaultValue: 'UTC' })
|
||||
@IsTimeZone()
|
||||
@IsOptional()
|
||||
timezone?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true, defaultValue: CalendarStartDay.MONDAY })
|
||||
@IsOptional()
|
||||
@Min(0)
|
||||
@Max(7)
|
||||
firstDayOfTheWeek?: number;
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsTimeZone,
|
||||
IsUUID,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import { CalendarStartDay } from 'twenty-shared/constants';
|
||||
|
||||
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { GraphType } from 'src/engine/metadata-modules/page-layout/enums/graph-type.enum';
|
||||
|
||||
@ObjectType('GaugeChartConfiguration')
|
||||
export class GaugeChartConfigurationDTO {
|
||||
@Field(() => GraphType)
|
||||
@IsEnum(GraphType)
|
||||
@IsNotEmpty()
|
||||
graphType: GraphType.GAUGE;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
aggregateFieldMetadataId: string;
|
||||
|
||||
@Field(() => AggregateOperations)
|
||||
@IsEnum(AggregateOperations)
|
||||
@IsNotEmpty()
|
||||
aggregateOperation: AggregateOperations;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: false })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
displayDataLabel?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
color?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
filter?: ObjectRecordFilter;
|
||||
|
||||
@Field(() => String, { nullable: true, defaultValue: 'UTC' })
|
||||
@IsTimeZone()
|
||||
@IsOptional()
|
||||
timezone?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true, defaultValue: CalendarStartDay.MONDAY })
|
||||
@IsOptional()
|
||||
@Min(0)
|
||||
@Max(7)
|
||||
firstDayOfTheWeek?: number;
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsOptional, IsString, IsUrl } from 'class-validator';
|
||||
|
||||
@ObjectType('IframeConfiguration')
|
||||
export class IframeConfigurationDTO {
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsUrl()
|
||||
url?: string;
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
import { Field, Float, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class CreatePageLayoutTabInput {
|
||||
@Field({ nullable: false })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title: string;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
position?: number;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
pageLayoutId: string;
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { GridPositionInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/grid-position.input';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
|
||||
|
||||
@InputType()
|
||||
export class CreatePageLayoutWidgetInput {
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
pageLayoutTabId: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title: string;
|
||||
|
||||
@Field(() => WidgetType, { nullable: true, defaultValue: WidgetType.VIEW })
|
||||
@IsEnum(WidgetType)
|
||||
@IsOptional()
|
||||
type?: WidgetType;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
objectMetadataId?: string | null;
|
||||
|
||||
@Field(() => GridPositionInput, { nullable: false })
|
||||
@ValidateNested()
|
||||
@Type(() => GridPositionInput)
|
||||
gridPosition: GridPositionInput;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
configuration?: Record<string, unknown> | null;
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class DeletePageLayoutWidgetInput {
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
id: string;
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class DestroyPageLayoutWidgetInput {
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
id: string;
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsInt, IsNotEmpty, Min } from 'class-validator';
|
||||
|
||||
import { GridPosition } from 'src/engine/metadata-modules/page-layout/types/grid-position.type';
|
||||
|
||||
@InputType('GridPositionInput')
|
||||
export class GridPositionInput implements GridPosition {
|
||||
@Field()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@IsNotEmpty()
|
||||
row: number;
|
||||
|
||||
@Field()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@IsNotEmpty()
|
||||
column: number;
|
||||
|
||||
@Field()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@IsNotEmpty()
|
||||
rowSpan: number;
|
||||
|
||||
@Field()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@IsNotEmpty()
|
||||
columnSpan: number;
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
import { Field, Float, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsString,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { UpdatePageLayoutWidgetWithIdInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-widget-with-id.input';
|
||||
|
||||
@InputType()
|
||||
export class UpdatePageLayoutTabWithWidgetsInput {
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
id: string;
|
||||
|
||||
@Field()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title: string;
|
||||
|
||||
@Field(() => Float)
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
position: number;
|
||||
|
||||
@Field(() => [UpdatePageLayoutWidgetWithIdInput])
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => UpdatePageLayoutWidgetWithIdInput)
|
||||
@IsNotEmpty()
|
||||
widgets: UpdatePageLayoutWidgetWithIdInput[];
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { Field, Float, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNumber, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class UpdatePageLayoutTabInput {
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
title?: string;
|
||||
|
||||
@Field(() => Float, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
position?: number;
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { GridPositionInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/grid-position.input';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
|
||||
|
||||
@InputType()
|
||||
export class UpdatePageLayoutWidgetWithIdInput {
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
id: string;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
pageLayoutTabId: string;
|
||||
|
||||
@Field()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
title: string;
|
||||
|
||||
@Field(() => WidgetType)
|
||||
@IsEnum(WidgetType)
|
||||
@IsNotEmpty()
|
||||
type: WidgetType;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
objectMetadataId: string | null;
|
||||
|
||||
@Field(() => GridPositionInput)
|
||||
@ValidateNested()
|
||||
@Type(() => GridPositionInput)
|
||||
@IsNotEmpty()
|
||||
gridPosition: GridPositionInput;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
configuration: Record<string, unknown> | null;
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsEnum,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { GridPositionInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/grid-position.input';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
|
||||
|
||||
@InputType()
|
||||
export class UpdatePageLayoutWidgetInput {
|
||||
@Field({ nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
title?: string;
|
||||
|
||||
@Field(() => WidgetType, { nullable: true })
|
||||
@IsEnum(WidgetType)
|
||||
@IsOptional()
|
||||
type?: WidgetType;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
objectMetadataId?: string | null;
|
||||
|
||||
@Field(() => GridPositionInput, { nullable: true })
|
||||
@ValidateNested()
|
||||
@Type(() => GridPositionInput)
|
||||
@IsOptional()
|
||||
gridPosition?: GridPositionInput;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
configuration?: Record<string, unknown> | null;
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@ import {
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { UpdatePageLayoutTabWithWidgetsInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-tab-with-widgets.input';
|
||||
import { UpdatePageLayoutTabWithWidgetsInput } from 'src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab-with-widgets.input';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
@InputType()
|
||||
|
||||
-166
@@ -1,166 +0,0 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsTimeZone,
|
||||
IsUUID,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import { CalendarStartDay } from 'twenty-shared/constants';
|
||||
|
||||
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AxisNameDisplay } from 'src/engine/metadata-modules/page-layout/enums/axis-name-display.enum';
|
||||
import { ObjectRecordGroupByDateGranularity } from 'src/engine/metadata-modules/page-layout/enums/date-granularity.enum';
|
||||
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout/enums/graph-order-by.enum';
|
||||
import { GraphType } from 'src/engine/metadata-modules/page-layout/enums/graph-type.enum';
|
||||
|
||||
@ObjectType('LineChartConfiguration')
|
||||
export class LineChartConfigurationDTO {
|
||||
@Field(() => GraphType)
|
||||
@IsEnum(GraphType)
|
||||
@IsNotEmpty()
|
||||
graphType: GraphType.LINE;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
aggregateFieldMetadataId: string;
|
||||
|
||||
@Field(() => AggregateOperations)
|
||||
@IsEnum(AggregateOperations)
|
||||
@IsNotEmpty()
|
||||
aggregateOperation: AggregateOperations;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
primaryAxisGroupByFieldMetadataId: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
primaryAxisGroupBySubFieldName?: string;
|
||||
|
||||
@Field(() => ObjectRecordGroupByDateGranularity, {
|
||||
nullable: true,
|
||||
defaultValue: ObjectRecordGroupByDateGranularity.DAY,
|
||||
})
|
||||
@IsEnum(ObjectRecordGroupByDateGranularity)
|
||||
@IsOptional()
|
||||
primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity;
|
||||
|
||||
@Field(() => GraphOrderBy, {
|
||||
nullable: true,
|
||||
defaultValue: GraphOrderBy.FIELD_ASC,
|
||||
})
|
||||
@IsEnum(GraphOrderBy)
|
||||
@IsOptional()
|
||||
primaryAxisOrderBy?: GraphOrderBy;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
secondaryAxisGroupByFieldMetadataId?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
secondaryAxisGroupBySubFieldName?: string;
|
||||
|
||||
@Field(() => ObjectRecordGroupByDateGranularity, {
|
||||
nullable: true,
|
||||
defaultValue: ObjectRecordGroupByDateGranularity.DAY,
|
||||
})
|
||||
@IsEnum(ObjectRecordGroupByDateGranularity)
|
||||
@IsOptional()
|
||||
secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity;
|
||||
|
||||
@Field(() => GraphOrderBy, { nullable: true })
|
||||
@IsEnum(GraphOrderBy)
|
||||
@IsOptional()
|
||||
secondaryAxisOrderBy?: GraphOrderBy;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
omitNullValues?: boolean;
|
||||
|
||||
@Field(() => AxisNameDisplay, {
|
||||
nullable: true,
|
||||
defaultValue: AxisNameDisplay.NONE,
|
||||
})
|
||||
@IsEnum(AxisNameDisplay)
|
||||
@IsOptional()
|
||||
axisNameDisplay?: AxisNameDisplay;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: false })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
displayDataLabel?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
displayLegend?: boolean;
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
rangeMin?: number;
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
rangeMax?: number;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
color?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
filter?: ObjectRecordFilter;
|
||||
|
||||
@Field(() => Boolean, {
|
||||
nullable: true,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isStacked?: boolean;
|
||||
|
||||
@Field(() => Boolean, {
|
||||
nullable: true,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isCumulative?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true, defaultValue: 'UTC' })
|
||||
@IsTimeZone()
|
||||
@IsOptional()
|
||||
timezone?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true, defaultValue: CalendarStartDay.MONDAY })
|
||||
@IsOptional()
|
||||
@Min(0)
|
||||
@Max(7)
|
||||
firstDayOfTheWeek?: number;
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
import { Field, Float, 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';
|
||||
import { PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-widget.dto';
|
||||
|
||||
@ObjectType('PageLayoutTab')
|
||||
export class PageLayoutTabDTO {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
title: string;
|
||||
|
||||
@Field(() => Float, { nullable: false, defaultValue: 0 })
|
||||
position: number;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
pageLayoutId: string;
|
||||
|
||||
@Field(() => [PageLayoutWidgetDTO], { nullable: true })
|
||||
widgets?: PageLayoutWidgetDTO[] | null;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt?: Date | null;
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
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 {
|
||||
WidgetConfiguration,
|
||||
WidgetConfigurationInterface,
|
||||
} from 'src/engine/metadata-modules/page-layout/dtos/widget-configuration.interface';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
|
||||
|
||||
registerEnumType(WidgetType, { name: 'WidgetType' });
|
||||
|
||||
@ObjectType('GridPosition')
|
||||
export class GridPositionDTO {
|
||||
@Field()
|
||||
row: number;
|
||||
|
||||
@Field()
|
||||
column: number;
|
||||
|
||||
@Field()
|
||||
rowSpan: number;
|
||||
|
||||
@Field()
|
||||
columnSpan: number;
|
||||
}
|
||||
|
||||
@ObjectType('PageLayoutWidget')
|
||||
export class PageLayoutWidgetDTO {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: false })
|
||||
pageLayoutTabId: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
title: string;
|
||||
|
||||
@Field(() => WidgetType, { nullable: false })
|
||||
type: WidgetType;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
objectMetadataId: string | null;
|
||||
|
||||
@Field(() => GridPositionDTO, { nullable: false })
|
||||
gridPosition: GridPositionDTO;
|
||||
|
||||
@Field(() => WidgetConfiguration, { nullable: true })
|
||||
configuration: WidgetConfigurationInterface | null;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
|
||||
@Field(() => Date, { nullable: true })
|
||||
deletedAt?: Date | null;
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ 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/metadata-modules/page-layout/dtos/page-layout-tab.dto';
|
||||
import { PageLayoutTabDTO } from 'src/engine/metadata-modules/page-layout-tab/dtos/page-layout-tab.dto';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
registerEnumType(PageLayoutType, { name: 'PageLayoutType' });
|
||||
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsTimeZone,
|
||||
IsUUID,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import { CalendarStartDay } from 'twenty-shared/constants';
|
||||
|
||||
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ObjectRecordGroupByDateGranularity } from 'src/engine/metadata-modules/page-layout/enums/date-granularity.enum';
|
||||
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout/enums/graph-order-by.enum';
|
||||
import { GraphType } from 'src/engine/metadata-modules/page-layout/enums/graph-type.enum';
|
||||
|
||||
@ObjectType('PieChartConfiguration')
|
||||
export class PieChartConfigurationDTO {
|
||||
@Field(() => GraphType)
|
||||
@IsEnum(GraphType)
|
||||
@IsNotEmpty()
|
||||
graphType: GraphType.PIE;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
aggregateFieldMetadataId: string;
|
||||
|
||||
@Field(() => AggregateOperations)
|
||||
@IsEnum(AggregateOperations)
|
||||
@IsNotEmpty()
|
||||
aggregateOperation: AggregateOperations;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
groupByFieldMetadataId: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
groupBySubFieldName?: string;
|
||||
|
||||
@Field(() => ObjectRecordGroupByDateGranularity, {
|
||||
nullable: true,
|
||||
defaultValue: ObjectRecordGroupByDateGranularity.DAY,
|
||||
})
|
||||
@IsEnum(ObjectRecordGroupByDateGranularity)
|
||||
@IsOptional()
|
||||
dateGranularity?: ObjectRecordGroupByDateGranularity;
|
||||
|
||||
@Field(() => GraphOrderBy, {
|
||||
nullable: true,
|
||||
defaultValue: GraphOrderBy.VALUE_DESC,
|
||||
})
|
||||
@IsEnum(GraphOrderBy)
|
||||
@IsOptional()
|
||||
orderBy?: GraphOrderBy;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: false })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
displayDataLabel?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
showCenterMetric?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
displayLegend?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: false })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
hideEmptyCategory?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
color?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
filter?: ObjectRecordFilter;
|
||||
|
||||
@Field(() => String, { nullable: true, defaultValue: 'UTC' })
|
||||
@IsTimeZone()
|
||||
@IsOptional()
|
||||
timezone?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true, defaultValue: CalendarStartDay.MONDAY })
|
||||
@IsOptional()
|
||||
@Min(0)
|
||||
@Max(7)
|
||||
firstDayOfTheWeek?: number;
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import { Field, InputType, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ObjectType('RatioAggregateConfig')
|
||||
@InputType('RatioAggregateConfigInput')
|
||||
export class RatioAggregateConfigDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
fieldMetadataId: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
optionValue: string;
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
import { type RichTextV2Metadata } from 'twenty-shared/types';
|
||||
|
||||
@ObjectType('RichTextV2Body')
|
||||
export class RichTextV2BodyDTO implements RichTextV2Metadata {
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
blocknote?: string | null;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
markdown: string | null;
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsNotEmpty, ValidateNested } from 'class-validator';
|
||||
|
||||
import { RichTextV2BodyDTO } from 'src/engine/metadata-modules/page-layout/dtos/rich-text-v2-body.dto';
|
||||
|
||||
@ObjectType('StandaloneRichTextConfiguration')
|
||||
export class StandaloneRichTextConfigurationDTO {
|
||||
@Field(() => RichTextV2BodyDTO)
|
||||
@ValidateNested()
|
||||
@Type(() => RichTextV2BodyDTO)
|
||||
@IsNotEmpty()
|
||||
body: RichTextV2BodyDTO;
|
||||
}
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
import { createUnionType } from '@nestjs/graphql';
|
||||
|
||||
import { AggregateChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/aggregate-chart-configuration.dto';
|
||||
import { BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/bar-chart-configuration.dto';
|
||||
import { GaugeChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/gauge-chart-configuration.dto';
|
||||
import { IframeConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/iframe-configuration.dto';
|
||||
import { LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/line-chart-configuration.dto';
|
||||
import { PieChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/pie-chart-configuration.dto';
|
||||
import { StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/standalone-rich-text-configuration.dto';
|
||||
import { GraphType } from 'src/engine/metadata-modules/page-layout/enums/graph-type.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout/enums/widget-configuration-type.enum';
|
||||
|
||||
export const WidgetConfiguration = createUnionType({
|
||||
name: 'WidgetConfiguration',
|
||||
types: () => [
|
||||
BarChartConfigurationDTO,
|
||||
LineChartConfigurationDTO,
|
||||
PieChartConfigurationDTO,
|
||||
AggregateChartConfigurationDTO,
|
||||
GaugeChartConfigurationDTO,
|
||||
IframeConfigurationDTO,
|
||||
StandaloneRichTextConfigurationDTO,
|
||||
],
|
||||
resolveType(configuration: Record<string, unknown>) {
|
||||
if (!('configurationType' in configuration)) {
|
||||
throw new Error(
|
||||
'Widget configuration missing configurationType discriminator. This indicates a validation bug or data corruption.',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
configuration.configurationType === WidgetConfigurationType.CHART_CONFIG
|
||||
) {
|
||||
switch (configuration.graphType) {
|
||||
case GraphType.VERTICAL_BAR:
|
||||
case GraphType.HORIZONTAL_BAR:
|
||||
return BarChartConfigurationDTO;
|
||||
case GraphType.LINE:
|
||||
return LineChartConfigurationDTO;
|
||||
case GraphType.PIE:
|
||||
return PieChartConfigurationDTO;
|
||||
case GraphType.AGGREGATE:
|
||||
return AggregateChartConfigurationDTO;
|
||||
case GraphType.GAUGE:
|
||||
return GaugeChartConfigurationDTO;
|
||||
default:
|
||||
throw new Error(`Unknown graph type: ${configuration.graphType}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
configuration.configurationType === WidgetConfigurationType.IFRAME_CONFIG
|
||||
) {
|
||||
return IframeConfigurationDTO;
|
||||
}
|
||||
|
||||
if (
|
||||
configuration.configurationType ===
|
||||
WidgetConfigurationType.STANDALONE_RICH_TEXT_CONFIG
|
||||
) {
|
||||
return StandaloneRichTextConfigurationDTO;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Unknown widget configuration type: ${configuration.configurationType}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export type WidgetConfigurationInterface =
|
||||
| BarChartConfigurationDTO
|
||||
| LineChartConfigurationDTO
|
||||
| PieChartConfigurationDTO
|
||||
| AggregateChartConfigurationDTO
|
||||
| GaugeChartConfigurationDTO
|
||||
| IframeConfigurationDTO
|
||||
| StandaloneRichTextConfigurationDTO;
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
import { ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-widget.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
|
||||
|
||||
@Entity({ name: 'pageLayoutTab', schema: 'core' })
|
||||
@ObjectType('PageLayoutTab')
|
||||
@Index(
|
||||
'IDX_PAGE_LAYOUT_TAB_WORKSPACE_ID_PAGE_LAYOUT_ID',
|
||||
['workspaceId', 'pageLayoutId'],
|
||||
{ where: '"deletedAt" IS NULL' },
|
||||
)
|
||||
export class PageLayoutTabEntity
|
||||
extends SyncableEntity
|
||||
implements Required<PageLayoutTabEntity>
|
||||
{
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
title: string;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => WorkspaceEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'workspaceId' })
|
||||
workspace: Relation<WorkspaceEntity>;
|
||||
|
||||
@Column({ nullable: false, type: 'float', default: 0 })
|
||||
position: number;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
pageLayoutId: string;
|
||||
|
||||
@ManyToOne(() => PageLayoutEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'pageLayoutId' })
|
||||
pageLayout: Relation<PageLayoutEntity>;
|
||||
|
||||
@OneToMany(() => PageLayoutWidgetEntity, (widget) => widget.pageLayoutTab, {
|
||||
cascade: true,
|
||||
})
|
||||
widgets: Relation<PageLayoutWidgetEntity[]>;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@DeleteDateColumn({ type: 'timestamptz' })
|
||||
deletedAt: Date | null;
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
import { ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WidgetConfigurationInterface } from 'src/engine/metadata-modules/page-layout/dtos/widget-configuration.interface';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-tab.entity';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
|
||||
import { GridPosition } from 'src/engine/metadata-modules/page-layout/types/grid-position.type';
|
||||
|
||||
@Entity({ name: 'pageLayoutWidget', schema: 'core' })
|
||||
@ObjectType('PageLayoutWidget')
|
||||
@Index(
|
||||
'IDX_PAGE_LAYOUT_WIDGET_WORKSPACE_ID_PAGE_LAYOUT_TAB_ID',
|
||||
['workspaceId', 'pageLayoutTabId'],
|
||||
{ where: '"deletedAt" IS NULL' },
|
||||
)
|
||||
export class PageLayoutWidgetEntity
|
||||
extends SyncableEntity
|
||||
implements Required<PageLayoutWidgetEntity>
|
||||
{
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
pageLayoutTabId: string;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => WorkspaceEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'workspaceId' })
|
||||
workspace: Relation<WorkspaceEntity>;
|
||||
|
||||
@ManyToOne(() => PageLayoutTabEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'pageLayoutTabId' })
|
||||
pageLayoutTab: Relation<PageLayoutTabEntity>;
|
||||
|
||||
@Column({ nullable: false })
|
||||
title: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: Object.values(WidgetType),
|
||||
nullable: false,
|
||||
default: WidgetType.VIEW,
|
||||
})
|
||||
type: WidgetType;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
objectMetadataId: string | null;
|
||||
|
||||
@ManyToOne(() => ObjectMetadataEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
})
|
||||
@JoinColumn({ name: 'objectMetadataId' })
|
||||
objectMetadata: Relation<ObjectMetadataEntity> | null;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: false })
|
||||
gridPosition: GridPosition;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
configuration: WidgetConfigurationInterface | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@DeleteDateColumn({ type: 'timestamptz' })
|
||||
deletedAt: Date | null;
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/inte
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-tab.entity';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
@Entity({ name: 'pageLayout', schema: 'core' })
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum AxisNameDisplay {
|
||||
NONE = 'NONE',
|
||||
X = 'X',
|
||||
Y = 'Y',
|
||||
BOTH = 'BOTH',
|
||||
}
|
||||
|
||||
registerEnumType(AxisNameDisplay, {
|
||||
name: 'AxisNameDisplay',
|
||||
description: 'Which axes should display labels',
|
||||
});
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum BarChartGroupMode {
|
||||
STACKED = 'STACKED',
|
||||
GROUPED = 'GROUPED',
|
||||
}
|
||||
|
||||
registerEnumType(BarChartGroupMode, {
|
||||
name: 'BarChartGroupMode',
|
||||
description: 'Display mode for bar charts with secondary grouping',
|
||||
});
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
|
||||
|
||||
registerEnumType(ObjectRecordGroupByDateGranularity, {
|
||||
name: 'ObjectRecordGroupByDateGranularity',
|
||||
description:
|
||||
'Date granularity options (e.g. DAY, MONTH, QUARTER, YEAR, WEEK, DAY_OF_THE_WEEK, MONTH_OF_THE_YEAR, QUARTER_OF_THE_YEAR)',
|
||||
});
|
||||
|
||||
export { ObjectRecordGroupByDateGranularity };
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum GraphOrderBy {
|
||||
FIELD_ASC = 'FIELD_ASC',
|
||||
FIELD_DESC = 'FIELD_DESC',
|
||||
VALUE_ASC = 'VALUE_ASC',
|
||||
VALUE_DESC = 'VALUE_DESC',
|
||||
}
|
||||
|
||||
registerEnumType(GraphOrderBy, {
|
||||
name: 'GraphOrderBy',
|
||||
description: 'Order by options for graph widgets',
|
||||
});
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
export enum GraphType {
|
||||
AGGREGATE = 'AGGREGATE',
|
||||
GAUGE = 'GAUGE',
|
||||
PIE = 'PIE',
|
||||
VERTICAL_BAR = 'VERTICAL_BAR',
|
||||
HORIZONTAL_BAR = 'HORIZONTAL_BAR',
|
||||
LINE = 'LINE',
|
||||
}
|
||||
|
||||
registerEnumType(GraphType, {
|
||||
name: 'GraphType',
|
||||
description: 'Type of graph widget',
|
||||
});
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
export enum WidgetConfigurationType {
|
||||
CHART_CONFIG = 'CHART_CONFIG',
|
||||
IFRAME_CONFIG = 'IFRAME_CONFIG',
|
||||
STANDALONE_RICH_TEXT_CONFIG = 'STANDALONE_RICH_TEXT_CONFIG',
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
export enum WidgetType {
|
||||
VIEW = 'VIEW',
|
||||
IFRAME = 'IFRAME',
|
||||
FIELDS = 'FIELDS',
|
||||
GRAPH = 'GRAPH',
|
||||
STANDALONE_RICH_TEXT = 'STANDALONE_RICH_TEXT',
|
||||
TIMELINE = 'TIMELINE',
|
||||
TASKS = 'TASKS',
|
||||
NOTES = 'NOTES',
|
||||
FILES = 'FILES',
|
||||
EMAILS = 'EMAILS',
|
||||
CALENDAR = 'CALENDAR',
|
||||
FIELD_RICH_TEXT = 'FIELD_RICH_TEXT',
|
||||
WORKFLOW = 'WORKFLOW',
|
||||
WORKFLOW_VERSION = 'WORKFLOW_VERSION',
|
||||
WORKFLOW_RUN = 'WORKFLOW_RUN',
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export enum PageLayoutTabExceptionCode {
|
||||
PAGE_LAYOUT_TAB_NOT_FOUND = 'PAGE_LAYOUT_TAB_NOT_FOUND',
|
||||
INVALID_PAGE_LAYOUT_TAB_DATA = 'INVALID_PAGE_LAYOUT_TAB_DATA',
|
||||
}
|
||||
|
||||
export enum PageLayoutTabExceptionMessageKey {
|
||||
PAGE_LAYOUT_TAB_NOT_FOUND = 'PAGE_LAYOUT_TAB_NOT_FOUND',
|
||||
TITLE_REQUIRED = 'TITLE_REQUIRED',
|
||||
PAGE_LAYOUT_ID_REQUIRED = 'PAGE_LAYOUT_ID_REQUIRED',
|
||||
PAGE_LAYOUT_NOT_FOUND = 'PAGE_LAYOUT_NOT_FOUND',
|
||||
PAGE_LAYOUT_TAB_NOT_DELETED = 'PAGE_LAYOUT_TAB_NOT_DELETED',
|
||||
}
|
||||
|
||||
const pageLayoutTabExceptionUserFriendlyMessages: Record<
|
||||
PageLayoutTabExceptionCode,
|
||||
MessageDescriptor
|
||||
> = {
|
||||
[PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND]: msg`Page layout tab not found.`,
|
||||
[PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA]: msg`Invalid page layout tab data.`,
|
||||
};
|
||||
|
||||
export class PageLayoutTabException extends CustomException<PageLayoutTabExceptionCode> {
|
||||
constructor(
|
||||
message: string,
|
||||
code: PageLayoutTabExceptionCode,
|
||||
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? pageLayoutTabExceptionUserFriendlyMessages[code],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const generatePageLayoutTabExceptionMessage = (
|
||||
key: PageLayoutTabExceptionMessageKey,
|
||||
value?: string,
|
||||
): string => {
|
||||
switch (key) {
|
||||
case PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND:
|
||||
return `Page layout tab with ID "${value}" not found`;
|
||||
case PageLayoutTabExceptionMessageKey.TITLE_REQUIRED:
|
||||
return 'Page layout tab title is required';
|
||||
case PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_ID_REQUIRED:
|
||||
return 'Page layout ID is required';
|
||||
case PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_NOT_FOUND:
|
||||
return 'Page layout not found';
|
||||
case PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_DELETED:
|
||||
return 'Page layout tab is not deleted and cannot be restored';
|
||||
default:
|
||||
assertUnreachable(key);
|
||||
}
|
||||
};
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export enum PageLayoutWidgetExceptionCode {
|
||||
PAGE_LAYOUT_WIDGET_NOT_FOUND = 'PAGE_LAYOUT_WIDGET_NOT_FOUND',
|
||||
INVALID_PAGE_LAYOUT_WIDGET_DATA = 'INVALID_PAGE_LAYOUT_WIDGET_DATA',
|
||||
}
|
||||
|
||||
export enum PageLayoutWidgetExceptionMessageKey {
|
||||
PAGE_LAYOUT_WIDGET_NOT_FOUND = 'PAGE_LAYOUT_WIDGET_NOT_FOUND',
|
||||
TITLE_REQUIRED = 'TITLE_REQUIRED',
|
||||
PAGE_LAYOUT_TAB_ID_REQUIRED = 'PAGE_LAYOUT_TAB_ID_REQUIRED',
|
||||
PAGE_LAYOUT_TAB_NOT_FOUND = 'PAGE_LAYOUT_TAB_NOT_FOUND',
|
||||
PAGE_LAYOUT_WIDGET_NOT_DELETED = 'PAGE_LAYOUT_WIDGET_NOT_DELETED',
|
||||
GRID_POSITION_REQUIRED = 'GRID_POSITION_REQUIRED',
|
||||
INVALID_WIDGET_GRID_POSITION = 'INVALID_WIDGET_GRID_POSITION',
|
||||
INVALID_WIDGET_CONFIGURATION = 'INVALID_WIDGET_CONFIGURATION',
|
||||
}
|
||||
|
||||
const pageLayoutWidgetExceptionUserFriendlyMessages: Record<
|
||||
PageLayoutWidgetExceptionCode,
|
||||
MessageDescriptor
|
||||
> = {
|
||||
[PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND]: msg`Page layout widget not found.`,
|
||||
[PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA]: msg`Invalid page layout widget data.`,
|
||||
};
|
||||
|
||||
export class PageLayoutWidgetException extends CustomException<PageLayoutWidgetExceptionCode> {
|
||||
constructor(
|
||||
message: string,
|
||||
code: PageLayoutWidgetExceptionCode,
|
||||
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
pageLayoutWidgetExceptionUserFriendlyMessages[code],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const generatePageLayoutWidgetExceptionMessage = (
|
||||
key: PageLayoutWidgetExceptionMessageKey,
|
||||
widgetTitle?: string,
|
||||
widgetType?: string,
|
||||
detailedError?: string,
|
||||
): string => {
|
||||
switch (key) {
|
||||
case PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_FOUND:
|
||||
return `Page layout widget with ID "${widgetTitle}" not found`;
|
||||
case PageLayoutWidgetExceptionMessageKey.TITLE_REQUIRED:
|
||||
return 'Page layout widget title is required';
|
||||
case PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_TAB_ID_REQUIRED:
|
||||
return 'Page layout tab ID is required';
|
||||
case PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND:
|
||||
return 'Page layout tab not found';
|
||||
case PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_DELETED:
|
||||
return 'Page layout widget is not deleted and cannot be restored';
|
||||
case PageLayoutWidgetExceptionMessageKey.GRID_POSITION_REQUIRED:
|
||||
return 'Grid position is required';
|
||||
case PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_GRID_POSITION:
|
||||
if (widgetTitle && detailedError) {
|
||||
return `Invalid grid position for widget "${widgetTitle}": ${detailedError}`;
|
||||
}
|
||||
if (detailedError) {
|
||||
return `Invalid grid position: ${detailedError}`;
|
||||
}
|
||||
|
||||
return 'Invalid widget grid position';
|
||||
case PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_CONFIGURATION:
|
||||
if (widgetTitle && widgetType && detailedError) {
|
||||
return `Invalid configuration for widget "${widgetTitle}" of type ${widgetType}: ${detailedError}`;
|
||||
}
|
||||
if (widgetTitle && widgetType) {
|
||||
return `Invalid configuration for widget "${widgetTitle}" of type ${widgetType}`;
|
||||
}
|
||||
if (widgetType) {
|
||||
return `Invalid configuration for widget type ${widgetType}`;
|
||||
}
|
||||
|
||||
return 'Invalid widget configuration';
|
||||
default:
|
||||
assertUnreachable(key);
|
||||
}
|
||||
};
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import {
|
||||
PageLayoutTabException,
|
||||
PageLayoutTabExceptionCode,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
|
||||
@Catch(PageLayoutTabException)
|
||||
export class PageLayoutTabRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
catch(exception: PageLayoutTabException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
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 {
|
||||
PageLayoutWidgetException,
|
||||
PageLayoutWidgetExceptionCode,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
|
||||
|
||||
@Catch(PageLayoutWidgetException)
|
||||
export class PageLayoutWidgetRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
catch(exception: PageLayoutWidgetException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
// TODO: change to 500 when we have input validation
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-29
@@ -9,20 +9,14 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata
|
||||
import { FlatPageLayoutTabModule } from 'src/engine/metadata-modules/flat-page-layout-tab/flat-page-layout-tab.module';
|
||||
import { FlatPageLayoutWidgetModule } from 'src/engine/metadata-modules/flat-page-layout-widget/flat-page-layout-widget.module';
|
||||
import { FlatPageLayoutModule } from 'src/engine/metadata-modules/flat-page-layout/flat-page-layout.module';
|
||||
import { PageLayoutTabController } from 'src/engine/metadata-modules/page-layout/controllers/page-layout-tab.controller';
|
||||
import { PageLayoutWidgetController } from 'src/engine/metadata-modules/page-layout/controllers/page-layout-widget.controller';
|
||||
import { PageLayoutController } from 'src/engine/metadata-modules/page-layout/controllers/page-layout.controller';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-tab.entity';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout-widget.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
|
||||
import { PageLayoutTabResolver } from 'src/engine/metadata-modules/page-layout/resolvers/page-layout-tab.resolver';
|
||||
import { PageLayoutWidgetResolver } from 'src/engine/metadata-modules/page-layout/resolvers/page-layout-widget.resolver';
|
||||
import { PageLayoutResolver } from 'src/engine/metadata-modules/page-layout/resolvers/page-layout.resolver';
|
||||
import { PageLayoutDuplicationService } from 'src/engine/metadata-modules/page-layout/services/page-layout-duplication.service';
|
||||
import { PageLayoutTabService } from 'src/engine/metadata-modules/page-layout/services/page-layout-tab.service';
|
||||
import { PageLayoutUpdateService } from 'src/engine/metadata-modules/page-layout/services/page-layout-update.service';
|
||||
import { PageLayoutWidgetService } from 'src/engine/metadata-modules/page-layout/services/page-layout-widget.service';
|
||||
import { PageLayoutService } from 'src/engine/metadata-modules/page-layout/services/page-layout.service';
|
||||
import { PageLayoutTabModule } from 'src/engine/metadata-modules/page-layout-tab/page-layout-tab.module';
|
||||
import { PageLayoutWidgetModule } from 'src/engine/metadata-modules/page-layout-widget/page-layout-widget.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
@@ -31,12 +25,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
PageLayoutEntity,
|
||||
PageLayoutTabEntity,
|
||||
PageLayoutWidgetEntity,
|
||||
WorkspaceEntity,
|
||||
]),
|
||||
TypeOrmModule.forFeature([PageLayoutEntity, WorkspaceEntity]),
|
||||
TwentyORMModule,
|
||||
PermissionsModule,
|
||||
FeatureFlagModule,
|
||||
@@ -48,28 +37,17 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
FlatPageLayoutTabModule,
|
||||
FlatPageLayoutWidgetModule,
|
||||
ApplicationModule,
|
||||
PageLayoutTabModule,
|
||||
PageLayoutWidgetModule,
|
||||
],
|
||||
controllers: [
|
||||
PageLayoutController,
|
||||
PageLayoutTabController,
|
||||
PageLayoutWidgetController,
|
||||
],
|
||||
controllers: [PageLayoutController],
|
||||
providers: [
|
||||
PageLayoutService,
|
||||
PageLayoutTabService,
|
||||
PageLayoutWidgetService,
|
||||
PageLayoutDuplicationService,
|
||||
PageLayoutResolver,
|
||||
PageLayoutTabResolver,
|
||||
PageLayoutWidgetResolver,
|
||||
PageLayoutUpdateService,
|
||||
WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor,
|
||||
],
|
||||
exports: [
|
||||
PageLayoutService,
|
||||
PageLayoutTabService,
|
||||
PageLayoutWidgetService,
|
||||
PageLayoutDuplicationService,
|
||||
],
|
||||
exports: [PageLayoutService, PageLayoutDuplicationService],
|
||||
})
|
||||
export class PageLayoutModule {}
|
||||
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
import {
|
||||
UseFilters,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout-tab.input';
|
||||
import { UpdatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-tab.input';
|
||||
import { PageLayoutTabDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-tab.dto';
|
||||
import { PageLayoutTabService } from 'src/engine/metadata-modules/page-layout/services/page-layout-tab.service';
|
||||
import { PageLayoutGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/page-layout/utils/page-layout-graphql-api-exception.filter';
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
|
||||
@Resolver(() => PageLayoutTabDTO)
|
||||
@UseInterceptors(WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor)
|
||||
@UseFilters(PageLayoutGraphqlApiExceptionFilter)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
export class PageLayoutTabResolver {
|
||||
constructor(private readonly pageLayoutTabService: PageLayoutTabService) {}
|
||||
|
||||
@Query(() => [PageLayoutTabDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayoutTabs(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('pageLayoutId', { type: () => String }) pageLayoutId: string,
|
||||
): Promise<PageLayoutTabDTO[]> {
|
||||
return this.pageLayoutTabService.findByPageLayoutId(
|
||||
workspace.id,
|
||||
pageLayoutId,
|
||||
);
|
||||
}
|
||||
|
||||
@Query(() => PageLayoutTabDTO)
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.findByIdOrThrow(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async createPageLayoutTab(
|
||||
@Args('input') input: CreatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.create(input, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async updatePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@Args('input') input: UpdatePageLayoutTabInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async deletePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
const deletedPageLayoutTab = await this.pageLayoutTabService.delete(
|
||||
id,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return isDefined(deletedPageLayoutTab);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async destroyPageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
return this.pageLayoutTabService.destroy(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutTabDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async restorePageLayoutTab(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
return this.pageLayoutTabService.restore(id, workspace.id);
|
||||
}
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
import {
|
||||
UseFilters,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
Args,
|
||||
Mutation,
|
||||
Parent,
|
||||
Query,
|
||||
ResolveField,
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout-widget.input';
|
||||
import { UpdatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-widget.input';
|
||||
import { PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-widget.dto';
|
||||
import { WidgetConfiguration } from 'src/engine/metadata-modules/page-layout/dtos/widget-configuration.interface';
|
||||
import { PageLayoutWidgetService } from 'src/engine/metadata-modules/page-layout/services/page-layout-widget.service';
|
||||
import { injectWidgetConfigurationDiscriminator } from 'src/engine/metadata-modules/page-layout/utils/inject-widget-configuration-discriminator.util';
|
||||
import { PageLayoutGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/page-layout/utils/page-layout-graphql-api-exception.filter';
|
||||
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
|
||||
|
||||
@Resolver(() => PageLayoutWidgetDTO)
|
||||
@UseInterceptors(WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor)
|
||||
@UseFilters(PageLayoutGraphqlApiExceptionFilter)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
export class PageLayoutWidgetResolver {
|
||||
constructor(
|
||||
private readonly pageLayoutWidgetService: PageLayoutWidgetService,
|
||||
) {}
|
||||
|
||||
@Query(() => [PageLayoutWidgetDTO])
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayoutWidgets(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('pageLayoutTabId', { type: () => String }) pageLayoutTabId: string,
|
||||
): Promise<PageLayoutWidgetDTO[]> {
|
||||
return this.pageLayoutWidgetService.findByPageLayoutTabId(
|
||||
workspace.id,
|
||||
pageLayoutTabId,
|
||||
);
|
||||
}
|
||||
|
||||
@Query(() => PageLayoutWidgetDTO)
|
||||
@UseGuards(NoPermissionGuard)
|
||||
async getPageLayoutWidget(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.findByIdOrThrow(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutWidgetDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async createPageLayoutWidget(
|
||||
@Args('input') input: CreatePageLayoutWidgetInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.create(input, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutWidgetDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async updatePageLayoutWidget(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@Args('input') input: UpdatePageLayoutWidgetInput,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.update(id, workspace.id, input);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutWidgetDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async deletePageLayoutWidget(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.delete(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async destroyPageLayoutWidget(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
return this.pageLayoutWidgetService.destroy(id, workspace.id);
|
||||
}
|
||||
|
||||
@Mutation(() => PageLayoutWidgetDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.LAYOUTS))
|
||||
async restorePageLayoutWidget(
|
||||
@Args('id', { type: () => String }) id: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
return this.pageLayoutWidgetService.restore(id, workspace.id);
|
||||
}
|
||||
|
||||
@ResolveField(() => WidgetConfiguration, { nullable: true })
|
||||
configuration(@Parent() widget: PageLayoutWidgetDTO) {
|
||||
return injectWidgetConfigurationDiscriminator(
|
||||
widget.type,
|
||||
widget.configuration,
|
||||
);
|
||||
}
|
||||
}
|
||||
-388
@@ -1,388 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
|
||||
import { fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-create-page-layout-tab-input-to-flat-page-layout-tab-to-create.util';
|
||||
import { fromDeletePageLayoutTabInputToFlatPageLayoutTabOrThrow } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-delete-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util';
|
||||
import { fromDestroyPageLayoutTabInputToFlatPageLayoutTabOrThrow } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-destroy-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util';
|
||||
import { fromRestorePageLayoutTabInputToFlatPageLayoutTabOrThrow } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-restore-page-layout-tab-input-to-flat-page-layout-tab-or-throw.util';
|
||||
import {
|
||||
fromUpdatePageLayoutTabInputToFlatPageLayoutTabToUpdateOrThrow,
|
||||
type UpdatePageLayoutTabInputWithId,
|
||||
} from 'src/engine/metadata-modules/flat-page-layout-tab/utils/from-update-page-layout-tab-input-to-flat-page-layout-tab-to-update-or-throw.util';
|
||||
import { reconstructFlatPageLayoutTabWithWidgets } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/reconstruct-flat-page-layout-tab-with-widgets.util';
|
||||
import { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
|
||||
import { CreatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout-tab.input';
|
||||
import { UpdatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-tab.input';
|
||||
import { type PageLayoutTabDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-tab.dto';
|
||||
import {
|
||||
PageLayoutTabException,
|
||||
PageLayoutTabExceptionCode,
|
||||
PageLayoutTabExceptionMessageKey,
|
||||
generatePageLayoutTabExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
import { fromFlatPageLayoutTabToPageLayoutTabDto } from 'src/engine/metadata-modules/page-layout/utils/from-flat-page-layout-tab-to-page-layout-tab-dto.util';
|
||||
import { fromFlatPageLayoutTabWithWidgetsToPageLayoutTabDto } from 'src/engine/metadata-modules/page-layout/utils/from-flat-page-layout-tab-with-widgets-to-page-layout-tab-dto.util';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Injectable()
|
||||
export class PageLayoutTabService {
|
||||
constructor(
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {}
|
||||
|
||||
async findByPageLayoutId(
|
||||
workspaceId: string,
|
||||
pageLayoutId: string,
|
||||
): Promise<PageLayoutTabDTO[]> {
|
||||
const { flatPageLayoutTabMaps, flatPageLayoutWidgetMaps } =
|
||||
await this.getPageLayoutTabFlatEntityMaps(workspaceId);
|
||||
|
||||
return Object.values(flatPageLayoutTabMaps.byId)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(tab) => tab.pageLayoutId === pageLayoutId && !isDefined(tab.deletedAt),
|
||||
)
|
||||
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
|
||||
.map((tab) =>
|
||||
fromFlatPageLayoutTabWithWidgetsToPageLayoutTabDto(
|
||||
reconstructFlatPageLayoutTabWithWidgets({
|
||||
tab,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutTabDTO> {
|
||||
const { flatPageLayoutTabMaps, flatPageLayoutWidgetMaps } =
|
||||
await this.getPageLayoutTabFlatEntityMaps(workspaceId);
|
||||
|
||||
const flatTab = flatPageLayoutTabMaps.byId[id];
|
||||
|
||||
if (!isDefined(flatTab) || isDefined(flatTab.deletedAt)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return fromFlatPageLayoutTabWithWidgetsToPageLayoutTabDto(
|
||||
reconstructFlatPageLayoutTabWithWidgets({
|
||||
tab: flatTab,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async getPageLayoutTabFlatEntityMaps(workspaceId: string): Promise<{
|
||||
flatPageLayoutTabMaps: FlatPageLayoutTabMaps;
|
||||
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
|
||||
}> {
|
||||
return this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps', 'flatPageLayoutWidgetMaps'],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async create(
|
||||
createPageLayoutTabInput: CreatePageLayoutTabInput,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
if (!isDefined(createPageLayoutTabInput.title)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.TITLE_REQUIRED,
|
||||
),
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(createPageLayoutTabInput.pageLayoutId)) {
|
||||
throw new PageLayoutTabException(
|
||||
generatePageLayoutTabExceptionMessage(
|
||||
PageLayoutTabExceptionMessageKey.PAGE_LAYOUT_ID_REQUIRED,
|
||||
),
|
||||
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const flatPageLayoutTabToCreate =
|
||||
fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate({
|
||||
createPageLayoutTabInput,
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [flatPageLayoutTabToCreate],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while creating page layout tab',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatPageLayoutTabToCreate.id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: UpdatePageLayoutTabInput,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const updatePageLayoutTabInput: UpdatePageLayoutTabInputWithId = {
|
||||
id,
|
||||
update: updateData,
|
||||
};
|
||||
|
||||
const flatPageLayoutTabToUpdate =
|
||||
fromUpdatePageLayoutTabInputToFlatPageLayoutTabToUpdateOrThrow({
|
||||
updatePageLayoutTabInput,
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatPageLayoutTabToUpdate],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while updating page layout tab',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async delete(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatPageLayoutTabToDelete =
|
||||
fromDeletePageLayoutTabInputToFlatPageLayoutTabOrThrow({
|
||||
deletePageLayoutTabInput: { id },
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatPageLayoutTabToDelete],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while deleting page layout tab',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatPageLayoutTabToDestroy =
|
||||
fromDestroyPageLayoutTabInputToFlatPageLayoutTabOrThrow({
|
||||
destroyPageLayoutTabInput: { id },
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [flatPageLayoutTabToDestroy],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while destroying page layout tab',
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async restore(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<Omit<PageLayoutTabDTO, 'widgets'>> {
|
||||
const { flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatPageLayoutTabToRestore =
|
||||
fromRestorePageLayoutTabInputToFlatPageLayoutTabOrThrow({
|
||||
restorePageLayoutTabInput: { id },
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [flatPageLayoutTabToRestore],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while restoring page layout tab',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatPageLayoutTabMaps: recomputedFlatPageLayoutTabMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutTabMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatPageLayoutTabToPageLayoutTabDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedFlatPageLayoutTabMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -12,8 +12,8 @@ import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
|
||||
import { reconstructFlatPageLayoutWithTabsAndWidgets } from 'src/engine/metadata-modules/flat-page-layout/utils/reconstruct-flat-page-layout-with-tabs-and-widgets.util';
|
||||
import { UpdatePageLayoutTabWithWidgetsInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-tab-with-widgets.input';
|
||||
import { UpdatePageLayoutWidgetWithIdInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-widget-with-id.input';
|
||||
import { UpdatePageLayoutTabWithWidgetsInput } from 'src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab-with-widgets.input';
|
||||
import { UpdatePageLayoutWidgetWithIdInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/update-page-layout-widget-with-id.input';
|
||||
import { UpdatePageLayoutWithTabsInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-with-tabs.input';
|
||||
import { PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
|
||||
import {
|
||||
|
||||
-478
@@ -1,478 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
|
||||
import { FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-create-page-layout-widget-input-to-flat-page-layout-widget-to-create.util';
|
||||
import { fromDeletePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-delete-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util';
|
||||
import { fromDestroyPageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-destroy-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util';
|
||||
import { fromRestorePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-restore-page-layout-widget-input-to-flat-page-layout-widget-or-throw.util';
|
||||
import {
|
||||
fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThrow,
|
||||
type UpdatePageLayoutWidgetInputWithId,
|
||||
} from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-update-page-layout-widget-input-to-flat-page-layout-widget-to-update-or-throw.util';
|
||||
import { CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout-widget.input';
|
||||
import { UpdatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-widget.input';
|
||||
import { type PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-widget.dto';
|
||||
import { WidgetConfigurationInterface } from 'src/engine/metadata-modules/page-layout/dtos/widget-configuration.interface';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
|
||||
import {
|
||||
PageLayoutWidgetException,
|
||||
PageLayoutWidgetExceptionCode,
|
||||
PageLayoutWidgetExceptionMessageKey,
|
||||
generatePageLayoutWidgetExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
|
||||
import { fromFlatPageLayoutWidgetToPageLayoutWidgetDto } from 'src/engine/metadata-modules/page-layout/utils/from-flat-page-layout-widget-to-page-layout-widget-dto.util';
|
||||
import { validateAndTransformWidgetConfiguration } from 'src/engine/metadata-modules/page-layout/utils/validate-and-transform-widget-configuration.util';
|
||||
import { validateWidgetGridPosition } from 'src/engine/metadata-modules/page-layout/utils/validate-widget-grid-position.util';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
type WidgetMigrationOperations = {
|
||||
flatEntityToCreate: FlatPageLayoutWidget[];
|
||||
flatEntityToUpdate: FlatPageLayoutWidget[];
|
||||
flatEntityToDelete: FlatPageLayoutWidget[];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PageLayoutWidgetService {
|
||||
constructor(
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {}
|
||||
|
||||
private async getFlatPageLayoutWidgetMaps(
|
||||
workspaceId: string,
|
||||
): Promise<FlatPageLayoutWidgetMaps> {
|
||||
const { flatPageLayoutWidgetMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatPageLayoutWidgetMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return flatPageLayoutWidgetMaps;
|
||||
}
|
||||
|
||||
private async validateWidgetConfigurationOrThrow({
|
||||
type,
|
||||
configuration,
|
||||
workspaceId,
|
||||
titleForError,
|
||||
}: {
|
||||
type: WidgetType;
|
||||
configuration: Record<string, unknown>;
|
||||
workspaceId: string;
|
||||
titleForError: string;
|
||||
}): Promise<WidgetConfigurationInterface> {
|
||||
const isDashboardV2Enabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_DASHBOARD_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
let validatedConfig: WidgetConfigurationInterface | null = null;
|
||||
|
||||
try {
|
||||
validatedConfig = await validateAndTransformWidgetConfiguration({
|
||||
type,
|
||||
configuration,
|
||||
isDashboardV2Enabled,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_CONFIGURATION,
|
||||
titleForError,
|
||||
type,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(validatedConfig)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_CONFIGURATION,
|
||||
titleForError,
|
||||
type,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
return validatedConfig;
|
||||
}
|
||||
|
||||
private async validateAndRunWidgetMigration({
|
||||
workspaceId,
|
||||
operations,
|
||||
errorMessage,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
operations: WidgetMigrationOperations;
|
||||
errorMessage: string;
|
||||
}): Promise<void> {
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: operations,
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
errorMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async findByPageLayoutTabId(
|
||||
workspaceId: string,
|
||||
pageLayoutTabId: string,
|
||||
): Promise<PageLayoutWidgetDTO[]> {
|
||||
const flatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
return Object.values(flatPageLayoutWidgetMaps.byId)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(widget) =>
|
||||
widget.pageLayoutTabId === pageLayoutTabId &&
|
||||
!isDefined(widget.deletedAt),
|
||||
)
|
||||
.sort(
|
||||
(widgetA, widgetB) =>
|
||||
new Date(widgetA.createdAt).getTime() -
|
||||
new Date(widgetB.createdAt).getTime(),
|
||||
)
|
||||
.map(fromFlatPageLayoutWidgetToPageLayoutWidgetDto);
|
||||
}
|
||||
|
||||
async findByIdOrThrow(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
const flatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
const flatWidget = flatPageLayoutWidgetMaps.byId[id];
|
||||
|
||||
if (!isDefined(flatWidget) || isDefined(flatWidget.deletedAt)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(flatWidget);
|
||||
}
|
||||
|
||||
async create(
|
||||
createPageLayoutWidgetInput: CreatePageLayoutWidgetInput,
|
||||
workspaceId: string,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
this.validateCreateInput(createPageLayoutWidgetInput);
|
||||
|
||||
validateWidgetGridPosition(
|
||||
createPageLayoutWidgetInput.gridPosition,
|
||||
createPageLayoutWidgetInput.title,
|
||||
);
|
||||
|
||||
const validatedConfig = await this.getValidatedConfigurationForCreate(
|
||||
createPageLayoutWidgetInput,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const flatPageLayoutWidgetToCreate =
|
||||
fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate({
|
||||
createPageLayoutWidgetInput: {
|
||||
...createPageLayoutWidgetInput,
|
||||
...(validatedConfig && {
|
||||
configuration: validatedConfig as Record<string, unknown>,
|
||||
}),
|
||||
},
|
||||
workspaceId,
|
||||
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
|
||||
});
|
||||
|
||||
await this.validateAndRunWidgetMigration({
|
||||
workspaceId,
|
||||
operations: {
|
||||
flatEntityToCreate: [flatPageLayoutWidgetToCreate],
|
||||
flatEntityToUpdate: [],
|
||||
flatEntityToDelete: [],
|
||||
},
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while creating page layout widget',
|
||||
});
|
||||
|
||||
const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatPageLayoutWidgetToCreate.id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private validateCreateInput(input: CreatePageLayoutWidgetInput): void {
|
||||
if (!isDefined(input.title)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.TITLE_REQUIRED,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(input.pageLayoutTabId)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_TAB_ID_REQUIRED,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(input.gridPosition)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.GRID_POSITION_REQUIRED,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async getValidatedConfigurationForCreate(
|
||||
input: CreatePageLayoutWidgetInput,
|
||||
workspaceId: string,
|
||||
): Promise<WidgetConfigurationInterface | null> {
|
||||
if (!input.configuration || !input.type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.validateWidgetConfigurationOrThrow({
|
||||
type: input.type,
|
||||
configuration: input.configuration,
|
||||
workspaceId,
|
||||
titleForError: input.title,
|
||||
});
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
updateData: UpdatePageLayoutWidgetInput,
|
||||
): Promise<PageLayoutWidgetDTO> {
|
||||
const existingFlatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
const existingWidget = this.getExistingWidgetOrThrow(
|
||||
id,
|
||||
existingFlatPageLayoutWidgetMaps,
|
||||
);
|
||||
|
||||
if (updateData.gridPosition) {
|
||||
const titleForValidation = updateData.title ?? existingWidget.title;
|
||||
|
||||
validateWidgetGridPosition(updateData.gridPosition, titleForValidation);
|
||||
}
|
||||
|
||||
const validatedConfig = await this.getValidatedConfigurationForUpdate(
|
||||
updateData,
|
||||
existingWidget,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const updatePageLayoutWidgetInput: UpdatePageLayoutWidgetInputWithId = {
|
||||
id,
|
||||
update: {
|
||||
...updateData,
|
||||
...(validatedConfig && {
|
||||
configuration: validatedConfig as Record<string, unknown>,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const flatPageLayoutWidgetToUpdate =
|
||||
fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThrow({
|
||||
updatePageLayoutWidgetInput,
|
||||
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
|
||||
});
|
||||
|
||||
await this.validateAndRunWidgetMigration({
|
||||
workspaceId,
|
||||
operations: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToUpdate: [flatPageLayoutWidgetToUpdate],
|
||||
flatEntityToDelete: [],
|
||||
},
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while updating page layout widget',
|
||||
});
|
||||
|
||||
const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private getExistingWidgetOrThrow(
|
||||
id: string,
|
||||
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps,
|
||||
): FlatPageLayoutWidget {
|
||||
const existingWidget = flatPageLayoutWidgetMaps.byId[id];
|
||||
|
||||
if (!isDefined(existingWidget) || isDefined(existingWidget.deletedAt)) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.PAGE_LAYOUT_WIDGET_NOT_FOUND,
|
||||
id,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return existingWidget;
|
||||
}
|
||||
|
||||
private async getValidatedConfigurationForUpdate(
|
||||
updateData: UpdatePageLayoutWidgetInput,
|
||||
existingWidget: FlatPageLayoutWidget,
|
||||
workspaceId: string,
|
||||
): Promise<WidgetConfigurationInterface | null> {
|
||||
if (!updateData.configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const typeForValidation = updateData.type ?? existingWidget.type;
|
||||
|
||||
if (!typeForValidation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const titleForError = updateData.title ?? existingWidget.title;
|
||||
|
||||
return this.validateWidgetConfigurationOrThrow({
|
||||
type: typeForValidation,
|
||||
configuration: updateData.configuration,
|
||||
workspaceId,
|
||||
titleForError,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(id: string, workspaceId: string): Promise<PageLayoutWidgetDTO> {
|
||||
const existingFlatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
const flatPageLayoutWidgetToDelete =
|
||||
fromDeletePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow({
|
||||
deletePageLayoutWidgetInput: { id },
|
||||
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
|
||||
});
|
||||
|
||||
await this.validateAndRunWidgetMigration({
|
||||
workspaceId,
|
||||
operations: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToUpdate: [flatPageLayoutWidgetToDelete],
|
||||
flatEntityToDelete: [],
|
||||
},
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while deleting page layout widget',
|
||||
});
|
||||
|
||||
const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async destroy(id: string, workspaceId: string): Promise<boolean> {
|
||||
const existingFlatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
const flatPageLayoutWidgetToDestroy =
|
||||
fromDestroyPageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow({
|
||||
destroyPageLayoutWidgetInput: { id },
|
||||
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
|
||||
});
|
||||
|
||||
await this.validateAndRunWidgetMigration({
|
||||
workspaceId,
|
||||
operations: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToUpdate: [],
|
||||
flatEntityToDelete: [flatPageLayoutWidgetToDestroy],
|
||||
},
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while destroying page layout widget',
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async restore(id: string, workspaceId: string): Promise<PageLayoutWidgetDTO> {
|
||||
const existingFlatPageLayoutWidgetMaps =
|
||||
await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
const flatPageLayoutWidgetToRestore =
|
||||
fromRestorePageLayoutWidgetInputToFlatPageLayoutWidgetOrThrow({
|
||||
restorePageLayoutWidgetInput: { id },
|
||||
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
|
||||
});
|
||||
|
||||
await this.validateAndRunWidgetMigration({
|
||||
workspaceId,
|
||||
operations: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToUpdate: [flatPageLayoutWidgetToRestore],
|
||||
flatEntityToDelete: [],
|
||||
},
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while restoring page layout widget',
|
||||
});
|
||||
|
||||
const recomputedMaps = await this.getFlatPageLayoutWidgetMaps(workspaceId);
|
||||
|
||||
return fromFlatPageLayoutWidgetToPageLayoutWidgetDto(
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: recomputedMaps,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
export type GridPosition = {
|
||||
row: number;
|
||||
column: number;
|
||||
rowSpan: number;
|
||||
columnSpan: number;
|
||||
};
|
||||
-378
@@ -1,378 +0,0 @@
|
||||
import {
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_INVALID_SUBFIELDS,
|
||||
INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
TEST_GAUGE_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_IFRAME_CONFIG,
|
||||
TEST_LINE_CHART_CONFIG,
|
||||
TEST_NUMBER_CHART_CONFIG,
|
||||
TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
TEST_PIE_CHART_CONFIG,
|
||||
TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
} from 'test/integration/constants/widget-configuration-test-data.constants';
|
||||
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
|
||||
import { validateAndTransformWidgetConfiguration } from 'src/engine/metadata-modules/page-layout/utils/validate-and-transform-widget-configuration.util';
|
||||
|
||||
jest.mock(
|
||||
'src/engine/core-modules/record-transformer/utils/transform-rich-text-v2.util',
|
||||
() => ({
|
||||
transformRichTextV2Value: jest.fn((value) =>
|
||||
Promise.resolve({
|
||||
blocknote: value.blocknote ?? null,
|
||||
markdown: value.markdown ?? null,
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
describe('validateAndTransformWidgetConfiguration', () => {
|
||||
describe('IFRAME widget', () => {
|
||||
it('should validate and transform valid iframe configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: TEST_IFRAME_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_IFRAME_CONFIG);
|
||||
});
|
||||
|
||||
it('should throw error for invalid URL', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/url must be a URL address/);
|
||||
});
|
||||
|
||||
it('should throw error for empty URL', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/url must be a URL address/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('STANDALONE_RICH_TEXT widget', () => {
|
||||
it('should validate and transform valid standalone rich text configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: TEST_STANDALONE_RICH_TEXT_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_STANDALONE_RICH_TEXT_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal standalone rich text configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_STANDALONE_RICH_TEXT_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for missing body', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: INVALID_STANDALONE_RICH_TEXT_CONFIG_MISSING_BODY,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/body/);
|
||||
});
|
||||
|
||||
it('should throw error when body is wrong type', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: INVALID_STANDALONE_RICH_TEXT_CONFIG_BODY_WRONG_TYPE,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should strip invalid subfields from body', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.STANDALONE_RICH_TEXT,
|
||||
configuration: INVALID_STANDALONE_RICH_TEXT_CONFIG_INVALID_SUBFIELDS,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect((result as any).body.blocknote).toBeDefined();
|
||||
expect((result as any).body.markdown).toBe('valid');
|
||||
expect((result as any).body.invalidField).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GRAPH widget', () => {
|
||||
describe('NUMBER graph', () => {
|
||||
it('should validate full number graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_NUMBER_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_NUMBER_CHART_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal number graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_NUMBER_CHART_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for partial number graph configuration with missing required fields', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/aggregateFieldMetadataId.*aggregateOperation/);
|
||||
});
|
||||
|
||||
it('should throw error for invalid UUID', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/aggregateFieldMetadataId must be a UUID/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('VERTICAL_BAR graph', () => {
|
||||
it('should validate full vertical bar graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal vertical bar graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for partial vertical bar graph configuration with missing required fields', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/primaryAxisGroupByFieldMetadataId/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HORIZONTAL_BAR graph', () => {
|
||||
it('should validate full horizontal bar graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal horizontal bar graph configuration', async () => {
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for partial horizontal bar graph configuration with missing required fields', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/primaryAxisGroupByFieldMetadataId/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null for unsupported graph type', async () => {
|
||||
const configuration = {
|
||||
graphType: 'UNSUPPORTED',
|
||||
viewId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
};
|
||||
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: configuration,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for missing graph type', async () => {
|
||||
const configuration = {
|
||||
viewId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
};
|
||||
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: configuration,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should throw error for null configuration', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: null,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow('Invalid configuration: not an object');
|
||||
});
|
||||
|
||||
it('should throw error for undefined configuration', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: undefined,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow('Invalid configuration: not an object');
|
||||
});
|
||||
|
||||
it('should throw error for non-object configuration', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.IFRAME,
|
||||
configuration: 'string',
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow('Invalid configuration: not an object');
|
||||
});
|
||||
|
||||
it('should return null for unsupported widget type', async () => {
|
||||
const configuration = { someField: 'value' };
|
||||
|
||||
const result = await validateAndTransformWidgetConfiguration({
|
||||
type: 'UNSUPPORTED' as WidgetType,
|
||||
configuration: configuration,
|
||||
isDashboardV2Enabled: false,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error messages', () => {
|
||||
it('should include validation details in error message', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/aggregateFieldMetadataId must be a UUID/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Feature flags', () => {
|
||||
it('should throw error for GAUGE chart type when IS_DASHBOARD_V2_ENABLED is false', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_GAUGE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).rejects.toThrow(/IS_DASHBOARD_V2_ENABLED feature flag/);
|
||||
});
|
||||
|
||||
it('should not throw error for GAUGE chart type when IS_DASHBOARD_V2_ENABLED is true', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_GAUGE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: true,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw error for PIE chart type regardless of IS_DASHBOARD_V2_ENABLED', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_PIE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_PIE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: true,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw error for LINE chart type regardless of IS_DASHBOARD_V2_ENABLED', async () => {
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_LINE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: false,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
await expect(
|
||||
validateAndTransformWidgetConfiguration({
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: TEST_LINE_CHART_CONFIG,
|
||||
isDashboardV2Enabled: true,
|
||||
}),
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
-187
@@ -1,187 +0,0 @@
|
||||
import { WIDGET_GRID_MAX_COLUMNS } from 'src/engine/metadata-modules/page-layout/constants/widget-grid-max-columns.constant';
|
||||
import { WIDGET_GRID_MAX_ROWS } from 'src/engine/metadata-modules/page-layout/constants/widget-grid-max-rows.constant';
|
||||
import { PageLayoutWidgetException } from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
|
||||
import { validateWidgetGridPosition } from 'src/engine/metadata-modules/page-layout/utils/validate-widget-grid-position.util';
|
||||
|
||||
describe('validateWidgetGridPosition', () => {
|
||||
const validGridPosition = {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 2,
|
||||
columnSpan: 3,
|
||||
};
|
||||
|
||||
describe('Valid grid positions', () => {
|
||||
it('should not throw for valid grid position', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(validGridPosition, 'Test Widget'),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw for widget at max column boundary', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: WIDGET_GRID_MAX_COLUMNS - 1,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw for widget at max row boundary', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS - 1,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw for widget spanning to column grid edge', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: 8,
|
||||
rowSpan: 1,
|
||||
columnSpan: 4,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw for widget spanning to row grid edge', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS - 5,
|
||||
column: 0,
|
||||
rowSpan: 5,
|
||||
columnSpan: 6,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid row positions', () => {
|
||||
it('should throw for row exceeding max rows', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{ ...validGridPosition, row: WIDGET_GRID_MAX_ROWS },
|
||||
'Test Widget',
|
||||
),
|
||||
).toThrow(PageLayoutWidgetException);
|
||||
});
|
||||
|
||||
it('should throw when widget extends beyond grid height', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS - 2,
|
||||
column: 0,
|
||||
rowSpan: 5,
|
||||
columnSpan: 6,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).toThrow(/extends beyond grid height/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid column positions', () => {
|
||||
it('should throw for column exceeding max columns', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{ ...validGridPosition, column: WIDGET_GRID_MAX_COLUMNS },
|
||||
'Test Widget',
|
||||
),
|
||||
).toThrow(PageLayoutWidgetException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Widget extending beyond grid', () => {
|
||||
it('should throw when widget extends beyond grid width', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: 10,
|
||||
rowSpan: 1,
|
||||
columnSpan: 3,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).toThrow(/extends beyond grid width/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error messages', () => {
|
||||
it('should include max columns value in error', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 0,
|
||||
column: 10,
|
||||
rowSpan: 1,
|
||||
columnSpan: 5,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).toThrow(new RegExp(WIDGET_GRID_MAX_COLUMNS.toString()));
|
||||
});
|
||||
|
||||
it('should include max rows value in error for row start', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS + 10,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).toThrow(new RegExp(WIDGET_GRID_MAX_ROWS.toString()));
|
||||
});
|
||||
|
||||
it('should include max rows value in error for row extension', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: 95,
|
||||
column: 0,
|
||||
rowSpan: 10,
|
||||
columnSpan: 6,
|
||||
},
|
||||
'Test Widget',
|
||||
),
|
||||
).toThrow(new RegExp(WIDGET_GRID_MAX_ROWS.toString()));
|
||||
});
|
||||
|
||||
it('should include widget title in error message', () => {
|
||||
expect(() =>
|
||||
validateWidgetGridPosition(
|
||||
{
|
||||
row: WIDGET_GRID_MAX_ROWS,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
'My Custom Widget',
|
||||
),
|
||||
).toThrow(/My Custom Widget/);
|
||||
});
|
||||
});
|
||||
});
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
import { type PageLayoutTabDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-tab.dto';
|
||||
|
||||
export const fromFlatPageLayoutTabToPageLayoutTabDto = (
|
||||
flatPageLayoutTab: FlatPageLayoutTab,
|
||||
): Omit<PageLayoutTabDTO, 'widgets'> => {
|
||||
const {
|
||||
createdAt,
|
||||
updatedAt,
|
||||
deletedAt,
|
||||
widgetIds: _widgetIds,
|
||||
...rest
|
||||
} = flatPageLayoutTab;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
createdAt: new Date(createdAt),
|
||||
updatedAt: new Date(updatedAt),
|
||||
deletedAt: deletedAt ? new Date(deletedAt) : null,
|
||||
};
|
||||
};
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
import { type FlatPageLayoutTabWithWidgets } from 'src/engine/metadata-modules/flat-page-layout-tab/utils/reconstruct-flat-page-layout-tab-with-widgets.util';
|
||||
import { type PageLayoutTabDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-tab.dto';
|
||||
import { fromFlatPageLayoutTabToPageLayoutTabDto } from 'src/engine/metadata-modules/page-layout/utils/from-flat-page-layout-tab-to-page-layout-tab-dto.util';
|
||||
import { fromFlatPageLayoutWidgetToPageLayoutWidgetDto } from 'src/engine/metadata-modules/page-layout/utils/from-flat-page-layout-widget-to-page-layout-widget-dto.util';
|
||||
|
||||
export const fromFlatPageLayoutTabWithWidgetsToPageLayoutTabDto = (
|
||||
flatPageLayoutTabWithWidgets: FlatPageLayoutTabWithWidgets,
|
||||
): PageLayoutTabDTO => {
|
||||
const { widgets, ...flatPageLayoutTab } = flatPageLayoutTabWithWidgets;
|
||||
|
||||
return {
|
||||
...fromFlatPageLayoutTabToPageLayoutTabDto(flatPageLayoutTab),
|
||||
widgets: widgets.map(fromFlatPageLayoutWidgetToPageLayoutWidgetDto),
|
||||
};
|
||||
};
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout-widget.dto';
|
||||
|
||||
export const fromFlatPageLayoutWidgetToPageLayoutWidgetDto = (
|
||||
flatPageLayoutWidget: FlatPageLayoutWidget,
|
||||
): PageLayoutWidgetDTO => {
|
||||
const { createdAt, updatedAt, deletedAt, ...rest } = flatPageLayoutWidget;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
createdAt: new Date(createdAt),
|
||||
updatedAt: new Date(updatedAt),
|
||||
deletedAt: deletedAt ? new Date(deletedAt) : null,
|
||||
};
|
||||
};
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { type FlatPageLayoutWithTabsAndWidgets } from 'src/engine/metadata-modules/flat-page-layout/utils/reconstruct-flat-page-layout-with-tabs-and-widgets.util';
|
||||
import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
|
||||
import { fromFlatPageLayoutTabWithWidgetsToPageLayoutTabDto } from 'src/engine/metadata-modules/page-layout/utils/from-flat-page-layout-tab-with-widgets-to-page-layout-tab-dto.util';
|
||||
import { fromFlatPageLayoutTabWithWidgetsToPageLayoutTabDto } from 'src/engine/metadata-modules/page-layout-tab/utils/from-flat-page-layout-tab-with-widgets-to-page-layout-tab-dto.util';
|
||||
import { fromFlatPageLayoutToPageLayoutDto } from 'src/engine/metadata-modules/page-layout/utils/from-flat-page-layout-to-page-layout-dto.util';
|
||||
|
||||
export const fromFlatPageLayoutWithTabsAndWidgetsToPageLayoutDto = (
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
import { type WidgetConfigurationInterface } from 'src/engine/metadata-modules/page-layout/dtos/widget-configuration.interface';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout/enums/widget-configuration-type.enum';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
|
||||
|
||||
type ConfigurationWithDiscriminator = WidgetConfigurationInterface & {
|
||||
configurationType: WidgetConfigurationType;
|
||||
};
|
||||
|
||||
export const injectWidgetConfigurationDiscriminator = (
|
||||
widgetType: WidgetType,
|
||||
configuration: WidgetConfigurationInterface | null,
|
||||
): ConfigurationWithDiscriminator | null => {
|
||||
if (!configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (widgetType === WidgetType.IFRAME) {
|
||||
return {
|
||||
...configuration,
|
||||
configurationType: WidgetConfigurationType.IFRAME_CONFIG,
|
||||
} satisfies ConfigurationWithDiscriminator;
|
||||
}
|
||||
|
||||
if (widgetType === WidgetType.GRAPH && 'graphType' in configuration) {
|
||||
return {
|
||||
...configuration,
|
||||
configurationType: WidgetConfigurationType.CHART_CONFIG,
|
||||
} satisfies ConfigurationWithDiscriminator;
|
||||
}
|
||||
|
||||
if (widgetType === WidgetType.STANDALONE_RICH_TEXT) {
|
||||
return {
|
||||
...configuration,
|
||||
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT_CONFIG,
|
||||
} satisfies ConfigurationWithDiscriminator;
|
||||
}
|
||||
|
||||
return configuration as ConfigurationWithDiscriminator;
|
||||
};
|
||||
+2
-2
@@ -8,11 +8,11 @@ import {
|
||||
import {
|
||||
PageLayoutTabException,
|
||||
PageLayoutTabExceptionCode,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
} from 'src/engine/metadata-modules/page-layout-tab/exceptions/page-layout-tab.exception';
|
||||
import {
|
||||
PageLayoutWidgetException,
|
||||
PageLayoutWidgetExceptionCode,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
|
||||
} from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import {
|
||||
PageLayoutException,
|
||||
PageLayoutExceptionCode,
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ import { GqlExecutionContext } from '@nestjs/graphql';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
|
||||
import { PageLayoutTabException } from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception';
|
||||
import { PageLayoutWidgetException } from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
|
||||
import { PageLayoutTabException } from 'src/engine/metadata-modules/page-layout-tab/exceptions/page-layout-tab.exception';
|
||||
import { PageLayoutWidgetException } from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { PageLayoutException } from 'src/engine/metadata-modules/page-layout/exceptions/page-layout.exception';
|
||||
import { pageLayoutGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/page-layout/utils/page-layout-graphql-api-exception-handler.util';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
|
||||
-225
@@ -1,225 +0,0 @@
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { validateSync, type ValidationError } from 'class-validator';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { transformRichTextV2Value } from 'src/engine/core-modules/record-transformer/utils/transform-rich-text-v2.util';
|
||||
import { AggregateChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/aggregate-chart-configuration.dto';
|
||||
import { BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/bar-chart-configuration.dto';
|
||||
import { GaugeChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/gauge-chart-configuration.dto';
|
||||
import { IframeConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/iframe-configuration.dto';
|
||||
import { LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/line-chart-configuration.dto';
|
||||
import { PieChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/pie-chart-configuration.dto';
|
||||
import { StandaloneRichTextConfigurationDTO } from 'src/engine/metadata-modules/page-layout/dtos/standalone-rich-text-configuration.dto';
|
||||
import { type WidgetConfigurationInterface } from 'src/engine/metadata-modules/page-layout/dtos/widget-configuration.interface';
|
||||
import { BarChartGroupMode } from 'src/engine/metadata-modules/page-layout/enums/bar-chart-group-mode.enum';
|
||||
import { GraphType } from 'src/engine/metadata-modules/page-layout/enums/graph-type.enum';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout/enums/widget-type.enum';
|
||||
|
||||
const formatValidationErrors = (errors: ValidationError[]): string => {
|
||||
return errors
|
||||
.map((err) => {
|
||||
const constraints = err.constraints
|
||||
? Object.values(err.constraints).join(', ')
|
||||
: 'Unknown error';
|
||||
|
||||
return `${err.property}: ${constraints}`;
|
||||
})
|
||||
.join('; ');
|
||||
};
|
||||
|
||||
const validateGraphConfiguration = ({
|
||||
configuration,
|
||||
isDashboardV2Enabled,
|
||||
}: {
|
||||
configuration: Record<string, unknown>;
|
||||
isDashboardV2Enabled: boolean;
|
||||
}): WidgetConfigurationInterface | null => {
|
||||
const graphType = configuration.graphType as GraphType;
|
||||
|
||||
if (!graphType || !Object.values(GraphType).includes(graphType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (graphType === GraphType.GAUGE && !isDashboardV2Enabled) {
|
||||
throw new Error(
|
||||
`Chart type ${graphType} requires IS_DASHBOARD_V2_ENABLED feature flag`,
|
||||
);
|
||||
}
|
||||
|
||||
switch (graphType) {
|
||||
case GraphType.VERTICAL_BAR:
|
||||
case GraphType.HORIZONTAL_BAR: {
|
||||
const instance = plainToInstance(BarChartConfigurationDTO, configuration);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(instance.secondaryAxisGroupByFieldMetadataId) &&
|
||||
!isDefined(instance.groupMode)
|
||||
) {
|
||||
instance.groupMode = BarChartGroupMode.STACKED;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
case GraphType.LINE: {
|
||||
const instance = plainToInstance(
|
||||
LineChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(instance.secondaryAxisGroupByFieldMetadataId) &&
|
||||
!isDefined(instance.isStacked)
|
||||
) {
|
||||
instance.isStacked = true;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
case GraphType.PIE: {
|
||||
const instance = plainToInstance(PieChartConfigurationDTO, configuration);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
case GraphType.AGGREGATE: {
|
||||
const instance = plainToInstance(
|
||||
AggregateChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
case GraphType.GAUGE: {
|
||||
const instance = plainToInstance(
|
||||
GaugeChartConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const validateIframeConfiguration = (
|
||||
configuration: unknown,
|
||||
): WidgetConfigurationInterface | null => {
|
||||
const instance = plainToInstance(IframeConfigurationDTO, configuration);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
const validateStandaloneRichTextConfiguration = async (
|
||||
configuration: unknown,
|
||||
): Promise<WidgetConfigurationInterface | null> => {
|
||||
const instance = plainToInstance(
|
||||
StandaloneRichTextConfigurationDTO,
|
||||
configuration,
|
||||
);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
whitelist: true,
|
||||
forbidUnknownValues: true,
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
if (instance.body) {
|
||||
instance.body = await transformRichTextV2Value(instance.body);
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
export const validateAndTransformWidgetConfiguration = async ({
|
||||
type,
|
||||
configuration,
|
||||
isDashboardV2Enabled,
|
||||
}: {
|
||||
type: WidgetType;
|
||||
configuration: unknown;
|
||||
isDashboardV2Enabled: boolean;
|
||||
}): Promise<WidgetConfigurationInterface | null> => {
|
||||
if (!configuration || typeof configuration !== 'object') {
|
||||
throw new Error('Invalid configuration: not an object');
|
||||
}
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case WidgetType.GRAPH:
|
||||
return validateGraphConfiguration({
|
||||
configuration: configuration as Record<string, unknown>,
|
||||
isDashboardV2Enabled,
|
||||
});
|
||||
case WidgetType.IFRAME:
|
||||
return validateIframeConfiguration(configuration);
|
||||
case WidgetType.STANDALONE_RICH_TEXT:
|
||||
return await validateStandaloneRichTextConfiguration(configuration);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
if (Array.isArray(error)) {
|
||||
const errorMessage = formatValidationErrors(error);
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
import { WIDGET_GRID_MAX_COLUMNS } from 'src/engine/metadata-modules/page-layout/constants/widget-grid-max-columns.constant';
|
||||
import { WIDGET_GRID_MAX_ROWS } from 'src/engine/metadata-modules/page-layout/constants/widget-grid-max-rows.constant';
|
||||
import {
|
||||
PageLayoutWidgetException,
|
||||
PageLayoutWidgetExceptionCode,
|
||||
PageLayoutWidgetExceptionMessageKey,
|
||||
generatePageLayoutWidgetExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception';
|
||||
|
||||
type GridPosition = {
|
||||
row: number;
|
||||
column: number;
|
||||
rowSpan: number;
|
||||
columnSpan: number;
|
||||
};
|
||||
|
||||
export const validateWidgetGridPosition = (
|
||||
gridPosition: GridPosition,
|
||||
widgetTitle: string,
|
||||
): void => {
|
||||
const { row, column, rowSpan, columnSpan } = gridPosition;
|
||||
|
||||
if (column >= WIDGET_GRID_MAX_COLUMNS) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_GRID_POSITION,
|
||||
widgetTitle,
|
||||
undefined,
|
||||
`column ${column} exceeds grid width (max column is ${WIDGET_GRID_MAX_COLUMNS - 1})`,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (column + columnSpan > WIDGET_GRID_MAX_COLUMNS) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_GRID_POSITION,
|
||||
widgetTitle,
|
||||
undefined,
|
||||
`widget extends beyond grid width (column ${column} + columnSpan ${columnSpan} > ${WIDGET_GRID_MAX_COLUMNS})`,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (row >= WIDGET_GRID_MAX_ROWS) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_GRID_POSITION,
|
||||
widgetTitle,
|
||||
undefined,
|
||||
`row ${row} exceeds maximum allowed rows (${WIDGET_GRID_MAX_ROWS})`,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
|
||||
if (row + rowSpan > WIDGET_GRID_MAX_ROWS) {
|
||||
throw new PageLayoutWidgetException(
|
||||
generatePageLayoutWidgetExceptionMessage(
|
||||
PageLayoutWidgetExceptionMessageKey.INVALID_WIDGET_GRID_POSITION,
|
||||
widgetTitle,
|
||||
undefined,
|
||||
`widget extends beyond grid height (row ${row} + rowSpan ${rowSpan} > ${WIDGET_GRID_MAX_ROWS})`,
|
||||
),
|
||||
PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA,
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user