Migrate Company and Person standard fields in preparation for the enrichment app (#21171)

# Migrate Company and Person standard fields in preparation for the
enrichment app

## Why

Our standard `Person`/`Company` objects accumulated fields that aren't
generic to every
business, while missing a more universal revenue field that essentially
every CRM ships.
This PR makes the **Standard application** hold a tighter, more
universal set of fields,
and sets the stage for a follow-up PR that introduces a **People Data
Labs enrichment app**
to populate them.

## What changes

### Standard fields

**Demoted (Standard → Workspace Custom application)** — not generic
enough to ship as standard:

| Object  | Field                          | Type     |
| ------- | ------------------------------ | -------- |
| Company | annualRecurringRevenue (ARR)   | CURRENCY |
| Company | employees                      | NUMBER   |
| Company | idealCustomerProfile (ICP)     | BOOLEAN  |
| Company | xLink (X/Twitter)              | LINKS    |
| Person  | xLink (X/Twitter)              | LINKS    |
| Person  | city                           | TEXT     |

**Added (new generic Standard field)** — present in
Salesforce/HubSpot/Zoho, PDL-populatable:

| Object | Field | Type |
| ------- | ------------- |
-------------------------------------------------------- |
| Company | annualRevenue | CURRENCY (generic total revenue; replaces
the niche ARR) |

### Behavior by workspace

* **New workspaces:** demoted fields are gone; `annualRevenue` is
**active**.
* **Existing workspaces:** demoted fields are **preserved as active
custom fields, data intact**;
`annualRevenue` is created **inactive (opt-in)** with its column ready,
so a later activation
  is a metadata-only toggle.

### Upgrade commands (v2.9)

Three idempotent, per-workspace commands, run in timestamp order:

1. **`upgrade:2-9:move-demoted-standard-fields-to-custom-application`**
(1799000040000) —
re-owns the 6 demoted fields to the workspace custom application
(`isCustom = true`,
new `applicationId` + fresh `universalIdentifier`), keeping their data
and active state.
2. **`upgrade:2-9:rename-conflicting-custom-fields`** (1799000045000) —
if a workspace already
has a *custom* field named `annualRevenue`, renames it to
`annualRevenueCustom`
(data preserved via column rename) so the standard field can be added.
Skips non-custom matches.
3. **`upgrade:2-9:add-inactive-generic-standard-fields`**
(1799000050000) — creates
`Company.annualRevenue` on existing workspaces as inactive, guarded to
skip workspaces
   missing the target object or where the name is still taken.

**Failure model:** the workspace iterator isolates failures per
workspace (one workspace failing
never affects others); within a workspace the runner records per-command
status and resumes on the
next run, and every command is idempotent, so partial runs self-heal.

### Supporting changes

* **Field-option color palette:** widened the `TagColor` union
(`twenty-shared` `FieldMetadataOptions`
+ the field-metadata `options.input` DTO) from 10 colors to the full
theme palette, benefiting any
  future SELECT/MULTI_SELECT field.
* **Dev seeder:**
* The default "Annual Recurring Revenue" dashboard widget now points at
the generic
    `annualRevenue` field (renamed to "Annual Revenue").
* Removed the "Companies by Size (Stacked by City)" widget (relied on
the demoted `employees`).
* `employees` is dropped from company data seeds and re-added as a
**custom** field seed, so dev
workspaces still get an `employees` column matching the demoted
behavior.

### Cleanup

Front-end record types (`Company.ts`/`Person.ts`), the
`getDisplayNameFromParticipant` test mock,
metadata integration specs, the Zapier `crud_record` test, and the
regenerated
`get-standard-object-metadata-related-entity-ids` snapshot.

## ⚠️ Breaking change (intentional)

Removes standard fields `Company.annualRecurringRevenue`,
`Company.employees`,
`Company.idealCustomerProfile`, `Company.xLink`, `Person.xLink`, and
`Person.city` from the core
GraphQL schema (replaced by `Company.annualRevenue`).

This is why the breaking-changes check reports a large number of
removals — `graphql-inspector`
flags any removed object field plus its derived
aggregate/order-by/filter/update types.

**Mitigation:** the
`upgrade:2-9:move-demoted-standard-fields-to-custom-application` command
re-owns these fields as custom fields per workspace, preserving their
name and data, so existing
tenants keep working. New workspaces won't have them.
This commit is contained in:
Raphaël Bosi
2026-06-04 17:54:04 +02:00
committed by GitHub
parent e0d42323af
commit 41d5d80a65
56 changed files with 1798 additions and 3544 deletions
@@ -0,0 +1,29 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { AddInactiveGenericStandardFieldsCommand } from 'src/database/commands/upgrade-version-command/2-10/2-10-workspace-command-1799000050000-add-inactive-generic-standard-fields.command';
import { MoveDemotedStandardFieldsToCustomApplicationCommand } from 'src/database/commands/upgrade-version-command/2-10/2-10-workspace-command-1799000040000-move-demoted-standard-fields-to-custom-application.command';
import { RenameConflictingCustomFieldsCommand } from 'src/database/commands/upgrade-version-command/2-10/2-10-workspace-command-1799000045000-rename-conflicting-custom-fields.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
@Module({
imports: [
ApplicationModule,
TypeOrmModule.forFeature([FieldMetadataEntity]),
WorkspaceCacheModule,
WorkspaceIteratorModule,
WorkspaceMetadataVersionModule,
WorkspaceMigrationModule,
],
providers: [
MoveDemotedStandardFieldsToCustomApplicationCommand,
RenameConflictingCustomFieldsCommand,
AddInactiveGenericStandardFieldsCommand,
],
})
export class V2_10_UpgradeVersionCommandModule {}
@@ -0,0 +1,159 @@
import { Command } from 'nest-commander';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
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';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
import { getMetadataSerializedRelationNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-serialized-relation-names.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
const DEMOTED_STANDARD_FIELDS: {
universalIdentifier: string;
label: string;
}[] = [
{
universalIdentifier: '20202020-602a-495c-9776-f5d5b11d227b',
label: 'Company.annualRecurringRevenue',
},
{
universalIdentifier: '20202020-8965-464a-8a75-74bafc152a0b',
label: 'Company.employees',
},
{
universalIdentifier: '20202020-ba6b-438a-8213-2c5ba28d76a2',
label: 'Company.idealCustomerProfile',
},
{
universalIdentifier: '20202020-6f64-4fd9-9580-9c1991c7d8c3',
label: 'Company.xLink',
},
{
universalIdentifier: '20202020-8fc2-487c-b84a-55a99b145cfd',
label: 'Person.xLink',
},
{
universalIdentifier: '20202020-5243-4ffb-afc5-2c675da41346',
label: 'Person.city',
},
];
@RegisteredWorkspaceCommand('2.10.0', 1799000040000)
@Command({
name: 'upgrade:2-10:move-demoted-standard-fields-to-custom-application',
description:
'Re-own the demoted Company ARR / ICP / Employees, Company/Person X (Twitter) and Person City standard fields to the workspace custom application, preserving their data and keeping them active',
})
export class MoveDemotedStandardFieldsToCustomApplicationCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly applicationService: ApplicationService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
@InjectRepository(FieldMetadataEntity)
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
);
const { flatFieldMetadataMaps } =
await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatFieldMetadataMaps',
]);
const fieldsToReown: { id: string; label: string }[] = [];
for (const {
universalIdentifier,
label,
} of DEMOTED_STANDARD_FIELDS) {
const flatFieldMetadata =
findFlatEntityByUniversalIdentifier<FlatFieldMetadata>({
flatEntityMaps: flatFieldMetadataMaps,
universalIdentifier,
});
if (!isDefined(flatFieldMetadata)) {
continue;
}
const isStillOwnedByStandardApplication =
flatFieldMetadata.applicationId === twentyStandardFlatApplication.id &&
!flatFieldMetadata.isCustom;
if (!isStillOwnedByStandardApplication) {
continue;
}
fieldsToReown.push({ id: flatFieldMetadata.id, label });
}
if (fieldsToReown.length === 0) {
this.logger.log(
`No standard fields to move to the custom application for workspace ${workspaceId}`,
);
return;
}
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Moving ${fieldsToReown.length} field(s) to the custom application for workspace ${workspaceId}: ${fieldsToReown.map(({ label }) => label).join(', ')}`,
);
if (isDryRun) {
return;
}
for (const { id } of fieldsToReown) {
await this.fieldMetadataRepository.update(
{ id },
{
applicationId: workspaceCustomFlatApplication.id,
isCustom: true,
universalIdentifier: uuidv4(),
},
);
}
const fieldMetadataRelatedNames = [
'fieldMetadata',
...getMetadataRelatedMetadataNames('fieldMetadata'),
...getMetadataSerializedRelationNames('fieldMetadata'),
] as const;
const cacheKeysToFlush = [
...new Set(fieldMetadataRelatedNames.map(getMetadataFlatEntityMapsKey)),
];
await this.workspaceCacheService.flush(workspaceId, cacheKeysToFlush);
await this.workspaceMetadataVersionService.incrementMetadataVersion(
workspaceId,
);
this.logger.log(
`Moved ${fieldsToReown.length} field(s) to the custom application for workspace ${workspaceId}`,
);
}
}
@@ -0,0 +1,196 @@
import { Command } from 'nest-commander';
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
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';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
const NEW_STANDARD_FIELDS: {
fieldName: string;
objectUniversalIdentifier: string;
}[] = [
{
fieldName: 'annualRevenue',
objectUniversalIdentifier: STANDARD_OBJECTS.company.universalIdentifier,
},
];
const computeAvailableFieldName = (
baseFieldName: string,
takenFieldNames: Set<string>,
): string => {
let candidateFieldName = `${baseFieldName}Custom`;
let suffix = 2;
while (takenFieldNames.has(candidateFieldName)) {
candidateFieldName = `${baseFieldName}Custom${suffix}`;
suffix++;
}
return candidateFieldName;
};
@RegisteredWorkspaceCommand('2.10.0', 1799000045000)
@Command({
name: 'upgrade:2-10:rename-conflicting-custom-fields',
description:
'Rename a pre-existing custom field whose name collides with the new generic standard field (Company annualRevenue), preserving its data, so the standard field can be added',
})
export class RenameConflictingCustomFieldsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
const { flatFieldMetadataMaps } =
await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatFieldMetadataMaps',
]);
const allFlatFieldMetadatas = Object.values(
flatFieldMetadataMaps.byUniversalIdentifier,
).filter(isDefined);
const takenFieldNamesByObject = new Map<string, Set<string>>();
for (const flatFieldMetadata of allFlatFieldMetadatas) {
const takenFieldNames =
takenFieldNamesByObject.get(
flatFieldMetadata.objectMetadataUniversalIdentifier,
) ?? new Set<string>();
takenFieldNames.add(flatFieldMetadata.name);
takenFieldNamesByObject.set(
flatFieldMetadata.objectMetadataUniversalIdentifier,
takenFieldNames,
);
}
const fieldsToRename: { field: FlatFieldMetadata; newName: string }[] = [];
for (const {
fieldName,
objectUniversalIdentifier,
} of NEW_STANDARD_FIELDS) {
const conflictingField = allFlatFieldMetadatas.find(
(flatFieldMetadata) =>
flatFieldMetadata.objectMetadataUniversalIdentifier ===
objectUniversalIdentifier && flatFieldMetadata.name === fieldName,
);
if (!isDefined(conflictingField)) {
continue;
}
if (!conflictingField.isCustom) {
this.logger.warn(
`Non-custom field named "${fieldName}" exists on object ${objectUniversalIdentifier} for workspace ${workspaceId}; skipping rename`,
);
continue;
}
const takenFieldNames =
takenFieldNamesByObject.get(objectUniversalIdentifier) ??
new Set<string>();
const newName = computeAvailableFieldName(fieldName, takenFieldNames);
takenFieldNames.add(newName);
fieldsToRename.push({ field: conflictingField, newName });
}
if (fieldsToRename.length === 0) {
this.logger.log(
`No custom fields conflict with the new standard field names for workspace ${workspaceId}`,
);
return;
}
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Renaming ${fieldsToRename.length} conflicting custom field(s) for workspace ${workspaceId}: ${fieldsToRename
.map(({ field, newName }) => `${field.name} -> ${newName}`)
.join(', ')}`,
);
if (isDryRun) {
return;
}
const fieldsToRenameByApplication = fieldsToRename.reduce<
Map<string, typeof fieldsToRename>
>((fieldsByApplication, fieldToRename) => {
const applicationUniversalIdentifier =
fieldToRename.field.applicationUniversalIdentifier;
const fieldsForApplication =
fieldsByApplication.get(applicationUniversalIdentifier) ?? [];
fieldsForApplication.push(fieldToRename);
fieldsByApplication.set(
applicationUniversalIdentifier,
fieldsForApplication,
);
return fieldsByApplication;
}, new Map());
for (const [
applicationUniversalIdentifier,
fieldsForApplication,
] of fieldsToRenameByApplication) {
const flatEntityToUpdate = fieldsForApplication.map(
({ field, newName }) => ({
...field,
name: newName,
label: `${field.label} (custom)`,
isLabelSyncedWithName: false,
}),
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
fieldMetadata: {
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate,
},
},
workspaceId,
isSystemBuild: true,
applicationUniversalIdentifier,
},
);
if (validateAndBuildResult.status === 'fail') {
this.logger.error(
`Failed to rename conflicting custom fields:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
);
throw new Error(
`Failed to rename conflicting custom fields for workspace ${workspaceId}`,
);
}
}
this.logger.log(
`Renamed ${fieldsToRename.length} conflicting custom field(s) for workspace ${workspaceId}`,
);
}
}
@@ -0,0 +1,182 @@
import { Command } from 'nest-commander';
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
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';
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
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 { 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';
const NEW_STANDARD_FIELD_UNIVERSAL_IDENTIFIERS = [
STANDARD_OBJECTS.company.fields.annualRevenue.universalIdentifier,
];
@RegisteredWorkspaceCommand('2.10.0', 1799000050000)
@Command({
name: 'upgrade:2-10:add-inactive-generic-standard-fields',
description:
'Create the new generic standard field (Company annualRevenue) on existing workspaces as inactive (opt-in), with its column ready for activation',
})
export class AddInactiveGenericStandardFieldsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly applicationService: ApplicationService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
const { twentyStandardFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
);
const {
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatObjectMetadataMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatFieldMetadataMaps',
'flatObjectMetadataMaps',
]);
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
computeTwentyStandardApplicationAllFlatEntityMaps({
now: new Date().toISOString(),
workspaceId,
twentyStandardApplicationId: twentyStandardFlatApplication.id,
});
const takenFieldNamesByObject = new Map<string, Set<string>>();
for (const existingFlatFieldMetadata of Object.values(
existingFlatFieldMetadataMaps.byUniversalIdentifier,
).filter(isDefined)) {
const takenFieldNames =
takenFieldNamesByObject.get(
existingFlatFieldMetadata.objectMetadataUniversalIdentifier,
) ?? new Set<string>();
takenFieldNames.add(existingFlatFieldMetadata.name);
takenFieldNamesByObject.set(
existingFlatFieldMetadata.objectMetadataUniversalIdentifier,
takenFieldNames,
);
}
const fieldsToCreate: FlatFieldMetadata[] = [];
for (const universalIdentifier of NEW_STANDARD_FIELD_UNIVERSAL_IDENTIFIERS) {
const standardFlatFieldMetadata =
standardAllFlatEntityMaps.flatFieldMetadataMaps.byUniversalIdentifier[
universalIdentifier
];
if (!isDefined(standardFlatFieldMetadata)) {
continue;
}
const fieldAlreadyExists = isDefined(
existingFlatFieldMetadataMaps.byUniversalIdentifier[
standardFlatFieldMetadata.universalIdentifier
],
);
if (fieldAlreadyExists) {
continue;
}
const targetObjectExists = isDefined(
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
flatEntityMaps: flatObjectMetadataMaps,
universalIdentifier:
standardFlatFieldMetadata.objectMetadataUniversalIdentifier,
}),
);
if (!targetObjectExists) {
continue;
}
const fieldNameIsTaken =
takenFieldNamesByObject
.get(standardFlatFieldMetadata.objectMetadataUniversalIdentifier)
?.has(standardFlatFieldMetadata.name) === true;
if (fieldNameIsTaken) {
this.logger.warn(
`Field name "${standardFlatFieldMetadata.name}" is already taken on its object for workspace ${workspaceId}; skipping (run upgrade:2-10:rename-conflicting-custom-fields first)`,
);
continue;
}
fieldsToCreate.push({
...standardFlatFieldMetadata,
isActive: false,
viewFieldIds: [],
viewFieldUniversalIdentifiers: [],
});
}
if (fieldsToCreate.length === 0) {
this.logger.log(
`All new generic standard fields already exist for workspace ${workspaceId}, skipping`,
);
return;
}
this.logger.log(
`${isDryRun ? '[DRY RUN] ' : ''}Creating ${fieldsToCreate.length} inactive standard field(s) for workspace ${workspaceId}: ${fieldsToCreate.map(({ name }) => name).join(', ')}`,
);
if (isDryRun) {
return;
}
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
fieldMetadata: {
flatEntityToCreate: fieldsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
workspaceId,
applicationUniversalIdentifier:
twentyStandardFlatApplication.universalIdentifier,
},
);
if (validateAndBuildResult.status === 'fail') {
this.logger.error(
`Failed to create inactive generic standard fields:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
);
throw new Error(
`Failed to create inactive generic standard fields for workspace ${workspaceId}`,
);
}
this.logger.log(
`Successfully created ${fieldsToCreate.length} inactive standard field(s) for workspace ${workspaceId}`,
);
}
}
@@ -12,6 +12,7 @@ import { V2_5_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-
import { V2_7_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-7/2-7-upgrade-version-command.module';
import { V2_8_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-8/2-8-upgrade-version-command.module';
import { V2_9_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-9/2-9-upgrade-version-command.module';
import { V2_10_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-10/2-10-upgrade-version-command.module';
@Module({
imports: [
@@ -27,6 +28,7 @@ import { V2_9_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-
V2_7_UpgradeVersionCommandModule,
V2_8_UpgradeVersionCommandModule,
V2_9_UpgradeVersionCommandModule,
V2_10_UpgradeVersionCommandModule,
],
})
export class WorkspaceCommandProviderModule {}