From 1e97ad48c0068b3a740046df06bf5421bb46ed95 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 12 Sep 2025 22:45:26 +0200 Subject: [PATCH] Fix on view filters (#14462) Fixes https://github.com/twentyhq/twenty/issues/14058 --- .../internal/usePersistViewFilterRecords.ts | 4 ++-- .../graphql-query-order.parser.ts | 19 ++++++++++++++++++- .../field-metadata-related-records.service.ts | 4 +--- .../src/utils/__tests__/parseJson.test.ts | 10 ++++++++++ packages/twenty-shared/src/utils/parseJson.ts | 6 +++++- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts index c0af1f7571..0986a1575c 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts @@ -16,7 +16,7 @@ import { type ViewFilter } from '@/views/types/ViewFilter'; import { convertViewFilterOperandToCore } from '@/views/utils/convertViewFilterOperandToCore'; import { useApolloClient } from '@apollo/client'; import { isNull } from '@sniptt/guards'; -import { isDefined, parseJson } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { type CoreViewFilter } from '~/generated/graphql'; export const usePersistViewFilterRecords = () => { @@ -86,7 +86,7 @@ export const usePersistViewFilterRecords = () => { variables: { id: viewFilter.id, input: { - value: parseJson(viewFilter.value), + value: viewFilter.value, operand: convertViewFilterOperandToCore(viewFilter.operand), positionInViewFilterGroup: viewFilter.positionInViewFilterGroup, viewFilterGroupId: viewFilter.viewFilterGroupId, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser.ts index 2f7b8b68d8..c999bba406 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser.ts @@ -1,3 +1,4 @@ +import { FieldMetadataType } from 'twenty-shared/types'; import { capitalize } from 'twenty-shared/utils'; import { @@ -51,7 +52,10 @@ export class GraphqlQueryOrderFieldParser { Object.assign(acc, compositeOrder); } else { - acc[`"${objectNameSingular}"."${key}"`] = + const orderByCasting = + this.getOptionalOrderByCasting(fieldMetadata); + + acc[`"${objectNameSingular}"."${key}"${orderByCasting}`] = this.convertOrderByToFindOptionsOrder( value as OrderByDirection, isForwardPagination, @@ -65,6 +69,19 @@ export class GraphqlQueryOrderFieldParser { ); } + private getOptionalOrderByCasting( + fieldMetadata: Pick, + ): string { + if ( + fieldMetadata.type === FieldMetadataType.SELECT || + fieldMetadata.type === FieldMetadataType.MULTI_SELECT + ) { + return '::text'; + } + + return ''; + } + private parseCompositeFieldForOrder( fieldMetadata: FieldMetadataEntity, // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service.ts index 8c0406b7b4..7b451f448a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service.ts @@ -103,9 +103,7 @@ export class FieldMetadataRelatedRecordsService { ); if (!existingViewGroup) { - throw new Error( - `View group not found for option "${oldOption.value}" during update.`, - ); + continue; } this.viewGroupService.update( diff --git a/packages/twenty-shared/src/utils/__tests__/parseJson.test.ts b/packages/twenty-shared/src/utils/__tests__/parseJson.test.ts index 83bc1e6593..ae90469a83 100644 --- a/packages/twenty-shared/src/utils/__tests__/parseJson.test.ts +++ b/packages/twenty-shared/src/utils/__tests__/parseJson.test.ts @@ -6,6 +6,16 @@ describe('parseJson', () => { expect(result).toBeNull(); }); + it('if value is undefined', () => { + const result = parseJson(undefined); + expect(result).toBeNull(); + }); + + it('if value is empty string null', () => { + const result = parseJson(''); + expect(result).toBeNull(); + }); + it('if value is number', () => { const result = parseJson(123); expect(result).toBe(123); diff --git a/packages/twenty-shared/src/utils/parseJson.ts b/packages/twenty-shared/src/utils/parseJson.ts index a54013b185..9afc7d7e47 100644 --- a/packages/twenty-shared/src/utils/parseJson.ts +++ b/packages/twenty-shared/src/utils/parseJson.ts @@ -1,11 +1,15 @@ import { isDefined } from "@/utils/validation"; -export const parseJson = (rawJson: string | boolean | null | number): T | null => { +export const parseJson = (rawJson: string | boolean | null | number | undefined): T | null => { try { if (!isDefined(rawJson)) { return null; } + if (rawJson === '') { + return null; + } + // This is a hack to handle the case where the value is a scalar value which is part of JSON spec but not implemented before ES2019 return JSON.parse("[" + rawJson + "]")[0]; } catch {