Resolve involved application ids from API metadata (#18221)

# Introduction
Resolving each create|updated|deleted entities related entities
application through their universal foreingKey ( silently failing if not
found leaving the validator handling that )
In order to compute all the required application in the dependency flat
entity maps

## Tested
Creating a field on a custom local twenty-apps installed on the
workspace + view field

## Out of scope
- updated snapshot
- update graphql generated front
This commit is contained in:
Paul Rastoin
2026-02-25 13:58:38 +01:00
committed by GitHub
parent f3857c41b8
commit 98e73791a4
11 changed files with 154 additions and 47 deletions
@@ -6,8 +6,10 @@ import {
} from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { FlatApplicationCacheMaps } from 'src/engine/core-modules/application/types/flat-application-cache-maps.type';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { MetadataEventEmitter } from 'src/engine/metadata-event-emitter/metadata-event-emitter';
import { ALL_MANY_TO_ONE_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import {
FlatEntityMapsException,
@@ -68,6 +70,122 @@ export class WorkspaceMigrationValidateBuildAndRunService {
this.isDebugEnabled = logLevels.includes('debug');
}
private computeAllInvolvedApplicationIds({
allFlatEntityOperationByMetadataName,
flatApplicationMaps,
applicationUniversalIdentifier,
allRelatedFlatEntityMaps,
}: Pick<
ValidateBuildAndRunWorkspaceMigrationFromMatriceArgs,
'allFlatEntityOperationByMetadataName' | 'applicationUniversalIdentifier'
> & {
flatApplicationMaps: FlatApplicationCacheMaps;
allRelatedFlatEntityMaps: Partial<AllFlatEntityMaps>;
}): string[] {
const applicationIds = new Set<string>();
const applicationId =
flatApplicationMaps.idByUniversalIdentifier[
applicationUniversalIdentifier
];
const twentyStandardApplicationId =
flatApplicationMaps.idByUniversalIdentifier[
TWENTY_STANDARD_APPLICATION.universalIdentifier
];
if (!isDefined(twentyStandardApplicationId) || !isDefined(applicationId)) {
throw new FlatEntityMapsException(
'Application to build and its dependent application not found',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
applicationIds.add(applicationId);
const isBuildingTwentyStandardApplication =
applicationUniversalIdentifier ===
TWENTY_STANDARD_APPLICATION.universalIdentifier;
if (!isBuildingTwentyStandardApplication) {
applicationIds.add(twentyStandardApplicationId);
}
for (const metadataName of Object.keys(
allFlatEntityOperationByMetadataName,
) as AllMetadataName[]) {
const flatEntityOperations =
allFlatEntityOperationByMetadataName[metadataName];
if (!isDefined(flatEntityOperations)) {
continue;
}
const { flatEntityToCreate, flatEntityToUpdate, flatEntityToDelete } =
flatEntityOperations;
const relations = ALL_MANY_TO_ONE_METADATA_RELATIONS[metadataName];
for (const flatEntity of [
...flatEntityToCreate,
...flatEntityToUpdate,
...flatEntityToDelete,
]) {
const entityApplicationId =
flatApplicationMaps.idByUniversalIdentifier[
flatEntity.applicationUniversalIdentifier
];
if (isDefined(entityApplicationId)) {
applicationIds.add(entityApplicationId);
}
for (const relation of Object.values(relations) as ({
foreignKey: string;
metadataName: AllMetadataName;
isNullable: boolean;
universalForeignKey: string;
} | null)[]) {
if (!isDefined(relation)) {
continue;
}
const { universalForeignKey, metadataName: targetMetadataName } =
relation;
const referencedUniversalIdentifier =
flatEntity[universalForeignKey as keyof typeof flatEntity];
if (!isDefined(referencedUniversalIdentifier)) {
continue;
}
const targetFlatEntityMaps =
allRelatedFlatEntityMaps[
getMetadataFlatEntityMapsKey(
targetMetadataName as AllMetadataName,
)
];
if (!isDefined(targetFlatEntityMaps)) {
continue;
}
const referencedEntity =
targetFlatEntityMaps.byUniversalIdentifier[
referencedUniversalIdentifier
];
if (isDefined(referencedEntity)) {
applicationIds.add(referencedEntity.applicationId);
}
}
}
}
return [...applicationIds];
}
private async computeAllRelatedFlatEntityMaps({
allFlatEntityOperationByMetadataName,
workspaceId,
@@ -105,26 +223,12 @@ export class WorkspaceMigrationValidateBuildAndRunService {
{},
);
const twentyStandardApplicationId =
flatApplicationMaps.idByUniversalIdentifier[
TWENTY_STANDARD_APPLICATION.universalIdentifier
];
const applicationId =
flatApplicationMaps.idByUniversalIdentifier[
applicationUniversalIdentifier
];
if (!isDefined(twentyStandardApplicationId) || !isDefined(applicationId)) {
throw new FlatEntityMapsException(
'Application to build and its dependent application not found',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const isBuildingTwentyStandardApplication =
applicationUniversalIdentifier ===
TWENTY_STANDARD_APPLICATION.universalIdentifier;
const applicationIds = this.computeAllInvolvedApplicationIds({
allFlatEntityOperationByMetadataName,
flatApplicationMaps,
applicationUniversalIdentifier,
allRelatedFlatEntityMaps,
});
const dependencyAllFlatEntityMaps = allMetadataNameCacheToCompute.reduce(
(allFlatEntityMaps, metadataName) => {
@@ -137,9 +241,7 @@ export class WorkspaceMigrationValidateBuildAndRunService {
getSubFlatEntityMapsByApplicationIdsOrThrow<
MetadataFlatEntity<typeof metadataName>
>({
applicationIds: isBuildingTwentyStandardApplication
? [applicationId]
: [applicationId, twentyStandardApplicationId],
applicationIds,
flatEntityMaps:
allRelatedFlatEntityMaps[metadataFlatEntityMapsKey],
}),