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.)
This commit is contained in:
Baptiste Devessier
2026-03-26 17:50:29 +01:00
committed by GitHub
parent 31718d163c
commit da1b1f1cbc
30 changed files with 3807 additions and 4472 deletions
@@ -1,431 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import {
CoreObjectNameSingular,
FieldMetadataType,
PageLayoutTabLayoutMode,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget/enums/field-display-mode.enum';
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 { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import {
GRID_POSITIONS,
VERTICAL_LIST_LAYOUT_POSITIONS,
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
const HOME_TAB_POSITION = 10;
const isActivityTargetField = (
fieldName: string,
objectNameSingular: string,
): boolean =>
(objectNameSingular === CoreObjectNameSingular.Note &&
fieldName === 'noteTargets') ||
(objectNameSingular === CoreObjectNameSingular.Task &&
fieldName === 'taskTargets');
const isJunctionRelationField = (field: FlatFieldMetadata): boolean => {
const settings = field.settings as
| { junctionTargetFieldId?: string }
| undefined;
return (
isDefined(settings?.junctionTargetFieldId) &&
typeof settings?.junctionTargetFieldId === 'string' &&
settings.junctionTargetFieldId.length > 0
);
};
const isRelationTargetAvailable = (
targetObject: FlatObjectMetadata | undefined,
): boolean => {
if (!isDefined(targetObject)) {
return false;
}
if (targetObject.isRemote) {
return false;
}
if (
targetObject.isSystem &&
targetObject.nameSingular !== CoreObjectNameSingular.WorkspaceMember
) {
return false;
}
return true;
};
@Command({
name: 'upgrade:1-20:backfill-field-widgets',
description:
'Backfill FIELD widgets for relation fields in existing page layouts',
})
export class BackfillFieldWidgetsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly applicationService: ApplicationService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspaceCacheService: WorkspaceCacheService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of FIELD widgets for workspace ${workspaceId}`,
);
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
);
const {
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatPageLayoutMaps,
flatPageLayoutTabMaps,
flatPageLayoutWidgetMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
'flatPageLayoutMaps',
'flatPageLayoutTabMaps',
'flatPageLayoutWidgetMaps',
]);
// Build a set of fieldMetadataIds that already have a FIELD widget
const existingFieldWidgetFieldIds = new Set<string>();
for (const widget of Object.values(
flatPageLayoutWidgetMaps.byUniversalIdentifier,
)) {
if (
isDefined(widget) &&
widget.configuration?.configurationType ===
WidgetConfigurationType.FIELD &&
isDefined(widget.configuration.fieldMetadataId)
) {
existingFieldWidgetFieldIds.add(widget.configuration.fieldMetadataId);
}
}
// Build object ID → objectMetadata map
const objectById = new Map<string, FlatObjectMetadata>();
for (const obj of Object.values(
flatObjectMetadataMaps.byUniversalIdentifier,
)) {
if (isDefined(obj)) {
objectById.set(obj.id, obj);
}
}
// Build object ID → RECORD_PAGE layout map
const recordPageLayoutByObjectId = new Map<
string,
{ id: string; universalIdentifier: string }
>();
for (const layout of Object.values(
flatPageLayoutMaps.byUniversalIdentifier,
)) {
if (
isDefined(layout) &&
layout.type === PageLayoutType.RECORD_PAGE &&
isDefined(layout.objectMetadataId)
) {
recordPageLayoutByObjectId.set(layout.objectMetadataId, {
id: layout.id,
universalIdentifier: layout.universalIdentifier,
});
}
}
// Build pageLayoutId → home tab map
const homeTabByPageLayoutId = new Map<
string,
{
id: string;
universalIdentifier: string;
widgetCount: number;
}
>();
for (const tab of Object.values(
flatPageLayoutTabMaps.byUniversalIdentifier,
)) {
if (
isDefined(tab) &&
tab.position === HOME_TAB_POSITION &&
tab.layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST
) {
homeTabByPageLayoutId.set(tab.pageLayoutId, {
id: tab.id,
universalIdentifier: tab.universalIdentifier,
widgetCount: tab.widgetIds?.length ?? 0,
});
}
}
// Group fields by objectMetadataId
const fieldsByObjectId = new Map<string, FlatFieldMetadata[]>();
// Map morphId → all field IDs sharing that morphId (for dedup)
const fieldIdsByMorphId = new Map<string, string[]>();
for (const field of Object.values(
flatFieldMetadataMaps.byUniversalIdentifier,
)) {
if (!isDefined(field) || !isDefined(field.objectMetadataId)) {
continue;
}
const list = fieldsByObjectId.get(field.objectMetadataId) ?? [];
list.push(field);
fieldsByObjectId.set(field.objectMetadataId, list);
if (
field.type === FieldMetadataType.MORPH_RELATION &&
isDefined(field.morphId)
) {
const morphFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
morphFieldIds.push(field.id);
fieldIdsByMorphId.set(field.morphId, morphFieldIds);
}
}
const now = new Date().toISOString();
const standardWidgetsToCreate: FlatPageLayoutWidget[] = [];
const customWidgetsToCreate: FlatPageLayoutWidget[] = [];
const processedMorphIds = new Set<string>();
for (const object of objectById.values()) {
const pageLayout = recordPageLayoutByObjectId.get(object.id);
if (!isDefined(pageLayout)) {
continue;
}
const homeTab = homeTabByPageLayoutId.get(pageLayout.id);
if (!isDefined(homeTab)) {
continue;
}
const flatApplication: FlatApplication = object.isCustom
? workspaceCustomFlatApplication
: twentyStandardFlatApplication;
const targetList: FlatPageLayoutWidget[] = object.isCustom
? customWidgetsToCreate
: standardWidgetsToCreate;
const fields = fieldsByObjectId.get(object.id) ?? [];
let nextWidgetIndex = homeTab.widgetCount;
for (const field of fields) {
if (
field.type !== FieldMetadataType.RELATION &&
field.type !== FieldMetadataType.MORPH_RELATION
) {
continue;
}
if (isActivityTargetField(field.name, object.nameSingular)) {
continue;
}
if (isJunctionRelationField(field)) {
continue;
}
if (field.type === FieldMetadataType.RELATION) {
const targetObject = isDefined(field.relationTargetObjectMetadataId)
? objectById.get(field.relationTargetObjectMetadataId)
: undefined;
if (!isRelationTargetAvailable(targetObject)) {
continue;
}
}
// For morph relations, skip if any sibling field (same morphId)
// already has a widget or was already processed in this run
if (
field.type === FieldMetadataType.MORPH_RELATION &&
isDefined(field.morphId)
) {
if (processedMorphIds.has(field.morphId)) {
continue;
}
const siblingFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
const alreadyHasWidget = siblingFieldIds.some((id) =>
existingFieldWidgetFieldIds.has(id),
);
if (alreadyHasWidget) {
continue;
}
processedMorphIds.add(field.morphId);
}
if (existingFieldWidgetFieldIds.has(field.id)) {
continue;
}
const widget: FlatPageLayoutWidget = {
id: v4(),
universalIdentifier: v4(),
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
workspaceId,
pageLayoutTabId: homeTab.id,
pageLayoutTabUniversalIdentifier: homeTab.universalIdentifier,
title: field.label,
type: WidgetType.FIELD,
gridPosition: { ...GRID_POSITIONS.FULL_WIDTH },
position: {
...VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
index: nextWidgetIndex,
},
configuration: {
configurationType: WidgetConfigurationType.FIELD,
fieldMetadataId: field.id,
fieldDisplayMode: FieldDisplayMode.CARD,
},
universalConfiguration: {
configurationType: WidgetConfigurationType.FIELD,
fieldMetadataId: field.universalIdentifier,
fieldDisplayMode: FieldDisplayMode.CARD,
},
objectMetadataId: object.id,
objectMetadataUniversalIdentifier: object.universalIdentifier,
createdAt: now,
updatedAt: now,
deletedAt: null,
conditionalDisplay: null,
overrides: null,
};
targetList.push(widget);
existingFieldWidgetFieldIds.add(field.id);
nextWidgetIndex++;
}
}
const totalWidgets =
standardWidgetsToCreate.length + customWidgetsToCreate.length;
if (totalWidgets === 0) {
this.logger.log(
`All FIELD widgets already exist for workspace ${workspaceId}, skipping`,
);
return;
}
this.logger.log(
`Found ${totalWidgets} FIELD widget(s) to create for workspace ${workspaceId} (${standardWidgetsToCreate.length} standard, ${customWidgetsToCreate.length} custom)`,
);
if (isDryRun) {
this.logger.log(
`[DRY RUN] Would create ${totalWidgets} FIELD widget(s) for workspace ${workspaceId}`,
);
return;
}
if (standardWidgetsToCreate.length > 0) {
const result =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
pageLayoutWidget: {
flatEntityToCreate: standardWidgetsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
applicationUniversalIdentifier:
twentyStandardFlatApplication.universalIdentifier,
},
);
if (result.status === 'fail') {
this.logger.error(
`Failed to create standard FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
);
throw new Error(
`Failed to create standard FIELD widgets for workspace ${workspaceId}`,
);
}
}
if (customWidgetsToCreate.length > 0) {
const result =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
pageLayoutWidget: {
flatEntityToCreate: customWidgetsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (result.status === 'fail') {
this.logger.error(
`Failed to create custom FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
);
throw new Error(
`Failed to create custom FIELD widgets for workspace ${workspaceId}`,
);
}
}
this.logger.log(
`Successfully created ${totalWidgets} FIELD widget(s) for workspace ${workspaceId}`,
);
}
}
@@ -0,0 +1,868 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import {
CoreObjectNameSingular,
FeatureFlagKey,
FieldMetadataType,
PageLayoutTabLayoutMode,
ViewType,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
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 { computeFlatDefaultRecordPageLayoutToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-default-record-page-layout-to-create.util';
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget/enums/field-display-mode.enum';
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 { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import {
GRID_POSITIONS,
VERTICAL_LIST_LAYOUT_POSITIONS,
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type';
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
const HOME_TAB_POSITION = 10;
const isActivityTargetField = (
fieldName: string,
objectNameSingular: string,
): boolean =>
(objectNameSingular === CoreObjectNameSingular.Note &&
fieldName === 'noteTargets') ||
(objectNameSingular === CoreObjectNameSingular.Task &&
fieldName === 'taskTargets');
const isJunctionRelationField = (field: FlatFieldMetadata): boolean => {
const settings = field.settings as
| { junctionTargetFieldId?: string }
| undefined;
return (
isDefined(settings?.junctionTargetFieldId) &&
typeof settings?.junctionTargetFieldId === 'string' &&
settings.junctionTargetFieldId.length > 0
);
};
const isRelationTargetAvailable = (
targetObject: FlatObjectMetadata | undefined,
): boolean => {
if (!isDefined(targetObject)) {
return false;
}
if (targetObject.isRemote) {
return false;
}
if (
targetObject.isSystem &&
targetObject.nameSingular !== CoreObjectNameSingular.WorkspaceMember
) {
return false;
}
return true;
};
@Command({
name: 'upgrade:1-20:backfill-page-layouts-and-fields-widget-view-fields',
description:
'Backfill RECORD_PAGE page layouts, sync FIELDS_WIDGET view fields, create FIELD widgets, and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
})
export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly applicationService: ApplicationService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly featureFlagService: FeatureFlagService,
private readonly workspaceCacheService: WorkspaceCacheService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of page layouts and field widgets for workspace ${workspaceId}`,
);
const isAlreadyEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
workspaceId,
);
if (isAlreadyEnabled) {
this.logger.log(
`IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED already enabled for workspace ${workspaceId}, skipping`,
);
return;
}
if (isDryRun) {
this.logger.log(
`[DRY RUN] Would create page layouts, sync view fields, create FIELD widgets and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED for workspace ${workspaceId}`,
);
return;
}
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
);
await this.backfillStandardPageLayoutsAndSyncViews({
workspaceId,
twentyStandardFlatApplication,
});
await this.backfillCustomObjectPageLayouts({
workspaceId,
workspaceCustomFlatApplication,
});
await this.backfillFieldWidgets({
workspaceId,
twentyStandardFlatApplication,
workspaceCustomFlatApplication,
});
await this.featureFlagService.enableFeatureFlags(
[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED],
workspaceId,
);
this.logger.log(
`Successfully backfilled page layouts and field widgets for workspace ${workspaceId}`,
);
}
private async backfillStandardPageLayoutsAndSyncViews({
workspaceId,
twentyStandardFlatApplication,
}: {
workspaceId: string;
twentyStandardFlatApplication: FlatApplication;
}): Promise<void> {
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
computeTwentyStandardApplicationAllFlatEntityMaps({
shouldIncludeRecordPageLayouts: true,
now: new Date().toISOString(),
workspaceId,
twentyStandardApplicationId: twentyStandardFlatApplication.id,
});
const recordPageLayoutUniversalIdentifiers = new Set<string>();
const recordPageLayoutsFromStandard = Object.values(
standardAllFlatEntityMaps.flatPageLayoutMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((pageLayout) => {
if (pageLayout.type !== PageLayoutType.RECORD_PAGE) {
return false;
}
recordPageLayoutUniversalIdentifiers.add(
pageLayout.universalIdentifier,
);
return true;
});
const tabUniversalIdentifiers = new Set<string>();
const tabsFromStandard = Object.values(
standardAllFlatEntityMaps.flatPageLayoutTabMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((tab) => {
if (
!recordPageLayoutUniversalIdentifiers.has(
tab.pageLayoutUniversalIdentifier,
)
) {
return false;
}
tabUniversalIdentifiers.add(tab.universalIdentifier);
return true;
});
const widgetsFromStandard = Object.values(
standardAllFlatEntityMaps.flatPageLayoutWidgetMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((widget) =>
tabUniversalIdentifiers.has(widget.pageLayoutTabUniversalIdentifier),
);
const {
flatPageLayoutMaps: existingFlatPageLayoutMaps,
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
flatViewMaps: existingFlatViewMaps,
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatPageLayoutMaps',
'flatPageLayoutTabMaps',
'flatPageLayoutWidgetMaps',
'flatViewMaps',
'flatViewFieldMaps',
'flatViewFieldGroupMaps',
]);
// Filter out page layouts, tabs, and widgets that already exist in the workspace
const pageLayoutsToCreate = recordPageLayoutsFromStandard.filter(
(pageLayout) =>
!isDefined(
existingFlatPageLayoutMaps.byUniversalIdentifier[
pageLayout.universalIdentifier
],
),
);
const pageLayoutTabsToCreate = tabsFromStandard.filter(
(tab) =>
!isDefined(
existingFlatPageLayoutTabMaps.byUniversalIdentifier[
tab.universalIdentifier
],
),
);
const pageLayoutWidgetsToCreate = widgetsFromStandard.filter(
(widget) =>
!isDefined(
existingFlatPageLayoutWidgetMaps.byUniversalIdentifier[
widget.universalIdentifier
],
),
);
// Collect all standard FIELDS_WIDGET view universal identifiers
// This includes both new views to create AND existing views (created by 1-19)
const allStandardFieldsWidgetViewUniversalIds = new Set<string>();
const viewsToCreate = Object.values(
standardAllFlatEntityMaps.flatViewMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((view) => {
if (view.type !== ViewType.FIELDS_WIDGET) {
return false;
}
allStandardFieldsWidgetViewUniversalIds.add(view.universalIdentifier);
if (
isDefined(
existingFlatViewMaps.byUniversalIdentifier[
view.universalIdentifier
],
)
) {
return false;
}
return true;
});
// Delete all existing viewFields for standard FIELDS_WIDGET views and recreate from current standard.
// This ensures positions, visibility, and groups match the current standard definition.
const viewFieldsToDelete = Object.values(
existingFlatViewFieldMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((viewField) =>
allStandardFieldsWidgetViewUniversalIds.has(
viewField.viewUniversalIdentifier,
),
);
const viewFieldsToCreate = Object.values(
standardAllFlatEntityMaps.flatViewFieldMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((viewField) =>
allStandardFieldsWidgetViewUniversalIds.has(
viewField.viewUniversalIdentifier,
),
);
// Same delete-and-recreate approach for viewFieldGroups
const viewFieldGroupsToDelete = Object.values(
existingFlatViewFieldGroupMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((viewFieldGroup) =>
allStandardFieldsWidgetViewUniversalIds.has(
viewFieldGroup.viewUniversalIdentifier,
),
);
const viewFieldGroupsToCreate = Object.values(
standardAllFlatEntityMaps.flatViewFieldGroupMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((viewFieldGroup) =>
allStandardFieldsWidgetViewUniversalIds.has(
viewFieldGroup.viewUniversalIdentifier,
),
);
this.logger.log(
`Creating ${pageLayoutsToCreate.length} page layouts, ${viewsToCreate.length} views, deleting ${viewFieldsToDelete.length} and creating ${viewFieldsToCreate.length} view fields, deleting ${viewFieldGroupsToDelete.length} and creating ${viewFieldGroupsToCreate.length} view field groups for workspace ${workspaceId}`,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
pageLayout: {
flatEntityToCreate: pageLayoutsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
pageLayoutTab: {
flatEntityToCreate: pageLayoutTabsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
pageLayoutWidget: {
flatEntityToCreate: pageLayoutWidgetsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
view: {
flatEntityToCreate: viewsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
viewField: {
flatEntityToCreate: viewFieldsToCreate,
flatEntityToDelete: viewFieldsToDelete,
flatEntityToUpdate: [],
},
viewFieldGroup: {
flatEntityToCreate: viewFieldGroupsToCreate,
flatEntityToDelete: viewFieldGroupsToDelete,
flatEntityToUpdate: [],
},
},
workspaceId,
applicationUniversalIdentifier:
twentyStandardFlatApplication.universalIdentifier,
},
);
if (validateAndBuildResult.status === 'fail') {
this.logger.error(
`Failed to create standard page layouts and sync view fields:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
);
throw new Error(
`Failed to create standard page layouts and sync view fields for workspace ${workspaceId}`,
);
}
}
private async backfillCustomObjectPageLayouts({
workspaceId,
workspaceCustomFlatApplication,
}: {
workspaceId: string;
workspaceCustomFlatApplication: FlatApplication;
}): Promise<void> {
const {
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatPageLayoutMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
'flatPageLayoutMaps',
]);
const existingPageLayouts = Object.values(
flatPageLayoutMaps.byUniversalIdentifier,
).filter(isDefined);
const objectIdsWithRecordPageLayout = new Set(
existingPageLayouts
.filter(
(layout: FlatPageLayout) =>
layout.type === PageLayoutType.RECORD_PAGE &&
isDefined(layout.objectMetadataId),
)
.map((layout: FlatPageLayout) => layout.objectMetadataId),
);
const customObjectsWithoutPageLayout = Object.values(
flatObjectMetadataMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter(
(objectMetadata) =>
objectMetadata.isCustom &&
!objectMetadata.isRemote &&
!objectIdsWithRecordPageLayout.has(objectMetadata.id),
);
if (customObjectsWithoutPageLayout.length === 0) {
this.logger.log(
`No custom objects without page layouts found for workspace ${workspaceId}`,
);
return;
}
this.logger.log(
`Creating page layouts for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}`,
);
const allCustomPageLayoutsToCreate: FlatPageLayout[] = [];
const allCustomPageLayoutTabsToCreate: FlatPageLayoutTab[] = [];
const allCustomPageLayoutWidgetsToCreate: FlatPageLayoutWidget[] = [];
const allCustomViewsToCreate: (UniversalFlatView & { id: string })[] = [];
const allCustomViewFieldsToCreate: UniversalFlatViewField[] = [];
for (const customObject of customObjectsWithoutPageLayout) {
const flatRecordPageFieldsView = computeFlatRecordPageFieldsViewToCreate({
objectMetadata: customObject,
flatApplication: workspaceCustomFlatApplication,
});
const objectFieldMetadatas = Object.values(
flatFieldMetadataMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((field) => field.objectMetadataId === customObject.id);
const viewFields = computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas: objectFieldMetadatas,
viewUniversalIdentifier: flatRecordPageFieldsView.universalIdentifier,
flatApplication: workspaceCustomFlatApplication,
labelIdentifierFieldMetadataUniversalIdentifier:
customObject.labelIdentifierFieldMetadataUniversalIdentifier,
excludeLabelIdentifier: true,
});
const { pageLayouts, pageLayoutTabs, pageLayoutWidgets } =
computeFlatDefaultRecordPageLayoutToCreate({
objectMetadata: customObject,
flatApplication: workspaceCustomFlatApplication,
recordPageFieldsView: flatRecordPageFieldsView,
workspaceId,
});
allCustomPageLayoutsToCreate.push(...pageLayouts);
allCustomPageLayoutTabsToCreate.push(...pageLayoutTabs);
allCustomPageLayoutWidgetsToCreate.push(...pageLayoutWidgets);
allCustomViewsToCreate.push(flatRecordPageFieldsView);
allCustomViewFieldsToCreate.push(...viewFields);
}
const customValidateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
pageLayout: {
flatEntityToCreate: allCustomPageLayoutsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
pageLayoutTab: {
flatEntityToCreate: allCustomPageLayoutTabsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
pageLayoutWidget: {
flatEntityToCreate: allCustomPageLayoutWidgetsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
view: {
flatEntityToCreate: allCustomViewsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
viewField: {
flatEntityToCreate: allCustomViewFieldsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (customValidateAndBuildResult.status === 'fail') {
this.logger.error(
`Failed to create custom object page layouts:\n${JSON.stringify(customValidateAndBuildResult, null, 2)}`,
);
throw new Error(
`Failed to create custom object page layouts for workspace ${workspaceId}`,
);
}
this.logger.log(
`Successfully created page layouts for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}`,
);
}
private async backfillFieldWidgets({
workspaceId,
twentyStandardFlatApplication,
workspaceCustomFlatApplication,
}: {
workspaceId: string;
twentyStandardFlatApplication: FlatApplication;
workspaceCustomFlatApplication: FlatApplication;
}): Promise<void> {
const {
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatPageLayoutMaps,
flatPageLayoutTabMaps,
flatPageLayoutWidgetMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
'flatPageLayoutMaps',
'flatPageLayoutTabMaps',
'flatPageLayoutWidgetMaps',
]);
// Build a set of fieldMetadataIds that already have a FIELD widget
const existingFieldWidgetFieldIds = new Set<string>();
for (const widget of Object.values(
flatPageLayoutWidgetMaps.byUniversalIdentifier,
)) {
if (
isDefined(widget) &&
widget.configuration?.configurationType ===
WidgetConfigurationType.FIELD &&
isDefined(widget.configuration.fieldMetadataId)
) {
existingFieldWidgetFieldIds.add(widget.configuration.fieldMetadataId);
}
}
// Build object ID → objectMetadata map
const objectById = new Map<string, FlatObjectMetadata>();
for (const obj of Object.values(
flatObjectMetadataMaps.byUniversalIdentifier,
)) {
if (isDefined(obj)) {
objectById.set(obj.id, obj);
}
}
// Build object ID → RECORD_PAGE layout map
const recordPageLayoutByObjectId = new Map<
string,
{ id: string; universalIdentifier: string }
>();
for (const layout of Object.values(
flatPageLayoutMaps.byUniversalIdentifier,
)) {
if (
isDefined(layout) &&
layout.type === PageLayoutType.RECORD_PAGE &&
isDefined(layout.objectMetadataId)
) {
recordPageLayoutByObjectId.set(layout.objectMetadataId, {
id: layout.id,
universalIdentifier: layout.universalIdentifier,
});
}
}
// Build pageLayoutId → home tab map
const homeTabByPageLayoutId = new Map<
string,
{
id: string;
universalIdentifier: string;
widgetCount: number;
}
>();
for (const tab of Object.values(
flatPageLayoutTabMaps.byUniversalIdentifier,
)) {
if (
isDefined(tab) &&
tab.position === HOME_TAB_POSITION &&
tab.layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST
) {
homeTabByPageLayoutId.set(tab.pageLayoutId, {
id: tab.id,
universalIdentifier: tab.universalIdentifier,
widgetCount: tab.widgetIds?.length ?? 0,
});
}
}
// Group fields by objectMetadataId
const fieldsByObjectId = new Map<string, FlatFieldMetadata[]>();
// Map morphId → all field IDs sharing that morphId (for dedup)
const fieldIdsByMorphId = new Map<string, string[]>();
for (const field of Object.values(
flatFieldMetadataMaps.byUniversalIdentifier,
)) {
if (!isDefined(field) || !isDefined(field.objectMetadataId)) {
continue;
}
const list = fieldsByObjectId.get(field.objectMetadataId) ?? [];
list.push(field);
fieldsByObjectId.set(field.objectMetadataId, list);
if (
field.type === FieldMetadataType.MORPH_RELATION &&
isDefined(field.morphId)
) {
const morphFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
morphFieldIds.push(field.id);
fieldIdsByMorphId.set(field.morphId, morphFieldIds);
}
}
const now = new Date().toISOString();
const standardWidgetsToCreate: FlatPageLayoutWidget[] = [];
const customWidgetsToCreate: FlatPageLayoutWidget[] = [];
const processedMorphIds = new Set<string>();
for (const object of objectById.values()) {
const pageLayout = recordPageLayoutByObjectId.get(object.id);
if (!isDefined(pageLayout)) {
continue;
}
const homeTab = homeTabByPageLayoutId.get(pageLayout.id);
if (!isDefined(homeTab)) {
continue;
}
const flatApplication: FlatApplication = object.isCustom
? workspaceCustomFlatApplication
: twentyStandardFlatApplication;
const targetList: FlatPageLayoutWidget[] = object.isCustom
? customWidgetsToCreate
: standardWidgetsToCreate;
const fields = fieldsByObjectId.get(object.id) ?? [];
let nextWidgetIndex = homeTab.widgetCount;
for (const field of fields) {
if (
field.type !== FieldMetadataType.RELATION &&
field.type !== FieldMetadataType.MORPH_RELATION
) {
continue;
}
if (isActivityTargetField(field.name, object.nameSingular)) {
continue;
}
if (isJunctionRelationField(field)) {
continue;
}
if (field.type === FieldMetadataType.RELATION) {
const targetObject = isDefined(field.relationTargetObjectMetadataId)
? objectById.get(field.relationTargetObjectMetadataId)
: undefined;
if (!isRelationTargetAvailable(targetObject)) {
continue;
}
}
// For morph relations, skip if any sibling field (same morphId)
// already has a widget or was already processed in this run
if (
field.type === FieldMetadataType.MORPH_RELATION &&
isDefined(field.morphId)
) {
if (processedMorphIds.has(field.morphId)) {
continue;
}
const siblingFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
const alreadyHasWidget = siblingFieldIds.some((id) =>
existingFieldWidgetFieldIds.has(id),
);
if (alreadyHasWidget) {
continue;
}
processedMorphIds.add(field.morphId);
}
if (existingFieldWidgetFieldIds.has(field.id)) {
continue;
}
const widget: FlatPageLayoutWidget = {
id: v4(),
universalIdentifier: v4(),
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
workspaceId,
pageLayoutTabId: homeTab.id,
pageLayoutTabUniversalIdentifier: homeTab.universalIdentifier,
title: field.label,
type: WidgetType.FIELD,
gridPosition: { ...GRID_POSITIONS.FULL_WIDTH },
position: {
...VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
index: nextWidgetIndex,
},
configuration: {
configurationType: WidgetConfigurationType.FIELD,
fieldMetadataId: field.id,
fieldDisplayMode: FieldDisplayMode.CARD,
},
universalConfiguration: {
configurationType: WidgetConfigurationType.FIELD,
fieldMetadataId: field.universalIdentifier,
fieldDisplayMode: FieldDisplayMode.CARD,
},
objectMetadataId: object.id,
objectMetadataUniversalIdentifier: object.universalIdentifier,
createdAt: now,
updatedAt: now,
deletedAt: null,
conditionalDisplay: null,
overrides: null,
};
targetList.push(widget);
existingFieldWidgetFieldIds.add(field.id);
nextWidgetIndex++;
}
}
const totalWidgets =
standardWidgetsToCreate.length + customWidgetsToCreate.length;
if (totalWidgets === 0) {
this.logger.log(
`All FIELD widgets already exist for workspace ${workspaceId}, skipping`,
);
return;
}
this.logger.log(
`Found ${totalWidgets} FIELD widget(s) to create for workspace ${workspaceId} (${standardWidgetsToCreate.length} standard, ${customWidgetsToCreate.length} custom)`,
);
if (standardWidgetsToCreate.length > 0) {
const result =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
pageLayoutWidget: {
flatEntityToCreate: standardWidgetsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
applicationUniversalIdentifier:
twentyStandardFlatApplication.universalIdentifier,
},
);
if (result.status === 'fail') {
this.logger.error(
`Failed to create standard FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
);
throw new Error(
`Failed to create standard FIELD widgets for workspace ${workspaceId}`,
);
}
}
if (customWidgetsToCreate.length > 0) {
const result =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
pageLayoutWidget: {
flatEntityToCreate: customWidgetsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (result.status === 'fail') {
this.logger.error(
`Failed to create custom FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
);
throw new Error(
`Failed to create custom FIELD widgets for workspace ${workspaceId}`,
);
}
}
this.logger.log(
`Successfully created ${totalWidgets} FIELD widget(s) for workspace ${workspaceId}`,
);
}
}
@@ -1,485 +0,0 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { FeatureFlagKey, ViewType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
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 { computeFlatDefaultRecordPageLayoutToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-default-record-page-layout-to-create.util';
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type';
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
@Command({
name: 'upgrade:1-20:backfill-page-layouts',
description:
'Backfill RECORD_PAGE page layouts for legacy workspaces and enable the IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED feature flag',
})
export class BackfillPageLayoutsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
protected readonly dataSourceService: DataSourceService,
private readonly applicationService: ApplicationService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly featureFlagService: FeatureFlagService,
private readonly workspaceCacheService: WorkspaceCacheService,
) {
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of page layouts for workspace ${workspaceId}`,
);
const isAlreadyEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
workspaceId,
);
if (isAlreadyEnabled) {
this.logger.log(
`IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED already enabled for workspace ${workspaceId}, skipping`,
);
return;
}
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
);
await this.backfillStandardObjectPageLayouts({
workspaceId,
twentyStandardFlatApplication,
isDryRun,
});
await this.backfillCustomObjectPageLayouts({
workspaceId,
workspaceCustomFlatApplication,
isDryRun,
});
if (!isDryRun) {
await this.featureFlagService.enableFeatureFlags(
[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED],
workspaceId,
);
}
this.logger.log(
isDryRun
? `[DRY RUN] Would create page layouts and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED for workspace ${workspaceId}`
: `Successfully created page layouts and enabled IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED for workspace ${workspaceId}`,
);
}
private async backfillStandardObjectPageLayouts({
workspaceId,
twentyStandardFlatApplication,
isDryRun,
}: {
workspaceId: string;
twentyStandardFlatApplication: FlatApplication;
isDryRun: boolean;
}): Promise<void> {
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
computeTwentyStandardApplicationAllFlatEntityMaps({
shouldIncludeRecordPageLayouts: true,
now: new Date().toISOString(),
workspaceId,
twentyStandardApplicationId: twentyStandardFlatApplication.id,
});
const {
flatPageLayoutMaps: existingFlatPageLayoutMaps,
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
flatViewMaps: existingFlatViewMaps,
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatPageLayoutMaps',
'flatPageLayoutTabMaps',
'flatPageLayoutWidgetMaps',
'flatViewMaps',
'flatViewFieldMaps',
'flatViewFieldGroupMaps',
]);
const recordPageLayoutUniversalIdentifiers = new Set<string>();
const pageLayoutsToCreate = Object.values(
standardAllFlatEntityMaps.flatPageLayoutMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((pageLayout) => {
if (pageLayout.type !== PageLayoutType.RECORD_PAGE) {
return false;
}
recordPageLayoutUniversalIdentifiers.add(
pageLayout.universalIdentifier,
);
if (
isDefined(
existingFlatPageLayoutMaps.byUniversalIdentifier[
pageLayout.universalIdentifier
],
)
) {
return false;
}
return true;
});
const tabUniversalIdentifiers = new Set<string>();
const pageLayoutTabsToCreate = Object.values(
standardAllFlatEntityMaps.flatPageLayoutTabMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((tab) => {
if (
!recordPageLayoutUniversalIdentifiers.has(
tab.pageLayoutUniversalIdentifier,
)
) {
return false;
}
tabUniversalIdentifiers.add(tab.universalIdentifier);
if (
isDefined(
existingFlatPageLayoutTabMaps.byUniversalIdentifier[
tab.universalIdentifier
],
)
) {
return false;
}
return true;
});
const pageLayoutWidgetsToCreate = Object.values(
standardAllFlatEntityMaps.flatPageLayoutWidgetMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter(
(widget) =>
tabUniversalIdentifiers.has(
widget.pageLayoutTabUniversalIdentifier,
) &&
!isDefined(
existingFlatPageLayoutWidgetMaps.byUniversalIdentifier[
widget.universalIdentifier
],
),
);
const viewUniversalIdentifiers = new Set<string>();
const viewsToCreate = Object.values(
standardAllFlatEntityMaps.flatViewMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((view) => {
if (view.type !== ViewType.FIELDS_WIDGET) {
return false;
}
if (
isDefined(
existingFlatViewMaps.byUniversalIdentifier[
view.universalIdentifier
],
)
) {
return false;
}
viewUniversalIdentifiers.add(view.universalIdentifier);
return true;
});
const viewFieldsToCreate = Object.values(
standardAllFlatEntityMaps.flatViewFieldMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter(
(viewField) =>
viewUniversalIdentifiers.has(viewField.viewUniversalIdentifier) &&
!isDefined(
existingFlatViewFieldMaps.byUniversalIdentifier[
viewField.universalIdentifier
],
),
);
const viewFieldGroupsToCreate = Object.values(
standardAllFlatEntityMaps.flatViewFieldGroupMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter(
(viewFieldGroup) =>
viewUniversalIdentifiers.has(
viewFieldGroup.viewUniversalIdentifier,
) &&
!isDefined(
existingFlatViewFieldGroupMaps.byUniversalIdentifier[
viewFieldGroup.universalIdentifier
],
),
);
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Found standard entities to create for workspace ${workspaceId}: ` +
`${pageLayoutsToCreate.length} page layout(s), ` +
`${pageLayoutTabsToCreate.length} tab(s), ` +
`${pageLayoutWidgetsToCreate.length} widget(s), ` +
`${viewsToCreate.length} view(s), ` +
`${viewFieldsToCreate.length} view field(s), ` +
`${viewFieldGroupsToCreate.length} view field group(s)`,
);
if (isDryRun) {
return;
}
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
pageLayout: {
flatEntityToCreate: pageLayoutsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
pageLayoutTab: {
flatEntityToCreate: pageLayoutTabsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
pageLayoutWidget: {
flatEntityToCreate: pageLayoutWidgetsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
view: {
flatEntityToCreate: viewsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
viewField: {
flatEntityToCreate: viewFieldsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
viewFieldGroup: {
flatEntityToCreate: viewFieldGroupsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
applicationUniversalIdentifier:
twentyStandardFlatApplication.universalIdentifier,
},
);
if (validateAndBuildResult.status === 'fail') {
this.logger.error(
`Failed to create standard page layouts:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
);
throw new Error(
`Failed to create standard page layouts for workspace ${workspaceId}`,
);
}
}
private async backfillCustomObjectPageLayouts({
workspaceId,
workspaceCustomFlatApplication,
isDryRun,
}: {
workspaceId: string;
workspaceCustomFlatApplication: FlatApplication;
isDryRun: boolean;
}): Promise<void> {
const {
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatPageLayoutMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
'flatPageLayoutMaps',
]);
const existingPageLayouts = Object.values(
flatPageLayoutMaps.byUniversalIdentifier,
).filter(isDefined);
const objectIdsWithRecordPageLayout = new Set(
existingPageLayouts
.filter(
(layout: FlatPageLayout) =>
layout.type === PageLayoutType.RECORD_PAGE &&
isDefined(layout.objectMetadataId),
)
.map((layout: FlatPageLayout) => layout.objectMetadataId),
);
const customObjectsWithoutPageLayout = Object.values(
flatObjectMetadataMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter(
(objectMetadata) =>
objectMetadata.isCustom &&
!objectMetadata.isRemote &&
!objectIdsWithRecordPageLayout.has(objectMetadata.id),
);
if (customObjectsWithoutPageLayout.length === 0) {
this.logger.log(
`No custom objects without page layouts found for workspace ${workspaceId}`,
);
return;
}
const allCustomPageLayoutsToCreate: FlatPageLayout[] = [];
const allCustomPageLayoutTabsToCreate: FlatPageLayoutTab[] = [];
const allCustomPageLayoutWidgetsToCreate: FlatPageLayoutWidget[] = [];
const allCustomViewsToCreate: (UniversalFlatView & { id: string })[] = [];
const allCustomViewFieldsToCreate: UniversalFlatViewField[] = [];
for (const customObject of customObjectsWithoutPageLayout) {
const flatRecordPageFieldsView = computeFlatRecordPageFieldsViewToCreate({
objectMetadata: customObject,
flatApplication: workspaceCustomFlatApplication,
});
const objectFieldMetadatas = Object.values(
flatFieldMetadataMaps.byUniversalIdentifier,
)
.filter(isDefined)
.filter((field) => field.objectMetadataId === customObject.id);
const viewFields = computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas: objectFieldMetadatas,
viewUniversalIdentifier: flatRecordPageFieldsView.universalIdentifier,
flatApplication: workspaceCustomFlatApplication,
labelIdentifierFieldMetadataUniversalIdentifier:
customObject.labelIdentifierFieldMetadataUniversalIdentifier,
});
const { pageLayouts, pageLayoutTabs, pageLayoutWidgets } =
computeFlatDefaultRecordPageLayoutToCreate({
objectMetadata: customObject,
flatApplication: workspaceCustomFlatApplication,
recordPageFieldsView: flatRecordPageFieldsView,
workspaceId,
});
allCustomPageLayoutsToCreate.push(...pageLayouts);
allCustomPageLayoutTabsToCreate.push(...pageLayoutTabs);
allCustomPageLayoutWidgetsToCreate.push(...pageLayoutWidgets);
allCustomViewsToCreate.push(flatRecordPageFieldsView);
allCustomViewFieldsToCreate.push(...viewFields);
}
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Found custom entities to create for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}: ` +
`${allCustomPageLayoutsToCreate.length} page layout(s), ` +
`${allCustomPageLayoutTabsToCreate.length} tab(s), ` +
`${allCustomPageLayoutWidgetsToCreate.length} widget(s), ` +
`${allCustomViewsToCreate.length} view(s), ` +
`${allCustomViewFieldsToCreate.length} view field(s)`,
);
if (isDryRun) {
return;
}
const customValidateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
pageLayout: {
flatEntityToCreate: allCustomPageLayoutsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
pageLayoutTab: {
flatEntityToCreate: allCustomPageLayoutTabsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
pageLayoutWidget: {
flatEntityToCreate: allCustomPageLayoutWidgetsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
view: {
flatEntityToCreate: allCustomViewsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
viewField: {
flatEntityToCreate: allCustomViewFieldsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
},
);
if (customValidateAndBuildResult.status === 'fail') {
this.logger.error(
`Failed to create custom object page layouts:\n${JSON.stringify(customValidateAndBuildResult, null, 2)}`,
);
throw new Error(
`Failed to create custom object page layouts for workspace ${workspaceId}`,
);
}
this.logger.log(
`Successfully created page layouts for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}`,
);
}
}
@@ -2,9 +2,8 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
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
IdentifyObjectPermissionMetadataCommand,
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
BackfillCommandMenuItemsCommand,
BackfillFieldWidgetsCommand,
BackfillNavigationMenuItemTypeCommand,
BackfillPageLayoutsCommand,
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
BackfillSelectFieldOptionIdsCommand,
DeleteOrphanNavigationMenuItemsCommand,
GenerateApplicationSdkClientsCommand,
@@ -85,9 +83,8 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
IdentifyObjectPermissionMetadataCommand,
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
BackfillCommandMenuItemsCommand,
BackfillFieldWidgetsCommand,
BackfillNavigationMenuItemTypeCommand,
BackfillPageLayoutsCommand,
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
BackfillSelectFieldOptionIdsCommand,
DeleteOrphanNavigationMenuItemsCommand,
GenerateApplicationSdkClientsCommand,
@@ -34,11 +34,11 @@ import { BackfillSystemFieldsIsSystemCommand } from 'src/database/commands/upgra
import { FixInvalidStandardUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-fix-invalid-standard-universal-identifiers.command';
import { SeedServerIdCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-seed-server-id.command';
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
import { IdentifyObjectPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-object-permission-metadata.command';
import { IdentifyPermissionFlagMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-permission-flag-metadata.command';
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';
@@ -46,7 +46,6 @@ import { MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrati
import { MakeWorkflowSearchableCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-workflow-searchable.command';
import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-messaging-infrastructure-to-metadata.command';
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command';
import { UpdateStandardIndexViewNamesCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-update-standard-index-view-names.command';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@@ -106,12 +105,11 @@ export class UpgradeCommand extends UpgradeCommandRunner {
protected readonly backfillNavigationMenuItemTypeCommand: BackfillNavigationMenuItemTypeCommand,
protected readonly backfillCommandMenuItemsCommand: BackfillCommandMenuItemsCommand,
protected readonly deleteOrphanNavigationMenuItemsCommand: DeleteOrphanNavigationMenuItemsCommand,
protected readonly backfillPageLayoutsCommand: BackfillPageLayoutsCommand,
protected readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
protected readonly generateApplicationSdkClientsCommand: GenerateApplicationSdkClientsCommand,
protected readonly seedCliApplicationRegistrationCommand: SeedCliApplicationRegistrationCommand,
protected readonly migrateRichTextToTextCommand: MigrateRichTextToTextCommand,
protected readonly migrateMessagingInfrastructureToMetadataCommand: MigrateMessagingInfrastructureToMetadataCommand,
protected readonly backfillFieldWidgetsCommand: BackfillFieldWidgetsCommand,
protected readonly backfillSelectFieldOptionIdsCommand: BackfillSelectFieldOptionIdsCommand,
protected readonly updateStandardIndexViewNamesCommand: UpdateStandardIndexViewNamesCommand,
protected readonly makeWorkflowSearchableCommand: MakeWorkflowSearchableCommand,
@@ -172,10 +170,9 @@ export class UpgradeCommand extends UpgradeCommandRunner {
this.migrateRichTextToTextCommand,
this.deleteOrphanNavigationMenuItemsCommand,
this.backfillCommandMenuItemsCommand,
this.backfillPageLayoutsCommand,
this.backfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
this.seedCliApplicationRegistrationCommand,
this.migrateMessagingInfrastructureToMetadataCommand,
this.backfillFieldWidgetsCommand,
this.backfillSelectFieldOptionIdsCommand,
this.updateStandardIndexViewNamesCommand,
this.makeWorkflowSearchableCommand,
@@ -455,6 +455,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
flatObjectMetadataToCreate.labelIdentifierFieldMetadataUniversalIdentifier,
viewUniversalIdentifier:
flatRecordPageFieldsViewToCreate.universalIdentifier,
excludeLabelIdentifier: true,
});
flatDefaultRecordPageLayoutsToCreate =
@@ -0,0 +1,209 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { v4 } from 'uuid';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { computeFlatViewFieldsToCreate } from '../compute-flat-view-fields-to-create.util';
const makeFieldMetadata = (
overrides: Partial<UniversalFlatFieldMetadata> & {
name: string;
type: FieldMetadataType;
},
): UniversalFlatFieldMetadata => {
const universalIdentifier = overrides.universalIdentifier ?? v4();
return {
universalIdentifier,
objectMetadataUniversalIdentifier: 'object-uid',
applicationUniversalIdentifier: 'app-uid',
name: overrides.name,
label: overrides.label ?? overrides.name,
type: overrides.type,
isCustom: overrides.isCustom ?? false,
isActive: true,
isSystem: false,
isUIReadOnly: false,
isNullable: true,
isUnique: false,
isLabelSyncedWithName: false,
defaultValue: null,
description: null,
icon: null,
options: null,
settings: null,
standardOverrides: null,
relationTargetObjectMetadataUniversalIdentifier: null,
createdAt: '2026-01-01T00:00:00.000Z',
updatedAt: '2026-01-01T00:00:00.000Z',
deletedAt: null,
} as unknown as UniversalFlatFieldMetadata;
};
const flatApplication = {
universalIdentifier: 'app-uid',
id: 'app-id',
} as never;
const viewUniversalIdentifier = 'view-uid';
describe('computeFlatViewFieldsToCreate', () => {
it('should exclude TS_VECTOR fields', () => {
const fields = [
makeFieldMetadata({
name: 'name',
type: FieldMetadataType.TEXT,
}),
makeFieldMetadata({
name: 'searchVector',
type: FieldMetadataType.TS_VECTOR,
}),
];
const result = computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas: fields,
viewUniversalIdentifier,
flatApplication,
labelIdentifierFieldMetadataUniversalIdentifier: null,
});
expect(result).toHaveLength(1);
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
fields[0].universalIdentifier,
);
});
it('should exclude POSITION fields', () => {
const fields = [
makeFieldMetadata({
name: 'name',
type: FieldMetadataType.TEXT,
}),
makeFieldMetadata({
name: 'position',
type: FieldMetadataType.POSITION,
}),
];
const result = computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas: fields,
viewUniversalIdentifier,
flatApplication,
labelIdentifierFieldMetadataUniversalIdentifier: null,
});
expect(result).toHaveLength(1);
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
fields[0].universalIdentifier,
);
});
it('should exclude RELATION and MORPH_RELATION fields', () => {
const fields = [
makeFieldMetadata({
name: 'name',
type: FieldMetadataType.TEXT,
}),
makeFieldMetadata({
name: 'company',
type: FieldMetadataType.RELATION,
}),
makeFieldMetadata({
name: 'target',
type: FieldMetadataType.MORPH_RELATION,
}),
];
const result = computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas: fields,
viewUniversalIdentifier,
flatApplication,
labelIdentifierFieldMetadataUniversalIdentifier: null,
});
expect(result).toHaveLength(1);
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
fields[0].universalIdentifier,
);
});
it('should exclude deletedAt field', () => {
const fields = [
makeFieldMetadata({
name: 'name',
type: FieldMetadataType.TEXT,
}),
makeFieldMetadata({
name: 'deletedAt',
type: FieldMetadataType.DATE_TIME,
}),
];
const result = computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas: fields,
viewUniversalIdentifier,
flatApplication,
labelIdentifierFieldMetadataUniversalIdentifier: null,
});
expect(result).toHaveLength(1);
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
fields[0].universalIdentifier,
);
});
it('should place label identifier field first', () => {
const labelField = makeFieldMetadata({
name: 'name',
type: FieldMetadataType.TEXT,
});
const otherField = makeFieldMetadata({
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
});
const fields = [otherField, labelField];
const result = computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas: fields,
viewUniversalIdentifier,
flatApplication,
labelIdentifierFieldMetadataUniversalIdentifier:
labelField.universalIdentifier,
});
expect(result).toHaveLength(2);
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
labelField.universalIdentifier,
);
expect(result[0].position).toBe(0);
expect(result[1].fieldMetadataUniversalIdentifier).toBe(
otherField.universalIdentifier,
);
expect(result[1].position).toBe(1);
});
it('should exclude label identifier when excludeLabelIdentifier is true', () => {
const labelField = makeFieldMetadata({
name: 'name',
type: FieldMetadataType.TEXT,
});
const otherField = makeFieldMetadata({
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
});
const result = computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas: [labelField, otherField],
viewUniversalIdentifier,
flatApplication,
labelIdentifierFieldMetadataUniversalIdentifier:
labelField.universalIdentifier,
excludeLabelIdentifier: true,
});
expect(result).toHaveLength(1);
expect(result[0].fieldMetadataUniversalIdentifier).toBe(
otherField.universalIdentifier,
);
});
});
@@ -11,22 +11,30 @@ export const computeFlatViewFieldsToCreate = ({
viewUniversalIdentifier,
flatApplication,
labelIdentifierFieldMetadataUniversalIdentifier,
excludeLabelIdentifier = false,
}: {
flatApplication: FlatApplication;
objectFlatFieldMetadatas: UniversalFlatFieldMetadata[];
viewUniversalIdentifier: string;
labelIdentifierFieldMetadataUniversalIdentifier: string | null;
excludeLabelIdentifier?: boolean;
}): UniversalFlatViewField[] => {
const createdAt = new Date().toISOString();
const defaultViewFields = objectFlatFieldMetadatas
.filter(
(field) =>
field.name !== 'deletedAt' &&
field.type !== FieldMetadataType.TS_VECTOR &&
field.type !== FieldMetadataType.POSITION &&
field.type !== FieldMetadataType.MORPH_RELATION &&
field.type !== FieldMetadataType.RELATION &&
// Include 'id' only if it's the label identifier (e.g., for junction tables)
(field.name !== 'id' ||
field.universalIdentifier ===
labelIdentifierFieldMetadataUniversalIdentifier) &&
// Exclude label identifier field when requested (e.g., for FIELDS_WIDGET views)
(!excludeLabelIdentifier ||
field.universalIdentifier !==
labelIdentifierFieldMetadataUniversalIdentifier),
)
.sort((a, b) => {
@@ -6,40 +6,37 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
"id": "00000000-0000-0000-0000-000000000011",
"tabs": {
"calendar": {
"id": "00000000-0000-0000-0000-000000000027",
"id": "00000000-0000-0000-0000-000000000026",
"widgets": {
"calendar": {
"id": "00000000-0000-0000-0000-000000000028",
"id": "00000000-0000-0000-0000-000000000027",
},
},
},
"emails": {
"id": "00000000-0000-0000-0000-000000000025",
"id": "00000000-0000-0000-0000-000000000024",
"widgets": {
"emails": {
"id": "00000000-0000-0000-0000-000000000026",
"id": "00000000-0000-0000-0000-000000000025",
},
},
},
"files": {
"id": "00000000-0000-0000-0000-000000000023",
"id": "00000000-0000-0000-0000-000000000022",
"widgets": {
"files": {
"id": "00000000-0000-0000-0000-000000000024",
"id": "00000000-0000-0000-0000-000000000023",
},
},
},
"home": {
"id": "00000000-0000-0000-0000-000000000012",
"widgets": {
"accountOwner": {
"id": "00000000-0000-0000-0000-000000000015",
},
"fields": {
"id": "00000000-0000-0000-0000-000000000013",
},
"opportunities": {
"id": "00000000-0000-0000-0000-000000000016",
"id": "00000000-0000-0000-0000-000000000015",
},
"people": {
"id": "00000000-0000-0000-0000-000000000014",
@@ -47,26 +44,26 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
},
},
"notes": {
"id": "00000000-0000-0000-0000-000000000021",
"id": "00000000-0000-0000-0000-000000000020",
"widgets": {
"notes": {
"id": "00000000-0000-0000-0000-000000000022",
"id": "00000000-0000-0000-0000-000000000021",
},
},
},
"tasks": {
"id": "00000000-0000-0000-0000-000000000019",
"id": "00000000-0000-0000-0000-000000000018",
"widgets": {
"tasks": {
"id": "00000000-0000-0000-0000-000000000020",
"id": "00000000-0000-0000-0000-000000000019",
},
},
},
"timeline": {
"id": "00000000-0000-0000-0000-000000000017",
"id": "00000000-0000-0000-0000-000000000016",
"widgets": {
"timeline": {
"id": "00000000-0000-0000-0000-000000000018",
"id": "00000000-0000-0000-0000-000000000017",
},
},
},
@@ -107,281 +104,278 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
},
},
"noteRecordPage": {
"id": "00000000-0000-0000-0000-000000000064",
"id": "00000000-0000-0000-0000-000000000063",
"tabs": {
"files": {
"id": "00000000-0000-0000-0000-000000000072",
"id": "00000000-0000-0000-0000-000000000071",
"widgets": {
"files": {
"id": "00000000-0000-0000-0000-000000000073",
"id": "00000000-0000-0000-0000-000000000072",
},
},
},
"home": {
"id": "00000000-0000-0000-0000-000000000065",
"id": "00000000-0000-0000-0000-000000000064",
"widgets": {
"fields": {
"id": "00000000-0000-0000-0000-000000000066",
"id": "00000000-0000-0000-0000-000000000065",
},
"noteRichText": {
"id": "00000000-0000-0000-0000-000000000067",
"id": "00000000-0000-0000-0000-000000000066",
},
},
},
"note": {
"id": "00000000-0000-0000-0000-000000000068",
"id": "00000000-0000-0000-0000-000000000067",
"widgets": {
"noteRichText": {
"id": "00000000-0000-0000-0000-000000000069",
"id": "00000000-0000-0000-0000-000000000068",
},
},
},
"timeline": {
"id": "00000000-0000-0000-0000-000000000070",
"id": "00000000-0000-0000-0000-000000000069",
"widgets": {
"timeline": {
"id": "00000000-0000-0000-0000-000000000071",
"id": "00000000-0000-0000-0000-000000000070",
},
},
},
},
},
"opportunityRecordPage": {
"id": "00000000-0000-0000-0000-000000000046",
"id": "00000000-0000-0000-0000-000000000045",
"tabs": {
"calendar": {
"id": "00000000-0000-0000-0000-000000000062",
"id": "00000000-0000-0000-0000-000000000061",
"widgets": {
"calendar": {
"id": "00000000-0000-0000-0000-000000000063",
"id": "00000000-0000-0000-0000-000000000062",
},
},
},
"emails": {
"id": "00000000-0000-0000-0000-000000000060",
"id": "00000000-0000-0000-0000-000000000059",
"widgets": {
"emails": {
"id": "00000000-0000-0000-0000-000000000061",
"id": "00000000-0000-0000-0000-000000000060",
},
},
},
"files": {
"id": "00000000-0000-0000-0000-000000000058",
"id": "00000000-0000-0000-0000-000000000057",
"widgets": {
"files": {
"id": "00000000-0000-0000-0000-000000000059",
"id": "00000000-0000-0000-0000-000000000058",
},
},
},
"home": {
"id": "00000000-0000-0000-0000-000000000047",
"id": "00000000-0000-0000-0000-000000000046",
"widgets": {
"company": {
"id": "00000000-0000-0000-0000-000000000050",
"id": "00000000-0000-0000-0000-000000000049",
},
"fields": {
"id": "00000000-0000-0000-0000-000000000048",
"id": "00000000-0000-0000-0000-000000000047",
},
"owner": {
"id": "00000000-0000-0000-0000-000000000051",
"id": "00000000-0000-0000-0000-000000000050",
},
"pointOfContact": {
"id": "00000000-0000-0000-0000-000000000049",
"id": "00000000-0000-0000-0000-000000000048",
},
},
},
"notes": {
"id": "00000000-0000-0000-0000-000000000056",
"id": "00000000-0000-0000-0000-000000000055",
"widgets": {
"notes": {
"id": "00000000-0000-0000-0000-000000000057",
"id": "00000000-0000-0000-0000-000000000056",
},
},
},
"tasks": {
"id": "00000000-0000-0000-0000-000000000054",
"id": "00000000-0000-0000-0000-000000000053",
"widgets": {
"tasks": {
"id": "00000000-0000-0000-0000-000000000055",
"id": "00000000-0000-0000-0000-000000000054",
},
},
},
"timeline": {
"id": "00000000-0000-0000-0000-000000000052",
"id": "00000000-0000-0000-0000-000000000051",
"widgets": {
"timeline": {
"id": "00000000-0000-0000-0000-000000000053",
"id": "00000000-0000-0000-0000-000000000052",
},
},
},
},
},
"personRecordPage": {
"id": "00000000-0000-0000-0000-000000000029",
"id": "00000000-0000-0000-0000-000000000028",
"tabs": {
"calendar": {
"id": "00000000-0000-0000-0000-000000000044",
"id": "00000000-0000-0000-0000-000000000043",
"widgets": {
"calendar": {
"id": "00000000-0000-0000-0000-000000000045",
"id": "00000000-0000-0000-0000-000000000044",
},
},
},
"emails": {
"id": "00000000-0000-0000-0000-000000000042",
"id": "00000000-0000-0000-0000-000000000041",
"widgets": {
"emails": {
"id": "00000000-0000-0000-0000-000000000043",
"id": "00000000-0000-0000-0000-000000000042",
},
},
},
"files": {
"id": "00000000-0000-0000-0000-000000000040",
"id": "00000000-0000-0000-0000-000000000039",
"widgets": {
"files": {
"id": "00000000-0000-0000-0000-000000000041",
"id": "00000000-0000-0000-0000-000000000040",
},
},
},
"home": {
"id": "00000000-0000-0000-0000-000000000030",
"id": "00000000-0000-0000-0000-000000000029",
"widgets": {
"company": {
"id": "00000000-0000-0000-0000-000000000032",
},
"fields": {
"id": "00000000-0000-0000-0000-000000000031",
},
"fields": {
"id": "00000000-0000-0000-0000-000000000030",
},
"pointOfContactForOpportunities": {
"id": "00000000-0000-0000-0000-000000000033",
"id": "00000000-0000-0000-0000-000000000032",
},
},
},
"notes": {
"id": "00000000-0000-0000-0000-000000000038",
"id": "00000000-0000-0000-0000-000000000037",
"widgets": {
"notes": {
"id": "00000000-0000-0000-0000-000000000039",
"id": "00000000-0000-0000-0000-000000000038",
},
},
},
"tasks": {
"id": "00000000-0000-0000-0000-000000000036",
"id": "00000000-0000-0000-0000-000000000035",
"widgets": {
"tasks": {
"id": "00000000-0000-0000-0000-000000000037",
"id": "00000000-0000-0000-0000-000000000036",
},
},
},
"timeline": {
"id": "00000000-0000-0000-0000-000000000034",
"id": "00000000-0000-0000-0000-000000000033",
"widgets": {
"timeline": {
"id": "00000000-0000-0000-0000-000000000035",
"id": "00000000-0000-0000-0000-000000000034",
},
},
},
},
},
"taskRecordPage": {
"id": "00000000-0000-0000-0000-000000000074",
"id": "00000000-0000-0000-0000-000000000073",
"tabs": {
"files": {
"id": "00000000-0000-0000-0000-000000000083",
"id": "00000000-0000-0000-0000-000000000081",
"widgets": {
"files": {
"id": "00000000-0000-0000-0000-000000000084",
"id": "00000000-0000-0000-0000-000000000082",
},
},
},
"home": {
"id": "00000000-0000-0000-0000-000000000075",
"id": "00000000-0000-0000-0000-000000000074",
"widgets": {
"assignee": {
"id": "00000000-0000-0000-0000-000000000078",
},
"fields": {
"id": "00000000-0000-0000-0000-000000000076",
"id": "00000000-0000-0000-0000-000000000075",
},
"taskRichText": {
"id": "00000000-0000-0000-0000-000000000077",
"id": "00000000-0000-0000-0000-000000000076",
},
},
},
"note": {
"id": "00000000-0000-0000-0000-000000000079",
"id": "00000000-0000-0000-0000-000000000077",
"widgets": {
"taskRichText": {
"id": "00000000-0000-0000-0000-000000000080",
"id": "00000000-0000-0000-0000-000000000078",
},
},
},
"timeline": {
"id": "00000000-0000-0000-0000-000000000081",
"id": "00000000-0000-0000-0000-000000000079",
"widgets": {
"timeline": {
"id": "00000000-0000-0000-0000-000000000082",
"id": "00000000-0000-0000-0000-000000000080",
},
},
},
},
},
"workflowRecordPage": {
"id": "00000000-0000-0000-0000-000000000085",
"id": "00000000-0000-0000-0000-000000000083",
"tabs": {
"flow": {
"id": "00000000-0000-0000-0000-000000000086",
"id": "00000000-0000-0000-0000-000000000084",
"widgets": {
"workflow": {
"id": "00000000-0000-0000-0000-000000000087",
"id": "00000000-0000-0000-0000-000000000085",
},
},
},
},
},
"workflowRunRecordPage": {
"id": "00000000-0000-0000-0000-000000000094",
"id": "00000000-0000-0000-0000-000000000092",
"tabs": {
"flow": {
"id": "00000000-0000-0000-0000-000000000098",
"id": "00000000-0000-0000-0000-000000000096",
"widgets": {
"workflowRun": {
"id": "00000000-0000-0000-0000-000000000099",
"id": "00000000-0000-0000-0000-000000000097",
},
},
},
"home": {
"id": "00000000-0000-0000-0000-000000000095",
"id": "00000000-0000-0000-0000-000000000093",
"widgets": {
"fields": {
"id": "00000000-0000-0000-0000-000000000096",
"id": "00000000-0000-0000-0000-000000000094",
},
"workflow": {
"id": "00000000-0000-0000-0000-000000000097",
"id": "00000000-0000-0000-0000-000000000095",
},
},
},
},
},
"workflowVersionRecordPage": {
"id": "00000000-0000-0000-0000-000000000088",
"id": "00000000-0000-0000-0000-000000000086",
"tabs": {
"flow": {
"id": "00000000-0000-0000-0000-000000000092",
"id": "00000000-0000-0000-0000-000000000090",
"widgets": {
"workflowVersion": {
"id": "00000000-0000-0000-0000-000000000093",
"id": "00000000-0000-0000-0000-000000000091",
},
},
},
"home": {
"id": "00000000-0000-0000-0000-000000000089",
"id": "00000000-0000-0000-0000-000000000087",
"widgets": {
"fields": {
"id": "00000000-0000-0000-0000-000000000090",
"id": "00000000-0000-0000-0000-000000000088",
},
"workflow": {
"id": "00000000-0000-0000-0000-000000000091",
"id": "00000000-0000-0000-0000-000000000089",
},
},
},
@@ -31,21 +31,12 @@ const COMPANY_PAGE_TABS = {
fieldUniversalIdentifier:
STANDARD_OBJECTS.company.fields.people.universalIdentifier,
},
accountOwner: {
universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0113',
title: 'Account Owner',
type: WidgetType.FIELD,
gridPosition: GRID_POSITIONS.FULL_WIDTH,
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
fieldUniversalIdentifier:
STANDARD_OBJECTS.company.fields.accountOwner.universalIdentifier,
},
opportunities: {
universalIdentifier: '20202020-ac01-4001-8001-c0aba11c0114',
title: 'Opportunities',
type: WidgetType.FIELD,
gridPosition: GRID_POSITIONS.FULL_WIDTH,
position: VERTICAL_LIST_LAYOUT_POSITIONS.FOURTH,
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
fieldUniversalIdentifier:
STANDARD_OBJECTS.company.fields.opportunities.universalIdentifier,
},
@@ -1,13 +1,10 @@
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import {
CONDITIONAL_DISPLAY_DEVICE_DESKTOP,
CONDITIONAL_DISPLAY_DEVICE_MOBILE,
GRID_POSITIONS,
TAB_PROPS,
VERTICAL_LIST_LAYOUT_POSITIONS,
WIDGET_PROPS,
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
import {
@@ -32,15 +29,6 @@ const TASK_PAGE_TABS = {
position: { layoutMode: TAB_PROPS.home.layoutMode, index: 1 },
conditionalDisplay: CONDITIONAL_DISPLAY_DEVICE_MOBILE,
},
assignee: {
universalIdentifier: '20202020-ac05-4005-8005-ba5ca11a5513',
title: 'Assignee',
type: WidgetType.FIELD,
gridPosition: GRID_POSITIONS.FULL_WIDTH,
position: VERTICAL_LIST_LAYOUT_POSITIONS.THIRD,
fieldUniversalIdentifier:
STANDARD_OBJECTS.task.fields.assignee.universalIdentifier,
},
},
},
note: {
@@ -45,19 +45,6 @@ export const computeStandardBlocklistViewFields = (
},
}),
blocklistRecordPageFieldsHandle: createStandardViewFieldFlatMetadata({
...args,
objectName: 'blocklist',
context: {
viewName: 'blocklistRecordPageFields',
viewFieldName: 'handle',
fieldName: 'handle',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
blocklistRecordPageFieldsWorkspaceMember:
createStandardViewFieldFlatMetadata({
...args,
@@ -81,19 +81,6 @@ export const computeStandardCalendarChannelViewFields = (
},
}),
calendarChannelRecordPageFieldsHandle: createStandardViewFieldFlatMetadata({
...args,
objectName: 'calendarChannel',
context: {
viewName: 'calendarChannelRecordPageFields',
viewFieldName: 'handle',
fieldName: 'handle',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
calendarChannelRecordPageFieldsConnectedAccount:
createStandardViewFieldFlatMetadata({
...args,
@@ -245,5 +245,123 @@ export const computeStandardCompanyViewFields = (
viewFieldGroupName: 'other',
},
}),
companyRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'company',
context: {
viewName: 'companyRecordPageFields',
viewFieldName: 'updatedAt',
fieldName: 'updatedAt',
position: 6,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
companyRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
...args,
objectName: 'company',
context: {
viewName: 'companyRecordPageFields',
viewFieldName: 'updatedBy',
fieldName: 'updatedBy',
position: 7,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
companyRecordPageFieldsPeople: createStandardViewFieldFlatMetadata({
...args,
objectName: 'company',
context: {
viewName: 'companyRecordPageFields',
viewFieldName: 'people',
fieldName: 'people',
position: 4,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
companyRecordPageFieldsTaskTargets: createStandardViewFieldFlatMetadata({
...args,
objectName: 'company',
context: {
viewName: 'companyRecordPageFields',
viewFieldName: 'taskTargets',
fieldName: 'taskTargets',
position: 5,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
companyRecordPageFieldsNoteTargets: createStandardViewFieldFlatMetadata({
...args,
objectName: 'company',
context: {
viewName: 'companyRecordPageFields',
viewFieldName: 'noteTargets',
fieldName: 'noteTargets',
position: 6,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
companyRecordPageFieldsOpportunities: createStandardViewFieldFlatMetadata({
...args,
objectName: 'company',
context: {
viewName: 'companyRecordPageFields',
viewFieldName: 'opportunities',
fieldName: 'opportunities',
position: 7,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
companyRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
...args,
objectName: 'company',
context: {
viewName: 'companyRecordPageFields',
viewFieldName: 'favorites',
fieldName: 'favorites',
position: 8,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
companyRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata({
...args,
objectName: 'company',
context: {
viewName: 'companyRecordPageFields',
viewFieldName: 'attachments',
fieldName: 'attachments',
position: 9,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
companyRecordPageFieldsTimelineActivities:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'company',
context: {
viewName: 'companyRecordPageFields',
viewFieldName: 'timelineActivities',
fieldName: 'timelineActivities',
position: 10,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
};
};
@@ -69,21 +69,6 @@ export const computeStandardConnectedAccountViewFields = (
},
}),
connectedAccountRecordPageFieldsHandle: createStandardViewFieldFlatMetadata(
{
...args,
objectName: 'connectedAccount',
context: {
viewName: 'connectedAccountRecordPageFields',
viewFieldName: 'handle',
fieldName: 'handle',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
},
),
connectedAccountRecordPageFieldsProvider:
createStandardViewFieldFlatMetadata({
...args,
@@ -33,19 +33,6 @@ export const computeStandardFavoriteFolderViewFields = (
},
}),
favoriteFolderRecordPageFieldsName: createStandardViewFieldFlatMetadata({
...args,
objectName: 'favoriteFolder',
context: {
viewName: 'favoriteFolderRecordPageFields',
viewFieldName: 'name',
fieldName: 'name',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
favoriteFolderRecordPageFieldsCreatedAt:
createStandardViewFieldFlatMetadata({
...args,
@@ -93,19 +93,6 @@ export const computeStandardMessageChannelViewFields = (
},
}),
messageChannelRecordPageFieldsHandle: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageChannel',
context: {
viewName: 'messageChannelRecordPageFields',
viewFieldName: 'handle',
fieldName: 'handle',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
messageChannelRecordPageFieldsConnectedAccount:
createStandardViewFieldFlatMetadata({
...args,
@@ -69,19 +69,6 @@ export const computeStandardMessageFolderViewFields = (
},
}),
messageFolderRecordPageFieldsName: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageFolder',
context: {
viewName: 'messageFolderRecordPageFields',
viewFieldName: 'name',
fieldName: 'name',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
messageFolderRecordPageFieldsMessageChannel:
createStandardViewFieldFlatMetadata({
...args,
@@ -93,20 +93,6 @@ export const computeStandardMessageParticipantViewFields = (
},
}),
messageParticipantRecordPageFieldsHandle:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageParticipant',
context: {
viewName: 'messageParticipantRecordPageFields',
viewFieldName: 'handle',
fieldName: 'handle',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
messageParticipantRecordPageFieldsMessage:
createStandardViewFieldFlatMetadata({
...args,
@@ -70,19 +70,6 @@ export const computeStandardNoteViewFields = (
}),
// noteRecordPageFields view fields
noteRecordPageFieldsTitle: createStandardViewFieldFlatMetadata({
...args,
objectName: 'note',
context: {
viewName: 'noteRecordPageFields',
viewFieldName: 'title',
fieldName: 'title',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
noteRecordPageFieldsNoteTargets: createStandardViewFieldFlatMetadata({
...args,
objectName: 'note',
@@ -122,5 +109,85 @@ export const computeStandardNoteViewFields = (
viewFieldGroupName: 'other',
},
}),
noteRecordPageFieldsBodyV2: createStandardViewFieldFlatMetadata({
...args,
objectName: 'note',
context: {
viewName: 'noteRecordPageFields',
viewFieldName: 'bodyV2',
fieldName: 'bodyV2',
position: 0,
isVisible: false,
size: 150,
viewFieldGroupName: 'additional',
},
}),
noteRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'note',
context: {
viewName: 'noteRecordPageFields',
viewFieldName: 'updatedAt',
fieldName: 'updatedAt',
position: 2,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
noteRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
...args,
objectName: 'note',
context: {
viewName: 'noteRecordPageFields',
viewFieldName: 'updatedBy',
fieldName: 'updatedBy',
position: 3,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
noteRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata({
...args,
objectName: 'note',
context: {
viewName: 'noteRecordPageFields',
viewFieldName: 'attachments',
fieldName: 'attachments',
position: 2,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
noteRecordPageFieldsTimelineActivities: createStandardViewFieldFlatMetadata(
{
...args,
objectName: 'note',
context: {
viewName: 'noteRecordPageFields',
viewFieldName: 'timelineActivities',
fieldName: 'timelineActivities',
position: 3,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
},
),
noteRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
...args,
objectName: 'note',
context: {
viewName: 'noteRecordPageFields',
viewFieldName: 'favorites',
fieldName: 'favorites',
position: 4,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
};
};
@@ -266,5 +266,103 @@ export const computeStandardOpportunityViewFields = (
viewFieldGroupName: 'other',
},
}),
opportunityRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'opportunity',
context: {
viewName: 'opportunityRecordPageFields',
viewFieldName: 'updatedAt',
fieldName: 'updatedAt',
position: 2,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
opportunityRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
...args,
objectName: 'opportunity',
context: {
viewName: 'opportunityRecordPageFields',
viewFieldName: 'updatedBy',
fieldName: 'updatedBy',
position: 3,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
opportunityRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
...args,
objectName: 'opportunity',
context: {
viewName: 'opportunityRecordPageFields',
viewFieldName: 'favorites',
fieldName: 'favorites',
position: 5,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
opportunityRecordPageFieldsTaskTargets: createStandardViewFieldFlatMetadata(
{
...args,
objectName: 'opportunity',
context: {
viewName: 'opportunityRecordPageFields',
viewFieldName: 'taskTargets',
fieldName: 'taskTargets',
position: 6,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
},
),
opportunityRecordPageFieldsNoteTargets: createStandardViewFieldFlatMetadata(
{
...args,
objectName: 'opportunity',
context: {
viewName: 'opportunityRecordPageFields',
viewFieldName: 'noteTargets',
fieldName: 'noteTargets',
position: 7,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
},
),
opportunityRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata(
{
...args,
objectName: 'opportunity',
context: {
viewName: 'opportunityRecordPageFields',
viewFieldName: 'attachments',
fieldName: 'attachments',
position: 8,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
},
),
opportunityRecordPageFieldsTimelineActivities:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'opportunity',
context: {
viewName: 'opportunityRecordPageFields',
viewFieldName: 'timelineActivities',
fieldName: 'timelineActivities',
position: 9,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
};
};
@@ -266,5 +266,152 @@ export const computeStandardPersonViewFields = (
viewFieldGroupName: 'other',
},
}),
personRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'updatedAt',
fieldName: 'updatedAt',
position: 4,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
personRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'updatedBy',
fieldName: 'updatedBy',
position: 5,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
personRecordPageFieldsAvatarFile: createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'avatarFile',
fieldName: 'avatarFile',
position: 0,
isVisible: false,
size: 150,
viewFieldGroupName: 'additional',
},
}),
personRecordPageFieldsPointOfContactForOpportunities:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'pointOfContactForOpportunities',
fieldName: 'pointOfContactForOpportunities',
position: 5,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
personRecordPageFieldsTaskTargets: createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'taskTargets',
fieldName: 'taskTargets',
position: 6,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
personRecordPageFieldsNoteTargets: createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'noteTargets',
fieldName: 'noteTargets',
position: 7,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
personRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'favorites',
fieldName: 'favorites',
position: 8,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
personRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'attachments',
fieldName: 'attachments',
position: 9,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
personRecordPageFieldsMessageParticipants:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'messageParticipants',
fieldName: 'messageParticipants',
position: 10,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
personRecordPageFieldsCalendarEventParticipants:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'calendarEventParticipants',
fieldName: 'calendarEventParticipants',
position: 11,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
personRecordPageFieldsTimelineActivities:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'person',
context: {
viewName: 'personRecordPageFields',
viewFieldName: 'timelineActivities',
fieldName: 'timelineActivities',
position: 12,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
};
};
@@ -255,19 +255,6 @@ export const computeStandardTaskViewFields = (
}),
// taskRecordPageFields view fields
taskRecordPageFieldsTitle: createStandardViewFieldFlatMetadata({
...args,
objectName: 'task',
context: {
viewName: 'taskRecordPageFields',
viewFieldName: 'title',
fieldName: 'title',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
taskRecordPageFieldsDueAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'task',
@@ -346,5 +333,85 @@ export const computeStandardTaskViewFields = (
viewFieldGroupName: 'other',
},
}),
taskRecordPageFieldsBodyV2: createStandardViewFieldFlatMetadata({
...args,
objectName: 'task',
context: {
viewName: 'taskRecordPageFields',
viewFieldName: 'bodyV2',
fieldName: 'bodyV2',
position: 0,
isVisible: false,
size: 150,
viewFieldGroupName: 'additional',
},
}),
taskRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'task',
context: {
viewName: 'taskRecordPageFields',
viewFieldName: 'updatedAt',
fieldName: 'updatedAt',
position: 2,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
taskRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
...args,
objectName: 'task',
context: {
viewName: 'taskRecordPageFields',
viewFieldName: 'updatedBy',
fieldName: 'updatedBy',
position: 3,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
taskRecordPageFieldsAttachments: createStandardViewFieldFlatMetadata({
...args,
objectName: 'task',
context: {
viewName: 'taskRecordPageFields',
viewFieldName: 'attachments',
fieldName: 'attachments',
position: 5,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
taskRecordPageFieldsTimelineActivities: createStandardViewFieldFlatMetadata(
{
...args,
objectName: 'task',
context: {
viewName: 'taskRecordPageFields',
viewFieldName: 'timelineActivities',
fieldName: 'timelineActivities',
position: 6,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
},
),
taskRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
...args,
objectName: 'task',
context: {
viewName: 'taskRecordPageFields',
viewFieldName: 'favorites',
fieldName: 'favorites',
position: 7,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
};
};
@@ -46,19 +46,6 @@ export const computeStandardWorkflowRunViewFields = (
}),
// workflowRunRecordPageFields view fields
workflowRunRecordPageFieldsName: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
context: {
viewName: 'workflowRunRecordPageFields',
viewFieldName: 'name',
fieldName: 'name',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
workflowRunRecordPageFieldsStatus: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
@@ -151,5 +138,110 @@ export const computeStandardWorkflowRunViewFields = (
viewFieldGroupName: 'other',
},
}),
workflowRunRecordPageFieldsEnqueuedAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
context: {
viewName: 'workflowRunRecordPageFields',
viewFieldName: 'enqueuedAt',
fieldName: 'enqueuedAt',
position: 0,
isVisible: false,
size: 150,
viewFieldGroupName: 'additional',
},
}),
workflowRunRecordPageFieldsOutput: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
context: {
viewName: 'workflowRunRecordPageFields',
viewFieldName: 'output',
fieldName: 'output',
position: 6,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
workflowRunRecordPageFieldsContext: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
context: {
viewName: 'workflowRunRecordPageFields',
viewFieldName: 'context',
fieldName: 'context',
position: 7,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
workflowRunRecordPageFieldsState: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
context: {
viewName: 'workflowRunRecordPageFields',
viewFieldName: 'state',
fieldName: 'state',
position: 8,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
workflowRunRecordPageFieldsUpdatedAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
context: {
viewName: 'workflowRunRecordPageFields',
viewFieldName: 'updatedAt',
fieldName: 'updatedAt',
position: 2,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
workflowRunRecordPageFieldsUpdatedBy: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
context: {
viewName: 'workflowRunRecordPageFields',
viewFieldName: 'updatedBy',
fieldName: 'updatedBy',
position: 3,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
workflowRunRecordPageFieldsFavorites: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
context: {
viewName: 'workflowRunRecordPageFields',
viewFieldName: 'favorites',
fieldName: 'favorites',
position: 9,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
workflowRunRecordPageFieldsTimelineActivities:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowRun',
context: {
viewName: 'workflowRunRecordPageFields',
viewFieldName: 'timelineActivities',
fieldName: 'timelineActivities',
position: 10,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
};
};
@@ -70,19 +70,6 @@ export const computeStandardWorkflowVersionViewFields = (
}),
// workflowVersionRecordPageFields view fields
workflowVersionRecordPageFieldsName: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowVersion',
context: {
viewName: 'workflowVersionRecordPageFields',
viewFieldName: 'name',
fieldName: 'name',
position: 0,
isVisible: true,
size: 150,
viewFieldGroupName: 'general',
},
}),
workflowVersionRecordPageFieldsStatus: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowVersion',
@@ -139,5 +126,101 @@ export const computeStandardWorkflowVersionViewFields = (
viewFieldGroupName: 'other',
},
}),
workflowVersionRecordPageFieldsSteps: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowVersion',
context: {
viewName: 'workflowVersionRecordPageFields',
viewFieldName: 'steps',
fieldName: 'steps',
position: 0,
isVisible: false,
size: 150,
viewFieldGroupName: 'additional',
},
}),
workflowVersionRecordPageFieldsCreatedBy:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowVersion',
context: {
viewName: 'workflowVersionRecordPageFields',
viewFieldName: 'createdBy',
fieldName: 'createdBy',
position: 1,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
workflowVersionRecordPageFieldsUpdatedAt:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowVersion',
context: {
viewName: 'workflowVersionRecordPageFields',
viewFieldName: 'updatedAt',
fieldName: 'updatedAt',
position: 2,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
workflowVersionRecordPageFieldsUpdatedBy:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowVersion',
context: {
viewName: 'workflowVersionRecordPageFields',
viewFieldName: 'updatedBy',
fieldName: 'updatedBy',
position: 3,
isVisible: false,
size: 150,
viewFieldGroupName: 'other',
},
}),
workflowVersionRecordPageFieldsRuns: createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowVersion',
context: {
viewName: 'workflowVersionRecordPageFields',
viewFieldName: 'runs',
fieldName: 'runs',
position: 4,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
workflowVersionRecordPageFieldsFavorites:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowVersion',
context: {
viewName: 'workflowVersionRecordPageFields',
viewFieldName: 'favorites',
fieldName: 'favorites',
position: 5,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
workflowVersionRecordPageFieldsTimelineActivities:
createStandardViewFieldFlatMetadata({
...args,
objectName: 'workflowVersion',
context: {
viewName: 'workflowVersionRecordPageFields',
viewFieldName: 'timelineActivities',
fieldName: 'timelineActivities',
position: 6,
isVisible: false,
size: 150,
viewFieldGroupName: 'general',
},
}),
};
};
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { ViewType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
@@ -95,8 +96,9 @@ export class FlatViewFieldValidatorService {
}
if (
flatView.type !== ViewType.FIELDS_WIDGET &&
flatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier ===
updatedFlatViewField.fieldMetadataUniversalIdentifier
updatedFlatViewField.fieldMetadataUniversalIdentifier
) {
const otherFlatViewFields =
findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow(
@@ -127,6 +129,7 @@ export class FlatViewFieldValidatorService {
flatViewFieldMaps: optimisticFlatViewFieldMaps,
flatFieldMetadataMaps,
flatObjectMetadataMaps,
flatViewMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewField
@@ -177,11 +180,18 @@ export class FlatViewFieldValidatorService {
flatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier ===
existingFlatViewField.fieldMetadataUniversalIdentifier
) {
validationResult.errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`Label identifier view field cannot be deleted`,
userFriendlyMessage: msg`Label identifier view field cannot be deleted`,
const flatView = findFlatEntityByUniversalIdentifier({
universalIdentifier: existingFlatViewField.viewUniversalIdentifier,
flatEntityMaps: flatViewMaps,
});
if (!isDefined(flatView) || flatView.type !== ViewType.FIELDS_WIDGET) {
validationResult.errors.push({
code: ViewExceptionCode.INVALID_VIEW_DATA,
message: t`Label identifier view field cannot be deleted`,
userFriendlyMessage: msg`Label identifier view field cannot be deleted`,
});
}
}
return validationResult;
@@ -289,8 +299,9 @@ export class FlatViewFieldValidatorService {
}
if (
flatView.type !== ViewType.FIELDS_WIDGET &&
flatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier ===
flatViewFieldToValidate.fieldMetadataUniversalIdentifier
flatViewFieldToValidate.fieldMetadataUniversalIdentifier
) {
validationResult.errors.push(
...validateLabelIdentifierFieldMetadataIdFlatViewField({
@@ -299,6 +310,7 @@ export class FlatViewFieldValidatorService {
}),
);
} else if (
flatView.type !== ViewType.FIELDS_WIDGET &&
otherFlatViewFields.some(
(flatViewField) =>
flatViewField.fieldMetadataUniversalIdentifier ===
@@ -93,6 +93,6 @@ describe('View side effect on object creation', () => {
expectToFail: false,
});
expect(createdViewFields.length).toBe(7);
expect(createdViewFields.length).toBe(5);
});
});
@@ -188,9 +188,6 @@ export const STANDARD_OBJECTS = {
},
},
viewFields: {
handle: {
universalIdentifier: 'e22a1d19-c1bb-4265-ae48-2054513c21fe',
},
workspaceMember: {
universalIdentifier: 'f2f5732f-7435-44be-986b-4c4d834fdfeb',
},
@@ -405,9 +402,6 @@ export const STANDARD_OBJECTS = {
},
},
viewFields: {
handle: {
universalIdentifier: 'cd1f641b-5746-49db-9a7e-82dd9a63593d',
},
connectedAccount: {
universalIdentifier: 'bdb40f41-f9ba-4b59-a8cf-878c23701ab3',
},
@@ -808,6 +802,33 @@ export const STANDARD_OBJECTS = {
createdBy: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1210',
},
updatedAt: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1212',
},
updatedBy: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1213',
},
people: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1214',
},
taskTargets: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1215',
},
noteTargets: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1216',
},
opportunities: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1217',
},
favorites: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1218',
},
attachments: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c1219',
},
timelineActivities: {
universalIdentifier: '20202020-af01-4a01-8a01-c0aba11c121a',
},
},
},
},
@@ -914,9 +935,6 @@ export const STANDARD_OBJECTS = {
},
},
viewFields: {
handle: {
universalIdentifier: '41d86e23-ceb2-41ab-975f-8ec5a1023ece',
},
provider: {
universalIdentifier: '83171d2a-0d11-42b1-991d-8d4346b02cff',
},
@@ -1213,9 +1231,6 @@ export const STANDARD_OBJECTS = {
},
},
viewFields: {
name: {
universalIdentifier: 'cac4f0f7-3a6a-49b1-b86f-41b20f2455c0',
},
createdAt: {
universalIdentifier: 'a4e42591-844c-47d1-b72e-5ded3d541694',
},
@@ -1554,9 +1569,6 @@ export const STANDARD_OBJECTS = {
},
},
viewFields: {
handle: {
universalIdentifier: 'a9cbb9a5-a6b4-417e-93ad-a5e578c222db',
},
connectedAccount: {
universalIdentifier: '19079cf6-2a9c-40b9-b6c2-58d63c6e37ad',
},
@@ -1670,9 +1682,6 @@ export const STANDARD_OBJECTS = {
},
},
viewFields: {
name: {
universalIdentifier: '6fa8c474-ee22-47f1-b830-4f169ff82315',
},
messageChannel: {
universalIdentifier: '2fb6ff09-bed5-4b31-af0f-7fa3df5612da',
},
@@ -1791,9 +1800,6 @@ export const STANDARD_OBJECTS = {
role: {
universalIdentifier: '5d1f9a65-85cc-41b2-a8bf-8e2c97aab4b3',
},
handle: {
universalIdentifier: '97295fc0-cdb8-4d84-8c1b-327837255c0d',
},
displayName: {
universalIdentifier: 'c50748fe-9f54-4e09-b572-111f076ec7db',
},
@@ -2018,9 +2024,6 @@ export const STANDARD_OBJECTS = {
},
},
viewFields: {
title: {
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115201',
},
createdAt: {
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115202',
},
@@ -2030,6 +2033,24 @@ export const STANDARD_OBJECTS = {
noteTargets: {
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115204',
},
bodyV2: {
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115205',
},
updatedAt: {
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115206',
},
updatedBy: {
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115207',
},
attachments: {
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115208',
},
timelineActivities: {
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a115209',
},
favorites: {
universalIdentifier: '20202020-af05-4a05-8a05-a0be5a11520a',
},
},
},
},
@@ -2278,6 +2299,27 @@ export const STANDARD_OBJECTS = {
createdBy: {
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca3208',
},
updatedAt: {
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320a',
},
updatedBy: {
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320b',
},
favorites: {
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320c',
},
taskTargets: {
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320d',
},
noteTargets: {
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320e',
},
attachments: {
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca320f',
},
timelineActivities: {
universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca3210',
},
},
},
},
@@ -2437,6 +2479,39 @@ export const STANDARD_OBJECTS = {
createdBy: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12210',
},
updatedAt: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12212',
},
updatedBy: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12213',
},
avatarFile: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12214',
},
pointOfContactForOpportunities: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12215',
},
taskTargets: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12216',
},
noteTargets: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12217',
},
favorites: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12218',
},
attachments: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea12219',
},
messageParticipants: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea1221a',
},
calendarEventParticipants: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea1221b',
},
timelineActivities: {
universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea1221c',
},
},
},
},
@@ -2610,9 +2685,6 @@ export const STANDARD_OBJECTS = {
},
},
viewFields: {
title: {
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6201',
},
dueAt: {
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6202',
},
@@ -2631,6 +2703,24 @@ export const STANDARD_OBJECTS = {
taskTargets: {
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6207',
},
bodyV2: {
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6208',
},
updatedAt: {
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a6209',
},
updatedBy: {
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a620a',
},
attachments: {
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a620b',
},
timelineActivities: {
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a620c',
},
favorites: {
universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a620d',
},
},
},
},
@@ -3114,9 +3204,6 @@ export const STANDARD_OBJECTS = {
workflowRunRecordPageFields: {
universalIdentifier: '20202020-a011-4a11-8a11-a0bcf10abcf1',
viewFields: {
name: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcf5',
},
status: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcf6',
},
@@ -3138,6 +3225,30 @@ export const STANDARD_OBJECTS = {
createdBy: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcfc',
},
enqueuedAt: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcfd',
},
output: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcfe',
},
context: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcff',
},
state: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd01',
},
updatedAt: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd02',
},
updatedBy: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd03',
},
favorites: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd04',
},
timelineActivities: {
universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abd05',
},
},
viewFieldGroups: {
general: {
@@ -3229,9 +3340,6 @@ export const STANDARD_OBJECTS = {
workflowVersionRecordPageFields: {
universalIdentifier: '20202020-a010-4a10-8a10-a0bcf10aaef1',
viewFields: {
name: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaef5',
},
status: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaef6',
},
@@ -3244,6 +3352,27 @@ export const STANDARD_OBJECTS = {
createdAt: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaef9',
},
steps: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefa',
},
createdBy: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefb',
},
updatedAt: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefc',
},
updatedBy: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefd',
},
runs: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaefe',
},
favorites: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaeff',
},
timelineActivities: {
universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaf01',
},
},
viewFieldGroups: {
general: {