ViewGroup and ViewFilters side effect in v2 (#15096)

# Introduction

### Summary
Implements side effect handling for `ViewGroup` and `ViewFilters` when
field metadata is updated in the v2 architecture. This ensures that
view-related records are properly maintained when enum field options are
modified, deleted, or created.

### Side effects
- **Side Effect System**: Added side effect handling for field metadata
updates that manages related view groups and view filters
- **Enum Field Updates**: When enum field options are modified, the
system now:
- **View Groups**: Creates new groups for added options, updates
existing groups for modified options, and deletes groups for removed
options
- **View Filters**: Updates filter values to reflect option changes and
removes filters that reference deleted options

### Enum runner fix
Update now works for both atomic enum and array enum ( multi select for
instance )

### Compute flat entity maps from to
Standardized this method usage across v2 services
Next step is to require dependencies dynamically

## Conclusion
closes https://github.com/twentyhq/core-team-issues/issues/1649
This commit is contained in:
Paul Rastoin
2025-10-17 17:09:26 +02:00
committed by GitHub
parent 4f1623ff76
commit 3462a2e288
58 changed files with 3108 additions and 1075 deletions
@@ -3,11 +3,8 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import type { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
import { ServerlessFunctionIdInput } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-id.input';
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
@@ -54,20 +51,17 @@ export class ServerlessFunctionV2Service {
workspaceId,
});
const toFlatServerlessFunctionMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatServerlessFunctionToCreate,
flatEntityMaps: existingFlatServerlessFunctionMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatServerlessFunctionMaps: {
from: existingFlatServerlessFunctionMaps,
to: toFlatServerlessFunctionMaps,
},
flatServerlessFunctionMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatServerlessFunctionMaps,
flatEntityToCreate: [flatServerlessFunctionToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
buildOptions: {
isSystemBuild: false,
@@ -119,25 +113,17 @@ export class ServerlessFunctionV2Service {
updateServerlessFunctionInput: serverlessFunctionInput,
});
const fromFlatServerlessFunctionMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [optimisticallyUpdatedFlatServerlessFunction.id],
flatEntityMaps: existingFlatServerlessFunctionMaps,
});
const toFlatServerlessFunctionMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatServerlessFunction,
flatEntityMaps: fromFlatServerlessFunctionMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatServerlessFunctionMaps: {
from: existingFlatServerlessFunctionMaps,
to: toFlatServerlessFunctionMaps,
},
flatServerlessFunctionMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatServerlessFunctionMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatServerlessFunction],
}),
},
buildOptions: {
isSystemBuild: false,
@@ -200,20 +186,18 @@ export class ServerlessFunctionV2Service {
deletedAt: new Date(),
};
const toFlatServerlessFunctionMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatServerlessFunctionWithDeletedAt,
flatEntityMaps: existingFlatServerlessFunctionMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatServerlessFunctionMaps: {
from: existingFlatServerlessFunctionMaps,
to: toFlatServerlessFunctionMaps,
},
flatServerlessFunctionMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatServerlessFunctionMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [
optimisticallyUpdatedFlatServerlessFunctionWithDeletedAt,
],
}),
},
buildOptions: {
isSystemBuild,
@@ -274,24 +258,16 @@ export class ServerlessFunctionV2Service {
);
}
const fromFlatServerlessFunctionMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [existingFlatServerlessFunction.id],
flatEntityMaps: existingFlatServerlessFunctionMaps,
});
const toFlatServerlessFunctionMaps =
deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatServerlessFunctionMaps,
entityToDeleteId: existingFlatServerlessFunction.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatServerlessFunctionMaps: {
from: fromFlatServerlessFunctionMaps,
to: toFlatServerlessFunctionMaps,
},
flatServerlessFunctionMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatServerlessFunctionMaps,
flatEntityToCreate: [],
flatEntityToDelete: [existingFlatServerlessFunction],
flatEntityToUpdate: [],
}),
},
buildOptions: {
isSystemBuild,