From dc435a7946b6d01578f97a6cd910126744aace77 Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Wed, 17 Sep 2025 10:32:47 +0200 Subject: [PATCH] Add default limit when fetching records (#14532) - set default limit on front find many hooks - use default constant in rest api find many endpoint - set a max limit in rest api find many endpoint --- .../__tests__/useEmailThreadInCommandMenu.test.tsx | 7 +++++-- .../__tests__/useRecordIndexTableQuery.test.tsx | 2 ++ .../object-record/hooks/useFindManyRecords.ts | 3 ++- .../object-record/hooks/useLazyFindManyRecords.ts | 3 ++- .../HttpRequestExecutionResult.stories.tsx | 1 - .../utils/__tests__/get-resolver-args.spec.ts | 1 - .../utils/get-resolver-args.util.ts | 4 ---- .../api/rest/input-factories/limit-input.factory.ts | 12 ++++++++++-- .../utils/__tests__/parameters.utils.spec.ts | 9 +++++++-- .../core-modules/open-api/utils/parameters.utils.ts | 8 ++++++-- .../utils/find-many-operation-factory.util.ts | 7 ++----- .../src/constants/QueryDefaultLimitRecords.ts | 1 + packages/twenty-shared/src/constants/index.ts | 1 + 13 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 packages/twenty-shared/src/constants/QueryDefaultLimitRecords.ts diff --git a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx index 756ba8d53e..4399c1f75e 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx @@ -3,7 +3,10 @@ import { renderHook, waitFor } from '@testing-library/react'; import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState'; import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; import gql from 'graphql-tag'; -import { QUERY_MAX_RECORDS } from 'twenty-shared/constants'; +import { + QUERY_DEFAULT_LIMIT_RECORDS, + QUERY_MAX_RECORDS, +} from 'twenty-shared/constants'; import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { useEmailThreadInCommandMenu } from '../useEmailThreadInCommandMenu'; @@ -309,7 +312,7 @@ const mocks = [ filter: { messageId: { in: ['1', '2'] }, role: { eq: 'from' } }, orderBy: undefined, lastCursor: undefined, - limit: undefined, + limit: QUERY_DEFAULT_LIMIT_RECORDS, }, }, result: jest.fn(() => ({ diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRecordIndexTableQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRecordIndexTableQuery.test.tsx index 58bae629ff..4eabd1ea6f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRecordIndexTableQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRecordIndexTableQuery.test.tsx @@ -10,6 +10,7 @@ import { RecordTableContextProvider } from '@/object-record/record-table/compone import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { type MockedResponse } from '@apollo/client/testing'; import gql from 'graphql-tag'; +import { QUERY_DEFAULT_LIMIT_RECORDS } from 'twenty-shared/constants'; import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { JestRecordIndexContextProviderWrapper } from '~/testing/jest/JestRecordIndexContextProviderWrapper'; import { @@ -630,6 +631,7 @@ const mocks: MockedResponse[] = [ variables: { filter: {}, orderBy: [{ position: 'AscNullsFirst' }], + limit: QUERY_DEFAULT_LIMIT_RECORDS, }, }, result: jest.fn(() => ({ diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts index 1868374f30..abd26e9d4d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts @@ -16,6 +16,7 @@ import { type OnFindManyRecordsCompleted } from '@/object-record/types/OnFindMan import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier'; import { type RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter'; +import { QUERY_DEFAULT_LIMIT_RECORDS } from 'twenty-shared/constants'; export type UseFindManyRecordsParams = ObjectMetadataItemIdentifier & RecordGqlOperationVariables & { @@ -31,13 +32,13 @@ export const useFindManyRecords = ({ objectNameSingular, filter, orderBy, - limit, skip, recordGqlFields, fetchPolicy, onError, onCompleted, cursorFilter, + limit = QUERY_DEFAULT_LIMIT_RECORDS, withSoftDeleted = false, }: UseFindManyRecordsParams) => { const { objectMetadataItem } = useObjectMetadataItem({ diff --git a/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts index 2a627a4edf..402576cfd2 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts @@ -15,6 +15,7 @@ import { cursorFamilyState } from '@/object-record/states/cursorFamilyState'; import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamilyState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier'; +import { QUERY_DEFAULT_LIMIT_RECORDS } from 'twenty-shared/constants'; type UseLazyFindManyRecordsParams = Omit< UseFindManyRecordsParams, @@ -25,7 +26,7 @@ export const useLazyFindManyRecords = ({ objectNameSingular, filter, orderBy, - limit, + limit = QUERY_DEFAULT_LIMIT_RECORDS, recordGqlFields, fetchPolicy = 'cache-first', }: UseLazyFindManyRecordsParams) => { diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/__stories__/HttpRequestExecutionResult.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/__stories__/HttpRequestExecutionResult.stories.tsx index a114151b01..6e35ae845f 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/__stories__/HttpRequestExecutionResult.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/__stories__/HttpRequestExecutionResult.stories.tsx @@ -311,7 +311,6 @@ export const LargeResponse: Story = { pagination: { totalCount: 500, page: 1, - limit: 50, totalPages: 10, }, }, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts index 9207fa7eaa..7239b5e50b 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts @@ -19,7 +19,6 @@ describe('getResolverArgs', () => { isNullable: true, isArray: true, }, - limit: { type: GraphQLInt, isNullable: true }, }, findOne: { filter: { kind: GqlInputTypeDefinitionKind.Filter, isNullable: false }, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts index f842c1090a..466352b1e9 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts @@ -28,10 +28,6 @@ export const getResolverArgs = ( type: GraphQLString, isNullable: true, }, - limit: { - type: GraphQLInt, - isNullable: true, - }, filter: { kind: GqlInputTypeDefinitionKind.Filter, isNullable: true, diff --git a/packages/twenty-server/src/engine/api/rest/input-factories/limit-input.factory.ts b/packages/twenty-server/src/engine/api/rest/input-factories/limit-input.factory.ts index dd4935141e..549ba07be4 100644 --- a/packages/twenty-server/src/engine/api/rest/input-factories/limit-input.factory.ts +++ b/packages/twenty-server/src/engine/api/rest/input-factories/limit-input.factory.ts @@ -1,10 +1,18 @@ import { BadRequestException, Injectable } from '@nestjs/common'; +import { + QUERY_DEFAULT_LIMIT_RECORDS, + QUERY_MAX_RECORDS, +} from 'twenty-shared/constants'; + import { type RequestContext } from 'src/engine/api/rest/types/RequestContext'; @Injectable() export class LimitInputFactory { - create(request: RequestContext, defaultLimit = 60): number { + create( + request: RequestContext, + defaultLimit = QUERY_DEFAULT_LIMIT_RECORDS, + ): number { if (!request.query?.limit) { return defaultLimit; } @@ -16,6 +24,6 @@ export class LimitInputFactory { ); } - return limit; + return Math.min(limit, QUERY_MAX_RECORDS); } } diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts index d3864e360f..4437a6c627 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/parameters.utils.spec.ts @@ -1,3 +1,8 @@ +import { + QUERY_DEFAULT_LIMIT_RECORDS, + QUERY_MAX_RECORDS, +} from 'twenty-shared/constants'; + import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { @@ -21,8 +26,8 @@ describe('computeParameters', () => { schema: { type: 'integer', minimum: 0, - maximum: 60, - default: 60, + maximum: QUERY_MAX_RECORDS, + default: QUERY_DEFAULT_LIMIT_RECORDS, }, }); }); diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts index b7ead876c8..e0885d33ca 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/parameters.utils.ts @@ -1,4 +1,8 @@ import { type OpenAPIV3_1 } from 'openapi-types'; +import { + QUERY_DEFAULT_LIMIT_RECORDS, + QUERY_MAX_RECORDS, +} from 'twenty-shared/constants'; import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; @@ -13,8 +17,8 @@ export const computeLimitParameters = ( schema: { type: 'integer', minimum: 0, - maximum: fromMetadata ? 1000 : 60, - default: fromMetadata ? 1000 : 60, + maximum: fromMetadata ? 1000 : QUERY_MAX_RECORDS, + default: fromMetadata ? 1000 : QUERY_DEFAULT_LIMIT_RECORDS, }, }; }; diff --git a/packages/twenty-server/test/integration/graphql/utils/find-many-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/find-many-operation-factory.util.ts index b6bf2f8bbc..4a776383d7 100644 --- a/packages/twenty-server/test/integration/graphql/utils/find-many-operation-factory.util.ts +++ b/packages/twenty-server/test/integration/graphql/utils/find-many-operation-factory.util.ts @@ -7,7 +7,6 @@ type FindManyOperationFactoryParams = { gqlFields: string; filter?: object; orderBy?: object; - limit?: number; after?: string; before?: string; first?: number; @@ -20,15 +19,14 @@ export const findManyOperationFactory = ({ gqlFields, filter = {}, orderBy = {}, - limit, after, before, first, last, }: FindManyOperationFactoryParams) => ({ query: gql` - query ${capitalize(objectMetadataPluralName)}($filter: ${capitalize(objectMetadataSingularName)}FilterInput, $orderBy: [${capitalize(objectMetadataSingularName)}OrderByInput], $limit: Int, $after: String, $before: String, $first: Int, $last: Int) { - ${objectMetadataPluralName}(filter: $filter, orderBy: $orderBy, limit: $limit, after: $after, before: $before, first: $first, last: $last) { + query ${capitalize(objectMetadataPluralName)}($filter: ${capitalize(objectMetadataSingularName)}FilterInput, $orderBy: [${capitalize(objectMetadataSingularName)}OrderByInput], $after: String, $before: String, $first: Int, $last: Int) { + ${objectMetadataPluralName}(filter: $filter, orderBy: $orderBy, after: $after, before: $before, first: $first, last: $last) { edges { node { ${gqlFields} @@ -47,7 +45,6 @@ export const findManyOperationFactory = ({ variables: { filter, orderBy, - limit, after, before, first, diff --git a/packages/twenty-shared/src/constants/QueryDefaultLimitRecords.ts b/packages/twenty-shared/src/constants/QueryDefaultLimitRecords.ts new file mode 100644 index 0000000000..daa2516378 --- /dev/null +++ b/packages/twenty-shared/src/constants/QueryDefaultLimitRecords.ts @@ -0,0 +1 @@ +export const QUERY_DEFAULT_LIMIT_RECORDS = 60; \ No newline at end of file diff --git a/packages/twenty-shared/src/constants/index.ts b/packages/twenty-shared/src/constants/index.ts index c7a000e334..1afe9335c8 100644 --- a/packages/twenty-shared/src/constants/index.ts +++ b/packages/twenty-shared/src/constants/index.ts @@ -13,6 +13,7 @@ export { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from './FieldRestric export { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from './LabelIdentifierFieldMetadataTypes'; export { MUTATION_MAX_MERGE_RECORDS } from './MutationMaxMergeRecords'; export { PermissionsOnAllObjectRecords } from './PermissionsOnAllObjectRecords'; +export { QUERY_DEFAULT_LIMIT_RECORDS } from './QueryDefaultLimitRecords'; export { QUERY_MAX_RECORDS } from './QueryMaxRecords'; export { QUOTED_STRING_REGEX } from './QuotedStringRegex'; export { RELATION_NESTED_QUERY_KEYWORDS } from './RelationNestedQueriesKeyword';