Implement page layout override (#18472)

This commit is contained in:
Weiko
2026-03-09 18:21:08 +01:00
committed by GitHub
parent 309fd7a526
commit c433b2b73f
90 changed files with 1270 additions and 57 deletions
@@ -11,7 +11,10 @@ import {
ValidateNested,
} from 'class-validator';
import { GraphQLJSON } from 'graphql-type-json';
import { PageLayoutWidgetPosition } from 'twenty-shared/types';
import {
PageLayoutWidgetConditionalDisplay,
PageLayoutWidgetPosition,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { GridPositionInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/grid-position.input';
@@ -60,4 +63,9 @@ export class UpdatePageLayoutWidgetWithIdInput {
@IsObject()
@IsOptional()
configuration: AllPageLayoutWidgetConfiguration;
@Field(() => GraphQLJSON, { nullable: true })
@IsObject()
@IsOptional()
conditionalDisplay?: PageLayoutWidgetConditionalDisplay | null;
}
@@ -10,7 +10,10 @@ import {
ValidateNested,
} from 'class-validator';
import { GraphQLJSON } from 'graphql-type-json';
import { PageLayoutWidgetPosition } from 'twenty-shared/types';
import {
PageLayoutWidgetConditionalDisplay,
PageLayoutWidgetPosition,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { GridPositionInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/grid-position.input';
@@ -49,4 +52,9 @@ export class UpdatePageLayoutWidgetInput {
@IsObject()
@IsOptional()
configuration?: AllPageLayoutWidgetConfiguration;
@Field(() => GraphQLJSON, { nullable: true })
@IsObject()
@IsOptional()
conditionalDisplay?: PageLayoutWidgetConditionalDisplay | null;
}
@@ -1,4 +1,9 @@
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import {
Field,
HideField,
ObjectType,
registerEnumType,
} from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import GraphQLJSON from 'graphql-type-json';
@@ -8,6 +13,7 @@ import {
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { type PageLayoutWidgetOverrides } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
import { PageLayoutWidgetPositionUnion } from 'src/engine/metadata-modules/page-layout-widget/dtos/page-layout-widget-position.union';
import { WidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/dtos/widget-configuration.interface';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
@@ -67,4 +73,10 @@ export class PageLayoutWidgetDTO {
@Field(() => Date, { nullable: true })
deletedAt?: Date;
@Field(() => Boolean, { nullable: false })
isOverridden: boolean;
@HideField()
overrides?: PageLayoutWidgetOverrides | null;
}
@@ -23,9 +23,15 @@ import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { PageLayoutWidgetConfigurationTypeSettings } from 'src/engine/metadata-modules/page-layout-widget/types/page-layout-widget-configuration.type';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { OverridableEntity } from 'src/engine/workspace-manager/types/overridable-entity';
import { type JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
export type PageLayoutWidgetOverrides = {
title?: string;
position?: PageLayoutWidgetPosition | null;
conditionalDisplay?: PageLayoutWidgetConditionalDisplay | null;
};
@Entity({ name: 'pageLayoutWidget', schema: 'core' })
@ObjectType('PageLayoutWidget')
@Index(
@@ -38,7 +44,7 @@ export class PageLayoutWidgetEntity<
TWidgetConfigurationType extends
WidgetConfigurationType = WidgetConfigurationType,
>
extends SyncableEntity
extends OverridableEntity<PageLayoutWidgetOverrides>
implements Required<PageLayoutWidgetEntity>
{
@PrimaryGeneratedColumn('uuid')
@@ -6,6 +6,7 @@ import {
} from '@nestjs/common';
import { Args, Mutation, Parent, Query, ResolveField } from '@nestjs/graphql';
import GraphQLJSON from 'graphql-type-json';
import { PermissionFlagType } from 'twenty-shared/constants';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
@@ -21,6 +22,7 @@ import { PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout-wid
import { WidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/dtos/widget-configuration.interface';
import { PageLayoutWidgetService } from 'src/engine/metadata-modules/page-layout-widget/services/page-layout-widget.service';
import { PageLayoutGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/page-layout/utils/page-layout-graphql-api-exception.filter';
import { resolveOverridableEntityProperty } from 'src/engine/metadata-modules/utils/resolve-overridable-entity-property.util';
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
@MetadataResolver(() => PageLayoutWidgetDTO)
@@ -95,6 +97,21 @@ export class PageLayoutWidgetResolver {
});
}
@ResolveField(() => String)
title(@Parent() widget: PageLayoutWidgetDTO): string {
return resolveOverridableEntityProperty(widget, 'title');
}
@ResolveField(() => GraphQLJSON, { nullable: true })
position(@Parent() widget: PageLayoutWidgetDTO) {
return resolveOverridableEntityProperty(widget, 'position');
}
@ResolveField(() => GraphQLJSON, { nullable: true })
conditionalDisplay(@Parent() widget: PageLayoutWidgetDTO) {
return resolveOverridableEntityProperty(widget, 'conditionalDisplay');
}
@ResolveField(() => WidgetConfiguration, { nullable: true })
configuration(@Parent() widget: PageLayoutWidgetDTO) {
return widget.configuration;
@@ -327,24 +327,32 @@ export class PageLayoutWidgetService {
existingFlatPageLayoutWidgetMaps,
);
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatFrontComponentMaps: existingFlatFrontComponentMaps,
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
flatViewMaps: existingFlatViewMaps,
} = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
const [
{
workspaceId,
flatMapsKeys: [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
'flatFrontComponentMaps',
'flatViewFieldGroupMaps',
'flatViewMaps',
],
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatFrontComponentMaps: existingFlatFrontComponentMaps,
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
flatViewMaps: existingFlatViewMaps,
},
);
{ workspaceCustomFlatApplication },
] = await Promise.all([
this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
'flatFrontComponentMaps',
'flatViewFieldGroupMaps',
'flatViewMaps',
],
},
),
this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
),
]);
const isConfigurationBeingUpdated = Object.prototype.hasOwnProperty.call(
updateData,
@@ -377,6 +385,10 @@ export class PageLayoutWidgetService {
flatFrontComponentMaps: existingFlatFrontComponentMaps,
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
flatViewMaps: existingFlatViewMaps,
callerApplicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
workspaceCustomApplicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
});
const shouldValidateChartFields =
@@ -1,3 +1,5 @@
import { isDefined } from 'twenty-shared/utils';
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-widget/dtos/page-layout-widget.dto';
@@ -9,6 +11,8 @@ export const fromFlatPageLayoutWidgetToPageLayoutWidgetDto = (
return {
...rest,
isOverridden:
isDefined(rest.overrides) && Object.keys(rest.overrides).length > 0,
objectMetadataId: objectMetadataId ?? undefined,
createdAt: new Date(createdAt),
updatedAt: new Date(updatedAt),