fix(server): skip callRecordings widget in calendar-event page sync when field is absent (#21967)

## Context

On main's auto-upgrade, `SyncCalendarEventRecordPageCommand` (2.15.0
workspace command) failed for workspaces that don't have the
call-recording feature metadata, aborting the upgrade with:

```
Migration action 'create' for 'pageLayoutWidget' (universalIdentifier: f473b435-...) failed
Caused by: Field metadata not found for universal identifier: 48d6d151-... (calendarEvent.callRecordings)
```

## Root cause

The command always included the `callRecordings` page-layout widget.
That widget's configuration references the
`calendarEvent.callRecordings` relation field (`48d6d151`). Workspaces
that never had the `callRecording` object / relation field synced fail
transpilation with `ENTITY_NOT_FOUND`, and since one workspace failure
aborts the segment, the whole upgrade stops.

On the affected environment, ~half of active/suspended workspaces lack
both the `callRecording` object and the `calendarEvent.callRecordings`
field.

## Fix

Only add the `callRecordings` widget when the `callRecordings` field
actually exists in the workspace (checked via
`flatFieldMetadataMaps.byUniversalIdentifier`). This mirrors the
command's existing guard on the `calendarEvent` object. Workspaces
without the field still get the fields / participants / timeline
widgets; the callRecordings widget is simply skipped.

The view fields for the record page do not reference `callRecordings`,
so only the widget needed guarding.

## Test plan
- [x] `nx typecheck twenty-server`
- [x] `oxlint --type-aware` on the changed file: 0 errors
- [ ] CI

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21967?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Charles Bochet
2026-06-22 17:02:29 +02:00
committed by GitHub
parent 6d7380dfec
commit 2276e12c12
@@ -61,12 +61,17 @@ const CALENDAR_EVENT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIERS = [
.widgets.fields.universalIdentifier,
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.calendarEventRecordPage.tabs.home
.widgets.participants.universalIdentifier,
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.calendarEventRecordPage.tabs.home
.widgets.callRecordings.universalIdentifier,
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.calendarEventRecordPage.tabs
.timeline.widgets.timeline.universalIdentifier,
];
const CALENDAR_EVENT_CALL_RECORDINGS_WIDGET_UNIVERSAL_IDENTIFIER =
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.calendarEventRecordPage.tabs.home
.widgets.callRecordings.universalIdentifier;
const CALENDAR_EVENT_CALL_RECORDINGS_FIELD_UNIVERSAL_IDENTIFIER =
STANDARD_OBJECTS.calendarEvent.fields.callRecordings.universalIdentifier;
@RegisteredWorkspaceCommand('2.15.0', 1800000002000)
@Command({
name: 'upgrade:2-15:sync-calendar-event-record-page',
@@ -96,6 +101,7 @@ export class SyncCalendarEventRecordPageCommand extends ActiveOrSuspendedWorkspa
const {
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatViewMaps,
flatViewFieldMaps,
flatViewFieldGroupMaps,
@@ -104,6 +110,7 @@ export class SyncCalendarEventRecordPageCommand extends ActiveOrSuspendedWorkspa
flatPageLayoutWidgetMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
'flatViewMaps',
'flatViewFieldMaps',
'flatViewFieldGroupMaps',
@@ -125,6 +132,19 @@ export class SyncCalendarEventRecordPageCommand extends ActiveOrSuspendedWorkspa
return;
}
const hasCallRecordingsField = isDefined(
flatFieldMetadataMaps.byUniversalIdentifier[
CALENDAR_EVENT_CALL_RECORDINGS_FIELD_UNIVERSAL_IDENTIFIER
],
);
const pageLayoutWidgetUniversalIdentifiers = hasCallRecordingsField
? [
...CALENDAR_EVENT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIERS,
CALENDAR_EVENT_CALL_RECORDINGS_WIDGET_UNIVERSAL_IDENTIFIER,
]
: CALENDAR_EVENT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIERS;
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
computeTwentyStandardApplicationAllFlatEntityMaps({
now: new Date().toISOString(),
@@ -169,8 +189,7 @@ export class SyncCalendarEventRecordPageCommand extends ActiveOrSuspendedWorkspa
standardFlatEntityMaps:
standardAllFlatEntityMaps.flatPageLayoutWidgetMaps,
existingFlatEntityMaps: flatPageLayoutWidgetMaps,
universalIdentifiers:
CALENDAR_EVENT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIERS,
universalIdentifiers: pageLayoutWidgetUniversalIdentifiers,
});
const totalOperationCount =