Route pre-2.19 upgrade commands through a legacy validate-build path (#22884)
## Problem Since the centralized metadata side-effect engine landed in v2.19, `WorkspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigrationFromRecord` runs `metadataSideEffectEngineService.expandWithSideEffects(...)` before building. As a result every historical upgrade command (`upgrade-version-command/1-21/*` … `2-18/*`), authored before the engine existed, now flows through it. Their operation matrix is no longer applied literally: the engine injects/cascades companions (system fields, `searchVector` field + GIN index, `searchFieldMetadata` rows, unique backing indexes) and can hard-fail on reserved-identifier collisions (`RESERVED_SYSTEM_UNIVERSAL_IDENTIFIER`). Two hazards for already-shipped commands: 1. **Collision → hard failure**: a command declaring a companion the engine now owns collides with the engine's deterministic `universalIdentifier`. 2. **Silent drift**: on object/field create/delete the engine adds/cascades companions the command author never intended, so workspaces upgraded now differ structurally from those upgraded incrementally before 2.19. Suspected real-world impact: a self-hosted user upgrading v2.6.1 → v2.21.0 hit `duplicate key value violates unique constraint "IDX_SEARCH_FIELD_METADATA_OBJECT_FIELD_UNIQUE"` in `upgrade:2-16:backfill-search-field-metadata`, because object-creating commands now cascade and pre-create the deterministic `searchFieldMetadata` rows the standalone backfill then re-inserts. ## Changes - `workspace-migration-validate-build-and-run-service.ts`: extract the shared compute-and-run tail into a private method, and add `validateBuildAndRunLegacyWorkspaceMigration` (marked `@deprecated`) that skips `expandWithSideEffects` and applies the matrix literally. The existing side-effect entry points are unchanged (the live API and application manifests depend on them). - Repoint **all** pre-2.19 upgrade command call sites (1-21 … 2-18, including `2-10 sync-call-recording-standard-objects`) to the legacy method. Only the four `2-20/*` commands (target version ≥ 2.19) remain on the side-effect path. - `2-16 backfill-search-field-metadata`: recompute `flatSearchFieldMetadataMaps` from the database before building the existing-rows dedupe set. The migration runner only invalidates the flat-maps keys a migration touched, so during a cross-version upgrade earlier commands can leave this map stale; a stale map breaks the dedupe and re-inserts rows, tripping `IDX_SEARCH_FIELD_METADATA_OBJECT_FIELD_UNIQUE`. This is the direct fix for the reported failure. - Export `FlatEntityMapsBundle` so the shared tail can be typed. - Document the side-effect vs legacy path and the selection rule in `packages/twenty-server/docs/UPGRADE_COMMANDS.md`. Selection rule: target version **< 2.19** → legacy path; **≥ 2.19** → side-effect path (default). No exceptions. ## Known gap / merge ordering The static twenty-standard definition declares all of `callRecording`'s fields (including the `searchVector` system field) but **not** its `searchVector` GIN index — every other searchable standard object declares its GIN index statically. On the legacy path, workspaces upgrading through `2-10 sync-call-recording-standard-objects` therefore create the `searchVector` column unindexed (`searchFieldMetadata` rows are created later in the same pipeline by the 2-16 backfill). The static GIN index declaration plus a backfill for already-upgraded workspaces land in a follow-up (twentyhq/core-team-issues#2672), which must ship in the same release as this PR. ## Out of scope (separate follow-ups) - `UpgradeMigrationService.getLastAttemptedInstanceCommand()` ordering. - callRecording `searchVector` GIN index static declaration + backfill (twentyhq/core-team-issues#2672, same-release dependency, see above). ## Test plan - `nx typecheck twenty-server` passes. - `nx lint:diff-with-main twenty-server` (oxlint + oxfmt) clean on changed files. - 2-20 command specs (which exercise the unchanged side-effect path) pass. --------- Co-authored-by: twenty <noreply@twenty.com>
This commit is contained in:
@@ -106,6 +106,24 @@ export class BackfillStandardSkillsCommand
|
||||
|
||||
The base class `ActiveOrSuspendedWorkspaceCommandRunner` handles workspace iteration and provides `--dry-run`, `--verbose`, and workspace filter options automatically.
|
||||
|
||||
### Applying a migration matrix: side-effect vs legacy path
|
||||
|
||||
Commands that build a metadata migration go through `WorkspaceMigrationValidateBuildAndRunService`. Two entry points exist:
|
||||
|
||||
- `validateBuildAndRunWorkspaceMigration` (default): runs the operation matrix through the metadata side-effect engine (`expandWithSideEffects`) before building. The engine injects and cascades engine-owned companions (system fields and relations, the `searchVector` field and its GIN index, `searchFieldMetadata` rows, unique backing indexes). This is what the live API and application manifests rely on, so new commands should use it.
|
||||
- `validateBuildAndRunLegacyWorkspaceMigration`: skips side-effect expansion and applies the matrix literally, exactly as it was authored.
|
||||
|
||||
The side-effect engine landed in v2.19. Commands authored before then declared their companions explicitly and were never designed to flow through the engine. Running them through it retroactively changes their behavior: it can hard-fail on reserved-identifier collisions (`RESERVED_SYSTEM_UNIVERSAL_IDENTIFIER`) and silently create rows the command never intended (for example, the deterministic `searchFieldMetadata` rows that the standalone `upgrade:2-16:backfill-search-field-metadata` backfill then re-inserts, hitting `IDX_SEARCH_FIELD_METADATA_OBJECT_FIELD_UNIQUE`).
|
||||
|
||||
Rule of thumb:
|
||||
|
||||
- Target version **< 2.19** → use the **legacy** method.
|
||||
- Target version **>= 2.19** → use the default side-effect method.
|
||||
|
||||
All pre-2.19 commands follow this rule, including `upgrade:2-10:sync-call-recording-standard-objects`: it builds its create-set from the static twenty-standard definition (which declares all of `callRecording`'s fields, including the `searchVector` system field) and runs it through the legacy path so nothing is injected on top. Its matrix contains no `searchFieldMetadata` operations; the deterministic rows are created later in the same upgrade pipeline by `upgrade:2-16:backfill-search-field-metadata`, which derives them from the standard definition.
|
||||
|
||||
Known gap: the static definition does not yet declare `callRecording`'s `searchVector` GIN index (every other searchable standard object declares its GIN index statically), so workspaces upgrading through 2-10 on the legacy path create the `searchVector` column unindexed. The static declaration plus a backfill for already-upgraded workspaces land in a follow-up (twentyhq/core-team-issues#2672), which must ship in the same release as this legacy path.
|
||||
|
||||
## Execution Order
|
||||
|
||||
Within a given version of Twenty, the upgrade pipeline runs commands in this order, sorted by timestamp within each group:
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ export class AddComposeEmailCommandMenuItemCommand extends ActiveOrSuspendedWork
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+2
-2
@@ -112,7 +112,7 @@ export class BackfillMessageThreadSubjectCommand extends ActiveOrSuspendedWorksp
|
||||
};
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
@@ -229,7 +229,7 @@ export class BackfillMessageThreadSubjectCommand extends ActiveOrSuspendedWorksp
|
||||
};
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ export class DeduplicateEngineCommandsCommand extends ActiveOrSuspendedWorkspace
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ export class FixSelectAllCommandMenuItemsCommand extends ActiveOrSuspendedWorksp
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ export class MigrateAiAgentTextToJsonResponseFormatCommand extends ActiveOrSuspe
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
agent: {
|
||||
|
||||
+1
-1
@@ -290,7 +290,7 @@ export class RefactorNavigationCommandsCommand extends ActiveOrSuspendedWorkspac
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -147,7 +147,7 @@ export class FixMessageThreadViewAndLabelIdentifierCommand extends ActiveOrSuspe
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
objectMetadata: {
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ export class UpdateSearchCommandMenuItemLabelsCommand extends ActiveOrSuspendedW
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ export class AddSendEmailRecordSelectionCommandMenuItemsCommand extends ActiveOr
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ export class BackfillStandardSkillsCommand extends ActiveOrSuspendedWorkspaceCom
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
skill: {
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ export class FixMergeCommandSelectAllCommand extends ActiveOrSuspendedWorkspaceC
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+3
-3
@@ -182,7 +182,7 @@ export class BackfillRecordPageLayoutsCommand extends ActiveOrSuspendedWorkspace
|
||||
);
|
||||
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
viewField: {
|
||||
@@ -400,7 +400,7 @@ export class BackfillRecordPageLayoutsCommand extends ActiveOrSuspendedWorkspace
|
||||
);
|
||||
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
@@ -544,7 +544,7 @@ export class BackfillRecordPageLayoutsCommand extends ActiveOrSuspendedWorkspace
|
||||
}
|
||||
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ export class UpdateGlobalObjectContextCommandMenuItemsCommand extends ActiveOrSu
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ export class GateExportImportCommandMenuItemsByPermissionFlagCommand extends Act
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -148,7 +148,7 @@ export class AddLayoutCustomizationGuardToEditCommandsCommand extends ActiveOrSu
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -163,7 +163,7 @@ export class RenameConflictingCustomFieldsCommand extends ActiveOrSuspendedWorks
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@ export class AddInactiveGenericStandardFieldsCommand extends ActiveOrSuspendedWo
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
|
||||
+2
-2
@@ -488,7 +488,7 @@ export class SyncCallRecordingStandardObjectsCommand extends ActiveOrSuspendedWo
|
||||
allFlatEntityOperationByMetadataName,
|
||||
} of collisionRenameMigrations) {
|
||||
const renameResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
applicationUniversalIdentifier,
|
||||
@@ -509,7 +509,7 @@ export class SyncCallRecordingStandardObjectsCommand extends ActiveOrSuspendedWo
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
applicationUniversalIdentifier:
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ export class SyncCreateRecordCommandAvailabilityExpressionCommand extends Active
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ export class FixStandardRelationFieldLabelsIconsCommand extends ActiveOrSuspende
|
||||
}
|
||||
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ export class SyncCallRecordingRequestStatusCommand extends ActiveOrSuspendedWork
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
applicationUniversalIdentifier:
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ export class DropCalendarEventRecordingPreferenceCommand extends ActiveOrSuspend
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
|
||||
+1
-1
@@ -217,7 +217,7 @@ export class SyncCalendarEventRecordPageCommand extends ActiveOrSuspendedWorkspa
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
applicationUniversalIdentifier:
|
||||
|
||||
+20
-12
@@ -7,8 +7,8 @@ import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/w
|
||||
import { buildSearchFieldMetadataBackfillOperations } from 'src/database/commands/upgrade-version-command/2-16/utils/build-search-field-metadata-backfill-operations.util';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
|
||||
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
|
||||
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';
|
||||
|
||||
@RegisteredWorkspaceCommand('2.16.0', 1799100000000)
|
||||
@@ -33,6 +33,15 @@ export class BackfillSearchFieldMetadataCommand extends ActiveOrSuspendedWorkspa
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
// The migration runner only invalidates the flat-maps keys a migration touched,
|
||||
// so during a cross-version upgrade earlier commands can leave this map stale.
|
||||
// A stale map breaks the existing-rows dedupe below and re-inserts rows,
|
||||
// tripping IDX_SEARCH_FIELD_METADATA_OBJECT_FIELD_UNIQUE. Recompute from the
|
||||
// database before deriving the create-set.
|
||||
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'flatSearchFieldMetadataMaps',
|
||||
]);
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
@@ -58,16 +67,15 @@ export class BackfillSearchFieldMetadataCommand extends ActiveOrSuspendedWorkspa
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const {
|
||||
flatSearchFieldMetadatasToCreateByApplicationUniversalIdentifier,
|
||||
} = buildSearchFieldMetadataBackfillOperations({
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatSearchFieldMetadataMaps,
|
||||
standardFlatSearchFieldMetadataMaps:
|
||||
standardAllFlatEntityMaps.flatSearchFieldMetadataMaps,
|
||||
customApplicationId: workspaceCustomFlatApplication.id,
|
||||
});
|
||||
const { flatSearchFieldMetadatasToCreateByApplicationUniversalIdentifier } =
|
||||
buildSearchFieldMetadataBackfillOperations({
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatSearchFieldMetadataMaps,
|
||||
standardFlatSearchFieldMetadataMaps:
|
||||
standardAllFlatEntityMaps.flatSearchFieldMetadataMaps,
|
||||
customApplicationId: workspaceCustomFlatApplication.id,
|
||||
});
|
||||
|
||||
const applicationUniversalIdentifiers = Object.keys(
|
||||
flatSearchFieldMetadatasToCreateByApplicationUniversalIdentifier,
|
||||
@@ -114,7 +122,7 @@ export class BackfillSearchFieldMetadataCommand extends ActiveOrSuspendedWorkspa
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ export class SyncCallRecordingStatusCommand extends ActiveOrSuspendedWorkspaceCo
|
||||
};
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
applicationUniversalIdentifier:
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ export class SyncCallRecordingNavigationCommandMenuItemAvailabilityExpressionCom
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
workspaceId,
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ export class AddReplyToMessageParticipantRoleOptionCommand extends ActiveOrSuspe
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
workspaceId,
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ export class AddWorkspaceMemberJobTitleFieldCommand extends ActiveOrSuspendedWor
|
||||
};
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ export class AddMessageIsDraftFieldCommand extends ActiveOrSuspendedWorkspaceCom
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ export class SetCalendarEventDescriptionDisplayedMaxRowsCommand extends ActiveOr
|
||||
};
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ export class DropMessageDirectionFieldCommand extends ActiveOrSuspendedWorkspace
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ export class BackfillImageIdentifierFieldMetadataIdCommand extends ActiveOrSuspe
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
objectMetadata: {
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ export class DeleteGaugeWidgetsCommand extends ActiveOrSuspendedWorkspaceCommand
|
||||
);
|
||||
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ export class NormalizeCompositeFieldDefaultsCommand extends ActiveOrSuspendedWor
|
||||
}));
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
fieldMetadata: {
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ export class SyncCommandMenuItemAvailabilityExpressionsCommand extends ActiveOrS
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ export class DropConnectedAccountStandardObjectCommand extends ActiveOrSuspended
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ export class DropChannelStandardObjectsCommand extends ActiveOrSuspendedWorkspac
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
|
||||
+1
-1
@@ -194,7 +194,7 @@ export class BackfillRelationJoinColumnIndexesCommand extends ActiveOrSuspendedW
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
isSystemBuild: true,
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ export class GateDefaultCommandMenuItemsByPermissionFlagCommand extends ActiveOr
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ export class BackfillFieldsWidgetNewFieldDefaultVisibilityCommand extends Active
|
||||
updatedWidgets,
|
||||
] of widgetsToBackfillByApplicationUniversalIdentifier) {
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import { InferDeletionFromMissingEntities } from 'src/engine/workspace-manager/w
|
||||
export type WorkspaceMigrationRelatedFlatEntityMaps =
|
||||
Partial<AllFlatEntityMaps> & WorkspaceMigrationBuilderAdditionalCacheDataMaps;
|
||||
|
||||
type FlatEntityMapsBundle = {
|
||||
export type FlatEntityMapsBundle = {
|
||||
flatApplicationMaps: FlatApplicationCacheMaps;
|
||||
allRelatedFlatEntityMaps: WorkspaceMigrationRelatedFlatEntityMaps;
|
||||
allMetadataNameCacheToCompute: AllMetadataName[];
|
||||
|
||||
+115
-15
@@ -19,7 +19,10 @@ import {
|
||||
IdByUniversalIdentifierByMetadataName,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/services/utils/enrich-create-workspace-migration-action-with-ids.util';
|
||||
import { WorkspaceMigrationBuildOrchestratorService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-build-orchestrator.service';
|
||||
import { WorkspaceMigrationFlatEntityMapsService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-flat-entity-maps.service';
|
||||
import {
|
||||
FlatEntityMapsBundle,
|
||||
WorkspaceMigrationFlatEntityMapsService,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-flat-entity-maps.service';
|
||||
import {
|
||||
WorkspaceMigrationOrchestratorBuildArgs,
|
||||
WorkspaceMigrationOrchestratorFailedResult,
|
||||
@@ -43,6 +46,21 @@ type ValidateBuildAndRunWorkspaceMigrationFromRecordArgs = {
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
type ValidateBuildAndRunWorkspaceMigrationFromRecordInternalArgs =
|
||||
ValidateBuildAndRunWorkspaceMigrationFromRecordArgs & {
|
||||
// Skips the metadata side-effect engine (expandWithSideEffects) and applies the
|
||||
// matrix literally. Only the deprecated legacy path sets this to true.
|
||||
skipSideEffectExpandEngine: boolean;
|
||||
};
|
||||
|
||||
type ComputeAndRunWorkspaceMigrationFromResolvedOperationsArgs = {
|
||||
workspaceId: string;
|
||||
allFlatEntityOperationRecordByMetadataName: AllFlatEntityOperationRecordByMetadataName;
|
||||
isSystemBuild: boolean;
|
||||
applicationUniversalIdentifier: string;
|
||||
dryRun?: boolean;
|
||||
} & FlatEntityMapsBundle;
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationValidateBuildAndRunService {
|
||||
private readonly isDebugEnabled: boolean;
|
||||
@@ -185,13 +203,60 @@ export class WorkspaceMigrationValidateBuildAndRunService {
|
||||
});
|
||||
}
|
||||
|
||||
public async validateBuildAndRunWorkspaceMigrationFromRecord({
|
||||
public async validateBuildAndRunWorkspaceMigrationFromRecord(
|
||||
args: ValidateBuildAndRunWorkspaceMigrationFromRecordArgs,
|
||||
): Promise<
|
||||
| WorkspaceMigrationOrchestratorFailedResult
|
||||
| (WorkspaceMigrationOrchestratorSuccessfulResult & {
|
||||
hasSchemaMetadataChanged: boolean;
|
||||
})
|
||||
> {
|
||||
return await this.validateBuildAndRunWorkspaceMigrationFromRecordInternal({
|
||||
...args,
|
||||
skipSideEffectExpandEngine: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Legacy path for upgrade commands authored before the metadata
|
||||
* side-effect engine landed in v2.19. These commands declare their operation
|
||||
* matrix literally and must not flow through expandWithSideEffects, which
|
||||
* would inject engine-owned companions and collide on reserved identifiers.
|
||||
* See packages/twenty-server/docs/UPGRADE_COMMANDS.md.
|
||||
*/
|
||||
public async validateBuildAndRunLegacyWorkspaceMigration({
|
||||
allFlatEntityOperationByMetadataName,
|
||||
workspaceId,
|
||||
isSystemBuild = false,
|
||||
applicationUniversalIdentifier,
|
||||
dryRun,
|
||||
}: ValidateBuildAndRunWorkspaceMigrationFromMatriceArgs): Promise<
|
||||
| WorkspaceMigrationOrchestratorFailedResult
|
||||
| (WorkspaceMigrationOrchestratorSuccessfulResult & {
|
||||
hasSchemaMetadataChanged: boolean;
|
||||
})
|
||||
> {
|
||||
return await this.validateBuildAndRunWorkspaceMigrationFromRecordInternal({
|
||||
allFlatEntityOperationRecordByMetadataName:
|
||||
transpileFlatEntityOperationArrayToRecord(
|
||||
allFlatEntityOperationByMetadataName,
|
||||
),
|
||||
workspaceId,
|
||||
isSystemBuild,
|
||||
applicationUniversalIdentifier,
|
||||
dryRun,
|
||||
skipSideEffectExpandEngine: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async validateBuildAndRunWorkspaceMigrationFromRecordInternal({
|
||||
allFlatEntityOperationRecordByMetadataName,
|
||||
workspaceId,
|
||||
isSystemBuild = false,
|
||||
applicationUniversalIdentifier,
|
||||
dryRun,
|
||||
}: ValidateBuildAndRunWorkspaceMigrationFromRecordArgs): Promise<
|
||||
skipSideEffectExpandEngine,
|
||||
}: ValidateBuildAndRunWorkspaceMigrationFromRecordInternalArgs): Promise<
|
||||
| WorkspaceMigrationOrchestratorFailedResult
|
||||
| (WorkspaceMigrationOrchestratorSuccessfulResult & {
|
||||
hasSchemaMetadataChanged: boolean;
|
||||
@@ -213,19 +278,55 @@ export class WorkspaceMigrationValidateBuildAndRunService {
|
||||
},
|
||||
);
|
||||
|
||||
const sideEffectExpansionResult =
|
||||
this.metadataSideEffectEngineService.expandWithSideEffects({
|
||||
allFlatEntityOperationRecordByMetadataName,
|
||||
sideEffectRelatedFlatEntityMaps: allRelatedFlatEntityMaps,
|
||||
context: {
|
||||
buildOptions: { isSystemBuild, applicationUniversalIdentifier },
|
||||
},
|
||||
});
|
||||
let resolvedFlatEntityOperationRecordByMetadataName =
|
||||
allFlatEntityOperationRecordByMetadataName;
|
||||
|
||||
if (sideEffectExpansionResult.status === 'fail') {
|
||||
return sideEffectExpansionResult;
|
||||
if (!skipSideEffectExpandEngine) {
|
||||
const sideEffectExpansionResult =
|
||||
this.metadataSideEffectEngineService.expandWithSideEffects({
|
||||
allFlatEntityOperationRecordByMetadataName,
|
||||
sideEffectRelatedFlatEntityMaps: allRelatedFlatEntityMaps,
|
||||
context: {
|
||||
buildOptions: { isSystemBuild, applicationUniversalIdentifier },
|
||||
},
|
||||
});
|
||||
|
||||
if (sideEffectExpansionResult.status === 'fail') {
|
||||
return sideEffectExpansionResult;
|
||||
}
|
||||
|
||||
resolvedFlatEntityOperationRecordByMetadataName =
|
||||
sideEffectExpansionResult.allFlatEntityOperationRecordByMetadataName;
|
||||
}
|
||||
|
||||
return await this.computeAndRunWorkspaceMigrationFromResolvedOperations({
|
||||
allFlatEntityOperationRecordByMetadataName:
|
||||
resolvedFlatEntityOperationRecordByMetadataName,
|
||||
workspaceId,
|
||||
isSystemBuild,
|
||||
applicationUniversalIdentifier,
|
||||
dryRun,
|
||||
flatApplicationMaps,
|
||||
allRelatedFlatEntityMaps,
|
||||
allMetadataNameCacheToCompute,
|
||||
});
|
||||
}
|
||||
|
||||
private async computeAndRunWorkspaceMigrationFromResolvedOperations({
|
||||
allFlatEntityOperationRecordByMetadataName,
|
||||
workspaceId,
|
||||
isSystemBuild,
|
||||
applicationUniversalIdentifier,
|
||||
dryRun,
|
||||
flatApplicationMaps,
|
||||
allRelatedFlatEntityMaps,
|
||||
allMetadataNameCacheToCompute,
|
||||
}: ComputeAndRunWorkspaceMigrationFromResolvedOperationsArgs): Promise<
|
||||
| WorkspaceMigrationOrchestratorFailedResult
|
||||
| (WorkspaceMigrationOrchestratorSuccessfulResult & {
|
||||
hasSchemaMetadataChanged: boolean;
|
||||
})
|
||||
> {
|
||||
const {
|
||||
fromToAllFlatEntityMaps,
|
||||
inferDeletionFromMissingEntities,
|
||||
@@ -235,8 +336,7 @@ export class WorkspaceMigrationValidateBuildAndRunService {
|
||||
} =
|
||||
this.workspaceMigrationFlatEntityMapsService.computeFromToAllFlatEntityMapsAndBuildOptions(
|
||||
{
|
||||
allFlatEntityOperationRecordByMetadataName:
|
||||
sideEffectExpansionResult.allFlatEntityOperationRecordByMetadataName,
|
||||
allFlatEntityOperationRecordByMetadataName,
|
||||
applicationUniversalIdentifier,
|
||||
flatApplicationMaps,
|
||||
allRelatedFlatEntityMaps,
|
||||
|
||||
Reference in New Issue
Block a user