Skip to content

Commit da1b1f1

Browse files
authored
Combine and clean upgrade commands for record page layouts (#19004)
- Remove the label identifier field for all standard objects as it's a first-class citizen that is displayed specifically in the app; it doesn't make a lot of sense to display it in the Fields widgets - Disable logic that made the label identifier required and in first position - Add all fields for all standard objects in record page layout view fields - Do not include position and ts vector fields in custom objects > [!IMPORTANT] > The command will create Field widgets for all relations. It is consistent to the way the frontend dynamically generates them as of today. We will have to decide which relations we pin as individual Field widgets before the release. (This will likely land in this command or in another one.)
1 parent 31718d1 commit da1b1f1

File tree

30 files changed

+3319
-3984
lines changed

30 files changed

+3319
-3984
lines changed

packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command.ts

Lines changed: 0 additions & 431 deletions
This file was deleted.

packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command.ts

Lines changed: 868 additions & 0 deletions
Large diffs are not rendered by default.

packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command.ts

Lines changed: 0 additions & 485 deletions
This file was deleted.

packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-upgrade-version-command.module.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import { Module } from '@nestjs/common';
22
import { TypeOrmModule } from '@nestjs/typeorm';
33

44
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
5-
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
65
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
7-
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
6+
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
87
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
98
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
109
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
@@ -67,9 +66,8 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
6766
IdentifyObjectPermissionMetadataCommand,
6867
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
6968
BackfillCommandMenuItemsCommand,
70-
BackfillFieldWidgetsCommand,
7169
BackfillNavigationMenuItemTypeCommand,
72-
BackfillPageLayoutsCommand,
70+
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
7371
BackfillSelectFieldOptionIdsCommand,
7472
DeleteOrphanNavigationMenuItemsCommand,
7573
GenerateApplicationSdkClientsCommand,
@@ -85,9 +83,8 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
8583
IdentifyObjectPermissionMetadataCommand,
8684
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
8785
BackfillCommandMenuItemsCommand,
88-
BackfillFieldWidgetsCommand,
8986
BackfillNavigationMenuItemTypeCommand,
90-
BackfillPageLayoutsCommand,
87+
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
9188
BackfillSelectFieldOptionIdsCommand,
9289
DeleteOrphanNavigationMenuItemsCommand,
9390
GenerateApplicationSdkClientsCommand,

packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,18 @@ import { BackfillSystemFieldsIsSystemCommand } from 'src/database/commands/upgra
3434
import { FixInvalidStandardUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-fix-invalid-standard-universal-identifiers.command';
3535
import { SeedServerIdCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-seed-server-id.command';
3636
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
37-
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
3837
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
39-
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
40-
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
38+
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
4139
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
40+
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
41+
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
4242
import { IdentifyObjectPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-object-permission-metadata.command';
4343
import { IdentifyPermissionFlagMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-permission-flag-metadata.command';
4444
import { MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-object-permission-universal-identifier-and-application-id-not-nullable-migration.command';
4545
import { MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-permission-flag-universal-identifier-and-application-id-not-nullable-migration.command';
4646
import { MakeWorkflowSearchableCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-workflow-searchable.command';
4747
import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-messaging-infrastructure-to-metadata.command';
4848
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
49-
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
5049
import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command';
5150
import { UpdateStandardIndexViewNamesCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-update-standard-index-view-names.command';
5251
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@@ -106,12 +105,11 @@ export class UpgradeCommand extends UpgradeCommandRunner {
106105
protected readonly backfillNavigationMenuItemTypeCommand: BackfillNavigationMenuItemTypeCommand,
107106
protected readonly backfillCommandMenuItemsCommand: BackfillCommandMenuItemsCommand,
108107
protected readonly deleteOrphanNavigationMenuItemsCommand: DeleteOrphanNavigationMenuItemsCommand,
109-
protected readonly backfillPageLayoutsCommand: BackfillPageLayoutsCommand,
108+
protected readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
110109
protected readonly generateApplicationSdkClientsCommand: GenerateApplicationSdkClientsCommand,
111110
protected readonly seedCliApplicationRegistrationCommand: SeedCliApplicationRegistrationCommand,
112111
protected readonly migrateRichTextToTextCommand: MigrateRichTextToTextCommand,
113112
protected readonly migrateMessagingInfrastructureToMetadataCommand: MigrateMessagingInfrastructureToMetadataCommand,
114-
protected readonly backfillFieldWidgetsCommand: BackfillFieldWidgetsCommand,
115113
protected readonly backfillSelectFieldOptionIdsCommand: BackfillSelectFieldOptionIdsCommand,
116114
protected readonly updateStandardIndexViewNamesCommand: UpdateStandardIndexViewNamesCommand,
117115
protected readonly makeWorkflowSearchableCommand: MakeWorkflowSearchableCommand,
@@ -172,10 +170,9 @@ export class UpgradeCommand extends UpgradeCommandRunner {
172170
this.migrateRichTextToTextCommand,
173171
this.deleteOrphanNavigationMenuItemsCommand,
174172
this.backfillCommandMenuItemsCommand,
175-
this.backfillPageLayoutsCommand,
173+
this.backfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
176174
this.seedCliApplicationRegistrationCommand,
177175
this.migrateMessagingInfrastructureToMetadataCommand,
178-
this.backfillFieldWidgetsCommand,
179176
this.backfillSelectFieldOptionIdsCommand,
180177
this.updateStandardIndexViewNamesCommand,
181178
this.makeWorkflowSearchableCommand,

packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
455455
flatObjectMetadataToCreate.labelIdentifierFieldMetadataUniversalIdentifier,
456456
viewUniversalIdentifier:
457457
flatRecordPageFieldsViewToCreate.universalIdentifier,
458+
excludeLabelIdentifier: true,
458459
});
459460

460461
flatDefaultRecordPageLayoutsToCreate =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { FieldMetadataType } from 'twenty-shared/types';
2+
import { v4 } from 'uuid';
3+
4+
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
5+
6+
import { computeFlatViewFieldsToCreate } from '../compute-flat-view-fields-to-create.util';
7+
8+
const makeFieldMetadata = (
9+
overrides: Partial<UniversalFlatFieldMetadata> & {
10+
name: string;
11+
type: FieldMetadataType;
12+
},
13+
): UniversalFlatFieldMetadata => {
14+
const universalIdentifier = overrides.universalIdentifier ?? v4();
15+
16+
return {
17+
universalIdentifier,
18+
objectMetadataUniversalIdentifier: 'object-uid',
19+
applicationUniversalIdentifier: 'app-uid',
20+
name: overrides.name,
21+
label: overrides.label ?? overrides.name,
22+
type: overrides.type,
23+
isCustom: overrides.isCustom ?? false,
24+
isActive: true,
25+
isSystem: false,
26+
isUIReadOnly: false,
27+
isNullable: true,
28+
isUnique: false,
29+
isLabelSyncedWithName: false,
30+
defaultValue: null,
31+
description: null,
32+
icon: null,
33+
options: null,
34+
settings: null,
35+
standardOverrides: null,
36+
relationTargetObjectMetadataUniversalIdentifier: null,
37+
createdAt: '2026-01-01T00:00:00.000Z',
38+
updatedAt: '2026-01-01T00:00:00.000Z',
39+
deletedAt: null,
40+
} as unknown as UniversalFlatFieldMetadata;
41+
};
42+
43+
const flatApplication = {
44+
universalIdentifier: 'app-uid',
45+
id: 'app-id',
46+
} as never;
47+
48+
const viewUniversalIdentifier = 'view-uid';
49+
50+
describe('computeFlatViewFieldsToCreate', () => {
51+
it('should exclude TS_VECTOR fields', () => {
52+
const fields = [
53+
makeFieldMetadata({
54+
name: 'name',
55+
type: FieldMetadataType.TEXT,
56+
}),
57+
makeFieldMetadata({
58+
name: 'searchVector',
59+
type: FieldMetadataType.TS_VECTOR,
60+
}),
61+
];
62+
63+
const result = computeFlatViewFieldsToCreate({
64+
objectFlatFieldMetadatas: fields,
65+
viewUniversalIdentifier,
66+
flatApplication,
67+
labelIdentifierFieldMetadataUniversalIdentifier: null,
68+
});
69+
70+
expect(result).toHaveLength(1);
71+
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
72+
fields[0].universalIdentifier,
73+
);
74+
});
75+
76+
it('should exclude POSITION fields', () => {
77+
const fields = [
78+
makeFieldMetadata({
79+
name: 'name',
80+
type: FieldMetadataType.TEXT,
81+
}),
82+
makeFieldMetadata({
83+
name: 'position',
84+
type: FieldMetadataType.POSITION,
85+
}),
86+
];
87+
88+
const result = computeFlatViewFieldsToCreate({
89+
objectFlatFieldMetadatas: fields,
90+
viewUniversalIdentifier,
91+
flatApplication,
92+
labelIdentifierFieldMetadataUniversalIdentifier: null,
93+
});
94+
95+
expect(result).toHaveLength(1);
96+
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
97+
fields[0].universalIdentifier,
98+
);
99+
});
100+
101+
it('should exclude RELATION and MORPH_RELATION fields', () => {
102+
const fields = [
103+
makeFieldMetadata({
104+
name: 'name',
105+
type: FieldMetadataType.TEXT,
106+
}),
107+
makeFieldMetadata({
108+
name: 'company',
109+
type: FieldMetadataType.RELATION,
110+
}),
111+
makeFieldMetadata({
112+
name: 'target',
113+
type: FieldMetadataType.MORPH_RELATION,
114+
}),
115+
];
116+
117+
const result = computeFlatViewFieldsToCreate({
118+
objectFlatFieldMetadatas: fields,
119+
viewUniversalIdentifier,
120+
flatApplication,
121+
labelIdentifierFieldMetadataUniversalIdentifier: null,
122+
});
123+
124+
expect(result).toHaveLength(1);
125+
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
126+
fields[0].universalIdentifier,
127+
);
128+
});
129+
130+
it('should exclude deletedAt field', () => {
131+
const fields = [
132+
makeFieldMetadata({
133+
name: 'name',
134+
type: FieldMetadataType.TEXT,
135+
}),
136+
makeFieldMetadata({
137+
name: 'deletedAt',
138+
type: FieldMetadataType.DATE_TIME,
139+
}),
140+
];
141+
142+
const result = computeFlatViewFieldsToCreate({
143+
objectFlatFieldMetadatas: fields,
144+
viewUniversalIdentifier,
145+
flatApplication,
146+
labelIdentifierFieldMetadataUniversalIdentifier: null,
147+
});
148+
149+
expect(result).toHaveLength(1);
150+
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
151+
fields[0].universalIdentifier,
152+
);
153+
});
154+
155+
it('should place label identifier field first', () => {
156+
const labelField = makeFieldMetadata({
157+
name: 'name',
158+
type: FieldMetadataType.TEXT,
159+
});
160+
const otherField = makeFieldMetadata({
161+
name: 'createdAt',
162+
type: FieldMetadataType.DATE_TIME,
163+
});
164+
const fields = [otherField, labelField];
165+
166+
const result = computeFlatViewFieldsToCreate({
167+
objectFlatFieldMetadatas: fields,
168+
viewUniversalIdentifier,
169+
flatApplication,
170+
labelIdentifierFieldMetadataUniversalIdentifier:
171+
labelField.universalIdentifier,
172+
});
173+
174+
expect(result).toHaveLength(2);
175+
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
176+
labelField.universalIdentifier,
177+
);
178+
expect(result[0].position).toBe(0);
179+
expect(result[1].fieldMetadataUniversalIdentifier).toBe(
180+
otherField.universalIdentifier,
181+
);
182+
expect(result[1].position).toBe(1);
183+
});
184+
185+
it('should exclude label identifier when excludeLabelIdentifier is true', () => {
186+
const labelField = makeFieldMetadata({
187+
name: 'name',
188+
type: FieldMetadataType.TEXT,
189+
});
190+
const otherField = makeFieldMetadata({
191+
name: 'createdAt',
192+
type: FieldMetadataType.DATE_TIME,
193+
});
194+
195+
const result = computeFlatViewFieldsToCreate({
196+
objectFlatFieldMetadatas: [labelField, otherField],
197+
viewUniversalIdentifier,
198+
flatApplication,
199+
labelIdentifierFieldMetadataUniversalIdentifier:
200+
labelField.universalIdentifier,
201+
excludeLabelIdentifier: true,
202+
});
203+
204+
expect(result).toHaveLength(1);
205+
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
206+
otherField.universalIdentifier,
207+
);
208+
});
209+
});

packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,30 @@ export const computeFlatViewFieldsToCreate = ({
1111
viewUniversalIdentifier,
1212
flatApplication,
1313
labelIdentifierFieldMetadataUniversalIdentifier,
14+
excludeLabelIdentifier = false,
1415
}: {
1516
flatApplication: FlatApplication;
1617
objectFlatFieldMetadatas: UniversalFlatFieldMetadata[];
1718
viewUniversalIdentifier: string;
1819
labelIdentifierFieldMetadataUniversalIdentifier: string | null;
20+
excludeLabelIdentifier?: boolean;
1921
}): UniversalFlatViewField[] => {
2022
const createdAt = new Date().toISOString();
2123
const defaultViewFields = objectFlatFieldMetadatas
2224
.filter(
2325
(field) =>
2426
field.name !== 'deletedAt' &&
27+
field.type !== FieldMetadataType.TS_VECTOR &&
28+
field.type !== FieldMetadataType.POSITION &&
2529
field.type !== FieldMetadataType.MORPH_RELATION &&
2630
field.type !== FieldMetadataType.RELATION &&
2731
// Include 'id' only if it's the label identifier (e.g., for junction tables)
2832
(field.name !== 'id' ||
2933
field.universalIdentifier ===
34+
labelIdentifierFieldMetadataUniversalIdentifier) &&
35+
// Exclude label identifier field when requested (e.g., for FIELDS_WIDGET views)
36+
(!excludeLabelIdentifier ||
37+
field.universalIdentifier !==
3038
labelIdentifierFieldMetadataUniversalIdentifier),
3139
)
3240
.sort((a, b) => {

0 commit comments

Comments
 (0)