Add call recording schema and meeting bot scaffold (#21584)

## Summary
- add 2.13 upgrade commands for call recording request status and
dropping CalendarEvent recordingPreference
- remove the recording preference from the core CalendarEvent standard
object
- add a scaffold-generated twenty-meeting-bot app with logo and the
CalendarEvent meetingBotPreference field

## Tests
- yarn install
- yarn lint
- yarn twenty dev:typecheck
- git diff --check


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21584?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:
nitin
2026-06-15 19:03:12 +05:30
committed by GitHub
parent d1ba63d4a4
commit 8a866dba54
47 changed files with 5716 additions and 1502 deletions
@@ -1,9 +1,12 @@
import { Command } from 'nest-commander';
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
import {
STANDARD_OBJECTS,
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-shared/metadata';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { v4 as uuidv4 } from 'uuid';
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
@@ -12,6 +15,7 @@ import { buildNavigationCommandMenuItemOperationsOrThrow } from 'src/database/co
import {
buildCalendarEventFieldRenameUpdates,
buildCallRecordingObjectRenameUpdates,
LEGACY_CALENDAR_EVENT_RECORDING_PREFERENCE_FIELD_UNIVERSAL_IDENTIFIER,
} from 'src/database/commands/upgrade-version-command/2-10/utils/call-recording-name-collision.util';
import {
getExistingOrStandardFlatEntityOrThrow,
@@ -43,7 +47,6 @@ const CALL_RECORDING_OBJECT_METADATA_UNIVERSAL_IDENTIFIERS = [
const CALL_RECORDING_FIELD_METADATA_UNIVERSAL_IDENTIFIERS = [
...getUniversalIdentifiers(STANDARD_OBJECTS.callRecording.fields),
STANDARD_OBJECTS.calendarEvent.fields.recordingPreference.universalIdentifier,
STANDARD_OBJECTS.calendarEvent.fields.callRecordings.universalIdentifier,
];
@@ -92,6 +95,91 @@ const CALL_RECORDING_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIERS = [
.timeline.widgets.timeline.universalIdentifier,
];
// Preserves the shipped 2.10 upgrade path after recordingPreference moved out of
// current standard metadata.
const buildLegacyCalendarEventRecordingPreferenceFieldMetadata = ({
calendarEventObjectMetadata,
now,
twentyStandardApplicationId,
workspaceId,
}: {
calendarEventObjectMetadata: FlatObjectMetadata;
now: string;
twentyStandardApplicationId: string;
workspaceId: string;
}): FlatFieldMetadata<FieldMetadataType.SELECT> => ({
id: uuidv4(),
universalIdentifier:
LEGACY_CALENDAR_EVENT_RECORDING_PREFERENCE_FIELD_UNIVERSAL_IDENTIFIER,
applicationId: twentyStandardApplicationId,
workspaceId,
objectMetadataId: calendarEventObjectMetadata.id,
type: FieldMetadataType.SELECT,
name: 'recordingPreference',
label: 'Recording Preference',
description:
'Whether to record this event, applied on top of the workspace policy',
icon: 'IconSettingsAutomation',
isActive: true,
isSystem: false,
isNullable: false,
isUnique: false,
isUIEditable: true,
isLabelSyncedWithName: false,
standardOverrides: null,
defaultValue: "'AUTO'",
settings: null,
options: [
{
id: '4c4761ce-ffbf-4176-be7f-5cf5257c8bff',
value: 'AUTO',
label: 'Auto',
position: 0,
color: 'blue',
},
{
id: '1ae19135-e1a1-4a96-b866-91643622e554',
value: 'ON',
label: 'On',
position: 1,
color: 'green',
},
{
id: '8c69a74f-2ab7-4c19-a813-eb0ea3533fd3',
value: 'OFF',
label: 'Off',
position: 2,
color: 'gray',
},
],
relationTargetFieldMetadataId: null,
relationTargetObjectMetadataId: null,
morphId: null,
viewFieldIds: [],
viewFilterIds: [],
fieldPermissionIds: [],
kanbanAggregateOperationViewIds: [],
calendarViewIds: [],
mainGroupByFieldMetadataViewIds: [],
createdAt: now,
updatedAt: now,
applicationUniversalIdentifier:
TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER,
objectMetadataUniversalIdentifier:
STANDARD_OBJECTS.calendarEvent.universalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
fieldPermissionUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
viewSortIds: [],
viewSortUniversalIdentifiers: [],
universalSettings: null,
});
@RegisteredWorkspaceCommand('2.10.0', 1799000055000)
@Command({
name: 'upgrade:2-10:sync-call-recording-standard-objects',
@@ -200,6 +288,22 @@ export class SyncCallRecordingStandardObjectsCommand extends ActiveOrSuspendedWo
renamedCollisionObjectMetadatas,
});
const legacyCalendarEventRecordingPreferenceFieldMetadata =
flatFieldMetadataMaps.byUniversalIdentifier[
LEGACY_CALENDAR_EVENT_RECORDING_PREFERENCE_FIELD_UNIVERSAL_IDENTIFIER
];
const legacyCalendarEventRecordingPreferenceFieldMetadataToCreate =
isDefined(legacyCalendarEventRecordingPreferenceFieldMetadata)
? []
: [
buildLegacyCalendarEventRecordingPreferenceFieldMetadata({
calendarEventObjectMetadata,
now,
twentyStandardApplicationId: twentyStandardFlatApplication.id,
workspaceId,
}),
];
const allFlatEntityOperationByMetadataName = {
objectMetadata: {
flatEntityToCreate:
@@ -214,14 +318,16 @@ export class SyncCallRecordingStandardObjectsCommand extends ActiveOrSuspendedWo
flatEntityToUpdate: [],
},
fieldMetadata: {
flatEntityToCreate:
getStandardFlatEntitiesToCreateOrThrow<FlatFieldMetadata>({
flatEntityToCreate: [
...getStandardFlatEntitiesToCreateOrThrow<FlatFieldMetadata>({
standardFlatEntityMaps:
standardAllFlatEntityMaps.flatFieldMetadataMaps,
existingFlatEntityMaps: flatFieldMetadataMaps,
universalIdentifiers:
CALL_RECORDING_FIELD_METADATA_UNIVERSAL_IDENTIFIERS,
}),
...legacyCalendarEventRecordingPreferenceFieldMetadataToCreate,
],
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
@@ -6,6 +6,7 @@ import {
buildCallRecordingObjectRenameUpdates,
findCalendarEventFieldNameCollisionsForCallRecording,
findCallRecordingObjectNameCollisions,
LEGACY_CALENDAR_EVENT_RECORDING_PREFERENCE_FIELD_UNIVERSAL_IDENTIFIER,
resolveAvailableOldCalendarEventFieldName,
resolveAvailableOldCallRecordingObjectNames,
} from 'src/database/commands/upgrade-version-command/2-10/utils/call-recording-name-collision.util';
@@ -327,8 +328,7 @@ describe('findCalendarEventFieldNameCollisionsForCallRecording', () => {
const maps = buildFlatFieldMetadataMaps([
getCalendarEventFieldMetadataMock({
universalIdentifier:
STANDARD_OBJECTS.calendarEvent.fields.recordingPreference
.universalIdentifier,
LEGACY_CALENDAR_EVENT_RECORDING_PREFERENCE_FIELD_UNIVERSAL_IDENTIFIER,
name: 'recordingPreference',
}),
getCalendarEventFieldMetadataMock({
@@ -15,6 +15,9 @@ const FIELD_OLD_NAME_SUFFIX = 'Old';
const FIELD_OLD_LABEL_SUFFIX = ' (Old)';
const MAX_OLD_NAME_ATTEMPTS = 100;
export const LEGACY_CALENDAR_EVENT_RECORDING_PREFERENCE_FIELD_UNIVERSAL_IDENTIFIER =
'1d231e7e-9bbe-410b-8007-ea7678a83e58';
const CALL_RECORDING_CALENDAR_EVENT_FIELD_NAMES = [
CALENDAR_EVENT_RECORDING_PREFERENCE_FIELD_NAME,
CALENDAR_EVENT_CALL_RECORDINGS_FIELD_NAME,
@@ -22,8 +25,7 @@ const CALL_RECORDING_CALENDAR_EVENT_FIELD_NAMES = [
const CALL_RECORDING_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIERS =
new Set<string>([
STANDARD_OBJECTS.calendarEvent.fields.recordingPreference
.universalIdentifier,
LEGACY_CALENDAR_EVENT_RECORDING_PREFERENCE_FIELD_UNIVERSAL_IDENTIFIER,
STANDARD_OBJECTS.calendarEvent.fields.callRecordings.universalIdentifier,
]);