[GQL_VIEW_FILTER_API_BREAKING_CHANGE][WHEN_RELEASED_REQUIRES_CACHE_FLUSH] ViewFilter migration to workspace migration v2 (#15010)

# Introduction
Migrating `viewFilter` to v2 in order to migrate later the field update
side effect on view to v2 too

## What's done
- Created flat-view-filter
- flat view filter runner
- flat view filter builder
- create view filter service v2 and input transpilers
- refactor the existing view filter resolver to fix standard (
BREAKING_CHANGE on graphql api update especially ) REST stays the same
- refactored the front to consume the mutations autogenerated

## New generic tools
### Compare two flat entity
Introducing a new util to compare two flat entity, it's strictly typed
and will be added to the generic builder in a following PR
This will ease flat entity addition as won't required to create a
specific abstraction for comparison
Generic builder will expect specific constant: properties to compare and
properties to stringify

### Transform flat entity for comparison
Forked and refactor the initial existing method for flat entity business
scope and type safety

## Coverage
Migrated existing integration tests to fit new contract API
This PR does not add strong coverage on validation exceptions
Deadlines are too short

close https://github.com/twentyhq/core-team-issues/issues/1666
This commit is contained in:
Paul Rastoin
2025-10-10 15:02:14 +02:00
committed by GitHub
parent 37473175a0
commit 651ab184a7
97 changed files with 3012 additions and 620 deletions
@@ -10,4 +10,5 @@ export const ALL_FLAT_ENTITY_MAPS_PROPERTIES = [
'flatCronTriggerMaps',
'flatRouteTriggerMaps',
'flatFieldMetadataMaps',
'flatViewFilterMaps',
] as const satisfies (keyof AllFlatEntityMaps)[];
@@ -11,4 +11,5 @@ export const EMPTY_ALL_FLAT_ENTITY_MAPS = {
flatCronTriggerMaps: EMPTY_FLAT_ENTITY_MAPS,
flatDatabaseEventTriggerMaps: EMPTY_FLAT_ENTITY_MAPS,
flatRouteTriggerMaps: EMPTY_FLAT_ENTITY_MAPS,
flatViewFilterMaps: EMPTY_FLAT_ENTITY_MAPS,
} as const satisfies AllFlatEntityMaps;
@@ -4,6 +4,7 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-m
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';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type';
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
@@ -18,4 +19,5 @@ export type AllFlatEntitiesByMetadataEngineName = {
cronTrigger: FlatCronTrigger;
databaseEventTrigger: FlatDatabaseEventTrigger;
routeTrigger: FlatRouteTrigger;
viewFilter: FlatViewFilter;
};
@@ -0,0 +1,77 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { type AllFlatEntities } from 'src/engine/core-modules/common/types/all-flat-entities.type';
import { transformFlatEntityForComparison } from 'src/engine/core-modules/common/utils/transform-flat-entity-for-comparison.util';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
export const compareTwoFlatEntity = <
TFlatEntity extends AllFlatEntities,
PToCompare extends keyof TFlatEntity,
PJsonB extends keyof TFlatEntity,
>({
fromFlatEntity,
toFlatEntity,
propertiesToCompare,
propertiesToStringify,
}: {
propertiesToCompare: PToCompare[];
propertiesToStringify: PJsonB[];
} & FromTo<TFlatEntity, 'flatEntity'>) => {
const [transformedFromFlatEntity, transformedToFlatEntity] = [
fromFlatEntity,
toFlatEntity,
].map((flatEntity) =>
transformFlatEntityForComparison({
flatEntity,
options: {
propertiesToCompare,
propertiesToStringify,
},
}),
);
const flatEntityDifferences = diff(
transformedFromFlatEntity,
transformedToFlatEntity,
);
return flatEntityDifferences.flatMap<
Array<
{
[P in PToCompare]: PropertyUpdate<TFlatEntity, P>;
}[PToCompare]
>[number]
>((difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as PToCompare;
const isJsonb = propertiesToStringify.includes(
property as unknown as PJsonB,
);
if (isJsonb) {
return {
from: parseJson(oldValue),
to: parseJson(value),
property,
};
}
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE':
default: {
// Should never occurs, we should only provide null never undefined and so on
return [];
}
}
});
};
@@ -0,0 +1,40 @@
import { type AllFlatEntities } from 'src/engine/core-modules/common/types/all-flat-entities.type';
import { orderObjectProperties } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/order-object-properties.util';
export function transformFlatEntityForComparison<
TFlatEntity extends AllFlatEntities,
PToCompare extends keyof TFlatEntity,
PJsonB extends keyof TFlatEntity,
>({
flatEntity,
options: { propertiesToCompare, propertiesToStringify },
}: {
flatEntity: TFlatEntity;
options: {
propertiesToCompare: PToCompare[];
propertiesToStringify: PJsonB[];
};
}) {
return propertiesToCompare.reduce(
(flatEntityAccumulator, propertyToCompare) => {
const currentValue = flatEntity[propertyToCompare];
if (
propertiesToStringify.includes(propertyToCompare as unknown as PJsonB)
) {
const orderedValue = orderObjectProperties(currentValue);
return {
...flatEntityAccumulator,
[propertyToCompare]: JSON.stringify(orderedValue),
};
}
return {
...flatEntityAccumulator,
[propertyToCompare]: currentValue,
};
},
{} as Pick<TFlatEntity, PToCompare>,
);
}