Migrate pagelayout position frontend (#18229)

## Context
Part 1 of migrating gridPosition in favor of typed position
FE should now always send both values to the BE and use both.

Next steps: 
- Update the backend to enforce and validate the new position field + DB
migrations gridPositon -> position (type: GRID)
- Cleanup frontend usage
- Cleanup backend
This commit is contained in:
Weiko
2026-03-02 14:42:30 +01:00
committed by GitHub
parent 78a0197643
commit 37bcb35391
15 changed files with 140 additions and 14 deletions
@@ -1,6 +1,7 @@
import {
AggregateOperations,
GraphOrderBy,
PageLayoutTabLayoutMode,
WidgetConfigurationType,
WidgetType,
type PageLayoutWidget,
@@ -73,12 +74,28 @@ describe('convertLayoutsToWidgets', () => {
columnSpan: 4,
rowSpan: 5,
});
expect(result[0].position).toEqual({
__typename: 'PageLayoutWidgetGridPosition',
layoutMode: PageLayoutTabLayoutMode.GRID,
column: 2,
row: 3,
columnSpan: 4,
rowSpan: 5,
});
expect(result[1].gridPosition).toEqual({
column: 6,
row: 7,
columnSpan: 8,
rowSpan: 9,
});
expect(result[1].position).toEqual({
__typename: 'PageLayoutWidgetGridPosition',
layoutMode: PageLayoutTabLayoutMode.GRID,
column: 6,
row: 7,
columnSpan: 8,
rowSpan: 9,
});
});
it('should use defaults when layout not found', () => {
@@ -84,6 +84,13 @@ describe('convertPageLayoutDraftToUpdateInput', () => {
rowSpan: 4,
columnSpan: 5,
});
expect(result.tabs[0].widgets[0].position).toEqual({
layoutMode: 'GRID',
row: 2,
column: 3,
rowSpan: 4,
columnSpan: 5,
});
});
it('should handle objectMetadataId as null when not provided', () => {
@@ -3,6 +3,7 @@ import {
AxisNameDisplay,
BarChartLayout,
GraphOrderBy,
PageLayoutTabLayoutMode,
WidgetConfigurationType,
} from '~/generated-metadata/graphql';
import { createDefaultGraphWidget } from '@/page-layout/utils/createDefaultGraphWidget';
@@ -67,6 +68,14 @@ describe('createDefaultGraphWidget', () => {
expect(widget.pageLayoutTabId).toBe('tab-1');
expect(widget.title).toBe('Test Widget');
expect(widget.gridPosition).toEqual(baseParams.gridPosition);
expect(widget.position).toEqual({
__typename: 'PageLayoutWidgetGridPosition',
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 2,
columnSpan: 2,
});
expect(widget.createdAt).toBeDefined();
expect(widget.updatedAt).toBeDefined();
expect(widget.deletedAt).toBeNull();
@@ -1,4 +1,7 @@
import { WidgetType } from '~/generated-metadata/graphql';
import {
PageLayoutTabLayoutMode,
WidgetType,
} from '~/generated-metadata/graphql';
import { createDefaultStandaloneRichTextWidget } from '@/page-layout/utils/createDefaultStandaloneRichTextWidget';
describe('createDefaultStandaloneRichTextWidget', () => {
@@ -20,6 +23,14 @@ describe('createDefaultStandaloneRichTextWidget', () => {
body: { blocknote: '[{"type":"paragraph","content":"Test"}]' },
},
gridPosition: { row: 0, column: 0, rowSpan: 4, columnSpan: 4 },
position: {
__typename: 'PageLayoutWidgetGridPosition',
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 4,
columnSpan: 4,
},
});
});
@@ -1,5 +1,6 @@
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { type Layouts } from 'react-grid-layout';
import { PageLayoutTabLayoutMode } from '~/generated-metadata/graphql';
export const convertLayoutsToWidgets = (
widgets: PageLayoutWidget[],
@@ -10,13 +11,26 @@ export const convertLayoutsToWidgets = (
return widgets.map((widget) => {
const layout = activeLayouts.find((l) => l.i === widget.id);
const row = layout?.y ?? 0;
const column = layout?.x ?? 0;
const rowSpan = layout?.h ?? 2;
const columnSpan = layout?.w ?? 2;
return {
...widget,
gridPosition: {
row: layout?.y ?? 0,
column: layout?.x ?? 0,
rowSpan: layout?.h ?? 2,
columnSpan: layout?.w ?? 2,
row,
column,
rowSpan,
columnSpan,
},
position: {
__typename: 'PageLayoutWidgetGridPosition' as const,
layoutMode: PageLayoutTabLayoutMode.GRID,
row,
column,
rowSpan,
columnSpan,
},
};
});
@@ -1,6 +1,9 @@
import { type DraftPageLayout } from '@/page-layout/types/DraftPageLayout';
import { isDynamicRelationWidget } from '@/page-layout/utils/isDynamicRelationWidget';
import { type UpdatePageLayoutWithTabsInput } from '~/generated-metadata/graphql';
import {
PageLayoutTabLayoutMode,
type UpdatePageLayoutWithTabsInput,
} from '~/generated-metadata/graphql';
export const convertPageLayoutDraftToUpdateInput = (
pageLayoutDraft: DraftPageLayout,
@@ -27,6 +30,14 @@ export const convertPageLayoutDraftToUpdateInput = (
rowSpan: widget.gridPosition.rowSpan,
columnSpan: widget.gridPosition.columnSpan,
},
position: {
layoutMode:
widget.position?.layoutMode ?? PageLayoutTabLayoutMode.GRID,
row: widget.gridPosition.row,
column: widget.gridPosition.column,
rowSpan: widget.gridPosition.rowSpan,
columnSpan: widget.gridPosition.columnSpan,
},
configuration: widget.configuration ?? null,
})),
})),
@@ -32,12 +32,23 @@ export const convertPageLayoutToTabLayouts = (
}
}
const gridPos =
isDefined(widget.position) &&
widget.position.__typename === 'PageLayoutWidgetGridPosition'
? {
row: widget.position.row,
column: widget.position.column,
rowSpan: widget.position.rowSpan,
columnSpan: widget.position.columnSpan,
}
: widget.gridPosition;
return {
i: widget.id,
x: widget.gridPosition.column,
y: widget.gridPosition.row,
w: widget.gridPosition.columnSpan,
h: widget.gridPosition.rowSpan,
x: gridPos.column,
y: gridPos.row,
w: gridPos.columnSpan,
h: gridPos.rowSpan,
minW,
minH,
};
@@ -1,5 +1,6 @@
import {
type GridPosition,
PageLayoutTabLayoutMode,
WidgetConfigurationType,
WidgetType,
} from '~/generated-metadata/graphql';
@@ -24,6 +25,14 @@ export const createDefaultFrontComponentWidget = (
frontComponentId,
},
gridPosition,
position: {
__typename: 'PageLayoutWidgetGridPosition',
layoutMode: PageLayoutTabLayoutMode.GRID,
row: gridPosition.row,
column: gridPosition.column,
rowSpan: gridPosition.rowSpan,
columnSpan: gridPosition.columnSpan,
},
objectMetadataId: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@@ -4,6 +4,7 @@ import {
BarChartLayout,
GraphOrderBy,
type GridPosition,
PageLayoutTabLayoutMode,
type PageLayoutWidget,
type WidgetConfiguration,
WidgetConfigurationType,
@@ -74,6 +75,14 @@ export const createDefaultGraphWidget = ({
type: WidgetType.GRAPH,
configuration,
gridPosition,
position: {
__typename: 'PageLayoutWidgetGridPosition',
layoutMode: PageLayoutTabLayoutMode.GRID,
row: gridPosition.row,
column: gridPosition.column,
rowSpan: gridPosition.rowSpan,
columnSpan: gridPosition.columnSpan,
},
objectMetadataId: resolvedObjectMetadataId,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@@ -1,5 +1,6 @@
import {
type GridPosition,
PageLayoutTabLayoutMode,
type PageLayoutWidget,
WidgetConfigurationType,
WidgetType,
@@ -24,6 +25,14 @@ export const createDefaultIframeWidget = (
url,
},
gridPosition,
position: {
__typename: 'PageLayoutWidgetGridPosition',
layoutMode: PageLayoutTabLayoutMode.GRID,
row: gridPosition.row,
column: gridPosition.column,
rowSpan: gridPosition.rowSpan,
columnSpan: gridPosition.columnSpan,
},
objectMetadataId: objectMetadataId ?? null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@@ -1,5 +1,6 @@
import {
type GridPosition,
PageLayoutTabLayoutMode,
type PageLayoutWidget,
type RichTextV2Body,
WidgetConfigurationType,
@@ -24,6 +25,14 @@ export const createDefaultStandaloneRichTextWidget = (
body,
},
gridPosition,
position: {
__typename: 'PageLayoutWidgetGridPosition',
layoutMode: PageLayoutTabLayoutMode.GRID,
row: gridPosition.row,
column: gridPosition.column,
rowSpan: gridPosition.rowSpan,
columnSpan: gridPosition.columnSpan,
},
objectMetadataId: objectMetadataId ?? null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@@ -4,6 +4,7 @@ import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { DYNAMIC_RELATION_WIDGET_ID_PREFIX } from '@/page-layout/utils/isDynamicRelationWidget';
import { isDefined } from 'twenty-shared/utils';
import {
PageLayoutTabLayoutMode,
WidgetConfigurationType,
WidgetType,
} from '~/generated-metadata/graphql';
@@ -25,6 +26,14 @@ const getRelationFieldWidgetToInsert = (
rowSpan: 1,
columnSpan: 12,
},
position: {
__typename: 'PageLayoutWidgetGridPosition' as const,
layoutMode: PageLayoutTabLayoutMode.GRID,
row: 0,
column: 0,
rowSpan: 1,
columnSpan: 12,
},
configuration: {
__typename: 'FieldConfiguration' as const,
configurationType: WidgetConfigurationType.FIELD,
@@ -30,6 +30,7 @@ export class WorkspaceFlatFrontComponentMapCacheService extends WorkspaceCachePr
const [frontComponents, applications] = await Promise.all([
this.frontComponentRepository.find({
where: { workspaceId },
withDeleted: true,
}),
this.applicationRepository.find({
where: { workspaceId },
@@ -329,7 +329,7 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
}
case WidgetConfigurationType.FRONT_COMPONENT: {
const { frontComponentId, ...rest } = configuration;
const { frontComponentId, configurationType } = configuration;
const frontComponentUniversalIdentifier: string | null =
frontComponentUniversalIdentifierById[frontComponentId] ?? null;
@@ -345,7 +345,7 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
}
return {
...rest,
configurationType,
frontComponentUniversalIdentifier,
};
}
@@ -313,7 +313,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
}
case WidgetConfigurationType.FRONT_COMPONENT: {
const { frontComponentUniversalIdentifier, ...rest } =
const { frontComponentUniversalIdentifier, configurationType } =
universalConfiguration;
if (!isDefined(frontComponentUniversalIdentifier)) {
@@ -336,7 +336,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
}
return {
...rest,
configurationType,
frontComponentId: flatFrontComponent.id,
};
}