From c095808aef5c4e627b8399e5cb0224a9cc3cd9e3 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 17 Oct 2024 11:54:32 +0200 Subject: [PATCH 01/10] Fix list items should have unique key error --- .../modules/app/effect-components/GotoHotkeysEffect.tsx | 1 + .../NavigationDrawerSectionForObjectMetadataItems.tsx | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffect.tsx index 15d371f9f44a..202b58b963e5 100644 --- a/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffect.tsx @@ -11,6 +11,7 @@ export const GotoHotkeys = () => { return nonSystemActiveObjectMetadataItems.map((objectMetadataItem) => ( diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems.tsx index 5e666e0cf542..bfa70da83f00 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems.tsx @@ -1,8 +1,10 @@ import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; +import { NavigationDrawerItemsCollapsedContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsedContainer'; import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; -import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper'; +import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle'; import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection'; import { getNavigationSubItemState } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemState'; @@ -11,8 +13,6 @@ import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemV import { useLocation } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; import { useIcons } from 'twenty-ui'; -import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle'; -import { NavigationDrawerItemsCollapsedContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsedContainer'; const ORDERED_STANDARD_OBJECTS = [ 'person', 'company', @@ -99,6 +99,7 @@ export const NavigationDrawerSectionForObjectMetadataItems = ({ return ( Date: Thu, 17 Oct 2024 16:25:24 +0200 Subject: [PATCH 02/10] Add filter to search resolver --- .../graphql-query-search-resolver.service.ts | 33 ++++++++++++++++--- .../workspace-resolvers-builder.interface.ts | 5 ++- .../utils/get-resolver-args.util.ts | 4 +++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts index cfd570281187..ff2b9097f807 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts @@ -4,12 +4,14 @@ import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/int import { Record as IRecord, OrderByDirection, + RecordFilter, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { SearchResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; @@ -24,11 +26,19 @@ export class GraphqlQuerySearchResolverService private readonly featureFlagService: FeatureFlagService, ) {} - async resolve( + async resolve< + ObjectRecord extends IRecord = IRecord, + Filter extends RecordFilter = RecordFilter, + >( args: SearchResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise> { - const { authContext, objectMetadataItem, objectMetadataMap } = options; + const { + authContext, + objectMetadataItem, + objectMetadataMapItem, + objectMetadataMap, + } = options; const repository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( @@ -54,9 +64,22 @@ export class GraphqlQuerySearchResolverService const limit = args?.limit ?? QUERY_MAX_RECORDS; - const resultsWithTsVector = (await repository - .createQueryBuilder() - .where(`"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTerms)`, { + const queryBuilder = repository.createQueryBuilder( + objectMetadataItem.nameSingular, + ); + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMapItem.fields, + objectMetadataMap, + ); + + const queryBuilderWithFilter = graphqlQueryParser.applyFilterToBuilder( + queryBuilder, + objectMetadataMapItem.nameSingular, + args.filter ?? ({} as Filter), + ); + + const resultsWithTsVector = (await queryBuilderWithFilter + .andWhere(`"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTerms)`, { searchTerms, }) .orderBy( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts index 219b185c451e..69bc97777b10 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface.ts @@ -48,8 +48,11 @@ export interface FindDuplicatesResolverArgs< data?: Data[]; } -export interface SearchResolverArgs { +export interface SearchResolverArgs< + Filter extends RecordFilter = RecordFilter, +> { searchInput?: string; + filter?: Filter; limit?: number; } 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 7e1755218730..b50047e62e2a 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 @@ -147,6 +147,10 @@ export const getResolverArgs = ( type: GraphQLInt, isNullable: true, }, + filter: { + kind: InputTypeDefinitionKind.Filter, + isNullable: true, + }, }; default: throw new Error(`Unknown resolver type: ${type}`); From 6d1f7b9cf5707aa538ecaf4c8f6e7a46c1fc5fca Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 17 Oct 2024 18:09:29 +0200 Subject: [PATCH 03/10] use useSearchRecords instead of FindMany in FilteredSearchQuery --- ...bjectMetadataItemsRelationPickerEffect.tsx | 29 -------- .../object-record/hooks/useSearchRecords.ts | 10 ++- .../components/RelationFromManyFieldInput.tsx | 2 - .../RecordDetailRelationSection.tsx | 2 - .../SingleEntitySelectMenuItemsWithSearch.tsx | 4 -- .../hooks/useRelationPickerEntitiesOptions.ts | 17 ++--- .../utils/generateSearchRecordsQuery.ts | 8 ++- .../hooks/useFilteredSearchEntityQuery.ts | 69 +++---------------- .../graphql-query-search-resolver.service.ts | 15 ++-- 9 files changed, 39 insertions(+), 117 deletions(-) delete mode 100644 packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx deleted file mode 100644 index 1d2d1ecd74b9..000000000000 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsRelationPickerEffect.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useEffect } from 'react'; - -import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; - -export const ObjectMetadataItemsRelationPickerEffect = ({ - relationPickerScopeId, -}: { - relationPickerScopeId?: string; -} = {}) => { - const { setSearchQuery } = useRelationPicker({ relationPickerScopeId }); - - const computeFilterFields = (relationPickerType: string) => { - if (relationPickerType === 'company') { - return ['name']; - } - - if (['workspaceMember', 'person'].includes(relationPickerType)) { - return ['name.firstName', 'name.lastName']; - } - - return ['name']; - }; - - useEffect(() => { - setSearchQuery({ computeFilterFields }); - }, [setSearchQuery]); - - return <>; -}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useSearchRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useSearchRecords.ts index 175f84554f19..7c1f90162597 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useSearchRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useSearchRecords.ts @@ -13,10 +13,11 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useQuery, WatchQueryFetchPolicy } from '@apollo/client'; import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; +import { isDefined } from '~/utils/isDefined'; import { logError } from '~/utils/logError'; export type UseSearchRecordsParams = ObjectMetadataItemIdentifier & - RecordGqlOperationVariables & { + Pick & { onError?: (error?: Error) => void; skip?: boolean; recordGqlFields?: RecordGqlOperationGqlRecordFields; @@ -29,6 +30,7 @@ export const useSearchRecords = ({ searchInput, limit, skip, + filter, recordGqlFields, fetchPolicy, }: UseSearchRecordsParams) => { @@ -45,10 +47,14 @@ export const useSearchRecords = ({ const { data, loading, error, previousData } = useQuery(searchRecordsQuery, { skip: - skip || !objectMetadataItem || !currentWorkspaceMember || !searchInput, + skip || + !objectMetadataItem || + !currentWorkspaceMember || + !isDefined(searchInput), variables: { search: searchInput, limit: limit, + filter: filter, }, fetchPolicy: fetchPolicy, onError: (error) => { diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput.tsx index be1cba38161b..f0cbf686dfc2 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/RelationFromManyFieldInput.tsx @@ -1,6 +1,5 @@ import { useContext } from 'react'; -import { ObjectMetadataItemsRelationPickerEffect } from '@/object-metadata/components/ObjectMetadataItemsRelationPickerEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { RelationFromManyFieldInputMultiRecordsEffect } from '@/object-record/record-field/meta-types/input/components/RelationFromManyFieldInputMultiRecordsEffect'; @@ -54,7 +53,6 @@ export const RelationFromManyFieldInput = ({ return ( <> - ) : ( <> - - gql` - query Search${capitalize(objectMetadataItem.namePlural)}($search: String, $limit: Int) { - ${getSearchRecordsQueryResponseField(objectMetadataItem.namePlural)}(searchInput: $search, limit: $limit){ + query Search${capitalize(objectMetadataItem.namePlural)}($search: String, $limit: Int, $filter: ${capitalize( + objectMetadataItem.nameSingular, + )}FilterInput) { + ${getSearchRecordsQueryResponseField(objectMetadataItem.namePlural)}(searchInput: $search, limit: $limit, filter: $filter){ edges { node ${mapObjectMetadataToGraphQLQuery({ objectMetadataItems, diff --git a/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts b/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts index 06ba43f92e60..9d6973385230 100644 --- a/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts +++ b/packages/twenty-front/src/modules/search/hooks/useFilteredSearchEntityQuery.ts @@ -1,39 +1,26 @@ -import { isNonEmptyString } from '@sniptt/guards'; - import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier'; import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit'; -import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter'; -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { useSearchRecords } from '@/object-record/hooks/useSearchRecords'; import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; -import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables'; -import { OrderBy } from '@/types/OrderBy'; -import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields'; import { isDefined } from '~/utils/isDefined'; -type SearchFilter = { fieldNames: string[]; filter: string | number }; - // TODO: use this for all search queries, because we need selectedEntities and entitiesToSelect each time we want to search // Filtered entities to select are export const useFilteredSearchEntityQuery = ({ - orderByField, - filters, - sortOrder = 'AscNullsLast', selectedIds, limit, excludeRecordIds = [], objectNameSingular, + searchFilter, }: { - orderByField: string; - filters: SearchFilter[]; - sortOrder?: OrderBy; selectedIds: string[]; limit?: number; excludeRecordIds?: string[]; objectNameSingular: string; + searchFilter?: string; }): EntitiesForMultipleEntitySelect => { const { mapToObjectRecordIdentifier } = useMapToObjectRecordIdentifier({ objectNameSingular, @@ -46,55 +33,21 @@ export const useFilteredSearchEntityQuery = ({ const selectedIdsFilter = { id: { in: selectedIds } }; const { loading: selectedRecordsLoading, records: selectedRecords } = - useFindManyRecords({ + useSearchRecords({ objectNameSingular, filter: selectedIdsFilter, - orderBy: [{ [orderByField]: sortOrder }], skip: !selectedIds.length, + searchInput: searchFilter, }); - const searchFilters = filters.map(({ fieldNames, filter }) => { - if (!isNonEmptyString(filter)) { - return undefined; - } - - const formattedFilters = fieldNames.reduce( - (previousValue: RecordGqlOperationFilter[], fieldName) => { - const [parentFieldName, subFieldName] = fieldName.split('.'); - - if (isNonEmptyString(subFieldName)) { - // Composite field - return [ - ...previousValue, - ...generateILikeFiltersForCompositeFields(filter, parentFieldName, [ - subFieldName, - ]), - ]; - } - - return [ - ...previousValue, - { - [fieldName]: { - ilike: `%${filter}%`, - }, - }, - ]; - }, - [], - ); - - return makeOrFilterVariables(formattedFilters); - }); - const { loading: filteredSelectedRecordsLoading, records: filteredSelectedRecords, - } = useFindManyRecords({ + } = useSearchRecords({ objectNameSingular, - filter: makeAndFilterVariables([...searchFilters, selectedIdsFilter]), - orderBy: [{ [orderByField]: sortOrder }], + filter: selectedIdsFilter, skip: !selectedIds.length, + searchInput: searchFilter, }); const notFilterIds = [...selectedIds, ...excludeRecordIds]; @@ -102,11 +55,11 @@ export const useFilteredSearchEntityQuery = ({ ? { not: { id: { in: notFilterIds } } } : undefined; const { loading: recordsToSelectLoading, records: recordsToSelect } = - useFindManyRecords({ + useSearchRecords({ objectNameSingular, - filter: makeAndFilterVariables([...searchFilters, notFilter]), + filter: notFilter, limit: limit ?? DEFAULT_SEARCH_REQUEST_LIMIT, - orderBy: [{ [orderByField]: sortOrder }], + searchInput: searchFilter, }); return { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts index ff2b9097f807..378dfff97fc4 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts @@ -16,6 +16,7 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { isDefined } from 'src/utils/is-defined'; @Injectable() export class GraphqlQuerySearchResolverService @@ -49,7 +50,7 @@ export class GraphqlQuerySearchResolverService const typeORMObjectRecordsParser = new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); - if (!args.searchInput) { + if (!isDefined(args.searchInput)) { return typeORMObjectRecordsParser.createConnection({ objectRecords: [], objectName: objectMetadataItem.nameSingular, @@ -79,9 +80,12 @@ export class GraphqlQuerySearchResolverService ); const resultsWithTsVector = (await queryBuilderWithFilter - .andWhere(`"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTerms)`, { - searchTerms, - }) + .andWhere( + searchTerms === '' + ? `"${SEARCH_VECTOR_FIELD.name}" IS NOT NULL` + : `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTerms)`, + searchTerms === '' ? {} : { searchTerms }, + ) .orderBy( `ts_rank("${SEARCH_VECTOR_FIELD.name}", to_tsquery(:searchTerms))`, 'DESC', @@ -107,6 +111,9 @@ export class GraphqlQuerySearchResolverService } private formatSearchTerms(searchTerm: string) { + if (searchTerm === '') { + return ''; + } const words = searchTerm.trim().split(/\s+/); const formattedWords = words.map((word) => { const escapedWord = word.replace(/[\\:'&|!()]/g, '\\$&'); From 45f26a38f0c9083ee725dc26b354db72a135e40f Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 17 Oct 2024 18:52:13 +0200 Subject: [PATCH 04/10] fix tests --- .../hooks/__tests__/useFilteredSearchEntityQuery.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx b/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx index 263b70decf0f..6b2409af45a3 100644 --- a/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx +++ b/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx @@ -80,13 +80,11 @@ describe('useFilteredSearchEntityQuery', () => { setMetadataItems(generatedMockObjectMetadataItems); return useFilteredSearchEntityQuery({ - orderByField: 'name', - filters: [{ fieldNames: ['name'], filter: 'Entity' }], - sortOrder: 'AscNullsLast', selectedIds: ['1'], limit: 10, excludeRecordIds: ['2'], objectNameSingular: 'person', + searchFilter: 'Entity', }); }, { wrapper: Wrapper }, From 3fa3d90b522a47fda0de596c5ea23a7e37d9a507 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 18 Oct 2024 11:03:20 +0200 Subject: [PATCH 05/10] fix test --- .../twenty-front/src/testing/graphqlMocks.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/twenty-front/src/testing/graphqlMocks.ts b/packages/twenty-front/src/testing/graphqlMocks.ts index 1cb7c4b3aef5..3a7f9fdf2183 100644 --- a/packages/twenty-front/src/testing/graphqlMocks.ts +++ b/packages/twenty-front/src/testing/graphqlMocks.ts @@ -113,6 +113,52 @@ export const graphqlMocks = { }, }); }), + graphql.query('SearchWorkspaceMembers', () => { + return HttpResponse.json({ + data: { + workspaceMembers: { + edges: mockWorkspaceMembers.map((member) => ({ + node: { + ...member, + messageParticipants: { + edges: [], + __typename: 'MessageParticipantConnection', + }, + authoredAttachments: { + edges: [], + __typename: 'AttachmentConnection', + }, + authoredComments: { + edges: [], + __typename: 'CommentConnection', + }, + accountOwnerForCompanies: { + edges: [], + __typename: 'CompanyConnection', + }, + authoredActivities: { + edges: [], + __typename: 'ActivityConnection', + }, + favorites: { + edges: [], + __typename: 'FavoriteConnection', + }, + connectedAccounts: { + edges: [], + __typename: 'ConnectedAccountConnection', + }, + assignedActivities: { + edges: [], + __typename: 'ActivityConnection', + }, + }, + cursor: null, + })), + }, + }, + }); + }), graphql.query('FindManyViewFields', ({ variables }) => { const viewId = variables.filter.view.eq; From cdcead663ada5ecf1171d9f6b65a2e7523c2ed86 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Fri, 18 Oct 2024 14:26:58 +0200 Subject: [PATCH 06/10] fix test --- packages/twenty-front/src/testing/graphqlMocks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/twenty-front/src/testing/graphqlMocks.ts b/packages/twenty-front/src/testing/graphqlMocks.ts index 3a7f9fdf2183..d634890a5f9b 100644 --- a/packages/twenty-front/src/testing/graphqlMocks.ts +++ b/packages/twenty-front/src/testing/graphqlMocks.ts @@ -116,7 +116,7 @@ export const graphqlMocks = { graphql.query('SearchWorkspaceMembers', () => { return HttpResponse.json({ data: { - workspaceMembers: { + searchWorkspaceMembers: { edges: mockWorkspaceMembers.map((member) => ({ node: { ...member, From 79a576a5c49b7b4afdff42f527464df54693bed6 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Mon, 21 Oct 2024 12:29:10 +0200 Subject: [PATCH 07/10] Use search in multi search pickers --- .../useGenerateCombinedSearchRecordsQuery.ts | 96 +++++++++++++++++++ .../utils/isObjectMetadataItemSearchable.ts | 17 ++++ ...atchesSearchFilterAndSelectedItemsQuery.ts | 75 ++++++++------- ...archMatchesSearchFilterAndToSelectQuery.ts | 30 ++---- .../hooks/useSearchFilterPerMetadataItem.ts | 67 ------------- 5 files changed, 162 insertions(+), 123 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery.ts create mode 100644 packages/twenty-front/src/modules/object-record/multiple-objects/hooks/utils/isObjectMetadataItemSearchable.ts delete mode 100644 packages/twenty-front/src/modules/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem.ts diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery.ts new file mode 100644 index 000000000000..581cbc2e5742 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery.ts @@ -0,0 +1,96 @@ +import { gql } from '@apollo/client'; +import { isUndefined } from '@sniptt/guards'; +import { useRecoilValue } from 'recoil'; + +import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; +import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; +import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; +import { isObjectMetadataItemSearchable } from '@/object-record/multiple-objects/hooks/utils/isObjectMetadataItemSearchable'; +import { getSearchRecordsQueryResponseField } from '@/object-record/utils/getSearchRecordsQueryResponseField'; +import { isNonEmptyArray } from '~/utils/isNonEmptyArray'; +import { capitalize } from '~/utils/string/capitalize'; + +export const useGenerateCombinedSearchRecordsQuery = ({ + operationSignatures, +}: { + operationSignatures: RecordGqlOperationSignature[]; +}) => { + const objectMetadataItems = useRecoilValue(objectMetadataItemsState); + + if (!isNonEmptyArray(operationSignatures)) { + return null; + } + + const filterPerMetadataItemArray = operationSignatures + .map( + ({ objectNameSingular }) => + `$filter${capitalize(objectNameSingular)}: ${capitalize( + objectNameSingular, + )}FilterInput`, + ) + .join(', '); + + const limitPerMetadataItemArray = operationSignatures + .map( + ({ objectNameSingular }) => + `$limit${capitalize(objectNameSingular)}: Int`, + ) + .join(', '); + + const queryKeyWithObjectMetadataItemArray = operationSignatures.map( + (queryKey) => { + const objectMetadataItem = objectMetadataItems.find( + (objectMetadataItem) => + objectMetadataItem.nameSingular === queryKey.objectNameSingular, + ); + + if (isUndefined(objectMetadataItem)) { + throw new Error( + `Object metadata item not found for object name singular: ${queryKey.objectNameSingular}`, + ); + } + + return { ...queryKey, objectMetadataItem }; + }, + ); + + const filteredQueryKeyWithObjectMetadataItemArray = + queryKeyWithObjectMetadataItemArray.filter(({ objectMetadataItem }) => + isObjectMetadataItemSearchable(objectMetadataItem), + ); + + return gql` + query CombinedSearchRecords( + ${filterPerMetadataItemArray}, + ${limitPerMetadataItemArray}, + $search: String, + ) { + ${filteredQueryKeyWithObjectMetadataItemArray + .map( + ({ objectMetadataItem, fields }) => + `${getSearchRecordsQueryResponseField(objectMetadataItem.namePlural)}(filter: $filter${capitalize( + objectMetadataItem.nameSingular, + )}, + limit: $limit${capitalize(objectMetadataItem.nameSingular)}, + searchInput: $search + ){ + edges { + node ${mapObjectMetadataToGraphQLQuery({ + objectMetadataItems: objectMetadataItems, + objectMetadataItem, + recordGqlFields: + fields ?? + generateDepthOneRecordGqlFields({ + objectMetadataItem, + }), + })} + cursor + } + totalCount + }`, + ) + .join('\n')} + } + `; +}; diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/utils/isObjectMetadataItemSearchable.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/utils/isObjectMetadataItemSearchable.ts new file mode 100644 index 000000000000..21bb1b2510e4 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/utils/isObjectMetadataItemSearchable.ts @@ -0,0 +1,17 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +const SEARCHABLE_STANDARD_OBJECTS_NAMES_PLURAL = [ + 'companies', + 'people', + 'opportunities', +]; +export const isObjectMetadataItemSearchable = ( + objectMetadataItem: ObjectMetadataItem, +) => { + return ( + objectMetadataItem.isCustom || + SEARCHABLE_STANDARD_OBJECTS_NAMES_PLURAL.includes( + objectMetadataItem.namePlural, + ) + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts index d42b2338b315..8bbcbe2d6466 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts @@ -5,17 +5,32 @@ import { useRecoilValue } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; +import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery'; import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; import { MultiObjectRecordQueryResult, useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray, } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; -import { useOrderByFieldPerMetadataItem } from '@/object-record/relation-picker/hooks/useOrderByFieldPerMetadataItem'; -import { useSearchFilterPerMetadataItem } from '@/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem'; +import { useMemo } from 'react'; import { isDefined } from '~/utils/isDefined'; import { capitalize } from '~/utils/string/capitalize'; +export const formatSearchResults = ( + searchResults: MultiObjectRecordQueryResult | undefined, +): MultiObjectRecordQueryResult => { + if (!searchResults) { + return {}; + } + + return Object.entries(searchResults).reduce((acc, [key, value]) => { + let newKey = key.replace(/^search/, ''); + newKey = newKey.charAt(0).toLowerCase() + newKey.slice(1); + acc[newKey] = value; + return acc; + }, {} as MultiObjectRecordQueryResult); +}; + export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({ selectedObjectRecordIds, searchFilterValue, @@ -27,18 +42,14 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({ }) => { const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - const { searchFilterPerMetadataItemNameSingular } = - useSearchFilterPerMetadataItem({ - objectMetadataItems, - searchFilterValue, - }); - - const objectMetadataItemsUsedInSelectedIdsQuery = objectMetadataItems.filter( - ({ nameSingular }) => { - return selectedObjectRecordIds.some(({ objectNameSingular }) => { - return objectNameSingular === nameSingular; - }); - }, + const objectMetadataItemsUsedInSelectedIdsQuery = useMemo( + () => + objectMetadataItems.filter(({ nameSingular }) => { + return selectedObjectRecordIds.some(({ objectNameSingular }) => { + return objectNameSingular === nameSingular; + }); + }), + [objectMetadataItems, selectedObjectRecordIds], ); const selectedAndMatchesSearchFilterTextFilterPerMetadataItem = @@ -53,31 +64,18 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({ if (!isNonEmptyArray(selectedIds)) return null; - const searchFilter = - searchFilterPerMetadataItemNameSingular[nameSingular] ?? {}; return [ `filter${capitalize(nameSingular)}`, { - and: [ - { - ...searchFilter, - }, - { - id: { - in: selectedIds, - }, - }, - ], + id: { + in: selectedIds, + }, }, ]; }) .filter(isDefined), ); - const { orderByFieldPerMetadataItem } = useOrderByFieldPerMetadataItem({ - objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery, - }); - const { limitPerMetadataItem } = useLimitPerMetadataItem({ objectMetadataItems: objectMetadataItemsUsedInSelectedIdsQuery, limit, @@ -93,15 +91,25 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({ ), }); + const multiSelectSearchQueryForSelectedIds = + useGenerateCombinedSearchRecordsQuery({ + operationSignatures: objectMetadataItemsUsedInSelectedIdsQuery.map( + (objectMetadataItem) => ({ + objectNameSingular: objectMetadataItem.nameSingular, + variables: {}, + }), + ), + }); + const { loading: selectedAndMatchesSearchFilterObjectRecordsLoading, data: selectedAndMatchesSearchFilterObjectRecordsQueryResult, } = useQuery( - multiSelectQueryForSelectedIds ?? EMPTY_QUERY, + multiSelectSearchQueryForSelectedIds ?? EMPTY_QUERY, { variables: { + search: searchFilterValue, ...selectedAndMatchesSearchFilterTextFilterPerMetadataItem, - ...orderByFieldPerMetadataItem, ...limitPerMetadataItem, }, skip: !isDefined(multiSelectQueryForSelectedIds), @@ -111,8 +119,9 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({ const { objectRecordForSelectArray: selectedAndMatchesSearchFilterObjectRecords, } = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ - multiObjectRecordsQueryResult: + multiObjectRecordsQueryResult: formatSearchResults( selectedAndMatchesSearchFilterObjectRecordsQueryResult, + ), }); return { diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts index 607eef1806de..b94fb0f99c1b 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts @@ -4,15 +4,14 @@ import { useRecoilValue } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; -import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; +import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery'; import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; import { MultiObjectRecordQueryResult, useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray, } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; -import { useOrderByFieldPerMetadataItem } from '@/object-record/relation-picker/hooks/useOrderByFieldPerMetadataItem'; -import { useSearchFilterPerMetadataItem } from '@/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem'; +import { formatSearchResults } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery'; import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; import { isDefined } from '~/utils/isDefined'; import { capitalize } from '~/utils/string/capitalize'; @@ -38,12 +37,6 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular); }); - const { searchFilterPerMetadataItemNameSingular } = - useSearchFilterPerMetadataItem({ - objectMetadataItems: selectableObjectMetadataItems, - searchFilterValue, - }); - const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem = Object.fromEntries( selectableObjectMetadataItems @@ -65,29 +58,19 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ ? { not: { id: { in: excludedIdsUnion } } } : undefined; - const searchFilters = [ - searchFilterPerMetadataItemNameSingular[nameSingular], - excludedIdsFilter, - ]; - return [ `filter${capitalize(nameSingular)}`, - makeAndFilterVariables(searchFilters), + makeAndFilterVariables([excludedIdsFilter]), ]; }) .filter(isDefined), ); - - const { orderByFieldPerMetadataItem } = useOrderByFieldPerMetadataItem({ - objectMetadataItems: selectableObjectMetadataItems, - }); - const { limitPerMetadataItem } = useLimitPerMetadataItem({ objectMetadataItems: selectableObjectMetadataItems, limit, }); - const multiSelectQuery = useGenerateCombinedFindManyRecordsQuery({ + const multiSelectQuery = useGenerateCombinedSearchRecordsQuery({ operationSignatures: selectableObjectMetadataItems.map( (objectMetadataItem) => ({ objectNameSingular: objectMetadataItem.nameSingular, @@ -101,8 +84,8 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ data: toSelectAndMatchesSearchFilterObjectRecordsQueryResult, } = useQuery(multiSelectQuery ?? EMPTY_QUERY, { variables: { + search: searchFilterValue, ...objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem, - ...orderByFieldPerMetadataItem, ...limitPerMetadataItem, }, skip: !isDefined(multiSelectQuery), @@ -111,8 +94,9 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ const { objectRecordForSelectArray: toSelectAndMatchesSearchFilterObjectRecords, } = useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray({ - multiObjectRecordsQueryResult: + multiObjectRecordsQueryResult: formatSearchResults( toSelectAndMatchesSearchFilterObjectRecordsQueryResult, + ), }); return { diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem.ts deleted file mode 100644 index a4822dea14a6..000000000000 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useSearchFilterPerMetadataItem.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { isNonEmptyString } from '@sniptt/guards'; - -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; -import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter'; -import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables'; -import { FieldMetadataType } from '~/generated/graphql'; -import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields'; -import { isDefined } from '~/utils/isDefined'; - -export const useSearchFilterPerMetadataItem = ({ - objectMetadataItems, - searchFilterValue, -}: { - objectMetadataItems: ObjectMetadataItem[]; - searchFilterValue: string; -}) => { - const searchFilterPerMetadataItemNameSingular = - Object.fromEntries( - objectMetadataItems - .map((objectMetadataItem) => { - if (searchFilterValue === '') return null; - - const labelIdentifierFieldMetadataItem = - getLabelIdentifierFieldMetadataItem(objectMetadataItem); - - let searchFilter: RecordGqlOperationFilter = {}; - - if (isDefined(labelIdentifierFieldMetadataItem)) { - switch (labelIdentifierFieldMetadataItem.type) { - case FieldMetadataType.FullName: { - if (isNonEmptyString(searchFilterValue)) { - const compositeFilter = makeOrFilterVariables( - generateILikeFiltersForCompositeFields( - searchFilterValue, - labelIdentifierFieldMetadataItem.name, - ['firstName', 'lastName'], - ), - ); - - if (isDefined(compositeFilter)) { - searchFilter = compositeFilter; - } - } - break; - } - default: { - if (isNonEmptyString(searchFilterValue)) { - searchFilter = { - [labelIdentifierFieldMetadataItem.name]: { - ilike: `%${searchFilterValue}%`, - }, - }; - } - } - } - } - - return [objectMetadataItem.nameSingular, searchFilter] as const; - }) - .filter(isDefined), - ); - - return { - searchFilterPerMetadataItemNameSingular, - }; -}; From e3aea54e31e73c475976cb835af31565d0d53d63 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Mon, 21 Oct 2024 14:55:03 +0200 Subject: [PATCH 08/10] replace multiSelectQueryForSelectedIds with multiSelectSearchQueryForSelectedIds --- ...earchMatchesSearchFilterAndSelectedItemsQuery.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts index 8bbcbe2d6466..b69ef1f40c6d 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery.ts @@ -4,7 +4,6 @@ import { useRecoilValue } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { EMPTY_QUERY } from '@/object-record/constants/EmptyQuery'; -import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; import { useGenerateCombinedSearchRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery'; import { useLimitPerMetadataItem } from '@/object-record/relation-picker/hooks/useLimitPerMetadataItem'; import { @@ -81,16 +80,6 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({ limit, }); - const multiSelectQueryForSelectedIds = - useGenerateCombinedFindManyRecordsQuery({ - operationSignatures: objectMetadataItemsUsedInSelectedIdsQuery.map( - (objectMetadataItem) => ({ - objectNameSingular: objectMetadataItem.nameSingular, - variables: {}, - }), - ), - }); - const multiSelectSearchQueryForSelectedIds = useGenerateCombinedSearchRecordsQuery({ operationSignatures: objectMetadataItemsUsedInSelectedIdsQuery.map( @@ -112,7 +101,7 @@ export const useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery = ({ ...selectedAndMatchesSearchFilterTextFilterPerMetadataItem, ...limitPerMetadataItem, }, - skip: !isDefined(multiSelectQueryForSelectedIds), + skip: !isDefined(multiSelectSearchQueryForSelectedIds), }, ); From 8fa9fc5d7f5dfdb662cbc29bdc85989e8b22408c Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Tue, 22 Oct 2024 10:30:37 +0200 Subject: [PATCH 09/10] Fix util linter error --- .../hooks/useGenerateCombinedSearchRecordsQuery.ts | 2 +- .../hooks => }/utils/isObjectMetadataItemSearchable.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/twenty-front/src/modules/object-record/{multiple-objects/hooks => }/utils/isObjectMetadataItemSearchable.ts (100%) diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery.ts index 581cbc2e5742..fc7725c3ae90 100644 --- a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/useGenerateCombinedSearchRecordsQuery.ts @@ -6,8 +6,8 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; import { RecordGqlOperationSignature } from '@/object-record/graphql/types/RecordGqlOperationSignature'; import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; -import { isObjectMetadataItemSearchable } from '@/object-record/multiple-objects/hooks/utils/isObjectMetadataItemSearchable'; import { getSearchRecordsQueryResponseField } from '@/object-record/utils/getSearchRecordsQueryResponseField'; +import { isObjectMetadataItemSearchable } from '@/object-record/utils/isObjectMetadataItemSearchable'; import { isNonEmptyArray } from '~/utils/isNonEmptyArray'; import { capitalize } from '~/utils/string/capitalize'; diff --git a/packages/twenty-front/src/modules/object-record/multiple-objects/hooks/utils/isObjectMetadataItemSearchable.ts b/packages/twenty-front/src/modules/object-record/utils/isObjectMetadataItemSearchable.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/multiple-objects/hooks/utils/isObjectMetadataItemSearchable.ts rename to packages/twenty-front/src/modules/object-record/utils/isObjectMetadataItemSearchable.ts From aae71fb9b790da27ddeda37146e929b3cf9e50b7 Mon Sep 17 00:00:00 2001 From: Marie Stoppa Date: Thu, 24 Oct 2024 13:15:22 +0200 Subject: [PATCH 10/10] rename to isObjectMetadataItemSearchableInCombinedRequest --- ...SearchMatchesSearchFilterAndToSelectQuery.ts | 6 +++++- ...ctMetadataItemSearchableInCombinedRequest.ts | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 packages/twenty-front/src/modules/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest.ts diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts index b94fb0f99c1b..c3150cd44ca8 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndToSelectQuery.ts @@ -12,6 +12,7 @@ import { } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { SelectedObjectRecordId } from '@/object-record/relation-picker/hooks/useMultiObjectSearch'; import { formatSearchResults } from '@/object-record/relation-picker/hooks/useMultiObjectSearchMatchesSearchFilterAndSelectedItemsQuery'; +import { isObjectMetadataItemSearchableInCombinedRequest } from '@/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest'; import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables'; import { isDefined } from '~/utils/isDefined'; import { capitalize } from '~/utils/string/capitalize'; @@ -35,7 +36,10 @@ export const useMultiObjectSearchMatchesSearchFilterAndToSelectQuery = ({ .filter(({ isSystem, isRemote }) => !isSystem && !isRemote) .filter(({ nameSingular }) => { return !excludedObjects?.includes(nameSingular as CoreObjectNameSingular); - }); + }) + .filter((object) => + isObjectMetadataItemSearchableInCombinedRequest(object), + ); const objectRecordsToSelectAndMatchesSearchFilterTextFilterPerMetadataItem = Object.fromEntries( diff --git a/packages/twenty-front/src/modules/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest.ts b/packages/twenty-front/src/modules/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest.ts new file mode 100644 index 000000000000..7b16a87ecbb5 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/utils/isObjectMetadataItemSearchableInCombinedRequest.ts @@ -0,0 +1,17 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +const SEARCHABLE_STANDARD_OBJECTS_IN_COMBINED_REQUEST_NAMES_PLURAL = [ + 'companies', + 'people', + 'opportunities', +]; +export const isObjectMetadataItemSearchableInCombinedRequest = ( + objectMetadataItem: ObjectMetadataItem, +) => { + return ( + objectMetadataItem.isCustom || + SEARCHABLE_STANDARD_OBJECTS_IN_COMBINED_REQUEST_NAMES_PLURAL.includes( + objectMetadataItem.namePlural, + ) + ); +};