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:
+19
-39
@@ -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,
|
||||
|
||||
+21
-43
@@ -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,
|
||||
|
||||
+8
@@ -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[]>;
|
||||
}
|
||||
|
||||
+67
-137
@@ -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,
|
||||
|
||||
+2
-4
@@ -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>>>>;
|
||||
|
||||
+12
@@ -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>>
|
||||
>;
|
||||
+64
@@ -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,
|
||||
};
|
||||
};
|
||||
+2
@@ -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,
|
||||
|
||||
+2
@@ -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,
|
||||
|
||||
+8
@@ -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>,
|
||||
},
|
||||
],
|
||||
|
||||
+7
-1
@@ -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(
|
||||
|
||||
+6
-2
@@ -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[];
|
||||
};
|
||||
|
||||
+349
@@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
+61
@@ -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;
|
||||
};
|
||||
+5
-5
@@ -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,
|
||||
|
||||
+84
-105
@@ -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 {
|
||||
|
||||
+4
-2
@@ -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;
|
||||
};
|
||||
|
||||
+103
@@ -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;
|
||||
};
|
||||
+75
@@ -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,
|
||||
};
|
||||
};
|
||||
+55
-52
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+119
@@ -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;
|
||||
};
|
||||
+81
@@ -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;
|
||||
};
|
||||
+121
@@ -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,
|
||||
};
|
||||
};
|
||||
+39
@@ -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);
|
||||
};
|
||||
+77
-191
@@ -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,
|
||||
|
||||
+16
-1
@@ -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,
|
||||
|
||||
+4
@@ -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,
|
||||
|
||||
+19
-39
@@ -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,
|
||||
|
||||
+27
-51
@@ -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,
|
||||
|
||||
+25
-45
@@ -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,
|
||||
|
||||
+27
-45
@@ -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,
|
||||
|
||||
+27
-45
@@ -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,
|
||||
|
||||
+30
-56
@@ -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,
|
||||
|
||||
+89
-20
@@ -213,6 +213,8 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
oldColumnName,
|
||||
newColumnName: columnName,
|
||||
oldToNewEnumOptionMap,
|
||||
columnDefinition,
|
||||
oldEnumTypeName: oldEnumName,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -315,49 +317,116 @@ export class WorkspaceSchemaEnumManagerService {
|
||||
oldColumnName,
|
||||
newColumnName,
|
||||
oldToNewEnumOptionMap,
|
||||
columnDefinition,
|
||||
oldEnumTypeName,
|
||||
}: {
|
||||
queryRunner: QueryRunner;
|
||||
schemaName: string;
|
||||
tableName: string;
|
||||
oldColumnName: string;
|
||||
newColumnName: string;
|
||||
oldEnumTypeName: string;
|
||||
oldToNewEnumOptionMap: Record<string, string>;
|
||||
columnDefinition: WorkspaceSchemaColumnDefinition;
|
||||
}): Promise<void> {
|
||||
const safeSchemaName = removeSqlDDLInjection(schemaName);
|
||||
const safeTableName = removeSqlDDLInjection(tableName);
|
||||
const safeOldColumnName = removeSqlDDLInjection(oldColumnName);
|
||||
const safeNewColumnName = removeSqlDDLInjection(newColumnName);
|
||||
|
||||
const newEnumTypeName = computePostgresEnumName({
|
||||
tableName,
|
||||
columnName: newColumnName,
|
||||
});
|
||||
|
||||
if (Object.keys(oldToNewEnumOptionMap).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const safeSchemaName = removeSqlDDLInjection(schemaName);
|
||||
const safeTableName = removeSqlDDLInjection(tableName);
|
||||
const safeOldColumnName = removeSqlDDLInjection(oldColumnName);
|
||||
const safeNewColumnName = removeSqlDDLInjection(newColumnName);
|
||||
const caseStatements = Object.entries(oldToNewEnumOptionMap)
|
||||
.map(
|
||||
([oldEnumOption, newEnumOption]) =>
|
||||
`WHEN '${removeSqlDDLInjection(oldEnumOption)}' THEN '${removeSqlDDLInjection(newEnumOption)}'::"${safeSchemaName}"."${newEnumTypeName}"`,
|
||||
)
|
||||
.join(' ');
|
||||
|
||||
const mappedValuesCondition = Object.keys(oldToNewEnumOptionMap)
|
||||
.map((oldValue) => `'${removeSqlDDLInjection(oldValue)}'`)
|
||||
.join(', ');
|
||||
|
||||
// Update rows with mapped enum values
|
||||
const updateMappedSql = `
|
||||
UPDATE "${safeSchemaName}"."${safeTableName}"
|
||||
SET "${safeNewColumnName}" =
|
||||
CASE "${safeOldColumnName}"::text
|
||||
${caseStatements}
|
||||
END
|
||||
WHERE "${safeOldColumnName}" IS NOT NULL
|
||||
AND "${safeOldColumnName}"::text IN (${mappedValuesCondition})`;
|
||||
const sqlQuery = columnDefinition.isArray
|
||||
? this.updateArrayEnum({
|
||||
safeSchemaName,
|
||||
safeTableName,
|
||||
safeOldColumnName,
|
||||
safeNewColumnName,
|
||||
newEnumTypeName,
|
||||
oldEnumTypeName,
|
||||
caseStatements,
|
||||
mappedValuesCondition,
|
||||
})
|
||||
: this.updateAtomicEnum({
|
||||
safeSchemaName,
|
||||
safeTableName,
|
||||
safeOldColumnName,
|
||||
safeNewColumnName,
|
||||
caseStatements,
|
||||
mappedValuesCondition,
|
||||
});
|
||||
|
||||
await queryRunner.query(updateMappedSql);
|
||||
await queryRunner.query(sqlQuery);
|
||||
}
|
||||
|
||||
private updateArrayEnum({
|
||||
safeNewColumnName,
|
||||
safeOldColumnName,
|
||||
safeSchemaName,
|
||||
safeTableName,
|
||||
newEnumTypeName,
|
||||
oldEnumTypeName,
|
||||
caseStatements,
|
||||
mappedValuesCondition,
|
||||
}: {
|
||||
safeSchemaName: string;
|
||||
safeTableName: string;
|
||||
safeOldColumnName: string;
|
||||
safeNewColumnName: string;
|
||||
newEnumTypeName: string;
|
||||
oldEnumTypeName: string;
|
||||
caseStatements: string;
|
||||
mappedValuesCondition: string;
|
||||
}) {
|
||||
return `
|
||||
UPDATE "${safeSchemaName}"."${safeTableName}"
|
||||
SET "${safeNewColumnName}" = (
|
||||
SELECT array_agg(
|
||||
CASE unnest_value::text
|
||||
${caseStatements}
|
||||
ELSE unnest_value::text::"${safeSchemaName}"."${newEnumTypeName}"
|
||||
END
|
||||
)
|
||||
FROM unnest("${safeOldColumnName}") AS unnest_value
|
||||
)
|
||||
WHERE "${safeOldColumnName}" IS NOT NULL
|
||||
AND "${safeOldColumnName}" && ARRAY[${mappedValuesCondition}]::"${safeSchemaName}"."${oldEnumTypeName}"[]`;
|
||||
}
|
||||
|
||||
private updateAtomicEnum({
|
||||
safeNewColumnName,
|
||||
safeOldColumnName,
|
||||
safeSchemaName,
|
||||
safeTableName,
|
||||
caseStatements,
|
||||
mappedValuesCondition,
|
||||
}: {
|
||||
caseStatements: string;
|
||||
mappedValuesCondition: string;
|
||||
safeSchemaName: string;
|
||||
safeTableName: string;
|
||||
safeOldColumnName: string;
|
||||
safeNewColumnName: string;
|
||||
}) {
|
||||
return `
|
||||
UPDATE "${safeSchemaName}"."${safeTableName}"
|
||||
SET "${safeNewColumnName}" =
|
||||
CASE "${safeOldColumnName}"::text
|
||||
${caseStatements}
|
||||
END
|
||||
WHERE "${safeOldColumnName}" IS NOT NULL
|
||||
AND "${safeOldColumnName}"::text IN (${mappedValuesCondition})`;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-1
@@ -59,6 +59,23 @@ export class WorkspaceMigrationV2ViewFilterActionsBuilderService extends Workspa
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatViewMaps,
|
||||
});
|
||||
|
||||
const flatFieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatViewFilterToValidate.fieldMetadataId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
const updatedFlatFieldMetadataMaps =
|
||||
replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: {
|
||||
...flatFieldMetadata,
|
||||
viewFilterIds: [
|
||||
...flatFieldMetadata.viewFilterIds,
|
||||
flatViewFilterToValidate.id,
|
||||
],
|
||||
},
|
||||
flatEntityMaps:
|
||||
dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
@@ -66,7 +83,7 @@ export class WorkspaceMigrationV2ViewFilterActionsBuilderService extends Workspa
|
||||
viewFilter: flatViewFilterToValidate,
|
||||
},
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
...dependencyOptimisticFlatEntityMaps,
|
||||
flatFieldMetadataMaps: updatedFlatFieldMetadataMaps,
|
||||
flatViewMaps: updatedFlatViewMaps,
|
||||
},
|
||||
};
|
||||
|
||||
+38
-2
@@ -59,6 +59,24 @@ export class WorkspaceMigrationV2ViewGroupActionsBuilderService extends Workspac
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatViewMaps,
|
||||
});
|
||||
|
||||
const flatFieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatViewGroupToValidate.fieldMetadataId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const updatedFlatFieldMetadataMaps =
|
||||
replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: {
|
||||
...flatFieldMetadata,
|
||||
viewGroupIds: [
|
||||
...flatFieldMetadata.viewGroupIds,
|
||||
flatViewGroupToValidate.id,
|
||||
],
|
||||
},
|
||||
flatEntityMaps:
|
||||
dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
@@ -66,7 +84,7 @@ export class WorkspaceMigrationV2ViewGroupActionsBuilderService extends Workspac
|
||||
viewGroup: flatViewGroupToValidate,
|
||||
},
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
...dependencyOptimisticFlatEntityMaps,
|
||||
flatFieldMetadataMaps: updatedFlatFieldMetadataMaps,
|
||||
flatViewMaps: updatedFlatViewMaps,
|
||||
},
|
||||
};
|
||||
@@ -112,6 +130,24 @@ export class WorkspaceMigrationV2ViewGroupActionsBuilderService extends Workspac
|
||||
})
|
||||
: dependencyOptimisticFlatEntityMaps.flatViewMaps;
|
||||
|
||||
const flatFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatViewGroupToValidate.fieldMetadataId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const updatedFlatFieldMetadataMaps = isDefined(flatFieldMetadata)
|
||||
? replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: {
|
||||
...flatFieldMetadata,
|
||||
viewGroupIds: flatFieldMetadata.viewGroupIds.filter(
|
||||
(id) => id !== flatViewGroupToValidate.id,
|
||||
),
|
||||
},
|
||||
flatEntityMaps:
|
||||
dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
})
|
||||
: dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps;
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
@@ -119,7 +155,7 @@ export class WorkspaceMigrationV2ViewGroupActionsBuilderService extends Workspac
|
||||
viewGroupId: flatViewGroupToValidate.id,
|
||||
},
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
...dependencyOptimisticFlatEntityMaps,
|
||||
flatFieldMetadataMaps: updatedFlatFieldMetadataMaps,
|
||||
flatViewMaps: updatedFlatViewMaps,
|
||||
},
|
||||
};
|
||||
|
||||
+22
-1
@@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ALL_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
|
||||
import { ViewFilterExceptionCode } from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception';
|
||||
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
@@ -76,6 +77,7 @@ export class FlatViewFilterValidatorService {
|
||||
validateFlatViewFilterDeletion({
|
||||
flatEntityToValidate: flatViewFilterToValidate,
|
||||
optimisticFlatEntityMaps: optimisticFlatViewFilterMaps,
|
||||
dependencyOptimisticFlatEntityMaps,
|
||||
}: FlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.viewFilter
|
||||
>): FailedFlatEntityValidation<FlatViewFilter> {
|
||||
@@ -98,7 +100,26 @@ export class FlatViewFilterValidatorService {
|
||||
message: t`View filter not found`,
|
||||
userFriendlyMessage: msg`View filter not found`,
|
||||
});
|
||||
} else if (!isDefined(existingViewFilter.deletedAt)) {
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const flatFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: existingViewFilter.fieldMetadataId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(flatFieldMetadata)) {
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const shouldValidateDeletion = isEnumFlatFieldMetadata(flatFieldMetadata)
|
||||
? flatFieldMetadata.options.some(
|
||||
(option) => option.value === existingViewFilter.value,
|
||||
)
|
||||
: true;
|
||||
|
||||
if (shouldValidateDeletion && !isDefined(existingViewFilter.deletedAt)) {
|
||||
validationResult.errors.push({
|
||||
code: ViewFilterExceptionCode.INVALID_VIEW_FILTER_DATA,
|
||||
message: t`View filter has to be soft deleted first`,
|
||||
|
||||
+24
-8
@@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ALL_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
|
||||
import { ViewExceptionCode } from 'src/engine/metadata-modules/view/exceptions/view.exception';
|
||||
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
@@ -88,6 +89,7 @@ export class FlatViewGroupValidatorService {
|
||||
public validateFlatViewGroupDeletion({
|
||||
flatEntityToValidate: { id: viewGroupIdToDelete },
|
||||
optimisticFlatEntityMaps: optimisticFlatViewGroupMaps,
|
||||
dependencyOptimisticFlatEntityMaps: { flatFieldMetadataMaps },
|
||||
}: FlatEntityValidationArgs<
|
||||
typeof ALL_METADATA_NAME.viewGroup
|
||||
>): FailedFlatEntityValidation<FlatViewGroup> {
|
||||
@@ -108,14 +110,28 @@ export class FlatViewGroupValidatorService {
|
||||
message: t`View group to delete not found`,
|
||||
userFriendlyMessage: msg`View group to delete not found`,
|
||||
});
|
||||
} else {
|
||||
if (!isDefined(existingFlatViewGroup.deletedAt)) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View group to delete has not been soft deleted`,
|
||||
userFriendlyMessage: msg`View group to delete has not been soft deleted`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const relatedFlatFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: existingFlatViewGroup.fieldMetadataId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (
|
||||
isDefined(relatedFlatFieldMetadata) &&
|
||||
isEnumFlatFieldMetadata(relatedFlatFieldMetadata) &&
|
||||
relatedFlatFieldMetadata.options.some(
|
||||
(option) => option.value === existingFlatViewGroup.fieldValue,
|
||||
) &&
|
||||
!isDefined(existingFlatViewGroup.deletedAt)
|
||||
) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View group to delete has not been soft deleted`,
|
||||
userFriendlyMessage: msg`View group to delete has not been soft deleted`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
|
||||
@@ -19,7 +19,9 @@ export const getMockFieldMetadataEntity = <
|
||||
overrides: GetMockFieldMetadataEntityOverride<T>,
|
||||
): FieldMetadataEntity => {
|
||||
return {
|
||||
viewFilters: [],
|
||||
viewFields: [],
|
||||
viewGroups: [],
|
||||
morphId: null,
|
||||
fieldPermissions: [],
|
||||
icon: null,
|
||||
|
||||
+20
-22
@@ -4,12 +4,12 @@ import { COMPANY_GQL_FIELDS } from 'test/integration/constants/company-gql-field
|
||||
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
|
||||
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
|
||||
import { createViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/create-view-filter-group-operation-factory.util';
|
||||
import { createViewOperationFactory } from 'test/integration/graphql/utils/create-view-operation-factory.util';
|
||||
import { destroyOneOperationFactory } from 'test/integration/graphql/utils/destroy-one-operation-factory.util';
|
||||
import { groupByOperationFactory } from 'test/integration/graphql/utils/group-by-operation-factory.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
|
||||
import { createOneCoreViewFilter } from 'test/integration/metadata/suites/view-filter/utils/create-one-core-view-filter.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test';
|
||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||
|
||||
@@ -419,17 +419,16 @@ describe('group-by resolvers (integration)', () => {
|
||||
);
|
||||
|
||||
// create a view with a filter: city eq cityToKeep
|
||||
const createViewResponse = await makeGraphqlAPIRequest(
|
||||
createViewOperationFactory({
|
||||
data: {
|
||||
name: 'People View City Keep',
|
||||
objectMetadataId: personObjectMetadataId,
|
||||
icon: 'Icon123',
|
||||
},
|
||||
}),
|
||||
);
|
||||
const { data: createViewData } = await createOneCoreView({
|
||||
input: {
|
||||
name: 'People View City Keep',
|
||||
objectMetadataId: personObjectMetadataId,
|
||||
icon: 'Icon123',
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
viewId = createViewResponse.body.data.createCoreView.id as string;
|
||||
viewId = createViewData.createCoreView.id;
|
||||
|
||||
// create a filter group and a filter for the view
|
||||
const viewFilterGroupResponse = await makeGraphqlAPIRequest(
|
||||
@@ -510,18 +509,17 @@ describe('group-by resolvers (integration)', () => {
|
||||
);
|
||||
|
||||
// create a view with any field filter
|
||||
const createViewResponse = await makeGraphqlAPIRequest(
|
||||
createViewOperationFactory({
|
||||
data: {
|
||||
name: 'People View City Keep',
|
||||
objectMetadataId: personObjectMetadataId,
|
||||
icon: 'Icon123',
|
||||
anyFieldFilterValue: cityA,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const { data: createViewData } = await createOneCoreView({
|
||||
input: {
|
||||
name: 'People View City Keep',
|
||||
objectMetadataId: personObjectMetadataId,
|
||||
icon: 'Icon123',
|
||||
anyFieldFilterValue: cityA,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
viewId = createViewResponse.body.data.createCoreView.id as string;
|
||||
viewId = createViewData.createCoreView.id;
|
||||
|
||||
const response = await makeGraphqlAPIRequest(
|
||||
groupByOperationFactory({
|
||||
|
||||
+23
-24
@@ -1,9 +1,7 @@
|
||||
import { createViewOperationFactory } from 'test/integration/graphql/utils/create-view-operation-factory.util';
|
||||
import { assertGraphQLSuccessfulResponse } from 'test/integration/graphql/utils/graphql-test-assertions.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import {
|
||||
assertViewStructure,
|
||||
cleanupViewRecords,
|
||||
@@ -56,30 +54,29 @@ describe('Create core view', () => {
|
||||
});
|
||||
|
||||
it('should create a new view with all properties', async () => {
|
||||
const input = {
|
||||
const { data, errors } = await createOneCoreView({
|
||||
input: {
|
||||
name: 'Kanban View',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
icon: 'IconDeal',
|
||||
type: ViewType.KANBAN,
|
||||
position: 1,
|
||||
isCompact: true,
|
||||
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(errors).toBeUndefined();
|
||||
assertViewStructure(data.createCoreView, {
|
||||
name: 'Kanban View',
|
||||
objectMetadataId: testObjectMetadataId,
|
||||
icon: 'IconDeal',
|
||||
type: ViewType.KANBAN,
|
||||
key: null,
|
||||
icon: 'IconDeal',
|
||||
position: 1,
|
||||
isCompact: true,
|
||||
openRecordIn: ViewOpenRecordIn.SIDE_PANEL,
|
||||
};
|
||||
|
||||
const operation = createViewOperationFactory({ data: input });
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
assertViewStructure(response.body.data.createCoreView, {
|
||||
name: input.name,
|
||||
objectMetadataId: input.objectMetadataId,
|
||||
type: input.type,
|
||||
key: null,
|
||||
icon: input.icon,
|
||||
position: input.position,
|
||||
isCompact: input.isCompact,
|
||||
openRecordIn: input.openRecordIn,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -90,11 +87,13 @@ describe('Create core view', () => {
|
||||
icon: 'IconList',
|
||||
};
|
||||
|
||||
const operation = createViewOperationFactory({ data: input });
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
const { data, errors } = await createOneCoreView({
|
||||
input,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
assertViewStructure(response.body.data.createCoreView, {
|
||||
expect(errors).toBeUndefined();
|
||||
assertViewStructure(data.createCoreView, {
|
||||
name: input.name,
|
||||
objectMetadataId: input.objectMetadataId,
|
||||
icon: input.icon,
|
||||
|
||||
+18
-18
@@ -1,16 +1,12 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { deleteViewOperationFactory } from 'test/integration/graphql/utils/delete-view-operation-factory.util';
|
||||
import { findViewOperationFactory } from 'test/integration/graphql/utils/find-view-operation-factory.util';
|
||||
import {
|
||||
assertGraphQLErrorResponseWithSnapshot,
|
||||
assertGraphQLSuccessfulResponse,
|
||||
} from 'test/integration/graphql/utils/graphql-test-assertions.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { cleanupViewRecords } from 'test/integration/utils/view-test.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import { deleteOneCoreView } from 'test/integration/metadata/suites/view/utils/delete-one-core-view.util';
|
||||
import { findOneCoreView } from 'test/integration/metadata/suites/view/utils/find-one-core-view.util';
|
||||
import { cleanupViewRecords } from 'test/integration/utils/view-test.util';
|
||||
|
||||
describe('Delete core view', () => {
|
||||
let testObjectMetadataId: string;
|
||||
@@ -67,24 +63,28 @@ describe('Delete core view', () => {
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const deleteOperation = deleteViewOperationFactory({ viewId: view.id });
|
||||
const deleteResponse = await makeGraphqlAPIRequest(deleteOperation);
|
||||
const { data: deleteData, errors: deleteErrors } = await deleteOneCoreView({
|
||||
viewId: view.id,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
assertGraphQLSuccessfulResponse(deleteResponse);
|
||||
expect(deleteResponse.body.data.deleteCoreView).toBe(true);
|
||||
expect(deleteErrors).toBeUndefined();
|
||||
expect(deleteData.deleteCoreView).toBe(true);
|
||||
|
||||
const getOperation = findViewOperationFactory({ viewId: view.id });
|
||||
const getResponse = await makeGraphqlAPIRequest(getOperation);
|
||||
const { data: getData } = await findOneCoreView({
|
||||
viewId: view.id,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(getResponse.body.data.getCoreView).toBeNull();
|
||||
expect(getData.getCoreView).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw an error when deleting non-existent view', async () => {
|
||||
const operation = deleteViewOperationFactory({
|
||||
const { errors } = await deleteOneCoreView({
|
||||
viewId: TEST_NOT_EXISTING_VIEW_ID,
|
||||
expectToFail: true,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponseWithSnapshot(response);
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
+28
-23
@@ -1,17 +1,13 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import { deleteViewOperationFactory } from 'test/integration/graphql/utils/delete-view-operation-factory.util';
|
||||
import { destroyViewOperationFactory } from 'test/integration/graphql/utils/destroy-view-operation-factory.util';
|
||||
import { findViewOperationFactory } from 'test/integration/graphql/utils/find-view-operation-factory.util';
|
||||
import {
|
||||
assertGraphQLErrorResponseWithSnapshot,
|
||||
assertGraphQLSuccessfulResponse,
|
||||
} from 'test/integration/graphql/utils/graphql-test-assertions.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { cleanupViewRecords } from 'test/integration/utils/view-test.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import { deleteOneCoreView } from 'test/integration/metadata/suites/view/utils/delete-one-core-view.util';
|
||||
import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util';
|
||||
import { findOneCoreView } from 'test/integration/metadata/suites/view/utils/find-one-core-view.util';
|
||||
import { cleanupViewRecords } from 'test/integration/utils/view-test.util';
|
||||
|
||||
describe('Destroy core view', () => {
|
||||
let testObjectMetadataId: string;
|
||||
@@ -56,7 +52,7 @@ describe('Destroy core view', () => {
|
||||
await cleanupViewRecords();
|
||||
});
|
||||
|
||||
it('should destroy an existing view', async () => {
|
||||
it.only('should destroy an existing view', async () => {
|
||||
const {
|
||||
data: { createCoreView: view },
|
||||
} = await createOneCoreView({
|
||||
@@ -67,29 +63,38 @@ describe('Destroy core view', () => {
|
||||
},
|
||||
expectToFail: false,
|
||||
});
|
||||
const deleteOperation = deleteViewOperationFactory({ viewId: view.id });
|
||||
const deleteResponse = await makeGraphqlAPIRequest(deleteOperation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(deleteResponse);
|
||||
const { data: deleteData, errors: deleteErrors } = await deleteOneCoreView({
|
||||
viewId: view.id,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const destroyOperation = destroyViewOperationFactory({ viewId: view.id });
|
||||
const destroyResponse = await makeGraphqlAPIRequest(destroyOperation);
|
||||
expect(deleteErrors).toBeUndefined();
|
||||
expect(deleteData.deleteCoreView).toBe(true);
|
||||
|
||||
assertGraphQLSuccessfulResponse(destroyResponse);
|
||||
expect(destroyResponse.body.data.destroyCoreView).toBe(true);
|
||||
const { data: destroyData, errors: destroyErrors } =
|
||||
await destroyOneCoreView({
|
||||
viewId: view.id,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const getOperation = findViewOperationFactory({ viewId: view.id });
|
||||
const getResponse = await makeGraphqlAPIRequest(getOperation);
|
||||
expect(destroyErrors).toBeUndefined();
|
||||
expect(destroyData.destroyCoreView).toBe(true);
|
||||
|
||||
expect(getResponse.body.data.getCoreView).toBeNull();
|
||||
const { data: getData } = await findOneCoreView({
|
||||
viewId: view.id,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(getData.getCoreView).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw an error when destroying non-existent view', async () => {
|
||||
const operation = destroyViewOperationFactory({
|
||||
const { errors } = await destroyOneCoreView({
|
||||
viewId: TEST_NOT_EXISTING_VIEW_ID,
|
||||
expectToFail: true,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponseWithSnapshot(response);
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
+22
-24
@@ -1,15 +1,10 @@
|
||||
import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants';
|
||||
import {
|
||||
assertGraphQLErrorResponseWithSnapshot,
|
||||
assertGraphQLSuccessfulResponse,
|
||||
} from 'test/integration/graphql/utils/graphql-test-assertions.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { updateViewOperationFactory } from 'test/integration/graphql/utils/update-view-operation-factory.util';
|
||||
import { updateViewData } from 'test/integration/graphql/utils/view-data-factory.util';
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import { updateOneCoreView } from 'test/integration/metadata/suites/view/utils/update-one-core-view.util';
|
||||
import { cleanupViewRecords } from 'test/integration/utils/view-test.util';
|
||||
|
||||
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
|
||||
@@ -71,32 +66,35 @@ describe('Update core view', () => {
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const updateInput = updateViewData({
|
||||
const updateInput = {
|
||||
id: view.id,
|
||||
name: 'Updated View',
|
||||
type: ViewType.KANBAN,
|
||||
isCompact: true,
|
||||
};
|
||||
|
||||
const { data, errors } = await updateOneCoreView({
|
||||
viewId: view.id,
|
||||
input: updateInput,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(errors).toBeUndefined();
|
||||
expect(data.updateCoreView).toMatchObject({
|
||||
id: view.id,
|
||||
name: 'Updated View',
|
||||
type: ViewType.KANBAN,
|
||||
isCompact: true,
|
||||
});
|
||||
|
||||
const operation = updateViewOperationFactory({
|
||||
viewId: view.id,
|
||||
data: updateInput,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLSuccessfulResponse(response);
|
||||
expect(response.body.data.updateCoreView).toMatchObject({
|
||||
id: view.id,
|
||||
...updateInput,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when updating non-existent view', async () => {
|
||||
const operation = updateViewOperationFactory({
|
||||
const { errors } = await updateOneCoreView({
|
||||
viewId: TEST_NOT_EXISTING_VIEW_ID,
|
||||
data: { name: 'Non-existent View' },
|
||||
input: { id: TEST_NOT_EXISTING_VIEW_ID, name: 'Non-existent View' },
|
||||
expectToFail: true,
|
||||
});
|
||||
const response = await makeGraphqlAPIRequest(operation);
|
||||
|
||||
assertGraphQLErrorResponseWithSnapshot(response);
|
||||
expectOneNotInternalServerErrorSnapshot({ errors });
|
||||
});
|
||||
});
|
||||
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
|
||||
|
||||
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
|
||||
type CreateViewOperationFactoryParams = {
|
||||
gqlFields?: string;
|
||||
data?: Partial<ViewEntity>;
|
||||
};
|
||||
|
||||
export const createViewOperationFactory = ({
|
||||
gqlFields = VIEW_GQL_FIELDS,
|
||||
data = {},
|
||||
}: CreateViewOperationFactoryParams = {}) => ({
|
||||
query: gql`
|
||||
mutation CreateCoreView($input: CreateViewInput!) {
|
||||
createCoreView(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: data,
|
||||
},
|
||||
});
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
|
||||
|
||||
export const findViewsOperationFactory = ({
|
||||
gqlFields = VIEW_GQL_FIELDS,
|
||||
objectMetadataId,
|
||||
}: {
|
||||
gqlFields?: string;
|
||||
objectMetadataId?: string;
|
||||
} = {}) => ({
|
||||
query: gql`
|
||||
query GetCoreViews($objectMetadataId: String) {
|
||||
getCoreViews(objectMetadataId: $objectMetadataId) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: objectMetadataId ? { objectMetadataId } : {},
|
||||
});
|
||||
@@ -1,34 +1,36 @@
|
||||
import { type GraphQLResponse } from 'test/integration/graphql/utils/graphql-test-assertions.util';
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
|
||||
import { type CreateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/create-view.input';
|
||||
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
|
||||
import { createViewOperationFactory } from './create-view-operation-factory.util';
|
||||
import { createViewData } from './view-data-factory.util';
|
||||
|
||||
interface CreateViewResponse extends Record<string, unknown> {
|
||||
createCoreView: ViewEntity;
|
||||
}
|
||||
|
||||
export const createTestViewWithGraphQL = async (
|
||||
overrides: Partial<ViewEntity> = {},
|
||||
): Promise<ViewEntity> => {
|
||||
const input = createViewData(overrides);
|
||||
const viewData = createViewData(overrides);
|
||||
const input: CreateViewInput = {
|
||||
name: viewData.name,
|
||||
objectMetadataId: viewData.objectMetadataId as string,
|
||||
icon: viewData.icon,
|
||||
type: viewData.type,
|
||||
position: viewData.position,
|
||||
isCompact: viewData.isCompact,
|
||||
openRecordIn: viewData.openRecordIn,
|
||||
};
|
||||
|
||||
const operation = createViewOperationFactory({ data: input });
|
||||
const response = (await makeGraphqlAPIRequest(
|
||||
operation,
|
||||
)) as GraphQLResponse<CreateViewResponse>;
|
||||
const { data, errors } = await createOneCoreView({
|
||||
input,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
if (response.body.errors) {
|
||||
throw new Error(
|
||||
`Failed to create test view: ${JSON.stringify(response.body.errors)}`,
|
||||
);
|
||||
if (errors) {
|
||||
throw new Error(`Failed to create test view: ${JSON.stringify(errors)}`);
|
||||
}
|
||||
|
||||
if (!response.body.data) {
|
||||
if (!data) {
|
||||
throw new Error('No data returned from createTestViewWithGraphQL');
|
||||
}
|
||||
|
||||
return response.body.data.createCoreView;
|
||||
return data.createCoreView;
|
||||
};
|
||||
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 MULTI_SELECT should handle adding new options while maintaining existing view filter 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_0",
|
||||
"OPTION_1",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 MULTI_SELECT should handle no changes update of options while maintaining existing view filter values 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_0",
|
||||
"OPTION_1",
|
||||
"OPTION_2",
|
||||
"OPTION_3",
|
||||
"OPTION_4",
|
||||
"OPTION_5",
|
||||
"OPTION_6",
|
||||
"OPTION_7",
|
||||
"OPTION_8",
|
||||
"OPTION_9",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 MULTI_SELECT should handle partial deletion of selected options in view filter 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_4",
|
||||
"OPTION_5",
|
||||
"OPTION_6",
|
||||
"OPTION_7",
|
||||
"OPTION_8",
|
||||
"OPTION_9",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 MULTI_SELECT should handle reordering of options while maintaining view filter values 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_0",
|
||||
"OPTION_1",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 MULTI_SELECT should throw error if view filter value is not a stringified JSON array 1`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
"code": "INTERNAL_SERVER_ERROR",
|
||||
"exceptionEventId": "mocked-exception-id",
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
},
|
||||
"message": "Unexpected invalid view filter value for filter 20202020-e3b5-4fa7-85aa-9b1950fc7bf5",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 MULTI_SELECT should update related multi selected options view filter 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_0_UPDATED",
|
||||
"OPTION_1",
|
||||
"OPTION_2_UPDATED",
|
||||
"OPTION_3",
|
||||
"OPTION_4_UPDATED",
|
||||
"OPTION_5",
|
||||
"OPTION_6_UPDATED",
|
||||
"OPTION_7",
|
||||
"OPTION_8_UPDATED",
|
||||
"OPTION_9",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 MULTI_SELECT should update related solo selected option view filter 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_5_UPDATED",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 SELECT should handle adding new options while maintaining existing view filter 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_0",
|
||||
"OPTION_1",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 SELECT should handle no changes update of options while maintaining existing view filter values 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_0",
|
||||
"OPTION_1",
|
||||
"OPTION_2",
|
||||
"OPTION_3",
|
||||
"OPTION_4",
|
||||
"OPTION_5",
|
||||
"OPTION_6",
|
||||
"OPTION_7",
|
||||
"OPTION_8",
|
||||
"OPTION_9",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 SELECT should handle partial deletion of selected options in view filter 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_4",
|
||||
"OPTION_5",
|
||||
"OPTION_6",
|
||||
"OPTION_7",
|
||||
"OPTION_8",
|
||||
"OPTION_9",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 SELECT should handle reordering of options while maintaining view filter values 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_0",
|
||||
"OPTION_1",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 SELECT should throw error if view filter value is not a stringified JSON array 1`] = `
|
||||
[
|
||||
{
|
||||
"extensions": {
|
||||
"code": "INTERNAL_SERVER_ERROR",
|
||||
"exceptionEventId": "mocked-exception-id",
|
||||
"userFriendlyMessage": "An error occurred.",
|
||||
},
|
||||
"message": "Unexpected invalid view filter value for filter 20202020-e3b5-4fa7-85aa-9b1950fc7bf5",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 SELECT should update related multi selected options view filter 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_0_UPDATED",
|
||||
"OPTION_1",
|
||||
"OPTION_2_UPDATED",
|
||||
"OPTION_3",
|
||||
"OPTION_4_UPDATED",
|
||||
"OPTION_5",
|
||||
"OPTION_6_UPDATED",
|
||||
"OPTION_7",
|
||||
"OPTION_8_UPDATED",
|
||||
"OPTION_9",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`update-one-field-metadata-view-filters-side-effect-v2 SELECT should update related solo selected option view filter 1`] = `
|
||||
{
|
||||
"operand": "IS",
|
||||
"positionInViewFilterGroup": null,
|
||||
"subFieldName": null,
|
||||
"value": [
|
||||
"OPTION_5_UPDATED",
|
||||
],
|
||||
"viewFilterGroupId": null,
|
||||
}
|
||||
`;
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { createOneCoreViewFilter } from 'test/integration/metadata/suites/view-filter/utils/create-one-core-view-filter.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import { findViewFilterWithRestApi } from 'test/integration/rest/utils/view-rest-api.util';
|
||||
import {
|
||||
eachTestingContextFilter,
|
||||
type EachTestingContext,
|
||||
} from 'twenty-shared/testing';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
ViewFilterOperand,
|
||||
type EnumFieldMetadataType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
type FieldMetadataComplexOption,
|
||||
type FieldMetadataDefaultOption,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { type ViewFilterValue } from 'src/engine/metadata-modules/view-filter/types/view-filter-value.type';
|
||||
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
|
||||
|
||||
type Option = FieldMetadataDefaultOption | FieldMetadataComplexOption;
|
||||
|
||||
const generateOption = (index: number): Option => ({
|
||||
label: `Option ${index}`,
|
||||
value: `OPTION_${index}`,
|
||||
color: 'green',
|
||||
position: index,
|
||||
});
|
||||
const generateOptions = (length: number) =>
|
||||
Array.from({ length }, (_value, index) => generateOption(index));
|
||||
const fakeOptionUpdate = ({ value, label, ...option }: Option) => ({
|
||||
...option,
|
||||
value: `${value}_UPDATED`,
|
||||
label: `${label} updated`,
|
||||
});
|
||||
|
||||
const ALL_OPTIONS = generateOptions(10);
|
||||
|
||||
const isEven = (_value: unknown, index: number) => index % 2 === 0;
|
||||
|
||||
type ViewFilterUpdate = {
|
||||
value: string[];
|
||||
};
|
||||
|
||||
type FieldMetadataOptionsAndType = {
|
||||
options: Option[];
|
||||
type: EnumFieldMetadataType;
|
||||
};
|
||||
|
||||
type TestCase = EachTestingContext<{
|
||||
fieldMetadata?: FieldMetadataOptionsAndType;
|
||||
createViewFilter?: ViewFilterUpdate;
|
||||
updateOptions: (
|
||||
options: FieldMetadataDefaultOption[] | FieldMetadataComplexOption[],
|
||||
) => FieldMetadataDefaultOption[] | FieldMetadataComplexOption[];
|
||||
expected?: null;
|
||||
}>;
|
||||
const testFieldMetadataType: EnumFieldMetadataType[] = [
|
||||
FieldMetadataType.SELECT,
|
||||
FieldMetadataType.MULTI_SELECT,
|
||||
];
|
||||
|
||||
describe('update-one-field-metadata-view-filters-side-effect-v2', () => {
|
||||
let idToDelete: string;
|
||||
|
||||
const createObjectSelectFieldAndView = async ({
|
||||
options,
|
||||
type: fieldMetadataType,
|
||||
}: FieldMetadataOptionsAndType) => {
|
||||
const singular = faker.lorem.words();
|
||||
const plural = singular + faker.lorem.word();
|
||||
const {
|
||||
data: { createOneObject },
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: getMockCreateObjectInput({
|
||||
labelSingular: singular,
|
||||
labelPlural: plural,
|
||||
nameSingular: singular.split(' ').join(''),
|
||||
namePlural: plural.split(' ').join(''),
|
||||
isLabelSyncedWithName: false,
|
||||
}),
|
||||
});
|
||||
|
||||
idToDelete = createOneObject.id;
|
||||
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata<typeof fieldMetadataType>({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createOneObject.id,
|
||||
type: fieldMetadataType,
|
||||
name: 'testName',
|
||||
label: 'Test name',
|
||||
isLabelSyncedWithName: true,
|
||||
options,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
options
|
||||
`,
|
||||
});
|
||||
|
||||
const {
|
||||
data: { createCoreView: createdView },
|
||||
} = await createOneCoreView({
|
||||
input: {
|
||||
id: faker.string.uuid(),
|
||||
icon: '123Icon',
|
||||
name: 'Test View',
|
||||
objectMetadataId: createOneObject.id,
|
||||
type: ViewType.TABLE,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
return { createOneObject, createOneField, createdView };
|
||||
};
|
||||
|
||||
afterEach(async () => {
|
||||
if (isDefined(idToDelete)) {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: idToDelete,
|
||||
updatePayload: {
|
||||
isActive: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: idToDelete },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe.each(testFieldMetadataType)('%s', (fieldType) => {
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
title:
|
||||
'should delete related view filter if all select field options got deleted',
|
||||
context: {
|
||||
updateOptions: () => generateOptions(3),
|
||||
expected: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should update related multi selected options view filter',
|
||||
context: {
|
||||
updateOptions: (options) =>
|
||||
options.map((option, index) =>
|
||||
isEven(option, index) ? fakeOptionUpdate(option) : option,
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should update related solo selected option view filter',
|
||||
context: {
|
||||
createViewFilter: {
|
||||
value: [ALL_OPTIONS[5].value],
|
||||
},
|
||||
updateOptions: (options) => [fakeOptionUpdate(options[5])],
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should handle partial deletion of selected options in view filter',
|
||||
context: {
|
||||
updateOptions: (options) => options.slice(4),
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should handle reordering of options while maintaining view filter values',
|
||||
context: {
|
||||
createViewFilter: {
|
||||
value: ALL_OPTIONS.slice(0, 2).map((option) => option.value),
|
||||
},
|
||||
updateOptions: (options) => [...options].reverse(),
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should handle no changes update of options while maintaining existing view filter values',
|
||||
context: {
|
||||
updateOptions: (options) => options,
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should handle adding new options while maintaining existing view filter',
|
||||
context: {
|
||||
fieldMetadata: {
|
||||
options: ALL_OPTIONS.slice(0, 5),
|
||||
type: fieldType,
|
||||
},
|
||||
createViewFilter: {
|
||||
value: ALL_OPTIONS.slice(0, 2).map((option) => option.value),
|
||||
},
|
||||
updateOptions: (options) => [
|
||||
...options,
|
||||
...generateOptions(6).slice(5),
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(eachTestingContextFilter(testCases))(
|
||||
'$title',
|
||||
async ({
|
||||
context: {
|
||||
expected,
|
||||
createViewFilter = {
|
||||
value: ALL_OPTIONS.map((option) => option.value),
|
||||
},
|
||||
fieldMetadata = { options: ALL_OPTIONS, type: fieldType },
|
||||
updateOptions,
|
||||
},
|
||||
}) => {
|
||||
const { createOneField, createdView } =
|
||||
await createObjectSelectFieldAndView(fieldMetadata);
|
||||
|
||||
const {
|
||||
data: { createCoreViewFilter: createdViewFilter },
|
||||
} = await createOneCoreViewFilter({
|
||||
input: {
|
||||
viewId: createdView.id,
|
||||
fieldMetadataId: createOneField.id,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: createViewFilter.value,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
const optionsWithIds = createOneField.options;
|
||||
|
||||
if (!isDefined(optionsWithIds)) {
|
||||
throw new Error('optionsWithIds is not defined');
|
||||
}
|
||||
const updatedOptions = updateOptions(optionsWithIds);
|
||||
|
||||
await updateOneFieldMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: createOneField.id,
|
||||
updatePayload: {
|
||||
options: updatedOptions,
|
||||
},
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
options
|
||||
`,
|
||||
});
|
||||
|
||||
const updatedViewFilter = await findViewFilterWithRestApi(
|
||||
createdViewFilter.id,
|
||||
);
|
||||
|
||||
if (expected !== undefined) {
|
||||
expect(updatedViewFilter).toBe(expected);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDefined(updatedViewFilter)) {
|
||||
throw new Error(
|
||||
'updatedViewFilter is not defined but should be at this point',
|
||||
);
|
||||
}
|
||||
|
||||
expect(updatedViewFilter.value).not.toBeNull();
|
||||
if (updatedViewFilter.value === null) {
|
||||
throw new Error('Invariant parsedValue should not be null');
|
||||
}
|
||||
expect(updatedOptions.map((option) => option.value)).toEqual(
|
||||
expect.arrayContaining(updatedViewFilter.value as string[]),
|
||||
);
|
||||
|
||||
expect({
|
||||
value: updatedViewFilter.value,
|
||||
operand: updatedViewFilter.operand,
|
||||
viewFilterGroupId: updatedViewFilter.viewFilterGroupId,
|
||||
positionInViewFilterGroup:
|
||||
updatedViewFilter.positionInViewFilterGroup,
|
||||
subFieldName: updatedViewFilter.subFieldName,
|
||||
}).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
|
||||
// Note these test exists only because we do not validate the view filter value on creation/update
|
||||
// Should be removed after https://github.com/twentyhq/core-team-issues/issues/1009 completion
|
||||
const failingTestCases: EachTestingContext<{
|
||||
createViewFilterValue: unknown;
|
||||
}>[] = [
|
||||
{
|
||||
title:
|
||||
'should throw error if view filter value is not a stringified JSON array',
|
||||
context: {
|
||||
createViewFilterValue: JSON.stringify(
|
||||
'not an array stringified json',
|
||||
),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(eachTestingContextFilter(failingTestCases))(
|
||||
'$title',
|
||||
async ({ context: { createViewFilterValue } }) => {
|
||||
const { createOneField, createdView } =
|
||||
await createObjectSelectFieldAndView({
|
||||
options: ALL_OPTIONS,
|
||||
type: fieldType,
|
||||
});
|
||||
|
||||
const viewFilterId = '20202020-e3b5-4fa7-85aa-9b1950fc7bf5';
|
||||
|
||||
await createOneCoreViewFilter({
|
||||
input: {
|
||||
id: viewFilterId,
|
||||
viewId: createdView.id,
|
||||
fieldMetadataId: createOneField.id,
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: createViewFilterValue as unknown as ViewFilterValue,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: `
|
||||
id
|
||||
`,
|
||||
});
|
||||
|
||||
const optionsWithIds = createOneField.options;
|
||||
|
||||
if (!isDefined(optionsWithIds)) {
|
||||
throw new Error('optionsWithIds is not defined');
|
||||
}
|
||||
const updatePayload = {
|
||||
options: optionsWithIds.map((option) => fakeOptionUpdate(option)),
|
||||
};
|
||||
const { errors, data } = await updateOneFieldMetadata({
|
||||
expectToFail: true,
|
||||
input: {
|
||||
idToUpdate: createOneField.id,
|
||||
updatePayload,
|
||||
},
|
||||
gqlFields: `
|
||||
id
|
||||
options
|
||||
`,
|
||||
});
|
||||
|
||||
expect(data).toBeNull();
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
+452
@@ -0,0 +1,452 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util';
|
||||
import { updateOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata.util';
|
||||
import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util';
|
||||
import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { getMockCreateObjectInput } from 'test/integration/metadata/suites/object-metadata/utils/generate-mock-create-object-metadata-input';
|
||||
import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util';
|
||||
import { createOneCoreViewGroup } from 'test/integration/metadata/suites/view-group/utils/create-one-core-view-group.util';
|
||||
import { findCoreViewGroups } from 'test/integration/metadata/suites/view-group/utils/find-core-view-groups.util';
|
||||
import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
type EnumFieldMetadataType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
type FieldMetadataComplexOption,
|
||||
type FieldMetadataDefaultOption,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
|
||||
|
||||
type Option = FieldMetadataDefaultOption | FieldMetadataComplexOption;
|
||||
|
||||
const generateOption = (index: number): Option => ({
|
||||
label: `Option ${index}`,
|
||||
value: `OPTION_${index}`,
|
||||
color: 'green',
|
||||
position: index,
|
||||
});
|
||||
|
||||
const generateOptions = (length: number) =>
|
||||
Array.from({ length }, (_value, index) => generateOption(index));
|
||||
|
||||
const fakeOptionUpdate = ({ value, label, ...option }: Option) => ({
|
||||
...option,
|
||||
value: `${value}_UPDATED`,
|
||||
label: `${label} updated`,
|
||||
});
|
||||
|
||||
const testFieldMetadataTypes: EnumFieldMetadataType[] = [
|
||||
FieldMetadataType.SELECT,
|
||||
FieldMetadataType.MULTI_SELECT,
|
||||
];
|
||||
|
||||
describe('update-one-field-metadata-view-groups-side-effect-v2', () => {
|
||||
let objectMetadataIdToDelete: string;
|
||||
|
||||
const createObjectWithSelectFieldAndView = async (
|
||||
fieldType: EnumFieldMetadataType,
|
||||
initialOptions: Option[],
|
||||
) => {
|
||||
const singular = 'sideEffect';
|
||||
const plural = 'sideEffects';
|
||||
|
||||
const {
|
||||
data: { createOneObject },
|
||||
} = await createOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: getMockCreateObjectInput({
|
||||
labelSingular: singular,
|
||||
labelPlural: plural,
|
||||
nameSingular: singular,
|
||||
namePlural: plural,
|
||||
isLabelSyncedWithName: false,
|
||||
}),
|
||||
});
|
||||
|
||||
objectMetadataIdToDelete = createOneObject.id;
|
||||
|
||||
const {
|
||||
data: { createOneField },
|
||||
} = await createOneFieldMetadata<typeof fieldType>({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
objectMetadataId: createOneObject.id,
|
||||
type: fieldType,
|
||||
name: 'statusField',
|
||||
label: 'Status Field',
|
||||
isLabelSyncedWithName: true,
|
||||
options: initialOptions,
|
||||
},
|
||||
gqlFields: 'id options',
|
||||
});
|
||||
|
||||
const {
|
||||
data: { createCoreView: view },
|
||||
} = await createOneCoreView({
|
||||
input: {
|
||||
id: faker.string.uuid(),
|
||||
icon: 'IconTable',
|
||||
name: 'Test View',
|
||||
objectMetadataId: createOneObject.id,
|
||||
type: ViewType.TABLE,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: 'id',
|
||||
});
|
||||
|
||||
return {
|
||||
objectMetadataId: createOneObject.id,
|
||||
fieldMetadataId: createOneField.id,
|
||||
fieldOptions: createOneField.options ?? [],
|
||||
viewId: view.id,
|
||||
};
|
||||
};
|
||||
|
||||
afterEach(async () => {
|
||||
await updateOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
idToUpdate: objectMetadataIdToDelete,
|
||||
updatePayload: {
|
||||
isActive: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
await deleteOneObjectMetadata({
|
||||
expectToFail: false,
|
||||
input: { idToDelete: objectMetadataIdToDelete },
|
||||
});
|
||||
});
|
||||
|
||||
describe.each(testFieldMetadataTypes)('%s field type', (fieldType) => {
|
||||
it('should delete all view groups when all enum field options are deleted', async () => {
|
||||
const initialOptions = generateOptions(3);
|
||||
const { fieldMetadataId, viewId } =
|
||||
await createObjectWithSelectFieldAndView(fieldType, initialOptions);
|
||||
|
||||
for (const [index, option] of initialOptions.entries()) {
|
||||
await createOneCoreViewGroup({
|
||||
input: {
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
fieldValue: option.value,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: 'id fieldValue',
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
data: { getCoreViewGroups: initialViewGroups },
|
||||
} = await findCoreViewGroups({
|
||||
viewId,
|
||||
gqlFields: 'id fieldValue fieldMetadataId',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
initialViewGroups.filter((vg) => vg.fieldMetadataId === fieldMetadataId)
|
||||
.length,
|
||||
).toBe(3);
|
||||
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
idToUpdate: fieldMetadataId,
|
||||
updatePayload: {
|
||||
options: [
|
||||
{
|
||||
color: 'blue',
|
||||
label: 'New option label',
|
||||
position: 42,
|
||||
value: 'NEW_OPTION_VALUE',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
gqlFields: 'id options',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const {
|
||||
data: { getCoreViewGroups: updatedViewGroups },
|
||||
} = await findCoreViewGroups({
|
||||
viewId,
|
||||
gqlFields: 'id fieldValue fieldMetadataId',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
updatedViewGroups.filter((vg) => vg.fieldMetadataId === fieldMetadataId)
|
||||
.length,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it('should update view group when option value is updated', async () => {
|
||||
const initialOptions = generateOptions(1);
|
||||
const { fieldMetadataId, fieldOptions, viewId } =
|
||||
await createObjectWithSelectFieldAndView(fieldType, initialOptions);
|
||||
|
||||
await createOneCoreViewGroup({
|
||||
input: {
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
fieldValue: initialOptions[0].value,
|
||||
isVisible: true,
|
||||
position: 0,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: 'id fieldValue',
|
||||
});
|
||||
|
||||
const updatedOptions = [fakeOptionUpdate(fieldOptions[0])];
|
||||
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
idToUpdate: fieldMetadataId,
|
||||
updatePayload: {
|
||||
options: updatedOptions,
|
||||
},
|
||||
},
|
||||
gqlFields: 'id options',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const {
|
||||
data: { getCoreViewGroups: updatedViewGroups },
|
||||
} = await findCoreViewGroups({
|
||||
viewId,
|
||||
gqlFields: 'id fieldValue fieldMetadataId',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const viewGroupsForField = updatedViewGroups.filter(
|
||||
(vg) => vg.fieldMetadataId === fieldMetadataId,
|
||||
);
|
||||
|
||||
expect(viewGroupsForField.length).toBe(1);
|
||||
expect(viewGroupsForField[0].fieldValue).toBe(
|
||||
`${initialOptions[0].value}_UPDATED`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should update multiple view groups when multiple option values are updated', async () => {
|
||||
const initialOptions = generateOptions(3);
|
||||
const { fieldMetadataId, fieldOptions, viewId } =
|
||||
await createObjectWithSelectFieldAndView(fieldType, initialOptions);
|
||||
|
||||
for (const [index, option] of initialOptions.entries()) {
|
||||
await createOneCoreViewGroup({
|
||||
input: {
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
fieldValue: option.value,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: 'id fieldValue',
|
||||
});
|
||||
}
|
||||
|
||||
const updatedOptions = fieldOptions.map((opt) => fakeOptionUpdate(opt));
|
||||
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
idToUpdate: fieldMetadataId,
|
||||
updatePayload: {
|
||||
options: updatedOptions,
|
||||
},
|
||||
},
|
||||
gqlFields: 'id options',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const {
|
||||
data: { getCoreViewGroups: updatedViewGroups },
|
||||
} = await findCoreViewGroups({
|
||||
viewId,
|
||||
gqlFields: 'id fieldValue fieldMetadataId',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const viewGroupsForField = updatedViewGroups.filter(
|
||||
(vg) => vg.fieldMetadataId === fieldMetadataId,
|
||||
);
|
||||
|
||||
expect(viewGroupsForField.length).toBe(3);
|
||||
const actualFieldValues = viewGroupsForField
|
||||
.map((vg) => vg.fieldValue)
|
||||
.sort();
|
||||
const expectedFieldValues = initialOptions
|
||||
.map((opt) => `${opt.value}_UPDATED`)
|
||||
.sort();
|
||||
|
||||
expect(actualFieldValues).toEqual(expectedFieldValues);
|
||||
});
|
||||
|
||||
it('should delete specific view groups when their options are removed', async () => {
|
||||
const initialOptions = generateOptions(5);
|
||||
const { fieldMetadataId, fieldOptions, viewId } =
|
||||
await createObjectWithSelectFieldAndView(fieldType, initialOptions);
|
||||
|
||||
for (const [index, option] of initialOptions.entries()) {
|
||||
await createOneCoreViewGroup({
|
||||
input: {
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
fieldValue: option.value,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: 'id fieldValue',
|
||||
});
|
||||
}
|
||||
|
||||
const updatedOptions = fieldOptions.slice(2);
|
||||
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
idToUpdate: fieldMetadataId,
|
||||
updatePayload: {
|
||||
options: updatedOptions,
|
||||
},
|
||||
},
|
||||
gqlFields: 'id options',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const {
|
||||
data: { getCoreViewGroups: updatedViewGroups },
|
||||
} = await findCoreViewGroups({
|
||||
viewId,
|
||||
gqlFields: 'id fieldValue fieldMetadataId',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const viewGroupsForField = updatedViewGroups.filter(
|
||||
(vg) => vg.fieldMetadataId === fieldMetadataId,
|
||||
);
|
||||
|
||||
expect(viewGroupsForField.length).toBe(3);
|
||||
const remainingFieldValues = viewGroupsForField.map(
|
||||
(vg) => vg.fieldValue,
|
||||
);
|
||||
|
||||
expect(remainingFieldValues).not.toContain(initialOptions[0].value);
|
||||
expect(remainingFieldValues).not.toContain(initialOptions[1].value);
|
||||
expect(remainingFieldValues).toContain(initialOptions[2].value);
|
||||
expect(remainingFieldValues).toContain(initialOptions[3].value);
|
||||
expect(remainingFieldValues).toContain(initialOptions[4].value);
|
||||
});
|
||||
|
||||
it('should preserve view groups when no options are changed', async () => {
|
||||
const initialOptions = generateOptions(3);
|
||||
const { fieldMetadataId, fieldOptions, viewId } =
|
||||
await createObjectWithSelectFieldAndView(fieldType, initialOptions);
|
||||
|
||||
for (const [index, option] of initialOptions.entries()) {
|
||||
await createOneCoreViewGroup({
|
||||
input: {
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
fieldValue: option.value,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: 'id fieldValue',
|
||||
});
|
||||
}
|
||||
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
idToUpdate: fieldMetadataId,
|
||||
updatePayload: {
|
||||
options: fieldOptions,
|
||||
},
|
||||
},
|
||||
gqlFields: 'id options',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const {
|
||||
data: { getCoreViewGroups: updatedViewGroups },
|
||||
} = await findCoreViewGroups({
|
||||
viewId,
|
||||
gqlFields: 'id fieldValue fieldMetadataId',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const viewGroupsForField = updatedViewGroups.filter(
|
||||
(vg) => vg.fieldMetadataId === fieldMetadataId,
|
||||
);
|
||||
|
||||
expect(viewGroupsForField.length).toBe(3);
|
||||
const actualFieldValues = viewGroupsForField
|
||||
.map((vg) => vg.fieldValue)
|
||||
.sort();
|
||||
const expectedFieldValues = initialOptions.map((opt) => opt.value).sort();
|
||||
|
||||
expect(actualFieldValues).toEqual(expectedFieldValues);
|
||||
});
|
||||
|
||||
it('should handle adding new options while maintaining existing view groups', async () => {
|
||||
const initialOptions = generateOptions(3);
|
||||
const { fieldMetadataId, fieldOptions, viewId } =
|
||||
await createObjectWithSelectFieldAndView(fieldType, initialOptions);
|
||||
|
||||
for (const [index, option] of initialOptions.entries()) {
|
||||
await createOneCoreViewGroup({
|
||||
input: {
|
||||
viewId,
|
||||
fieldMetadataId,
|
||||
fieldValue: option.value,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
},
|
||||
expectToFail: false,
|
||||
gqlFields: 'id fieldValue',
|
||||
});
|
||||
}
|
||||
|
||||
const newOptions = generateOptions(6).slice(3);
|
||||
const updatedOptions = [...fieldOptions, ...newOptions];
|
||||
|
||||
await updateOneFieldMetadata({
|
||||
input: {
|
||||
idToUpdate: fieldMetadataId,
|
||||
updatePayload: {
|
||||
options: updatedOptions,
|
||||
},
|
||||
},
|
||||
gqlFields: 'id options',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const {
|
||||
data: { getCoreViewGroups: updatedViewGroups },
|
||||
} = await findCoreViewGroups({
|
||||
viewId,
|
||||
gqlFields: 'id fieldValue fieldMetadataId',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const viewGroupsForField = updatedViewGroups.filter(
|
||||
(vg) => vg.fieldMetadataId === fieldMetadataId,
|
||||
);
|
||||
|
||||
expect(viewGroupsForField.length).toBe(6);
|
||||
const actualFieldValues = viewGroupsForField
|
||||
.map((vg) => vg.fieldValue)
|
||||
.sort();
|
||||
const expectedFieldValues = updatedOptions.map((opt) => opt.value).sort();
|
||||
|
||||
expect(actualFieldValues).toEqual(expectedFieldValues);
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const deleteViewOperationFactory = ({ viewId }: { viewId: string }) => ({
|
||||
export const deleteCoreViewQueryFactory = ({ viewId }: { viewId: string }) => ({
|
||||
query: gql`
|
||||
mutation DeleteCoreView($id: String!) {
|
||||
deleteCoreView(id: $id)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { deleteCoreViewQueryFactory } from 'test/integration/metadata/suites/view/utils/delete-core-view-query-factory.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const deleteOneCoreView = async ({
|
||||
viewId,
|
||||
expectToFail,
|
||||
}: {
|
||||
viewId: string;
|
||||
expectToFail?: boolean;
|
||||
}): CommonResponseBody<{
|
||||
deleteCoreView: boolean;
|
||||
}> => {
|
||||
const graphqlOperation = deleteCoreViewQueryFactory({
|
||||
viewId,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View deletion should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View deletion has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const destroyViewOperationFactory = ({
|
||||
export const destroyCoreViewQueryFactory = ({
|
||||
viewId,
|
||||
}: {
|
||||
viewId: string;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { destroyCoreViewQueryFactory } from 'test/integration/metadata/suites/view/utils/destroy-core-view-query-factory.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
export const destroyOneCoreView = async ({
|
||||
viewId,
|
||||
expectToFail,
|
||||
}: {
|
||||
viewId: string;
|
||||
expectToFail?: boolean;
|
||||
}): CommonResponseBody<{
|
||||
destroyCoreView: boolean;
|
||||
}> => {
|
||||
const graphqlOperation = destroyCoreViewQueryFactory({
|
||||
viewId,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View destruction should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View destruction has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
|
||||
|
||||
export const findViewOperationFactory = ({
|
||||
export const findCoreViewQueryFactory = ({
|
||||
gqlFields = VIEW_GQL_FIELDS,
|
||||
viewId,
|
||||
}: {
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { findCoreViewQueryFactory } from 'test/integration/metadata/suites/view/utils/find-core-view-query-factory.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
import { type ViewDTO } from 'src/engine/metadata-modules/view/dtos/view.dto';
|
||||
|
||||
export const findOneCoreView = async ({
|
||||
viewId,
|
||||
gqlFields,
|
||||
expectToFail,
|
||||
}: {
|
||||
viewId: string;
|
||||
gqlFields?: string;
|
||||
expectToFail?: boolean;
|
||||
}): CommonResponseBody<{
|
||||
getCoreView: ViewDTO;
|
||||
}> => {
|
||||
const graphqlOperation = findCoreViewQueryFactory({
|
||||
viewId,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View search should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View search has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
+6
-4
@@ -1,14 +1,16 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { VIEW_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants';
|
||||
|
||||
export const updateViewOperationFactory = ({
|
||||
import { type UpdateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/update-view.input';
|
||||
|
||||
export const updateCoreViewQueryFactory = ({
|
||||
gqlFields = VIEW_GQL_FIELDS,
|
||||
viewId,
|
||||
data = {},
|
||||
input,
|
||||
}: {
|
||||
gqlFields?: string;
|
||||
viewId: string;
|
||||
data?: object;
|
||||
input: UpdateViewInput;
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation UpdateCoreView($id: String!, $input: UpdateViewInput!) {
|
||||
@@ -19,6 +21,6 @@ export const updateViewOperationFactory = ({
|
||||
`,
|
||||
variables: {
|
||||
id: viewId,
|
||||
input: data,
|
||||
input,
|
||||
},
|
||||
});
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
import { updateCoreViewQueryFactory } from 'test/integration/metadata/suites/view/utils/update-core-view-query-factory.util';
|
||||
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
import { type ViewDTO } from 'src/engine/metadata-modules/view/dtos/view.dto';
|
||||
import { type UpdateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/update-view.input';
|
||||
|
||||
export const updateOneCoreView = async ({
|
||||
viewId,
|
||||
input,
|
||||
gqlFields,
|
||||
expectToFail,
|
||||
}: {
|
||||
viewId: string;
|
||||
input: UpdateViewInput;
|
||||
gqlFields?: string;
|
||||
expectToFail?: boolean;
|
||||
}): CommonResponseBody<{
|
||||
updateCoreView: ViewDTO;
|
||||
}> => {
|
||||
const graphqlOperation = updateCoreViewQueryFactory({
|
||||
viewId,
|
||||
input,
|
||||
gqlFields,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
if (expectToFail === true) {
|
||||
warnIfNoErrorButExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View update should have failed but did not',
|
||||
});
|
||||
}
|
||||
|
||||
if (expectToFail === false) {
|
||||
warnIfErrorButNotExpectedToFail({
|
||||
response,
|
||||
errorMessage: 'View update has failed but should not',
|
||||
});
|
||||
}
|
||||
|
||||
return { data: response.body.data, errors: response.body.errors };
|
||||
};
|
||||
Reference in New Issue
Block a user