Compare commits

...

3 Commits

Author SHA1 Message Date
Paul Rastoin c4775450f5 Backfill owner standard field check colliding joinColumnName (#17449)
# Introduction
Previously the command would have been `old` renaming only any `owner`
field on `opportunity` object
Now we also search for any colliding `joinColumnName` with `ownerId`
which is also introduced by the new standard owner field

In a nutshell: previously gracefully handling existing custom `owner`
field collision but not for RELATION types that has an additional
collision surface: `joinColumnName`

Related
https://github.com/twentyhq/twenty/issues/17413#issuecomment-3799872079
2026-01-26 16:50:19 +01:00
Paul Rastoin cb6957fa51 Fix upgrade command order backfillStandardPageLayoutsCommand (#17430)
# Introduction
`backfillStandardPageLayoutsCommand` expects field and object metadata
to have been identified in prior to be run
https://github.com/twentyhq/twenty/issues/17413#issuecomment-3796933563
2026-01-25 21:53:29 +01:00
Paul Rastoin 9633740132 [Debug log level] Print validation build result failure (#17423)
# Introduction
As per title, in order to ease debug
Motivation
https://github.com/twentyhq/twenty/issues/17413#issuecomment-3796168946
2026-01-25 12:38:16 +01:00
3 changed files with 125 additions and 28 deletions
@@ -15,7 +15,11 @@ import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
import { MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
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 { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant';
@@ -115,32 +119,22 @@ export class BackfillOpportunityOwnerFieldCommand extends ActiveOrSuspendedWorks
return;
}
const customOwnerField = flatFieldMetadatas.find(
(field) =>
field.name === 'owner' &&
field.universalIdentifier !== ownerUniversalIdentifier,
);
const customOwnerField =
flatFieldMetadatas.find(
(field) =>
field.name === 'owner' &&
field.universalIdentifier !== ownerUniversalIdentifier,
// Some self hoster might have run the name renaming successfully but not the joinColumnName
// Rather than asking them to restore db also grabbing them here
) ?? flatFieldMetadatas.find((field) => field.name === 'ownerOld');
if (isDefined(customOwnerField)) {
if (options.dryRun) {
this.logger.log(
`[DRY RUN] Would rename custom owner field to 'ownerOld' in workspace ${workspaceId}`,
);
} else {
await this.fieldMetadataService.updateOneField({
updateFieldInput: {
id: customOwnerField.id,
name: 'ownerOld',
label: 'Owner (Old)',
},
workspaceId,
isSystemBuild: true,
});
this.logger.log(
`Renamed custom owner field to 'ownerOld' in workspace ${workspaceId}`,
);
}
await this.renameCustomOwnerFieldToOwnerOld({
customOwnerField,
flatFieldMetadataMaps,
workspaceId,
dryRun: options.dryRun,
});
}
if (options.dryRun) {
@@ -232,4 +226,96 @@ export class BackfillOpportunityOwnerFieldCommand extends ActiveOrSuspendedWorks
throw error;
}
}
private async renameCustomOwnerFieldToOwnerOld({
customOwnerField,
flatFieldMetadataMaps,
workspaceId,
dryRun,
}: {
customOwnerField: FlatFieldMetadata;
flatFieldMetadataMaps: MetadataFlatEntityMaps<'fieldMetadata'>;
workspaceId: string;
dryRun?: boolean;
}): Promise<void> {
if (dryRun) {
this.logger.log(
`[DRY RUN] Would rename custom owner field to 'ownerOld' in workspace and search for colliding foreignKey ${workspaceId}`,
);
return;
}
const isMorphOrRelationField =
isMorphOrRelationFlatFieldMetadata(customOwnerField);
const hasCollidingJoinColumnName =
isMorphOrRelationField &&
isDefined(customOwnerField.settings.joinColumnName) &&
customOwnerField.settings.joinColumnName === 'ownerId';
await this.fieldMetadataService.updateOneField({
updateFieldInput: {
id: customOwnerField.id,
name: 'ownerOld',
label: 'Owner (Old)',
...(hasCollidingJoinColumnName
? {
settings: {
...customOwnerField.settings,
joinColumnName: `ownerOldId`,
},
}
: {}),
},
workspaceId,
isSystemBuild: true,
});
this.logger.log(
`Renamed custom owner field to 'ownerOld' in workspace ${workspaceId}`,
);
if (!isMorphOrRelationField || hasCollidingJoinColumnName) {
return;
}
const relatedFlatFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: customOwnerField.relationTargetFieldMetadataId,
flatEntityMaps: flatFieldMetadataMaps,
});
if (
!isDefined(relatedFlatFieldMetadata) ||
!isMorphOrRelationFlatFieldMetadata(relatedFlatFieldMetadata)
) {
this.logger.error(
`Could not find custom owner relation target field ${customOwnerField.relationTargetFieldMetadataId}`,
);
return;
}
const targetHasCollidingJoinColumnName =
isDefined(relatedFlatFieldMetadata.settings.joinColumnName) &&
relatedFlatFieldMetadata.settings.joinColumnName === 'ownerId';
if (!targetHasCollidingJoinColumnName) {
return;
}
await this.fieldMetadataService.updateOneField({
updateFieldInput: {
id: relatedFlatFieldMetadata.id,
settings: {
...relatedFlatFieldMetadata.settings,
joinColumnName: `ownerOldId`,
},
},
workspaceId,
isSystemBuild: true,
});
this.logger.log(
`Renamed custom owner field target related field join column name to 'ownerOldId' in workspace ${workspaceId}`,
);
}
}
@@ -28,10 +28,10 @@ import { IdentifyAgentMetadataCommand } from 'src/database/commands/upgrade-vers
import { IdentifyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-field-metadata.command';
import { IdentifyIndexMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-index-metadata.command';
import { IdentifyObjectMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-object-metadata.command';
import { IdentifyRemainingEntitiesMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-remaining-entities-metadata.command';
import { IdentifyRoleMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-role-metadata.command';
import { IdentifyViewFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-field-metadata.command';
import { IdentifyViewFilterMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-filter-metadata.command';
import { IdentifyRemainingEntitiesMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-remaining-entities-metadata.command';
import { IdentifyViewGroupMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-group-metadata.command';
import { IdentifyViewMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-metadata.command';
import { MakeAgentUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-agent-universal-identifier-and-application-id-not-nullable-migration.command';
@@ -142,8 +142,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
const commands_1160: VersionCommands = [
this.updateTaskOnDeleteActionCommand,
this.backfillOpportunityOwnerFieldCommand,
this.backfillStandardPageLayoutsCommand,
this.identifyAgentMetadataCommand,
this.identifyFieldMetadataCommand,
this.identifyObjectMetadataCommand,
@@ -153,6 +151,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
this.identifyViewFilterMetadataCommand,
this.identifyViewGroupMetadataCommand,
this.identifyIndexMetadataCommand,
this.backfillOpportunityOwnerFieldCommand,
this.backfillStandardPageLayoutsCommand,
this
.makeAgentUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
this
@@ -6,6 +6,7 @@ import {
} from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
@@ -38,12 +39,18 @@ export class WorkspaceMigrationValidateBuildAndRunService {
private readonly logger = new Logger(
WorkspaceMigrationValidateBuildAndRunService.name,
);
private readonly isDebugEnabled: boolean;
constructor(
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMigrationBuildOrchestratorService: WorkspaceMigrationBuildOrchestratorService,
private readonly workspaceCacheService: WorkspaceCacheService,
) {}
twentyConfigService: TwentyConfigService,
) {
const logLevels = twentyConfigService.get('LOG_LEVELS');
this.isDebugEnabled = logLevels.includes('debug');
}
private async computeAllRelatedFlatEntityMaps({
allFlatEntityOperationByMetadataName,
@@ -186,6 +193,10 @@ export class WorkspaceMigrationValidateBuildAndRunService {
});
if (validateAndBuildResult.status === 'fail') {
if (this.isDebugEnabled) {
this.logger.debug(JSON.stringify(validateAndBuildResult, null, 2));
}
return validateAndBuildResult;
}