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
@@ -13,11 +13,8 @@ import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/
import { fromCreateCronTriggerInputToFlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/utils/from-create-cron-trigger-input-to-flat-cron-trigger.util';
import { fromUpdateCronTriggerInputToFlatCronTriggerToUpdateOrThrow } from 'src/engine/metadata-modules/cron-trigger/utils/from-update-cron-trigger-input-to-flat-cron-trigger-to-update-or-throw.util';
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 { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
@@ -49,20 +46,17 @@ export class CronTriggerV2Service {
},
);
const toFlatCronTriggerMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatCronTriggerToCreate,
flatEntityMaps: existingFlatCronTriggerMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatCronTriggerMaps: {
from: existingFlatCronTriggerMaps,
to: toFlatCronTriggerMaps,
},
flatCronTriggerMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatCronTriggerMaps,
flatEntityToCreate: [flatCronTriggerToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatServerlessFunctionMaps:
@@ -115,24 +109,17 @@ export class CronTriggerV2Service {
updateCronTriggerInput: cronTriggerInput,
});
const fromFlatCronTriggerMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [optimisticallyUpdatedFlatCronTrigger.id],
flatEntityMaps: existingFlatCronTriggerMaps,
});
const toFlatCronTriggerMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatCronTrigger,
flatEntityMaps: fromFlatCronTriggerMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatCronTriggerMaps: {
from: existingFlatCronTriggerMaps,
to: toFlatCronTriggerMaps,
},
flatCronTriggerMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatCronTriggerMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatCronTrigger],
}),
},
dependencyAllFlatEntityMaps: {
flatServerlessFunctionMaps:
@@ -193,23 +180,16 @@ export class CronTriggerV2Service {
);
}
const fromFlatCronTriggerMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [existingFlatCronTrigger.id],
flatEntityMaps: existingFlatCronTriggerMaps,
});
const toFlatCronTriggerMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatCronTriggerMaps,
entityToDeleteId: existingFlatCronTrigger.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatCronTriggerMaps: {
from: fromFlatCronTriggerMaps,
to: toFlatCronTriggerMaps,
},
flatCronTriggerMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatCronTriggerMaps,
flatEntityToCreate: [],
flatEntityToDelete: [existingFlatCronTrigger],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatServerlessFunctionMaps: existingFlatServerlessFunctionMaps,
@@ -13,11 +13,8 @@ import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-e
import { fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/utils/from-create-database-event-trigger-input-to-flat-database-event-trigger.util';
import { fromUpdateDatabaseEventTriggerInputToFlatDatabaseEventTriggerToUpdateOrThrow } from 'src/engine/metadata-modules/database-event-trigger/utils/from-update-database-event-trigger-input-to-flat-database-event-trigger-to-update-or-throw.util';
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 { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
@@ -52,22 +49,17 @@ export class DatabaseEventTriggerV2Service {
workspaceId,
});
const toFlatDatabaseEventTriggerMaps = addFlatEntityToFlatEntityMapsOrThrow(
{
flatEntity: flatDatabaseEventTriggerToCreate,
flatEntityMaps: existingFlatDatabaseEventTriggerMaps,
},
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatDatabaseEventTriggerMaps: {
from: existingFlatDatabaseEventTriggerMaps,
to: toFlatDatabaseEventTriggerMaps,
},
flatDatabaseEventTriggerMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatDatabaseEventTriggerMaps,
flatEntityToCreate: [flatDatabaseEventTriggerToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatServerlessFunctionMaps:
@@ -129,25 +121,19 @@ export class DatabaseEventTriggerV2Service {
},
);
const fromFlatDatabaseEventTriggerMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [optimisticallyUpdatedFlatDatabaseEventTrigger.id],
flatEntityMaps: existingFlatDatabaseEventTriggerMaps,
});
const toFlatDatabaseEventTriggerMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatDatabaseEventTrigger,
flatEntityMaps: fromFlatDatabaseEventTriggerMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatDatabaseEventTriggerMaps: {
from: existingFlatDatabaseEventTriggerMaps,
to: toFlatDatabaseEventTriggerMaps,
},
flatDatabaseEventTriggerMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatDatabaseEventTriggerMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [
optimisticallyUpdatedFlatDatabaseEventTrigger,
],
}),
},
dependencyAllFlatEntityMaps: {
flatServerlessFunctionMaps:
@@ -216,24 +202,16 @@ export class DatabaseEventTriggerV2Service {
);
}
const fromFlatDatabaseEventTriggerMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [existingFlatDatabaseEventTrigger.id],
flatEntityMaps: existingFlatDatabaseEventTriggerMaps,
});
const toFlatDatabaseEventTriggerMaps =
deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatDatabaseEventTriggerMaps,
entityToDeleteId: existingFlatDatabaseEventTrigger.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatDatabaseEventTriggerMaps: {
from: fromFlatDatabaseEventTriggerMaps,
to: toFlatDatabaseEventTriggerMaps,
},
flatDatabaseEventTriggerMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatDatabaseEventTriggerMaps,
flatEntityToCreate: [],
flatEntityToDelete: [existingFlatDatabaseEventTrigger],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatServerlessFunctionMaps: existingFlatServerlessFunctionMaps,
@@ -26,6 +26,8 @@ import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-meta
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
@Entity('fieldMetadata')
@Check(
@@ -190,4 +192,10 @@ export class FieldMetadataEntity<
@OneToMany(() => ViewFieldEntity, (viewField) => viewField.fieldMetadata)
viewFields: Relation<ViewFieldEntity[]>;
@OneToMany(() => ViewFilterEntity, (viewFilter) => viewFilter.fieldMetadata)
viewFilters: Relation<ViewFilterEntity[]>;
@OneToMany(() => ViewGroupEntity, (viewGroup) => viewGroup.fieldMetadata)
viewGroups: Relation<ViewGroupEntity[]>;
}
@@ -13,12 +13,8 @@ import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
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 { 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 { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { fromCreateFieldInputToFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util';
import { fromDeleteFieldInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util';
import { fromFlatFieldMetadataToFieldMetadataDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util';
@@ -91,45 +87,6 @@ export class FieldMetadataServiceV2 {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
});
const fromFlatFieldMetadataMaps = getSubFlatEntityMapsOrThrow({
flatEntityMaps: existingFlatFieldMetadataMaps,
flatEntityIds: [
...new Set(flatFieldMetadatasToDelete.map((flatField) => flatField.id)),
],
});
const toFlatFieldMetadataMaps = flatFieldMetadatasToDelete.reduce(
(flatFieldMetadataMaps, flatFieldMetadataToDelete) =>
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatFieldMetadataToDelete.id,
flatEntityMaps: flatFieldMetadataMaps,
}),
fromFlatFieldMetadataMaps,
);
const fromFlatIndexMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [...flatIndexesToUpdate, ...flatIndexesToDelete].map(
({ id }) => id,
),
flatEntityMaps: existingFlatIndexMaps,
});
const toFlatIndexMapsWithUpdatedFlatIndex = flatIndexesToUpdate.reduce(
(flatIndexMaps, flatIndex) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatIndex,
flatEntityMaps: flatIndexMaps,
}),
fromFlatIndexMaps,
);
const toFlatIndexMaps = flatIndexesToDelete.reduce(
(flatIndexMaps, flatIndex) =>
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatIndex.id,
flatEntityMaps: flatIndexMaps,
}),
toFlatIndexMapsWithUpdatedFlatIndex,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -144,14 +101,18 @@ export class FieldMetadataServiceV2 {
},
},
fromToAllFlatEntityMaps: {
flatFieldMetadataMaps: {
from: fromFlatFieldMetadataMaps,
to: toFlatFieldMetadataMaps,
},
flatIndexMaps: {
from: fromFlatIndexMaps,
to: toFlatIndexMaps,
},
flatFieldMetadataMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatFieldMetadataMaps,
flatEntityToCreate: [],
flatEntityToDelete: flatFieldMetadatasToDelete,
flatEntityToUpdate: [],
}),
flatIndexMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatIndexMaps,
flatEntityToCreate: [],
flatEntityToDelete: flatIndexesToDelete,
flatEntityToUpdate: flatIndexesToUpdate,
}),
},
workspaceId,
},
@@ -180,6 +141,9 @@ export class FieldMetadataServiceV2 {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatViewFilterMaps: existingFlatViewFilterMaps,
flatViewGroupMaps: existingFlatViewGroupMaps,
flatViewMaps: existingFlatViewMaps,
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
@@ -187,6 +151,9 @@ export class FieldMetadataServiceV2 {
'flatObjectMetadataMaps',
'flatIndexMaps',
'flatFieldMetadataMaps',
'flatViewFilterMaps',
'flatViewGroupMaps',
'flatViewMaps',
],
},
);
@@ -196,6 +163,8 @@ export class FieldMetadataServiceV2 {
flatIndexMaps: existingFlatIndexMaps,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
updateFieldInput,
flatViewFilterMaps: existingFlatViewFilterMaps,
flatViewGroupMaps: existingFlatViewGroupMaps,
});
if (inputTranspilationResult.status === 'fail') {
@@ -203,81 +172,56 @@ export class FieldMetadataServiceV2 {
}
const {
flatFieldMetadatasToUpdate: optimisticallyUpdatedFlatFieldMetadatas,
flatFieldMetadatasToUpdate,
flatIndexMetadatasToUpdate,
flatIndexMetadatasToDelete,
flatIndexMetadatasToCreate,
flatViewGroupsToCreate,
flatViewGroupsToDelete,
flatViewGroupsToUpdate,
flatViewFiltersToDelete,
flatViewFiltersToUpdate,
} = inputTranspilationResult.result;
const toFlatFieldMetadataMaps =
optimisticallyUpdatedFlatFieldMetadatas.reduce(
(flatFieldMaps, flatFieldMetadata) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMaps,
flatEntity: flatFieldMetadata,
}),
getSubFlatEntityMapsOrThrow({
flatEntityIds: optimisticallyUpdatedFlatFieldMetadatas.map(
({ id }) => id,
),
flatEntityMaps: existingFlatFieldMetadataMaps,
}),
);
const fromFlatIndexMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [
...flatIndexMetadatasToUpdate,
...flatIndexMetadatasToDelete,
].map(({ id }) => id),
flatEntityMaps: existingFlatIndexMaps,
});
const toFlatIndexMapsWithCreatedFlatIndex =
flatIndexMetadatasToCreate.reduce(
(flatIndexMaps, flatIndex) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatIndex,
flatEntityMaps: flatIndexMaps,
}),
fromFlatIndexMaps,
);
const toFlatIndexMapsWithUpdatedFlatIndex =
flatIndexMetadatasToUpdate.reduce(
(flatIndexMaps, flatIndex) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatIndex,
flatEntityMaps: flatIndexMaps,
}),
toFlatIndexMapsWithCreatedFlatIndex,
);
const toFlatIndexMaps = flatIndexMetadatasToDelete.reduce(
(flatIndexMaps, flatIndex) =>
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatIndex.id,
flatEntityMaps: flatIndexMaps,
}),
toFlatIndexMapsWithUpdatedFlatIndex,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
dependencyAllFlatEntityMaps: {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatViewMaps: existingFlatViewMaps,
},
fromToAllFlatEntityMaps: {
flatFieldMetadataMaps: {
from: existingFlatFieldMetadataMaps,
to: toFlatFieldMetadataMaps,
},
flatIndexMaps: {
from: fromFlatIndexMaps,
to: toFlatIndexMaps,
},
flatFieldMetadataMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatFieldMetadataMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: flatFieldMetadatasToUpdate,
}),
flatIndexMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatIndexMaps,
flatEntityToCreate: flatIndexMetadatasToCreate,
flatEntityToDelete: flatIndexMetadatasToDelete,
flatEntityToUpdate: flatIndexMetadatasToUpdate,
}),
flatViewFilterMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFilterMaps,
flatEntityToCreate: [],
flatEntityToDelete: flatViewFiltersToDelete,
flatEntityToUpdate: flatViewFiltersToUpdate,
}),
flatViewGroupMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewGroupMaps,
flatEntityToCreate: flatViewGroupsToCreate,
flatEntityToDelete: flatViewGroupsToDelete,
flatEntityToUpdate: flatViewGroupsToUpdate,
}),
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: {
index: true,
viewGroup: true,
viewFilter: true,
},
},
workspaceId,
@@ -293,7 +237,7 @@ export class FieldMetadataServiceV2 {
return this.fieldMetadataRepository.findOneOrFail({
where: {
id: optimisticallyUpdatedFlatFieldMetadatas[0].id,
id: flatFieldMetadatasToUpdate[0].id,
workspaceId,
},
});
@@ -358,24 +302,6 @@ export class FieldMetadataServiceV2 {
{ flatFieldMetadatas: [], indexMetadatas: [] },
);
const toFlatFieldMetadataMaps = flatFieldMetadatasToCreate.reduce(
(flatFieldMaps, flatFieldMetadataToCreate) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatFieldMetadataToCreate,
flatEntityMaps: flatFieldMaps,
}),
EMPTY_FLAT_ENTITY_MAPS,
);
const toFlatIndexMaps = flatIndexMetadatasToCreate.reduce(
(flatIndexMaps, flatIndex) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatIndex,
flatEntityMaps: flatIndexMaps,
}),
EMPTY_FLAT_ENTITY_MAPS,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -383,14 +309,18 @@ export class FieldMetadataServiceV2 {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
},
fromToAllFlatEntityMaps: {
flatFieldMetadataMaps: {
from: existingFlatFieldMetadataMaps,
to: toFlatFieldMetadataMaps,
},
flatIndexMaps: {
from: existingFlatIndexMaps,
to: toFlatIndexMaps,
},
flatFieldMetadataMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatFieldMetadataMaps,
flatEntityToCreate: flatFieldMetadatasToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
flatIndexMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatIndexMaps,
flatEntityToCreate: flatIndexMetadatasToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
buildOptions: {
isSystemBuild: false,
@@ -1,11 +1,9 @@
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type FlatEntityPropertiesToCompare } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-to-compare.type';
import { type FlatEntityPropertyUpdate } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-property-update.type';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
export type FlatEntityPropertiesUpdates<
T extends AllMetadataName,
K extends FlatEntityPropertiesToCompare<T> = FlatEntityPropertiesToCompare<T>,
> = Array<
PropertyUpdate<MetadataFlatEntity<T>, Extract<K, keyof MetadataFlatEntity<T>>>
>;
> = Array<FlatEntityPropertyUpdate<T, Extract<K, keyof MetadataFlatEntity<T>>>>;
@@ -0,0 +1,12 @@
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type FlatEntityPropertiesToCompare } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-to-compare.type';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
export type FlatEntityPropertyUpdate<
T extends AllMetadataName,
K extends FlatEntityPropertiesToCompare<T> = FlatEntityPropertiesToCompare<T>,
> = PropertyUpdate<
MetadataFlatEntity<T>,
Extract<K, keyof MetadataFlatEntity<T>>
>;
@@ -0,0 +1,64 @@
import { type AllFlatEntities } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entities.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
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 { 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';
type ComputeFlatEntityMapsFromToArgs<T extends AllFlatEntities> = {
flatEntityMaps: FlatEntityMaps<T>;
flatEntityToUpdate: T[];
flatEntityToCreate: T[];
flatEntityToDelete: T[];
};
export const computeFlatEntityMapsFromTo = <T extends AllFlatEntities>({
flatEntityMaps,
flatEntityToCreate,
flatEntityToDelete,
flatEntityToUpdate,
}: ComputeFlatEntityMapsFromToArgs<T>): {
from: FlatEntityMaps<T>;
to: FlatEntityMaps<T>;
} => {
const fromFlatEntityMaps =
flatEntityToDelete.length > 0
? getSubFlatEntityMapsOrThrow({
flatEntityIds: [...flatEntityToDelete, ...flatEntityToUpdate].map(
({ id }) => id,
),
flatEntityMaps,
})
: flatEntityMaps;
const toFlatEntityMapsWithDeleted = flatEntityToDelete.reduce(
(flatEntityMaps, flatEntity) =>
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatEntity.id,
flatEntityMaps,
}),
fromFlatEntityMaps,
);
const toFlatEntityMapsWithUpdated = flatEntityToUpdate.reduce(
(flatEntityMaps, flatEntity) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity,
flatEntityMaps,
}),
toFlatEntityMapsWithDeleted,
);
const toFlatEntityMaps = flatEntityToCreate.reduce(
(flatEntityMaps, flatEntity) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity,
flatEntityMaps,
}),
toFlatEntityMapsWithUpdated,
);
return {
from: fromFlatEntityMaps,
to: toFlatEntityMaps,
};
};
@@ -19,6 +19,8 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date;
return {
viewFilterIds: [],
viewGroupIds: [],
viewFieldIds: [],
createdAt,
updatedAt: createdAt,
@@ -32,6 +32,8 @@ export const getRelationTargetFlatFieldMetadataMock = ({
const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date;
return {
viewFilterIds: [],
viewGroupIds: [],
viewFieldIds: [],
createdAt,
updatedAt: createdAt,
@@ -132,6 +132,8 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"viewFilterIds": [],
"viewGroupIds": [],
"workspaceId": Any<String>,
},
{
@@ -165,6 +167,8 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"viewFilterIds": [],
"viewGroupIds": [],
"workspaceId": Any<String>,
},
{
@@ -196,6 +200,8 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"viewFilterIds": [],
"viewGroupIds": [],
"workspaceId": Any<String>,
},
{
@@ -229,6 +235,8 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"viewFieldIds": [],
"viewFilterIds": [],
"viewGroupIds": [],
"workspaceId": Any<String>,
},
],
@@ -42,9 +42,15 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceFlatMapC
viewFields: {
id: true,
},
viewFilters: {
id: true,
},
viewGroups: {
id: true,
},
},
withDeleted: true,
relations: ['viewFields'],
relations: ['viewFields', 'viewFilters', 'viewGroups'],
});
return fieldMetadatas.reduce(
@@ -2,20 +2,24 @@ import { type FieldMetadataType } from 'twenty-shared/types';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export const fieldMetadataRelationProperties = [
export const FIELD_METADATA_RELATION_PROPERTIES = [
'relationTargetFieldMetadata',
'relationTargetObjectMetadata',
'fieldPermissions',
'indexFieldMetadatas',
'object',
'viewFields',
'viewFilters',
'viewGroups',
] as const satisfies (keyof FieldMetadataEntity)[];
export type FieldMetadataEntityRelationProperties =
(typeof fieldMetadataRelationProperties)[number];
(typeof FIELD_METADATA_RELATION_PROPERTIES)[number];
export type FlatFieldMetadata<T extends FieldMetadataType = FieldMetadataType> =
Omit<FieldMetadataEntity<T>, FieldMetadataEntityRelationProperties> & {
universalIdentifier: string;
viewFieldIds: string[];
viewFilterIds: string[];
viewGroupIds: string[];
};
@@ -0,0 +1,349 @@
import {
type EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { type FieldMetadataDefaultOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
import {
type CompareToFlatFieldMetadataEnumOptionsArgs,
compareTwoFlatFieldMetadataEnumOptions,
} from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata-enum-options.util';
type GetOptionsDifferencesTestContext = EachTestingContext<
CompareToFlatFieldMetadataEnumOptionsArgs & {
expected: {
created: FieldMetadataDefaultOption[];
updated: {
from: FieldMetadataDefaultOption;
to: FieldMetadataDefaultOption;
}[];
deleted: FieldMetadataDefaultOption[];
};
}
>;
describe('compareTwoFlatFieldMetadataEnumOptions', () => {
const testCases: GetOptionsDifferencesTestContext[] = [
{
title: 'should identify created options',
context: {
compareLabel: false,
fromOptions: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
],
toOptions: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
],
expected: {
created: [
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
],
updated: [],
deleted: [],
},
},
},
{
title: 'should identify updated options',
context: {
compareLabel: false,
fromOptions: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
],
toOptions: [
{
id: '1',
label: 'Option 1',
value: 'updated-value1',
position: 0,
},
],
expected: {
created: [],
updated: [
{
from: {
id: '1',
label: 'Option 1',
value: 'value1',
position: 0,
},
to: {
id: '1',
label: 'Option 1',
value: 'updated-value1',
position: 0,
},
},
],
deleted: [],
},
},
},
{
title: 'should identify deleted options',
context: {
compareLabel: false,
fromOptions: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
],
toOptions: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
],
expected: {
created: [],
updated: [],
deleted: [
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
],
},
},
},
{
title: 'should identify all types of changes',
context: {
compareLabel: false,
fromOptions: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
{ id: '3', label: 'Option 3', value: 'value3', position: 2 },
],
toOptions: [
{
id: '1',
label: 'Option 1',
value: 'updated-value1',
position: 0,
},
{ id: '3', label: 'Option 3', value: 'value3', position: 1 },
{ id: '4', label: 'Option 4', value: 'value4', position: 2 },
],
expected: {
created: [
{ id: '4', label: 'Option 4', value: 'value4', position: 2 },
],
updated: [
{
from: {
id: '1',
label: 'Option 1',
value: 'value1',
position: 0,
},
to: {
id: '1',
label: 'Option 1',
value: 'updated-value1',
position: 0,
},
},
],
deleted: [
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
],
},
},
},
{
title: 'should handle empty arrays',
context: {
compareLabel: false,
fromOptions: [],
toOptions: [],
expected: {
created: [],
updated: [],
deleted: [],
},
},
},
{
title: 'should handle all new options',
context: {
compareLabel: false,
fromOptions: [],
toOptions: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
],
expected: {
created: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
],
updated: [],
deleted: [],
},
},
},
{
title: 'should handle all deleted options',
context: {
compareLabel: false,
fromOptions: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
],
toOptions: [],
expected: {
created: [],
updated: [],
deleted: [
{ id: '1', label: 'Option 1', value: 'value1', position: 0 },
{ id: '2', label: 'Option 2', value: 'value2', position: 1 },
],
},
},
},
{
title:
'should not consider changes to label as updates when value remains the same',
context: {
compareLabel: false,
fromOptions: [
{
id: 'f86eaffd-b773-4c9a-957b-86dca4a62731',
label: 'Option 0',
value: 'option0',
position: 1,
},
{
id: '28d80b3c-79bd-4a1b-a868-9616534de0fa',
label: 'Option 1',
value: 'option1',
position: 2,
},
{
id: '25a05cd8-256f-4652-9e4a-6d9ca0b96f4d',
label: 'Option 2',
value: 'option2',
position: 3,
},
],
toOptions: [
{
id: 'f86eaffd-b773-4c9a-957b-86dca4a62731',
label: 'Option 0_UPDATED', // Label changed but value remains the same
value: 'option0',
position: 1,
},
{
id: '28d80b3c-79bd-4a1b-a868-9616534de0fa',
label: 'Option 1', // No change
value: 'option1',
position: 2,
},
{
id: '25a05cd8-256f-4652-9e4a-6d9ca0b96f4d',
label: 'Option 2_UPDATED', // Label changed but value remains the same
value: 'option2',
position: 3,
},
],
expected: {
created: [],
updated: [], // No updates because only labels changed, not values
deleted: [],
},
},
},
{
title:
'should consider changes to label as updates when value remains the same if compareLabel is true',
context: {
fromOptions: [
{
id: 'f86eaffd-b773-4c9a-957b-86dca4a62731',
label: 'Option 0',
value: 'option0',
position: 1,
},
{
id: '28d80b3c-79bd-4a1b-a868-9616534de0fa',
label: 'Option 1',
value: 'option1',
position: 2,
},
{
id: '25a05cd8-256f-4652-9e4a-6d9ca0b96f4d',
label: 'Option 2',
value: 'option2',
position: 3,
},
],
toOptions: [
{
id: 'f86eaffd-b773-4c9a-957b-86dca4a62731',
label: 'Option 0_UPDATED', // Label changed but value remains the same
value: 'option0',
position: 1,
},
{
id: '28d80b3c-79bd-4a1b-a868-9616534de0fa',
label: 'Option 1', // No change
value: 'option1',
position: 2,
},
{
id: '25a05cd8-256f-4652-9e4a-6d9ca0b96f4d',
label: 'Option 2_UPDATED', // Label changed but value remains the same
value: 'option2',
position: 3,
},
],
expected: {
created: [],
updated: [
{
to: {
id: 'f86eaffd-b773-4c9a-957b-86dca4a62731',
label: 'Option 0_UPDATED',
position: 1,
value: 'option0',
},
from: {
id: 'f86eaffd-b773-4c9a-957b-86dca4a62731',
label: 'Option 0',
position: 1,
value: 'option0',
},
},
{
to: {
id: '25a05cd8-256f-4652-9e4a-6d9ca0b96f4d',
label: 'Option 2_UPDATED',
position: 3,
value: 'option2',
},
from: {
id: '25a05cd8-256f-4652-9e4a-6d9ca0b96f4d',
label: 'Option 2',
position: 3,
value: 'option2',
},
},
],
deleted: [],
},
compareLabel: true,
},
},
];
test.each(eachTestingContextFilter(testCases))(
'$title',
({ context: { fromOptions, toOptions, expected, compareLabel } }) => {
const result = compareTwoFlatFieldMetadataEnumOptions({
compareLabel: compareLabel ?? false,
fromOptions: fromOptions,
toOptions: toOptions,
});
expect(result.created).toEqual(expected.created);
expect(result.updated).toEqual(expected.updated);
expect(result.deleted).toEqual(expected.deleted);
},
);
});
@@ -0,0 +1,61 @@
import { type EnumFieldMetadataType, type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
type FieldMetadataEnumOptions =
FlatFieldMetadata<EnumFieldMetadataType>['options'];
type Differences<T> = {
created: T[];
updated: { from: T; to: T }[];
deleted: T[];
};
export type CompareToFlatFieldMetadataEnumOptionsArgs = FromTo<
FieldMetadataEnumOptions,
'options'
> & {
compareLabel: boolean;
};
export const compareTwoFlatFieldMetadataEnumOptions = ({
compareLabel = false,
fromOptions,
toOptions,
}: CompareToFlatFieldMetadataEnumOptionsArgs): Differences<
FieldMetadataEnumOptions[number]
> => {
const differences: Differences<FieldMetadataEnumOptions[number]> = {
created: [],
updated: [],
deleted: [],
};
const fromOptionsMap = new Map(fromOptions.map((opt) => [opt.id, opt]));
for (const newOption of toOptions) {
const oldOption = fromOptionsMap.get(newOption.id);
if (!isDefined(oldOption)) {
differences.created.push(newOption);
continue;
}
if (
oldOption.value !== newOption.value ||
(compareLabel && oldOption.label !== newOption.label)
) {
differences.updated.push({ from: oldOption, to: newOption });
continue;
}
}
const toOptionsMap = new Map(toOptions.map((opt) => [opt.id, opt]));
for (const oldOption of fromOptions) {
if (!toOptionsMap.has(oldOption.id)) {
differences.deleted.push(oldOption);
}
}
return differences;
};
@@ -4,7 +4,7 @@ import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
type FlatFieldMetadata,
fieldMetadataRelationProperties,
FIELD_METADATA_RELATION_PROPERTIES,
} from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export const fromFieldMetadataEntityToFlatFieldMetadata = <
@@ -15,14 +15,14 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = <
): FlatFieldMetadata => {
const fieldMetadataWithoutRelations = removePropertiesFromRecord(
fieldMetadataEntity,
fieldMetadataRelationProperties,
FIELD_METADATA_RELATION_PROPERTIES,
);
return {
...fieldMetadataWithoutRelations,
viewFieldIds: fieldMetadataEntity.viewFields.map(
(viewField) => viewField.id,
),
viewGroupIds: fieldMetadataEntity.viewGroups.map(({ id }) => id),
viewFieldIds: fieldMetadataEntity.viewFields.map(({ id }) => id),
viewFilterIds: fieldMetadataEntity.viewFilters.map(({ id }) => id),
universalIdentifier:
fieldMetadataWithoutRelations.standardId ??
fieldMetadataWithoutRelations.id,
@@ -16,110 +16,36 @@ import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { FLAT_FIELD_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-editable-properties.constant';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { type FlatFieldMetadataEditableProperties } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-editable-properties.constant';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { computeFlatFieldMetadataRelatedFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-metadata-related-flat-field-metadata.util';
import { handleIndexChangesDuringFieldUpdate } from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-index-changes-during-field-update.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import {
type FlatFieldMetadataUpdateSideEffects,
handleFlatFieldMetadataUpdateSideEffect,
} from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-flat-field-metadata-update-side-effect.util';
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
type UpdatedFlatFieldMetadataAndIndexToUpdate = {
flatFieldMetadata: FlatFieldMetadata;
};
type SanitizedUpdateFieldInput = ReturnType<
typeof extractAndSanitizeObjectStringFields<
UpdateFieldInput,
FlatFieldMetadataEditableProperties[]
>
>;
type ApplyUpdatesToFlatFieldMetadataArgs = {
updatedEditableFieldProperties: SanitizedUpdateFieldInput;
fromFlatFieldMetadata: FlatFieldMetadata;
} & Pick<
AllFlatEntityMaps,
'flatIndexMaps' | 'flatObjectMetadataMaps' | 'flatFieldMetadataMaps'
>;
const applyUpdatesToFlatFieldMetadata = ({
updatedEditableFieldProperties,
fromFlatFieldMetadata,
flatObjectMetadataMaps,
flatIndexMaps,
flatFieldMetadataMaps,
}: ApplyUpdatesToFlatFieldMetadataArgs): {
flatFieldMetadata: FlatFieldMetadata;
flatIndexMetadataToUpdate: FlatIndexMetadata[];
flatIndexMetadatasToDelete: FlatIndexMetadata[];
flatIndexMetadatasToCreate: FlatIndexMetadata[];
} => {
const { flatFieldMetadata: updatedFlatFieldMetadata } =
FLAT_FIELD_METADATA_EDITABLE_PROPERTIES.reduce<UpdatedFlatFieldMetadataAndIndexToUpdate>(
({ flatFieldMetadata }, property) => {
const updatedPropertyValue = updatedEditableFieldProperties[property];
const isPropertyUpdated =
updatedPropertyValue !== undefined &&
flatFieldMetadata[property] !== updatedPropertyValue;
if (!isPropertyUpdated) {
return { flatFieldMetadata };
}
const updatedFlatFieldMetadata = {
...flatFieldMetadata,
[property]: updatedPropertyValue,
};
if (property === 'options') {
updatedFlatFieldMetadata.options =
updatedEditableFieldProperties[property]?.map((option) => ({
id: v4(),
...option,
})) ?? [];
}
return {
flatFieldMetadata: updatedFlatFieldMetadata,
};
},
{
flatFieldMetadata: structuredClone(fromFlatFieldMetadata),
},
);
const indexChanges = handleIndexChangesDuringFieldUpdate({
originalFlatFieldMetadata: fromFlatFieldMetadata,
updatedFlatFieldMetadata,
flatIndexMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
});
return {
flatFieldMetadata: updatedFlatFieldMetadata,
...indexChanges,
};
};
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
type FromUpdateFieldInputToFlatFieldMetadataArgs = {
updateFieldInput: UpdateFieldInput;
} & Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps' | 'flatIndexMaps' | 'flatFieldMetadataMaps'
| 'flatObjectMetadataMaps'
| 'flatIndexMaps'
| 'flatFieldMetadataMaps'
| 'flatViewFilterMaps'
| 'flatViewGroupMaps'
>;
type FlatFieldMetadataAndIndexToUpdate = {
flatFieldMetadatasToUpdate: FlatFieldMetadata[];
flatIndexMetadatasToUpdate: FlatIndexMetadata[];
flatIndexMetadatasToDelete: FlatIndexMetadata[];
flatIndexMetadatasToCreate: FlatIndexMetadata[];
};
} & FlatFieldMetadataUpdateSideEffects;
export const fromUpdateFieldInputToFlatFieldMetadata = ({
flatIndexMaps,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatFieldMetadataMaps,
updateFieldInput: rawUpdateFieldInput,
flatViewFilterMaps,
flatViewGroupMaps,
}: FromUpdateFieldInputToFlatFieldMetadataArgs): FieldInputTranspilationResult<FlatFieldMetadataAndIndexToUpdate> => {
const updateFieldInputInformalProperties =
extractAndSanitizeObjectStringFields(rawUpdateFieldInput, [
@@ -188,10 +114,15 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
return {
status: 'success',
result: {
flatViewGroupsToCreate: [],
flatViewGroupsToDelete: [],
flatViewGroupsToUpdate: [],
flatFieldMetadatasToUpdate: [updatedStandardFlatFieldMetadata],
flatIndexMetadatasToUpdate: [],
flatIndexMetadatasToDelete: [],
flatIndexMetadatasToCreate: [],
flatViewFiltersToDelete: [],
flatViewFiltersToUpdate: [],
},
};
}
@@ -220,47 +151,95 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
...relatedFlatFieldMetadatasToUpdate,
];
const initialAccumulator: FlatFieldMetadataAndIndexToUpdate = {
flatFieldMetadatasToUpdate: [],
flatIndexMetadatasToUpdate: [],
flatViewFiltersToDelete: [],
flatViewFiltersToUpdate: [],
flatViewGroupsToCreate: [],
flatViewGroupsToDelete: [],
flatIndexMetadatasToCreate: [],
flatIndexMetadatasToDelete: [],
flatViewGroupsToUpdate: [],
};
updatedEditableFieldProperties.options = !isDefined(
updatedEditableFieldProperties.options,
)
? updatedEditableFieldProperties.options
: updatedEditableFieldProperties.options.map((option) => ({
id: v4(),
...option,
}));
const optimisticiallyUpdatedFlatFieldMetadatas =
flatFieldMetadatasToUpdate.reduce<FlatFieldMetadataAndIndexToUpdate>(
(acc, fromFlatFieldMetadata) => {
(accumulator, fromFlatFieldMetadata) => {
const toFlatFieldMetadata = mergeUpdateInExistingRecord({
existing: fromFlatFieldMetadata,
properties: FLAT_FIELD_METADATA_EDITABLE_PROPERTIES,
update: updatedEditableFieldProperties,
});
const {
flatFieldMetadata,
flatIndexMetadataToUpdate,
flatIndexMetadatasToDelete,
flatViewGroupsToCreate,
flatViewGroupsToDelete,
flatViewGroupsToUpdate,
flatIndexMetadatasToUpdate,
flatViewFiltersToDelete,
flatViewFiltersToUpdate,
flatIndexMetadatasToCreate,
} = applyUpdatesToFlatFieldMetadata({
flatIndexMetadatasToDelete,
} = handleFlatFieldMetadataUpdateSideEffect({
flatViewFilterMaps,
flatViewGroupMaps,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
fromFlatFieldMetadata,
flatFieldMetadataMaps,
flatIndexMaps,
updatedEditableFieldProperties,
toFlatFieldMetadata,
});
return {
flatFieldMetadatasToUpdate: [
...acc.flatFieldMetadatasToUpdate,
flatFieldMetadata,
...accumulator.flatFieldMetadatasToUpdate,
toFlatFieldMetadata,
],
flatIndexMetadatasToUpdate: [
...acc.flatIndexMetadatasToUpdate,
...flatIndexMetadataToUpdate,
...accumulator.flatIndexMetadatasToUpdate,
...flatIndexMetadatasToUpdate,
],
flatViewFiltersToDelete: [
...accumulator.flatViewFiltersToDelete,
...flatViewFiltersToDelete,
],
flatViewFiltersToUpdate: [
...accumulator.flatViewFiltersToUpdate,
...flatViewFiltersToUpdate,
],
flatViewGroupsToCreate: [
...accumulator.flatViewGroupsToCreate,
...flatViewGroupsToCreate,
],
flatViewGroupsToDelete: [
...accumulator.flatViewGroupsToDelete,
...flatViewGroupsToDelete,
],
flatViewGroupsToUpdate: [
...accumulator.flatViewGroupsToUpdate,
...flatViewGroupsToUpdate,
],
flatIndexMetadatasToDelete: [
...acc.flatIndexMetadatasToDelete,
...accumulator.flatIndexMetadatasToDelete,
...flatIndexMetadatasToDelete,
],
flatIndexMetadatasToCreate: [
...acc.flatIndexMetadatasToCreate,
...accumulator.flatIndexMetadatasToCreate,
...flatIndexMetadatasToCreate,
],
};
},
{
flatFieldMetadatasToUpdate: [],
flatIndexMetadatasToUpdate: [],
flatIndexMetadatasToDelete: [],
flatIndexMetadatasToCreate: [],
},
initialAccumulator,
);
return {
@@ -37,8 +37,8 @@ export const getDefaultFlatFieldMetadata = ({
),
isSystem: false,
isUnique: createFieldInput.isUnique ?? null,
label: createFieldInput.label ?? null,
name: createFieldInput.name ?? null,
label: createFieldInput.label,
name: createFieldInput.name,
objectMetadataId: createFieldInput.objectMetadataId,
relationTargetFieldMetadataId: null,
relationTargetObjectMetadataId: null,
@@ -54,5 +54,7 @@ export const getDefaultFlatFieldMetadata = ({
updatedAt: createdAt,
isUIReadOnly: createFieldInput.isUIReadOnly ?? false,
morphId: null,
viewFilterIds: [],
viewGroupIds: [],
} as const satisfies FlatFieldMetadata;
};
@@ -0,0 +1,103 @@
import { type EnumFieldMetadataType, type FromTo } from 'twenty-shared/types';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
import {
type FlatViewFiltersToDeleteAndUpdate,
recomputeViewFiltersOnFlatFieldMetadataOptionsUpdate,
} from 'src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-filters-on-flat-field-metadata-options-update.util';
import { recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate } from 'src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-enum-flat-field-metadata-is-nullable-update.util';
import {
type FlatViewGroupsToDeleteUpdateAndCreate,
recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate,
} from 'src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
type HandleEnumFlatFieldMetadataOptionsUpdateSideEffectsArgs = FromTo<
FlatFieldMetadata,
'flatFieldMetadata'
> &
Pick<AllFlatEntityMaps, 'flatViewFilterMaps' | 'flatViewGroupMaps'>;
type EnumFieldMetadataSideEffectResult = FlatViewGroupsToDeleteUpdateAndCreate &
FlatViewFiltersToDeleteAndUpdate;
const EMPTY_ENUM_FIELD_METADATA_SIDE_EFFECT_RESULT: EnumFieldMetadataSideEffectResult =
{
flatViewFiltersToDelete: [],
flatViewFiltersToUpdate: [],
flatViewGroupsToCreate: [],
flatViewGroupsToDelete: [],
flatViewGroupsToUpdate: [],
};
export const handleEnumFlatFieldMetadataUpdateSideEffects = ({
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatViewFilterMaps,
flatViewGroupMaps,
}: HandleEnumFlatFieldMetadataOptionsUpdateSideEffectsArgs): EnumFieldMetadataSideEffectResult => {
const sideEffectResult = structuredClone(
EMPTY_ENUM_FIELD_METADATA_SIDE_EFFECT_RESULT,
);
if (
!isEnumFlatFieldMetadata(toFlatFieldMetadata) ||
!isEnumFlatFieldMetadata(fromFlatFieldMetadata)
) {
return sideEffectResult;
}
if (
JSON.stringify(fromFlatFieldMetadata.options) !==
JSON.stringify(toFlatFieldMetadata.options)
) {
const optionsPropertyUpdate: PropertyUpdate<
FlatFieldMetadata<EnumFieldMetadataType>,
'options'
> = {
from: fromFlatFieldMetadata.options,
property: 'options',
to: toFlatFieldMetadata.options,
};
const { flatViewFiltersToDelete, flatViewFiltersToUpdate } =
recomputeViewFiltersOnFlatFieldMetadataOptionsUpdate({
flatViewFilterMaps,
fromFlatFieldMetadata,
update: optionsPropertyUpdate,
});
sideEffectResult.flatViewFiltersToDelete.push(...flatViewFiltersToDelete);
sideEffectResult.flatViewFiltersToUpdate.push(...flatViewFiltersToUpdate);
const {
flatViewGroupsToCreate,
flatViewGroupsToDelete,
flatViewGroupsToUpdate,
} = recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate({
flatViewGroupMaps,
fromFlatFieldMetadata,
update: optionsPropertyUpdate,
});
sideEffectResult.flatViewGroupsToCreate.push(...flatViewGroupsToCreate);
sideEffectResult.flatViewGroupsToDelete.push(...flatViewGroupsToDelete);
sideEffectResult.flatViewGroupsToUpdate.push(...flatViewGroupsToUpdate);
}
if (fromFlatFieldMetadata.isNullable !== toFlatFieldMetadata.isNullable) {
const { flatViewGroupsToCreate, flatViewGroupsToDelete } =
recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate({
flatViewGroupMaps,
fromFlatFieldMetadata,
toFlatFieldMetadata,
});
sideEffectResult.flatViewGroupsToCreate.push(...flatViewGroupsToCreate);
sideEffectResult.flatViewGroupsToDelete.push(...flatViewGroupsToDelete);
}
return sideEffectResult;
};
@@ -0,0 +1,75 @@
import { type FromTo } from 'twenty-shared/types';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { handleEnumFlatFieldMetadataUpdateSideEffects } from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-enum-flat-field-metadata-update-side-effects.util';
import {
type FieldMetadataUpdateIndexSideEffect,
handleIndexChangesDuringFieldUpdate,
} from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-index-changes-during-field-update.util';
import { type FlatViewFiltersToDeleteAndUpdate } from 'src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-filters-on-flat-field-metadata-options-update.util';
import { type FlatViewGroupsToDeleteUpdateAndCreate } from 'src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util';
export type FlatFieldMetadataUpdateSideEffects =
FlatViewFiltersToDeleteAndUpdate &
FlatViewGroupsToDeleteUpdateAndCreate &
FieldMetadataUpdateIndexSideEffect;
type HandleFlatFieldMetadataUpdateSideEffectArgs = FromTo<
FlatFieldMetadata,
'flatFieldMetadata'
> &
Pick<
AllFlatEntityMaps,
| 'flatIndexMaps'
| 'flatObjectMetadataMaps'
| 'flatFieldMetadataMaps'
| 'flatViewFilterMaps'
| 'flatViewGroupMaps'
>;
export const handleFlatFieldMetadataUpdateSideEffect = ({
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatObjectMetadataMaps,
flatIndexMaps,
flatFieldMetadataMaps,
flatViewFilterMaps,
flatViewGroupMaps,
}: HandleFlatFieldMetadataUpdateSideEffectArgs): FlatFieldMetadataUpdateSideEffects => {
const {
flatViewFiltersToDelete,
flatViewFiltersToUpdate,
flatViewGroupsToCreate,
flatViewGroupsToDelete,
flatViewGroupsToUpdate,
} = handleEnumFlatFieldMetadataUpdateSideEffects({
flatViewFilterMaps,
flatViewGroupMaps,
fromFlatFieldMetadata,
toFlatFieldMetadata,
});
const {
flatIndexMetadatasToUpdate,
flatIndexMetadatasToCreate,
flatIndexMetadatasToDelete,
} = handleIndexChangesDuringFieldUpdate({
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatIndexMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
});
return {
flatIndexMetadatasToUpdate,
flatViewFiltersToDelete,
flatViewFiltersToUpdate,
flatViewGroupsToCreate,
flatViewGroupsToDelete,
flatIndexMetadatasToDelete,
flatIndexMetadatasToCreate,
flatViewGroupsToUpdate,
};
};
@@ -1,3 +1,5 @@
import { type FromTo } from 'twenty-shared/types';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -8,61 +10,64 @@ import { recomputeIndexOnFlatFieldMetadataNameUpdate } from 'src/engine/metadata
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
type IndexChanges = {
flatIndexMetadataToUpdate: FlatIndexMetadata[];
export type FieldMetadataUpdateIndexSideEffect = {
flatIndexMetadatasToUpdate: FlatIndexMetadata[];
flatIndexMetadatasToDelete: FlatIndexMetadata[];
flatIndexMetadatasToCreate: FlatIndexMetadata[];
};
const NO_INDEX_CHANGES: IndexChanges = {
flatIndexMetadataToUpdate: [],
flatIndexMetadatasToDelete: [],
flatIndexMetadatasToCreate: [],
};
type FromToFlatFieldMetadataAndFlatEntityMaps = FromTo<
FlatFieldMetadata,
'flatFieldMetadata'
> &
Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps' | 'flatFieldMetadataMaps' | 'flatIndexMaps'
>;
const FIELD_METADATA_UPDATE_INDEX_SIDE_EFFECT: FieldMetadataUpdateIndexSideEffect =
{
flatIndexMetadatasToUpdate: [],
flatIndexMetadatasToDelete: [],
flatIndexMetadatasToCreate: [],
};
export const handleIndexChangesDuringFieldUpdate = ({
originalFlatFieldMetadata,
updatedFlatFieldMetadata,
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatIndexMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
}: {
originalFlatFieldMetadata: FlatFieldMetadata;
updatedFlatFieldMetadata: FlatFieldMetadata;
flatIndexMaps: FlatEntityMaps<FlatIndexMetadata>;
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
}): IndexChanges => {
}: FromToFlatFieldMetadataAndFlatEntityMaps): FieldMetadataUpdateIndexSideEffect => {
if (
!hasIndexRelevantChanges({
originalFlatFieldMetadata,
updatedFlatFieldMetadata,
fromFlatFieldMetadata,
toFlatFieldMetadata,
})
) {
return NO_INDEX_CHANGES;
return FIELD_METADATA_UPDATE_INDEX_SIDE_EFFECT;
}
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
flatEntityId: originalFlatFieldMetadata.objectMetadataId,
flatEntityId: fromFlatFieldMetadata.objectMetadataId,
});
const relatedIndexes = findRelatedIndexes({
flatFieldMetadata: originalFlatFieldMetadata,
flatFieldMetadata: fromFlatFieldMetadata,
flatObjectMetadata,
flatIndexMaps,
});
if (relatedIndexes.length === 0) {
return handleNoExistingIndexes({
updatedFlatFieldMetadata,
toFlatFieldMetadata,
flatObjectMetadata,
});
}
return handleExistingIndexes({
updatedFlatFieldMetadata,
originalFlatFieldMetadata,
toFlatFieldMetadata,
fromFlatFieldMetadata,
relatedIndexes,
flatObjectMetadata,
flatFieldMetadataMaps,
@@ -70,14 +75,11 @@ export const handleIndexChangesDuringFieldUpdate = ({
};
const hasIndexRelevantChanges = ({
originalFlatFieldMetadata,
updatedFlatFieldMetadata,
}: {
originalFlatFieldMetadata: FlatFieldMetadata;
updatedFlatFieldMetadata: FlatFieldMetadata;
}): boolean =>
originalFlatFieldMetadata.name !== updatedFlatFieldMetadata.name ||
originalFlatFieldMetadata.isUnique !== updatedFlatFieldMetadata.isUnique;
fromFlatFieldMetadata,
toFlatFieldMetadata,
}: FromTo<FlatFieldMetadata, 'flatFieldMetadata'>): boolean =>
fromFlatFieldMetadata.name !== toFlatFieldMetadata.name ||
fromFlatFieldMetadata.isUnique !== toFlatFieldMetadata.isUnique;
const findRelatedIndexes = ({
flatFieldMetadata,
@@ -101,45 +103,46 @@ const findRelatedIndexes = ({
};
const handleNoExistingIndexes = ({
updatedFlatFieldMetadata,
toFlatFieldMetadata,
flatObjectMetadata,
}: {
updatedFlatFieldMetadata: FlatFieldMetadata;
toFlatFieldMetadata: FlatFieldMetadata;
flatObjectMetadata: FlatObjectMetadata;
}): IndexChanges => {
if (!updatedFlatFieldMetadata.isUnique) {
return NO_INDEX_CHANGES;
}): FieldMetadataUpdateIndexSideEffect => {
if (!toFlatFieldMetadata.isUnique) {
return FIELD_METADATA_UPDATE_INDEX_SIDE_EFFECT;
}
const newIndex = generateIndexForFlatFieldMetadata({
flatFieldMetadata: updatedFlatFieldMetadata,
flatFieldMetadata: toFlatFieldMetadata,
flatObjectMetadata,
workspaceId: flatObjectMetadata.workspaceId,
});
return {
...NO_INDEX_CHANGES,
...FIELD_METADATA_UPDATE_INDEX_SIDE_EFFECT,
flatIndexMetadatasToCreate: [newIndex],
};
};
const handleExistingIndexes = ({
updatedFlatFieldMetadata,
originalFlatFieldMetadata,
fromFlatFieldMetadata,
toFlatFieldMetadata,
relatedIndexes,
flatObjectMetadata,
flatFieldMetadataMaps,
}: {
updatedFlatFieldMetadata: FlatFieldMetadata;
originalFlatFieldMetadata: FlatFieldMetadata;
relatedIndexes: FlatIndexMetadata[];
flatObjectMetadata: FlatObjectMetadata;
flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps'];
}): IndexChanges => {
if (updatedFlatFieldMetadata.isUnique === false) {
} & FromTo<
FlatFieldMetadata,
'flatFieldMetadata'
>): FieldMetadataUpdateIndexSideEffect => {
if (toFlatFieldMetadata.isUnique === false) {
const expectedUniqueIndex = generateIndexForFlatFieldMetadata({
flatFieldMetadata: {
...originalFlatFieldMetadata,
...fromFlatFieldMetadata,
isUnique: true,
},
flatObjectMetadata,
@@ -151,7 +154,7 @@ const handleExistingIndexes = ({
);
return {
...NO_INDEX_CHANGES,
...FIELD_METADATA_UPDATE_INDEX_SIDE_EFFECT,
flatIndexMetadatasToDelete: uniqueIndexToDelete
? [uniqueIndexToDelete]
: [],
@@ -161,16 +164,16 @@ const handleExistingIndexes = ({
const updatedIndexes = recomputeIndexOnFlatFieldMetadataNameUpdate({
flatFieldMetadataMaps,
flatObjectMetadata,
fromFlatFieldMetadata: originalFlatFieldMetadata,
fromFlatFieldMetadata,
toFlatFieldMetadata: {
name: updatedFlatFieldMetadata.name,
isUnique: updatedFlatFieldMetadata.isUnique,
name: toFlatFieldMetadata.name,
isUnique: toFlatFieldMetadata.isUnique,
},
relatedFlatIndexMetadata: relatedIndexes,
});
return {
...NO_INDEX_CHANGES,
flatIndexMetadataToUpdate: updatedIndexes,
...FIELD_METADATA_UPDATE_INDEX_SIDE_EFFECT,
flatIndexMetadatasToUpdate: updatedIndexes,
};
};
@@ -0,0 +1,119 @@
import { isNonEmptyString } from '@sniptt/guards';
import { type EnumFieldMetadataType } from 'twenty-shared/types';
import { isDefined, parseJson } from 'twenty-shared/utils';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { compareTwoFlatFieldMetadataEnumOptions } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata-enum-options.util';
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
type RecomputeViewFiltersOnFlatFieldMetadataOptionsUpdateArgs = {
fromFlatFieldMetadata: FlatFieldMetadata<EnumFieldMetadataType>;
update: PropertyUpdate<FlatFieldMetadata<EnumFieldMetadataType>, 'options'>;
} & Pick<AllFlatEntityMaps, 'flatViewFilterMaps'>;
export type FlatViewFiltersToDeleteAndUpdate = {
flatViewFiltersToDelete: FlatViewFilter[];
flatViewFiltersToUpdate: FlatViewFilter[];
};
export const recomputeViewFiltersOnFlatFieldMetadataOptionsUpdate = ({
flatViewFilterMaps,
fromFlatFieldMetadata,
update,
}: RecomputeViewFiltersOnFlatFieldMetadataOptionsUpdateArgs): FlatViewFiltersToDeleteAndUpdate => {
const flatViewFiltersToCreateAndUpdate: FlatViewFiltersToDeleteAndUpdate = {
flatViewFiltersToDelete: [],
flatViewFiltersToUpdate: [],
};
const {
deleted: deletedFieldMetadataOptions,
updated: updatedFieldMetadataOptions,
} = compareTwoFlatFieldMetadataEnumOptions({
compareLabel: false,
fromOptions: fromFlatFieldMetadata.options,
toOptions: update.to,
});
if (
deletedFieldMetadataOptions.length === 0 &&
updatedFieldMetadataOptions.length === 0
) {
return flatViewFiltersToCreateAndUpdate;
}
const flatViewFilters = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.viewFilterIds,
flatEntityMaps: flatViewFilterMaps,
});
for (const viewFilter of flatViewFilters) {
const rawViewFilterValue = viewFilter.value;
if (!isDefined(rawViewFilterValue)) {
continue;
}
// TODO: all view filter value should be stored as JSON, this is ongoing work (we are missing a command to migrate the data)
const viewFilterValue = isNonEmptyString(rawViewFilterValue)
? parseJson(rawViewFilterValue)
: rawViewFilterValue;
if (!isDefined(viewFilterValue) || !Array.isArray(viewFilterValue)) {
throw new FieldMetadataException(
`Unexpected invalid view filter value for filter ${viewFilter.id}`,
FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR,
);
}
const viewFilterOptions = viewFilterValue
.flatMap((value) => {
if (!isDefined(fromFlatFieldMetadata.options)) {
return undefined;
}
return fromFlatFieldMetadata.options.find(
(option) => option.value === value,
);
})
.filter(isDefined);
const afterDeleteViewFilterOptions = viewFilterOptions.filter(
(viewFilterOption) =>
!deletedFieldMetadataOptions.some(
(option) => option.value === viewFilterOption.value,
),
);
if (afterDeleteViewFilterOptions.length === 0) {
flatViewFiltersToCreateAndUpdate.flatViewFiltersToDelete.push(viewFilter);
continue;
}
const afterUpdateAndDeleteViewFilterOptions =
afterDeleteViewFilterOptions.map((viewFilterOption) => {
const updatedOption = updatedFieldMetadataOptions.find(
({ from }) => viewFilterOption.value === from.value,
);
return isDefined(updatedOption) ? updatedOption.to : viewFilterOption;
});
const optionsValues = afterUpdateAndDeleteViewFilterOptions.map(
(option) => option.value,
);
flatViewFiltersToCreateAndUpdate.flatViewFiltersToUpdate.push({
...viewFilter,
value: optionsValues,
});
}
return flatViewFiltersToCreateAndUpdate;
};
@@ -0,0 +1,81 @@
import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
import { reduceFlatViewGroupsByViewId } from 'src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-id.util';
type RecomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdateArgs = FromTo<
FlatFieldMetadata,
'flatFieldMetadata'
> &
Pick<AllFlatEntityMaps, 'flatViewGroupMaps'>;
type EnumFieldMetadataIsNullableUpdateSideEffect = {
flatViewGroupsToDelete: FlatViewGroup[];
flatViewGroupsToCreate: FlatViewGroup[];
};
const EMPTY_ENUM_FIELD_METADATA_IS_NULLABLE_UPDATE_SIDE_EFFECT_RESULT: EnumFieldMetadataIsNullableUpdateSideEffect =
{
flatViewGroupsToCreate: [],
flatViewGroupsToDelete: [],
};
export const recomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdate = ({
flatViewGroupMaps: allFlatViewGroups,
fromFlatFieldMetadata,
toFlatFieldMetadata,
}: RecomputeViewGroupsOnEnumFlatFieldMetadataIsNullableUpdateArgs): {
flatViewGroupsToDelete: FlatViewGroup[];
flatViewGroupsToCreate: FlatViewGroup[];
} => {
const sideEffectResult = structuredClone(
EMPTY_ENUM_FIELD_METADATA_IS_NULLABLE_UPDATE_SIDE_EFFECT_RESULT,
);
const flatViewGroups = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.viewGroupIds,
flatEntityMaps: allFlatViewGroups,
});
const { flatViewGroupRecordByViewId, highestViewGroupPositionByViewId } =
reduceFlatViewGroupsByViewId({
flatViewGroups,
});
for (const viewId in flatViewGroupRecordByViewId) {
const flatViewGroups = Object.values(flatViewGroupRecordByViewId[viewId]);
const emptyValueFlatViewGroup = flatViewGroups.find(
(flatViewGroup) => flatViewGroup.fieldValue === '',
);
if (
toFlatFieldMetadata.isNullable === true &&
!isDefined(emptyValueFlatViewGroup)
) {
const highestViewGroupPosition = highestViewGroupPositionByViewId[viewId];
const viewGroupId = v4();
const createdAt = new Date();
sideEffectResult.flatViewGroupsToCreate.push({
fieldMetadataId: toFlatFieldMetadata.id,
id: viewGroupId,
universalIdentifier: viewGroupId,
fieldValue: '',
position: highestViewGroupPosition + 1,
isVisible: true,
workspaceId: toFlatFieldMetadata.workspaceId,
createdAt,
updatedAt: createdAt,
deletedAt: null,
viewId,
});
} else if (isDefined(emptyValueFlatViewGroup)) {
sideEffectResult.flatViewGroupsToDelete.push(emptyValueFlatViewGroup);
}
}
return sideEffectResult;
};
@@ -0,0 +1,121 @@
import { isDefined } from 'class-validator';
import { type EnumFieldMetadataType } from 'twenty-shared/types';
import { v4 } from 'uuid';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { compareTwoFlatFieldMetadataEnumOptions } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata-enum-options.util';
import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
import { reduceFlatViewGroupsByViewId } from 'src/engine/metadata-modules/flat-view-group/utils/reduce-flat-view-groups-by-view-id.util';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
type RecomputeViewGroupsOnFlatFieldMetadataOptionsUpdateArgs = {
fromFlatFieldMetadata: FlatFieldMetadata<EnumFieldMetadataType>;
update: PropertyUpdate<FlatFieldMetadata<EnumFieldMetadataType>, 'options'>;
} & Pick<AllFlatEntityMaps, 'flatViewGroupMaps'>;
export type FlatViewGroupsToDeleteUpdateAndCreate = {
flatViewGroupsToDelete: FlatViewGroup[];
flatViewGroupsToUpdate: FlatViewGroup[];
flatViewGroupsToCreate: FlatViewGroup[];
};
export const recomputeViewGroupsOnFlatFieldMetadataOptionsUpdate = ({
flatViewGroupMaps,
fromFlatFieldMetadata,
update,
}: RecomputeViewGroupsOnFlatFieldMetadataOptionsUpdateArgs): FlatViewGroupsToDeleteUpdateAndCreate => {
const {
deleted: deletedFieldMetadataOptions,
updated: updatedFieldMetadataOptions,
created: createdFieldMetadataOptions,
} = compareTwoFlatFieldMetadataEnumOptions({
compareLabel: false,
fromOptions: fromFlatFieldMetadata.options,
toOptions: update.to,
});
const flatViewGroups = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityIds: fromFlatFieldMetadata.viewGroupIds,
flatEntityMaps: flatViewGroupMaps,
});
if (flatViewGroups.length === 0) {
return {
flatViewGroupsToCreate: [],
flatViewGroupsToDelete: [],
flatViewGroupsToUpdate: [],
};
}
const workspaceId = fromFlatFieldMetadata.workspaceId;
const flatViewGroupsToDelete = deletedFieldMetadataOptions.flatMap((option) =>
flatViewGroups.filter(
(flatViewGroup) => flatViewGroup.fieldValue === option.value,
),
);
const flatViewGroupsToUpdate = updatedFieldMetadataOptions.flatMap(
({ from: fromOption, to: toOption }) =>
flatViewGroups.flatMap((flatViewGroup) =>
flatViewGroup.fieldValue === fromOption.value
? { ...flatViewGroup, fieldValue: toOption.value }
: [],
),
);
const viewGroupsByViewId = reduceFlatViewGroupsByViewId({
flatViewGroups: flatViewGroups.filter(
(flatViewGroup) =>
!flatViewGroupsToDelete.some(
(flatViewGroupToDelete) =>
flatViewGroupToDelete.id === flatViewGroup.id,
),
),
});
const viewIds = Object.keys(viewGroupsByViewId.flatViewGroupRecordByViewId);
const createdAt = new Date();
const flatViewGroupsToCreate = createdFieldMetadataOptions.flatMap(
(option, createdOptionIndex) =>
viewIds.map<FlatViewGroup>((viewId) => {
const viewGroupHighestPosition =
viewGroupsByViewId.highestViewGroupPositionByViewId[viewId];
if (!isDefined(viewGroupHighestPosition)) {
throw new FlatEntityMapsException(
'View id highest position not found, should never occur',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const viewGroupId = v4();
return {
id: viewGroupId,
fieldMetadataId: fromFlatFieldMetadata.id,
viewId,
workspaceId,
createdAt: createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: viewGroupId,
isVisible: true,
fieldValue: option.value,
position: viewGroupHighestPosition + createdOptionIndex + 1,
};
}),
);
return {
flatViewGroupsToCreate,
flatViewGroupsToDelete,
flatViewGroupsToUpdate,
};
};
@@ -0,0 +1,39 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
type FlatViewGroupsByViewId = {
flatViewGroupRecordByViewId: Record<string, Record<string, FlatViewGroup>>;
highestViewGroupPositionByViewId: Record<string, number>;
};
export const reduceFlatViewGroupsByViewId = ({
flatViewGroups,
}: {
flatViewGroups: FlatViewGroup[];
}): FlatViewGroupsByViewId => {
const initialAccumulator: FlatViewGroupsByViewId = {
flatViewGroupRecordByViewId: {},
highestViewGroupPositionByViewId: {},
};
return flatViewGroups.reduce((accumulator, flatViewGroup) => {
const accumulatorHighestPosition =
accumulator.highestViewGroupPositionByViewId[flatViewGroup.viewId];
return {
flatViewGroupRecordByViewId: {
...accumulator.flatViewGroupRecordByViewId,
[flatViewGroup.viewId]: {
...accumulator.flatViewGroupRecordByViewId[flatViewGroup.viewId],
[flatViewGroup.id]: flatViewGroup,
},
},
highestViewGroupPositionByViewId: {
...accumulator.highestViewGroupPositionByViewId,
[flatViewGroup.viewId]: isDefined(accumulatorHighestPosition)
? Math.max(accumulatorHighestPosition, flatViewGroup.position)
: flatViewGroup.position,
},
};
}, initialAccumulator);
};
@@ -2,14 +2,9 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
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 { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-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 { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata-and-flat-field-metadatas-to-create.util';
@@ -86,76 +81,34 @@ export class ObjectMetadataServiceV2 {
flatViewMaps: existingFlatViewMaps,
});
const toFlatObjectMetadataMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatObjectMetadata,
flatEntityMaps: EMPTY_FLAT_ENTITY_MAPS,
});
const toFlatFieldMetadataMaps = otherObjectFlatFieldMetadatas.reduce(
(flatFieldMaps, flatFieldMetadata) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatFieldMetadata,
flatEntityMaps: flatFieldMaps,
}),
getSubFlatEntityMapsOrThrow({
flatEntityIds: otherObjectFlatFieldMetadatas.map(({ id }) => id),
flatEntityMaps: existingFlatFieldMetadataMaps,
}),
);
const toFlatIndexMaps = flatIndexMetadataToUpdate.reduce(
(flatIndexMaps, flatIndexMetadata) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatIndexMetadata,
flatEntityMaps: flatIndexMaps,
}),
getSubFlatEntityMapsOrThrow({
flatEntityIds: flatIndexMetadataToUpdate.map(({ id }) => id),
flatEntityMaps: existingFlatIndexMaps,
}),
);
const flatViewFieldMapsWithUpdated = flatViewFieldToUpdate.reduce(
(flatViewFieldMaps, flatViewField) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatViewField,
flatEntityMaps: flatViewFieldMaps,
}),
getSubFlatEntityMapsOrThrow({
flatEntityIds: flatViewFieldToUpdate.map(({ id }) => id),
flatEntityMaps: existingFlatViewFieldMaps,
}),
);
const toFlatViewFieldMaps = flatViewFieldToCreate.reduce(
(flatViewFieldMaps, flatViewField) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewField,
flatEntityMaps: flatViewFieldMaps,
}),
flatViewFieldMapsWithUpdated,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatObjectMetadataMaps: {
from: existingFlatObjectMetadataMaps,
to: toFlatObjectMetadataMaps,
},
flatIndexMaps: {
from: existingFlatIndexMaps,
to: toFlatIndexMaps,
},
flatFieldMetadataMaps: {
from: existingFlatFieldMetadataMaps,
to: toFlatFieldMetadataMaps,
},
flatViewFieldMaps: {
from: existingFlatViewFieldMaps,
to: toFlatViewFieldMaps,
},
flatObjectMetadataMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatObjectMetadataMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatObjectMetadata],
}),
flatIndexMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatIndexMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: flatIndexMetadataToUpdate,
}),
flatFieldMetadataMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatFieldMetadataMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: otherObjectFlatFieldMetadatas,
}),
flatViewFieldMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFieldMaps,
flatEntityToCreate: flatViewFieldToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: flatViewFieldToUpdate,
}),
},
dependencyAllFlatEntityMaps: {
flatViewMaps: existingFlatViewMaps,
@@ -240,70 +193,28 @@ export class ObjectMetadataServiceV2 {
deleteObjectInput,
});
const impactedObjectMetadataIds = Array.from(
new Set(
flatFieldMetadatasToDelete.map(
(flatFieldMetadata) => flatFieldMetadata.objectMetadataId,
),
),
);
const fromFlatObjectMetadataMaps = getSubFlatEntityMapsOrThrow({
flatEntityMaps: existingFlatObjectMetadataMaps,
flatEntityIds: impactedObjectMetadataIds,
});
const toFlatObjectMetadataMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatObjectMetadataToDelete.id,
flatEntityMaps: fromFlatObjectMetadataMaps,
});
const fromFlatFieldMetadataMaps = getSubFlatEntityMapsOrThrow({
flatEntityMaps: existingFlatFieldMetadataMaps,
flatEntityIds: flatFieldMetadatasToDelete.map(
(flatField) => flatField.id,
),
});
const toFlatFieldMetadataMaps = flatFieldMetadatasToDelete.reduce(
(flatFieldMaps, flatField) =>
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatField.id,
flatEntityMaps: flatFieldMaps,
}),
fromFlatFieldMetadataMaps,
);
const fromFlatIndexMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: flatIndexToDelete.map((flatIndex) => flatIndex.id),
flatEntityMaps: existingFlatIndexMaps,
});
const toFlatIndexMaps = flatIndexToDelete.reduce(
(flatIndexMaps, flatIndex) =>
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatIndex.id,
flatEntityMaps: flatIndexMaps,
}),
fromFlatIndexMaps,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatObjectMetadataMaps: {
from: fromFlatObjectMetadataMaps,
to: toFlatObjectMetadataMaps,
},
flatIndexMaps: {
from: fromFlatIndexMaps,
to: toFlatIndexMaps,
},
flatFieldMetadataMaps: {
from: fromFlatFieldMetadataMaps,
to: toFlatFieldMetadataMaps,
},
flatObjectMetadataMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatObjectMetadataMaps,
flatEntityToCreate: [],
flatEntityToDelete: [flatObjectMetadataToDelete],
flatEntityToUpdate: [],
}),
flatIndexMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatIndexMaps,
flatEntityToCreate: [],
flatEntityToDelete: flatIndexToDelete,
flatEntityToUpdate: [],
}),
flatFieldMetadataMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatFieldMetadataMaps,
flatEntityToCreate: [],
flatEntityToDelete: flatFieldMetadatasToDelete,
flatEntityToUpdate: [],
}),
},
buildOptions: {
inferDeletionFromMissingEntities: {
@@ -366,21 +277,14 @@ export class ObjectMetadataServiceV2 {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
});
const toFlatFieldMetadataMaps = [
...flatFieldMetadataToCreateOnObject,
...relationTargetFlatFieldMetadataToCreate,
].reduce<FlatEntityMaps<FlatFieldMetadata>>(
(flatFieldMaps, flatField) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatField,
flatEntityMaps: flatFieldMaps,
}),
EMPTY_FLAT_ENTITY_MAPS,
);
const toFlatObjectMetadataMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatObjectMetadataToCreate,
flatEntityMaps: EMPTY_FLAT_ENTITY_MAPS,
const flatFieldMetadataMapsFromTo = computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatFieldMetadataMaps,
flatEntityToCreate: [
...flatFieldMetadataToCreateOnObject,
...relationTargetFlatFieldMetadataToCreate,
],
flatEntityToDelete: [],
flatEntityToUpdate: [],
});
const flatDefaultViewToCreate = await this.createDefaultFlatView({
@@ -388,16 +292,11 @@ export class ObjectMetadataServiceV2 {
workspaceId,
});
const toFlatViewMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatDefaultViewToCreate,
flatEntityMaps: existingFlatViewMaps,
});
const flatDefaultViewFieldsToCreate =
await this.createDefaultFlatViewFields({
objectFlatFieldMetadatas: findManyFlatEntityByIdInFlatEntityMapsOrThrow(
{
flatEntityMaps: toFlatFieldMetadataMaps,
flatEntityMaps: flatFieldMetadataMapsFromTo.to,
flatEntityIds: flatFieldMetadataToCreateOnObject.map(
({ id }) => id,
),
@@ -407,48 +306,35 @@ export class ObjectMetadataServiceV2 {
workspaceId,
});
const toFlatViewFieldMaps = flatDefaultViewFieldsToCreate.reduce(
(acc, flatViewField) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewField,
flatEntityMaps: acc,
}),
existingFlatViewFieldMaps,
);
const toFlatIndexMaps = flatIndexMetadataToCreate.reduce(
(flatIndexMaps, flatIndexMetadata) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatIndexMetadata,
flatEntityMaps: flatIndexMaps,
}),
existingFlatIndexMaps,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatObjectMetadataMaps: {
from: existingFlatObjectMetadataMaps,
to: toFlatObjectMetadataMaps,
},
flatViewMaps: {
from: existingFlatViewMaps,
to: toFlatViewMaps,
},
flatViewFieldMaps: {
from: existingFlatViewFieldMaps,
to: toFlatViewFieldMaps,
},
flatFieldMetadataMaps: {
from: existingFlatFieldMetadataMaps,
to: toFlatFieldMetadataMaps,
},
flatIndexMaps: {
from: existingFlatIndexMaps,
to: toFlatIndexMaps,
},
flatObjectMetadataMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatObjectMetadataMaps,
flatEntityToCreate: [flatObjectMetadataToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
flatViewMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewMaps,
flatEntityToCreate: [flatDefaultViewToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
flatViewFieldMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFieldMaps,
flatEntityToCreate: flatDefaultViewFieldsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
flatFieldMetadataMaps: flatFieldMetadataMapsFromTo,
flatIndexMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatIndexMaps,
flatEntityToCreate: flatIndexMetadataToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
buildOptions: {
isSystemBuild: false,
@@ -28,6 +28,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
type: FieldMetadataType.UUID,
id: v4(),
viewFieldIds: [],
viewGroupIds: [],
isLabelSyncedWithName: false,
isUnique: true,
objectMetadataId,
@@ -47,6 +48,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isSystem: true,
isUIReadOnly: true,
defaultValue: 'uuid',
viewFilterIds: [],
createdAt,
updatedAt: createdAt,
@@ -62,6 +64,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
type: FieldMetadataType.TEXT,
id: v4(),
viewFieldIds: [],
viewGroupIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -81,6 +84,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isSystem: false,
isUIReadOnly: false,
defaultValue: "''",
viewFilterIds: [],
createdAt,
updatedAt: createdAt,
@@ -96,6 +100,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
type: FieldMetadataType.DATE_TIME,
id: v4(),
viewFieldIds: [],
viewGroupIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -115,6 +120,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isSystem: false,
isUIReadOnly: true,
defaultValue: 'now',
viewFilterIds: [],
createdAt,
updatedAt: createdAt,
@@ -130,6 +136,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
type: FieldMetadataType.DATE_TIME,
id: v4(),
viewFieldIds: [],
viewGroupIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -149,6 +156,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isSystem: false,
isUIReadOnly: true,
defaultValue: 'now',
viewFilterIds: [],
createdAt,
updatedAt: createdAt,
@@ -164,6 +172,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
type: FieldMetadataType.DATE_TIME,
id: v4(),
viewFieldIds: [],
viewGroupIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -183,6 +192,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isSystem: false,
isUIReadOnly: true,
defaultValue: null,
viewFilterIds: [],
createdAt,
updatedAt: createdAt,
@@ -198,6 +208,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
type: FieldMetadataType.ACTOR,
id: v4(),
viewFieldIds: [],
viewGroupIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -217,7 +228,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isSystem: false,
isUIReadOnly: true,
defaultValue: { name: "''", source: "'MANUAL'" },
viewFilterIds: [],
createdAt,
updatedAt: createdAt,
options: null,
@@ -232,6 +243,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
type: FieldMetadataType.POSITION,
id: v4(),
viewFieldIds: [],
viewGroupIds: [],
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
@@ -251,6 +263,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isSystem: true,
isUIReadOnly: true,
defaultValue: 0,
viewFilterIds: [],
createdAt,
updatedAt: createdAt,
@@ -265,6 +278,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
const searchVectorField: FlatFieldMetadata<FieldMetadataType.TS_VECTOR> = {
type: FieldMetadataType.TS_VECTOR,
viewFieldIds: [],
viewGroupIds: [],
id: v4(),
isLabelSyncedWithName: false,
isUnique: false,
@@ -285,6 +299,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isSystem: true,
isUIReadOnly: true,
defaultValue: null,
viewFilterIds: [],
createdAt,
updatedAt: createdAt,
@@ -63,7 +63,9 @@ const generateSourceFlatFieldMetadata = ({
}
return {
viewFilterIds: [],
viewFieldIds: [],
viewGroupIds: [],
createdAt,
updatedAt: createdAt,
defaultValue: null,
@@ -129,6 +131,8 @@ const generateTargetFlatFieldMetadata = ({
return {
morphId: null,
viewFieldIds: [],
viewFilterIds: [],
viewGroupIds: [],
id: sourceFlatFieldMetadata.relationTargetFieldMetadataId,
name: sourceFlatObjectMetadata.nameSingular,
label: sourceFlatObjectMetadata.labelSingular,
@@ -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 { CreateRouteTriggerInput } from 'src/engine/metadata-modules/route-trigger/dtos/create-route-trigger.input';
import { RouteTriggerIdInput } from 'src/engine/metadata-modules/route-trigger/dtos/route-trigger-id.input';
import { UpdateRouteTriggerInput } from 'src/engine/metadata-modules/route-trigger/dtos/update-route-trigger.input';
@@ -48,20 +45,17 @@ export class RouteTriggerV2Service {
workspaceId,
});
const toFlatRouteMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatRouteTriggerToCreate,
flatEntityMaps: existingFlatRouteMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatRouteTriggerMaps: {
from: existingFlatRouteMaps,
to: toFlatRouteMaps,
},
flatRouteTriggerMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatRouteMaps,
flatEntityToCreate: [flatRouteTriggerToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatServerlessFunctionMaps:
@@ -114,24 +108,17 @@ export class RouteTriggerV2Service {
updateRouteTriggerInput: routeTriggerInput,
});
const fromFlatRouteMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [optimisticallyUpdatedFlatRouteTrigger.id],
flatEntityMaps: existingFlatRouteMaps,
});
const toFlatRouteMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatRouteTrigger,
flatEntityMaps: fromFlatRouteMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
workspaceId,
fromToAllFlatEntityMaps: {
flatRouteTriggerMaps: {
from: existingFlatRouteMaps,
to: toFlatRouteMaps,
},
flatRouteTriggerMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatRouteMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatRouteTrigger],
}),
},
dependencyAllFlatEntityMaps: {
flatServerlessFunctionMaps:
@@ -192,23 +179,16 @@ export class RouteTriggerV2Service {
);
}
const fromFlatRouteMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [existingFlatRoute.id],
flatEntityMaps: existingFlatRouteMaps,
});
const toFlatRouteMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatRouteMaps,
entityToDeleteId: existingFlatRoute.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatRouteTriggerMaps: {
from: fromFlatRouteMaps,
to: toFlatRouteMaps,
},
flatRouteTriggerMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatRouteMaps,
flatEntityToCreate: [],
flatEntityToDelete: [existingFlatRoute],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatServerlessFunctionMaps: existingFlatServerlessFunctionMaps,
@@ -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,
@@ -2,13 +2,9 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
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 { fromCreateViewFieldInputToFlatViewFieldToCreate } from 'src/engine/metadata-modules/flat-view-field/utils/from-create-view-field-input-to-flat-view-field-to-create.util';
import { fromDeleteViewFieldInputToFlatViewFieldOrThrow } from 'src/engine/metadata-modules/flat-view-field/utils/from-delete-view-field-input-to-flat-view-field-or-throw.util';
import { fromDestroyViewFieldInputToFlatViewFieldOrThrow } from 'src/engine/metadata-modules/flat-view-field/utils/from-destroy-view-field-input-to-flat-view-field-or-throw.util';
@@ -58,19 +54,16 @@ export class ViewFieldV2Service {
workspaceId,
});
const toFlatViewFieldMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewFieldToCreate,
flatEntityMaps: existingFlatViewFieldMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewFieldMaps: {
from: existingFlatViewFieldMaps,
to: toFlatViewFieldMaps,
},
flatViewFieldMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFieldMaps,
flatEntityToCreate: [flatViewFieldToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatFieldMetadataMaps,
@@ -133,19 +126,16 @@ export class ViewFieldV2Service {
updateViewFieldInput,
});
const toFlatViewFieldMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatView,
flatEntityMaps: EMPTY_FLAT_ENTITY_MAPS,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewFieldMaps: {
from: existingFlatViewFieldMaps,
to: toFlatViewFieldMaps,
},
flatViewFieldMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFieldMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatView],
}),
},
dependencyAllFlatEntityMaps: {
flatObjectMetadataMaps,
@@ -207,19 +197,16 @@ export class ViewFieldV2Service {
deleteViewFieldInput,
});
const toFlatViewFieldMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatViewWithDeletedAt,
flatEntityMaps: existingFlatViewFieldMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewFieldMaps: {
from: existingFlatViewFieldMaps,
to: toFlatViewFieldMaps,
},
flatViewFieldMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFieldMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatViewWithDeletedAt],
}),
},
dependencyAllFlatEntityMaps: {
flatObjectMetadataMaps,
@@ -277,23 +264,16 @@ export class ViewFieldV2Service {
flatViewFieldMaps: existingFlatViewFieldMaps,
});
const fromFlatViewFieldMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [existingViewFieldToDelete.id],
flatEntityMaps: existingFlatViewFieldMaps,
});
const toFlatViewFieldMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatViewFieldMaps,
entityToDeleteId: existingViewFieldToDelete.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewFieldMaps: {
from: fromFlatViewFieldMaps,
to: toFlatViewFieldMaps,
},
flatViewFieldMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFieldMaps,
flatEntityToCreate: [],
flatEntityToDelete: [existingViewFieldToDelete],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatViewMaps: existingFlatViewMaps,
@@ -2,13 +2,9 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
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 { fromCreateViewFilterInputToFlatViewFilterToCreate } from 'src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util';
import { fromDeleteViewFilterInputToFlatViewFilterOrThrow } from 'src/engine/metadata-modules/flat-view-filter/utils/from-delete-view-filter-input-to-flat-view-filter-or-throw.util';
import { fromDestroyViewFilterInputToFlatViewFilterOrThrow } from 'src/engine/metadata-modules/flat-view-filter/utils/from-destroy-view-filter-input-to-flat-view-filter-or-throw.util';
@@ -58,19 +54,16 @@ export class ViewFilterV2Service {
workspaceId,
});
const toFlatViewFilterMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewFilterToCreate,
flatEntityMaps: existingFlatViewFilterMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewFilterMaps: {
from: existingFlatViewFilterMaps,
to: toFlatViewFilterMaps,
},
flatViewFilterMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFilterMaps,
flatEntityToCreate: [flatViewFilterToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatFieldMetadataMaps,
@@ -133,19 +126,16 @@ export class ViewFilterV2Service {
updateViewFilterInput,
});
const toFlatViewFilterMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatViewFilter,
flatEntityMaps: EMPTY_FLAT_ENTITY_MAPS,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewFilterMaps: {
from: existingFlatViewFilterMaps,
to: toFlatViewFilterMaps,
},
flatViewFilterMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFilterMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatViewFilter],
}),
},
dependencyAllFlatEntityMaps: {
flatViewMaps,
@@ -207,19 +197,18 @@ export class ViewFilterV2Service {
deleteViewFilterInput,
});
const toFlatViewFilterMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatViewFilterWithDeletedAt,
flatEntityMaps: existingFlatViewFilterMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewFilterMaps: {
from: existingFlatViewFilterMaps,
to: toFlatViewFilterMaps,
},
flatViewFilterMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFilterMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [
optimisticallyUpdatedFlatViewFilterWithDeletedAt,
],
}),
},
dependencyAllFlatEntityMaps: {
flatFieldMetadataMaps,
@@ -281,23 +270,16 @@ export class ViewFilterV2Service {
flatViewFilterMaps: existingFlatViewFilterMaps,
});
const fromFlatViewFilterMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [existingViewFilterToDelete.id],
flatEntityMaps: existingFlatViewFilterMaps,
});
const toFlatViewFilterMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatViewFilterMaps,
entityToDeleteId: existingViewFilterToDelete.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewFilterMaps: {
from: fromFlatViewFilterMaps,
to: toFlatViewFilterMaps,
},
flatViewFilterMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewFilterMaps,
flatEntityToCreate: [],
flatEntityToDelete: [existingViewFilterToDelete],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatViewMaps: existingFlatViewMaps,
@@ -2,13 +2,9 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
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 { fromCreateViewGroupInputToFlatViewGroupToCreate } from 'src/engine/metadata-modules/flat-view-group/utils/from-create-view-group-input-to-flat-view-group-to-create.util';
import { fromDeleteViewGroupInputToFlatViewGroupOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-delete-view-group-input-to-flat-view-group-or-throw.util';
import { fromDestroyViewGroupInputToFlatViewGroupOrThrow } from 'src/engine/metadata-modules/flat-view-group/utils/from-destroy-view-group-input-to-flat-view-group-or-throw.util';
@@ -56,19 +52,16 @@ export class ViewGroupV2Service {
workspaceId,
});
const toFlatViewGroupMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewGroupToCreate,
flatEntityMaps: existingFlatViewGroupMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewGroupMaps: {
from: existingFlatViewGroupMaps,
to: toFlatViewGroupMaps,
},
flatViewGroupMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewGroupMaps,
flatEntityToCreate: [flatViewGroupToCreate],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatFieldMetadataMaps,
@@ -130,19 +123,16 @@ export class ViewGroupV2Service {
updateViewGroupInput,
});
const toFlatViewGroupMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatViewGroup,
flatEntityMaps: EMPTY_FLAT_ENTITY_MAPS,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewGroupMaps: {
from: existingFlatViewGroupMaps,
to: toFlatViewGroupMaps,
},
flatViewGroupMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewGroupMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatViewGroup],
}),
},
dependencyAllFlatEntityMaps: {
flatViewMaps,
@@ -204,19 +194,18 @@ export class ViewGroupV2Service {
deleteViewGroupInput,
});
const toFlatViewGroupMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: optimisticallyUpdatedFlatViewGroupWithDeletedAt,
flatEntityMaps: existingFlatViewGroupMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewGroupMaps: {
from: existingFlatViewGroupMaps,
to: toFlatViewGroupMaps,
},
flatViewGroupMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewGroupMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [
optimisticallyUpdatedFlatViewGroupWithDeletedAt,
],
}),
},
dependencyAllFlatEntityMaps: {
flatFieldMetadataMaps,
@@ -278,23 +267,16 @@ export class ViewGroupV2Service {
flatViewGroupMaps: existingFlatViewGroupMaps,
});
const fromFlatViewGroupMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [existingViewGroupToDelete.id],
flatEntityMaps: existingFlatViewGroupMaps,
});
const toFlatViewGroupMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatViewGroupMaps,
entityToDeleteId: existingViewGroupToDelete.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewGroupMaps: {
from: fromFlatViewGroupMaps,
to: toFlatViewGroupMaps,
},
flatViewGroupMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewGroupMaps,
flatEntityToCreate: [],
flatEntityToDelete: [existingViewGroupToDelete],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatViewMaps: existingFlatViewMaps,
@@ -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 { fromCreateViewInputToFlatViewToCreate } from 'src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util';
import { fromDeleteViewInputToFlatViewOrThrow } from 'src/engine/metadata-modules/flat-view/utils/from-delete-view-input-to-flat-view-or-throw.util';
import { fromDestroyViewInputToFlatViewOrThrow } from 'src/engine/metadata-modules/flat-view/utils/from-destroy-view-input-to-flat-view-or-throw.util';
@@ -47,19 +44,16 @@ export class ViewV2Service {
workspaceId,
});
const toFlatViewMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewFromCreateInput,
flatEntityMaps: existingFlatViewMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewMaps: {
from: existingFlatViewMaps,
to: toFlatViewMaps,
},
flatViewMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewMaps,
flatEntityToCreate: [flatViewFromCreateInput],
flatEntityToDelete: [],
flatEntityToUpdate: [],
}),
},
dependencyAllFlatEntityMaps: {
flatObjectMetadataMaps: flatObjectMetadataMaps,
@@ -113,23 +107,16 @@ export class ViewV2Service {
flatViewMaps: existingFlatViewMaps,
});
const fromFlatViewMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [flatViewFromUpdateInput.id],
flatEntityMaps: existingFlatViewMaps,
});
const toFlatViewMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatViewFromUpdateInput,
flatEntityMaps: fromFlatViewMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewMaps: {
from: fromFlatViewMaps,
to: toFlatViewMaps,
},
flatViewMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [flatViewFromUpdateInput],
}),
},
buildOptions: {
isSystemBuild: false,
@@ -174,28 +161,22 @@ export class ViewV2Service {
},
);
const flatViewFromDeleteInput = fromDeleteViewInputToFlatViewOrThrow({
deleteViewInput,
flatViewMaps: existingFlatViewMaps,
});
const fromFlatViewMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [flatViewFromDeleteInput.id],
flatEntityMaps: existingFlatViewMaps,
});
const toFlatViewMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatViewFromDeleteInput,
flatEntityMaps: fromFlatViewMaps,
});
const optimisticallyUpdatedFlatViewWithDeletedAt =
fromDeleteViewInputToFlatViewOrThrow({
deleteViewInput,
flatViewMaps: existingFlatViewMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewMaps: {
from: fromFlatViewMaps,
to: toFlatViewMaps,
},
flatViewMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewMaps,
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatViewWithDeletedAt],
}),
},
buildOptions: {
isSystemBuild: false,
@@ -245,23 +226,16 @@ export class ViewV2Service {
flatViewMaps: existingFlatViewMaps,
});
const fromFlatViewMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [flatViewFromDestroyInput.id],
flatEntityMaps: existingFlatViewMaps,
});
const toFlatViewMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatViewMaps,
entityToDeleteId: flatViewFromDestroyInput.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewMaps: {
from: fromFlatViewMaps,
to: toFlatViewMaps,
},
flatViewMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: existingFlatViewMaps,
flatEntityToCreate: [],
flatEntityToDelete: [flatViewFromDestroyInput],
flatEntityToUpdate: [],
}),
},
buildOptions: {
isSystemBuild: false,