Allow filtering by page layout type and fetch more fields (#17750)

<img width="3456" height="2160" alt="CleanShot 2026-02-05 at 18 01
28@2x"
src="https://github.com/user-attachments/assets/d8914bdd-e44c-459d-a87f-1b00dd4c29c2"
/>
This commit is contained in:
Baptiste Devessier
2026-02-05 18:34:28 +01:00
committed by GitHub
parent e505eba978
commit 955c1b4916
9 changed files with 190 additions and 16 deletions
+1
View File
@@ -20,6 +20,7 @@ module.exports = {
'./src/modules/prefetch/graphql/**/*.{ts,tsx}',
'./src/modules/subscription/graphql/**/*.{ts,tsx}',
'./src/modules/dashboards/graphql/**/*.{ts,tsx}',
'./src/modules/page-layout/graphql/**/*.{ts,tsx}',
'!./src/**/*.test.{ts,tsx}',
@@ -3933,6 +3933,7 @@ export type QueryGetPageLayoutWidgetsArgs = {
export type QueryGetPageLayoutsArgs = {
objectMetadataId?: InputMaybe<Scalars['String']>;
pageLayoutType?: InputMaybe<PageLayoutType>;
};
File diff suppressed because one or more lines are too long
@@ -8,6 +8,7 @@ export const PAGE_LAYOUT_FRAGMENT = gql`
name
objectMetadataId
type
defaultTabToFocusOnMobileAndSidePanelId
tabs {
...PageLayoutTabFragment
}
@@ -6,7 +6,9 @@ export const PAGE_LAYOUT_TAB_FRAGMENT = gql`
fragment PageLayoutTabFragment on PageLayoutTab {
id
title
icon
position
layoutMode
widgets {
...PageLayoutWidgetFragment
}
@@ -15,6 +15,22 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
row
rowSpan
}
position {
... on PageLayoutWidgetGridPosition {
layoutMode
row
column
rowSpan
columnSpan
}
... on PageLayoutWidgetVerticalListPosition {
layoutMode
index
}
... on PageLayoutWidgetCanvasPosition {
layoutMode
}
}
configuration {
... on BarChartConfiguration {
configurationType
@@ -22,6 +22,7 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CreatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout.input';
import { UpdatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout.input';
import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { PageLayoutRestApiExceptionFilter } from 'src/engine/metadata-modules/page-layout/filters/page-layout-rest-api-exception.filter';
import { PageLayoutService } from 'src/engine/metadata-modules/page-layout/services/page-layout.service';
@@ -36,11 +37,15 @@ export class PageLayoutController {
async findMany(
@AuthWorkspace() workspace: WorkspaceEntity,
@Query('objectMetadataId') objectMetadataId?: string,
@Query('pageLayoutType') pageLayoutType?: PageLayoutType,
): Promise<PageLayoutDTO[]> {
if (isDefined(objectMetadataId)) {
return this.pageLayoutService.findByObjectMetadataId({
return this.pageLayoutService.findBy({
workspaceId: workspace.id,
objectMetadataId,
filter: {
objectMetadataId,
pageLayoutType,
},
});
}
@@ -18,6 +18,7 @@ import { CreatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/d
import { UpdatePageLayoutWithTabsInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-with-tabs.input';
import { UpdatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout.input';
import { PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { PageLayoutUpdateService } from 'src/engine/metadata-modules/page-layout/services/page-layout-update.service';
import { PageLayoutService } from 'src/engine/metadata-modules/page-layout/services/page-layout.service';
import { PageLayoutGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/page-layout/utils/page-layout-graphql-api-exception.filter';
@@ -40,11 +41,16 @@ export class PageLayoutResolver {
@AuthWorkspace() workspace: WorkspaceEntity,
@Args('objectMetadataId', { type: () => String, nullable: true })
objectMetadataId?: string,
@Args('pageLayoutType', { type: () => PageLayoutType, nullable: true })
pageLayoutType?: PageLayoutType,
): Promise<PageLayoutDTO[]> {
if (objectMetadataId) {
return this.pageLayoutService.findByObjectMetadataId({
if (objectMetadataId || pageLayoutType) {
return this.pageLayoutService.findBy({
workspaceId: workspace.id,
objectMetadataId,
filter: {
objectMetadataId,
pageLayoutType,
},
});
}
@@ -5,8 +5,8 @@ import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { type FlatPageLayoutMaps } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout-maps.type';
@@ -69,12 +69,15 @@ export class PageLayoutService {
);
}
async findByObjectMetadataId({
async findBy({
workspaceId,
objectMetadataId,
filter: { objectMetadataId, pageLayoutType },
}: {
workspaceId: string;
objectMetadataId: string;
filter: {
objectMetadataId?: string;
pageLayoutType?: PageLayoutType;
};
}): Promise<PageLayoutDTO[]> {
const {
flatPageLayoutMaps,
@@ -86,11 +89,17 @@ export class PageLayoutService {
flatPageLayoutMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter(
(layout) =>
layout.objectMetadataId === objectMetadataId &&
!isDefined(layout.deletedAt),
);
.filter((layout) => {
const isNotDeleted = !isDefined(layout.deletedAt);
const matchesObjectMetadataId = isNonEmptyString(objectMetadataId)
? layout.objectMetadataId === objectMetadataId
: true;
const matchesPageLayoutType = isDefined(pageLayoutType)
? layout.type === pageLayoutType
: true;
return isNotDeleted && matchesObjectMetadataId && matchesPageLayoutType;
});
return activeLayouts.map((layout) =>
fromFlatPageLayoutWithTabsAndWidgetsToPageLayoutDto(