From 7781d70bb89870a7dbf38fbe2a1594e1670049f9 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 20 Sep 2024 05:42:59 +0200 Subject: [PATCH] Fix CSV export missing last page (#7167) --- .../options/hooks/useTableData.ts | 8 +- .../graphql-query-filter-field.parser.spec.ts | 43 ------ ...aphql-query-filter-operator.parser.spec.ts | 131 ------------------ .../graphql-query-filter-field.parser.ts | 5 + .../graphql-query-order.parser.spec.ts | 50 ------- 5 files changed, 7 insertions(+), 230 deletions(-) delete mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-field.parser.spec.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-operator.parser.spec.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/__tests__/graphql-query-order.parser.spec.ts diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useTableData.ts b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useTableData.ts index 46572dffe..1e6255276 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useTableData.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useTableData.ts @@ -142,10 +142,6 @@ export const useTableData = ({ }); useEffect(() => { - const MAXIMUM_REQUESTS = isDefined(totalCount) - ? Math.min(maximumRequests, totalCount / pageSize) - : maximumRequests; - const fetchNextPage = async () => { setInflight(true); setPreviousRecordCount(records.length); @@ -167,8 +163,8 @@ export const useTableData = ({ } if ( - pageCount >= MAXIMUM_REQUESTS || - (isDefined(totalCount) && records.length === totalCount) + pageCount >= maximumRequests || + (isDefined(totalCount) && records.length >= totalCount) ) { setPageCount(0); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-field.parser.spec.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-field.parser.spec.ts deleted file mode 100644 index bce39127f..000000000 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-field.parser.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { FindOperator, Not } from 'typeorm'; - -import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; - -import { GraphqlQueryFilterFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser'; -import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; - -describe('GraphqlQueryFilterFieldParser', () => { - let parser: GraphqlQueryFilterFieldParser; - let mockFieldMetadataMap: Record; - - beforeEach(() => { - mockFieldMetadataMap = { - simpleField: { - id: '1', - name: 'simpleField', - type: FieldMetadataType.TEXT, - label: 'Simple Field', - objectMetadataId: 'obj1', - }, - }; - parser = new GraphqlQueryFilterFieldParser(mockFieldMetadataMap); - }); - it('should parse simple field correctly', () => { - const result = parser.parse('simpleField', 'value', false); - - expect(result).toEqual({ simpleField: 'value' }); - }); - - it('should negate simple field correctly', () => { - const result = parser.parse('simpleField', 'value', true); - - expect(result).toEqual({ simpleField: Not('value') }); - }); - - it('should parse object value using operator parser', () => { - const result = parser.parse('simpleField', { like: '%value%' }, false); - - expect(result).toEqual({ - simpleField: new FindOperator('like', '%%value%%'), - }); - }); -}); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-operator.parser.spec.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-operator.parser.spec.ts deleted file mode 100644 index 4d1494141..000000000 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/__tests__/graphql-query-filter-operator.parser.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { - FindOperator, - ILike, - In, - IsNull, - LessThan, - LessThanOrEqual, - Like, - MoreThan, - MoreThanOrEqual, - Not, -} from 'typeorm'; - -import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; -import { GraphqlQueryFilterOperatorParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-operator.parser'; - -describe('GraphqlQueryFilterOperatorParser', () => { - let parser: GraphqlQueryFilterOperatorParser; - - beforeEach(() => { - parser = new GraphqlQueryFilterOperatorParser(); - }); - - describe('parseOperator', () => { - it('should parse eq operator correctly', () => { - const result = parser.parseOperator({ eq: 'value' }, false); - - expect(result).toBe('value'); - }); - - it('should parse neq operator correctly', () => { - const result = parser.parseOperator({ neq: 'value' }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(Not('value')); - }); - - it('should parse gt operator correctly', () => { - const result = parser.parseOperator({ gt: 5 }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(MoreThan(5)); - }); - - it('should parse gte operator correctly', () => { - const result = parser.parseOperator({ gte: 5 }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(MoreThanOrEqual(5)); - }); - - it('should parse lt operator correctly', () => { - const result = parser.parseOperator({ lt: 5 }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(LessThan(5)); - }); - - it('should parse lte operator correctly', () => { - const result = parser.parseOperator({ lte: 5 }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(LessThanOrEqual(5)); - }); - - it('should parse in operator correctly', () => { - const result = parser.parseOperator({ in: [1, 2, 3] }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(In([1, 2, 3])); - }); - - it('should parse is operator with NULL correctly', () => { - const result = parser.parseOperator({ is: 'NULL' }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(IsNull()); - }); - - it('should parse is operator with non-NULL value correctly', () => { - const result = parser.parseOperator({ is: 'NOT_NULL' }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(Not(IsNull())); - }); - - it('should parse like operator correctly', () => { - const result = parser.parseOperator({ like: 'test' }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(Like('%test%')); - }); - - it('should parse ilike operator correctly', () => { - const result = parser.parseOperator({ ilike: 'test' }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(ILike('%test%')); - }); - - it('should parse startsWith operator correctly', () => { - const result = parser.parseOperator({ startsWith: 'test' }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(ILike('test%')); - }); - - it('should parse endsWith operator correctly', () => { - const result = parser.parseOperator({ endsWith: 'test' }, false); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(ILike('%test')); - }); - - it('should negate the operator when isNegated is true', () => { - const result = parser.parseOperator({ eq: 'value' }, true); - - expect(result).toBeInstanceOf(FindOperator); - expect(result).toEqual(Not('value')); - }); - - it('should throw an exception for unsupported operator', () => { - expect(() => - parser.parseOperator({ unsupported: 'value' }, false), - ).toThrow(GraphqlQueryRunnerException); - expect(() => - parser.parseOperator({ unsupported: 'value' }, false), - ).toThrow('Operator "unsupported" is not supported'); - }); - }); -}); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts index 9918a88af..e3f4a8348 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts @@ -1,3 +1,4 @@ +import { isArray } from 'class-validator'; import { ObjectLiteral, WhereExpressionBuilder } from 'typeorm'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; @@ -48,6 +49,10 @@ export class GraphqlQueryFilterFieldParser { } const [[operator, value]] = Object.entries(filterValue); + if (operator === 'in' && (!isArray(value) || value.length === 0)) { + return; + } + const { sql, params } = this.computeWhereConditionParts( fieldMetadata, operator, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/__tests__/graphql-query-order.parser.spec.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/__tests__/graphql-query-order.parser.spec.ts deleted file mode 100644 index cf9864f3e..000000000 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/__tests__/graphql-query-order.parser.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; - -import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser'; -import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { FieldMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; - -describe('GraphqlQueryOrderFieldParser', () => { - let parser: GraphqlQueryOrderFieldParser; - const fieldMetadataMap: FieldMetadataMap = {}; - - beforeEach(() => { - fieldMetadataMap['name'] = { - id: 'name-id', - name: 'name', - type: FieldMetadataType.TEXT, - label: 'Name', - objectMetadataId: 'object-id', - }; - fieldMetadataMap['age'] = { - id: 'age-id', - name: 'age', - type: FieldMetadataType.NUMBER, - label: 'Age', - objectMetadataId: 'object-id', - }; - fieldMetadataMap['address'] = { - id: 'address-id', - name: 'address', - type: FieldMetadataType.ADDRESS, - label: 'Address', - objectMetadataId: 'object-id', - }; - - parser = new GraphqlQueryOrderFieldParser(fieldMetadataMap); - }); - describe('parse', () => { - it('should parse simple order by fields', () => { - const orderBy = [ - { name: OrderByDirection.AscNullsFirst }, - { age: OrderByDirection.DescNullsLast }, - ]; - const result = parser.parse(orderBy); - - expect(result).toEqual({ - name: { direction: 'ASC', nulls: 'FIRST' }, - age: { direction: 'DESC', nulls: 'LAST' }, - }); - }); - }); -});