Migrate view filter group to v2 (#16876)

## Introduction
On the side hanlded PR with a // agents on another repo
Made several iterations to fix behavior and direction
Find below auto-generated PR description

closes https://github.com/twentyhq/core-team-issues/issues/2037

Created generic tooling for entity circular dep checking, will be useful
for permissions validation too @Weiko


## Migrate `viewFilterGroup` entity to v2 flat architecture

### Summary
Migrates the `viewFilterGroup` entity from v1 to the v2 flat entity
architecture, following the established patterns for other v2 entities
like `viewFilter`, `view`, and `viewField`.

### Changes

**Types & Constants**
- Added `FlatViewFilterGroup` and `FlatViewFilterGroupMaps` types
- Added editable properties constant for `viewFilterGroup`
- Registered `viewFilterGroup` in `ALL_METADATA_NAME`,
`ALL_METADATA_RELATION_PROPERTIES`,
`ALL_METADATA_MANY_TO_ONE_RELATIONS`, and related constants

**Cache Service**
- Created `WorkspaceFlatViewFilterGroupMapCacheService` with proper
relation loading for `viewFilters` and `childViewFilterGroups`
- Updated `WorkspaceFlatViewMapCacheService` to load `viewFilterGroups`
relation

**Builder & Validator**
- Created `WorkspaceMigrationV2ViewFilterGroupActionsBuilderService`
- Created `FlatViewFilterGroupValidatorService` with creation, update,
and deletion validation
- Integrated validation into the orchestrator service (runs before
`viewFilter` validation)

**Action Handlers**
- Created create, update, and delete action handlers for
`viewFilterGroup`

**Service Migration**
- Rewrote `ViewFilterGroupService` to use v2 migration pattern with
`WorkspaceMigrationValidateBuildAndRunService`
- Created utility functions for transforming DTOs to flat entities

**Database Migration**
- Added migration to make `parentViewFilterGroupId` foreign key
deferrable (handles self-referential parent/child insertions)

**ViewFilter Integration**
- Added `viewFilterGroupId` validation in
`FlatViewFilterValidatorService`
- Updated `viewFilter` many-to-one relations to include
`viewFilterGroup`

**Tests**
- Added integration tests for successful creation, update, deletion, and
destruction
- Added failing test cases for non-existent entities and invalid
references
- Added failing test for `viewFilter` creation with non-existent
`viewFilterGroupId`

### Breaking Changes
None - existing API contracts are preserved.
This commit is contained in:
Paul Rastoin
2026-01-02 14:16:01 +01:00
committed by GitHub
parent 0adbe439d5
commit 98a9ae2a0e
106 changed files with 4648 additions and 439 deletions
@@ -78,10 +78,13 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
workspaceId,
});
const flatViewFilterGroupMaps = createEmptyFlatEntityMaps();
const flatViewFilterMaps = buildStandardFlatViewFilterMetadataMaps({
dependencyFlatEntityMaps: {
flatFieldMetadataMaps,
flatViewMaps,
flatViewFilterGroupMaps,
},
now,
standardObjectMetadataRelatedEntityIds,
@@ -116,6 +116,7 @@ export const createStandardViewFlatMetadata = <
viewFieldIds: [],
viewFilterIds: [],
viewGroupIds: [],
viewFilterGroupIds: [],
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -19,6 +19,7 @@ export const fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseE
summary: {
invalidAgent: 0,
invalidViewFilter: 0,
invalidViewFilterGroup: 0,
invalidObjectMetadata: 0,
invalidView: 0,
invalidViewField: 0,
@@ -1,17 +1,17 @@
import { Injectable } from '@nestjs/common';
import { AllMetadataName } from 'twenty-shared/metadata';
import { type AllMetadataName } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { MetadataFlatEntityAndRelatedFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type MetadataFlatEntityAndRelatedFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
import { createEmptyOrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/constant/empty-orchestrator-actions-report.constant';
import { EMPTY_ORCHESTRATOR_FAILURE_REPORT } from 'src/engine/workspace-manager/workspace-migration-v2/constant/empty-orchestrator-failure-report.constant';
import {
WorkspaceMigrationOrchestratorBuildArgs,
WorkspaceMigrationOrchestratorFailedResult,
WorkspaceMigrationOrchestratorSuccessfulResult,
type WorkspaceMigrationOrchestratorBuildArgs,
type WorkspaceMigrationOrchestratorFailedResult,
type WorkspaceMigrationOrchestratorSuccessfulResult,
} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { aggregateOrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/utils/aggregate-orchestrator-actions-report.util';
import { WorkspaceMigrationV2AgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/workspace-migration-v2-agent-actions-builder.service';
@@ -30,6 +30,7 @@ import { WorkspaceMigrationV2RowLevelPermissionPredicateGroupActionsBuilderServi
import { WorkspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/row-level-permission-predicate/workspace-migration-v2-row-level-permission-predicate-actions-builder.service';
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
import { WorkspaceMigrationV2ViewFilterGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter-group/workspace-migration-v2-view-filter-group-actions-builder.service';
import { WorkspaceMigrationV2ViewFilterActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/workspace-migration-v2-view-filter-actions-builder.service';
import { WorkspaceMigrationV2ViewGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-group/workspace-migration-v2-view-group-actions-builder.service';
import { WorkspaceMigrationV2ViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service';
@@ -42,6 +43,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
private readonly workspaceMigrationV2ViewActionsBuilderService: WorkspaceMigrationV2ViewActionsBuilderService,
private readonly workspaceMigrationV2ViewFieldActionsBuilderService: WorkspaceMigrationV2ViewFieldActionsBuilderService,
private readonly workspaceMigrationV2ViewFilterActionsBuilderService: WorkspaceMigrationV2ViewFilterActionsBuilderService,
private readonly workspaceMigrationV2ViewFilterGroupActionsBuilderService: WorkspaceMigrationV2ViewFilterGroupActionsBuilderService,
private readonly workspaceMigrationV2ViewGroupActionsBuilderService: WorkspaceMigrationV2ViewGroupActionsBuilderService,
private readonly workspaceMigrationV2ServerlessFunctionActionsBuilderService: WorkspaceMigrationV2ServerlessFunctionActionsBuilderService,
private readonly workspaceMigrationV2DatabaseEventTriggerActionsBuilderService: WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService,
@@ -144,6 +146,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatRouteTriggerMaps,
flatFieldMetadataMaps,
flatViewFilterMaps,
flatViewFilterGroupMaps,
flatViewGroupMaps,
flatRowLevelPermissionPredicateMaps,
flatRowLevelPermissionPredicateGroupMaps,
@@ -347,6 +350,46 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
if (isDefined(flatViewFilterGroupMaps)) {
const {
from: fromFlatViewFilterGroupMaps,
to: toFlatViewFilterGroupMaps,
} = flatViewFilterGroupMaps;
const viewFilterGroupResult =
await this.workspaceMigrationV2ViewFilterGroupActionsBuilderService.validateAndBuild(
{
additionalCacheDataMaps,
from: fromFlatViewFilterGroupMaps,
to: toFlatViewFilterGroupMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {
flatViewMaps: optimisticAllFlatEntityMaps.flatViewMaps,
flatViewFilterGroupMaps:
optimisticAllFlatEntityMaps.flatViewFilterGroupMaps,
},
workspaceId,
},
);
this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation(
{
allFlatEntityMaps: optimisticAllFlatEntityMaps,
flatEntityMapsAndRelatedFlatEntityMaps:
viewFilterGroupResult.optimisticFlatEntityMapsAndRelatedFlatEntityMaps,
},
);
if (viewFilterGroupResult.status === 'fail') {
orchestratorFailureReport.viewFilterGroup.push(
...viewFilterGroupResult.errors,
);
} else {
orchestratorActionsReport.viewFilterGroup =
viewFilterGroupResult.actions;
}
}
if (isDefined(flatViewFilterMaps)) {
const { from: fromFlatViewFilterMaps, to: toFlatViewFilterMaps } =
flatViewFilterMaps;
@@ -361,6 +404,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatFieldMetadataMaps:
optimisticAllFlatEntityMaps.flatFieldMetadataMaps,
flatViewMaps: optimisticAllFlatEntityMaps.flatViewMaps,
flatViewFilterGroupMaps:
optimisticAllFlatEntityMaps.flatViewFilterGroupMaps,
},
workspaceId,
},
@@ -890,6 +935,9 @@ export class WorkspaceMigrationBuildOrchestratorService {
...aggregatedOrchestratorActionsReport.viewField.deleted,
...aggregatedOrchestratorActionsReport.viewField.created,
...aggregatedOrchestratorActionsReport.viewField.updated,
...aggregatedOrchestratorActionsReport.viewFilterGroup.deleted,
...aggregatedOrchestratorActionsReport.viewFilterGroup.created,
...aggregatedOrchestratorActionsReport.viewFilterGroup.updated,
...aggregatedOrchestratorActionsReport.viewFilter.deleted,
...aggregatedOrchestratorActionsReport.viewFilter.created,
...aggregatedOrchestratorActionsReport.viewFilter.updated,
@@ -0,0 +1,245 @@
import { type EachTestingContext } from 'twenty-shared/testing';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import {
type CircularDependencyValidationResult,
validateFlatEntityCircularDependency,
} from 'src/engine/workspace-manager/workspace-migration-v2/utils/validate-flat-entity-circular-dependency.util';
type TestFlatEntity = SyncableFlatEntity & {
parentId: string | null;
};
const createFlatEntityMaps = (
entities: TestFlatEntity[],
): FlatEntityMaps<TestFlatEntity> => ({
byId: Object.fromEntries(entities.map((entity) => [entity.id, entity])),
idByUniversalIdentifier: Object.fromEntries(
entities.map((entity) => [entity.universalIdentifier, entity.id]),
),
universalIdentifiersByApplicationId: {},
});
const createTestEntity = (
id: string,
parentId: string | null = null,
): TestFlatEntity => ({
id,
parentId,
universalIdentifier: id,
applicationId: 'test-app',
workspaceId: 'test-workspace',
});
type TestContext = {
flatEntityId: string;
flatEntityParentId: string;
maxDepth?: number;
entities: TestFlatEntity[];
expected: CircularDependencyValidationResult;
};
type ValidateCircularDependencyTestCase = EachTestingContext<TestContext>;
const testCases: ValidateCircularDependencyTestCase[] = [
{
title: 'should fail with self_reference when entity is its own parent',
context: {
flatEntityId: 'entity-1',
flatEntityParentId: 'entity-1',
entities: [],
expected: {
status: 'fail',
reason: 'self_reference',
},
},
},
{
title: 'should succeed with depth 2 when parent has no parent',
context: {
flatEntityId: 'entity-child',
flatEntityParentId: 'entity-parent',
entities: [createTestEntity('entity-parent', null)],
expected: {
status: 'success',
depth: 2,
},
},
},
{
title: 'should succeed with depth 3 when parent has a grandparent',
context: {
flatEntityId: 'entity-child',
flatEntityParentId: 'entity-parent',
entities: [
createTestEntity('entity-grandparent', null),
createTestEntity('entity-parent', 'entity-grandparent'),
],
expected: {
status: 'success',
depth: 3,
},
},
},
{
title:
'should fail with circular_dependency when parent points back to entity',
context: {
flatEntityId: 'entity-a',
flatEntityParentId: 'entity-b',
entities: [createTestEntity('entity-b', 'entity-a')],
expected: {
status: 'fail',
reason: 'circular_dependency',
depth: 2,
},
},
},
{
title:
'should fail with circular_dependency when chain creates a cycle (A -> B -> C -> A)',
context: {
flatEntityId: 'entity-a',
flatEntityParentId: 'entity-b',
entities: [
createTestEntity('entity-b', 'entity-c'),
createTestEntity('entity-c', 'entity-a'),
],
expected: {
status: 'fail',
reason: 'circular_dependency',
depth: 3,
},
},
},
{
title:
'should fail with circular_dependency when ancestors form a cycle among themselves',
context: {
flatEntityId: 'entity-new',
flatEntityParentId: 'entity-a',
entities: [
createTestEntity('entity-a', 'entity-b'),
createTestEntity('entity-b', 'entity-a'),
],
expected: {
status: 'fail',
reason: 'circular_dependency',
depth: 3,
},
},
},
{
title: 'should succeed when parent does not exist in maps',
context: {
flatEntityId: 'entity-child',
flatEntityParentId: 'non-existent-parent',
entities: [],
expected: {
status: 'success',
depth: 1,
},
},
},
{
title: 'should fail with max_depth_exceeded when depth exceeds maxDepth',
context: {
flatEntityId: 'entity-child',
flatEntityParentId: 'entity-parent',
maxDepth: 2,
entities: [
createTestEntity('entity-grandparent', null),
createTestEntity('entity-parent', 'entity-grandparent'),
],
expected: {
status: 'fail',
reason: 'max_depth_exceeded',
depth: 3,
},
},
},
{
title: 'should succeed when depth equals maxDepth',
context: {
flatEntityId: 'entity-child',
flatEntityParentId: 'entity-parent',
maxDepth: 3,
entities: [
createTestEntity('entity-grandparent', null),
createTestEntity('entity-parent', 'entity-grandparent'),
],
expected: {
status: 'success',
depth: 3,
},
},
},
{
title: 'should succeed with depth 2 when maxDepth is 2 and parent is root',
context: {
flatEntityId: 'entity-child',
flatEntityParentId: 'entity-parent',
maxDepth: 2,
entities: [createTestEntity('entity-parent', null)],
expected: {
status: 'success',
depth: 2,
},
},
},
{
title:
'should check max depth before circular dependency to prevent infinite loop',
context: {
flatEntityId: 'entity-new',
flatEntityParentId: 'entity-a',
maxDepth: 2,
entities: [
createTestEntity('entity-a', 'entity-b'),
createTestEntity('entity-b', 'entity-c'),
createTestEntity('entity-c', 'entity-d'),
createTestEntity('entity-d', 'entity-a'),
],
expected: {
status: 'fail',
reason: 'max_depth_exceeded',
depth: 3,
},
},
},
{
title: 'should handle deep valid hierarchy without maxDepth',
context: {
flatEntityId: 'entity-new',
flatEntityParentId: 'entity-1',
entities: [
createTestEntity('entity-5', null),
createTestEntity('entity-4', 'entity-5'),
createTestEntity('entity-3', 'entity-4'),
createTestEntity('entity-2', 'entity-3'),
createTestEntity('entity-1', 'entity-2'),
],
expected: {
status: 'success',
depth: 6,
},
},
},
];
describe('validateFlatEntityCircularDependency', () => {
test.each(testCases)('$title', ({ context }) => {
const flatEntityMaps = createFlatEntityMaps(context.entities);
const result = validateFlatEntityCircularDependency({
flatEntityId: context.flatEntityId,
flatEntityParentId: context.flatEntityParentId,
maxDepth: context.maxDepth,
parentIdKey: 'parentId',
flatEntityMaps,
});
expect(result).toEqual(context.expected);
});
});
@@ -0,0 +1,107 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
export type CircularDependencyValidationSuccess = {
status: 'success';
depth: number;
};
export type CircularDependencyValidationFailure = {
status: 'fail';
reason: 'self_reference' | 'circular_dependency' | 'max_depth_exceeded';
depth?: number;
};
export type CircularDependencyValidationResult =
| CircularDependencyValidationSuccess
| CircularDependencyValidationFailure;
export const validateFlatEntityCircularDependency = <
T extends SyncableFlatEntity,
K extends keyof T,
>({
flatEntityId,
flatEntityParentId,
maxDepth,
parentIdKey,
flatEntityMaps,
}: {
flatEntityId: string;
flatEntityParentId: string;
maxDepth?: number;
parentIdKey: K;
flatEntityMaps: FlatEntityMaps<T>;
}): CircularDependencyValidationResult => {
// Direct self-reference check
if (flatEntityId === flatEntityParentId) {
return {
status: 'fail',
reason: 'self_reference',
};
}
// Traverse ancestor chain to detect cycles and measure depth
const visited = new Set<string>();
let currentParentId: string | null | undefined = flatEntityParentId;
let depth = 1;
while (isDefined(currentParentId)) {
if (isDefined(maxDepth) && depth > maxDepth) {
return {
status: 'fail',
reason: 'max_depth_exceeded',
depth,
};
}
// Check for circular dependency
if (currentParentId === flatEntityId) {
return {
status: 'fail',
reason: 'circular_dependency',
depth,
};
}
// Check for cycle in ancestors (already visited node)
if (visited.has(currentParentId)) {
return {
status: 'fail',
reason: 'circular_dependency',
depth,
};
}
visited.add(currentParentId);
const parentEntity: T | undefined = flatEntityMaps.byId[currentParentId];
if (!isDefined(parentEntity)) {
break;
}
const nextParentId: T[K] = parentEntity[parentIdKey];
currentParentId =
isDefined(nextParentId) && typeof nextParentId === 'string'
? nextParentId
: undefined;
depth++;
}
// Check max depth for the final depth value
if (isDefined(maxDepth) && depth > maxDepth) {
return {
status: 'fail',
reason: 'max_depth_exceeded',
depth,
};
}
return {
status: 'success',
depth,
};
};
@@ -0,0 +1,26 @@
import { type FlatEntityPropertiesUpdates } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-updates.type';
import { type FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group.type';
export type CreateViewFilterGroupAction = {
type: 'create_view_filter_group';
viewFilterGroup: FlatViewFilterGroup;
};
export type UpdateViewFilterGroupAction = {
type: 'update_view_filter_group';
viewFilterGroupId: string;
updates: FlatEntityPropertiesUpdates<'viewFilterGroup'>;
};
export type DeleteViewFilterGroupAction = {
type: 'delete_view_filter_group';
viewFilterGroupId: string;
};
export type WorkspaceMigrationViewFilterGroupActionV2 =
| CreateViewFilterGroupAction
| UpdateViewFilterGroupAction
| DeleteViewFilterGroupAction;
export type WorkspaceMigrationViewFilterGroupActionTypeV2 =
WorkspaceMigrationViewFilterGroupActionV2['type'];
@@ -0,0 +1,113 @@
import { Injectable } from '@nestjs/common';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { type UpdateViewFilterGroupAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter-group/types/workspace-migration-view-filter-group-action-v2.type';
import { WorkspaceEntityMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { type FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
import { type FlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-result.type';
import { FlatViewFilterGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-filter-group-validator.service';
@Injectable()
export class WorkspaceMigrationV2ViewFilterGroupActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
typeof ALL_METADATA_NAME.viewFilterGroup
> {
constructor(
private readonly flatViewFilterGroupValidatorService: FlatViewFilterGroupValidatorService,
) {
super(ALL_METADATA_NAME.viewFilterGroup);
}
protected validateFlatEntityCreation(
args: FlatEntityValidationArgs<typeof ALL_METADATA_NAME.viewFilterGroup>,
): FlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.viewFilterGroup,
'created'
> {
const validationResult =
this.flatViewFilterGroupValidatorService.validateFlatViewFilterGroupCreation(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatViewFilterGroupToValidate } = args;
return {
status: 'success',
action: {
type: 'create_view_filter_group',
viewFilterGroup: flatViewFilterGroupToValidate,
},
};
}
protected validateFlatEntityDeletion(
args: FlatEntityValidationArgs<typeof ALL_METADATA_NAME.viewFilterGroup>,
): FlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.viewFilterGroup,
'deleted'
> {
const validationResult =
this.flatViewFilterGroupValidatorService.validateFlatViewFilterGroupDeletion(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatViewFilterGroupToValidate } = args;
return {
status: 'success',
action: {
type: 'delete_view_filter_group',
viewFilterGroupId: flatViewFilterGroupToValidate.id,
},
};
}
protected validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.viewFilterGroup
>,
): FlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.viewFilterGroup,
'updated'
> {
const validationResult =
this.flatViewFilterGroupValidatorService.validateFlatViewFilterGroupUpdate(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityId, flatEntityUpdates } = args;
const updateViewFilterGroupAction: UpdateViewFilterGroupAction = {
type: 'update_view_filter_group',
viewFilterGroupId: flatEntityId,
updates: flatEntityUpdates,
};
return {
status: 'success',
action: updateViewFilterGroupAction,
};
}
}
@@ -0,0 +1,260 @@
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { type ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { type FlatViewFilterGroupMaps } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group-maps.type';
import { type FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group.type';
import { ViewFilterGroupExceptionCode } from 'src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception';
import { findFlatEntityPropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/utils/find-flat-entity-property-update.util';
import { validateFlatEntityCircularDependency } from 'src/engine/workspace-manager/workspace-migration-v2/utils/validate-flat-entity-circular-dependency.util';
import {
type FailedFlatEntityValidation,
type FlatEntityValidationError,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { type FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
// View filter groups can have at most 2 levels of nesting (root and one child level)
const VIEW_FILTER_GROUP_MAX_DEPTH = 2;
@Injectable()
export class FlatViewFilterGroupValidatorService {
constructor() {}
private getCircularDependencyValidationErrors({
viewFilterGroupId,
parentViewFilterGroupId,
flatViewFilterGroupMaps,
}: {
viewFilterGroupId: string;
parentViewFilterGroupId: string;
flatViewFilterGroupMaps: FlatViewFilterGroupMaps;
}): FlatEntityValidationError<ViewFilterGroupExceptionCode>[] {
const circularDependencyResult = validateFlatEntityCircularDependency({
flatEntityId: viewFilterGroupId,
flatEntityParentId: parentViewFilterGroupId,
maxDepth: VIEW_FILTER_GROUP_MAX_DEPTH,
parentIdKey: 'parentViewFilterGroupId',
flatEntityMaps: flatViewFilterGroupMaps,
});
if (circularDependencyResult.status === 'success') {
return [];
}
switch (circularDependencyResult.reason) {
case 'self_reference':
return [
{
code: ViewFilterGroupExceptionCode.CIRCULAR_DEPENDENCY,
message: t`View filter group cannot be its own parent`,
userFriendlyMessage: msg`View filter group cannot be its own parent`,
},
];
case 'circular_dependency':
return [
{
code: ViewFilterGroupExceptionCode.CIRCULAR_DEPENDENCY,
message: t`Circular dependency detected in view filter group hierarchy`,
userFriendlyMessage: msg`Circular dependency detected in view filter group hierarchy`,
},
];
case 'max_depth_exceeded':
return [
{
code: ViewFilterGroupExceptionCode.MAX_DEPTH_EXCEEDED,
message: t`View filter group hierarchy exceeds maximum depth of ${VIEW_FILTER_GROUP_MAX_DEPTH}`,
userFriendlyMessage: msg`View filter group hierarchy exceeds maximum depth of ${VIEW_FILTER_GROUP_MAX_DEPTH}`,
},
];
}
}
validateFlatViewFilterGroupCreation({
flatEntityToValidate: flatViewFilterGroupToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewFilterGroupMaps: optimisticFlatViewFilterGroupMaps,
flatViewMaps,
},
remainingFlatEntityMapsToValidate,
}: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewFilterGroup
>): FailedFlatEntityValidation<FlatViewFilterGroup> {
const validationResult: FailedFlatEntityValidation<FlatViewFilterGroup> = {
type: 'create_view_filter_group',
errors: [],
flatEntityMinimalInformation: {
id: flatViewFilterGroupToValidate.id,
},
};
const existingViewFilterGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatViewFilterGroupToValidate.id,
flatEntityMaps: optimisticFlatViewFilterGroupMaps,
});
if (isDefined(existingViewFilterGroup)) {
validationResult.errors.push({
code: ViewFilterGroupExceptionCode.INVALID_VIEW_FILTER_GROUP_DATA,
message: t`View filter group with this id already exists`,
userFriendlyMessage: msg`View filter group with this id already exists`,
});
}
const referencedView = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatViewFilterGroupToValidate.viewId,
flatEntityMaps: flatViewMaps,
});
if (!isDefined(referencedView)) {
validationResult.errors.push({
code: ViewFilterGroupExceptionCode.VIEW_NOT_FOUND,
message: t`View not found`,
userFriendlyMessage: msg`View not found`,
});
}
if (isDefined(flatViewFilterGroupToValidate.parentViewFilterGroupId)) {
const circularDependencyErrors =
this.getCircularDependencyValidationErrors({
viewFilterGroupId: flatViewFilterGroupToValidate.id,
parentViewFilterGroupId:
flatViewFilterGroupToValidate.parentViewFilterGroupId,
flatViewFilterGroupMaps: optimisticFlatViewFilterGroupMaps,
});
if (circularDependencyErrors.length > 0) {
validationResult.errors.push(...circularDependencyErrors);
}
const referencedParentInOptimistic = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatViewFilterGroupToValidate.parentViewFilterGroupId,
flatEntityMaps: optimisticFlatViewFilterGroupMaps,
});
const referencedParentInRemaining = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatViewFilterGroupToValidate.parentViewFilterGroupId,
flatEntityMaps: remainingFlatEntityMapsToValidate,
});
if (
!isDefined(referencedParentInOptimistic) &&
!isDefined(referencedParentInRemaining)
) {
validationResult.errors.push({
code: ViewFilterGroupExceptionCode.VIEW_FILTER_GROUP_NOT_FOUND,
message: t`Parent view filter group not found`,
userFriendlyMessage: msg`Parent view filter group not found`,
});
}
}
return validationResult;
}
validateFlatViewFilterGroupDeletion({
flatEntityToValidate: flatViewFilterGroupToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewFilterGroupMaps: optimisticFlatViewFilterGroupMaps,
},
}: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewFilterGroup
>): FailedFlatEntityValidation<FlatViewFilterGroup> {
const validationResult: FailedFlatEntityValidation<FlatViewFilterGroup> = {
type: 'delete_view_filter_group',
errors: [],
flatEntityMinimalInformation: {
id: flatViewFilterGroupToValidate.id,
},
};
const existingViewFilterGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatViewFilterGroupToValidate.id,
flatEntityMaps: optimisticFlatViewFilterGroupMaps,
});
if (!isDefined(existingViewFilterGroup)) {
validationResult.errors.push({
code: ViewFilterGroupExceptionCode.VIEW_FILTER_GROUP_NOT_FOUND,
message: t`View filter group not found`,
userFriendlyMessage: msg`View filter group not found`,
});
}
return validationResult;
}
validateFlatViewFilterGroupUpdate({
flatEntityId,
flatEntityUpdates,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewFilterGroupMaps: optimisticFlatViewFilterGroupMaps,
},
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.viewFilterGroup
>): FailedFlatEntityValidation<FlatViewFilterGroup> {
const validationResult: FailedFlatEntityValidation<FlatViewFilterGroup> = {
type: 'update_view_filter_group',
errors: [],
flatEntityMinimalInformation: {
id: flatEntityId,
},
};
const existingViewFilterGroup =
optimisticFlatViewFilterGroupMaps.byId[flatEntityId];
if (!isDefined(existingViewFilterGroup)) {
validationResult.errors.push({
code: ViewFilterGroupExceptionCode.VIEW_FILTER_GROUP_NOT_FOUND,
message: t`View filter group not found`,
userFriendlyMessage: msg`View filter group not found`,
});
}
const parentViewFilterGroupIdUpdate = findFlatEntityPropertyUpdate({
property: 'parentViewFilterGroupId',
flatEntityUpdates,
});
if (!isDefined(parentViewFilterGroupIdUpdate)) {
return validationResult;
}
const newParentViewFilterGroupId = parentViewFilterGroupIdUpdate.to;
if (!isDefined(newParentViewFilterGroupId)) {
return validationResult;
}
const circularDependencyErrors = this.getCircularDependencyValidationErrors(
{
viewFilterGroupId: flatEntityId,
parentViewFilterGroupId: newParentViewFilterGroupId,
flatViewFilterGroupMaps: optimisticFlatViewFilterGroupMaps,
},
);
if (circularDependencyErrors.length > 0) {
validationResult.errors.push(...circularDependencyErrors);
}
const referencedParentViewFilterGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: newParentViewFilterGroupId,
flatEntityMaps: optimisticFlatViewFilterGroupMaps,
});
if (!isDefined(referencedParentViewFilterGroup)) {
validationResult.errors.push({
code: ViewFilterGroupExceptionCode.VIEW_FILTER_GROUP_NOT_FOUND,
message: t`Parent view filter group not found`,
userFriendlyMessage: msg`Parent view filter group not found`,
});
}
return validationResult;
}
}
@@ -1,15 +1,15 @@
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { type ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.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';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type';
import { type FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
@Injectable()
@@ -22,6 +22,7 @@ export class FlatViewFilterValidatorService {
flatViewFilterMaps: optimisticFlatViewFilterMaps,
flatViewMaps,
flatFieldMetadataMaps,
flatViewFilterGroupMaps,
},
}: FlatEntityValidationArgs<
typeof ALL_METADATA_NAME.viewFilter
@@ -73,6 +74,21 @@ export class FlatViewFilterValidatorService {
});
}
if (isDefined(flatViewFilterToValidate.viewFilterGroupId)) {
const referencedViewFilterGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatViewFilterToValidate.viewFilterGroupId,
flatEntityMaps: flatViewFilterGroupMaps,
});
if (!isDefined(referencedViewFilterGroup)) {
validationResult.errors.push({
code: ViewFilterExceptionCode.INVALID_VIEW_FILTER_DATA,
message: t`View filter group not found`,
userFriendlyMessage: msg`View filter group not found`,
});
}
}
return validationResult;
}
@@ -116,6 +132,7 @@ export class FlatViewFilterValidatorService {
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatViewFilterMaps: optimisticFlatViewFilterMaps,
flatFieldMetadataMaps,
flatViewFilterGroupMaps,
},
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.viewFilter
@@ -160,6 +177,21 @@ export class FlatViewFilterValidatorService {
});
}
if (isDefined(updatedFlatViewFilter.viewFilterGroupId)) {
const referencedViewFilterGroup = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: updatedFlatViewFilter.viewFilterGroupId,
flatEntityMaps: flatViewFilterGroupMaps,
});
if (!isDefined(referencedViewFilterGroup)) {
validationResult.errors.push({
code: ViewFilterExceptionCode.INVALID_VIEW_FILTER_DATA,
message: t`View filter group not found`,
userFriendlyMessage: msg`View filter group not found`,
});
}
}
return validationResult;
}
}
@@ -18,6 +18,7 @@ import { FlatRowLevelPermissionPredicateGroupValidatorService } from 'src/engine
import { FlatRowLevelPermissionPredicateValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-row-level-permission-predicate-validator.service';
import { FlatServerlessFunctionValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-serverless-function-validator.service';
import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service';
import { FlatViewFilterGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-filter-group-validator.service';
import { FlatViewFilterValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-filter-validator.service';
import { FlatViewGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-group-validator.service';
import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service';
@@ -28,6 +29,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
FlatViewValidatorService,
FlatViewFieldValidatorService,
FlatViewFilterValidatorService,
FlatViewFilterGroupValidatorService,
FlatViewGroupValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
@@ -50,6 +52,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace
FlatViewValidatorService,
FlatViewFieldValidatorService,
FlatViewFilterValidatorService,
FlatViewFilterGroupValidatorService,
FlatViewGroupValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
@@ -19,6 +19,7 @@ import { WorkspaceMigrationV2RowLevelPermissionPredicateActionsBuilderService }
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
import { WorkspaceMigrationV2ViewFilterActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/workspace-migration-v2-view-filter-actions-builder.service';
import { WorkspaceMigrationV2ViewFilterGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter-group/workspace-migration-v2-view-filter-group-actions-builder.service';
import { WorkspaceMigrationV2ViewGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-group/workspace-migration-v2-view-group-actions-builder.service';
import { WorkspaceMigrationV2ViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service';
import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/workspace-migration-builder-validators.module';
@@ -31,6 +32,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationV2ViewActionsBuilderService,
WorkspaceMigrationV2ViewFieldActionsBuilderService,
WorkspaceMigrationV2ViewFilterActionsBuilderService,
WorkspaceMigrationV2ViewFilterGroupActionsBuilderService,
WorkspaceMigrationV2ViewGroupActionsBuilderService,
WorkspaceMigrationV2IndexActionsBuilderService,
WorkspaceMigrationV2ServerlessFunctionActionsBuilderService,
@@ -53,6 +55,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationV2ObjectActionsBuilderService,
WorkspaceMigrationV2ViewFieldActionsBuilderService,
WorkspaceMigrationV2ViewFilterActionsBuilderService,
WorkspaceMigrationV2ViewFilterGroupActionsBuilderService,
WorkspaceMigrationV2ViewGroupActionsBuilderService,
WorkspaceMigrationV2FieldActionsBuilderService,
WorkspaceMigrationV2ServerlessFunctionActionsBuilderService,
@@ -0,0 +1,59 @@
import { Injectable } from '@nestjs/common';
import {
type OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-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 { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { type CreateViewFilterGroupAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter-group/types/workspace-migration-view-filter-group-action-v2.type';
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateViewFilterGroupActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_view_filter_group',
) {
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<CreateViewFilterGroupAction>): Partial<AllFlatEntityMaps> {
const { flatViewFilterGroupMaps } = allFlatEntityMaps;
const { viewFilterGroup } = action;
const updatedFlatViewFilterGroupMaps = addFlatEntityToFlatEntityMapsOrThrow(
{
flatEntity: viewFilterGroup,
flatEntityMaps: flatViewFilterGroupMaps,
},
);
return {
flatViewFilterGroupMaps: updatedFlatViewFilterGroupMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<CreateViewFilterGroupAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { viewFilterGroup } = action;
const viewFilterGroupRepository =
queryRunner.manager.getRepository<ViewFilterGroupEntity>(
ViewFilterGroupEntity,
);
await viewFilterGroupRepository.insert({
...viewFilterGroup,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<CreateViewFilterGroupAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,62 @@
import { Injectable } from '@nestjs/common';
import {
type OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { type DeleteViewFilterGroupAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter-group/types/workspace-migration-view-filter-group-action-v2.type';
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteViewFilterGroupActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete_view_filter_group',
) {
constructor() {
super();
}
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<DeleteViewFilterGroupAction>): Partial<AllFlatEntityMaps> {
const { flatViewFilterGroupMaps } = allFlatEntityMaps;
const { viewFilterGroupId } = action;
const updatedFlatViewFilterGroupMaps =
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: viewFilterGroupId,
flatEntityMaps: flatViewFilterGroupMaps,
});
return {
flatViewFilterGroupMaps: updatedFlatViewFilterGroupMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<DeleteViewFilterGroupAction>,
): Promise<void> {
const { action, queryRunner, workspaceId } = context;
const { viewFilterGroupId } = action;
const viewFilterGroupRepository =
queryRunner.manager.getRepository<ViewFilterGroupEntity>(
ViewFilterGroupEntity,
);
await viewFilterGroupRepository.delete({
id: viewFilterGroupId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<DeleteViewFilterGroupAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,73 @@
import { Injectable } from '@nestjs/common';
import {
type OptimisticallyApplyActionOnAllFlatEntityMapsArgs,
WorkspaceMigrationRunnerActionHandler,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-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';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { type UpdateViewFilterGroupAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter-group/types/workspace-migration-view-filter-group-action-v2.type';
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
@Injectable()
export class UpdateViewFilterGroupActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update_view_filter_group',
) {
constructor() {
super();
}
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps,
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<UpdateViewFilterGroupAction>): Partial<AllFlatEntityMaps> {
const { flatViewFilterGroupMaps } = allFlatEntityMaps;
const { viewFilterGroupId } = action;
const existingViewFilterGroup = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: viewFilterGroupId,
flatEntityMaps: flatViewFilterGroupMaps,
});
const updatedViewFilterGroup = {
...existingViewFilterGroup,
...fromFlatEntityPropertiesUpdatesToPartialFlatEntity(action),
};
const updatedFlatViewFilterGroupMaps =
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: updatedViewFilterGroup,
flatEntityMaps: flatViewFilterGroupMaps,
});
return {
flatViewFilterGroupMaps: updatedFlatViewFilterGroupMaps,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerArgs<UpdateViewFilterGroupAction>,
): Promise<void> {
const { action, queryRunner } = context;
const { viewFilterGroupId } = action;
const viewFilterGroupRepository =
queryRunner.manager.getRepository<ViewFilterGroupEntity>(
ViewFilterGroupEntity,
);
const update = fromFlatEntityPropertiesUpdatesToPartialFlatEntity(action);
await viewFilterGroupRepository.update(viewFilterGroupId, update);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<UpdateViewFilterGroupAction>,
): Promise<void> {
return;
}
}
@@ -51,6 +51,9 @@ import { UpdateViewFieldActionHandlerService } from 'src/engine/workspace-manage
import { CreateViewFilterActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-filter/services/create-view-filter-action-handler.service';
import { DeleteViewFilterActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-filter/services/delete-view-filter-action-handler.service';
import { UpdateViewFilterActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-filter/services/update-view-filter-action-handler.service';
import { CreateViewFilterGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-filter-group/services/create-view-filter-group-action-handler.service';
import { DeleteViewFilterGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-filter-group/services/delete-view-filter-group-action-handler.service';
import { UpdateViewFilterGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-filter-group/services/update-view-filter-group-action-handler.service';
import { CreateViewGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-group/services/create-view-group-action-handler.service';
import { DeleteViewGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-group/services/delete-view-group-action-handler.service';
import { UpdateViewGroupActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/view-group/services/update-view-group-action-handler.service';
@@ -84,6 +87,10 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor
UpdateViewFilterActionHandlerService,
DeleteViewFilterActionHandlerService,
CreateViewFilterGroupActionHandlerService,
UpdateViewFilterGroupActionHandlerService,
DeleteViewFilterGroupActionHandlerService,
CreateViewGroupActionHandlerService,
UpdateViewGroupActionHandlerService,
DeleteViewGroupActionHandlerService,